## 为什么嵌入式代码规范如此重要? 嵌入式开发常面临资源受限(RAM/Flash小)、硬件耦合度高、团队协作频繁等挑战。混乱的命名、缺失的注释、冗长的函数会让代码在数月后变得难以理解,甚至引发难以排查的bug。规范不是束缚,而是为代码建立“可读性契约”,让每一位开发者都能快速定位问题、安全修改功能。 ## 命名规范:让名字自解释 ### 1. 变量命名:类型+用途 - 全局变量:使用`g_`前缀,如`g_sysTickCount`。 - 局部变量:小驼峰,如`adcValue`。 - 指针变量:加`p_`前缀,如`p_buffer`。 - 布尔变量:用`is`/`has`/`enable`开头,如`isTimerRunning`。 ```c // 反例 int n; // 含义不明 uint32_t t; // 是时间?计数? // 正例 static uint32_t g_sysTickCount; // 系统节拍计数 uint16_t adcValue; // ADC采样值 bool isUartReady; // UART是否就绪 ``` ### 2. 函数命名:动词+对象 - 模块前缀:如`uart_`、`timer_`、`flash_`。 - 动作明确:`uart_init()`、`timer_start()`、`flash_erase_sector()`。 - 返回值语义化:`uart_read_byte()`返回`int`(-1表示错误),而非`uint8_t`。 ```c // 反例 void process(void); // 处理什么? int get(void); // 获取什么? // 正例 void uart_init(uint32_t baudrate); int uart_read_byte(uint8_t *data); // 返回0成功,-1失败 ``` ### 3. 宏与常量:全大写+下划线 - 宏定义:`#define LED_ON 1` - 枚举常量:`typedef enum { STATE_IDLE, STATE_RUNNING } State_t;` - 避免魔法数字,使用有意义的常量。 ```c #define ADC_CHANNEL_TEMP 0 #define ADC_CHANNEL_BATT 1 #define TIMEOUT_MS 1000 ``` ## 注释规范:解释为什么,而不是什么 ### 1. 文件头注释 每个源文件开头应有版权、作者、日期、功能描述。 ```c /** * @file uart_driver.c * @brief UART底层驱动,支持中断收发 * @author Zhang San * @date 2025-03-01 * @note 依赖:stm32f4xx_hal.h */ ``` ### 2. 函数注释 说明功能、参数、返回值、注意事项,尤其对硬件操作。 ```c /** * @brief 初始化UART1,8N1格式 * @param baudrate: 波特率,如9600, 115200 * @retval 0成功,-1参数错误 */ int uart_init(uint32_t baudrate); ``` ### 3. 关键代码注释 - 解释复杂算法或硬件时序。 - 说明为什么这样写,而非逐行翻译。 ```c // 等待发送完成,否则可能丢失数据(参考手册第12.3节) while (!(UART1->SR & UART_SR_TC)); ``` ### 4. 避免无效注释 ```c // 反例 int a = 0; // 将a赋值为0 // 正例 int retryCount = 0; // 重试次数,超过3次则报错 ``` ## 可维护性实践:让代码易于修改和移植 ### 1. 模块化与信息隐藏 - 每个外设或功能独立成.c/.h文件。 - 头文件只暴露必要接口,内部静态函数用`static`修饰。 - 使用`#ifndef`防止重复包含。 ```c // uart_driver.h #ifndef UART_DRIVER_H #define UART_DRIVER_H #include int uart_init(uint32_t baudrate); int uart_send_byte(uint8_t data); int uart_receive_byte(uint8_t *data); #endif ``` ### 2. 使用typedef简化复杂类型 ```c typedef struct { uint32_t baudrate; uint8_t data_bits; uint8_t stop_bits; uint8_t parity; } UART_Config_t; void uart_init(const UART_Config_t *config); ``` ### 3. 避免硬编码硬件地址 使用寄存器映射或宏定义,便于移植。 ```c // 反例 *(volatile uint32_t *)0x40011000 |= 0x01; // 正例 #define GPIOA_CRL ((volatile uint32_t *)0x40010800) #define GPIO_PIN0 (1 << 0) GPIOA_CRL[0] |= GPIO_PIN0; ``` ### 4. 错误处理与断言 - 函数入口检查参数,非法值返回错误码。 - 关键假设使用`assert()`(调试阶段)。 ```c int uart_send_byte(uint8_t data) { if (uart_is_busy()) { return -1; // 忙,返回错误 } // 发送逻辑 return 0; } ``` ### 5. 代码风格统一 - 缩进:4个空格,不用Tab。 - 大括号:K&R风格(左大括号不换行)。 - 每行不超过80字符,便于阅读。 ```c void timer_isr(void) { if (g_sysTickCount < UINT32_MAX) { g_sysTickCount++; } } ``` ## 完整示例:一个规范的LED控制模块 ```c // led.h #ifndef LED_H #define LED_H #include #define LED_ON 1 #define LED_OFF 0 typedef enum { LED_RED = 0, LED_GREEN, LED_BLUE } LedId_t; void led_init(void); void led_set(LedId_t id, uint8_t state); void led_toggle(LedId_t id); #endif // led.c #include "led.h" #include "stm32f4xx.h" // 假设使用STM32 static void led_hw_set(LedId_t id, uint8_t state); void led_init(void) { // 使能GPIO时钟 RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // 配置PD12-14为输出 GPIOD->MODER &= ~(GPIO_MODER_MODER12 | GPIO_MODER_MODER13 | GPIO_MODER_MODER14); GPIOD->MODER |= (GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0); // 初始化为灭 led_set(LED_RED, LED_OFF); led_set(LED_GREEN, LED_OFF); led_set(LED_BLUE, LED_OFF); } void led_set(LedId_t id, uint8_t state) { if (id > LED_BLUE) { return; // 参数错误 } led_hw_set(id, state); } void led_toggle(LedId_t id) { if (id > LED_BLUE) { return; } // 读取当前状态并翻转 uint8_t current = (GPIOD->ODR >> (12 + id)) & 1; led_hw_set(id, current ? LED_OFF : LED_ON); } static void led_hw_set(LedId_t id, uint8_t state) { uint16_t pin = (GPIO_PIN_12 << id); // 假设GPIO_PIN_12已定义 if (state == LED_ON) { GPIOD->BSRR = pin; } else { GPIOD->BSRR = (uint32_t)pin << 16; } } ``` ## 注意事项与常见陷阱 - **命名一致性**:团队内统一风格,避免混用`uart`和`UART`。 - **注释不要过度**:只注释有深度的逻辑,避免逐行注释。 - **避免全局变量滥用**:全局变量增加耦合,尽量用静态变量+访问函数。 - **头文件自包含**:每个.h应能独立编译,包含所需依赖。 - **版本控制**:代码中不要出现`#if 0`注释掉的代码,用git管理历史。 - **静态分析**:使用`cppcheck`或`PC-Lint`检查潜在问题。 ## 总结 嵌入式代码规范不是一蹴而就,而是持续迭代的过程。从命名、注释到模块化设计,每一步都在提升代码的可维护性。良好的规范能减少调试时间,让团队协作更顺畅,也让你的代码在硬件升级后依然易于复用。建议从今天开始,逐步应用这些实践到你的项目中。