| 도구 | 용도 | 비고 |
|---|---|---|
| VSCode | 코드 편집기 | 가볍고 확장성이 좋음 |
| GCC ARM Toolchain | 컴파일러 | arm-none-eabi-gcc |
| Make | 빌드 도구 | Makefile 실행 |
| Wokwi | 시뮬레이터 | 웹 기반, 하드웨어 불필요 |
| OpenOCD | 디버거 (선택) | 실제 하드웨어 사용 시 |
코드 작성 (VSCode)
↓
컴파일 (GCC ARM)
↓
링킹 (ld)
↓
바이너리 생성 (.elf, .bin, .hex)
↓
시뮬레이션 (Wokwi) 또는 다운로드 (실제 보드)
↓
디버깅 및 테스트
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
- 괄호 쌍 색상 구분
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"
}
# 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
# 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
# 1. Homebrew 사용
brew install --cask gcc-arm-embedded
# 2. 설치 확인
arm-none-eabi-gcc --version
Windows:
# MinGW 또는 Cygwin 설치
# 또는 chocolatey 사용
choco install make
Linux:
sudo apt install build-essential
macOS:
# Xcode Command Line Tools에 포함
xcode-select --install
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.h | FreeRTOS 설정 (Tick, Heap, Priority 등) |
| stm32f4xx_it.c | 인터럽트 핸들러 (SysTick, PendSV 등) |
| STM32F401RETx_FLASH.ld | 메모리 맵 정의 (Flash, RAM 주소) |
| Makefile | 빌드 자동화 스크립트 |
1. Wokwi 웹사이트 접속
https://wokwi.com/
2. 새 프로젝트 생성
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
간단한 LED 깜빡임 Task 구현
#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);
}
#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);
}
#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 */
# 프로젝트 이름
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)
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
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 file | Include 경로 문제 | Makefile의 C_INCLUDES 확인 |
region 'FLASH' overflowed | 코드가 Flash 크기 초과 | 최적화 옵션 변경 (-O2) |
region 'RAM' overflowed | Heap/Stack이 RAM 초과 | configTOTAL_HEAP_SIZE 줄이기 |
HardFault_Handler | 잘못된 메모리 접근 | Stack 크기 증가, Null 포인터 확인 |
목표: 서로 다른 주기로 깜빡이는 2개의 LED Task 만들기
요구사항:
목표: 우선순위에 따른 Task 실행 순서 확인
요구사항:
목표: C 버전 코드를 C++로 변환
요구사항:
| 용어 | 설명 |
|---|---|
| GCC ARM | ARM 프로세서용 컴파일러 |
| Toolchain | 컴파일러, 링커, 디버거 등의 도구 모음 |
| Wokwi | 웹 기반 임베디드 시뮬레이터 |
| Makefile | 빌드 자동화 스크립트 |
| Linker Script | 메모리 맵 정의 파일 (.ld) |
| ELF | Executable and Linkable Format |
| HEX/BIN | 펌웨어 바이너리 형식 |