十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

esp-iot-solution 编码规范完全指南:从头文件到 CMake 的工程实践手册

esp-iot-solution 编码规范完全指南:从头文件到 CMake 的工程实践手册 esp-iot-solution 编码规范完全指南从头文件到 CMake 的工程实践手册【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solutionesp-iot-solution 是乐鑫Espressif维护的开源 IoT 组件仓库汇集了音频、显示、蓝牙、传感器、USB 等大量驱动组件与示例工程。面对如此规模庞大、协作密集的代码库统一的编码规范是保证代码可读性、可维护性与可评审性的基石。本文基于仓库内 docs/zh_CN/contribute/style-guide.rst 编写系统梳理该仓库的总体设计原则、目录组织、头文件与注释规范、函数与变量命名体系、排版格式要求以及 CMake 代码风格并结合仓库内真实源码如 i2c_bus.h与 CI 格式化配置 进行佐证。读完本文你将能够写出符合 esp-iot-solution 社区要求的代码并理解仓库在 CI 中如何自动检查与统一代码风格。总体原则五条基础共识在动手写代码之前esp-iot-solution 要求每一位贡献者遵循以下总体原则简洁明了结构清晰代码是写给人和编译器共同阅读的优先保证结构上的清晰。统一风格易于维护全仓库采用一致风格降低跨组件、跨贡献者协作时的认知成本。充分注释易于理解公共接口必须有注释让使用者不读实现也能正确调用。继承 ESP-IDF 已有规范esp-iot-solution 构建在 ESP-IDF 之上凡是 ESP-IDF 已经约定俗成的规则直接继承不另起炉灶。这与文档中明确指出的该部分继承 ESP-IDF 规范的表述一致。继承第三方代码已有规范仓库内含有大量来自第三方的底层驱动如 MPU6050、BME690 等传感器驱动对这类代码保留其原有风格不做强制改造。这五条原则贯穿本文所有小节是理解后续所有具体规则的出发点。目录结构四个顶层目录的分工esp-iot-solution 仓库的顶层目录承担了不同的职责新贡献代码时应当对号入座目录职责components按照功能分类组织组件如 audio、bluetooth、display、sensors、usb 等大类下若存在多级子目录必须包含一个README做综述和索引docsrstreStructuredText格式的文档包括各个组件的使用指南与 API 说明examples总体上按照与组件对应的功能分类例如examples/display、examples/usb、examples/bluetoothtoolsCI 脚本、调试工具例如 tools/ci 下的检查脚本与格式化配置以真实仓库为例components/sensors 下按adc、humiture、imu、gas等外设类型继续分层每个传感器组件目录内部都带有自己的 README 或组件级文档examples/display/gui 则集中了 GUI 相关的示例工程。这种组件与示例一一对应的组织方式让使用者可以快速在 examples 中找到某个组件的可运行参考。头文件规范对外接口的唯一出口头文件是组件的门面esp-iot-solution 对头文件提出了六条硬性要求尽量每一.c对应一个同名的.h文件保持实现与声明的对应关系清晰。单个组件存在多个.h时主要对外头文件的命名尽量与组件名保持一致。例如 components/i2c_bus 的对外接口就集中在与组件同名的 i2c_bus.h 中。头文件中主要放函数声明不放函数实现避免把实现细节暴露给使用者。尽量不在头文件中定义任何形式的变量防止多个编译单元引入重复定义。头文件应按照注释规范对函数接口进行充分注释详细规则见下文注释规范。添加宏定义避免重复引用宏定义名为大写的头文件名加下划线填充#ifndef _IOT_I2C_BUS_H_ #define _IOT_I2C_BUS_H_ #endif仓库中的真实实现与此一致——例如 i2c_bus.h 实际使用_I2C_BUS_H_作为 include guard虽然组件名与守卫宏略有差异这是历史原因但大写文件名加下划线填充的模式被严格遵循。此外为了让 C 代码可以无缝地被 C 工程调用函数声明需要添加extern C修饰以支持 C/C 混合编程#ifdef __cplusplus extern C { #endif //c code #ifdef __cplusplus } #endif在 i2c_bus.h 中可以看到同样的包裹结构这正是 ESP-IDF 体系下头文件的标准写法。注释规范让接口自解释Doxygen 注释框架esp-iot-solution 建议安装 VS Code 插件Doxygen Documentation Generator来自动生成注释框架避免手工编写 Doxygen 标签带来的格式不一致。自动生成的注释框架如下/** * brief * * param port * param conf * return i2c_bus_handle_t */ i2c_bus_handle_t iot_i2c_bus_create(i2c_port_t port, i2c_config_t* conf);其中brief 描述函数功能、性能或用法param 描述输入输出参数return 描述函数返回值。文档还要求补充完整信息和参数方向标注[in]/[out]/** * brief Create an I2C bus instance then return a handle if created successfully. * note Each I2C bus works in a singleton mode, which means for an i2c port only one group parameter works. When * iot_i2c_bus_create is called more than one time for the same i2c port, following parameter will override the previous one. * * param[in] port I2C port number * param[in] conf Pointer to I2C parameters * return i2c_bus_handle_t Return the I2C bus handle if created successfully, return NULL if failed. */ i2c_bus_handle_t iot_i2c_bus_create(i2c_port_t port, i2c_config_t* conf);这个例子同时体现了两条隐性要求note 用于说明容易被忽略的语义细节如单例模式的覆盖行为返回值注释必须同时描述成功与失败两种情形返回 handle 或 NULL。注释写作要求注释中避免使用单词缩写除非该缩写已是行业共识。版权声明注释新代码需携带 SPDX 版权声明第三方代码请保留其原始版权声明信息/* * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */仓库中几乎所有源码文件包括 i2c_bus.h 在内都以 SPDX 头开头这是 ESP-IDF 生态的通用做法也便于 CI 进行版权合规检查。函数规范命名、作用域与重入性函数的组织与命名直接决定组件的可复用性esp-iot-solution 要求多处重复使用的代码尽量设计为函数避免复制粘贴。作用域仅限于当前文件的函数必须声明为static。设计使用静态全局变量、静态局部变量的函数时需要考虑重入问题尽量在一个固定函数中操作静态全局变量。如果函数存在重入或线程安全问题需在注释中说明。同一组件内的公有函数名应保持同一前缀形成命名空间效果。函数名统一使用snake_case格式只使用小写字母单词之间加_。函数命名遵循分层命名指引应保持与已有代码风格一致不严格约束函数名格式函数示例说明iot_type_xxxiot_sensor_xxx;iot_board_xxx;iot_storage_...高度抽象的 iot 组件type_xxximu_xxx;light_xxx;eeprom_xxx对一类外设的抽象name_xxxmpu6050_xxx;底层 driver由于可能来自第三方不约束函数名xxx_creat / xxx_delete—创建和销毁xxx_read / xxx_write—数据操作这套命名体系的价值在于读者仅凭函数前缀即可判断其抽象层级——iot_前缀代表跨平台的高度抽象组件type_前缀代表某类外设的通用接口而具体的name_芯片型号前缀则说明它直连底层寄存器、大概率继承自第三方驱动。变量规范作用域、前缀与命名变量是代码中数量最多、也最容易被忽视的部分。esp-iot-solution 的变量规范要点如下避免使用全局变量确需使用时声明为静态全局变量并通过get_、set_等接口进行变量操作。作用域仅限于当前文件的变量必须声明为static。静态全局变量添加g_前缀静态局部变量添加s_前缀。局部变量设计大小时应考虑栈溢出问题嵌入式环境的栈资源有限大数组尤其需要谨慎。任何变量定义时必须赋初值杜绝未初始化读取。变量功能要明确避免将单一变量做多个用途。句柄类型变量在对象销毁后应重新赋值为 NULL防止悬空指针。变量统一使用snake_case格式只使用小写字母单词之间加_。避免不必要的缩写例如data不必缩写为dat。变量应尽量使用有意义的词语或已经达成共识的符号与词语缩写。变量命名指引汇总如下类型规范示例全局变量避免使用x静态全局变量static标识g_前缀赋初值static uint32_t g_connect_num 0;静态局部变量static标识s_前缀赋初值static uint32_t s_connect_num 0;迭代计数变量使用通用的ijk—常用缩写遵循 abbreviations-in-code 社区共识addr,buf,cfg,cmd,ctrl常用缩写对照表缩写全称缩写全称缩写全称缩写全称addraddressididentifierlenlengthptrpointerbufbufferinfoinformationobjobjectretreturncfgconfighdrheaderparamparametertemptemporary、temperaturecmdcommandinitinitializepospositiontstimestamp类型定义规范_t后缀与枚举格式类型名使用snake_case格式并加_t后缀typedef int signed_32_bit_t;枚举应通过 typedef 以下列方式定义枚举成员统一使用大写下划线命名并带有模块前缀typedef enum { MODULE_FOO_ONE, MODULE_FOO_TWO, MODULE_FOO_THREE } module_foo_t;这种成员带模块前缀 类型带_t后缀的组合在仓库中随处可见——例如 i2c_bus.h 中软件 I2C 端口枚举I2C_NUM_SW_0、I2C_NUM_SW_1等即采用I2C_NUM_SW_前缀类型名为i2c_sw_port_t。格式与排版规范格式与排版部分整体继承 ESP-IDF 编码规范核心要点如下。1. 缩进每个缩进层使用4 个空格不要使用制表符Tab缩进。将编辑器配置为每次按 Tab 键时发出 4 个空格。仓库的 astyle 配置 tools/ci/astyle-rules.yml 中同样以--indentspaces4 --convert-tabs强制执行该规则。2. 垂直间隔在函数之间放置一个空行不要以空行开始或结束函数体void function1() { do_one_thing(); do_another_thing(); // INCORRECT, dont place empty line here } // place empty line here void function2() { // INCORRECT, dont use an empty line here int var 0; while (var SOME_CONSTANT) { do_stuff(var); } }只要不严重影响可读性最大行长度为120 个字符。3. 水平间隔总是在条件和循环关键字之后添加单个空格if (condition) { // correct // ... } switch (n) { // correct case 0: // ... } for(int i 0; i CONST; i) { // INCORRECT // ... }在二元操作符两端添加单个空格一元运算符不需要空格乘除法运算符之间可以省略空格const int y y0 (x - x0) * (y1 - y0) / (x1 - x0); // correct const int y y0 (x - x0)*(y1 - y0)/(x1 - x0); // also okay int y_cur -y; // correct y_cur; const int y y0(x-x0)*(y1-y0)/(x1-x0); // INCORRECT.和-操作符周围不需要任何空格。可以在一行内添加水平间隔来对齐函数参数以提高可读性gpio_matrix_in(PIN_CAM_D6, I2S0I_DATA_IN14_IDX, false); gpio_matrix_in(PIN_CAM_D7, I2S0I_DATA_IN15_IDX, false); gpio_matrix_in(PIN_CAM_HREF, I2S0I_H_ENABLE_IDX, false); gpio_matrix_in(PIN_CAM_PCLK, I2S0I_DATA_IN15_IDX, false);但要注意如果有人新增一行且第一个参数是更长的标识符例如PIN_CAM_VSYNC所有行都需重新对齐产生无意义的 diff。因此尽量少使用这种对齐尤其当该列后续可能继续增行时。不要使用制表符进行水平对齐不要在行尾添加尾随空格。4. 括号函数定义的大括号放在单独的一行// This is correct: void function(int arg) { } // NOT like this: void function(int arg) { }函数体内将左大括号与条件语句和循环语句放在同一行if (condition) { do_one(); } else if (other_condition) { do_two(); }这种函数定义大括号独占一行、控制语句大括号跟行的混合风格即 astyle 配置中的--styleotbsOne True Brace Style配合--attach-namespaces --attach-classes。5. 注释的合理使用//用于单行注释多行注释可以逐行使用//或使用/* */块注释。以下是几条与代码质量直接相关的注意点不要使用注释来禁用某些功能void init_something() { setup_dma(); // load_resources(); // WHY is this thing commented, asks the reader? start_timer(); }不再需要的代码请完全删除需要时可以在 git 历史中找回若因临时原因禁用某调用且计划将来恢复请在相邻行添加解释void init_something() { setup_dma(); // TODO: we should load resources here, but loader is not fully integrated yet. // load_resources(); start_timer(); }#if 0 ... #endif块同理不使用就完全删除否则必须添加注释说明为何禁用。不要用#if 0或注释来存储将来可能需要的代码段。不要添加关于作者和修改日期的琐碎注释git blame 可以查到每一行的修改者例如下面这种注释只会徒增噪音void init_something() { setup_dma(); // XXX add 2016-09-01 init_dma_list(); fill_dma_item(0); // end XXX add start_timer(); }6. 代码行的结束统一 LF 行尾commit 中只能包含以 LFUnix 风格结尾的文件。Windows 用户可以通过设置 git 的core.autocrlf实现本地 checkout 使用 CRLF、commit 时自动转换为 LF。由于 MSYS2 使用 Unix 风格行尾编辑 ESP-IDF 系源码时通常更简单的方式是直接将文本编辑器配置为使用 LF 结尾。如果分支中意外提交了 LF 结尾的文件可以在 MSYS2 或 Unix 终端运行以下命令批量转换请先切换到 IDF 工作目录并确认已 checkout 正确的分支末尾的master可替换为其他分支名git rebase --exec git diff-tree --no-commit-id --name-only -r HEAD | xargs dos2unix git commit -a --amend --no-edit --allow-empty master要修正单个提交可以运行dos2unix FILENAME然后git commit --amend7. 用 astyle 格式化代码仓库使用astyle程序自动格式化代码。CI 侧的格式化参数集中定义在 tools/ci/astyle-rules.yml 的DEFAULT节点中--styleotbs --attach-namespaces --attach-classes --indentspaces4 --convert-tabs --align-referencename --keep-one-line-statements --pad-header --pad-oper --unpad-paren --max-continuation-indent120这些参数与本文前面描述的缩进4 空格、操作符两侧空格--pad-header --pad-oper、大括号风格--styleotbs、禁制表符--convert-tabs等规则一一对应说明格式规范与工具链是强绑定的。同时该配置文件通过not_formatted_permanent列出了不参与格式化的例外文件——主要是第三方上游源码如components/utilities/xz/、BME690 传感器驱动、生成文件如docs/doxygen-known-warnings.txt、docs/sphinx-known-warnings.txt以及部分 UI/字体文件这与继承第三方代码已有规范的总体原则一致。格式化单个文件可运行仓库内的 tools/ci/format.sh编码规范文档中写作tools/format.sh实际脚本位于tools/ci目录下tools/ci/format.sh components/my_component/file.c使用建议从头编写一个新文件或进行完全重写时可以重新格式化整个文件如果只是修改文件的一小部分不要重新格式化未修改的代码以最小化 diff方便他人评审。CMake 代码风格CMake 构建脚本同样有明确的风格约定缩进是 4 个空格。最大行长为 120 个字符分割行时尽量集中于可读性例如在单独的行上配对关键字/参数对。不要在endforeach()、endif()等命令后面的可选括号中放入任何内容。对命令、函数和宏名使用小写with_underscores。局部作用域的变量使用小写with_underscores。全局作用域的变量使用大写WITH_UNDERSCORES。其他规则遵循 cmake-lint 项目的默认设置。这一约定与 C 代码变量区分g_/s_前缀的思路一脉相承通过命名风格直接传达变量的作用域信息减少阅读负担。规范落地CI 如何守护代码风格编码规范并非停留在文档层面esp-iot-solution 通过 CI 工具链将其自动化。在 tools/ci 目录下可以看到配套的检查脚本format.sh本地格式化脚本用于按 astyle-rules.yml 中的参数重排代码check_components.py、check_readme_links.py、check_executables.py 等负责组件结构、README 链接、可执行文件权限等维度的自动化检查check_copyright_config.yaml 与 check_copyright_ignore.txt用于校验源码中的版权声明与本文版权声明注释一节呼应——SPDX 头是 CI 检查的硬性要求。对贡献者而言正确的提交姿势是先按本文规则编写代码提交前运行 format.sh 或让 pre-commit 钩子完成格式化确保 diff 干净、行尾为 LF、版权声明完整再发起合并请求。总结esp-iot-solution 的编码规范是一套原则 细则 工具链三位一体的完整体系总体五原则定义了价值取向目录、头文件、注释、函数、变量、类型定义六类规范约束了代码的静态形态格式与排版规范缩进、空行、括号、注释使用、行尾、astyle 工具与 CMake 风格则保证了全仓库视觉上的统一。对于想要为仓库贡献组件的开发者建议对照本文逐项自检对于想要在自有 ESP-IDF 工程中引入这套规范团队的开发者tools/ci/astyle-rules.yml 中现成的 astyle 参数与 format.sh 脚本同样可以直接借鉴让多人的嵌入式 C 工程告别风格之争。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表