Files
STM32F4-Base/docs/代码规范/嵌入式C语言代码规范_V1.0.md
T
wandering1 511fd30cc6 docs: 清理全仓变更记录流水(43 篇整节删 / 23 篇留一行 / 5 篇保留)
用户裁定:文档只写现状,历史流水一律删掉,过程查 git log。

- 43 篇「变更记录 / 修订记录」整节删除:继电保护规划 12.6 KB、产品
  需求 11.8 KB、项目部署环境 5.5 KB、BSD Socket 指南 5.3 KB、架构
  总览 5.0 KB、实机测试实施指南 3.8 KB 等
- 23 篇别处仍有指向:只留节名 + 一行「本文件不再记流水账,过程查
  git log」,节号与锚点不位移(doc-links 不因此断链)
- 5 篇整表保留(被当唯一证据 / 活台账引用):看门狗设计说明、配置
  文件设计说明、RAM预算台账、内容页架构、本地工具路径环境变量方案
- 规范同步:文档规范 §5.1 改新裁定;测试文档模板规范删示例节与
  登记句;测试开发标准流程 / UI 两篇 / 实机测试实施指南 / 四篇单测
  说明 / 并行任务工作树方案 / 编号规范 的「登记变更记录」改「写进
  正文」
- 技能同步:stale-doc-count-sweep / ui-verify / id-family 三个 SKILL
  正文不再要求追加流水行(顺带消掉 SKILL.md:121「产品需求 §12」
  失效引用)
- 修 5 处悬空指针:采样有效性校验目录锚点、BSD 指南「文末变更
  记录」、docs/README 过期描述、网络自愈架构两处指向面

72 篇文档净减 600 余行;check_doc_links --all --sections-strict 可判
定比例 61% → 78%、失效引用 0;静态门禁 passed=50 failed=0;gitlint
10 条历史提交信息 0 failed。

未提交他人在飞改动:.dsh/skills/keil-build/*(%SystemDrive% 守卫)。
2026-10-28 22:00:34 +08:00

15 KiB
Raw Blame History

1. 文档概述

1.1 目的

本文档旨在统一嵌入式C语言代码的编写风格,提高代码的可读性、可维护性、可移植性和可靠性,降低团队协作成本,减少代码缺陷。

1.2 适用范围

本规范适用于所有基于C语言的嵌入式软件开发项目,包括但不限于51单片机、STM32、ARM、DSP等平台。

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 头文件包含规则

  1. 头文件必须包含头文件保护宏,格式为MODULE_NAME_H(不得使用__双下划线前缀,其为C标准保留标识符)
  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_等前缀开头
  7. 前缀冲突时,存储类前缀(g_/s_)优先于类型前缀(p_/a_),如静态数组s_uart_rx_buffer 示例:
uint8_t g_system_status = 0;      /* 全局系统状态变量 */
static uint16_t s_timer_count = 0; /* 静态定时器计数变量 */
uint8_t *p_data_buffer = NULL;  /* 数据缓冲区指针 */
uint16_t a_adc_value[10] = { 0 }; /* ADC采样值数组 */
bool is_button_pressed = false; /* 按钮是否按下标志 */

3.3 函数命名

  1. 采用小写字母+下划线命名法
  2. 公共函数以模块名作为前缀
  3. 私有函数以模块名+private作为前缀
  4. 函数名应为"动词+名词"结构,明确表示函数功能 示例:
/* 公共函数 */
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. 头文件对外常量使用#define(见2.3.4);源文件内私有常量可用const,命名遵循3.2(小写+下划线) 示例:
/* 头文件对外常量:使用 #define */
#define UART_BAUD_RATE_9600    9600
#define UART_BUFFER_SIZE       128

/* 源文件内私有常量:可用 const,命名遵循 3.2(小写) */
const uint8_t uart_default_data_bits = 8;

3.5 类型定义命名

  1. 采用小写字母+下划线命名法
  2. 以_t作为后缀
  3. 枚举类型成员以模块名作为前缀 示例:
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. 一元运算符前后不加空格 正确示例:
int32_t a = 10;
int32_t b = 0;
int32_t i = 0;
uint8_t c[10] = { 0 };

if (a > 5) {
    b = a + 3;
}
for (i = 0; i < 10; i++) {
    c[i] = 0;
}

错误示例:

int32_t a=10;
int32_t b=0;
int32_t i=0;
uint8_t c[10]={0};
if(a>5){
    b=a+3;
}
for(i=0;i<10;i++){
    c[i]=0;
}

4.3 换行与空行

  1. 每行代码长度不超过80个字符
  2. 函数之间必须空一行
  3. 逻辑上相关的代码块之间可以空一行
  4. 函数内变量声明区和代码执行区之间必须空一行
  5. 长表达式应在运算符处换行,新行与运算符对齐 示例:
int32_t calculate_sum(int32_t a, int32_t b, int32_t c, int32_t d) {
    int32_t sum = 0;

    sum = a + b
        + c
        + d;

    return sum;
}

4.4 大括号使用

  1. 所有包含关系必须加大括号,即使只有一条语句或为空
  2. 左大括号{与前面的语句在同一行,前面加一个空格
  3. 右大括号}单独占一行,与对应的左大括号缩进级别相同
  4. 空函数体的大括号也必须单独占一行 正确示例:
if (condition) {
    do_something();
}

while (1) {
}

错误示例:

if (condition)
    do_something();

while (1) ;

5. 注释规范

5.1 通用注释原则

  1. 注释必须清晰、准确、简洁,与代码保持一致
  2. 解释性注释使用/* */,调试性注释使用//
  3. 注释应解释"为什么这么做",而不是"做了什么"
  4. 代码修改时,必须同步修改相关注释
  5. 禁止注释掉的代码提交到版本库

