☰
STM32DUINO V3.0 Core API 升级可能造成的问题及对策
2026/10/31 17:40:54 网站建设 项目流程

Arduino 官方发布了一套通用API 框架,希望减少差异,一统各硬件平台,比如AVR,STM32。官方repo 链接:arduino/ArduinoCore-API。stm32duino 在V2.12 版本后发布了V3.0,全面迁移到了这套Core API,但是目前我的建议是先不要跟着升级,尤其是用V2 版本的老项目,至少也需要仔细测试评估过再说。原因:

  • 存在一批可能导致不兼容的API 变更;
  • 有用户报告说编译后程序体积变大了4.8kB(15.3%),导致Flash 里塞不下;
  • 串口和SPI 类全面虚函数化,对运行时性能可能有少量影响;
  • V3.0.0 版本迁移没处理干净,留下了一些BUG,而V3.0.1 版本尚未发布;
  • 这次升级并没有带来明显的功能性收益;

升级收益

  • 新增STM32C5xx 系列硬件支持,并引入C5xx 系列的HALv2 库;

变更和问题

1、引入Arduino Core API

stm32duino 自己fork 了一份core api,而不是直接引入arduino 官方的东西,链接在:stm32duino/ArduinoCore-API。理论上迁移到core api 以后能提高用户程序的可移植性和库的通用性,但是我觉得收益很小,因为不通用的部分主要是真实的硬件架构和外设差异,这些东西不太可能完美的靠api 层覆盖。比方说,怎么在没有DMA 的硬件上使用DMA?现有各arduino 平台在通用性上已经做的差不多了。

2、外设抽象类

这是最显著的变化,core api 里定义了几个外设抽象类:

  • HardwareSerial
  • HardwareSPI
  • HardwareI2C
2.1、HardwareSerial

其中HardwareSerial顶掉了旧的串口实现类的名字,于是以后Serial对象的类型要变更为Uart。旧的v2 版程序里的对象定义都要改:

// v2HardwareSerialSerial1(PA9,PA10);HardwareSerialSerial3(PB11,PB10,NC,NC);// v3UartSerial1(PA9,PA10);UartSerial3(PB11,PB10,NC,NC);

因为Uart继承了HardwareSerial,所以旧代码里接收HardwareSerial对象指针或引用的部分不用改,比如:

voidwait_for_serial(HardwareSerial&s){while(s.available()==0){yield();}}

理论上旧代码要改的应该也不多,Uart类的接口和旧的HardwareSerial完全兼容,但是我觉得更好的做法应该是用HardwareUART作为抽象基类,留着HardwareSerial保持兼容。实际上我对这整套抽象基类的设计都很不喜欢,太土味OOP 了,徒增虚函数开销,只实现了约束接口的作用,不如用concept。

2.2 HardwareSPI

传统的Arduino SPI 是个仅作为SPI 主机侧使用的接口,内部实现非常简单,用软件轮询驱动通讯。现在,SPIClass成为了HardwareSPI的实现类,接口变成虚函数,还增加了SPI 从机侧和硬件中断接口。下面是抽象类定义:

