RTOS #3

홍태준·2026년 1월 13일

RTOS

목록 보기
3/20
post-thumbnail

Week 1 Day 3: 개발 환경 구축 (STM32 + Wokwi)

학습 목표

  • VSCode 개발 환경 설정
  • STM32 프로젝트 구조 이해
  • Wokwi 시뮬레이터 설정 및 사용법
  • 첫 번째 FreeRTOS 프로젝트 빌드 및 실행

1. 개발 환경 개요

필요한 도구

도구용도비고
VSCode코드 편집기가볍고 확장성이 좋음
GCC ARM Toolchain컴파일러arm-none-eabi-gcc
Make빌드 도구Makefile 실행
Wokwi시뮬레이터웹 기반, 하드웨어 불필요
OpenOCD디버거 (선택)실제 하드웨어 사용 시

개발 프로세스

코드 작성 (VSCode)
    ↓
컴파일 (GCC ARM)
    ↓
링킹 (ld)
    ↓
바이너리 생성 (.elf, .bin, .hex)
    ↓
시뮬레이션 (Wokwi) 또는 다운로드 (실제 보드)
    ↓
디버깅 및 테스트

2. VSCode 설치 및 설정

VSCode 설치

  1. https://code.visualstudio.com/ 접속
  2. 운영체제에 맞는 버전 다운로드
  3. 설치 진행

필수 확장 프로그램

C/C++ 관련:

1. C/C++ (Microsoft)
   - IntelliSense, 디버깅 지원
   
2. C/C++ Extension Pack (Microsoft)
   - C/C++ Themes, CMake 등 포함

3. Cortex-Debug
   - ARM 디버깅 지원

기타 유용한 확장:

4. Makefile Tools
   - Makefile 지원

5. Wokwi Simulator
   - VSCode에서 Wokwi 실행

6. Better Comments
   - 주석 가독성 향상

7. Bracket Pair Colorizer 2
   - 괄호 쌍 색상 구분

VSCode 설정 (settings.json)

C 버전용 설정:

{
    "C_Cpp.default.includePath": [
        "${workspaceFolder}/**",
        "${workspaceFolder}/Core/Inc",
        "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc",
        "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/include",
        "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F"
    ],
    "C_Cpp.default.defines": [
        "STM32F401xE",
        "USE_HAL_DRIVER"
    ],
    "C_Cpp.default.compilerPath": "/usr/bin/arm-none-eabi-gcc",
    "C_Cpp.default.cStandard": "c11",
    "C_Cpp.default.intelliSenseMode": "gcc-arm"
}

C++ 버전용 설정:

{
    "C_Cpp.default.includePath": [
        "${workspaceFolder}/**",
        "${workspaceFolder}/Core/Inc",
        "${workspaceFolder}/Drivers/STM32F4xx_HAL_Driver/Inc",
        "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/include",
        "${workspaceFolder}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F"
    ],
    "C_Cpp.default.defines": [
        "STM32F401xE",
        "USE_HAL_DRIVER"
    ],
    "C_Cpp.default.compilerPath": "/usr/bin/arm-none-eabi-g++",
    "C_Cpp.default.cppStandard": "c++17",
    "C_Cpp.default.intelliSenseMode": "gcc-arm"
}

3. GCC ARM Toolchain 설치

Windows

# 1. ARM GCC 다운로드
https://developer.arm.com/downloads/-/gnu-rm

# 2. 설치 후 환경 변수 설정
Path에 추가: C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin

# 3. 설치 확인
arm-none-eabi-gcc --version

Linux (Ubuntu/Debian)

# 1. 패키지 설치
sudo apt update
sudo apt install gcc-arm-none-eabi

# 2. 추가 도구 설치
sudo apt install binutils-arm-none-eabi
sudo apt install libnewlib-arm-none-eabi

# 3. 설치 확인
arm-none-eabi-gcc --version

macOS

# 1. Homebrew 사용
brew install --cask gcc-arm-embedded

# 2. 설치 확인
arm-none-eabi-gcc --version

Make 도구 설치

Windows:

# MinGW 또는 Cygwin 설치
# 또는 chocolatey 사용
choco install make

Linux:

sudo apt install build-essential

macOS:

# Xcode Command Line Tools에 포함
xcode-select --install

