Firmware & RTOS/FreeRTOS & Real-time Scheduling

FreeRTOS 시작하기: CMSIS-RTOS v1 설정 및 태스크 구현 완벽 가이드

임베디드 친구 2025. 1. 10. 08:37
반응형

ARM Cortex-M 프로세서를 활용한 프로젝트에서 멀티태스킹을 구현할 때, CMSIS-RTOS v1은 하나의 표준 규격처럼 널리 사용됩니다. 베어메탈(Firmware) 환경에서 RTOS 환경으로 전환하는 과정에서 많은 개발자가 초기 설정에 어려움을 겪곤 합니다. 특히 시장 점유율이 가장 높은 FreeRTOS 커널 위에 CMSIS 인터페이스를 결합하는 구조는 코드의 이식성과 하드웨어 추상화를 위해 필수로 알아두어야 할 설계 방식입니다.

이번 글에서는 칩 제조사인 STMicroelectronics가 제공하는 통합 개발 환경인 STM32CubeIDE를 기준으로, GUI 설정 도구를 통해 FreeRTOS 환경을 구축하고 간단한 LED 토글 멀티태스크 코드를 빌드하여 구동하는 과정을 단계별로 자세히 알아보겠습니다.

핵심 요약 3줄

  • STM32CubeIDE 장점: 통합 Configuration Tool(IOC)을 활용하면 복잡한 FreeRTOS 소스코드 수동 추가 없이 클릭 몇 번으로 기본 커널 환경을 구성할 수 있습니다.
  • 추상화 계층 적용: Middleware 설정에서 CMSIS_V1 인터페이스를 활성화하여 FreeRTOS 네이티브 API가 아닌 표준 API 기반의 코드를 자동 생성합니다.
  • 실무 검증 필수: 멀티태스크 구현 시 SysTick 타이머 소스 분리와 스택 할당 오류 등 초기 세팅 단계에서 유의해야 할 필수 항목들이 존재합니다.

1. 개발 환경 및 필수 도구

효율적인 실습과 양산 프로젝트 진행을 위해 아래의 개발 툴체인이 기본적으로 준비되어 있어야 합니다.

구성 요소 권장 툴 및 패키지 비고
통합 개발 환경 (IDE) STM32CubeIDE ST 공식 무료 IDE (GCC 컴파일러 및 CubeMX 내장)
대체 개발 환경 (선택) Keil MDK (MDK-ARM) 상용 프로젝트에서 자주 쓰이며 디버깅 기능이 강력함
하드웨어 드라이버 STM32 MCU용 MCU Package STM32F4, G4, H7 등 사용 칩셋에 맞는 HAL 라이브러리
하드웨어 보드 STM32 Nucleo 또는 Discovery 보드 온보드 ST-LINK 디버거가 포함된 테스트 보드

 

2. STM32CubeIDE에서 FreeRTOS 설정하기

최근의 임베디드 개발은 수동으로 공식 홈페이지에서 커널 패키지를 다운로드하기보다 IDE 내부에 내장된 Device Configuration Tool(IOC)을 사용하는 것이 휠씬 빠르고 구성 오류가 적습니다.

2.1 Middleware 및 인터페이스 설정

  1. 프로젝트 내의 .ioc 파일을 더블 클릭하여 그래픽 설정 창을 엽니다.
  2. 좌측 카테고리 메뉴에서 Middleware > FREERTOS 항목을 선택합니다.
  3. 오른쪽 Mode 설정의 Interface 옵션 드롭다운 메뉴에서 CMSIS_V1을 선택합니다. 더 최신 규격인 v2가 존재하지만, 기존 오픈소스 라이브러리와의 호환성과 안정성 측면에서 여전히 v1이 실무 현장에서 자주 채택됩니다.

2.2 시스템 타이머 소스 변경 (중요 항목)

