Files
STM32F4-Base/docs/嵌入式C语言代码规范(V1.0).md

495 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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 */
```