4. STM32 프로젝트 구조

기본 디렉토리 구조

STM32_FreeRTOS_Project/
├── Core/
│   ├── Inc/                    # 헤더 파일
│   │   ├── main.h
│   │   ├── FreeRTOSConfig.h
│   │   └── stm32f4xx_it.h
│   └── Src/                    # 소스 파일
│       ├── main.c (또는 main.cpp)
│       ├── stm32f4xx_it.c
│       └── system_stm32f4xx.c
├── Drivers/
│   └── STM32F4xx_HAL_Driver/   # HAL 드라이버
│       ├── Inc/
│       └── Src/
├── Middlewares/
│   └── Third_Party/
│       └── FreeRTOS/
│           └── Source/         # FreeRTOS 소스
│               ├── tasks.c
│               ├── queue.c
│               ├── list.c
│               ├── timers.c
│               ├── portable/
│               │   └── GCC/
│               │       └── ARM_CM4F/
│               └── include/
├── Makefile                    # 빌드 스크립트
├── STM32F401RETx_FLASH.ld     # 링커 스크립트
└── wokwi.toml                 # Wokwi 설정 파일

주요 파일 설명

파일역할
main.c / main.cpp메인 함수, Task 생성, 하드웨어 초기화
FreeRTOSConfig.hFreeRTOS 설정 (Tick, Heap, Priority 등)
stm32f4xx_it.c인터럽트 핸들러 (SysTick, PendSV 등)
STM32F401RETx_FLASH.ld메모리 맵 정의 (Flash, RAM 주소)
Makefile빌드 자동화 스크립트

5. Wokwi 시뮬레이터 설정

Wokwi란?

  • 웹 기반 임베디드 시뮬레이터
  • 실제 하드웨어 없이 테스트 가능
  • STM32, Arduino, ESP32 등 지원
  • 무료 사용 가능

Wokwi 프로젝트 생성

1. Wokwi 웹사이트 접속

https://wokwi.com/

2. 새 프로젝트 생성

  • "New Project" 클릭
  • "STM32" 선택
  • 템플릿에서 "Empty STM32 Project" 선택

3. diagram.json 설정

{
  "version": 1,
  "author": "Your Name",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-stm32f401re",
      "id": "stm32",
      "top": 0,
      "left": 0,
      "attrs": {}
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -50,
      "left": 200,
      "attrs": { "color": "red" }
    }
  ],
  "connections": [
    [ "led1:A", "stm32:PA5", "green", [ "v0" ] ],
    [ "led1:C", "stm32:GND", "black", [ "v0" ] ]
  ]
}

4. wokwi.toml 설정

[wokwi]
version = 1
elf = "build/project.elf"
firmware = "build/project.bin"

[[net.forward]]
protocol = "tcp"
port = 3333
targetPort = 3333

6. 첫 번째 FreeRTOS 프로젝트

프로젝트 목표

간단한 LED 깜빡임 Task 구현

main.c (C 버전)

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

// LED GPIO 정의
#define LED_PIN GPIO_PIN_5
#define LED_PORT GPIOA

// Task 함수 선언
void vLEDTask(void *pvParameters);

// 시스템 초기화
void SystemClock_Config(void);
void GPIO_Init(void);

int main(void) {
    // HAL 초기화
    HAL_Init();
    
    // 시스템 클럭 설정
    SystemClock_Config();
    
    // GPIO 초기화
    GPIO_Init();
    
    // LED Task 생성
    xTaskCreate(
        vLEDTask,           // Task 함수
        "LED",              // Task 이름
        128,                // Stack 크기 (words)
        NULL,               // 파라미터
        1,                  // 우선순위
        NULL                // Task 핸들
    );
    
    // 스케줄러 시작
    vTaskStartScheduler();
    
    // 여기는 실행되지 않음
    while(1);
}

// LED Task 구현
void vLEDTask(void *pvParameters) {
    while(1) {
        // LED ON
        HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET);
        vTaskDelay(pdMS_TO_TICKS(500));  // 500ms 대기
        
        // LED OFF
        HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);
        vTaskDelay(pdMS_TO_TICKS(500));  // 500ms 대기
    }
}

