FreeRTOS 소프트웨어 타이머 활용 가이드

마이크로컨트롤러 개발에서 타이머는 필수적인 요소입니다. 일반적으로 우리가 접하는 타이머는 마이크로컨트롤러 칩 내부에 내장된 하드웨어 타이머로, 정밀한 시간 측정과 다양한 기능을 제공합니다. 그러나 FreeRTOS와 같은 실시간 운영체제(RTOS) 환경에서는 소프트웨어 기반의 타이머도 활용할 수 있습니다. 본 문서에서는 FreeRTOS의 소프트웨어 타이머의 기본 개념, 설정 방법, 그리고 주요 API 함수들을 상세히 다룹니다.

1. FreeRTOS 소프트웨어 타이머 소개

FreeRTOS의 소프트웨어 타이머는 하드웨어 타이머와 달리 RTOS의 시스템 틱(tick)을 기반으로 동작하는 순수 소프트웨어적인 구현체입니다. 하드웨어 타이머만큼의 초정밀성은 없지만, 특정 간격으로 콜백 함수를 실행해야 하는 다양한 애플리케이션 시나리오에 적합합니다. 소프트웨어 타이머는 사용이 간편하고, 하드웨어 자원 제약 없이 필요한 만큼 생성할 수 있다는 장점이 있습니다.

1.1 동작 원리

사용자는 소프트웨어 타이머 생성 시 원하는 타임아웃 주기와 실행될 콜백 함수를 지정합니다. 타이머가 성공적으로 생성 및 시작되면, FreeRTOS는 시스템 틱을 기반으로 시간을 계수하다가 설정된 타임아웃 주기에 도달하면 해당 콜백 함수를 호출합니다. 타이머는 필요에 따라 한 번만 실행되는 원샷(one-shot) 모드 또는 주기적으로 반복 실행되는 오토리로드(auto-reload) 모드로 설정할 수 있습니다.

  • 소프트웨어 타이머의 개수는 시스템의 힙 메모리가 허용하는 한도 내에서 자유롭게 설정할 수 있습니다.
  • FreeRTOS에서 소프트웨어 타이머 기능은 선택 사항이며, 사용하려면 FreeRTOSConfig.h 파일에서 configUSE_TIMERS 매크로를 1로 설정해야 합니다.

1.2 소프트웨어 타이머 서비스 태스크

FreeRTOS 스케줄러(vTaskStartScheduler())가 시작될 때, 유휴 태스크(idle task)와 함께 소프트웨어 타이머 서비스 태스크(timer service task)가 자동으로 생성됩니다. 이 태스크는 모든 소프트웨어 타이머의 관리와 처리를 담당합니다.

void vTaskStartScheduler( void )
{
    // ... 기타 초기화 코드 생략 ...

#if ( configUSE_TIMERS == 1 )
    {
        // configUSE_TIMERS가 1로 설정된 경우
        // 소프트웨어 타이머 서비스 태스크 생성 함수 호출
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask(); 
        }
    }
#endif /* configUSE_TIMERS */

    // ... 스케줄러 시작 코드 생략 ...
}

생성된 모든 소프트웨어 타이머는 궁극적으로 이 서비스 태스크 내에서 처리됩니다. 서비스 태스크는 타이머의 타임아웃을 감지하고 해당 콜백 함수를 실행하며, 타이머 관련 명령 큐에 들어온 메시지를 처리하는 역할도 수행합니다.

1.3 타이머 명령 큐

FreeRTOS는 타이머 서비스 태스크와 다른 태스크 또는 인터럽트 간의 통신을 위해 전용 명령 큐(command queue)를 사용합니다. 대부분의 소프트웨어 타이머 API 함수는 이 명령 큐에 특정 명령을 전송하여 타이머 서비스 태스크에게 동작을 요청합니다. 이 큐는 FreeRTOS 내부용으로, 사용자가 직접 접근할 수는 없으며 오직 제공된 API 함수를 통해서만 상호작용할 수 있습니다.

