Embedded System PWM

Wonho Kim·2025년 1월 1일

Embedded System

목록 보기
4/7

디지털과 아날로그

디지털 신호는 이산적인 값(0 or 1)로 정보를 표현하며 유한한 정밀도를 가지며 노이즈에 강하다. 따라서 데이터 처리/전송 등에 용이하다.
e.g. CD, 디지털 시계, 컴퓨터, 스마트폰

아날로그 신호는 연속적인 값으로 정보를 표현하며 자연계에서 일어나느 물리적인 양이 시간에 따라 연속적으로 변하는 것이다.
이론적으로는 무한한 정밀도를 가지며 노이즈에 취약하다.
e.g. LP 레코드, 아날로그 시계, 온도계

디지털 시스템은 아래와 같은 장점이 있다.

  • 내, 외부 잡음에 강함
  • 설계하기 용이함
  • 기능 구현의 유연성 & 개발기간 단축
  • 정확성과 정밀도가 높음
  • 소형화 & 저가격화

디지털 정보의 표현

디지털 시스템에서는 정보를 표현하기 위해 2진수 체계를 사용한다.

☃️ 디지털 정보의 표현 단위
1 nibble = 4 bit
1 byte = 8 bit
1 byte = 1 character
영어는 1 byte로 1문자, 한글은 2byte로 1문자 표현
1 word: 특정 CPU에서 취급하는 명령어나 데이터 길이에 해당하는 비트 수

☃️ 정논리와 부논리

펄스 파형

펄스파형은 LOW 상태와 HIGH 상태를 반복하는 전압레벨로 구성된다. 이상적인 주기 펄스는 2개의 에지(edge)로 구성되며, 상승 에지(rising edge)와 하강 에지(falling edge)가 있다.

물론 실제 펄스파형은 위의 그림과 같이 정확하게? 이산적일 수 없으며 아래와 같은 구조를 가진다.

  • 상승시간: tr = tr90 - tr10
  • 하강시간: tf = tf90 - tf10
  • 펄스 폭: tw

주파수와 주기와의 관계

Duty Cycle

ADC와 DAC

아날로그-디지털 변환 과정은 표본화, 양자화, 부호화이다.

ADC는 Analog-to-Digital Converter의 약어로 말 그대로 위의 과정을 거쳐 디지털 신호로 변환해 주는 장치를 의미한다.

DAC는 Digital-to-Analog Converter의 약어로 디지털 신호를 다시 아날로그 신호로 변환해주는 장치를 의미한다.

PWM

Pulse Width Modulation의 약어로 한 주기에 있는 HIGH 레벨과 LOW 레벨의 비율을 변화시킴으로써 아날로그 신호를 생성한 것과 같은 효과를 출력하는 방법이다.

위에서 배운 Duty Cycle을 늘리면 LED의 밝기가 밝아지고, 줄일수록 밝기가 어두워지는 것이다.

결론적으로 주파수를 빠르게 발생시킨 다음(보통 100Hz가 넘어가면 인간의 눈에는 LED의 깜빡거림을 인지하지 못함) duty cycle의 비율을 조정하여 LED의 밝기를 아날로그 신호처럼 조절할 수 있다는 것이 핵심이다.

PWM 사용설정

pinMode 함수에서 OUTPUT 대신 PWM_OUTPUT으로 설정하면 된다.

void pinMode (gpio, PWM_OUTPUT);

우리가 사용하는 라즈베리 파이에서는 GPIO12, GPIO18이 PWM0, GPIO13, GPIO19에 PWM1을 사용할 수 있도록 구성되어 있다.

pwmWrite 함수: PWM의 Duty를 조절하는 함수

void pwmWrite(int pin, int value);

pin: 핀 번호를 설정
value: 해당 PWM 핀의 Duty 값을 설정

  • 0 ~ 1024까지 가능

PWM 제어 예제 (Balanced Mode)

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

#define PWM0 18
#define PWM1 19

int pwmControl()
{
	int duty = 0;
    
    pinMode(PWM0, PWM_OUTPUT);
    
    while (1)
    {
		for (duty = 0; duty <= 1024; ++duty)
        {
        	pwmWrite(PWM0, duty);
            delay(2);
        }
    }
    return 0;
}

int main()
{
	printf("PWM Example with wiringPi\n");
    wiringPiSetupGpio();
    pwmControl();
    return 0;
}

LED의 밝기가 변하도록 수정

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

#define PWM0 18
#define PWM1 19