FreeRTOS 커널은 스케줄링 주기를 관리하기 위해 시스템 틱 타이머를 사용합니다. STM32 HAL 라이브러리 역시 기본값으로 SysTick을 사용하므로, 타이머 충돌을 막기 위해 HAL 타이머 소스를 변경해야 합니다.

  1. 좌측 메뉴에서 System Core > SYS를 클릭합니다.
  2. Timebase Source 항목을 찾아서 기본값인 SysTick 대신 TIM1, TIM2, TIM5 등 여유 있는 하드웨어 타이머 중 하나로 변경해 줍니다.

3. 주요 설정 및 자동 생성 파일 분석

IOC 설정을 마치고 저장하거나 Alt + K 키를 누르면 코드가 자동으로 생성됩니다. 이 과정에서 프로젝트 내에 추가되는 핵심 파일들의 구조와 역할은 다음과 같습니다.

파일명 저장 경로 주요 역할 및 핵심 설정 내용
FreeRTOSConfig.h Core/Inc/ 또는 Middleware/ FreeRTOS 커널의 세부 동작을 정의하는 헤더 파일입니다.

configUSE_PREEMPTION = 1 설정을 통해 선점형 스케줄링을 활성화하고, configTICK_RATE_HZ 값을 1000으로 지정하여 1ms 주기의 시스템 틱을 설정합니다.
cmsis_os.c / .h Middleware/Third_Party/FreeRTOS/ CMSIS 표준 API 함수들을 FreeRTOS 고유의 커널 함수로 연결해 주는 래퍼(Wrapper) 구현 파일입니다.
main.c Core/Src/ 하드웨어 주변장치 초기화 코드가 포함된 엔트리 포인트입니다.

이 파일 내부에서 CMSIS API를 통한 태스크 구조체 정의 및 생성 함수가 호출되며 최종적으로 커널 스케줄러가 기동됩니다.

 

4. 실전 예제: 멀티 태스크 구현

자동 생성된 main.c 파일의 사용자 코드 영역에 구현할 수 있는 실무 예시입니다. CMSIS-RTOS v1 API를 활용하여 서로 다른 주기로 두 개의 LED 핸들을 제어하는 멀티태스킹 소스코드 구조입니다.

#include "cmsis_os.h"
#include "stm32f4xx_hal.h" // 사용 중인 MCU 계열에 맞는 HAL 헤더 적용

/* 태스크 실행 함수 프로토타입 선언 */
void Task1_Entry(void const * argument);
void Task2_Entry(void const * argument);

/* CMSIS-RTOS 제어를 위한 태스크(쓰레드) ID 핸들 변수 */
osThreadId Task1Handle;
osThreadId Task2Handle;

int main(void) {
    // STM32 HAL 라이브러리 및 시스템 클럭 초기화
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init(); // LED 제어를 위한 GPIO 초기화 함수 호출

    /* 1. 태스크 매크로 정의 (태스크 이름, 진입 함수명, 우선순위, 인스턴스 수, 스택 크기 단어 단위) */
    osThreadDef(Task1, Task1_Entry, osPriorityNormal, 0, 128);
    osThreadDef(Task2, Task2_Entry, osPriorityNormal, 0, 128);

    /* 2. 정의된 매크로 정보에 따라 실제 태스크 객체 런타임 생성 */
    Task1Handle = osThreadCreate(osThread(Task1), NULL);
    Task2Handle = osThreadCreate(osThread(Task2), NULL);

    /* 3. RTOS 커널 스케줄러 구동 */
    osKernelStart();

    /* 스케줄러가 정상 구동되면 아래 메인 루프에는 제어권이 도달하지 않습니다. */
    while (1) {
        ;
    }
}

/* [태스크 1] 1000ms 주기로 동작하는 LED1 제어 루프 */
void Task1_Entry(void const * argument) {
    for (;;) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        osDelay(1000); // 1000ms 동안 현재 태스크를 Block 상태로 전환하고 자원을 양보함
    }
}

/* [태스크 2] 500ms 주기로 동작하는 LED2 제어 루프 */
void Task2_Entry(void const * argument) {
    for (;;) {
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
        osDelay(500); // 500ms 지연 처리
    }
}

 

