引言:为什么在嵌入式中使用C++不是自虐,而是超能力
\n\n当听到“嵌入式开发”时,眼前通常会浮现一位拿着烙铁、用纯C语言编写代码并对着数据手册祈祷的严肃工程师。或者相反——一位热衷于在Arduino上使用digitalWrite()并因点亮LED而欣喜的爱好者。但在这两个世界之间,存在着一个黄金中间地带——基于C++的嵌入式开发。
没错,就是那个因模板和虚函数而令人望而生畏的C++。事实上,现代微控制器(ARM Cortex-M、RISC-V)完全能够处理C++代码。更重要的是,合理使用面向对象编程、封装甚至模板,可以使您的嵌入式代码更清晰、更安全、更快速。
\n\n在本文中,我们将探讨如何编写高效的微控制器C++代码,避开虚函数表的陷阱,并学习以C++风格在寄存器层面操作硬件。准备好了吗?
\n\n1. 基础:嵌入式C++与普通C++的区别
\n\n嵌入式开发的首要规则:资源有限。您没有千兆字节的RAM和千兆赫兹的处理器。您只有32 KB的闪存和8 KB的RAM。每个字节都至关重要。因此,嵌入式中的C++不是关于“编写一个具有10层继承的类”,而是关于对抽象的控制。
\n\n可以放心使用的特性:
\n- \n
- 类和结构体——用于分组数据和函数。如果没有虚函数,这(在内存上)是零成本的。 \n
- 命名空间——避免产生像
hal_gpio_init()这样的前缀。 \n - constexpr和模板——编译时计算,节省时钟周期。 \n
- 引用和常量引用传递——比指针更安全。 \n
非必要不使用的特性:
\n- \n
- 动态内存(new/delete)——堆会碎片化,分配器可能失败。在裸机项目中是禁忌。 \n
- 异常(try/catch)——需要运行时支持,占用大量空间。 \n
- 虚函数——每个虚函数都会在RAM/Flash中创建一个表(vtable),调用通过双重间接寻址。但有时它们是有道理的(驱动程序)。 \n
- RTTI(typeid)——完全不需要。 \n
核心很简单:如果您不使用“重量级”特性,C++编译器生成的代码与C一样高效。检查反汇编代码——您会感到满意。
\n\n2. 实践:以C++风格操作寄存器
\n\n在经典的嵌入式C中,我们编写类似这样的代码:
\n#define GPIOA_BASE 0x40020000\n#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))\nGPIOA_MODER |= (1 << 10); // 设置第10位(输出)\n\n这可行,但看起来过时了。在C++中,我们可以创建一个没有额外开销的便捷封装。
\n\n示例:用于操作引脚的类
\n#include <cstdint>\n\nclass GpioPin {\npublic:\n // 禁止复制\n GpioPin(const GpioPin&) = delete;\n GpioPin& operator=(const GpioPin&) = delete;\n\n // 模式枚举\n enum class Mode : uint32_t {\n Input = 0b00,\n Output = 0b01,\n AltFunc = 0b10,\n Analog = 0b11\n };\n\n // 构造函数:接受端口地址和引脚编号(0-15)\n GpioPin(volatile uint32_t* port_base, uint8_t pin)\n : port_(port_base), pin_(pin) {\n // 可以立即配置引脚\n }\n\n // 设置工作模式\n void setMode(Mode mode) {\n volatile uint32_t* moder = port_ + 0x00; // MODER寄存器偏移\n *moder &= ~(0b11 << (pin_ * 2));\n *moder |= (static_cast<uint32_t>(mode) << (pin_ * 2));\n }\n\n // 设置高电平\n void high() {\n volatile uint32_t* bsrr