통합 개발 환경(Integrated Development Environment) 설치에 대해 알아봅니다. IDE는 코드를 개발, 컴파일, 링크 및 디버그하는 데 필요한 모든 필수 도구를 포함하는 소프트웨어입니다.
여기서는 STM32 개발보드를 사용할 예정이므로, STMicroelectronics에서 개발한 Eclipse 기반 STM32CubeIDE를 사용할 겁니다. 설치 링크로 가서 여러분의 컴퓨터에 맞는 소프트웨어를 다운로드할 수 있습니다. 저는 Windows 기반 컴퓨터를 사용하고 있으므로 Windows 소프트웨어를 다운로드할 것입니다. 여기서 버전 선택이 가능한데 사용 가능한 최신 버전을 선택한 다음 설치를 진행해주세요.
참고로, 현 시점에서 STM32CubeIDE 가 2.0 버전으로 업데이트 되었네요. 2025년 11월 18일에 공식 출시되었다고 합니다. 주요 변화로는 STM32CubeMX의 IDE 내부 통합 제거... ㅠㅠ. 이제 STM32CubeMX는 별도로 설치해야 합니다.
💻 "STM32CubeIDE 2.0.0: 독립 실행형 도구 및 업데이트된 프로젝트 워크플로" 영상 참고

프로젝트는 STM32CubeMX에서 생성한 후, STM32Cube IDE로 import 하는 순서로 사용하는 거 같습니다. 주의사항으로 STM32CubeMX에서 툴체인을 STM32CubeIDE로 선택해야 합니다. 그런데도 빌드가 안되면, 프로젝트를 한번 삭제한 후 재생성을 해보면 제대로 됩니다. (음 이건 좀 오류네)
결론적으로, STM32CubeIDE와 STM32CubeMX는 별도로 설치하고, STM32CubeMX에서 자신의 개발보드에 맞게 선택 후 프로젝트 생성 후, STM32CubeIDE로 불러와서 사용하도록 합니다.
제가 가지고 있는 STM32 개발 보드는 "NUCLEO-F103RB" 이며, STM32F103RBT6 마이크로컨트롤러를 메인으로 사용하고 있습니다. Cortex-M3 코어입니다.

Cortex-M3는 ARM에서 설계한 32비트 RISC 프로세서 코어로, 저비용, 저전력, 고성능을 목표로 하는 마이크로컨트롤러(MCU) 전용 아키텍처입니다. 또한, 하드웨어 부동 소수점 연산 장치(FPU)가 포함되어 있지 않습니다. FPU가 없어 소수 연산(float, double)을 소프트웨어 라이브러리로 처리해야 하므로 연산 속도가 상대적으로 느립니다.
LED, 버튼, LCD, 키패드 및 다양한 외부 구성 요소를 연결하기 위한 풍부한 I/O 핀 세트가 보드에 있습니다. 이것들은 I/O 핀 또는 I/O 헤더라고 부르는 것입니다.
그 외에도 이 보드의 또 다른 중요한 기능은 온보드 프로그래머와 디버거입니다. 이것은 이 보드의 중요한 기능 중 하나입니다. 따라서 이 하드웨어에서 코드를 프로그래밍하고 디버그하려면 외부 프로그래머와 디버거를 구매할 필요가 없습니다. 보드에는 "ST-LINK"라고 부르는 자체 프로그래머와 디버거가 함께 제공됩니다. 이것을 사용하여 코드를 프로그래밍하고 디버그할 것입니다.
IDE, 즉 STM32CubeIDE를 설치하면 이 프로그래머와 디버거용 드라이버가 자동으로 컴퓨터에 설치됩니다. 그렇지 않으면 ST에서 이 ST-LINK 하드웨어용 별도 장치 드라이버도 제공하므로 ST 웹사이트에서 다운로드하여 설치할 수 있습니다.
그렇다면 이 보드를 PC에 어떻게 연결할까요? 매우 간단합니다. 미니 USB 케이블 하나만 필요합니다. 보드의 미니 USB 커넥터를 사용하여 PC에 연결하면 됩니다.
ST 웹사이트에서 마이크로컨트롤러와 개발 보드와 관련된 몇 가지 문서를 다운로드해서 나중에 참고하면 좋습니다. 대표적으로는 데이터시트, 레퍼런스 메뉴얼, 회로도 등이 있습니다.
두 가지 문서 세트가 있습니다. 마이크로컨트롤러와 관련된 문서와 개발 보드와 관련된 문서입니다.
먼저 마이크로컨트롤러와 관련된 문서를 다운로드해봅시다. 구글에서 마이크로컨트롤러 부품 번호, 예를 들어 "STM32F103RBT6"를 검색해서 ST 웹사이트에 접속합니다. Resources로 이동합니다. Resources로 가면 데이터시트를 다운로드할 수 있습니다.

애플리케이션 노트는 참조하지 않을 것입니다. 나중에 필요하다면 참조할 수 있습니다. 레퍼런스 메뉴얼은 다운로드 받습니다. 이것은 중요합니다.

사용자 매뉴얼과 회로도 같은 보드 관련 문서도 다운로드해야 합니다. 이를 위해 가장 좋은 방법은 보드를 검색한 다음 제조사가 제공하는 랜딩 페이지로 이동하는 것입니다. 예를 들어, "NUCLEO-F103RB"를 검색해봅니다.
여기서 제품 사양서(product specification)를 다운로드해야 합니다.