int pwmControl()
{
	int duty = 0;
    
    pinMode(PWM0, PWM_OUTPUT);
    
    while (1)
    {
		for (duty = 0; duty <= 1024; ++duty)
        {
        	pwmWrite(PWM0, duty);
            delay(2);
        }
        
        for (duty = 1024; duty >= 0; --duty)
        {
        	pwmWrite(PWM0, duty);
            delay(2);
        }
    }
    return 0;
}

int main()
{
	printf("PWM Example with wiringPi\n");
    wiringPiSetupGpio();
    pwmControl();
    return 0;
}

임의의 PWM 신호 생성

라즈베리 파이에서는 2가지의 PWM 생성 모드를 지원한다.

Balanced mode (default): PWM 주파수가 유동적으로 변화됨
Mark-Space mode: 전통적인 PWM 생성 방법
(PWM 범위, 주파수 등을 설정)

일정한 PWM 주파수를 생성하기 위해서는 Mark-Space 모드로 생성해야 한다.

pwmSetMode 함수: 출력 모드를 설정하는 함수

void pwmSetMode(int mode);

mode: 해당 PWM 핀의 모드를 설정

  • PWM_MODE_BAL: Balanced mode
  • PWM_MODE_MS: Mark-Space mode

pwmSetRange 함수: PWM의 범위 설정

void pwmSetRange(unsigned int range);

range: PWM의 범위 값

pwmSetClock 함수: PWM 주파수를 설정하기 위해 나눗수를 설정

void pwmSetClock(int divisor);

divisor: PWM의 주기를 설정하기 위해 기본 클럭의 나눗수를 설정

PWM 설정 예시

PWM은 19.2 MHz의 기본 주파수를 가지고 있다.

목표

  • PWM 주파수 (1/T)를 200 Hz로 설정
  • PWM 범위를 100으로 설정

따라서 pwmSetRange(100); pwmSetClock(960);으로 작성하면 200 Hz 주파수를 가진 PWM 파형이 생성되는 것이다.

PWM의 duty를 입력받아 조정하는 코드

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

#define PWM0 18
#define PWM1 19
#define PWM_VALUE 1024

int pwmControl()
{
	int duty = 0;
    
    pinMode(PWM1, PWM_OUTPUT);
    
    pwmSetMode(PWM_MODE_MS);
    pwmSetClock(96);
    pwmSetRange(200);
    
    while (1)
    {
    	printf("Enter duty cycle (0%~100%): ");
        scanf("%d", &duty);
        
        if (duty < 0 || duty > 100)
        {
        	printf("Invalid duty cycle.]\n");
            continue;
        }
        
        int pwmValue = (duty * PWM_VALUE) / 100;
        pwmWrite(PWM1, pwmValue);
    }
    return 0;
}

int main()
{
	printf("PWM duty Example with wiringPi\n");
    wiringPiSetupGpio();
    pwmControl();
    return 0;
}

두 개의 스위치를 사용하여 PWM의 밝기를 조절

스위치1 (GPIO 18)은 밝기 증가 스위치이며 20%씩 밝기가 증가한다.

스위치2 (GPIO 24)는 밝기 감소 스위치이며 20%씩 밝기가 감소한다.

LED (Source 방식)는 PWM1을 사용하며 주파수는 1 kHz, 범위는 200으로 설정한다.

PWM width 값이 실시간으로 출력되도록 하라.

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

#define PWM1 19      // LED 제어용 PWM 핀
#define SWITCH_INC 18 // 밝기 증가 스위치
#define SWITCH_DEC 24 // 밝기 감소 스위치
#define PWM_VALUE 1024
#define STEP 20      // 밝기 증감 단위 (20%)
#define MAX_DUTY 100 // 최대 밝기 100%
#define MIN_DUTY 0   // 최소 밝기 0%

int currentDuty = 0; // 현재 밝기 (0% ~ 100%)

void setup()
{
    // GPIO 핀 설정
    pinMode(PWM1, PWM_OUTPUT);
    pinMode(SWITCH_INC, INPUT);
    pinMode(SWITCH_DEC, INPUT);

    // PWM 설정 (1kHz 주파수)
    pwmSetMode(PWM_MODE_MS);
    pwmSetClock(96);
    pwmSetRange(200);

    // 초기 PWM 값 설정
    pwmWrite(PWM1, (currentDuty * PWM_VALUE) / 100);
}

