지노랩 /JinoLab

FreeRTOSConfig.h 생성 및 통합 (STM32CubeIDE 기준) 본문

임베디드 시스템/RTOS

FreeRTOSConfig.h 생성 및 통합 (STM32CubeIDE 기준)

지노랩/JinoLab 2025. 6. 10. 09:50

지난 강의에서 FreeRTOS 커널 소스를 프로젝트에 복사하고 헤더 검색 경로를 추가했지만,
FreeRTOSConfig.h 파일이 없어서 컴파일 에러가 발생했습니다. 이 강의에서는 FreeRTOSConfig.h를 직접 가져와서 프로젝트에 추가하고,
나머지 설정을 마무리하여 빌드가 정상적으로 완료되도록 하는 과정을 단계별로 정리합니다.


목차

  1. FreeRTOSConfig.h의 필요성
  2. FreeRTOSConfig.h 가져오기
  3. 헤더 검색 경로(include path) 설정
  4. 컴파일 에러 해결: SystemCoreClock 외 정의되지 않은 심볼 처리
  5. 컴파일 에러 해결: 중복된 예외 핸들러 제거
  6. 컴파일 에러 해결: Hook 관련 옵션 비활성화
  7. 최종 빌드 확인

1. FreeRTOSConfig.h의 필요성

  • FreeRTOSConfig.h는 FreeRTOS 커널을 사용자가 원하는 동작으로 설정(커스터마이즈)하기 위해 반드시 필요한 헤더 파일입니다.
  • FreeRTOS 소스 패키지에는 포함되어 있지 않으며, 프로젝트마다 반드시 하나를 만들어야 합니다.
  • 주요 역할:
    • configUSE_PREEMPTION, configTICK_RATE_HZ, configMINIMAL_STACK_SIZE, configMAX_PRIORITIES 등 사용자 정의 매크로를 통해
      FreeRTOS 커널 동작(선점형/비선점형, 틱 주기, 스택 크기, 우선순위 개수 등)을 설정
    • configCPU_CLOCK_HZ 같은 프로세서 의존 매크로를 통해 CPU 클럭 정보를 커널에 전달
    • 메모리 할당 방식, 타이머 사용 여부, 각종 훅(Hook) 함수 사용 여부를 켜고 끄는 역할

2. FreeRTOSConfig.h 가져오기

2.1 FreeRTOS 데모 디렉터리 확인

  1. 로컬에 다운로드된 FreeRTOS 커널 소스 폴더(FreeRTOSvXXXXXX)를 엽니다.
  2. 그 안에 있는 FreeRTOS/Demo 폴더 → STM32F407 (혹은 “STM32F407VG-SK” 등) 데모 경로로 이동
  3. [FreeRTOS 소스]/FreeRTOS/Demo/ARM_CM4F_STM32F407ZG-SK/FreeRTOSConfig.h
  4. 해당 FreeRTOSConfig.h 파일을 복사합니다.

2.2 STM32CubeIDE 프로젝트에 붙여넣기

  1. STM32CubeIDE에서 Project Explorer 창에 있는 프로젝트를 우클릭 → Refresh
    (소스 폴더 변경 후 동기화)
  2. FreeRTOSConfig.h를 보관하려는 위치(예: Core/Inc/)를 우클릭 → New → File
    • 파일 이름: FreeRTOSConfig.h
  3. 복사해 온 FreeRTOSConfig.h 내용을 방금 생성한 파일에 붙여넣기 → 저장(Ctrl+S)

Tip: 일반적으로 FreeRTOSConfig.h는 프로젝트의 Inc/ 폴더(헤더 전용) 안에 두는 편이 편리합니다.
다른 위치에 두려면 해당 경로를 헤더 검색 경로에 추가해야 합니다.


3. 헤더 검색 경로(include path) 설정