그리고, 매우 중요한 사용자 매뉴얼(user manuals)을 다운로드합니다. 회로도도 다운로드 해줍니다.

PC에서 아무 드라이브에나 폴더 하나를 만드세요. 저는 문서에다가 RTOS 폴더를 하나 만들었습니다. 그리고 그 아래에다가 RTOS_workspace와 Software&Toolchain 이란 폴더를 만들었습니다.
첫 번째는 RTOS_workspace입니다. 모든 실습을 이 안에다가 폴더를 각각 만들어서 사용할 것입니다. 이것이 FreeRTOS 프로젝트를 포함하는 우리의 워크스페이스입니다.
두 번째는 Software&Toolchain입니다. 다양한 소프트웨어와 툴체인을 여기다가 다운로드 할 것입니다.

이제, FreeRTOS 커널을 다운로드해봅시다. freertos.org 를 접속해봅니다.

예전에는 커널만 별도로 다운로드 받을 수 있었던거 같은데 지금은 없네요. FreeRTOS LTS를 다운로드 받으면 기본적으로 커널 외에도 TCP/IP, MQTT, OTA, 라이브러리 등 AWS 클라우드 연결에 필요한 핵심 라이브러리들이 세트로 묶여 있습니다. 이걸 다운로드 받아서 커널만 사용할 수 있긴 합니다.

혹은 깃허브 > FreeRTOS-Kernel 에서 직접 커널만 다운로드 받을 수 있네요. 현재는 11.2.0 버전 입니다. 아무튼 우리가 필요한 거는 일단 커널만입니다.
다운받은 커널 압축파일을 Software&Toolchain 에다가 압축해제하겠습니다. 이 디렉토리에서 FreeRTOS 커널의 소스 코드를 볼 수 있습니다.

구조를 간단하게 살펴보겠습니다. 크게 공통 코드, 하드웨어 의존 코드, 그리고 설정 파일 세 가지로 나뉩니다.
#1. 공통 소스 파일 (루트 폴더)
어떤 MCU를 쓰더라도 변하지 않는 핵심 로직이 담긴 .c 파일들입니다.
#2. include/ 폴더
위의 소스 파일들이 사용하는 헤더 파일(.h)들이 모여 있습니다. 우리가 코드 상단에 #include "FreeRTOS.h"나 #include "task.h"를 쓰는 이유가 이 폴더 때문입니다.
#3. portable/ 폴더 (하드웨어 의존적)
FreeRTOS는 수많은 CPU 아키텍처에서 돌아가야 하므로, CPU 마다 다른 하드웨어 설정(어셈블리 코드 등)을 이 폴더에 따로 모아두었습니다.
먼저, 컴파일러별로 구분되어 있습니다. GCC, IAR, RVDS 등 사용하는 컴파일러 폴더가 있습니다. 우리는 GCC를 사용합니다.

그 다음에, 아키텍처별로 구분되어 있습니다. 그 안에 들어가면 ARM_CM4F(Cortex-M4), ESP32, RISC-V 등 본인의 MCU에 맞는 폴더가 있습니다.

그 안에 들어가면, port.c & portmacro.h 파일이 있습니다. 실제 틱(Tick) 타이머를 설정하고 인터럽트를 제어하는 하드웨어 제어 코드가 들어있습니다.
#4. FreeRTOSConfig.h (사용자가 직접 만들어야 함)
이 파일은 커널 폴더에 기본으로 들어있지 않고, 보통 사용자의 프로젝트 폴더에 둡니다. RTOS의 옵션을 켜고 끄는 설정창 같은 역할입니다. 예를 들어, "최대 우선순위는 몇 개로 할 것인가?", "시스템 클럭은 몇 MHz인가?", "뮤텍스 기능을 사용할 것인가?" 등을 결정합니다.
이제 "Hello World FreeRTOS"와 비슷한 프로젝트를 만들어보겠습니다.
RTOS 프로젝트의 기본은 태스크(task)입니다. 즉, 실시간 운영 체제는 다양한 태스크를 관리할 수 있습니다. 하나 또는 두 개의 태스크를 만들고 태스크를 사용하여 "Hello World"를 출력할 것입니다. 이를 위해 태스크에 대해 더 이해해야 합니다.
하지만 먼저 FreeRTOS 프로젝트를 만들고 성공적으로 컴파일하는 것을 해봅시다. 먼저 여기 워크스페이스 폴더로 가봅시다. RTOS_workspace 폴더가 모든 FreeRTOS 프로젝트를 보관할 워크스페이스가 될 것입니다.

그 후에, STM32CubeMx 소프트웨어를 열어봅니다. 여기서 가장 먼저 자신이 사용하고 있는 STM32 개발보드를 선택해야 합니다. 저는 "NUCLEO-F103RB"를 사용합니다.
그러면 이런식으로 뜰겁니다. 여기서 다양한 주변 장치를 선택할 수 있고, 주변 장치를 구성할 수 있으며, 클럭을 설정할 수 있고, 많은 설정 작업을 수행할 수 있습니다.

처음에는 그냥 초기 설정 그대로, 코드를 생성해보도록 하겠습니다. 프로젝트 매너저에서 프로젝트 이름과 위치를 지정해줍니다. 여기서 중요한 것은 Toolchain/IDE를 STM32CubeIDE로 해줘야 합니다. 이렇게 설정해야만 STM32CubeIDE가 인식할 수 있는 프로젝트 파일(.project, .cproject)이 생성됩니다.