1.4 소프트웨어 타이머의 상태

소프트웨어 타이머는 크게 두 가지 상태를 가집니다.

  1. 휴면(Dormant) 상태: 타이머가 생성된 직후의 초기 상태입니다. 타이머 핸들은 유효하지만, 아직 카운트가 시작되지 않아 타임아웃 콜백 함수가 실행되지 않습니다.
  2. 실행(Running) 상태: 타이머가 시작되어 활성화된 상태입니다. 타이머는 주기적으로 시간을 계수하며, 타임아웃 시 콜백 함수가 실행됩니다.

상태 전이 방식:

  • 원샷 타이머: 타임아웃 콜백 함수가 한 번 실행된 후 휴면 상태로 돌아갑니다.
  • 오토리로드 타이머: 타임아웃 콜백 함수가 실행된 후에도 계속 실행 상태를 유지하며 다음 타임아웃을 기다립니다.

1.5 타이머 리셋

타이머가 타임아웃되기 전에 반복적으로 리셋(재설정) 작업을 수행하면, 콜백 함수가 실행되는 것을 계속 지연시킬 수 있습니다. 이는 "워치독(watchdog)"과 유사한 방식으로 특정 태스크의 정상 작동을 감시하는 데 사용될 수 있습니다. 단, 의도치 않은 지연을 피하려면 타이머 리셋 시점을 신중하게 고려해야 합니다.

1.6 소프트웨어 타이머 구조체

FreeRTOS 소프트웨어 타이머는 내부적으로 xTIMER 구조체를 통해 관리됩니다. 주요 멤버는 다음과 같습니다:

typedef struct
{
    const char *pcTimerName;                /* 타이머의 이름 */
    ListItem_t xTimerListItem;              /* 타이머를 리스트에 연결하기 위한 리스트 항목 */
    TickType_t xTimerPeriodInTicks;         /* 타이머의 주기 (틱 단위) */
    void *pvTimerID;                        /* 타이머에 할당된 ID (사용자 정의 데이터) */
    TimerCallbackFunction_t pxCallbackFunction; /* 타임아웃 시 호출될 콜백 함수 포인터 */
#if ( configUSE_TRACE_FACILITY == 1 )
    UBaseType_t uxTimerNumber;              /* 디버깅을 위한 타이머 번호 */
#endif
    uint8_t ucStatus;                       /* 타이머의 현재 상태 */
} xTIMER;

2. 소프트웨어 타이머 설정

소프트웨어 타이머를 사용하기 위해서는 FreeRTOSConfig.h 파일에 다음 매크로들을 적절히 설정해야 합니다.

  1. configUSE_TIMERS: 소프트웨어 타이머 기능을 활성화하려면 이 값을 1로 설정해야 합니다.
  2. configTIMER_TASK_PRIORITY: 소프트웨어 타이머 서비스 태스크의 우선순위를 정의합니다. 이 값은 0부터 (configMAX_PRIORITY - 1) 사이여야 합니다. 일반적으로 높은 우선순위를 할당하여 타이머 콜백이 신속하게 처리되도록 합니다.
  3. configTIMER_QUEUE_LENGTH: 타이머 명령 큐의 길이를 정의합니다. 이 값은 0보다 커야 타이머 기능이 정상적으로 동작합니다.
  4. configTIMER_TASK_STACK_DEPTH: 소프트웨어 타이머 서비스 태스크의 스택 크기를 정의합니다. 모든 타이머 콜백 함수가 이 태스크의 컨텍스트에서 실행되므로, 스택 오버플로우를 방지하기 위해 충분한 크기를 할당해야 합니다.

3. 소프트웨어 타이머 API 함수

FreeRTOS는 timers.ctimers.h 파일에 소프트웨어 타이머를 제어하기 위한 다양한 API 함수를 제공합니다.

표 1: 주요 소프트웨어 타이머 제어 함수