void updatePWM(int duty)
{
    // PWM 값 업데이트
    int pwmValue = (duty * PWM_VALUE) / 100;
    pwmWrite(PWM1, pwmValue);

    // 현재 PWM 값 출력
    printf("Current PWM duty cycle: %d%%\n", duty);
}

void controlBrightness()
{
    while (1)
    {
        // 밝기 증가 스위치가 눌렸을 때
        if (digitalRead(SWITCH_INC) == LOW)
        {
            if (currentDuty + STEP <= MAX_DUTY)
            {
                currentDuty += STEP;
                updatePWM(currentDuty);
            }
            delay(300); // 버튼 디바운싱을 위한 지연
        }

        // 밝기 감소 스위치가 눌렸을 때
        if (digitalRead(SWITCH_DEC) == LOW)
        {
            if (currentDuty - STEP >= MIN_DUTY)
            {
                currentDuty -= STEP;
                updatePWM(currentDuty);
            }
            delay(300); // 버튼 디바운싱을 위한 지연
        }

        delay(50); // CPU 사용량을 줄이기 위한 작은 지연
    }
}

int main()
{
    printf("PWM Brightness Control with Switches\n");
    wiringPiSetupGpio(); // GPIO 핀 설정
    setup();             // 초기 설정
    controlBrightness();  // 밝기 제어
    return 0;
}

softPWM

wiringPi에서 제공해주는 소프트웨어적 기법의 PWM이다.

PWM 핀이 아닌 일반 GPIO도 소프트웨어적으로 PWM 신호를 생성할 수 있도록 도와준다.
(softPWM으로 활성화된 각 핀은 CPU 리소스의 0.5%를 점유함)

softPWM.h 헤더 파일을 참조하며 softPWM을 사용하기 위해서는 GPIO 핀 모드를 OUTPUT으로 설정한다.

pinmode(gpio, OUTPUT);

softPWMCreate 함수: PWM의 범위 설정

int softPWMCreate(int pin, int initialValue, int pwmRange)

pin: GPIO 핀 번호
initialValue: 초기 펄스 폭 (0 ~ pwmRange)
pwmRange: PWM 범위 (주기)
권장 설정 값

  • 기본 펄스 시간: 100us
  • Range: 100
  • PWM 주기: 100us x 100 = 10,000us = 10ms
  • 기본 PWM 주파수 = 100 Hz

PWM 주파수를 올리기 위해서는 펄스 시간을 낮추거나 Range를 낮추어야 하는데, 200 Hz가 넘어가면 이상작동한다고 알려져 있으므로 pwmRange 값은 50 이상으로 설정하는 것을 권장한다.

softPwmWrite 함수: PWM의 범위 설정

int softPwmWrite(int pin, int value)

pin: GPIO 핀 번호
value: PWM 출력 값 (0 ~ pwmRange)

softPWM 예제 코드

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

void softPwmControl(int gpio)
{
    pinMode(gpio, OUTPUT);
    softPwmCreate(gpio, 0, 100);
    softPwmWrite(gpio, 50);
}

int main(int argc, char** argv)
{
    int gno;
    if (argc < 2)
    {
        printf("Usage : %s GPIO_NO\n", argv[0]);
        return -1;
    }
    gno = atoi(argv[1]);
    wiringPiSetupGpio();
    softPwmControl(gno);
    while(1);
    return 0;
}

소프웨어적으로 PWM을 생성하기 때문에 내부적으로 GPIO를 thread로 제어한다.
따라서 컴파일 시 -lpthread 옵션을 추가해 주어야 한다.

~$ gcc -o test test.c -lwiringPi -lpthread

softPWM 활용하여 LED 밝기 변하는 코드

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

void softPwmControl(int gpio)
{
    pinMode(gpio, OUTPUT);
    softPwmCreate(gpio, 0, 100);
    
    while (TRUE)
    {
        for (int i = 0; i <= 100; i++)
        {
            softPwmWrite(gpio, i);
            delay(2);
        }
        
        for (int i = 100; i >= 0; i--)
        {
            softPwmWrite(gpio, i);
            delay(2);
        }
    }
}

int main(int argc, char** argv)
{
    int gno;
    if (argc < 2)
    {
        printf("Usage : %s GPIO_NO\n", argv[0]);
        return -1;
    }
    gno = atoi(argv[1]);
    wiringPiSetupGpio();
    softPwmControl(gno);
    return 0;
}
profile
새싹 백엔드 개발자

0개의 댓글