그런 다음에, GENERATE CODE 버튼을 누릅니다. 폴더가 생성되면 STM32CubeIDE를 실행합니다. File -> Import -> Existing Projects into Workspace를 선택하거나, 생성된 폴더 안의 .project 파일을 더블 클릭하면 바로 열립니다.
STM32CubeIDE 에서 본격적으로 코드를 작성하고 빌드, 실행, 디버깅 등이 가능합니다. 한번 빌드가 제대로 잘 되는지 확인해봅니다. 처음엔 잘 돼야 정상입니다.
STM32Cube IDE로 불러와서 빌드 하는 것까지 성공했다면, 이제 프로젝트에 FreeRTOS 커널 소스를 추가해봅시다. FreeRTOS 커널을 추가할 수 있는 두 가지 방법이 있습니다. 수동으로 추가하거나 STM32Cube의 장치 구성 도구를 사용할 수 있습니다.
여기서는 FreeRTOS 커널 소스를 프로젝트에 수동으로 추가할 겁니다. 왜냐하면 수동으로 배우면 나중에 다른 마이크로컨트롤러를 사용하거나 다른 IDE를 사용할 때도 적용할 수 있기 때문이죠. STM32Cube의 도움을 받는 것보다 조금 복잡하지만 배울만한 가치가 있습니다.
STM32Cube IDE의 장치 구성 도구를 사용하는 방법을 간단하게 살펴보면 CubeMX 내부에서 Middleware -> FREERTOS를 활성화해서 사용할 수 있습니다.

여기보면 Interface 선택 (CMSIS_V1 vs CMSIS_V2)가 있는데, CMSIS_V1은 오래된 표준이지만 가볍습니다. CMSIS_V2은 최신 표준이며 더 많은 기능을 지원합니다. 특별한 이유가 없다면 V2를 추천합니다.
이게 끝입니다. CMSIS RTOS API 버전을 선택하는 것만으로 FreeRTOS 커널 소스를 프로젝트에 추가할 수 있습니다. 정말 쉽죠. 이것의 단점은 내일 갑자기 다른 벤더의 다른 마이크로컨트롤러를 사용한다면 IDE에서 그러한 기능을 제공하지 않을 수 있습니다. 그런 경우 FreeRTOS를 프로젝트에 수동으로 추가해야 합니다.
우리는 CMSIS RTOS API 레이어를 사용하지 않을 것입니다. 단지 FreeRTOS 커널 소스만 사용할 것입니다. 하지만 STM32Cube IDE의 장치 구성 도구를 사용하면 CMSIS RTOS API 레이어도 추가합니다. 즉, 장치 구성 도구는 FreeRTOS와 CMSIS를 함께 사용합니다.
CMSIS-RTOS API는 한 마디로 말해 "FreeRTOS를 한 번 더 포장한 껍데기(Wrapper)"라고 이해하시면 가장 정확합니다.
💻 이미지 출처: https://diversant.sk/CMSIS/Documentation/RTOS/html/index.html

ARM에서 만든 표준 규격으로, 어떤 RTOS를 사용하든 개발자가 동일한 함수명으로 코딩할 수 있게 해주는 추상화 계층입니다.
왜 이런 레이어를 만들었을까요? 여러분이 배달 앱을 사용한다고 가정해봅시다. 배달 앱(CMSIS-RTOS)은 사용자에게 보이는 화면입니다. "주문하기" 버튼은 항상 같은 자리에 있습니다. 식당(실제 RTOS)은 "중국집(FreeRTOS)", "피자집(ThreadX)", "치킨집(uCOS)" 등 실제 음식을 만드는 곳입니다.
식당마다 요리하는 방식(함수 사용법)은 다르지만, 사용자는 배달 앱의 표준화된 버튼(API)만 누르면 됩니다. 나중에 식당(RTOS)을 바꿔도 주문 방식(코드)은 크게 바뀌지 않는다는 장점이 있습니다.
CMSIS-RTOS API는 적응 레이어(adaptation layer)입니다. 이 레이어는 애플리케이션을 위한 범용 API를 노출합니다. 이 레이어는 RTOS 특정 API를 사용하도록 허용하지 않습니다. 여기서 장점은 내일 FreeRTOS를 다른 RTOS로 바꾼다고 해도, 애플리케이션 코드를 크게 변경하지 않고도 애플리케이션 코드를 실행할 수 있다는 것입니다.
왜냐하면 애플리케이션은 CMSIS-RTOS 레이어가 노출하는 API에 의존하기 때문입니다. 따라서 애플리케이션 코드는 다른 RTOS 커널에서도 여전히 작동할 겁니다.
STM32 장치 구성 도구를 사용하면 FreeRTOS 레이어와 함께 이 레이어도 추가할 것입니다. 하지만 여기서는 CMSIS-RTOS API 레이어를 건너뛸 겁니다. FreeRTOS 커널 코드만 사용하고 애플리케이션에서 직접 FreeRTOS가 노출하는 API를 호출할 것입니다. 그렇게 하면 FreeRTOS API를 논의하고 집중할 시간이 더 많아질 것입니다.