함수 설명
xTimerCreate() 동적 메모리 할당 방식으로 소프트웨어 타이머 생성
xTimerCreateStatic() 정적 메모리 할당 방식으로 소프트웨어 타이머 생성
xTimerStart() 소프트웨어 타이머 시작 (실행 상태로 전환)
xTimerStartFromISR() ISR 내에서 소프트웨어 타이머 시작
xTimerStop() 소프트웨어 타이머 정지 (휴면 상태로 전환)
xTimerStopFromISR() ISR 내에서 소프트웨어 타이머 정지
xTimerReset() 소프트웨어 타이머 재설정 (카운트 리셋, 휴면 시 시작)
xTimerResetFromISR() ISR 내에서 소프트웨어 타이머 재설정
xTimerChangePeriod() 소프트웨어 타이머의 주기 변경
xTimerChangePeriodFromISR() ISR 내에서 소프트웨어 타이머의 주기 변경
xTimerDelete() 소프트웨어 타이머 삭제

표 2: 소프트웨어 타이머 정보 조회 및 특수 기능 함수

함수 설명
xTimerIsTimerActive() 타이머가 실행 상태인지 휴면 상태인지 확인
pvTimerGetTimerID() 타이머에 할당된 사용자 정의 ID 반환
vTimerSetReloadMode() 타이머의 모드(원샷/오토리로드) 변경
vTimerSetTimerID() 타이머의 사용자 정의 ID 변경
xTimerPendFunctionCall() 일반 함수를 타이머 서비스 태스크에서 실행하도록 요청
xTimerPendFunctionCallFromISR() ISR 내에서 일반 함수를 타이머 서비스 태스크에서 실행하도록 요청
pcTimerGetName() 타이머의 이름 반환
xTimerGetPeriod() 타이머의 주기(틱 단위) 반환
xTimerGetExpiryTime() 타이머의 다음 타임아웃 예정 시간 반환
xTimerGetReloadMode() 타이머의 모드(원샷/오토리로드) 확인
uxTimerGetReloadMode() (이전 버전 호환) 타이머의 모드 확인

3.1 xTimerCreate()xTimerCreateStatic()

이 함수들은 소프트웨어 타이머를 생성하고 핸들을 반환합니다. xTimerCreate()는 힙 메모리에서 동적으로 타이머를 할당하는 반면, xTimerCreateStatic()은 사용자가 제공한 메모리 버퍼를 사용합니다. 동적 생성 함수 프로토타입은 다음과 같습니다:

TimerHandle_t xTimerCreate( const char * const timerName,
                            const TickType_t periodTicks,
                            const UBaseType_t autoReload,
                            void * const timerId,
                            TimerCallbackFunction_t callbackFunction );
  • timerName: 타이머의 이름 (디버깅용).
  • periodTicks: 타이머의 주기 (FreeRTOS 틱 단위).
  • autoReload: pdTRUE로 설정하면 오토리로드 타이머(주기적), pdFALSE로 설정하면 원샷 타이머(1회성).
  • timerId: 타이머에 할당할 사용자 정의 ID 또는 데이터를 저장할 포인터.
  • callbackFunction: 타이머 타임아웃 시 호출될 콜백 함수 포인터.

타이머 콜백 함수의 프로토타입은 다음과 같습니다:

void TimerCallbackFunction( TimerHandle_t xTimer );

3.2 xTimerStart()xTimerStartFromISR()

생성된 타이머를 휴면 상태에서 실행 상태로 전환하여 카운팅을 시작합니다.

BaseType_t xTimerStart( TimerHandle_t xTimer,
                            TickType_t blockTime );
  • xTimer: 시작할 타이머의 핸들.
  • blockTime: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

ISR 버전인 xTimerStartFromISR()은 인터럽트 서비스 루틴에서 호출하며, 태스크 스위치 필요 여부를 나타내는 pxHigherPriorityTaskWoken 파라미터를 사용합니다.

3.3 xTimerStop()xTimerStopFromISR()

실행 중인 타이머를 정지시키고 휴면 상태로 전환합니다.

BaseType_t xTimerStop( TimerHandle_t xTimer,
                       TickType_t blockTime );
  • xTimer: 정지할 타이머의 핸들.
  • blockTime: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

