termuni 2025. 3. 18. 18:21
#include <wiringPi.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main (void)
{
  int bright ;

  printf ("Raspberry Pi wiringPi PWM test program\n") ;

  if (wiringPiSetup () == -1)
    exit (1) ;

  pinMode (1, PWM_OUTPUT) ;

  for (;;)
  {
    for (bright = 0 ; bright < 1024 ; ++bright)
    {
      pwmWrite (1, bright) ;
      delay (1) ;
    }

    for (bright = 1023 ; bright >= 0 ; --bright)
    {
      pwmWrite (1, bright) ;
      delay (1) ;
    }
  }

  return 0 ;
}

해당 코드가 pwm의 예제 코드이며, 이 코드에서 다시 pinMode부터 분석해보려고 한다.

주요 분석 코드로 pinMode, pwmWrite 를 분석 할 예정이다.


1. pinMode(int, int)

사실, 이 코드에서 제일 헷갈리는 부분이다. 왜냐면..

if (모드가 Input일때)
 if()~
  else if ~
  else if ~
  else if ~
..

이런 식으로, 정말 헷갈리게 적혀있어서 어렵다!!

그래서, 동작하기에 필요한 부분을 다 빼고 조건문만 정리해보았더니 다음과 같다.


① 입력 관련

if (INPUT==mode  || PM_OFF==mode) {
    if (piRP1Model()) 
    {
        if (INPUT==mode){  ...  } 
        else  { //PM_OFF  ... }
    } 
    else {   //GPIO 세팅  }

    if (PM_OFF==mode && !usingGpioMem && pwm && gpioToPwmALT[pin]>0) 
    {
        //PWM pin -> reset
        if (channel>=0 && channel<=3 && piRP1Model()) { ... }
    }
}

② 출력 관련 (지난번에 봐서, 대강 넘어감)

else if (mode == OUTPUT) {...}

③ Soft_PWM(CPU 사용 PWM), Soft_Tone(소리출력용 주파수 등 설정용), PWM_Tone(하드웨어 PWM)

else if (mode == SOFT_PWM_OUTPUT) 
{
    softPwmCreate (origPin, 0, 100) ;
} 
else if (mode == SOFT_TONE_OUTPUT) 
{
    softToneCreate (origPin) ;
} 
else if (mode == PWM_TONE_OUTPUT)
{
    pinMode (origPin, PWM_OUTPUT) ;	// Call myself to enable PWM mode
    pwmSetMode (PWM_MODE_MS) ;
}

 


④ PWM

이때, RP1Model 함수에 따라, 라즈베리파이 5에 해당하는 함수를 확인한다.

라즈베리파이 5가 아닌 경우, 기존 방식대로 pwm을 설정해주게 된다!

else if (PWM_OUTPUT == mode || PWM_MS_OUTPUT == mode || PWM_BAL_OUTPUT == mode) 
{
    if (0 == alt) {  // Not a hardware capable PWM pin
        // 하드웨어 PWM이 지원되지 않는 핀이면 아무 동작도 안 함.
    }

    int channel = gpioToPwmPort[pin];

    if (piRP1Model()) {  // ✅ 라즈베리파이 RP1 칩셋 (라즈베리파이 5)인지 확인
        if (channel >= 0 && channel <= 3) {
            // PWM 채널 설정 (마크/스페이스 모드 사용)
            pwm[RP1_PWM0_CHAN_START + RP1_PWM0_CHAN_OFFSET * channel + RP1_PWM0_CHAN_CTRL] =
                (RP1_PWM_TRAIL_EDGE_MS | RP1_PWM_FIFO_POP_MASK);
            
            // PWM 글로벌 활성화
            unsigned int ctrl = pwm[RP1_PWM0_GLOBAL_CTRL];
            pwm[RP1_PWM0_GLOBAL_CTRL] = ctrl | (1 << channel) | RP1_PWM_CTRL_SETUPDATE;
            
            // GPIO 모드를 PWM 출력 모드로 변경
            pads[1 + pin] = RP1_PAD_DEFAULT_FROM9;  // 출력 활성화
            pinModeAlt(origPin, alt);  // PWM 모드로 설정
        }
    } 
    else {  // ✅ 라즈베리파이 4 이하 모델의 PWM 설정 방식
        // 해당 핀을 PWM 모드로 설정
        *(gpio + fSel) = (*(gpio + fSel) & ~(7 << shift)) | (alt << shift);
        delayMicroseconds(110);  // 클럭 안정화를 위해 대기

        if (PWM_OUTPUT == mode || PWM_BAL_OUTPUT == mode) {
            pwmSetMode(PWM_MODE_BAL);  // 균형 모드 (Balanced Mode, 기본값)
        } 
        else {
            pwmSetMode(PWM_MODE_MS);  // 마크/스페이스 모드 (Mark-Space Mode)
        }
    }

    if (PWM_OUTPUT == mode) {  // ✅ 기본 PWM 설정
        pwmSetRange(1024);  // 기본 PWM 범위를 1024로 설정
        pwmSetClock(32);    // 19.2MHz / 32 = 600kHz (기본 PWM 주파수 설정)
    }
}

⑤ GPIO_CLOCK

else if (mode == GPIO_CLOCK) { ... }

여기까지 해서, 우선 영역을 구분했다.

 

그렇다면, PWM 모드는 어떤 것들이 있는 것일까?

PWM_MODE_BAL (Balanced Mode, 기본값)

  • PWM 파형을 균등하게 배분하여 부드러운 출력 가능
  • 주파수 변화가 있을 때 신호 왜곡이 적음

PWM_MODE_MS (Mark-Space Mode)

  • 마크(ON)와 스페이스(OFF) 비율을 정밀하게 제어할 수 있음
  • 서보 모터 제어나 특정 하드웨어에서 요구하는 신호에 적합

그렇다면, PWM_TONE_OUTPUT과의 차이점은?

PWM_OUTPUT은 정확한 신호를 제공하는 하드웨어 PWM
PWM_TONE_OUTPUT
소프트웨어 방식으로 신호를 생성하는 기능이다.

 

구분 PWM_OUTPUT / PWM_MS_OUTPUT / PWM_BAL_OUTPUT PWM_TONE_OUTPUT
사용 방식 하드웨어 PWM을 사용 GPIO를 톤(주파수) 출력 모드로 설정
주요 용도 LED 밝기 조절, 모터 속도 제어 사운드 발생 (부저, 스피커 제어)
신호 형태 듀티 사이클 기반 특정 주파수의 신호 출력
정확성 하드웨어 PWM이므로 정확도가 높음 소프트웨어 방식이라 정확도가 낮을 수 있음
지원 핀 라즈베리파이에서 일부 GPIO 핀만 하드웨어 PWM 지원 모든 GPIO 핀에서 사용 가능
설정 방식 pwmWrite(pin, value); softToneWrite(pin, frequency);

최종 정리

구분설명

 

PWM_OUTPUT 기본 PWM (균형 모드)
PWM_MS_OUTPUT 마크/스페이스 PWM (서보 모터 등)
PWM_BAL_OUTPUT 균형 모드 PWM (라즈베리파이 기본 설정)
PWM_TONE_OUTPUT 주파수 생성 (소리 발생)

라즈베리파이 5에선...

라즈베리파이 5에서는 새로운 칩셋으로 인해, 새로운 레지스터로 수행해야 한다.

PWM을 제어하려면 RP1_PWM0_GLOBAL_CTRL 및 RP1_PWM0_CHAN_CTRL 등의 새로운 레지스터를 사용하게 되며, 주요 레지스터는 다음과 같다.

레지스터 설명
RP1_PWM0_GLOBAL_CTRL PWM 전체를 활성화하는 전역 제어 레지스터
RP1_PWM0_CHAN_CTRL 각 PWM 채널을 개별적으로 제어하는 레지스터
RP1_PWM0_CHAN_START PWM 신호 시작 지점 설정
RP1_PWM0_CHAN_OFFSET 각 PWM 채널 간의 오프셋
RP1_PAD_DEFAULT_FROM9 특정 GPIO 핀을 PWM 모드로 설정
RP1_PWM_CTRL_SETUPDATE PWM 설정을 즉시 반영하는 플래그
 +-----+-----+---------+------+---+---Pi 5---+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   -  | 0 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   -  | 0 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   -  | 0 |  7 || 8  | 0 |  -   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 0 |  -   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   -  | 0 | 11 || 12 | 0 |  -   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   -  | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   -  | 0 | 15 || 16 | 0 |  -   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 |  -   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   -  | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   -  | 0 | 21 || 22 | 0 |  -   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   -  | 0 | 23 || 24 | 0 |  -   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 0 |  -   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   -  | 0 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   -  | 0 | 31 || 32 | 0 |  -   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   -  | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   -  | 0 | 35 || 36 | 0 |  -   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   -  | 0 | 37 || 38 | 0 |  -   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 |  -   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 5---+---+------+---------+-----+-----+

이때 내 라즈베리파이의 gpio readall 결과는 위와 같고, 일반적으로 지원하는 핀은 다음과 같다.

PWM0 채널 0 GPIO 12 Pin 32
PWM0 채널 1 GPIO 13 Pin 33
PWM1 채널 0 GPIO 18 Pin 12
PWM1 채널 1 GPIO 19 Pin 35

2. pwmWrite(int, int)

void pwmWrite (int pin, int value)
{
	...
  if ((pin & PI_GPIO_MASK) == 0)		// On-Board Pin
  {
  	//대강 핀 모드에 맞게 핀 값 세팅
	
    if (piRP1Model()) {					//라베파 5인 경우
      if (channel>=0 && channel<=3) {
        unsigned int addr = RP1_PWM0_CHAN_START+RP1_PWM0_CHAN_OFFSET*channel+RP1_PWM0_CHAN_DUTY;
        pwm[addr] = value;				// ✅ RP1 PWM 채널의 듀티 사이클 설정
        readback = pwm[addr];
      }
    } 
    else {
      *(pwm + channel) = value ;		// ✅ 기존 BCM 방식으로 PWM 듀티 사이클 설정
      readback = *(pwm + channel);
    }
  }
  else									//확장 GPIO 설치 한 경우
  {
    if ((node = wiringPiFindNode (pin)) != NULL)
      node->pwmWrite (node, pin, value) ;
  }
}

 

라즈베리파이 4 이전(BCM2711)까지에서는 pwm + channel 주소에 직접 값을 쓰는 방식으로 PWM 설정하며,

 

라즈베리파이 5 이후부턴 기존 BCM 방식과 다르게, PWM 값을 전용 레지스터(RP1_PWM0_CHAN_DUTY)에 직접 기록하여 제어하고, PWM 설정 후, 값을 다시 읽어 확인 (readback = pwm[addr])

그렇다면 readback은 무엇일까?

 

  • *(pwm + channel) = value; → PWM 레지스터(PWM_DAT)에 값을 한 번 저장
  • readback = *(pwm + channel); → 저장된 값이 반영되었는지 확인
  • 이후에는 이 값이 하드웨어 PWM 회로에 의해 자동으로 사용

그렇다면 하드웨어 PWM 회로는 어떤 과정으로 동작하는거지? 싶어서 확인해보니 다음과 같다.

  1. PWM_CTL 레지스터에서 PWM 기능을 활성화
  2. PWM_RNG 레지스터에서 PWM 주기(범위)를 설정
  3. PWM_DAT 레지스터 값을 하드웨어 타이머가 지속적으로 참조
  4. PWM 신호는 타이머가 계속 동작하면서 자동으로 생성
  5. PWM_DAT 값이 변경되지 않는 한, PWM 신호는 변하지 않고 유지

하드웨어 동작 과정의 예시는 다음과 같다.

시간 (t) PWM 카운터 값 (0 ~ 1023) PWM_DAT 값 (512) 출력 상태 (GPIO 18)
t = 0 0 512 HIGH (ON)
t = 256 256 512 HIGH (ON)
t = 511 511 512 HIGH (ON)
t = 512 512 512 LOW (OFF)
t = 768 768 512 LOW (OFF)
t = 1023 1023 512 LOW (OFF)
t = 0 (다음 사이클 시작) 0 512 HIGH (ON)

이런식으로, pwm 카운터를 정해두고 pwm 데이터와 비교하며 출력상태를 조절하게 된다. 

그러면 어떻게 관리하는 것이고 어떻게 차이를 두는 것일까?

이건.. GPT에게 물어보았다.

            ┌──────────────────────────┐
            │       PWM Counter        │  (0 → 1 → 2 → ... → PWM_RNG)
            └────────────┬─────────────┘
                         │
                         ▼
            ┌──────────────────────────┐
            │      Comparator (>)      │  (현재 Counter 값과 PWM_DAT 값 비교)
            └────────────┬─────────────┘
                         │
                ┌────────┴────────┐
                │   Logic Gates   │   (AND, OR 등으로 출력 결정)
                └────────┬────────┘
                         │
                         ▼
                   PWM Output  (HIGH / LOW)

 

 

           ┌───────────────────────────┐
Counter ──▶│  비교기: (Counter < DAT)  │─── AND 게이트 ──▶ PWM 출력 (HIGH/LOW)
           └───────────────────────────┘

대강 이런 식으로 진행되는 것!


사실 집중이 크게 되지 않아서.. 이상하게 정리 된 부분도 있을 것 같다...

그러니, 만약 보고 이상하다면 알려주신다면 매우 감사!! ㅎㅎㅎ