| name | embedded-c-guidelines |
| description | C99 embedded firmware โ HAL Ops Table pattern, FreeRTOS static tasks/queues/semaphores, ISR-safe ring buffer, no-malloc policy, Unity test with mock drivers, CMake HOST/STM32 dual-build. For writing drivers (GPIO/UART/SPI/I2C), HAL abstraction, RTOS tasks, or MCU firmware (STM32/ESP32). |
| triggers | ["embedded","firmware","HAL","FreeRTOS","MCU","microcontroller","STM32","ESP32","RTOS","ring buffer","Unity test","mock driver","embedded C","peripheral driver","hal_gpio","hal_uart"] |
Embedded C Guidelines
Reference
Base code: base/c-embedded/ โ C99 + CMake + HAL Ops Table + FreeRTOS ํจํด
Core Architecture
src/
โโโ hal/ โ HAL ์ธํฐํ์ด์ค + ๋์คํจ์ฒ (ํ๋ซํผ ๋
๋ฆฝ)
โ โโโ hal_types.h โ ๊ณตํต ํ์
(hal_err_t, GPIO/UART ์ด๊ฑฐํ)
โ โโโ hal_gpio.* โ GPIO HAL
โ โโโ hal_uart.* โ UART HAL
โ โโโ hal_timer.* โ Timer HAL
โโโ drivers/
โ โโโ mock/ โ Host ํ
์คํธ์ฉ Mock (gpio_mock, uart_mock)
โ โโโ stm32/ โ MCU ๋๋ผ์ด๋ฒ stub (์ค ํ๊ฒฝ ์ ๊ต์ฒด)
โโโ tasks/
โ โโโ freertos_types.h โ FreeRTOS ํ์
(real/mock ํธํ)
โ โโโ task_led.* โ ๊ธฐ๋ณธ ํ์คํฌ ํจํด (vTaskDelay ๋ฃจํ)
โ โโโ task_uart_monitor.* โ Queue ํจํด (ISR โ Task)
โโโ utils/
โโโ ring_buffer.* โ ์ ์ Ring Buffer (ISR-safe, no malloc)
โโโ logger.* โ UART ๋๋ฒ๊ทธ ๋ก๊ฑฐ
Pattern 1: HAL Ops Table (Function Pointer Table)
ํต์ฌ ์์น: ์ฑ ์ฝ๋๋ HAL API๋ง ํธ์ถ. ํ๋ซํผ ๋๋ผ์ด๋ฒ๋ ๋ฐํ์์ ๋ฑ๋ก.
typedef struct {
hal_err_t (*init) (uint8_t pin, hal_gpio_dir_t dir);
hal_err_t (*write) (uint8_t pin, hal_gpio_level_t level);
hal_err_t (*read) (uint8_t pin, hal_gpio_level_t *out_level);
hal_err_t (*toggle)(uint8_t pin);
void (*deinit)(uint8_t pin);
} hal_gpio_ops_t;
hal_err_t hal_gpio_register(const hal_gpio_ops_t *ops);
hal_err_t hal_gpio_init (uint8_t pin, hal_gpio_dir_t dir);
hal_err_t hal_gpio_write (uint8_t pin, hal_gpio_level_t level);
hal_err_t hal_gpio_read (uint8_t pin, hal_gpio_level_t *out_level);
hal_err_t hal_gpio_toggle(uint8_t pin);
void hal_gpio_deinit(uint8_t pin);
static const hal_gpio_ops_t *s_ops = NULL;
hal_err_t hal_gpio_register(const hal_gpio_ops_t *ops) {
if (ops == NULL) return HAL_ERR_PARAM;
s_ops = ops;
return HAL_OK;
}
hal_err_t hal_gpio_write(uint8_t pin, hal_gpio_level_t level) {
if (s_ops == NULL || s_ops->write == NULL) return HAL_ERR_NOT_INIT;
return s_ops->write(pin, level);
}
#ifdef PLATFORM_HOST
#define GPIO_OPS gpio_mock_ops
#elif defined(PLATFORM_STM32)
#define GPIO_OPS gpio_stm32_ops
#endif
hal_gpio_register(&GPIO_OPS);
์ MCU ํฌํ
: src/drivers/[platform]/gpio_[platform].c์ ops ๊ตฌํ ํ main.c์์ ๋ฑ๋ก.
Pattern 2: FreeRTOS Task โ Static Allocation
ํต์ฌ ์์น: ํ์คํฌ TCB์ ์คํ ์ ์ ํ ๋น. xTaskCreate ๋์ xTaskCreateStatic ์ฌ์ฉ.
FreeRTOSConfig.h ํ์ ์ค์ โ ์์ผ๋ฉด xTaskCreateStatic ๋งํฌ ์๋ฌ + idle task ๊ตฌํ ํ์:
#define configSUPPORT_STATIC_ALLOCATION 1
void vApplicationGetIdleTaskMemory(StaticTask_t **ppTCB,
StackType_t **ppStack, uint32_t *pSize);
#define TASK_LED_STACK_SIZE (256U)
typedef struct {
uint8_t pin;
uint32_t period_ms;
} task_led_params_t;
void task_led_run(void *pv_params);
extern StaticTask_t g_task_led_tcb;
extern StackType_t g_task_led_stack[TASK_LED_STACK_SIZE];
StaticTask_t g_task_led_tcb;
StackType_t g_task_led_stack[TASK_LED_STACK_SIZE];
void task_led_run(void *pv_params)
{
const task_led_params_t *p = (const task_led_params_t *)pv_params;
uint8_t pin = p ? p->pin : GPIO_LED_STATUS_PIN;
uint32_t period_ms = p ? p->period_ms : 1000U;
hal_err_t err = hal_gpio_init(pin, HAL_GPIO_DIR_OUTPUT);
if (err != HAL_OK) {
vTaskSuspend(NULL);
}
for (;;) {
hal_gpio_write(pin, HAL_GPIO_LEVEL_HIGH);
vTaskDelay(pdMS_TO_TICKS(period_ms / 2U));
hal_gpio_write(pin, HAL_GPIO_LEVEL_LOW);
vTaskDelay(pdMS_TO_TICKS(period_ms / 2U));
}
}
static task_led_params_t s_led_params = {
.pin = GPIO_LED_STATUS_PIN,
.period_ms = 1000U,
};
TaskHandle_t h = xTaskCreateStatic(
task_led_run,
"led",
TASK_LED_STACK_SIZE,
&s_led_params,
2U,
g_task_led_stack,
&g_task_led_tcb
);
configASSERT(h != NULL);
Pattern 3: FreeRTOS Queue โ ISR โ Task
ํต์ฌ ์์น: ISR์ xQueueSendFromISR ์ฌ์ฉ. Task๋ xQueueReceive๋ก ๋ธ๋กํน ๋๊ธฐ.
StaticQueue_t g_uart_monitor_queue_storage;
uart_monitor_msg_t g_uart_monitor_queue_buf[UART_MONITOR_QUEUE_SIZE];
QueueHandle_t g_uart_monitor_queue;
BaseType_t task_uart_monitor_push_byte_from_isr(uint8_t port, uint8_t byte);
QueueHandle_t task_uart_monitor_queue_init(void) {
g_uart_monitor_queue = xQueueCreateStatic(
UART_MONITOR_QUEUE_SIZE,
sizeof(uart_monitor_msg_t),
(uint8_t *)s_queue_buf,
&s_queue_storage
);
return g_uart_monitor_queue;
}
void UART_RX_IRQHandler(void) {
uint8_t byte = platform_uart_read_byte();
uart_monitor_msg_t msg = {
.type = UART_MSG_BYTE_RECEIVED,
.data = byte,
.port = UART_DEBUG_PORT,
};
BaseType_t woken = pdFALSE;
xQueueSendFromISR(g_uart_monitor_queue, &msg, &woken);
portYIELD_FROM_ISR(woken);
}
void task_uart_monitor_run(void *pv_params) {
(void)pv_params;
uart_monitor_msg_t msg;
for (;;) {
if (xQueueReceive(g_uart_monitor_queue, &msg, portMAX_DELAY) == pdPASS) {
switch (msg.type) {
case UART_MSG_BYTE_RECEIVED:
handle_byte(msg.data);
break;
default:
break;
}
}
}
}
Pattern 4: Static Ring Buffer (ISR-Safe)
ํต์ฌ ์์น: ์ ์ ๋ฐฐ์ด๋ง ์ฌ์ฉ. ํฌ๊ธฐ๋ ๋ฐ๋์ 2์ ๊ฑฐ๋ญ์ ๊ณฑ. head/tail์ volatile.
typedef struct {
uint8_t *buf;
size_t capacity;
size_t mask;
volatile size_t head;
volatile size_t tail;
} ring_buf_t;
hal_err_t ring_buf_init (ring_buf_t *rb, uint8_t *storage, size_t size);
hal_err_t ring_buf_write_byte(ring_buf_t *rb, uint8_t byte);
hal_err_t ring_buf_read_byte (ring_buf_t *rb, uint8_t *out);
size_t ring_buf_available (const ring_buf_t *rb);
static uint8_t s_uart_buf[256];
static ring_buf_t s_uart_rb;
void app_init(void) {
ring_buf_init(&s_uart_rb, s_uart_buf, sizeof(s_uart_buf));
}
void UART_RX_IRQHandler(void) {
uint8_t b = uart_read_byte();
ring_buf_write_byte(&s_uart_rb, b);
}
void process_uart(void) {
uint8_t b;
while (ring_buf_read_byte(&s_uart_rb, &b) == HAL_OK) {
handle_byte(b);
}
}
Pattern 5: Unity Test with Mock Driver
๊ตฌ์กฐ: ๊ฐ ํ
์คํธ ํ์ผ์ด ๋
๋ฆฝ ์คํํ์ผ. setUp/tearDown ์ค๋ณต ์ ์ ์ถฉ๋ ์์.
#include "unity.h"
#include "hal_gpio.h"
#include "gpio_mock.h"
void setUp(void) { hal_gpio_register(&gpio_mock_ops); }
void tearDown(void) { hal_gpio_deinit(0U); }
void test_gpio_write_high_sets_level(void) {
hal_gpio_init(0U, HAL_GPIO_DIR_OUTPUT);
hal_gpio_write(0U, HAL_GPIO_LEVEL_HIGH);
hal_gpio_level_t level;
hal_gpio_read(0U, &level);
TEST_ASSERT_EQUAL_INT(HAL_GPIO_LEVEL_HIGH, (int)level);
}
void test_gpio_read_null_returns_error(void) {
hal_gpio_init(0U, HAL_GPIO_DIR_OUTPUT);
hal_err_t err = hal_gpio_read(0U, NULL);
TEST_ASSERT_EQUAL_INT(HAL_ERR_PARAM, (int)err);
}
int main(void) {
UnityBegin("test_hal_gpio.c");
RUN_TEST(test_gpio_write_high_sets_level);
RUN_TEST(test_gpio_read_null_returns_error);
return UnityEnd();
}
extern const hal_gpio_ops_t gpio_mock_ops;
void gpio_mock_set_input_level(uint8_t pin, hal_gpio_level_t level);
hal_gpio_level_t gpio_mock_get_level(uint8_t pin);
# tests/CMakeLists.txt โ ๊ฐ ํ
์คํธ ํ์ผ์ ๋
๋ฆฝ ์คํํ์ผ๋ก ๋น๋
add_executable(test_hal_gpio test_hal_gpio.c)
add_executable(test_ring_buffer test_ring_buffer.c)
target_link_libraries(test_hal_gpio PRIVATE hal drivers unity)
target_link_libraries(test_ring_buffer PRIVATE utils unity)
add_test(NAME gpio_hal COMMAND test_hal_gpio)
add_test(NAME ring_buffer COMMAND test_ring_buffer)
CMake Dual Build
# CMakeLists.txt
option(BUILD_FOR_HOST "Build for host PC (mock drivers + tests)" ON)
if(BUILD_FOR_HOST)
add_compile_definitions(PLATFORM_HOST FREERTOS_MOCK)
set(DRIVER_SRC
src/drivers/mock/gpio_mock.c
src/drivers/mock/uart_mock.c
)
else()
add_compile_definitions(PLATFORM_STM32)
set(DRIVER_SRC
src/drivers/stm32/gpio_stm32.c
src/drivers/stm32/uart_stm32.c
)
# -DCMAKE_TOOLCHAIN_FILE=arm-none-eabi-toolchain.cmake ๋ก ํฌ๋ก์ค ์ปดํ์ผ
endif()
cmake -B build -DBUILD_FOR_HOST=ON -DBUILD_TESTS=ON
cmake --build build
ctest --test-dir build -V
cmake -B build_stm32 \
-DBUILD_FOR_HOST=OFF \
-DCMAKE_TOOLCHAIN_FILE=arm-none-eabi-toolchain.cmake
cmake --build build_stm32
FreeRTOS Mock Types (Host Build)
#ifdef FREERTOS_MOCK
typedef uint32_t TickType_t;
typedef uint32_t StackType_t;
typedef struct { uint8_t dummy[128]; } StaticTask_t;
typedef struct { uint8_t dummy[64]; } StaticQueue_t;
#define pdMS_TO_TICKS(ms) ((TickType_t)(ms))
#define portMAX_DELAY ((TickType_t)0xFFFFFFFFUL)
static inline void vTaskDelay(TickType_t t) { (void)t; }
static inline void vTaskSuspend(TaskHandle_t h){ (void)h; }
#else
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#endif
hal_types.h โ ๊ณตํต ์๋ฌ ์ฝ๋
typedef enum {
HAL_OK = 0,
HAL_ERR_GENERIC = -1,
HAL_ERR_PARAM = -2,
HAL_ERR_BUSY = -3,
HAL_ERR_TIMEOUT = -4,
HAL_ERR_NOT_INIT = -5,
HAL_ERR_OVERFLOW = -6,
} hal_err_t;
Porting to a New Platform
include/app_config.h โ ํ ๋ฒํธ, ์คํ ํฌ๊ธฐ, ํฌํธ ๋ฒํธ ๋ฑ ๋ชจ๋ ์ปค์คํฐ๋ง์ด์ง ์์ ์ ์
src/drivers/[platform]/gpio_[platform].c ์์ฑ โ hal_gpio_ops_t ๊ตฌํ
src/drivers/[platform]/uart_[platform].c ์์ฑ โ hal_uart_ops_t ๊ตฌํ
CMakeLists.txt์ PLATFORM_[PLATFORM] ์กฐ๊ฑด ๋ถ๊ธฐ + #define GPIO_OPS ์ถ๊ฐ
- FreeRTOS ์ค ์์ค ์ฐ๊ฒฐ:
FreeRTOS/Source/ + MCU ํฌํธ + FreeRTOSConfig.h
Anti-Patterns
uint8_t *buf = malloc(256);
static uint8_t buf[256];
uint8_t buf[100];
ring_buf_init(&rb, buf, sizeof(buf));
uint8_t buf[128];
void IRQHandler(void) {
xQueueSend(q, &msg, 0);
}
void IRQHandler(void) {
BaseType_t woken = pdFALSE;
xQueueSendFromISR(q, &msg, &woken);
portYIELD_FROM_ISR(woken);
}
add_executable(test_foo test_foo.c)
add_executable(test_bar test_bar.c)
#include "FreeRTOS.h"
#include "freertos_types.h"
NVIC_SetPriority(USART1_IRQn, 0U);
NVIC_SetPriority(USART1_IRQn, 6U);
Line count: < 500 โ
| Reference: base/c-embedded/ โ
| Anti-patterns: 7 โ