3.4 xTimerReset()xTimerResetFromISR()

타이머의 카운트를 초기화하고 다시 시작합니다. 휴면 상태의 타이머에 대해 호출하면 xTimerStart()와 동일하게 동작합니다.

BaseType_t xTimerReset( TimerHandle_t xTimer,
                        TickType_t blockTime );
  • xTimer: 재설정할 타이머의 핸들.
  • blockTime: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

3.5 xTimerChangePeriod()xTimerChangePeriodFromISR()

타이머의 주기를 변경합니다. 타이머의 현재 상태와 관계없이 주기를 변경할 수 있으며, 변경 후 타이머는 실행 상태로 전환됩니다.

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                               TickType_t newPeriodTicks,
                               TickType_t blockTime );
  • xTimer: 주기를 변경할 타이머의 핸들.
  • newPeriodTicks: 새로운 주기 (FreeRTOS 틱 단위).
  • blockTime: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

3.6 xTimerDelete()

소프트웨어 타이머를 삭제하고 할당된 자원을 해제합니다.

BaseType_t xTimerDelete( TimerHandle_t xTimer,
                         TickType_t blockTime );
  • xTimer: 삭제할 타이머의 핸들.
  • blockTime: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

3.7 xTimerIsTimerActive()

타이머가 현재 실행 상태인지 여부를 확인합니다.

BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer );
  • xTimer: 상태를 확인할 타이머의 핸들.
  • 반환값: pdTRUE (실행 중), pdFALSE (휴면 또는 정지).

3.8 pvTimerGetTimerID()

타이머 생성 시 할당된 사용자 정의 ID를 반환합니다.

void *pvTimerGetTimerID( TimerHandle_t xTimer );
  • xTimer: ID를 얻을 타이머의 핸들.

3.9 vTimerSetReloadMode()

타이머의 재시작 모드를 원샷 또는 오토리로드로 변경합니다.

void vTimerSetReloadMode( TimerHandle_t xTimer,
                           const UBaseType_t autoReload );
  • xTimer: 모드를 변경할 타이머의 핸들.
  • autoReload: pdTRUE (오토리로드), pdFALSE (원샷).

3.10 vTimerSetTimerID()

타이머의 사용자 정의 ID를 변경합니다.

void vTimerSetTimerID( TimerHandle_t xTimer, void *newID );
  • xTimer: ID를 설정할 타이머의 핸들.
  • newID: 새로 설정할 ID 포인터.

3.11 xTimerPendFunctionCall()xTimerPendFunctionCallFromISR()

다른 함수를 타이머 서비스 태스크(RTOS 데몬 태스크)의 컨텍스트에서 실행하도록 요청합니다. 이 함수는 호출 즉시 실행되지 않고, 타이머 서비스 태스크의 큐에 추가되어 해당 태스크가 실행될 때 처리됩니다.

BaseType_t xTimerPendFunctionCall(
                            PendedFunction_t funcToExecute,
                            void *param1,
                            uint32_t param2,
                            TickType_t ticksToWait );
  • funcToExecute: 실행을 요청할 함수 포인터 (PendedFunction_t 타입).
  • param1: 요청된 함수의 첫 번째 인자.
  • param2: 요청된 함수의 두 번째 인자.
  • ticksToWait: 명령 큐에 명령을 보내기 위한 최대 대기 시간.

요청되는 함수의 프로토타입은 다음과 같아야 합니다:

void MyPendedFunction( void * paramA, uint32_t paramB );

xTimerPendFunctionCallFromISR() 함수는 인터럽트 루틴 내에서 사용되어, 시간이 오래 걸리거나 비결정적인 작업을 인터럽트 외부로 미루어 인터럽트 응답성을 향상시키는 데 유용합니다.

간단한 사용 예시:

// 태스크 핸들 정의
TaskHandle_t myAppTaskHandle;
// 요청될 함수 선언
void printMessageFromDaemon(void * messagePtr, uint32_t messageID);