예를 들어, 애플리케이션이 태스크를 생성하고 싶다고 가정해봅시다. 이 경우 애플리케이션은 CMSIS CORE가 제공하는 범용 API를 사용할 수 있습니다. 이 경우 API 이름은 osThreadCreate일 수 있습니다. 이것은 RTOS 태스크를 생성하기 위해 CMSIS-RTOS 레이어가 제공하는 API입니다.
예를 들어, FreeRTOS를 사용하는 경우, 기본 RTOS가 FreeRTOS라면 FreeRTOS가 제공하는 실제 API는 vTaskCreate입니다. 이것은 RTOS 특정적입니다. 다른 RTOS에서는 다를 수 있습니다. 따라서 애플리케이션은 실제 태스크 생성 API가 무엇인지 신경 쓸 필요가 없습니다. 단지 osThreadCreate를 호출하기만 하면 됩니다.
아래는 우리 프로젝트 레이어가 어떻게 보이는지입니다.

main.c 또는 사용자 애플리케이션을 작성하는 애플리케이션 레이어가 있습니다. CMSIS-RTOS-API 레이어는 있을 수도 있지만 저희는 없습니다. 그 아래는 FreeRTOS 커널 소스가 있습니다. 이 레이어를 추가해야 합니다.
그리고 CMSIS-CORE 레이어도 있습니다. CMSIS-CORE 레이어는 다시 Cortex-M 프로세서 기반 주변 장치를 다루기 위한 표준화된 API를 제공합니다.
참고로, CMSIS는 Cortex Microcontroller Software Interface Standard의 약자로, ARM Cortex-M 프로세서 기반의 마이크로컨트롤러(MCU)를 위한 공통 소프트웨어 인터페이스 표준을 의미합니다. 이 표준은 ARM에서 제정하였으며, 주요 목적과 특징은 다음과 같습니다.
프로젝트로 돌아가봅시다. 여기, 애플리케이션에서 Core 폴더를 확장하면 소스 파일이 보입니다. 이것이 우리의 애플리케이션 레이어입니다.

그리고 CMSIS CORE 레이어도 있습니다. Drivers > CMSIS > Include로 이동하면 여기 있습니다. 저희는 Cortex-M3를 사용하고 있습니다. 이것이 CMSIS 레이어입니다. CMSIS CORE API를 포함합니다. ARM Cortex-Mx 프로세서의 주변 장치를 다루는 표준화된 함수를 제공합니다.

이제 FreeRTOS 커널을 프로젝트에 추가해봅시다. 먼저 할 일은 프로젝트에다가 폴더를 하나 만드는 겁니다. ThirdParty 라는 폴더를 하나 만들어주겠습니다. 그 아래에 FreeRTOS 폴더를 만들어줍니다.
이제 위에서 확인한 FreeRTOS 커널 소스를 복사해야 합니다. 전부다 복사하지는 마시고, 필요한 것들만 복사해서 사용하도록 합니다.
먼저, include와 루트 폴더의 공통 소스 파일(task.c 등)을 복사해서 붙여넣어줍니다. portable과 examples는 복사하지 마세요.

이제 portable을 복사해야 합니다. 일단 복사해서 붙여넣어줍니다. portable 안에 들어가서 필요없는 폴더를 삭제해야 합니다. 저희는 GCC를 사용하고 있습니다. 그래서 다른 컴파일러와 관련된 다른 모든 폴더를 삭제하면 됩니다. MemMange와 readme 는 남겨놓습니다. MemMange는 메모리 관리(Heap) 방식이 들어있습니다.

이제 GCC 안으로 들어가세요. 여기서 프로세서 아키텍처와 관련이 없는 모든 폴더를 삭제해야 합니다. 저는 Cortex-M3를 기반으로 하는 STM32F103 마이크로컨트롤러를 사용하고 있습니다. 따라서 ARM_CM3 을 제외한 나머지는 삭제해줍니다.
ARM_CM3 안에 들어가보면 두 개의 파일이 보입니다. 하나는 소스 파일이고 다른 하나는 헤더 파일입니다. 이것들은 실제로 포트 코드 또는 아키텍처 레벨 코드이며, FreeRTOS가 우리의 프로세서 아키텍처에서 실행되기 위해 필요합니다.

이제 프로젝트로 돌아갑시다. 마우스 오른쪽 버튼을 클릭하고 Refresh를 선택하세요. 그렇게 하면 방금 추가한 FreeRTOS 폴더가 여기에 나타나는 것을 볼 수 있습니다.
이제, 이것(ThirdParty)을 마우스 오른쪽 버튼으로 클릭하고 Properties로 이동한 다음 C/C++ Build로 이동하여 "exclude resource from build" 옵션이 체크 해제되어 있는지 확인합니다.

Core 폴더를 살펴보겠습니다. 여기 main.c가 있습니다. main.c에 애플리케이션 코드를 추가할 것입니다. 그리고 여기에 it.c라는 것이 있는데, 이것은 모든 인터럽트 핸들러와 시스템 예외 핸들러를 포함합니다. msp.c 는 다양한 주변 장치에 대한 장치 초기화 코드를 포함합니다.
또한, 힙 관리를 위한 sysmem.c가 있습니다. 이 프로젝트의 경우 FreeRTOS는 자체 힙 관리 코드를 제공합니다. 동적 메모리 할당을 처리하는 코드입니다. 그래서 이 프로젝트에는 이 sysmem.c가 필요하지 않습니다. 따라서 해당 파일을 마우스 오른쪽 버튼으로 클릭하고 Properties를 선택한 다음 "exclude resource from build" 옵션을 체크할 수 있습니다.


