CS 지식 정리/라즈베리파이-C언어

Wiring Pi 파헤치기 (1) 예제부터 차근차근 알아보기

termuni 2025. 3. 7. 22:41

https://github.com/WiringPi/WiringPi

 

GitHub - WiringPi/WiringPi: The arguably fastest GPIO Library for the Raspberry Pi

The arguably fastest GPIO Library for the Raspberry Pi - WiringPi/WiringPi

github.com

 

이 예제는 위 Git에 올라와있는 Wiring Pi에서 가져온 내용이다.

단순히 Wiring Pi를 쓰는것 부터 시작해서, 이 Pi를 어떻게 만들었는지까지 살펴볼 예정이다.


#include <stdio.h>
#include <wiringPi.h>

// LED Pin - wiringPi pin 0 is BCM_GPIO 17.

#define	LED	0

int main (void)
{
  printf ("Raspberry Pi blink\n") ;

  wiringPiSetup () ;
  pinMode (LED, OUTPUT) ;

  for (;;)
  {
    digitalWrite (LED, HIGH) ;	// On
    delay (500) ;		// mS
    digitalWrite (LED, LOW) ;	// Off
    delay (500) ;
  }
  return 0 ;
}

 

이 예제는 blink.c라는 이름을 가진 예제코드이다.

 

딱 봐도, 루프문을 돌면서 500ms마다 깜빡이는 예제 코드이며 한 눈에 보더라도 어떤것이 어떤 역할을 할 것인지 뻔히 보인다. 

 

그래도, 한번 차근차근 확인해보자.


1. wiringPiSetup()

wiringPi를 쓸 수 있도록 Setting하는 함수이며, 한 번 Setup 된다면 다시 중복 Setup되지 않는다. (wiringPiSetup이 TRUE인 경우 Return 0를 통해 멈추도록 설정)

실제 wiringPi.c 내부 코드 중 일부

참고로, 이 셋업을 통해 라즈베리파이의 버전, 모델명 등을 가져와서 세팅해준다.

이때, 별도 모델이 아닌 경우 

이와같이 설정될 것으로 보인다.

이를 통해 설정된 것은 GPIO뿐만 아닌,

 

PWM, Clock, Drive, 시스템 Timer 등이 결정된다.

이때 타이머와 관련하여, 설정이 이와 같다. 1MHz로 설정된다.

그리고 이 값들을 외부에서 사용할 것을 고려하여, 다음과 같이 저장해둔다.

 

이 정도이고, 만약 자신이 일반적인 라즈베리파이를 쓰지 않는 것 같다고 생각된다면 데이터시트와 wiring Pi를 점검하길 바란다.


2. pinMode(int, int)

이 함수가 쓰인 모습을 보면 pinMode(LED, OUTPUT)이다. 근데, 이 두 값 모두 #define으로 사전에 정의된 값이다.

그리고 LED의 경우 내가 LED를 꽂아놓은 핀으로 이름을 변경하고, OUTPUT은 <wiringPi.h>에 있기에 충분히 유추 가능한 값이다.

만약 입력을 받고 싶다면 INPUT으로 설정하면 되겠지만... 그 경우 해당 핀이 input이 가능한 핀인지, 그리고 그 input을 어떻게 받아올것인지 등 다양한 것을 고려 후 사용하길 바란다.

OUTPUT의 경우일 때 어떻게 되는지의 코드 내의 일부 모습.

이 중 else 부분이 gpio의 INPUT/OUTPUT을 설정하는 요소이다.
이와 관련하여 GPT의 힘을 빌어 알아보았다.


GPIO의 GPFSELn (GPIO Function Select Register)라는 레지스터가 있는데,

이 레지스터는 fSel = gpioToGPFSEL[pin]을 통해 그 의도를 엿볼 수 있다.

static uint8_t gpioToGPFSEL [] =
{
  0,0,0,0,0,0,0,0,0,0,
  1,1,1,1,1,1,1,1,1,1,
  2,2,2,2,2,2,2,2,2,2,
  3,3,3,3,3,3,3,3,3,3,
  4,4,4,4,4,4,4,4,4,4,
  5,5,5,5,5,5,5,5,5,5,
} ;

즉, GPFSEL0부터 GPFSEL5까지 있을 것으로 예상된다.

 

그리고, 추가로 추측하면 다음과 같을 것으로 예상된다.

GPFSEL 레지스터의 구조 (32비트)

비트 위치 31-30-29 28-27-26 ... 8-7-6 5-4-3 2-1-0

GPIO 핀 번호 GPIO 9 GPIO 8 ... GPIO 2 GPIO 1 GPIO 0
기능 값 (3비트) 000 000 ... 000 000 000

 

기능 3비트 값

입력 (INPUT) 000
출력 (OUTPUT) 001
대체 기능 (ALT0~ALT5) 100 ~ 111

-> 즉, GPIO의 현재 설정을 초기화하려면 해당 위치의 3비트(000~111)를 0으로 만들어야 함