// FreeRTOS 초기화 및 태스크 생성
void setupFreeRTOS(void)
{
    taskENTER_CRITICAL(); // 임계 영역 진입
    xTaskCreate((TaskFunction_t)myApplicationTask,
                "MyAppTask",
                128, // 스택 크기
                NULL,
                1, // 우선순위
                &myAppTaskHandle);
    taskEXIT_CRITICAL(); // 임계 영역 종료
    vTaskStartScheduler(); // 스케줄러 시작
}

// 애플리케이션 태스크 함수
void myApplicationTask(void *pvParameters)
{
    while(1)
    {
        // 1초마다 타이머 서비스 태스크에 함수 실행 요청
        xTimerPendFunctionCall(printMessageFromDaemon, (void *)"Hello from timer daemon!", 100, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(1000)); // 1초 대기
    }
}

// 타이머 서비스 태스크에 의해 실행될 함수
void printMessageFromDaemon(void * messagePtr, uint32_t messageID) {
    char* msg = (char*) messagePtr;
    printf("Daemon executed: \"%s\" with ID: %lu\n", msg, messageID);
}

위 코드에서는 myApplicationTask가 1초마다 printMessageFromDaemon 함수를 타이머 서비스 태스크에서 실행하도록 요청합니다. 결과적으로 시리얼 포트에는 1초마다 메시지가 출력됩니다.

3.12 pcTimerGetName()

타이머의 이름을 문자열로 반환합니다.

const char * pcTimerGetName( TimerHandle_t xTimer );
  • xTimer: 이름을 얻을 타이머의 핸들.

3.13 xTimerGetPeriod()

타이머의 주기(틱 단위)를 반환합니다.

TickType_t xTimerGetPeriod( TimerHandle_t xTimer );
  • xTimer: 주기를 얻을 타이머의 핸들.

3.14 xTimerGetExpiryTime()

타이머가 다음번에 타임아웃될 시스템 틱 시간을 반환합니다.

TickType_t xTimerGetExpiryTime( TimerHandle_t xTimer );
  • xTimer: 다음 타임아웃 시간을 얻을 타이머의 핸들.

사용 예시:

static void processTimerEvent( TimerHandle_t activeTimer )
{
    TickType_t remainingTicks;

    // 현재 틱 카운트와 다음 만료 시간의 차이를 계산하여 남은 시간을 얻는다.
    remainingTicks = xTimerGetExpiryTime( activeTimer ) - xTaskGetTickCount();
    // ... 남은 시간을 활용한 로직 ...
}

3.15 xTimerGetReloadMode()uxTimerGetReloadMode()

타이머의 재시작 모드(원샷/오토리로드)를 확인합니다.

BaseType_t xTimerGetReloadMode( TimerHandle_t xTimer );
  • xTimer: 모드를 확인할 타이머의 핸들.
  • 반환값: pdTRUE (오토리로드), pdFALSE (원샷).

uxTimerGetReloadMode()는 이전 버전과의 호환성을 위해 존재하며, 새로운 애플리케이션에서는 xTimerGetReloadMode() 사용이 권장됩니다.

4. 소프트웨어 타이머 실습 예제

4.1. 실습 내용

STM32F103RCT6 보드에서 FreeRTOS를 실행하며 버튼 입력을 통해 소프트웨어 타이머를 제어하는 예제를 구현합니다. 구체적인 요구사항은 다음과 같습니다:

  • 원샷 타이머 하나와 주기적(오토리로드) 타이머 하나를 생성합니다.
  • 태스크 1: LED를 주기적으로 깜빡여 시스템의 정상 작동을 나타냅니다.
  • 태스크 2: 버튼 입력을 감지하여 소프트웨어 타이머를 시작, 정지, 또는 재설정합니다.
  • 각 동작 시 시리얼 포트를 통해 관련 정보를 출력합니다.

4.2 코드 구현

