嵌入式项目规范
本文讨论了嵌入式项目的规范,适用于单片机和16位及32位嵌入式系统的新项目代码编写,涵盖语言使用、内存管理、项目结构、代码规范、注释规范和代码建议。
适用范围
仅适用于对单片机和16位及32位嵌入式系统的代码编写,例如STM32、ESP32工程。不适用于树莓派、RK3588等带Linux的嵌入式系统。以下仅针对于新项目。
重要要点
-
限制使用抽象语言:包括但不限于C++、Java、JavaScript,既是为了团队考虑(C++入门门槛较高,不方便后期维护),也是为了安全性考虑。面向对象和垃圾回收机制并不适用于嵌入式系统。C++中的new运算符涉及到动态内存分配(标准libc中的malloc),不能使用,见第二条。不建议使用micropython,尤其是性能及其敏感的部分,例如轮腿直立算法部分,底层驱动部分,Python的执行速度较低(性能要求高)而且不可预测,因此会丢失实时性。
-
限制使用libc库中的malloc:libc库中的malloc对内存碎片的回收不够激进,会导致产生内存碎片。由于嵌入式系统通常没有内存管理单元(MMU)没有高级的操作系统内存管理,大量的内存碎片会导致malloc最终无法分配内存。malloc函数的时间复杂度不固定,平均25us,最坏情况高达250us,严禁在高频率调用场景和中断服务函数使用malloc。如果要使用malloc请使用内存管理库或者rtos来解决,也可以自己设计内存池,尽量使用静态变量(static关键字)。
-
严禁中断服务函数编写高耗时任务:禁止使用耗时函数例如malloc和free。严禁使用C++的所有stl容器和new delete关键字。
项目结构
结构规范
对于STM32,一律使用STM32CubeMX生成的项目结构。例如以下的cmake工程项目结构,组员主要修改和编辑User BSP文件夹。
结构建议
- 建议所有电控组和算法组编写代码时使用git工具来管理代码,用于多人协作和备份。
- 所有项目文件每日备份。
代码规范
命名规范(google规范修改)
- 通用命名:尽量用描述性语言,尽量用全称,除非包含四个或四个以上的单词,否则不要用下划线,也就是命名时尽量不要超过三个单词。
- 不使用拼音:除了部分中文名可以用拼音之外,所有名称都应该用英文,绝对不允许使用拼音首字母简写。
- 长短名与作用域:作用域比较大的变量应该用长名字,作用域小的用短名字,不超过几行的临时变量可以用i, j, k, m, n等。
- 变量名:变量名用小驼峰:line,savingAccount,比较长的变量名可换下划线:heart_beat_time_out_times。
- 函数名:函数名用下划线命名法:get_Name(),compute_total_width()。
- 缩写命名:命名中的缩写首字母大写:exportHtmlSource(),而不是 exportHTMLSource()。
- 类型名:类型使用大驼峰命名法(建议用于C++),对于typedef的结构体类型推荐使用xx_xx_的格式(推荐用于C)。
typedef struct {
lv_obj_t *thisPage;
lv_obj_t *flowChartButton; // 流程图按钮
lv_obj_t *flowChartLabel; // 流程图标签
lv_obj_t *exampleButton; // 临床案例按钮
lv_obj_t *exampleLabel; // 临床案例标签
lv_style_t *pageStyle; // 页面的字体外观配置
flow_chart_page_t* flowChart;
lv_event_cb_t exampleButton_cb;
} flow_chart_page_t;
注释规范(javadoc规范)
- 注释参考一律使用javadoc规范(因为各个现代IDE对该格式支持比较好比较智能,例如clion和stm32cubeIDE)必须在头文件对函数进行以下样式的注释。
/**
* 该函数用于快速创建带标签的按钮
* @param[in] parent 父级控件
* @param[out] outlabel 创建的标签
* @param[in] text 按钮的标签文字
* @param[in] callback 按钮按下的事件回调函数
* @param[in] callback 按钮按下的事件回调函数
* @param[in] style 按钮的样式
* @return 返回创建的按钮指针
*/
lv_obj_t* fast_button_create_style(lv_obj_t* parent, lv_obj_t** outlabel, const char* text, lv_event_cb_t callback);
代码建议(NASA规范)
- 简化控制流:避免使用goto语句、setjmp或longjmp结构以及递归。这样可以提高代码的验证能力和清晰度。
- 循环上限:所有循环必须有固定的上限,确保代码不会失控。
- 禁止动态内存分配:初始化后不再使用动态内存分配,以避免内存泄漏和性能问题。
- 函数长度限制:任何函数的长度不应超过60行,以确保每个函数都是一个可理解和验证的逻辑单元。
- 断言密度:每个函数至少应有两个断言,用于检查不应发生的异常情况。
- 最小作用域声明:数据对象应在尽可能小的作用域级别声明,以支持数据隐藏。
- 检查返回值和参数:所有非void函数的返回值必须检查,并且每个函数内部必须检查参数的有效性。
- 限制多层指针使用:指针的使用应受到限制,通常只允许不超过一层的解引用。