*(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift));

 

1. shift = (GPIO 번호 % 10) * 3

  • 각 GPIO는 3비트씩 차지하므로 GPIO 번호에 따라 shift 값을 계산

2. 7 = 0b111 → 7 << shift

  • 7 = 0b111 (3비트 마스크)
  • 7 << shift → 해당 GPIO의 3비트만 1로 설정

3. ~(7 << shift) → 해당 GPIO의 3비트만 0으로 변경 (기존 값 제거)

  • ~(7 << shift)는 해당 GPIO의 3비트 위치를 0으로 초기화하는 역할

4. & ~(7 << shift) → 기존 값에서 해당 GPIO의 3비트만 0으로 변경

  • & 연산자로 특정 GPIO의 기존 설정을 제거하고 000(INPUT) 상태로 만듦
  • 즉, 특정 위치의 GPIO 3 비트만 변경하는 것!

다음 예제를 통해 그 동작을 예상할 수 있다.

초기 상태 (예제)

GPFSEL1 = 0b00000000000100000000000000000000  (GPIO 17이 OUTPUT)

코드 실행

*(gpio + 1) = (*(gpio + 1) & ~(7 << 21));  // GPIO 17을 INPUT(000)으로 설정

연산 과정

7 << 21 = 0b00000000011100000000000000000000  (GPIO 17의 3비트만 1)
~(7 << 21) = 0b11111111100011111111111111111111  (해당 3비트만 0)

기존 GPFSEL1 값과 AND 연산

0b00000000000100000000000000000000   (기존 값)
& 0b11111111100011111111111111111111  (마스크 적용)
--------------------------------------------------
  0b00000000000000000000000000000000   (GPIO 17이 INPUT)

 

반대로 OUT에 대해서 이 레지스터를 분석하면 아래와 같이 변화한다.

  1. GPFSEL1 = 0b00000000000000000000000000000000  (모든 GPIO가 INPUT)
  2. *(gpio + 1) = (*(gpio + 1) & ~(7 << 21)) | (1 << 21); (17번 GPIO를 OUTPUT으로 변경)
  3. GPFSEL1 = 0b00000000000100000000000000000000  (GPIO 17이 OUTPUT)

 

참고용으로, shift를 결정짓는 테이블은 위와 같이 생겼다.

왜 굳이 테이블을 사용하고 계산을 안 했나 생각해보았을 때, 계산보다 테이블에서 참조하는 것이 오히려 메모리 소비량이 적을 것 같다.


3. digitalWrite(int, int)

 

핀의 번호와 HIGH/LOW를 입력함으로서 해당 핀을 쓸지 말지 결정하는 요소이다.

 

여기에서 GPSET과 GPCLR를 사용하는데, 각각 총 64개씩 정의되어있다.

지난 요약에서 보면,

GPIO_SET / GPIO_CLR 분석 (왜 특정 핀을 지정하지 않는가?)

이라고 하면서 정리해둔 내용이 있다.

그 내용에서 나왔었던 세팅을 하기 위한 것들로 이해하면 될 것 같다.

그 핀 번호를 바탕으로 이렇게 특정 Pin을 On/Off로 바꾸기 위해 동작하고, 

해당 레지스터의 변화를 알아차리고 레지스터 구조에 따라 변화시키면서 GPIO의 ON/OFF를 동작시키게 된다.

 

그래서 

    digitalWrite (LED, HIGH) ;	// On

으로 On/Off를 통제할 수 있는 것이다.


4. delay(int)

 

아두이노에서도 자주 등장하는 delay 함수이다.

다만 의아했던 점은 wiringPi.c와 WiringPi/devLib/scrollPhat.c 둘 다 존재한다는 점. 다만 scrollPhat에서는 static으로 존재하였다.

 

이건 이해를 못 하겠어서, GPT의 도움을 빌렸다. 대강 확인해보니...

scrollphat이 사용하는 기기적 특성 때문이라고..

 

그리고 nanosleep은 찾아보니, C에서 제공하는 delay 관련 함수였다.

 

하지만.. 궁금해서 gpt에게 물어보았다.

 

- 하드웨어 타이머 + OS 스케줄링을 활용하여 CPU를 정밀하게 멈춤
- 시스템 타이머 인터럽트가 발생할 때까지 현재 프로세스를 대기 상태로 변경

 

대강 이런 원리라고 한다.

 

더 자세한 원리가 궁금하다면, 알아서 찾아보자.. 내 수준에서 정밀한 설명은 어려울 것 같다. 더 공부해야할듯


대강 오늘은 이렇게 4가지 함수()와 그 원리에 대해서 알아보았다.

wiringPiSetup ();
pinMode (PIN_NUMBER, OUTPUT or INPUT) ;
digitalWrite (PIN_NUMBER, HIGH or LOW) ;	// On or Off
delay (msec);

역시 남의 코드를 분석하는 것이 제일 즐거운 일인 것 같다.. ㅎ

다음에는 thread에 대해서 알아보려고 한다!