1. main_freertos.c (또는 유사한 FreeRTOS 진입점 파일)

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h" // 필요한 경우 세마포어 포함
#include "gpio.h"   // LED 및 버튼 GPIO 헤더
#include "key_handler.h" // 버튼 처리 로직 헤더
#include "stdio.h"  // printf 사용

// 태스크 정의
#define LED_TASK_PRIORITY       ( tskIDLE_PRIORITY + 1 )
#define LED_TASK_STACK_SIZE     128
TaskHandle_t            ledToggleTaskHandle;
void ledToggleTask(void *pvParameters);

#define BUTTON_SCAN_TASK_PRIORITY   ( tskIDLE_PRIORITY + 2 )
#define BUTTON_SCAN_TASK_STACK_SIZE 128
TaskHandle_t            buttonScanTaskHandle;
void buttonScanTask(void *pvParameters);

// 타이머 핸들 및 ID 정의
#define ONE_SHOT_TIMER_ID   ( 0x01 )
#define PERIODIC_TIMER_ID   ( 0x02 )

TimerHandle_t oneShotTimerHandle;
TimerHandle_t periodicTimerHandle;

// 타이머 콜백 함수 선언
void oneShotTimerCallback( TimerHandle_t xTimer );
void periodicTimerCallback( TimerHandle_t xTimer );

/**
 * @brief       FreeRTOS 애플리케이션 진입 함수
 */
void startFreeRTOS(void)
{
    taskENTER_CRITICAL(); // 임계 영역 진입

    // 원샷 타이머 생성: 500틱 (500ms) 후 1회 실행
    oneShotTimerHandle = xTimerCreate( "OneShotTimer",
                                pdMS_TO_TICKS(500),
                                pdFALSE, // 원샷 모드
                                (void *)ONE_SHOT_TIMER_ID,
                                oneShotTimerCallback );

    // 주기적 타이머 생성: 1000틱 (1000ms) 주기로 반복 실행
    periodicTimerHandle = xTimerCreate( "PeriodicTimer",
                                pdMS_TO_TICKS(1000),
                                pdTRUE, // 오토리로드 모드
                                (void *)PERIODIC_TIMER_ID,
                                periodicTimerCallback );

    // LED 토글 태스크 생성
    xTaskCreate((TaskFunction_t)ledToggleTask,
                "LED_Toggle",
                LED_TASK_STACK_SIZE,
                NULL,
                LED_TASK_PRIORITY,
                &ledToggleTaskHandle);

    // 버튼 스캔 태스크 생성
    xTaskCreate((TaskFunction_t)buttonScanTask,
                "Button_Scan",
                BUTTON_SCAN_TASK_STACK_SIZE,
                NULL,
                BUTTON_SCAN_TASK_PRIORITY,
                &buttonScanTaskHandle);

    taskEXIT_CRITICAL(); // 임계 영역 종료
    vTaskStartScheduler(); // 스케줄러 시작
}

/**
 * @brief       ledToggleTask: LED0을 토글하여 시스템 활성 상태를 나타냄
 */
void ledToggleTask(void *pvParameters)
{
    while(1)
    {
        HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin); // LED0 토글
        vTaskDelay(pdMS_TO_TICKS(500)); // 500ms 대기
    }
}

/**
 * @brief       buttonScanTask: 버튼 입력을 스캔하고 타이머를 제어
 */
