Yumud
基于C++20的超轻量级高性能嵌入式开发框架 充分释放个位级成本MCU的巨大潜能
随着科技与技术的不断进步,价格个位数的MCU和前沿现代化语言冲击着现有的设施。在硬件上,价格超1元便已经是32位MCU的天下,很难找到一个死守8位的MCU的理由。在软件上,用C进行开发已经难以满足安全的高标准与健壮性的需求,绝大多数开发者开发的都是面向特定业务直接对接SDK的涂鸦式的代码,这对于广大支持现代编译器的32位MCU显然是没有必要的。硬件已经日新月异,可嵌入式的主流话语开发环境却仍然止步于C89,这不禁让人扼腕————传统的面向过程与四处散落全局变量的思维显然已经不适用于编写出安全高效可复用性强的代码。但我们却不得不承认嵌入式环境确实难寻成熟可靠的嵌入式C++框架。基于这样的想法,我从大二(2023年末)开始打造一个完备的嵌入式框架,并借助它获得了不少国家级别的奖项,现在我将它开源于此。在今天,借助C++20的强大特性,我们可以轻松编写安全,高效复用性强的代码。让嵌入式开发从繁琐枯燥的外设配置变成了调度抽象语义的艺术品,以便于我们编写出更快更清爽更加与现代设计接轨的代码。
本框架专为追求极致性能与资源利用率的低成本 MCU 应用而设计,特别适用于教育、原型验证及工业边缘计算等领域。我们希望推动嵌入式开发向更高层次演进,在保障功能完整性的同时兼顾资源效率与代码质量,助力更多开发者突破硬件限制,创造有价值的智能系统。
一些开发者认为 C++ 和 STL 不适合用于资源受限的嵌入式环境,这种观点往往源于传统开发习惯或对某些特性开销的担忧。诸如iostream,异常等非零成本开销的设施会在一开始就占用数十上百KB的内存空间,而一旦在嵌入式开发中尝试应用C++并不慎用到了这些设施就很容易得出这样的结论。而我们需要做的,仅仅只是择取其中零成本抽象的C++子集。在一线开发长期浸淫中,我也总结了更适合嵌入式开发的C++子集,这是在编写本库的过程中得到大量实践验证的:
-
尽可能提高优化选项,将默认的O0提升到至少O2的标准,同时开启lto优化:默认的O0优化选项会占用大量内存空间,且为了保持函数的调用关系与代码一致,导致很多调用和计算无法内联或乱序执行,同时 lto优化能在最后在全局合并减少重复的代码,使用两者能极大减少二进制文件体积。也正因此,依赖使用Og进行程序追踪从来不是本库鼓励的开发方式,因为它让压缩数倍二进制空间的优化难以正常开展。正确的开发流应当是将副作用最小化,在编译期就将纯函数完成测试,或是利用本库提供的错误调试工具快速定位问题。
-
禁用异常与rtti:异常和rtti等特性是C++中被诟病已久的非零成本抽象特性,更不要说它上来就占用数十k二进制文件而不适合嵌入式开发的原罪。在本库中,异常被禁用,本库对异常安全不做任何处理。更合理的做法可以参考借鉴rust的Result,Option等工具,通过在结果中附加执行情况来为调用方提供错误信息。本框架中实现了rust的Result类,以帮助开发者进行更轻量现代化的错误处理。
-
手动重写newlib:很多嵌入式开发者在开发之初就早早地重写_write函数,开启-u_printf_float链接选项,殊不知这也是一项开销相当大的举动,newlib中提供的c风格设施一方面没有对MCU提供足够的轻量化设计,同时也很难被编译器内联与编译期求值。本框架自主实现的一系列字符串转换函数以及与之对应的流输出类,能够在可读性更好的同时完全避免printf等设施的占用。
-
多使用模板和内联:很多C开发者总是抱着课本上模板会增大二进制文件体积的教条思维去批判C++,而事实上模板很难造成可观的体积膨胀,恰恰相反,编译器在得到模板提供的类型信息后能够减少很多无用的推迟到运行期判决的代码,使得代码能够更好的内联压缩。例如SDK中通常提供传递配置结构体去配置外设的方式,或是获取中断标志位的函数,实际上这些函数内部做了大量不必要的判断与重复读取寄存器,反而加大了二进制文件体积和运行期的负担,通过将部分函数用模板改写,部分代码设置能内联为对寄存器直接进行位修改,既快又轻量。在未来的计划中,为了减少SDK调用,同时减少适配不同产商芯片的移植成本,将逐渐通过结构体位域,编译时求值与模板元函数来实现对外设的配置与映射。
-
避免浮点改用定点:浮点对于编写桌面应用或是使用带有FPU单元的开发者来说触手可得,但大量的芯片却只提供了整数相关的硬件计算单元,而支持高性能的的芯片并不是作者的兴趣所在,在极限低成本的平台发挥超强的效能才是成就所在。故本库提供了内置的定点数支持,参考了德州仪器开源的iqmath库并对它做了改进,使得定点数被改写为一个重载了四则运算的模板类,我们能像用浮点数一样没有语法噪声地使用定点数并获得超高速的超越函数计算,其性能表现在具有浮点单元甚至硬件加速单元的芯片上同样优于浮点计算。
-
多使用STL:STL当然可用,但STL可用不代表我们可以盲目使用,请尽可能避免动态分配的容器。首先,零成本开销的库文件是完全可以使用的,诸如 <type_traits> <ranges> <algorithm> 等库,他们在提供抽象的同时几乎不会引入额外的占用。其次<functional> <quene> <vector> <unordered_map>等库同样可用,但在使用容器前思考背后的动态分配是否有必要,否则尽量使用<array> <span> <string_view>等在栈上进行内存分配或是不具有内存所有权的容器。最后<iostream> <exception>等库应当极力避免使用,他们会引入大量我们不需要的函数,占用大量体积。
成本从来都是我最看重的一点,在资源丰富的硬件平台上做低效率的crud从来不是一个成熟的开发者应该引以为傲的资本,本框架将始终保持对人民币2元以下的单片机的支持并将其作为核心的优化动力。在本框架上,完全可以在超低成本的芯片上编写大量业务代码而不超过芯片的极限,实现低性能MCU的最大化复用。例如,通过本框架可以在短时间内部署大量低成本节点进行组网并实现传感,控制,计算等组件的模块化。真正使得嵌入式变成一门优雅的技术。
下面是一些案例的效果:
| 作用 |
描述 |
开销 |
性能 |
| 步进电机FOC驱动器 |
基于CH32V203(对标F103)芯片实现了完整的Can节点及FOC控制 |
36KB / 64KB |
电流环/速度环/位置环均能以40khz+运行 |
| 机器人视觉主控板 |
基于CH32V307(对标F407)芯片实现了完整的Can主机以及视觉/AI/行为树的运行 |
93KB / 192KB |
能以60fps运行视觉算法,自动完成对目标的搜索 |
| 九轴传感节点 |
基于CH32V203(对标F103)芯片实现了完整的Can节点及串口命令行 |
22KB / 32KB |
6微秒单次解算 |
直接或间接基于本库代码获得的奖项(含金量较低的奖项不列出):
| 名称 |
位次 |
年份 |
| 全国大学生电子设计竞赛 (国赛E题) |
国一 |
2025 |
| 全国大学生电子设计竞赛 (模拟邀请赛) |
国一第二 |
2024 |
| 全国大学生电子设计竞赛(省赛E题) |
省一第一 |
2024 |
| 全国大学生FPGA竞赛 |
国一(最佳创意) |
2024 |
| 工程创新竞赛 |
国二 |
2023 |
本框架的突出特点:
- 实现了完全的跨平台的抽象
- 提供了debug,日志相关的轻量高效使用模板元调优的工具,与stl保持良好兼容
- 使用了模板化的定点数类,编写无语法噪声的定点代码不再是问题
- 提供了大量外设的抽象层,同时保证他们具有易用的接口与内存安全的操作(添加相关仲裁机制)
- 提供了大量嵌入式常用设备的支持,具有风格一致的代码与一众严格进行错误处理的SPI和I2C设备(参考drivers文件夹中的数百种设备)
- 提供了大量的机器人学,图像处理,电力拖动相关的中间件(参考robots/nvcv2/digipw等文件夹)
- 在支持传统的虚函数,CRTP多态的同时也支持类似Rust的Trait特征表述以实现非侵入式多态
- (计划)使用模板元进行硬件平台的快速配置
- (计划)提供异步去中心分布通信框架
固然承认,时至今日Arduino和HAL这两个框架在如今的嵌入式开发中占有相当大的话语权,但与这两者相比,本库依然有不可替代得巨大优势,在此与其对比以更好地呈现本框架的优势。
本库与主流开发框架的比较:
vs arduino:
- 提供了对外设微粒度的调控,同时满足初级开发和高级开发的需求
- 提供了统一的设备管理层,不需要再用宏定义进一步区分各个平台,hal层以上的设施均不再依赖特定的硬件实现
- 提供了对常用总线的原生支持,设备所需的外设资源能够从外部进行依赖注入,而不是定死的全局单例
- C++化程度更高,减少Java或C风格C++的刻板写法,拒绝裸指针托管动态资源等上古C++写法
- 大面积使用模板元编程,以实现代码的复用与泛化,减少样板代码的同时增加代码易用性
- 大量使用stl(参考odrive),提供了与stl组件良好的交互接口
- 注重内存安全,底层几乎不使用任何基于动态内存组件(如arduino滥用toString与字符串拼接等大量涉及动态内存分配的操作)
- 注重错误处理,设备层使用Result进行错误传递,禁止忽视错误(如I2c通信常见错误但不处理会读出异常数据)
vs HAL:
- 做到了真正的硬件抽象,对不同厂家的芯片都提供的近乎相同的api,使得开发不再被厂家裹挟
- 应用层与库函数完全隔离,在hal层以上的设备层/中间件层/应用层完全不需要和厂方提供的C-style SDK进行交互
- 没有传染性的HAL前缀表示函数的命名空间
- 用多态或lambda取代函数指针的使用,减少了编写异步回调代码的心智负担
- 所有外设通过用户自定义宏或是模板元裁剪而不是工程文件管理,提供了更高的自由度
- 将自动化生成代码与用户业务代码隔离,无需危险地与自动生成的代码互作
⚠️声明
目前,本框架尚处于积极开发的早期不稳定阶段,受到完整支持的芯片只有CH32V2/CH32V3系列,在探索新的范式后api随时会发生变更,暂定于以下里程碑完成后发布稳定版本:
- 将驱动层使用C++20的协程进行异步化重构(rtos由于调度开销大且引入大量竞态条件,且不支持零成本抽象,将不在任何未来的版本中被考虑)
- 确定驱动层具有对大部分MCU的适普能力且未来不会发生重大更名变化(要求编写更范化的primitive与api)
- 经验证后与多个不同型号的MCU具有良好互作能力
- 使用如xmake/cmake等构建工具重写文件结构
🔨开发环境
请参考doc/setup_environment.md中的内容
🍴目录
hal 硬件抽象层(Hardware Abstraction Layer)
algo 计算机算法
dsp 信号处理
types 各种类型
nvcv2 图像处理框架
digipw 电力电子与电力传动
drivers 设备驱动
ral 寄存器访问层(Register Access Layer)
concept 外设的抽象基类
sys 系统
src 主要源代码
- 宏定义头文件
- example/testbench
- main文件
robots 机器人学相关代码
评论