https://zino-dev.tistory.com/53
STM32 개발 환경 세팅 — CubeMX + CubeIDE (Nucleo-F401RE)
RTOS 공부를 위해 STM32 개발 환경을 세팅한 과정을 정리한다. 사용한 보드는 Nucleo-F401RE, OS는 macOS 기준이다.필요한 툴 두 가지STM32 개발에는 툴이 두 개 필요하다. 예전에는 CubeIDE 하나에 다 들어있
zino-dev.tistory.com
이전 포스팅에서 세팅한 환경을 기반으로 보드에 있는 LED를 켜는 코드를 작성하려한다.
Bare-metal이란?
코드를 작성하기 전에 Bare-metal이 뭔지 짚고 넘어간다.
일반 PC에서는 코드가 OS 위에서 실행된다. 하지만 STM32 같은 MCU는 OS 없이 코드가 하드웨어 위에서 직접 실행된다. 이를 Bare-metal 프로그래밍이라고 한다.
일반 PC:
내 코드 → OS (Windows / Linux) → 하드웨어
STM32 Bare-metal:
내 코드 → 하드웨어 (OS 없음)
전원이 켜지면 MCU는 main() 부터 순서대로 실행한다. OS가 없어도 동작한다.
HAL이란?
코드에서 사용하는 HAL_GPIO_TogglePin(), HAL_Delay() 같은 함수는 FreeRTOS와 전혀 관계없는 ST가 제공하는 하드웨어 추상화 라이브러리(HAL, Hardware Abstraction Layer)다.
HAL은 GPIO, UART, I2C 같은 하드웨어 레지스터를 직접 다루는 복잡한 작업을 함수로 감싸서 편하게 쓸 수 있게 해준다. OS 없이도 동작한다.
┌─────────────────────┐
│ 내 애플리케이션 코드 │ (LED Blink 로직)
├─────────────────────┤
│ HAL 라이브러리 │ (HAL_GPIO_TogglePin, HAL_Delay)
├─────────────────────┤
│ STM32 하드웨어 │ (레지스터, 클럭, GPIO...)
└─────────────────────┘
코드 작성

Core → Src → main.c 를 열면 CubeMX가 자동 생성한 초기화 코드들이 있다. while(1) 안에 아래 코드를 추가한다.
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // PA5 (온보드 LED) 토글
HAL_Delay(500); // 500ms 대기
}
Nucleo-F401RE의 온보드 LED는 PA5 핀에 연결되어 있다. HAL_GPIO_TogglePin() 은 핀의 현재 상태를 반전시키는 함수로, 호출할 때마다 켜짐/꺼짐이 번갈아 전환된다.

빌드 & 실행
Cmd+B 로 빌드하고, 보드를 USB로 연결한 뒤 F11 로 디버그 모드로 실행한다.
macOS에서는 ST-Link Server 를 별도로 설치해야 한다. 설치하지 않으면 F11 실행 시 "ST-Link Server is required" 에러가 뜬다. 👉 https://www.st.com/en/development-tools/st-link-server.html
디버그 화면으로 전환 여부를 묻는 팝업이 뜨면 Switch 를 누르고, 이후 상단의 Resume(▶️) 또는 F8 을 누르면 코드가 실행된다.
보드의 초록색 LED가 500ms 간격으로 깜빡이면 성공이다.
Bare-metal의 한계
동작은 잘 되지만 한 가지 문제가 있다.
HAL_Delay(500) 은 CPU가 500ms 동안 아무것도 안 하고 멈춰서 기다리는 방식이다. 이 시간 동안 CPU는 숫자만 세면서 시간을 통째로 낭비한다.
LED 하나만 깜빡이는 지금은 문제없지만, 동시에 버튼도 감지하고 센서도 읽어야 한다면 이 방식으로는 한계가 있다. HAL_Delay() 가 실행되는 동안 다른 작업은 전혀 할 수 없기 때문이다.
HAL_Delay(500) 동안:
[LED 토글] [————— 500ms CPU 대기중 —————] [LED 토글] ...
↑ 이 시간동안 다른 작업 불가
이 문제를 해결하는 것이 바로 FreeRTOS 다.
이번에는 FreeRTOS를 추가하고 Task를 만들어 같은 LED Blink를 구현해본다. HAL_Delay() 대신 osDelay() 를 쓰면 대기 시간 동안 CPU가 다른 Task를 실행할 수 있게 된다.
FreeRTOS 추가 — CubeMX에서 설정

