## 1. 文档概述 ### 1.1 目的 本文档旨在统一嵌入式C语言代码的编写风格,提高代码的可读性、可维护性、可移植性和可靠性,降低团队协作成本,减少代码缺陷。 ### 1.2 适用范围 本规范适用于所有基于C语言的嵌入式软件开发项目,包括但不限于51单片机、STM32、ARM、DSP等平台。 ### 1.3 修订记录 | 版本号 | 修订日期 | 修订内容 | 修订人 | | ---- | ---------- | ------ | --- | | V1.0 | 2026-05-11 | 创建初始版本 | 王建锋 | ## 2. 文件结构规范 ### 2.1 头文件(.h)结构 ```c #ifndef __MODULE_NAME_H #define __MODULE_NAME_H /* * 模块名称:模块英文名称 * 模块功能:简要描述模块的主要功能 * 适用平台:列出支持的硬件平台 * 作者:作者姓名 * 创建日期:YYYY-MM-DD * 修改记录: * YYYY-MM-DD 修改人 修改内容说明 */ #ifdef __cplusplus extern "C" { #endif /* 头文件包含区 - 仅包含本模块必需的头文件 */ #include "stdint.h" /* 宏定义区 */ #define MODULE_NAME_CONSTANT 100 /* 类型定义区 */ typedef enum { MODULE_NAME_STATUS_OK = 0, MODULE_NAME_STATUS_ERROR } module_name_status_t; /* 函数声明区 */ void module_name_init(void); #ifdef __cplusplus } #endif #endif /* __MODULE_NAME_H */ ``` ### 2.2 源文件(.c)结构 ```c /* * 模块名称:模块英文名称 * 模块功能:简要描述模块的主要功能 * 适用平台:列出支持的硬件平台 * 作者:作者姓名 * 创建日期:YYYY-MM-DD * 修改记录: * YYYY-MM-DD 修改人 修改内容说明 */ /* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */ #include "module_name.h" /* 私有宏定义区 */ #define MODULE_NAME_PRIVATE_CONSTANT 200 /* 私有类型定义区 */ typedef struct { uint8_t data; } module_name_private_t; /* 全局变量定义区 - 尽量避免使用全局变量 */ uint8_t g_module_name_global_var = 0; /* 静态变量定义区 */ static module_name_private_t s_module_name_private_var; /* 私有函数声明区 */ static void module_name_private_function(void); /* 函数定义区 - 先写公共函数,再写私有函数 */ void module_name_init(void) { /* 函数实现 */ } static void module_name_private_function(void) { /* 函数实现 */ } ``` ### 2.3 头文件包含规则 1. 头文件必须包含头文件保护宏,格式为`__MODULE_NAME_H` 2. 头文件中只包含本模块接口必需的其他头文件 3. 源文件中先包含系统头文件,再包含自定义头文件 4. 禁止在头文件中定义变量和函数体 5. 禁止使用相对路径包含头文件 ## 3. 命名规范 ### 3.1 通用命名原则 1. 所有名称必须使用英文,禁止使用拼音和中文 2. 名称必须准确反映其实际含义,做到"见名知意" 3. 名称长度适中,避免过长或过短 4. 禁止使用单个字母作为变量名(循环变量i、j、k除外) 5. 禁止使用关键字和保留字作为名称 ### 3.2 变量命名 1. 采用**小写字母+下划线**命名法 2. 全局变量以`g_`前缀开头 3. 静态变量以`s_`前缀开头 4. 指针变量以`p_`前缀开头 5. 数组变量以`a_`前缀开头 6. 布尔变量以`is_`、`has_`、`can_`等前缀开头 **示例:** ```c uint8_t g_system_status; /* 全局系统状态变量 */ static uint16_t s_timer_count; /* 静态定时器计数变量 */ uint8_t *p_data_buffer; /* 数据缓冲区指针 */ uint16_t a_adc_value[10]; /* ADC采样值数组 */ bool is_button_pressed; /* 按钮是否按下标志 */ ``` ### 3.3 函数命名 1. 采用**小写字母+下划线**命名法 2. 公共函数以**模块名**作为前缀 3. 私有函数以**模块名+private**作为前缀 4. 函数名应为"动词+名词"结构,明确表示函数功能 **示例:** ```c /* 公共函数 */ void uart_init(uint32_t baud_rate); uint8_t uart_send_byte(uint8_t data); /* 私有函数 */ static void uart_private_handle_interrupt(void); ``` ### 3.4 宏和常量命名 1. 采用**大写字母+下划线**命名法 2. 以**模块名**作为前缀 3. 常量优先使用`const`定义,而非`#define` **示例:** ```c #define UART_BAUD_RATE_9600 9600 #define UART_BUFFER_SIZE 128 const uint8_t UART_DEFAULT_DATA_BITS = 8; ``` ### 3.5 类型定义命名 1. 采用**小写字母+下划线**命名法 2. 以`_t`作为后缀 3. 枚举类型成员以**模块名**作为前缀 **示例:** ```c typedef enum { UART_STATUS_OK = 0, UART_STATUS_ERROR, UART_STATUS_TIMEOUT } uart_status_t; typedef struct { uint8_t data_bits; uint8_t stop_bits; uint32_t baud_rate; } uart_config_t; ``` ## 4. 格式与排版规范 ### 4.1 缩进 1. 使用**4个空格**进行缩进,禁止使用Tab键 2. 所有包含关系的内容必须缩进 3. 同一级别的代码保持相同的缩进级别 ### 4.2 空格使用 1. 所有赋值语句、比较语句、算术运算符前后必须加空格 2. 函数参数列表中,逗号后面必须加空格 3. 关键字后面必须加空格 4. 括号内部两侧不加空格 5. 一元运算符前后不加空格 **正确示例:** ```c int a = 10; if (a > 5) { b = a + 3; } for (i = 0; i < 10; i++) { c[i] = 0; } ``` **错误示例:** ```c int a=10; if(a>5){ b=a+3; } for(i=0;i<10;i++){ c[i]=0; } ``` ### 4.3 换行与空行 1. 每行代码长度不超过80个字符 2. 函数之间必须空一行 3. 逻辑上相关的代码块之间可以空一行 4. 函数内变量声明区和代码执行区之间必须空一行 5. 长表达式应在运算符处换行,新行与运算符对齐 **示例:** ```c int calculate_sum(int a, int b, int c, int d) { int sum; sum = a + b + c + d; return sum; } ``` ### 4.4 大括号使用 1. **所有包含关系必须加大括号**,即使只有一条语句或为空 2. 左大括号`{`与前面的语句在同一行,前面加一个空格 3. 右大括号`}`单独占一行,与对应的左大括号缩进级别相同 4. 空函数体的大括号也必须单独占一行 **正确示例:** ```c if (condition) { do_something(); } while (1) { } ``` **错误示例:** ```c if (condition) do_something(); while (1) ; ``` ## 5. 注释规范 ### 5.1 通用注释原则 1. 注释必须清晰、准确、简洁,与代码保持一致 2. 解释性注释使用`/* */`,调试性注释使用`//` 3. 注释应解释"为什么这么做",而不是"做了什么" 4. 代码修改时,必须同步修改相关注释 5. 禁止注释掉的代码提交到版本库 ### 5.2 文件头注释 每个文件开头必须包含文件头注释,格式见2.1和2.2节。 ### 5.3 函数注释 所有函数(包括私有函数)必须包含完整的函数注释,格式如下: ```c /* * 函数功能:详细描述函数的功能 * 入口参数:param1 - 参数1说明 类型 取值范围 * param2 - 参数2说明 类型 取值范围 * 出口参数:param3 - 参数3说明 类型 取值范围 * 返回值:返回值说明 类型 取值范围 * 限定条件:函数使用的前提条件和限制 * 函数说明:1. 函数的详细说明 * 2. 注意事项 * 3. 其他需要说明的内容 */ ``` **示例:** ```c /* * 函数功能:毫秒级软件延时函数 * 入口参数:ms - 需要延时的毫秒数 unsigned int 0 - 65535 * 限定条件:0 <= ms <= 65535 * 函数说明:1. 采用空指令循环方式实现延时,会阻塞CPU运行 * 2. 延时精度依赖系统时钟,默认适配12MHz时钟(12T模式) * 3. 系统时钟改变时,需重新调整内层循环次数 * 4. 当ms为0时,函数立即返回 */ void delay_ms(unsigned int ms) { unsigned int i; unsigned int j; for (i = 0; i < ms; i++) { for (j = 0; j < 123; j++) { } } } ``` ### 5.4 变量注释 1. 全局变量和静态变量必须添加注释 2. 重要的局部变量应添加注释 3. 注释可以写在变量定义的同一行或上一行 **示例:** ```c /* 系统运行时间,单位:毫秒 */ uint32_t g_system_time = 0; static uint8_t s_uart_rx_buffer[UART_BUFFER_SIZE]; /* UART接收缓冲区 */ ``` ### 5.5 代码行注释 1. **关键逻辑代码每一行都要添加注释** 2. 复杂的算法和逻辑必须添加详细注释 3. 注释应单独占一行,与被注释代码缩进级别相同 **示例:** ```c /* 计算CRC校验值 */ uint16_t crc_calculate(uint8_t *data, uint16_t length) { uint16_t crc = 0xFFFF; uint16_t i; uint16_t j; /* 遍历所有数据字节 */ for (i = 0; i < length; i++) { /* 将当前字节与CRC寄存器低8位异或 */ crc ^= data[i]; /* 对每个位进行处理 */ for (j = 0; j < 8; j++) { /* 检查最低位是否为1 */ if (crc & 0x0001) { /* 最低位为1,右移并与多项式异或 */ crc = (crc >> 1) ^ 0xA001; } else { /* 最低位为0,直接右移 */ crc = crc >> 1; } } } /* 返回计算得到的CRC值 */ return crc; } ``` ## 6. 编程实践规范 ### 6.1 变量声明与初始化 1. 变量应在使用前声明,并尽可能在靠近使用的地方声明 2. 所有变量必须初始化,禁止使用未初始化的变量 3. 尽量使用局部变量,避免使用全局变量 4. 指针变量必须初始化为`NULL` 5. 使用标准数据类型(`uint8_t`、`int32_t`等),避免使用`char`、`int`等不确定长度的类型 **示例:** ```c void function(void) { uint8_t status = 0; uint16_t count = 0; uint8_t *p_data = NULL; p_data = (uint8_t *)malloc(100); if (p_data == NULL) { return; } /* 使用p_data */ free(p_data); p_data = NULL; } ``` ### 6.2 函数设计原则 1. 函数应遵循"单一职责原则",一个函数只做一件事 2. 函数长度不宜过长,一般不超过50行 3. 函数参数不宜过多,一般不超过5个 4. 函数必须有明确的返回值,用于表示执行状态 5. 避免使用函数参数作为返回值 6. 私有函数必须声明为`static` ### 6.3 控制结构 1. `if`语句中,常量应写在比较运算符的左边 2. `switch`语句必须包含`default`分支 3. 避免使用`goto`语句,除非用于错误处理 4. 循环嵌套不宜超过3层 **示例:** ```c if (0 == status) { do_something(); } switch (command) { case COMMAND_START: start_process(); break; case COMMAND_STOP: stop_process(); break; default: handle_unknown_command(); break; } ``` ### 6.4 错误处理 1. 所有可能失败的函数都必须检查返回值 2. 对输入参数进行合法性检查 3. 对指针进行非空检查 4. 数组访问时检查下标是否越界 5. 使用断言`assert`检查开发阶段的错误 **示例:** ```c uint8_t uart_send_data(uint8_t *data, uint16_t length) { /* 检查输入参数合法性 */ if (data == NULL) { return UART_STATUS_ERROR; } if (length == 0 || length > UART_BUFFER_SIZE) { return UART_STATUS_ERROR; } /* 发送数据 */ return UART_STATUS_OK; } ``` ## 7. 可移植性与安全规范 1. 避免使用编译器特有的扩展功能 2. 避免使用硬编码的数值,使用宏定义代替 3. 注意字节序问题,多字节数据传输时进行字节序转换 4. 注意数据类型的长度和符号问题 5. 避免使用不安全的函数,如`strcpy`、`sprintf`等,使用`strncpy`、`snprintf`代替 6. 禁止使用可变参数函数 7. 禁止使用递归函数 ## 8. 版本控制规范 1. 每次提交必须填写清晰、准确的提交信息 2. 提交信息格式:`[模块名] 修改内容说明` 3. 每次提交只包含一个逻辑修改 4. 提交前必须进行代码编译和测试 5. 禁止提交编译错误的代码 6. 禁止提交调试信息和注释掉的代码 ## 9. 附录 ### 9.1 完整示例代码 ```c #ifndef __LED_H #define __LED_H /* * 模块名称:LED控制模块 * 模块功能:提供LED初始化、点亮、熄灭和翻转功能 * 适用平台:STM32F103系列单片机 * 作者:张三 * 创建日期:2026-05-11 * 修改记录: * 2026-05-11 张三 创建初始版本 */ #ifdef __cplusplus extern "C" { #endif #include "stdint.h" /* LED编号定义 */ #define LED_NUM_1 0 #define LED_NUM_2 1 #define LED_NUM_MAX 2 /* LED状态定义 */ #define LED_OFF 0 #define LED_ON 1 /* * 函数功能:LED初始化函数 * 入口参数:led_num - LED编号 uint8_t 0 - LED_NUM_MAX-1 * 返回值:0 - 成功,其他 - 失败 * 限定条件:无 * 函数说明:初始化LED对应的GPIO引脚为推挽输出模式 */ uint8_t led_init(uint8_t led_num); /* * 函数功能:点亮LED * 入口参数:led_num - LED编号 uint8_t 0 - LED_NUM_MAX-1 * 返回值:0 - 成功,其他 - 失败 * 限定条件:led_init()函数已成功调用 * 函数说明:将LED对应的GPIO引脚置为低电平 */ uint8_t led_on(uint8_t led_num); /* * 函数功能:熄灭LED * 入口参数:led_num - LED编号 uint8_t 0 - LED_NUM_MAX-1 * 返回值:0 - 成功,其他 - 失败 * 限定条件:led_init()函数已成功调用 * 函数说明:将LED对应的GPIO引脚置为高电平 */ uint8_t led_off(uint8_t led_num); /* * 函数功能:翻转LED状态 * 入口参数:led_num - LED编号 uint8_t 0 - LED_NUM_MAX-1 * 返回值:0 - 成功,其他 - 失败 * 限定条件:led_init()函数已成功调用 * 函数说明:将LED对应的GPIO引脚电平取反 */ uint8_t led_toggle(uint8_t led_num); #ifdef __cplusplus } #endif #endif /* __LED_H */ ```