随着科技与技术的不断进步,价格个位数的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中的内容
-
GPIO(IO相关代码)
- bitband(位带操作)
- Gpio(单个IO)
- Port(IO端口)
- VirtualPort(虚拟IO端口)(用于将多个片上与片外端口按顺序绑定至一个IO端口上)
-
UART(已验证6·000·000波特率的长时间压力测试)
- 对基本输入输出流的支持
- 环形缓冲区支持
- DMA/中断支持
- LinBus
- 智能卡
-
SPI(已验证144Mhz的长时间压力测试)
- 一般数据收发
- DMA数据收发
- 虚拟片选集线器
- 软件SPI
-
I2C(以验证5.4Mhz的软件I2C)
- 软件I2C
- 硬件I2C
- 一般数据收发
- DMA数据收发
- SMbus
- PMbus
-
I2S(以验证软件I2S只发)
- 软件I2S只发
- 硬件I2S
- I2S读取
-
CAN(控制局域网总线)
- 信箱及FIFO驱动
- 输入输出环形缓冲区
- 标准ID与拓展ID下Msg发送接收
- 标准ID下的过滤器
- 基于模板的自定义过滤器
- CANFD
-
Adc
- 任意通道单路信号采集
- 任意通道多路信号采集
- DMA绑定API
- 片上温度/参考电压采集
- 虚拟ADCChannel类
-
DVP
- DVP总线驱动
-
CRC
-
OPA
-
NVIC
-
EXTI
-
FLASH
- 快速FLASH读写
-
TIM(定时器相关代码)
-
中断与回调函数绑定
-
编码器模式
-
PWMModule PWM输出集线器
-
PwmIntf PWM输出概念
- GPIOPwm 使用GPIO配合定时触发模拟pwm
- TimerOC(定时器输出)
- TimerOC
-
CaptureChannel 输入捕获概念
- ExtiCapture 基于EXTI的输入捕获
- TimerIC(定时器输入捕获)
-
-
TCP/UDP
-
USB
- USBFS USBFS虚拟串口驱动
- utils
-
BLE
-
random 伪随机数发生器
-
encrypt 加密
- aes
- base64
- crc
- curve25519
- lz77
- sha256
-
interpolation 插值
- 线性插值
- 三次插值
- 四次插值
- 多项式插值
- 弧形插值
-
优化算法
- 粒子群算法
-
astar A*寻路算法
-
constexprmath 编译期超越函数计算
-
控制器
-
PID 控制器
- pd控制器
- pi控制器
- 2型控制器
- 3型控制器
-
模糊PID控制器
-
LQR 控制器
- DARE方程求解
-
MPC 控制器
-
ADRC 控制器
- 跟踪微分器
- 2...4阶微分跟踪器
- 指令整形器
- 一维指令整形器
- 二维指令整形器
- 拓展观测器
- 线性拓展观测器
- 非线性拓展观测器
- 跟踪微分器
-
零差拍控制器
- dq轴零拍差控制器
- 转速零拍差控制器
-
EFC 控制器
-
滑模控制器
-
-
fft(未测试)
-
滤波器
-
巴特沃斯滤波器
- coeff计算
- 低通滤波器
- 高通滤波器
- 带通滤波器
- 阻带滤波器
- 四阶零相移滤波器
-
一阶滤波器
- 低通滤波器
- 高通滤波器
-
自制滤波器
- 施密特触发器
- 抖动滤波器
- 毛刺滤波器
-
通用FIR 滤波器
-
卡尔曼滤波器
- 一维卡尔曼滤波器
- 通用卡尔曼滤波器
-
-
cordic 三角运算单元
-
电机控制
- 侦测器
- 堵转侦测器
- 滤波器
- 陷波滤波器
- 二阶滤波器
- 无感观测器
- 滑模观测器
- [] 超螺旋滑模观测器
- 非线性磁链观测器
- 龙伯格观测器
- 高频注入观测器
- 正弦高频注入观测器
- 方波高频注入观测器
- 滑模观测器
- 位置微分跟踪器
- 相位补偿器
- 侦测器
-
信号发生
- dtmf
-
合成器
- 梳状滤波器
- 延迟线
- 变频平滑器
- 全通滤波器
- 音符
- 复音合成器
- disperser
-
lti 基于ABCD矩阵描述的线性时不变系统
-
状态向量
-
Z变换
-
aabb 三维包围盒
-
Arc2D 二维弧形元素
-
basis 三维旋转矩阵
-
颜色
- RGBA,RGB,sRGB
- RGB565,RGB232,RGB888,RGBA32,Binary,Gray
-
姿态
- 二维相机
- 二维位姿
- 三维位姿
- dh 连接件DH参数
- 二维旋转
- 三维旋转
- 二维扭转
- 三维扭转
-
区域
- AABB
- 二维直线
- 投影矩形
- 平面
- 区间
- 二维射线
- 三维射线
- 二维矩形
- 二维线段
-
变换
- 三维旋转矩阵
- 欧拉角
- 二维变换矩阵
- 三维变换矩阵
-
Image 图像类
- Font 字体类
- 英文字体
- 中文字体
- PackedImage 压缩二值化图片
- Painter 绘图算法驱动
- Font 字体类
-
向量
- quat 四元数
- polar 极坐标
- complex 复数
- vector2 二维向量
- vector3 三维向量
-
颜色空间转换
- 布尔运算
-
形态学
-
自适应阈值化
-
CANNY
-
卷积核
-
洪水填充算法
-
边线提取
-
开闭运算
-
仿射变换
-
模板匹配
-
Mnist深度学习识别
-
apriltag识别
-
霍夫变换
-
-
软开关Buck控制器
-
太阳能
- MPPT
-
spll
- spll_1ph_notch
- spll_1ph_sogi_fll
- spll_1ph_sogi
- spll_3ph_ddsrf
- spll_3ph_srf
-
控制器
- 定点Pi电流控制器
- 准Pr控制器
-
PWM
- SVM
- DPWM
-
坐标
- alpha-beta坐标
- dq坐标
- uvw坐标
-
斩波生成
- 交错三相斩波生成
- 交错双相斩波生成
-
编译期系数整定
- pi电流环系数整定
-
ADC
- AD7606
- ADS112C04
- ADS1115
- FDC2X1X
- HX711
- INA219
- INA226
- INA228
- INA3221
- SGM58031
- TM7705
-
音频
- JQ8900
-
摄像头
- MT9V034
- OV2640
-
一般IO
- LED
- 模拟LED
- 按键
- 按键矩阵
- LED
-
converter 变换器
- AW32001
- AXP192
- AXP2101
- DRV2605L
- MP5980
- MP2980
- MP6570
- SC8721
- SC8815
-
DAC
- TM8211
- MCP4725
- DAC128S085
-
屏幕
- SSD1306(OLED)
- ST7789(tft)
-
编码器
-
磁编码器
- AS5047
- AS5600
- KTH7823
- VCE2755/VCE2758
- MA730
- MT6701
- MT6816
- MT6825
- MT6826S
- MT6835
- TLE5012
-
AB编码器
-
-
栅极驱动器
- DRV8323
- DRV8301
- MP6540
-
HID设备
- CH455
- FT6336
- GT911
- HT16K33
- PS2手柄
- TCA8418
- TM1637
- TM1650
- TM1668
-
环境传感
- BH1750
- MAX31855
- MAX90333
- MAX90393
- MAX90640
- TCS34725
- NTC
-
IMU
-
加速度计
- L3G4200D
- LISDW12
- LIS3DH
-
六轴
- ADXL345
- BMI088
- BMI160
- BMI270
- BMI323
- ICM42605
- ICM42688
- ICM45686
- LSM303
- MPU6050
-
九轴
- BNO055
- ICM20948
-
地磁
- AK8963
- AK8975
- AK09911C
- BMM150
- BMM350
- HMC5883L
- IST8310
- MMC5603
- MMC5983
- QMC5883L
- RM3100
-
-
气压计
- BMP085/BMP180
- BMP280
- BMP390
- HP203B
-
存储器
- EEPROM(AT24)
- FLASH(W25)
- SD卡
-
调制器
- DSHOT
- NEC
-
网络
- LAN8742
- W5500
- YT8512
-
空间感知
- LD19
- LDS14
- PAJ7620
- PAW3220
- PAW3327
- PAW3395
- PAW3805
- PMW3901
- VL53L0X
- VL53L1X
- VL53L3X
- VL53L5X
- VL6180X
-
识别器
- U13T
-
虚拟总线
- CH9431
- MCP23016
- TCA9548A
-
虚拟IO
- AW9523
- MPR121
- NCA9555
- PCA9695
- PCF8574
- PCF8575
- TTP229
-
虚拟总线
- CH9431
- CH9434
- TCA9548A
- TCAN1145A
-
无线
- CH9141
- ECB02
- HC12
- LR1121
- LT8920
- LT8960
- NRFL01
- Si24R1
- XL2400
- XL2400P
- XN297L
- 通用IP
- musb
- ch32
-
crc
-
dma
-
i2c
-
spi
-
tim
-
uart
-
ch32v003 寄存器布局
- afio
-
ch32v203 寄存器布局
- afio
-
ch32l103 寄存器布局
- afio
-
ch32x035 寄存器布局
- afio
-
- py32
- syscfg
- gpio
- i2c
- spi
- usart
- canfd
- stm32
- hrtimer
- gd32
- hc32f460
- hrtimer
- timer4
- timer6
- timera
- chip 用于萃取不同外设的模板元工具
-
arch 架构
- arm
- cm3
- cm4
- traveo
- cm7
- riscv
- qingkeV3
- qingkeV4
- xtensa
- loongarch
- tricore
- aurix
- arm
-
constants 编译期常量
- concepts c++20概念约束拓展
- enums 内置枚举类型
- uints 单位转换
-
clock 时钟
- 毫秒 微秒 纳秒级时间戳
- Systick回调函数
- 生成精确启动秒数
- std::chrono支持
-
debug
- assert
- panic
- 错误输出流
- 错误颜色输出
- 错误日志输出
-
file 文件系统
-
io
- 寄存器
-
polymorphism 多态
- proxy3
- metaclass
- traits
-
Stream(输入输出流)
-
printlnprintprints<<(基本输出流操作)- 直接将类型格式化输出
- 将各输出类型重载输出
- 重载了容器的输出
- 添加对std::hex, std::setpos, std::setprecision等函数的支持
- StringStream(静态打印)
- fmt 支持类fmt格式化打印
-
-
String 字符串类(arduino)
- string (字符串主体)
- string_view (字符串视图)
- string_utils (字符串工具)
- 超轻量级xtoa(数字转换到字符串)
- 超轻量级atox(字符串转换到数字)
-
utils 工具
- setget 属性访问
- Bitfield 位域类
- PerUnit 标幺量
- BytesIterator 数据大小端遍历
- Reg 寄存器类
- hive c++23 std::hive
- Result rust风格错误处理
- Option rust风格结果处理
- hashfunc 哈希函数
- Variant 内建动态类型变量
-
math 数学类型库
-
IQ(IQMATH)
- 支持四则运算 大小比较 类型转换
- 支持超越函数
- sin cos tan atan atan2 acos exp log
- 支持std::超越函数
- 支持concept
- 使用模板重构
- 可选的浮点数杜绝机制
- 支持全平台的iqmath
-
matrix 矩阵
- static_matrix 静态矩阵
- 加减乘法
- 求转置/逆
- 求行列式
- 方法库
- static_matrix 静态矩阵
-
float 各类浮点数
- fp32
- bf16
- fp16
- fp8
- fp8e4m3
- fp8e5m2
-
- 宏定义头文件
- example/testbench
- main文件
-
动作池
- 运动队列
- 单个运动
- 组合运动
-
行为树
-
复合节点
- 选择节点
- 顺序节点
- 全通节点
-
工厂类
-
装饰器
-
-
Can网络
- SLCAN协议
- ECCAN协议
-
CANOPEN协议
- Cia301
- Cia402
- 子节点
- Nmt协议
- Pdo协议
- Pdo异步会话
- Sdo协议
- Sdo异步会话
-
轨迹
-
FOC
-
步进电机FOC算法
-
电流环
-
力矩环
-
速度环
- 低速速度环(0rpm~240rpm)
- 中速速度环(300rpm~2400rpm)
- 高速速度环(2400rpm~8000rpm)
-
位置环
- 高精度位置环
- 常规位置环
-
自校准算法
-
自检查算法
-
基于串口的RPC
-
基于CAN的RPC
-
数据存档
-
高级插值规划
- 直线规划
- 正弦规划
- S形规划
-
-
无刷电机FOC算法
-
-
姿态结算
- mahony
-
运动学正逆解
-
Scara正逆解
-
轮腿正逆解
- 串联腿动力学建模
- 并联腿动力学建模
-
交叉臂正逆解
-
六轴正逆解
-
-
机械控制
- scara机械臂
- 麦克纳姆底盘
- 转动关节与平动关节
- 飞达
- 夹爪
-
mavlink
- Serde/DeSerde
- 载荷
-
Tween 插值器
-
DJI RoboMaster相关驱动
- M2006
- M3508
- M6020
- DR16
-
rpc rpc框架
- 串口 RPC
- CAN RPC
-
AI
- 神经元
- 网络
- 复合网络
- 训练器
- 损失函数
-
光栅器
- 任务队列
- 光栅化器