sysmem.c 를 제외시키는 이유는 FreeRTOS가 힙 관리 코드를 제공하기 때문입니다. 이것은 portable > MemMang 폴더에 있습니다. 여기 1, 2, 3, 4, 5가 보입니다. 이 프로젝트에서는 heap_4.c를 사용할 것입니다. 나머지는 삭제해주세요.

이제 컴파일을 시도해봅시다. 무슨 일이 일어나는지 봅시다. 여기 보시면 FreeRTOS.h를 찾을 수 없다고 합니다.

FreeRTOS 헤더 파일이 정확히 어디에 있는지 IDE에 알려줘야 합니다. FreeRTOS라는 새 폴더를 추가했고 FreeRTOS는 자체 헤더 파일과 함께 제공됩니다. 하지만 이 경로가 include 경로 목록에 없습니다. 빌드의 include 경로 목록에 이 경로를 추가해야 합니다.
빌드가 FreeRTOS 커널에 의해 추가된 헤더 파일을 감지하지 못하는 문제가 있습니다. 지금 할 일은 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Properties로 이동합니다. 여기서 Settings를 선택하세요. 여기서 Tool Settings로 이동하고 MCU GCC Compiler를 선택하세요.
이제 컴파일러에 대한 include 경로를 추가해야 합니다. Add를 클릭하고 Workspace를 클릭하겠습니다. FreeRTOS 폴더안에 include 폴더를 선택해서 Add 하세요. 여기에 추가되었습니다.

portable 폴더에도 일부 헤더 파일이 있습니다. portable 안에, GCC 안에, ARM_CM3 경로 안에 있습니다. 이 폴더에 아키텍처 레벨 헤더 파일이 있습니다. 그 경로도 추가하겠습니다.

여기에 두 개의 경로를 추가했고 Apply를 클릭합니다. "변경 사항은 다시 빌드될 때까지 인덱스에 반영되지 않습니다" 라고 표시됩니다. 괜찮습니다. Yes를 클릭하세요.
이제 프로젝트를 다시 빌드해봅시다. 이제 FreeRTOS 관련 헤더 파일 문제가 대부분 해결된 것을 볼 수 있습니다. 하지만 또 다른 오류가 발생했습니다. 바로 FreeRTOSConfig.h입니다.

이 FreeRTOSConfig.h는 FreeRTOS 커널 소스를 커스터마이징하는 데 사용되는 구성 헤더 파일입니다. 이것은 FreeRTOS 커널 소스와 함께 제공되지 않습니다. 외부에서 추가해야 합니다. 즉, 사용자가 직접 FreeRTOSConfig 파일을 만들고 프로젝트에 추가해야 합니다.
먼저 이 FreeRTOSConfig.h가 무엇일까요? 이름이 나타내듯이 FreeRTOS 커널을 커스터마이징하기 위한 구성 파일입니다. 기본적으로 C 헤더 파일입니다. 이 헤더 파일에는 FreeRTOS 커널을 커스터마이징할 수 있는 다양한 구성 항목이 포함되어 있습니다. 흥미롭게도 이것은 FreeRTOS 커널 다운로드와 함께 제공되지 않습니다. 직접 만들어야 하며, 애플리케이션에 따라 특정적입니다.
이 FreeRTOSConfig.h에 대해 더 이해하기 위해서, 공식 문서를 이용할 수 있습니다.
💻 공식 문서 참고: https://www.freertos.org/Documentation/02-Kernel/03-Supported-devices/02-Customization

이 문서는 FreeRTOS가 FreeRTOSConfig.h라는 구성 파일을 사용하여 커스터마이징된다고 말합니다. 모든 FreeRTOS 애플리케이션은 pre-processor include 경로에 FreeRTOSConfig.h 헤더 파일이 있어야 합니다.
여기서 볼 수 있듯이 FreeRTOSConfig.h는 다양한 구성 가능한 항목들의 모음에 불과합니다. 이것들은 표준 구성 항목들입니다. C 매크로로 작성해야 합니다. 예를 들어 이 구성 항목 configUSE_PREEMPTION을 생각해봅시다. 이 구성 항목이 1로 설정되면 FreeRTOS가 선점형 커널로 구성됩니다. 즉, 커널 내부에서 선점이 활성화됩니다. 이 구성 항목을 0으로 설정하면 커널 내부의 선점이 비활성화됩니다.
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* Here is a good place to include header files that are required across
your application. */
#include "something.h"
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ 60000000
#define configSYSTICK_CLOCK_HZ 1000000
#define configTICK_RATE_HZ 250
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
...이하 생략...
실습을 진행하면서 더 많은 구성 항목과 이러한 구성 항목의 정확한 의미와 중요성을 탐색할 것입니다. 하지만 더 많은 정보를 원하면 공식 문서에서 각각의 구성 항목을 클릭할 수 있으며 , 실제로 해당 구성 항목의 중요성이 무엇인지 설명합니다.
당분간 이 모든 구성 항목에 대해 걱정하지 마세요. 빌드 문제를 해결하기 위해 이 헤더 파일을 포함하기만 하면 됩니다. 웹 페이지를 탐색하면 프로세서 특정 구성 항목도 볼 수 있습니다. 이것은 매우 중요합니다. FreeRTOSConfig.h는 프로세서에 의존하는 구성 항목도 포함합니다. 그래서 한 프로세서 아키텍처에 사용되는 FreeRTOSConfig.h는 다른 프로세서 아키텍처에 사용되는 프로젝트에 사용되지 않을 수 있습니다. 이 점을 주의하세요.
우리의 일차적인 목표는 FreeRTOSConfig.h를 추가해서 빌드 문제를 해결하는 겁니다. 이 FreeRTOSConfig.h를 얻기 위해 한 가지를 하겠습니다. FreeRTOS 다운로드의 demo 폴더로 들어가서 거기서 FreeRTOSConfig.h를 가져올 것입니다.
만약에 demo 폴더가 없다면 깃허브를 참고하세요. 💻 깃허브 참고.
이것들은 FreeRTOS에서 다양한 마이크로컨트롤러를 위해 제공하는 데모 애플리케이션입니다. 이 경로에서 마이크로컨트롤러를 검색하세요. 제 마이크로컨트롤러 "STM32F103"을 검색해봅니다.