5.2 文件头注释

每个文件开头必须包含文件头注释,格式见2.1和2.2节。

5.3 函数注释

所有函数(包括私有函数)必须包含完整的函数注释,格式如下:

/*
 * 函数功能:详细描述函数的功能
 * 入口参数:param1 - 参数1说明  类型  取值范围
 *           param2 - 参数2说明  类型  取值范围
 * 出口参数:param3 - 参数3说明  类型  取值范围
 * 返回值:返回值说明  类型  取值范围
 * 限定条件:函数使用的前提条件和限制
 * 函数说明:1. 函数的详细说明
 *           2. 注意事项
 *           3. 其他需要说明的内容
 */

示例:

#define DELAY_INNER_LOOP 123  /* 延时内层循环次数,按12MHz时钟整定 */

/*
 * 函数功能:毫秒级软件延时函数
 * 入口参数:ms - 需要延时的毫秒数  uint32_t  0 - 65535
 * 出口参数:无
 * 返回值:无(void)
 * 限定条件:0 <= ms <= 65535
 * 函数说明:1. 采用空指令循环方式实现延时,会阻塞CPU运行
 *           2. 延时精度依赖系统时钟,默认适配12MHz时钟
 *           3. 系统时钟改变时,需重新调整内层循环次数(DELAY_INNER_LOOP)
 *           4. 当ms为0时,函数立即返回
 */
void delay_ms(uint32_t ms) {
    uint32_t i;
    uint32_t j;

    for (i = 0; i < ms; i++) {
        for (j = 0; j < DELAY_INNER_LOOP; j++) {
        }
    }
}

5.4 变量注释

  1. 全局变量和静态变量必须添加注释
  2. 重要的局部变量应添加注释
  3. 注释可以写在变量定义的同一行或上一行 示例:
/* 系统运行时间,单位:毫秒 */
uint32_t g_system_time = 0;

static uint8_t s_uart_rx_buffer[UART_BUFFER_SIZE];  /* UART接收缓冲区 */

5.5 代码行注释

  1. 关键逻辑代码每一行都要添加注释
  2. 复杂的算法和逻辑必须添加详细注释
  3. 注释应单独占一行(变量定义的行内注释除外,见5.4.3),与被注释代码缩进级别相同 示例:
#define CRC16_INITIAL        0xFFFF  /* CRC-16初值 */
#define CRC16_POLY_REFLECTED 0xA001  /* CRC-16/MCC反射多项式 */
#define BITS_PER_BYTE        8       /* 每字节的位数 */

/* 计算CRC校验值 */
uint16_t crc_calculate(uint8_t *data, uint16_t length) {
    uint16_t crc = CRC16_INITIAL;
    uint16_t i;
    uint16_t j;

    /* 遍历所有数据字节 */
    for (i = 0; i < length; i++) {
        /* 将当前字节与CRC寄存器低8位异或 */
        crc ^= data[i];

        /* 对每个位进行处理 */
        for (j = 0; j < BITS_PER_BYTE; j++) {
            /* 检查最低位是否为1 */
            if (crc & 0x0001) {
                /* 最低位为1,右移并与多项式异或 */
                crc = (crc >> 1) ^ CRC16_POLY_REFLECTED;
            } else {
                /* 最低位为0,直接右移 */
                crc = crc >> 1;
            }
        }
    }

    /* 返回计算得到的CRC值 */
    return crc;
}

6. 编程实践规范

6.1 变量声明与初始化

  1. 变量应在使用前声明,并尽可能在靠近使用的地方声明
  2. 所有变量必须初始化,禁止使用未初始化的变量
  3. 尽量使用局部变量,避免使用全局变量
  4. 指针变量必须初始化为NULL
  5. 使用标准数据类型(uint8_t、int32_t等),避免使用char、int等不确定长度的类型 示例:
#define DATA_BUF_SIZE 100  /* 数据缓冲区大小(字节) */

void function(void) {
    uint8_t status = 0;
    uint16_t count = 0;
    uint8_t *p_data = NULL;

    p_data = (uint8_t *)malloc(DATA_BUF_SIZE);
    if (p_data == NULL) {
        return;
    }

    /* 使用p_data */

    free(p_data);
    p_data = NULL;
}

⚠ 本仓例外(2026-10-23 补,别照抄这段示例):示例里的 malloc/free 是通用 C 写法,本仓 禁止——工具链是 MicroLIB,其 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 只有 512 B, KB 级分配必然返回 NULL,而模拟器(宿主 CRT 堆)完全正常、照不出来(只有真机才炸)。这段示例只教 三件事:声明即初始化、检查 NULL、释放后置 NULL。实际分配一律 pvPortMalloc/vPortFree,或把 分配收进平台后端(MCU 端与宿主端各一份实现,例:simulator/sim/cfg_file_store_sim.c 的 cfg_store_alloc)。机器拦截 = 提交门禁 coding-standard 的 CODE-RULE-20,细则见 docs/门禁说明/coding-standard.md。

6.2 函数设计原则

  1. 函数应遵循"单一职责原则",一个函数只做一件事
  2. 函数长度不宜过长,一般不超过50行
  3. 函数参数不宜过多,一般不超过5个
  4. 有失败路径的函数必须有明确的返回值,用于表示执行状态;无失败路径的纯输出函数可返回void
  5. 避免使用函数参数作为返回值
  6. 私有函数必须声明为static

6.3 控制结构

  1. if语句中,常量应写在比较运算符的右边
  2. switch语句必须包含default分支
  3. 避免使用goto语句,除非用于错误处理
  4. 循环嵌套不宜超过3层 示例:
if (status == 0) {
    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检查开发阶段的错误 示例:
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 完整示例代码

#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 */