임베디드

STM32 LED 작동 (Nucleo-F401RE)

Zino. 2026. 5. 13. 21:31

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() 와 다르다.

HAL_Delay()osDelay()
동작 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 작동시키기)