음... 왜 4가지나 있는거죠...ㅠㅠ 여기서 GCC_Rowley과 Primer_GCC를 고민하다가 후자를 선택했습니다. 그냥 더 최신인 거 같아서요. 여기보면 FreeRTOSConfig.h 파일이 있을 겁니다.

코드를 복사해서 ThirdParty > FreeRTOS 폴더 안에 FreeRTOSConfig.h 생성 후에 붙여넣기를 해주겠습니다. 적어도 이 FreeRTOSConfig.h는 신뢰할 수 있을 겁니다. 왜냐하면 이 FreeRTOSConfig.h는 동일한 마이크로컨트롤러, 즉 STM32F103을 기반으로 하는 프로젝트를 위해 작성되었기 때문입니다.
이제 컴파일을 시도해보겠습니다. 하지만 컴파일하기 전에 이 헤더 파일의 경로를 include 경로에 추가해야 합니다. 그렇지 않으면 빌드가 이 헤더 파일을 인식하지 못할 것입니다. 그래서 Properties로 갑시다. FreeRTOSConfig.h를 FreeRTOS 아래에 보관했으므로, FreeRTOS 경로를 추가해주면 됩니다.

❗️ 주의. 깃허브 Demo에서 찾은 FreeRTOSConfig.h 파일은 STM32CubeMX + HAL 형식과 맞지 않는 거 같습니다.
따라서 다른 방법으로 STM32CubeMx 에서 FreeRTOS 활성화 및 CMSIS V2 선택해서 별도의 임시 프로젝트를 만들어서 코드를 생성해보면 FreeRTOSConfig.h 파일을 얻을 수 있습니다. 이렇게 하면 ST가 제공한 표준 템플릿이고, STM32CubeMX / HAL 기반이라서 현재 구조와 더 잘 맞습니다.
예를 들어 보겠습니다. STM32CubeMX에서 생성된 파일은 이런식으로 되어있습니다.
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
void xPortSysTickHandler(void);
#endif
...
#define configCPU_CLOCK_HZ ( SystemCoreClock )
ARM 계열 컴파일러면 안에 코드가 활성화 된다는 뜻이고요. SystemCoreClock는 현재 CPU 클럭 값입니다. HAL/CMSIS에서 관리하는 전역 변수입니다. 아래 파일에 정의되어있네요.

그리고 나서 configCPU_CLOCK_HZ을 SystemCoreClock을 가져와서 사용하고 있네요. 이렇게 하면 HAL이 자동으로 관리하고 있고, PLL이 변경돼도 안전합니다. 이 방식이 훨씬 좋겠죠?
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 ) ❌
#define configCPU_CLOCK_HZ ( SystemCoreClock ) ✅
또 다른 중요한 예를 보겠습니다. FreeRTOSConfig.h 마지막 부분에 이런 코드가 있습니다. STM32CubeMX로 생성한 프로젝트라면 반드시 해줘야 하는 설정입니다.
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
🤔 SVC_Handler, PendSV_Handler, SysTick_Handler이 뭔가요?
이 세 가지는 Cortex-M 프로세서가 RTOS를 돌리기 위해 반드시 필요한 세 가지 핵심 인터럽트(Exception)입니다.
결론적으로, PendSV_Handler는 FreeRTOS 스케줄러를 구현하는 데 사용됩니다. 그리고 SysTick_Handler는 주어진 간격으로 스케줄러를 시작하는 데 사용됩니다. SysTick 타이머는 정의된 간격으로 스케줄러를 시작하는 데 사용됩니다.
🤔 왜 위와 같은 설정이 필요할까요? (이름 매핑)
FreeRTOS 커널은 내부적으로 하드웨어 인터럽트를 다룰 때 자신만의 함수 이름을 사용합니다. "vPortSVCHandler, xPortPendSVHandler, xPortSysTickHandler". port.c 파일을 보면 확인할 수 있습니다.

이것은 실제로 스케줄러 구현입니다. 스케줄러 구현은 PendSV Handler를 사용하여 수행됩니다. 스케줄러는 기본적으로 태스크 간 컨텍스트 스위칭을 수행합니다. 나중에 자세히 살펴보겠습니다.

하지만 STM32의 표준 시작 코드와 인터럽트 벡터 테이블에는 ARM CMSIS 표준 이름으로 등록되어 있습니다. "SVC_Handler, PendSV_Handler, SysTick_Handler". stm32xxxx_it.c 파일을 보면 확인할 수 있습니다.