classHardwareSPI{public:virtual~HardwareSPI(){}virtualuint8_ttransfer(uint8_tdata)=0;virtualuint16_ttransfer16(uint16_tdata)=0;virtualvoidtransfer(void*buf,size_t count)=0;// Transaction FunctionsvirtualvoidusingInterrupt(intinterruptNumber)=0;virtualvoidnotUsingInterrupt(intinterruptNumber)=0;virtualvoidbeginTransaction(SPISettings settings)=0;virtualvoidendTransaction(void)=0;// SPI Configuration methodsvirtualvoidattachInterrupt()=0;virtualvoiddetachInterrupt()=0;virtualvoidbegin()=0;virtualvoidend()=0;};

旧的setter 彻底删掉了,现在只能经过beginTransaction传入SPI 硬件配置。SPISettings类也被标准化成单纯的配置参数容器,而AVR 平台上SPISettings的旧实现是内置了配置参数到寄存器值的转换,不存储原始配置参数,不提供getter,只缓存了寄存器值,用于快速复用配置。不知道新的core api 要怎么处理这个行为冲突。毕竟AVR 平台是arduino 生态的老家,不太可能放着不管。

classSPISettings{public:constexprSPISettings(uint32_tclock,BitOrder bitOrder,SPIMode dataMode,SPIBusMode busMode=SPI_CONTROLLER):clockFreq(clock),dataMode(dataMode),bitOrder(bitOrder),busMode(busMode){}constexprSPISettings(uint32_tclock,BitOrder bitOrder,intdataMode,SPIBusMode busMode=SPI_CONTROLLER):clockFreq(clock),dataMode((SPIMode)dataMode),bitOrder(bitOrder),busMode(busMode){}// Default speed set to 4MHz, SPI mode set to MODE 0 and Bit order set to MSB first.constexprSPISettings():clockFreq(4000000),dataMode(SPI_MODE0),bitOrder(MSBFIRST),busMode(SPI_CONTROLLER){}booloperator==(constSPISettings&rhs)const;booloperator!=(constSPISettings&rhs)const;uint32_tgetClockFreq()const;SPIModegetDataMode()const;BitOrdergetBitOrder()const;SPIBusModegetBusMode()const;private:uint32_tclockFreq;SPIMode dataMode;BitOrder bitOrder;SPIBusMode busMode;friendclassHardwareSPI;};

另外一个问题是枚举命名变更,SPI 设备模式枚举的名字把MASTER改成了CONTROLLER,SLAVE改成了PERIPHERAL:

// V2// Device modetypedefenum{SPI_MASTER,/* Device is master */SPI_SLAVE/* Device is slave */}SPIDeviceMode;// V3// Platforms should define SPI_HAS_PERIPHERAL_MODE if SPI peripheral// mode is supported, to allow applications to check whether peripheral// mode is available or not.typedefenum{SPI_CONTROLLER=0,SPI_PERIPHERAL=1,}SPIBusMode;

这个问题不大,SPI 库一直都是默认以主模式运行,根本不支持从模式,上面V3 的注释也说了,硬件平台可以自己决定要不要支持从模式,如果支持,就定义宏SPI_HAS_PERIPHERAL_MODE,应用代码应该检查这个标志。

政治正确的问题没什么好说的。与这个改名同步,Arduino 现在也在倡议把信号线名称MISO改成CIPO, 也就是从MasterInSlaveOut 改为*C**ontrollerInPeripheralOut。MOSI,SS 也一样。另一种说法是把MOSI 改成SDO (SerialDataOut),MISO 改成SDI (SerialDataIn),省略主从关系,默认以主设备为中心看数据流向。怎么感觉这样改反而是完全剥夺了从设备的权益[doge]。

2.3、HardwareI2C

和SPI 库一样,core api 提供了硬件I2C 抽象类,Wire 或TwoWire 现在要继承它并实现那些虚函数。不过Wire 库本身就比较庞大,多一层虚函数,影响比SPI 库更小。下面是抽象类定义:

classHardwareI2C:publicStream{public:virtualvoidbegin()=0;virtualvoidbegin(uint8_taddress)=0;virtualvoidend()=0;virtualvoidsetClock(uint32_tfreq)=0;virtualvoidbeginTransmission(uint8_taddress)=0;virtualuint8_tendTransmission(boolstopBit)=0;virtualuint8_tendTransmission(void)=0;virtualsize_trequestFrom(uint8_taddress,size_t len,boolstopBit)=0;virtualsize_trequestFrom(uint8_taddress,size_t len)=0;virtualvoidonReceive(void(*)(int))=0;virtualvoidonRequest(void(*)(void))=0;};

I2C 还保留了setClock接口,而不是要求在beginTrasmission时传入配置参数,emmm,有点恶心。另外,注意Wire 库里begin函数有多个重载:

voidbegin(uint8_t,boolgeneralCall=false,boolNoStretchMode=false);voidbegin(int,boolgeneralCall=false,boolNoStretchMode=false);voidbegin()override{begin(false);}voidbegin(uint8_taddress)override{begin(address,false,false);}

其中,从core api 继承的begin(uint8_t)可能和begin(uint8_t, bool, bool)冲突,因为后者的两个bool 参数都有默认值。而如果传入的地址是字面量整数就ok,因为整数字面量默认类型是int,会优先匹配到begin(int, bool, bool)这个重载。

uint8_taddr=0x3c;begin(addr);// 出错,同时匹配到两个重载begin(0x3c);// 没问题

至于有这个int重载的原因,是为了兼容I2C 标准里的11bit 扩展地址,用uint8_t只能传入一般的7bit 地址。显然arduino 官方的core api 设计没考虑到这个问题。

3、变更的枚举和常量值

引脚模式枚举里面OUTPUT_OPEN_DRAIN和INPUT_ANALOG的值互换了,代码里没直接使用整数值的话不影响。

// V2#defineINPUT0x0#defineOUTPUT0x1#defineINPUT_PULLUP0x2#defineINPUT_PULLDOWN0x3#defineINPUT_ANALOG0x4#defineOUTPUT_OPEN_DRAIN0x5// V3typedefenum{INPUT=0x0,OUTPUT=0x1,INPUT_PULLUP=0x2,INPUT_FLOATING=INPUT,/* 0x0 */INPUT_PULLDOWN=0x3,OUTPUT_OPENDRAIN=0x4,/* ← 2.12 是 0x5 */OUTPUT_OPEN_DRAIN=OUTPUT_OPENDRAIN,INPUT_ANALOG=0x5,/* ← 2.12 是 0x4 */}PinMode;

串口模式从uint8_t类型变成了uint16_t,数值也发生变化。旧代码里没直接写数值就没太大影响,不过如果旧代码里把配置常量用uint8_t存储过,不改代码的话就装不下新的uint16_t常量了。

// V2// Define config for Serial.begin(baud, config);// below configs are not supported by STM32//#define SERIAL_5N1 0x00//#define SERIAL_5N2 0x08// ...//#define SERIAL_6N2 0x0A#defineSERIAL_8N10x06#defineSERIAL_8N20x0E// ...#defineSERIAL_8O20x3E// V3#defineSERIAL_5N1(SERIAL_STOP_BIT_1|SERIAL_PARITY_NONE|SERIAL_DATA_5)#defineSERIAL_6N1(SERIAL_STOP_BIT_1|SERIAL_PARITY_NONE|SERIAL_DATA_6)// ...#defineSERIAL_8S2(SERIAL_STOP_BIT_2|SERIAL_PARITY_SPACE|SERIAL_DATA_8)

4、浮点转字符串打印性能退化

V2 版本Print.h里提供的printFloat是个函数模板:

template<classT>size_tprintFloat(T,uint8_t){/* .. */}

如果程序里只需要打印单精度浮点float,模板就只会实例化成一份printfFloat(float, uint8_t),但是如果一份固件里同时用了打印float和double,就只能再多实例化一份双精度版本printFloat(double, uint8_t),两个版本并存,显然这样会导致代码体积膨胀。可能是出于这种考虑,V3 版本没有这个模板,只提供一份双精度版实现,哪怕只用单精度浮点float,也要转换成double再打印。

这样最大的问题是,在STM32F4 之类有单精度硬件浮点单元FPU 的芯片上,旧的代码生成的单精度版本可以用硬件浮点处理,速度更快,代码体积更小,如果强行用双精度,那就只能像没有FPU 一样用软件处理浮点,导致性能退化。

至于M0 系列的芯片,本来就没有硬件FPU,旧代码可能会分别实例化出一份单精度和双精度的实现,导致体积膨胀,所以换成用V3 以后可能会省一点空间。最佳方案可能是编译时检测目标芯片是否配置了FPU,然后再决定要不要提供一份有硬件加速的单精度实现。

5、String 类从双精度浮点double 构造时要吃掉大量栈空间

String(float/double)构造函数里的临时缓冲从固定char buf[33]改成了按FLT/DBL_MAX_10_EXP算出来的大小(api/String.h:61-62定义FLT_MAX_DECIMAL_PLACES = 10、DBL_MAX_DECIMAL_PLACES = 10):

// String.cppString::String(doublevalue,unsignedchardecimalPlaces){staticsize_tconstDOUBLE_BUF_SIZE=DBL_MAX_10_EXP+DBL_MAX_DECIMAL_PLACES+1+1+1;// 308 + 10 + 3 = 321charbuf[DOUBLE_BUF_SIZE];// 2.12 这里是 char buf[33];...}

所以输入double时,缓冲区尺寸从33 字节膨胀到了321 字节,这还是挺可观的,尤其是对那些内存只有2K、4K 的M0 芯片而言。不过String里与浮点转换有关的函数用了两种实现,第一种就是这个构造函数,另外还有String::concat(double),这个函数把double转换成字符串并拼接到已有的String对象后面,它的缓冲区没膨胀。所以:

doubled=3.14;String s{d};// 一下吃掉321 字节缓冲区空间String ss;ss+=d;// 调用concat 拼接,正常

对策:PlatformIO 锁定库版本

可以在platformio.ini锁定库版本到V2.12,如下:

[env:app] platform = platformio/ststm32@19.7.1 ; platform 版本也要锁 platform_packages = platformio/framework-arduinoststm32@4.21200.0 ; 锁定stm32duino 框架版本 ; 还有这两个库,最新版会检测stm32duino 框架版本,V3 以下不能编译,所以也要锁定到V3 之前的版本 lib_deps = stm32duino/STM32duino Low Power @1.5.0 stm32duino/STM32duino RTC @1.9.0

总结

可以看出来,官方这套core api 还相当不成熟,官方源码里还留着一堆TODO 和问号,比如在HardwareSerial.h里 ,名为XXX 的某人问道:

// XXX: Those constants should be defined as const int / enums?// XXX: shall we use namespaces too?#defineSERIAL_PARITY_EVEN(0x1ul)#defineSERIAL_PARITY_ODD(0x2ul)#defineSERIAL_PARITY_NONE(0x3ul)#defineSERIAL_PARITY_MARK(0x4ul)

你问我我问谁。总之,这套core api 并没有增加什么功能收益,我只看到了一堆兼容性隐患和草率的设计决策。而且官方repo 里攒了99 个issue,但是上一次提交更新已经是去年的事了。前途堪忧,怕不是已经死了。所以还是那句话,目前不推荐使用V3 版stm32duino,除非st 接手core api 然后积极更新,但那也要看他们什么时候能发V3.0.1,没准直接反悔回退了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询