이번에는 타이머와 PWM을 이용하여 LED를 점멸 하고자 한다.
1. Timer를 이용한 LED 점멸
1.1. LED 연결
LED는 기존과 같이 10번에 연결되어있다. 하나로 하기엔 좀 밋밋하더면 11번에도 LED를 연결해주자.
1.2. 소스코드
소스코드는 다음과 같다.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"
// LED 핀 정의
#define LED_PIN_1 10 // GPIO 10번에 연결된 LED
#define LED_PIN_2 11 // GPIO 11번에 연결된 LED
// 타이머 핸들
TimerHandle_t xTimer1 = NULL;
TimerHandle_t xTimer2 = NULL;
// LED1번의 동작 처리를 위한 핸들을 만든다.
void led_toggle_1(TimerHandle_t xTimer)
{
static bool led_state = false;
led_state = !led_state;
gpio_set_level(LED_PIN_1, led_state); // LED 상태 변경
}
// LED2번의 동작 처리를 위한 핸들를 만든다.
void led_toggle_2(TimerHandle_t xTimer)
{
static bool led_state = false;
led_state = !led_state;
gpio_set_level(LED_PIN_2, led_state); // LED 상태 변경
}
void app_main(void)
{
// GPIO 10번과 11번을 출력으로 설정한다.
gpio_set_direction(LED_PIN_1, GPIO_MODE_OUTPUT);
gpio_set_direction(LED_PIN_2, GPIO_MODE_OUTPUT);
// 타이머 생성, 이름은 Timer1, 250ms 단위로 설정하고, 핸들러를 등록해준다.
xTimer1 = xTimerCreate("Timer1", pdMS_TO_TICKS(250), pdTRUE, (void*) 0, led_toggle_1);
if (xTimer1 == NULL) {
printf("Timer1 Create Failed!\n");
return;
}
// 타이머 생성, 이름은 Timer2, 500ms 단위로 설정하고, 핸들러를 등록해준다.
xTimer2 = xTimerCreate("Timer2", pdMS_TO_TICKS(500), pdTRUE, (void*) 0, led_toggle_2);
if (xTimer2 == NULL) {
printf("Timer2 Create Failed!\n");
return;
}
// 타이머를 시작한다.
xTimerStart(xTimer1, 0);
xTimerStart(xTimer2, 0);
}
1.3. 동작 타이밍
위의 소스를 그래프로 표현하면 아래와 같다. LED1은 250ms 단위로 On/Off가 되고, LED2는 500ms 단위로 On/Off 가 된다.
사실 좀더 정확하게 보려면 아날라이저나 오실로스코프 등으로 확인하는 것이 좋다.
1.4. 아날라이저를 통한 확인
필자가 전 회사 다닐때, 필요할 것같아서 사둔 즁국산 시그널 아날라이저가 있는데 그걸로 확인해 보도록 하겠다. 나름 성능이 괜찮아서...
- GPIO 10 | 250ms
아래의 Channel0은 GPIO 10번 이며, 옆의 길이를 보면 250ms 단위로 동작하고, 사이클은 500ms로 표시.
- GPIO 11 | 500ms
아래의 Channel1은 GPIO 11번 이며, 옆의 정보를 보면 500ms 단위로 동작하고, 사이클은 약 1000ms로 표시.
1.5. 결과 확인
아.. 역시 사진으로는 잘 안보인다. 아.. 잘안보이기는하는데, 왼쪽부터, 다꺼졋을때, 빨간색만 켜졌을 때, 그리고 둘다 켜졌을 때이다. 표도 잘 안난다. 심지어 노랭이만 들어온거는 여러장 찍었는데도 안보인다. 1sec, 2sec로 할껄 그랬다...
2. PWM을 이용한 LED 점멸
2.1. LED 연결
LED는 기존과 같이 10번에 연결되어있다.
2.2. 소스코드
소스코드는 다음과 같다. 문제는 ESP-IDF툴 때문인지 뭔지 잘 모르겠지만 LEDC_HIGH_SPEED_MODE를 선택하면 오류가 난다. 그래서 LED_LOW_SPEED_MODE를 선택해서 예제를 작성했다. 그런데 다른 노트북에서는 잘되는데... 이거 참 난감하다. 회사 PC 설치해서 또 비교해보고 내부 구조를 보던지 해야되겟다.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#define LED_PIN 10 // LED가 연결된 핀 번호
void app_main(void)
{
// LEDC PWM 설정
ledc_channel_config_t ledc_channel = {
.gpio_num = LED_PIN,
// .speed_mode = LEDC_HIGH_SPEED_MODE, // ESP32-S3 빌드시 오류가 발생되었다.
.speed_mode = LEDC_LOW_SPEED_MODE, // 저속모드는 정밀제어시 유리하다
.channel = LEDC_CHANNEL_0, // 채널 0 사용
.intr_type = LEDC_INTR_DISABLE, // 인터럽트 비활성화
.timer_sel = LEDC_TIMER_0, // 타이머 0 사용
.duty = 0, // 초기 듀티 사이클 0 (LED 밝기 0)
.hpoint = 0,
};
// PWM 타이머 설정
ledc_timer_config_t ledc_timer = {
// .speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0, // 타이머 0 사용
.duty_resolution = LEDC_TIMER_8_BIT, // 8비트 해상도 (0-255 범위)
.freq_hz = 5000, // 주파수 5kHz
.clk_cfg = LEDC_AUTO_CLK, // 자동 클럭 설정
};
// PWM 타이머와 채널 초기화
ledc_timer_config(&ledc_timer);
ledc_channel_config(&ledc_channel);
while(1) {
// 1초당안 255단계에 걸쳐 서서히 밝게 만든다.
for (int i = 0; i <= 255; i++) {
ledc_set_duty(ledc_channel.speed_mode, ledc_channel.channel, i); // 듀티 값을 설정한다
ledc_update_duty(ledc_channel.speed_mode, ledc_channel.channel); // 설정된 값을 적용한다.
//vTaskDelay(pdMS_TO_TICKS(4));
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
// 켜졌으니 꺼주자.
for (int i = 255; i >= 0; i--) {
ledc_set_duty(ledc_channel.speed_mode, ledc_channel.channel, i);
ledc_update_duty(ledc_channel.speed_mode, ledc_channel.channel);
//vTaskDelay(pdMS_TO_TICKS(4)); // 너무 빨리 밝아진다..
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
2.3. 동작 타이밍
밝기 설정에 따른 딜레이를 4로 하니 너무 빠른것 같다. 그리고 4ms로 해도 실제 시간은 1초가 더 소요된다. 간단하게 계산해보면 255 * 4ms = 1020ms가 된다. 따라서 정확하게 끊으려면 250으로 반복되도록 해야된다. 어쨋든 10ms로 딜레이를 설정하면 그럭저럭 볼만한데...., 어쨋거나 아래는 4ms로 했을 때의 그래프이다. 10ms로 하면 아무래도 시간이 더 소요될 수 밖에 없다.
2.4. 아날라이저를 통한 확인
우선 아래의 그래프에서 확인 가능한것은 Frequecy와 Period이다. 그리고 Duty Cycle을 확인할 수 있었다.
그러면 해당 소스가 맞는지.. 확인해보면 된다. 소스에서 프리퀀시를 5Khz로 설정했다. 정확히 아날라이저에서 5Khz로 표시되었고, Period는 주기 계산을 해보면 나오는데 T = 1/f, 1/5000 = 0.0002(s) = 200us이므로 주기도 맞다. 그리고 마지막으로 Cycle은 다음과 같이 계산된다.
- 측정주기 : 200us
- High가 유지된 시간 = 80us (200us-120us:Low)
- Cycle = (High Time / Period)*100 = (80us/200us) * 100 = 40%
- 1ms 동안high가 되는 회수 검증 = 1000us/200us = 5 (위의 그래프에 1ms당 5번씩 High가 표시됨을 알수있다.)
특정 클록을 유지하는게 아니라 가변이라서 시간에 따라서 측정시간에 따라 달라진다. 5khz 만큼의 값이 나오기 때문에 많은 데이터가 쌓여서 특정 시점을 잘 잡으면 사이클이 바뀌는 것을 확인할 수 있을 것이다.
아... 피곤하다..
최근댓글