따라서 위에서 #define 문은 "하드웨어가 SVC_Handler를 부르면, FreeRTOS의 vPortSVCHandler가 실행되게 해라"라고 별명을 붙여주는 역할을 합니다.
🤔 이 설정을 안하면 어떻게 되나요?
RTOS 스케줄러가 시작되지 않거나, 첫 번째 태스크를 실행하려 할 때 CPU가 Default_Handler에 빠져서 멈춰버립니다. SysTick이 작동하지 않아 태스크 전환(Context Switching)이 일어나지 않습니다.
만약 FreeRTOSConfig.h에서 위와 같이 #define을 해줬는데, stm32xxxx_it.c 파일에도 void SVC_Handler(void) { ... } 라는 함수가 동시에 존재하면 아래와 같이 "Duplicated Definition(중복 정의)" 에러가 발생합니다.

실제로, 위의 stm32xxxx_it.c 파일을 보면 SVC_Handler 가 있었죠? 따라서 #define을 사용할 거라면, stm32xxxx_it.c에 있는 비어있는 핸들러 함수들은 삭제하거나 주석 처리해야 합니다.
근데 그냥 코드를 수정해버리면 나중에 CubeMX 설정 코드랑 맞지 않을 수 있습니다. 따라서 호환성을 위해 직접 코드를 수정하는 것보다는 CubeMX 설정에서 수정한다음에 코드를 재생성해서 사용하는 것이 바람직합니다.
System Core로 이동하여 NVIC를 여세요. Code generation 탭을 클릭하고, Pendable request for system service, 즉 PendSV의 체크를 해제하세요. Time base: System tick timer와 System service call, 즉 SVC_Handler도 체크 해제해줍니다.

그 후 코드를 재생성해줍니다. 그러고나서 it.c 파일을 확인해봅니다. 이제 SVC_Handler 와 같은 함수들이 사라진 것을 볼 수 있을 겁니다.
그리고 나서, 빌드를 다시 해보겠습니다. 아, 에러가 아직 발생합니다. ㅠㅠ

여기서 핵심은 "undefined reference to 'vApplicationTickHook'" 입니다. FreeRTOS 내부(tasks.c)에서 vApplicationTickHook()을 호출하려고 했는데 사용자 코드에 그 함수가 존재하지 않아서 생기는 에러입니다. 즉, 선언만 있고 구현이 없습니다.
이런 상황이 생기는 이유는 FreeRTOSConfig.h 에서 configUSE_TICK_HOOK가 1로 설정되어 있기 때문입니다. 이렇게 돼 있으면 FreeRTOS는 매 Tick마다 vApplicationTickHook 함수를 무조건 호출합니다.
따라서 가장 간단한 해결 방법은 Tick Hook 안 쓸 거면 configUSE_TICK_HOOK를 0으로 만들어주면 됩니다. 대부분 이렇게 해결하면 됩니다.
IDLE_HOOK, MALLOC_FAILED_HOOK, CHECK_FOR_STACK_OVERFLOW 도 0으로 만들어주세요. 이것도 사용하려면 함수를 구현해야 되는데, 지금은 빠르게 에러를 해결하는게 목적이니까요.
이제 프로젝트를 다시 빌드해봅니다. 아마 모든 문제가 해결되었을 겁니다. 그렇다고 이것이 타겟 보드에서 바로 실행된다는 건 아닙니다. 다른 변경 사항도 수행해야 합니다.
우리는 프로젝트를 성공적으로 컴파일 했습니다. 하지만 타겟 보드(STM32)에서 FreeRTOS를 성공적으로 실행하기 위해서 설정해야 하는 필수 작업이 있습니다.
SysTick 타이머는 ARM Cortex-Mx 프로세서의 주변 장치라는 것을 알고 계실 것입니다. ARM Cortex-Mx 프로세서 내부에 있습니다.
우리 프로젝트에서는 STM32Cube 소프트웨어가 제공하는 Hardware Abstraction Layer(HAL)가 있습니다. HAL를 사용하여 ADC, DAC, 타이머, UART, USB 등과 같은 마이크로컨트롤러의 다양한 주변 장치와 통신할 수 있습니다. STM32Cube HAL 레이어은 기본적으로 SysTick 타이머를 time base source 로 사용합니다. HAL 레이어는 많은 지연 관련 기능을 제공합니다. 타임아웃 기능도 제공합니다. 이러한 타이밍 관련 작업을 수행하려면 타이머를 사용해야 합니다.
문제가 되는 부분은 FreeRTOS 커널도 ARM Cortex-Mx 프로세서의 내부 SysTick 타이머를 time-base(시간 기준)으로 사용한다는 것입니다. FreeRTOS 커널이 SysTick 타이머를 이용해 태스크 지연 구현, 다른 간격으로 태스크 스케줄링, 미리 정의된 간격으로 스케줄러 실행 등 다양한 타이밍 관련 기능을 구현하는 데 사용됩니다.

프로젝트에서 FreeRTOS와 STM32 Cube HAL 레이어를 모두 사용하는 경우 time base source 사용에 충돌이 발생하는 것은 당연합니다. FreeRTOS와 STM32 Cube HAL 레이어 모두에 SysTick 타이머를 사용할 수는 없습니다. 이 충돌을 해결하려면 STM32 Cube HAL 레이어의 time base source로 SysTick 타이머가 아닌 다른 것을 사용하는 것이 강력히 권장됩니다.
🤔 만약 두 시스템이 하나의 타이머를 같이 쓰려고 한다면 어떤 문제가 발생할까요?
따라서 SysTick 타이머는 FreeRTOS 전용으로만 사용하고, STM32 Cube HAL 레이어의 경우 마이크로컨트롤러의 타이머 주변 장치 중 하나를 time base source로 사용할 수 있습니다.
프로젝트 설정을 수정해봅시다. CubeMX를 열어봅니다. 여기서 System Core로 이동하고 SYS로 이동하세요. 여기서 Time base source가 보입니다. Basic Timer (TIM6/7)이 기능은 적지만 가볍고 설정이 쉬습니다. TIM6을 선택해줍니다.