만약 FreeRTOSConfig.h를 Core/Inc/가 아닌 다른 경로에 두었다면,
빌드 설정에 포함 경로를 반드시 추가해야 합니다.
이 예제에서는 Core/Inc/에 넣었으므로 추가 작업은 불필요합니다.
하지만 혹시 ThirdParty/FreeRTOS/include나 ThirdParty/FreeRTOS/portable/GCC/ARM_CM4F를 아직 등록하지 않았다면,
다음 단계를 따라 헤더 검색 경로를 다시 확인합니다.

  1. 프로젝트를 우클릭 → Properties
  2. C/C++ BuildSettings
  3. 탭에서 MCU GCC Compiler → Includes 선택
  4. 아래 Add... 버튼 클릭 →
    • 경로 1: FreeRTOS 헤더 모음이 있는 폴더
    • ${ProjDirPath}/ThirdParty/FreeRTOS/include
    • 경로 2: 포팅용 헤더(*.h)가 있는 폴더
    • ${ProjDirPath}/ThirdParty/FreeRTOS/portable/GCC/ARM_CM4F
  5. Apply → “Changes made will not be reflected in index until it is rebuilt. Continue?” → YesOK

ProjDirPath는 프로젝트 루트(예: C:/Workspace/MyFreeRTOSProject)를 가리킵니다.
폴더 위치를 프로젝트 구조에 맞춰 적절히 바꿔주세요.


4. 컴파일 에러 해결: SystemCoreClock 미정의

빌드 시 다음과 같은 에러가 남을 수 있습니다.

fatal error: 'FreeRTOSConfig.h' : No such file or directory   ← 해결 후
undefined reference to `SystemCoreClock'    ← 새로 발생

SystemCoreClock은 system_stm32f4xx.c(또는 .h)에 정의된 전역 변수입니다.
하지만 FreeRTOSConfig.h 내부에서 SystemCoreClock을 참조할 때, 컴파일러는 아직 이를 알지 못해 에러가 발생합니다.

4.1 FreeRTOSConfig.h 내 SystemCoreClock 참조 부분 찾기

  • FreeRTOSConfig.h 파일을 열고, configCPU_CLOCK_HZ 아래에 다음 코드가 있을 수 있습니다.또는
  • extern uint32_t SystemCoreClock; /* 직접 extern 선언한 형태 */
  • #ifdef __ICCARM__ /* IAR 보드용 예시 */ extern uint32_t SystemCoreClock; #endif
  • 문제: 위 #ifdef __ICCARM__ 블록은 IAR 컴파일러 전용으로 작성되었기 때문에,
    GCC로 컴파일할 경우 SystemCoreClock이 extern 선언되지 않습니다.
  • 해결: GCC, ARMCC(Keil) 등에서도 SystemCoreClock을 extern으로 선언하도록 조건문을 수정합니다.

4.2 조건부 컴파일 매크로 수정

  1. FreeRTOSConfig.h에서 SystemCoreClock 관련 부분을 찾아, 다음과 같이 변경하세요.
  2. #if defined(__ICCARM__) /* IAR 컴파일러 */ extern uint32_t SystemCoreClock; #elif defined(__GNUC__) /* GCC 컴파일러 */ extern uint32_t SystemCoreClock; #elif defined(__CC_ARM) /* Keil(ARMCC) 컴파일러 */ extern uint32_t SystemCoreClock; #else extern uint32_t SystemCoreClock; /* 기타 툴체인에서도 사용 가능 */ #endif
  3. 저장(Ctrl+S) 후 프로젝트를 Rebuild:
    • SystemCoreClock 관련 에러가 사라지고, 다음 단계로 넘어갑니다.
  4. Project → Build Project

5. 컴파일 에러 해결: 중복된 예외 핸들러 제거

다음으로, 포팅 레이어(portable/GCC/ARM_CM4F/port.c)에서 이미 정의된 PendSV_Handler, SysTick_Handler, SVC_Handler 등이
CubeMX가 자동 생성한 it.c에도 동일 이름으로 함수를 제공하기 때문에 “중복 정의” 에러가 발생할 수 있습니다.

예시 에러:

“_PendSV_Handler” redefined
“_SysTick_Handler” redefined
“_SVC_Handler” redefined

5.1 원인

  • **FreeRTOS 포팅 코드(port.c)**는 FreeRTOS 커널이 동작하기 위해 PendSV, SysTick, SVC 예외 핸들러를 직접 구현합니다.
  • 반면, STM32CubeIDE가 생성한 Core/Src/it.c 파일에도 기본적으로 빈(weak) 핸들러 구현이 들어있어 충돌이 발생합니다.

5.2 해결 방법: STM32CubeMX 설정 변경

it.c에서 핸들러를 지우기보다는, CubeMX 설정을 통해 “해당 예외 핸들러를 코드 생성에서 제외”시키는 편이 안전합니다.

  1. 프로젝트 루트에 있는 .ioc 파일(예: MyFreeRTOSProject.ioc)을 더블클릭하여 STM32CubeMX 설정 창을 엽니다.
  2. 좌측 메뉴에서 System Core → NVIC(또는 “NVIC 설정” 탭)을 클릭
  3. NVIC 관련 설정 화면 우측 상단 탭에서 “Configuration” 아이콘(톱니바퀴 모양) 클릭
  4. 아래와 같이 핸들러 생성을 끕니다:
    • PendSV interrupt 우측 체크박스 “Generate Interrupt Handler” → 해제
    • SysTick interrupt 우측 “Generate Interrupt Handler” → 해제
    • SVC interrupt 우측 “Generate Interrupt Handler” → 해제
  5. 상단 툴바에서 “Generate Code”(또는 Ctrl+S)를 눌러 코드 재생성
    • it.c 파일에서 더 이상 PendSV_Handler, SysTick_Handler, SVC_Handler 정의가 나타나지 않습니다.

5.3 빌드 확인

  • 다시 Project → Build Project 수행
  • 포팅 코드에서 구현된 예외 핸들러만 존재하므로 더 이상 “중복 정의” 에러가 발생하지 않습니다.

6. 컴파일 에러 해결: Hook 관련 옵션 비활성화

빌드 후에도 몇 가지 Hook 함수(스택 오버플로우 검사, 메모리 할당 실패 등)가 설정되어 있어
다음과 같은 에러가 나타날 수 있습니다.

undefined reference to `vApplicationTickHook'
undefined reference to `vApplicationMallocFailedHook'
undefined reference to `vApplicationStackOverflowHook'
…