FreeRTOS는 코드를 직접 추가하는 것이 아니라 STM32CubeMX를 통해 추가한다.

CubeMX에서 기존 프로젝트(LED_test.ioc)를 열고 왼쪽 카테고리에서
Middleware and Software Packs → FREERTOS
를 선택한다. Interface를 CMSIS_V2 로 설정하면 FreeRTOS가 활성화된다.
Task 추가
FreeRTOS를 활성화하면 하단에 Tasks and Queues 탭이 나타난다. Add 버튼을 클릭해 새 Task를 추가한다.

위 내용과 같이 Task를 추가해준다.
Task Name과 Entry Function을 같은 이름으로 설정할 수 없기 때문에 위와 같이 구분해서 입력한다.
설정 후 Generate Code 를 클릭한다. 경고창이 뜨면 Yes 를 누르고 진행한다.
생성된 코드 확인
Generate Code 후 CubeIDE에서 main.c 를 열면 ledTaskFunc 함수가 자동으로 생성되어 있다.

CubeMX에서 설정한 Task Name, 우선순위, Stack 크기는 아래와 같이 코드로 변환되어 있다.
const osThreadAttr_t ledTask_attributes = {
.name = "ledTask", // Task Name
.stack_size = 128 * 4, // Stack Size
.priority = osPriorityNormal, // Priority
};
// 스케줄러 시작 전에 Task 등록
ledTaskHandle = osThreadNew(ledTaskFunc, NULL, &ledTask_attributes);
// 스케줄러 시작 — 이 아래는 실행되지 않음
osKernelStart();
FreeRTOS에서 Task는 일반 함수처럼 직접 호출하지 않는다. osThreadNew() 로 등록해두면 스케줄러가 시작될 때 자동으로 실행한다. CubeMX가 이 등록 코드까지 자동으로 생성해주기 때문에 따로 작성할 필요가 없다.
ledTaskFunc 내부의 USER CODE BEGIN 영역에 LED 코드를 작성한다.

여기서 osDelay() 는 Bare-metal에서 쓴 HAL_Delay() 와 다르다.
| 동작 | CPU가 멈춰서 기다림 | Task를 Blocked 상태로 전환 |
| 대기 중 CPU | 낭비 (숫자만 셈) | 다른 Task 실행 가능 |
| FreeRTOS 필요 | 불필요 | 필요 |
osDelay(500) 이 호출되는 순간 이 Task는 500ms 동안 Blocked 상태가 된다. 그 사이 스케줄러는 다른 Task를 실행할 수 있다. CPU가 낭비되지 않는다.
빌드 & 실행
Cmd+B 로 빌드하고 F11 로 실행한다. 보드의 초록색 LED가 500ms 간격으로 깜빡이면 성공이다.
겉으로 보이는 동작은 Bare-metal과 똑같지만, 내부적으로는 완전히 다르다. osDelay() 로 대기하는 500ms 동안 CPU는 다른 Task를 실행할 수 있는 상태다. 현재 코드에서는 하나의 Task만 다루기에 차이를 알 수 없지만 다른 Task를 동시에 사용하기 위해서는 이와 같은 방식을 사용해야한다.
다음 포스팅에서는 버튼 입력을 감지하는 Event-driven Task 를 추가해서 두 Task가 동시에 동작하는 것을 구현해본다.(버튼을 이용해 LED 작동시키기)
'임베디드' 카테고리의 다른 글
| CAN 통신 - 가상버스 실습 (python-can 라이브러리) (0) | 2026.05.20 |
|---|---|
| CAN 통신 기초 (0) | 2026.05.19 |
| STM32 개발 환경 세팅 — CubeMX + CubeIDE (Nucleo-F401RE) (0) | 2026.05.06 |
| RTOS (0) | 2026.04.30 |
| 엔코더 모터 (Encoder Motor) 제어 (0) | 2025.09.17 |