// GPIO 초기화
void GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // GPIOA 클럭 활성화
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // PA5를 출력으로 설정
    GPIO_InitStruct.Pin = LED_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);
}

// 시스템 클럭 설정 (84MHz)
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    // HSE 사용
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    
    // 클럭 설정
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | 
                                   RCC_CLOCKTYPE_SYSCLK |
                                   RCC_CLOCKTYPE_PCLK1 | 
                                   RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

main.cpp (C++ 버전)

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

// GPIO 래퍼 클래스
class GPIO {
private:
    GPIO_TypeDef* port;
    uint16_t pin;
    
public:
    GPIO(GPIO_TypeDef* p, uint16_t pn) : port(p), pin(pn) {}
    
    void init() {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        
        // 클럭 활성화
        if(port == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
        else if(port == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
        else if(port == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
        
        // 출력으로 설정
        GPIO_InitStruct.Pin = pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(port, &GPIO_InitStruct);
    }
    
    void set() {
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
    }
    
    void reset() {
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
    }
    
    void toggle() {
        HAL_GPIO_TogglePin(port, pin);
    }
};

// Task 베이스 클래스
class Task {
protected:
    TaskHandle_t taskHandle;
    const char* taskName;
    uint16_t stackSize;
    UBaseType_t priority;
    
    virtual void run() = 0;
    
    static void taskEntry(void* pvParameters) {
        Task* task = static_cast<Task*>(pvParameters);
        task->run();
    }
    
public:
    Task(const char* name, uint16_t stack, UBaseType_t prio)
        : taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
    
    void create() {
        xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
    }
    
    virtual ~Task() {}
};

// LED Task 클래스
class LEDTask : public Task {
private:
    GPIO& led;
    uint32_t delayMs;
    
public:
    LEDTask(GPIO& ledPin, uint32_t delay)
        : Task("LED", 128, 1), led(ledPin), delayMs(delay) {}
    
    void run() override {
        while(1) {
            led.set();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
            
            led.reset();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
};

// 시스템 초기화 클래스
class System {
public:
    static void init() {
        HAL_Init();
        clockConfig();
    }
    
private:
    static void clockConfig() {
        RCC_OscInitTypeDef RCC_OscInitStruct = {0};
        RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
        
        // HSE 사용
        RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
        RCC_OscInitStruct.HSEState = RCC_HSE_ON;
        RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
        RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
        RCC_OscInitStruct.PLL.PLLM = 8;
        RCC_OscInitStruct.PLL.PLLN = 336;
        RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
        RCC_OscInitStruct.PLL.PLLQ = 7;
        HAL_RCC_OscConfig(&RCC_OscInitStruct);
        
        // 클럭 설정
        RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | 
                                       RCC_CLOCKTYPE_SYSCLK |
                                       RCC_CLOCKTYPE_PCLK1 | 
                                       RCC_CLOCKTYPE_PCLK2;
        RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
        RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
        RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
        RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
        HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
    }
};

int main(void) {
    // 시스템 초기화
    System::init();
    
    // GPIO 객체 생성 및 초기화
    GPIO led(GPIOA, GPIO_PIN_5);
    led.init();
    
    // LED Task 생성
    LEDTask ledTask(led, 500);
    ledTask.create();
    
    // 스케줄러 시작
    vTaskStartScheduler();
    
    while(1);
}

FreeRTOSConfig.h

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

// CPU 설정
#define configUSE_PREEMPTION                    1
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configCPU_CLOCK_HZ                      ( 84000000 )
#define configTICK_RATE_HZ                      ( 1000 )
#define configMAX_PRIORITIES                    ( 5 )
#define configMINIMAL_STACK_SIZE                ( 128 )
#define configTOTAL_HEAP_SIZE                   ( 15 * 1024 )
#define configMAX_TASK_NAME_LEN                 ( 16 )
#define configUSE_16_BIT_TICKS                  0
#define configIDLE_SHOULD_YIELD                 1

// 기능 활성화
#define configUSE_MUTEXES                       1
#define configUSE_RECURSIVE_MUTEXES             1
#define configUSE_COUNTING_SEMAPHORES           1
#define configUSE_QUEUE_SETS                    0
#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               ( 2 )
#define configTIMER_QUEUE_LENGTH                10
#define configTIMER_TASK_STACK_DEPTH            128

// Hook 함수
#define configUSE_MALLOC_FAILED_HOOK            0
#define configCHECK_FOR_STACK_OVERFLOW          2

// Cortex-M 설정
#define configPRIO_BITS                         4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY \
    ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
    ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

// API 함수 포함
#define INCLUDE_vTaskPrioritySet                1
#define INCLUDE_uxTaskPriorityGet               1
#define INCLUDE_vTaskDelete                     1
#define INCLUDE_vTaskCleanUpResources           0
#define INCLUDE_vTaskSuspend                    1
#define INCLUDE_vTaskDelayUntil                 1
#define INCLUDE_vTaskDelay                      1

// Cortex-M 특화 정의
#define configPRE_SLEEP_PROCESSING(x)
#define configPOST_SLEEP_PROCESSING(x)

#endif /* FREERTOS_CONFIG_H */

7. Makefile 작성

기본 Makefile

# 프로젝트 이름
TARGET = stm32_freertos

# 디렉토리 정의
BUILD_DIR = build
CORE_DIR = Core
DRIVERS_DIR = Drivers/STM32F4xx_HAL_Driver
FREERTOS_DIR = Middlewares/Third_Party/FreeRTOS/Source

# 컴파일러 설정
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
CXX = $(PREFIX)g++
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size

# CPU 설정
CPU = -mcpu=cortex-m4
FPU = -mfpu=fpv4-sp-d16
FLOAT-ABI = -mfloat-abi=hard
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# C 소스 파일
C_SOURCES = \
$(CORE_DIR)/Src/main.c \
$(CORE_DIR)/Src/stm32f4xx_it.c \
$(CORE_DIR)/Src/system_stm32f4xx.c \
$(DRIVERS_DIR)/Src/stm32f4xx_hal.c \
$(DRIVERS_DIR)/Src/stm32f4xx_hal_gpio.c \
$(DRIVERS_DIR)/Src/stm32f4xx_hal_rcc.c \
$(DRIVERS_DIR)/Src/stm32f4xx_hal_cortex.c \
$(FREERTOS_DIR)/tasks.c \
$(FREERTOS_DIR)/queue.c \
$(FREERTOS_DIR)/list.c \
$(FREERTOS_DIR)/timers.c \
$(FREERTOS_DIR)/portable/GCC/ARM_CM4F/port.c \
$(FREERTOS_DIR)/portable/MemMang/heap_4.c

# 어셈블리 소스 파일
ASM_SOURCES = \
startup_stm32f401xe.s

# Include 경로
C_INCLUDES = \
-I$(CORE_DIR)/Inc \
-I$(DRIVERS_DIR)/Inc \
-I$(FREERTOS_DIR)/include \
-I$(FREERTOS_DIR)/portable/GCC/ARM_CM4F

# 컴파일 플래그
CFLAGS = $(MCU) $(C_INCLUDES) -Wall -fdata-sections -ffunction-sections
CFLAGS += -DSTM32F401xE -DUSE_HAL_DRIVER

# 디버그 빌드
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif

# 최적화
CFLAGS += -O0

# 링커 플래그
LDSCRIPT = STM32F401RETx_FLASH.ld
LIBS = -lc -lm -lnosys
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBS) \
          -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# 빌드
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

# 오브젝트 파일 생성
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))

OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(CP) -O ihex $< $@

$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(CP) -O binary -S $< $@

$(BUILD_DIR):
	mkdir $@

# 클린
clean:
	-rm -fR $(BUILD_DIR)

# 의존성
-include $(wildcard $(BUILD_DIR)/*.d)

8. 빌드 및 실행

빌드 명령어

C 프로젝트:

# 빌드
make

# 클린
make clean

# 디버그 빌드
make DEBUG=1

C++ 프로젝트:

# Makefile에서 CC를 CXX로 변경하고 빌드
make

# 또는 main.c를 main.cpp로 변경

빌드 출력

arm-none-eabi-gcc -c ... -o build/main.o
arm-none-eabi-gcc -c ... -o build/tasks.o
...
arm-none-eabi-gcc ... -o build/stm32_freertos.elf
   text    data     bss     dec     hex filename
  12345     256    2048   14649    3939 build/stm32_freertos.elf

Wokwi에서 실행

  1. Wokwi 웹사이트에 업로드
  2. diagram.json에서 연결 확인
  3. "Start Simulation" 클릭
  4. LED가 500ms 간격으로 깜빡이는지 확인

9. 디버깅 기법

Serial 출력 (printf 디버깅)

C 버전:

// UART를 통한 printf 리다이렉션
int _write(int file, char *ptr, int len) {
    HAL_UART_Transmit(&huart2, (uint8_t*)ptr, len, HAL_MAX_DELAY);
    return len;
}

void vLEDTask(void *pvParameters) {
    while(1) {
        printf("LED ON\n");
        HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET);
        vTaskDelay(pdMS_TO_TICKS(500));
        
        printf("LED OFF\n");
        HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

C++ 버전:

class DebugLogger {
public:
    static void print(const char* message) {
        HAL_UART_Transmit(&huart2, (uint8_t*)message, 
                          strlen(message), HAL_MAX_DELAY);
    }
    
    template<typename T>
    static void print(const char* label, T value) {
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "%s: %d\n", label, value);
        print(buffer);
    }
};

class LEDTask : public Task {
    void run() override {
        while(1) {
            DebugLogger::print("LED ON");
            led.set();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
            
            DebugLogger::print("LED OFF");
            led.reset();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
};

일반적인 빌드 에러 해결

에러원인해결 방법
undefined reference to 'main'main 함수가 없음main.c 또는 main.cpp 확인
error: FreeRTOS.h: No such fileInclude 경로 문제Makefile의 C_INCLUDES 확인
region 'FLASH' overflowed코드가 Flash 크기 초과최적화 옵션 변경 (-O2)
region 'RAM' overflowedHeap/Stack이 RAM 초과configTOTAL_HEAP_SIZE 줄이기
HardFault_Handler잘못된 메모리 접근Stack 크기 증가, Null 포인터 확인

실습 과제

과제 1: 두 개의 LED Task

목표: 서로 다른 주기로 깜빡이는 2개의 LED Task 만들기

요구사항:

  • LED1: PA5, 500ms 간격
  • LED2: PA6, 1000ms 간격
  • 각각 독립적인 Task로 구현

과제 2: 우선순위 실험

목표: 우선순위에 따른 Task 실행 순서 확인

요구사항:

  • 3개의 Task 생성 (우선순위 1, 2, 3)
  • 각 Task에서 UART로 메시지 출력
  • 실행 순서 관찰

과제 3: C++로 변환

목표: C 버전 코드를 C++로 변환

요구사항:

  • Task 클래스 상속
  • GPIO 클래스 활용
  • 객체 지향 설계 적용

요약

오늘 배운 핵심 내용

  1. 개발 환경: VSCode + GCC ARM + Wokwi
  2. 프로젝트 구조: Core, Drivers, Middlewares 디렉토리
  3. 빌드 시스템: Makefile 기반 자동화
  4. 첫 FreeRTOS 프로젝트: LED Task 구현
  5. 디버깅: Serial 출력 및 에러 해결

핵심 용어 정리

용어설명
GCC ARMARM 프로세서용 컴파일러
Toolchain컴파일러, 링커, 디버거 등의 도구 모음
Wokwi웹 기반 임베디드 시뮬레이터
Makefile빌드 자동화 스크립트
Linker Script메모리 맵 정의 파일 (.ld)
ELFExecutable and Linkable Format
HEX/BIN펌웨어 바이너리 형식

복습 문제

  1. VSCode에서 필수로 설치해야 할 확장 프로그램 3가지는?
  2. GCC ARM Toolchain의 역할은 무엇인가요?
  3. STM32 프로젝트에서 Core, Drivers, Middlewares 디렉토리의 역할을 설명하세요.
  4. Makefile에서 C_SOURCES와 C_INCLUDES의 차이는?
  5. FreeRTOSConfig.h에서 가장 중요한 설정 3가지는?
  6. xTaskCreate() 함수의 각 파라미터는 무엇을 의미하나요?
  7. C와 C++로 Task를 구현할 때의 차이점은?
  8. 빌드 시 생성되는 .elf, .hex, .bin 파일의 차이는?

profile
당신의 코딩 메이트

0개의 댓글