이들은 FreeRTOSConfig.h에서 Hook 함수 사용 여부 매크로가 활성화되어 있기 때문에 발생합니다.

6.1 FreeRTOSConfig.h 수정

Core/Inc/FreeRTOSConfig.h를 열고, Hook 사용 매크로를 찾아 0으로 설정해 끕니다.

/* --------------------------------------------------- */
/* Hook 함수 사용 여부 (1: 활성, 0: 비활성)             */
#define configUSE_MALLOC_FAILED_HOOK     0   /* malloc 실패 훅 비활성  */
#define configCHECK_FOR_STACK_OVERFLOW   0   /* 스택 오버플로우 검사 비활성  */
#define configUSE_IDLE_HOOK              0   /* Idle 훅 비활성  */
#define configUSE_TICK_HOOK              0   /* Tick 훅 비활성 */
  • 이전 기본값 예제에서 이들 매크로를 1로 두었다가 Hook 함수 정의(vApplicationMallocFailedHook 등)가 없어서 에러가 생긴 것입니다.
  • 위와 같이 모두 0으로 수정하면, Hook 함수 호출이 컴파일 타임에 제거되어 심볼 미정의 에러가 사라집니다.
  • 저장(Ctrl+S) 후 Build Project 수행 → Hook 관련 에러가 없어집니다.

7. 최종 빌드 확인

  1. 모든 수정사항 저장(Ctrl+S)
  2. Project → Clean… → “Clean all projects” 체크 → OK
  3. Project → Build Project
    • 더 이상 헤더 파일 누락, 중복 정의, Hook 함수 미정의 등 에러 없이 빌드 성공 상태여야 합니다.
    Build finished successfully (0 errors, 0 warnings)
    
  4. ST-Link 연결 후 Run → Run 또는 Debug → Start를 눌러 타겟에 다운로드
  5. 정상적으로 실행된다면, FreeRTOS 스케줄러가 시작된 상태에서 Cortex-M 예외 핸들러들이 Hot Swap 되며 동작 중입니다.