5. 성공적인 개발을 위한 팁

  • Heap 메모리 관리 정책 확인: FreeRTOS는 동적으로 태스크나 큐를 생성할 때 메모리를 관리하기 위해 heap_1.c부터 heap_5.c까지의 가비지 컬렉션 및 할당 알고리즘을 제공합니다. STM32CubeIDE의 기본값은 조각화 방지와 동적 해제가 모두 안정적인 heap_4.c입니다. 수동으로 태스크를 자주 할당하고 해제하는 구조라면 FreeRTOSConfig.h에서 메모리 할당 정책이 4번으로 되어 있는지 한 번 더 교차 검증하는 편이 좋습니다.
  • 컴파일러 최적화 옵션 고려: RTOS가 적용된 코드는 컴파일러의 최적화 레벨(O2, O3, Os 등)에 따라 컨텍스트 스위칭 타이밍이나 인라인화된 레지스터 접근 코드에서 예상치 못한 타이밍 꼬임 현상이 일어날 수 있습니다. 개발 단계에서는 최적화 옵션을 기본값인 -O0(최적화 없음) 또는 -Og(디버깅 최적화)로 두고 검증을 마친 뒤 양산 단계로 넘어가는 방법을 추천합니다.

6. 흔히 하는 실수

  • Timebase Source 변경 누락: 신입 개발자들이 가장 자주 누락하는 논리적 에러 요소입니다. 앞서 2.2절에서 언급한 HAL 라이브러리의 타임베이스 소스를 SysTick에서 하드웨어 타이머로 바꾸지 않으면, HAL 내부 지연 함수인 HAL_Delay()와 FreeRTOS의 스케줄러 커널 인터럽트가 동일한 SysTick 인터럽트 벡터를 공유하며 무한 루프에 빠지거나 먹통이 되는 하드 fault가 발생합니다.
  • 사용자 코드 영역(User Code Blocks) 무시: STM32CubeIDE에서 소스코드를 자동 생성하면 파일 곳곳에 /* USER CODE BEGIN ... */과 /* USER CODE END ... */ 주석 라인이 삽입됩니다. 개발자가 구현할 기능 코드나 변수 선언을 반드시 이 주석 블록 내부에 작성해야 합니다. 이 영역을 벗어난 곳에 코드를 임의 작성하면, 나중에 IOC 파일에서 주변장치 핀맵을 수정하고 재설정 코드를 뽑아낼 때 작성했던 소스코드가 전부 지워져 버리는 난감한 상황을 겪게 됩니다.

7. 결론 및 디버깅 확인 방법

위 가이드에 맞춰 프로젝트 빌드가 성공했다면 타겟 보드에 펌웨어를 다운로드합니다. 육안으로 두 개의 LED가 각각 1초와 0.5초 주기로 어긋나며 정상 작동하는지 확인하는 것만으로도 멀티태스킹 환경이 구축된 것을 직관적으로 알 수 있습니다.

조금 더 명확하게 내부 동작 상태를 검증하고 싶다면 STM32CubeIDE 상단 메뉴의 Window > Show View > SFRs 또는 RTOS Viewer 창을 활성화하면 됩니다. 실시간으로 현재 CPU 점유율을 차지하고 있는 태스크의 흐름과 스택이 안전 범위를 유지하는지 눈으로 확인할 수 있으므로 시스템 안정성 확보가 한결 수월해집니다.

첫 단추인 태스크 생성을 성공적으로 완료했으니 다음 글에서는 실무 설계에서 가장 핵심이 되는 요소인 태스크 간 데이터 전송을 위한 큐(Queue)와 동기화를 제어하는 세마포어(Semaphore)의 세부 활용법과 구현 예제를 자세히 다루어 보도록 하겠습니다. 궁금한 점이나 설정 중 막히는 부분이 있다면 댓글로 편하게 공유해 주시기 바랍니다.

반응형