저는 TIM 1, 2, 3, 4 밖에 안보이네요. 어쩔 수 없이 TIM4 를 선택해줍니다.
그 후, NVIC로 가서, 여기서 할 일은 여기 Priority Group 으로 가서 이것을 preemption priority에 4비트, sub priority에 0비트로 변경하는 것입니다.

🤔 Priority Group 이란 뭔가요? (권한의 구조)
ARM Cortex-M 프로세서는 인터럽트의 우선순위를 8비트로 관리하는데, STM32는 이 중 보통 4비트만 사용합니다. 이 4비트를 어떻게 나눌지 결정하는 것이 Priority Group입니다.
🤔 왜 4비트 / 0비트로 설정해야 할까요?
FreeRTOS는 모든 인터럽트가 "선점 가능(Preemptible)" 하기를 원합니다. 4비트를 모두 Preemption에 몰아주면 2^4 = 16단계(0~15)의 명확한 계급이 생깁니다.
FreeRTOS는 내부적으로 인터럽트 우선순위를 계산할 때, 하위 비트(Sub Priority)가 섞여 있으면 계산이 복잡해지고 논리적 오류가 발생할 수 있습니다. 그래서 "Sub Priority는 아예 쓰지 마(0비트)!"라고 권장하는 것입니다. (아하)
이제 코드를 생성해보세요. 코드를 확인해봅시다. Src를 보면 이제 새로운 파일 stm32f4xx_hal_timebase_timer.c가 생성된 것을 볼 수 있습니다.

그 파일은 방금 전에 우리가 설정했던 "Timebase Source를 TIM4로 바꾼 결과물"입니다.
원래 STM32의 시간 관리(HAL_Delay 등)는 stm32f4xx_hal.c 내부의 기본 함수가 처리하지만, 사용자가 SysTick이 아닌 다른 타이머(예: TIM4)를 시계로 지정하면 CubeMX가 그 설정을 반영하기 위해 이 파일을 별도로 생성해 줍니다.
다시 말해, 이 파일은 "HAL 라이브러리 전용 시계(TIM4)의 작동 명세서"입니다. SysTick 대신 우리가 선택한 TIM4가 1ms마다 인터럽트를 발생시키도록 설정하고, 그 인터럽트가 발생할 때마다 uwTick이라는 변수(HAL의 시간 기준)를 1씩 올리는 코드가 들어있습니다.
이제 코드를 빌드해보세요. 프로젝트가 성공적으로 빌드되어야 합니다. FreeRTOS 커널을 STM32 프로젝트에 통합하는 작업을 거의 완료했습니다.
이것은 STM32CubeMX를 이용해 기존 STM32 프로젝트에 FreeRTOS 커널을 직접 추가하는 방법을 보여드리기 위한 것입니다. 단지 추가 정보일 뿐입니다. 프로젝트에 FreeRTOS를 이미 수동으로 통합했다면 그냥 이런 방법도 있구나 하고 넘어가시면 됩니다.
STM32 프로젝트가 이미 있다고 가정해봅시다. STM32CubeMX을 열어봅니다. 그런 다음 Pinout and Configuration 섹션으로 이동합니다. 여기서 이 Middleware and Software를 엽니다. 여기 FreeRTOS 옵션이 있습니다.

이제 CMSIS RTOS v1, 즉 버전 1로 작업할 것인지 아니면 CMSIS RTOS 버전 2로 작업할 것인지 인터페이스를 선택해야 합니다. 여기서 CMSIS_v2를 선택하면 많은 탭으로 구성된 이 구성 창이 나타나며, 이를 사용하여 RTOS를 구성할 수 있습니다.

예를 들어 FreeRTOS의 힙 사용량을 구성할 수 있습니다. 애플리케이션이 동적 메모리 할당을 사용할 것이라고 생각한다면 여기서 예약하려는 힙 메모리 양을 구성할 수 있습니다. 이외에도 FreeRTOS와 관련된 많은 구성 매개변수가 있습니다.
여기서 볼 수 있듯이 FreeRTOS 커널을 프로젝트에 추가하려고 할 때 몇 가지 경고가 나타나고 있습니다. Yes를 클릭하겠습니다. 잠시 후에 이것에 대해 논의하겠습니다.
이제 보시다시피 프로젝트에 새 폴더 Middlewares가 생성되었습니다. 이 Third Party를 열어보면 FreeRTOS 커널이 프로젝트에 추가된 것을 볼 수 있습니다. 열어보면 이것이 CMSIS RTOS v2 레이어, 즉 이 레이어인 것을 볼 수 있습니다.
(...이하 생략...) 사실 귀찮아서 안해봄. 나중에 하면 소개 예정ㅎㅎ
이번 시간에는 배운 내용을 정리해보면 다음과 같습니다.
다음 시간에는 태스크가 무엇인지, FreeRTOS 태스크를 만드는 방법 및 실행, 디버깅 출력에 대해 알아봅니다. 정말 흥미로울 겁니다. ㅎㅎ