부록: Hello World 태스크 예제

이제 빌드가 성공적으로 되었다면, 간단한 “Hello World” FreeRTOS 태스크를 시험해 볼 수 있습니다.
Core/Src/main.c를 다음과 같이 수정해 보세요.

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"

/* 태스크 함수 prototype */
static void vHelloTask(void *pvParameters);

int main(void)
{
    /* HAL 초기화 및 시스템 클럭 설정 (CubeMX 자동 생성 코드) */
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();    /* 필요 시, GPIO 설정 */
    MX_USART2_UART_Init(); /* 필요 시, UART 설정 */

    /* “헬로 월드” 태스크 생성 */
    xTaskCreate(
        vHelloTask,               /* 태스크 함수 */
        "HelloTask",              /* 태스크 이름(디버깅용) */
        configMINIMAL_STACK_SIZE, /* 스택 크기(워드 단위) */
        NULL,                     /* 파라미터 */
        tskIDLE_PRIORITY + 1,     /* 우선순위 (IDLE+1) */
        NULL                      /* 태스크 핸들 */
    );

    /* FreeRTOS 스케줄러 시작 */
    vTaskStartScheduler();

    /* 스케줄러가 시작되면 절대 이곳으로 오지 않습니다. */
    while (1)
    {
    }
}

/*-----------------------------------------------------------*/
/* 헬로 월드 태스크: 1초마다 문자열 출력 */
static void vHelloTask(void *pvParameters)
{
    (void) pvParameters;

    for (;;)
    {
        /* 디버그 콘솔로 “Hello FreeRTOS World!” 출력 */
        printf("Hello FreeRTOS World!\r\n");

        /* 1000ms 딜레이 */
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

/*-----------------------------------------------------------*/
/** SystemClock_Config, MX_GPIO_Init, MX_USART2_UART_Init 등은
 *  CubeMX가 자동 생성한 함수이며, UART printf 출력용 HAL_UART_Transmit 구현이 필요합니다.
 *  또는 LED 깜빡임으로 동작 확인 가능.                               **/

위 예제에서 printf를 사용하려면, UART2 설정과 함께 syscalls.c에 write()가 구현되어 있어야
UART로 출력이 나갑니다. 만약 번거롭다면, 다음과 같이 GPIO LED 토글만 작성해도 됩니다.

/* vHelloTask 내부 예시: PD13 LED 깜빡임 */
for (;;)
{
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
    vTaskDelay(pdMS_TO_TICKS(500));
}
  • 빌드 후 다운로드 → Run
    • UART로 주기적으로 “Hello FreeRTOS World!” 메시지가 찍히거나,
    • **PD13(녹색 LED)**가 반초 간격으로 깜빡이면
      → FreeRTOS 스케줄러 및 태스크가 정상 동작하는 것입니다.

요약

  1. FreeRTOSConfig.h는 사용자가 직접 만들어야 하는 “커널 설정” 헤더입니다.
  2. 데모 프로젝트(Demo/.../FreeRTOSConfig.h)에서 STM32F407용 샘플을 복사해 사용하는 것이 가장 빠릅니다.
  3. 헤더 검색 경로에 ThirdParty/FreeRTOS/include와 ThirdParty/FreeRTOS/portable/GCC/ARM_CM4F를 추가했는지 확인합니다.
  4. SystemCoreClock extern 선언을 컴파일러에 맞춰 수정하여 정의되지 않음 에러 해결.
  5. NVIC 설정에서 PendSV/SysTick/SVC 핸들러 생성을 비활성화하여 중복 정의 오류 해결.
  6. Hook 관련 매크로를 0으로 변경해 미정의 함수 오류를 제거.
  7. 위 과정을 마치면 빌드 성공 후 FreeRTOS 스케줄러가 실행됩니다.

이제 FreeRTOSConfig.h가 포함된 정상 빌드 환경이 갖춰졌습니다.
다음 강의에서는 큐, 세마포, 메모리 관리, 소프트웨어 타이머 등 FreeRTOS 주요 API 사용을 배우겠습니다.