void buttonScanTask(void *pvParameters)
{
    while(1)
    {
        // Key_Scan_Process는 사용자 정의 버튼 스캔 함수라고 가정
        // 예를 들어, Key_Scan_Process(BUTTON0, &onButton0Pressed) 와 같이 사용
        // 여기서는 직접 FreeRTOS 타이머 API 호출
        if (readButtonState(BUTTON0) == GPIO_PIN_RESET) // 버튼 0이 눌렸다면
        {
            vTaskDelay(pdMS_TO_TICKS(50)); // 디바운싱
            if (readButtonState(BUTTON0) == GPIO_PIN_RESET) {
                printf("Button 0 Pressed: Starting Timers\r\n");
                xTimerStart(oneShotTimerHandle, portMAX_DELAY);
                xTimerStart(periodicTimerHandle, portMAX_DELAY);
                while(readButtonState(BUTTON0) == GPIO_PIN_RESET); // 버튼 떼기를 기다림
            }
        }
        else if (readButtonState(BUTTON1) == GPIO_PIN_RESET) // 버튼 1이 눌렸다면
        {
            vTaskDelay(pdMS_TO_TICKS(50)); // 디바운싱
            if (readButtonState(BUTTON1) == GPIO_PIN_RESET) {
                printf("Button 1 Pressed: Stopping Timers\r\n");
                xTimerStop(oneShotTimerHandle, portMAX_DELAY);
                xTimerStop(periodicTimerHandle, portMAX_DELAY);
                while(readButtonState(BUTTON1) == GPIO_PIN_RESET);
            }
        }
        else if (readButtonState(BUTTON2) == GPIO_PIN_RESET) // 버튼 2가 눌렸다면
        {
            vTaskDelay(pdMS_TO_TICKS(50)); // 디바운싱
            if (readButtonState(BUTTON2) == GPIO_PIN_RESET) {
                printf("Button 2 Pressed: Resetting Timers\r\n");
                xTimerReset(oneShotTimerHandle, portMAX_DELAY);
                xTimerReset(periodicTimerHandle, portMAX_DELAY);
                while(readButtonState(BUTTON2) == GPIO_PIN_RESET);
            }
        }
        vTaskDelay(pdMS_TO_TICKS(10)); // 버튼 스캔 주기
    }
}

/**
 * @brief       oneShotTimerCallback: 원샷 타이머 타임아웃 시 호출될 콜백 함수
 */
void oneShotTimerCallback( TimerHandle_t xTimer )
{
    static uint16_t executionCount = 0;
    executionCount++;
    printf("One-Shot Timer Callback Executed: %d times\r\n", executionCount);
    // 이 타이머는 원샷이므로 한 번 실행 후 휴면 상태로 돌아갑니다.
}

/**
 * @brief       periodicTimerCallback: 주기적 타이머 타임아웃 시 호출될 콜백 함수
 */
void periodicTimerCallback( TimerHandle_t xTimer )
{
    static uint16_t executionCount = 0;
    executionCount++;
    printf("Periodic Timer Callback Executed: %d times\r\n", executionCount);
}

2. key_handler.h (버튼 GPIO 정의 및 읽기 함수)

// key_handler.h 예시
#ifndef __KEY_HANDLER_H
#define __KEY_HANDLER_H

#include "stm32f1xx_hal.h" // HAL 라이브러리 포함

// 사용자 보드에 맞는 버튼 GPIO 정의
#define BUTTON0_GPIO_Port   GPIOB
#define BUTTON0_Pin         GPIO_PIN_0

#define BUTTON1_GPIO_Port   GPIOB
#define BUTTON1_Pin         GPIO_PIN_1

#define BUTTON2_GPIO_Port   GPIOB
#define BUTTON2_Pin         GPIO_PIN_2

// 버튼 상태를 읽는 함수 (실제 HAL 함수에 매핑)
GPIO_PinState readButtonState(uint8_t buttonId) {
    switch(buttonId) {
        case 0: return HAL_GPIO_ReadPin(BUTTON0_GPIO_Port, BUTTON0_Pin);
        case 1: return HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin);
        case 2: return HAL_GPIO_ReadPin(BUTTON2_GPIO_Port, BUTTON2_Pin);
        default: return GPIO_PIN_SET; // 기본적으로 눌리지 않은 상태 반환
    }
}

#endif // __KEY_HANDLER_H

참고: 위의 key_handler.hbuttonScanTask 내의 readButtonState 함수는 간단한 예시이며, 실제 구현에서는 디바운싱 로직과 함께 더 견고한 버튼 스캔 라이브러리를 사용하는 것이 좋습니다.

태그: freertos 소프트웨어 타이머 RTOS 임베디드 시스템 STM32

8월 20일 09:27에 게시됨