14 KiB
14 KiB
1. 文档概述
1.1 目的
本文档旨在统一嵌入式C语言代码的编写风格,提高代码的可读性、可维护性、可移植性和可靠性,降低团队协作成本,减少代码缺陷。
1.2 适用范围
本规范适用于所有基于C语言的嵌入式软件开发项目,包括但不限于51单片机、STM32、ARM、DSP等平台。
1.3 修订记录
| 版本号 | 修订日期 | 修订内容 | 修订人 |
|---|---|---|---|
| V1.0 | 2026-05-11 | 创建初始版本 | 王建锋 |
2. 文件结构规范
2.1 头文件(.h)结构
#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)结构
/*
* 模块名称:模块英文名称
* 模块功能:简要描述模块的主要功能
* 适用平台:列出支持的硬件平台
* 作者:作者姓名
* 创建日期: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 头文件包含规则
- 头文件必须包含头文件保护宏,格式为
__MODULE_NAME_H - 头文件中只包含本模块接口必需的其他头文件
- 源文件中先包含系统头文件,再包含自定义头文件
- 禁止在头文件中定义变量和函数体
- 禁止使用相对路径包含头文件
3. 命名规范
3.1 通用命名原则
- 所有名称必须使用英文,禁止使用拼音和中文
- 名称必须准确反映其实际含义,做到"见名知意"
- 名称长度适中,避免过长或过短
- 禁止使用单个字母作为变量名(循环变量i、j、k除外)
- 禁止使用关键字和保留字作为名称
3.2 变量命名
- 采用小写字母+下划线命名法
- 全局变量以
g_前缀开头 - 静态变量以
s_前缀开头 - 指针变量以
p_前缀开头 - 数组变量以
a_前缀开头 - 布尔变量以
is_、has_、can_等前缀开头 示例:
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 函数命名
- 采用小写字母+下划线命名法
- 公共函数以模块名作为前缀
- 私有函数以模块名+private作为前缀
- 函数名应为"动词+名词"结构,明确表示函数功能 示例:
/* 公共函数 */
void uart_init(uint32_t baud_rate);
uint8_t uart_send_byte(uint8_t data);
/* 私有函数 */
static void uart_private_handle_interrupt(void);
3.4 宏和常量命名
- 采用大写字母+下划线命名法
- 以模块名作为前缀
- 常量优先使用
const定义,而非#define示例:
#define UART_BAUD_RATE_9600 9600
#define UART_BUFFER_SIZE 128
const uint8_t UART_DEFAULT_DATA_BITS = 8;
3.5 类型定义命名
- 采用小写字母+下划线命名法
- 以
_t作为后缀 - 枚举类型成员以模块名作为前缀 示例:
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 缩进
- 使用4个空格进行缩进,禁止使用Tab键
- 所有包含关系的内容必须缩进
- 同一级别的代码保持相同的缩进级别
4.2 空格使用
- 所有赋值语句、比较语句、算术运算符前后必须加空格
- 函数参数列表中,逗号后面必须加空格
- 关键字后面必须加空格
- 括号内部两侧不加空格
- 一元运算符前后不加空格 正确示例:
int a = 10;
if (a > 5) {
b = a + 3;
}
for (i = 0; i < 10; i++) {
c[i] = 0;
}
错误示例:
int a=10;
if(a>5){
b=a+3;
}
for(i=0;i<10;i++){
c[i]=0;
}
4.3 换行与空行
- 每行代码长度不超过80个字符
- 函数之间必须空一行
- 逻辑上相关的代码块之间可以空一行
- 函数内变量声明区和代码执行区之间必须空一行
- 长表达式应在运算符处换行,新行与运算符对齐 示例:
int calculate_sum(int a, int b, int c, int d) {
int sum;
sum = a + b
+ c
+ d;
return sum;
}
4.4 大括号使用
- 所有包含关系必须加大括号,即使只有一条语句或为空
- 左大括号
{与前面的语句在同一行,前面加一个空格 - 右大括号
}单独占一行,与对应的左大括号缩进级别相同 - 空函数体的大括号也必须单独占一行 正确示例:
if (condition) {
do_something();
}
while (1) {
}
错误示例:
if (condition)
do_something();
while (1) ;
5. 注释规范
5.1 通用注释原则
- 注释必须清晰、准确、简洁,与代码保持一致
- 解释性注释使用
/* */,调试性注释使用// - 注释应解释"为什么这么做",而不是"做了什么"
- 代码修改时,必须同步修改相关注释
- 禁止注释掉的代码提交到版本库
5.2 文件头注释
每个文件开头必须包含文件头注释,格式见2.1和2.2节。
5.3 函数注释
所有函数(包括私有函数)必须包含完整的函数注释,格式如下:
/*
* 函数功能:详细描述函数的功能
* 入口参数:param1 - 参数1说明 类型 取值范围
* param2 - 参数2说明 类型 取值范围
* 出口参数:param3 - 参数3说明 类型 取值范围
* 返回值:返回值说明 类型 取值范围
* 限定条件:函数使用的前提条件和限制
* 函数说明:1. 函数的详细说明
* 2. 注意事项
* 3. 其他需要说明的内容
*/
示例:
/*
* 函数功能:毫秒级软件延时函数
* 入口参数: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 变量注释
- 全局变量和静态变量必须添加注释
- 重要的局部变量应添加注释
- 注释可以写在变量定义的同一行或上一行 示例:
/* 系统运行时间,单位:毫秒 */
uint32_t g_system_time = 0;
static uint8_t s_uart_rx_buffer[UART_BUFFER_SIZE]; /* UART接收缓冲区 */
5.5 代码行注释
- 关键逻辑代码每一行都要添加注释
- 复杂的算法和逻辑必须添加详细注释
- 注释应单独占一行,与被注释代码缩进级别相同 示例:
/* 计算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 变量声明与初始化
- 变量应在使用前声明,并尽可能在靠近使用的地方声明
- 所有变量必须初始化,禁止使用未初始化的变量
- 尽量使用局部变量,避免使用全局变量
- 指针变量必须初始化为
NULL - 使用标准数据类型(
uint8_t、int32_t等),避免使用char、int等不确定长度的类型 示例:
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 函数设计原则
- 函数应遵循"单一职责原则",一个函数只做一件事
- 函数长度不宜过长,一般不超过50行
- 函数参数不宜过多,一般不超过5个
- 函数必须有明确的返回值,用于表示执行状态
- 避免使用函数参数作为返回值
- 私有函数必须声明为
static
6.3 控制结构
if语句中,常量应写在比较运算符的左边switch语句必须包含default分支- 避免使用
goto语句,除非用于错误处理 - 循环嵌套不宜超过3层 示例:
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 错误处理
- 所有可能失败的函数都必须检查返回值
- 对输入参数进行合法性检查
- 对指针进行非空检查
- 数组访问时检查下标是否越界
- 使用断言
assert检查开发阶段的错误 示例:
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. 可移植性与安全规范
- 避免使用编译器特有的扩展功能
- 避免使用硬编码的数值,使用宏定义代替
- 注意字节序问题,多字节数据传输时进行字节序转换
- 注意数据类型的长度和符号问题
- 避免使用不安全的函数,如
strcpy、sprintf等,使用strncpy、snprintf代替 - 禁止使用可变参数函数
- 禁止使用递归函数
8. 版本控制规范
- 每次提交必须填写清晰、准确的提交信息
- 提交信息格式:
[模块名] 修改内容说明 - 每次提交只包含一个逻辑修改
- 提交前必须进行代码编译和测试
- 禁止提交编译错误的代码
- 禁止提交调试信息和注释掉的代码
9. 附录
9.1 完整示例代码
#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 */