이번에는 타이머와 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 만큼의 값이 나오기 때문에 많은 데이터가 쌓여서 특정 시점을 잘 잡으면 사이클이 바뀌는 것을 확인할 수 있을 것이다. 

     

    아... 피곤하다..

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기