前一段时间由于全球性MCU大规模紧缺,国际环境又对国内很不友好,一直在寻找一颗相对有前途国产MCU方案来做FOC电机控制,看了小华HC32f460的资料,各种独特的设计AOS协处理灵活自动化、管脚配置极高的自由度、运放内置等等特性让习惯了stm系和各种其他国产山寨的人眼前一亮,有很多特色很适合由于bldc控制安全性提升,于是一下子入了这个资料少、例程少的深坑,相当耗时调试、模棱两可的资料都极度折磨人。 仔细想想,ST的MCU能发展得这么大规模,内有官方修正资料,价格又极度便宜,外有各种开源的设计采纳(比如ESC开源项目odrive、vesc等),这些都成就了stm32f4系列的大卖,因为很多人踩坑才越做越好。目前看下来国产,唯有小华出了个类似stm32cubemx配置软件,各种资料有在逐步补充,芯片价格也在立创降到10元以下促销,种种措施相比其他家国产MCU,亲切度一下子就让人有所好感了,虽然有很多不如意的地方,库函数也是没法看的,也还是让人愿意去尝试踩下坑,在社区提升一下芯片相关生态。这里记录一下,供后来者参考,如果后续有空会针对此平台不定期开发一套开源的foc方案出来(软件、硬件),类似odrive、moteus、vesc、simplefoc这种项目,考虑是针对hc32f460芯片性能的极致优化。
开发环境: vscode+clangd+插件作为c/c++编辑环境、cmake作为工程管理、arm gcc 12.2作为编译器,开发软件全套采用开源方案,效果如图。
库选择与坑:
- 官方ddl库 优点官方例程相对较多,可以配合XHCode使用,底层配置起来配合GUI较快; 缺点代码质量极差,群里吐槽的很多,也有一些bug。
- ametal库 这是周立功开发的开源通用MCU开发框架,目前几乎支持了华大的所有系列,代码质量高、上手速度快; 缺点是通用的东西为了追求兼容性牺牲了性能,虽然各MCU通用功能例子多,但是MCU专用功能处理方式例程少得摸索,虽然官方周立功有ametal-bldc库应该支持hc32f460但是不开源,得自己摸索bldc电机控制相关的配置方法。
两套代码都跑通以后,发现采用ametal性能难以调优,且其主频为了兼容stm32f4最高只能配置168Mhz,很多std库一加载时间会比dll长数十倍,为了极致性能,是不可接受的,所以最终选择直接用ddl库。
受odrive和simple影响,一直采用c++开发项目,这些项目的结构性、可读性也比vesc这种纯C的设计好很多。
代码移植完成调试,不论ametal库,还是dll库,使用后c++发现直接会进hard_fault硬件错误中断,仔细调试发现要么是c++ class的构造函数没有调用,要么是gcc 编译配置为了性能优化开了--ffast-math引起了hard_fault问题。
仔细分析了两天,对比startup_hc32f460.s文件和stm32f4的startup文件发现:
/* Call the clock system initialization function. */
bl SystemInit
bl __libc_init_array
/* Call the application's entry point. */
bl main
bx lr
.size Reset_Handler, . - Reset_Handler
stm32f4的startup文件在Systeminit和main函数调用之间还调用__libc_init_array,单步这个函数发现这个函数执行了全部c++ class的变量的构造函数,初始化了crt、ffast-math快速运算相关的内容。
对于ddl库,在main和systeminit补上__libc_init_array调用即可,keil、iar等的startup文件同样没有调用libc_init_array,也有c++一些内容初始化不会调用问题。
对于 atemal库,直接加入__libc_init_array是不行的,因为atemal库是在main函数中才开了系统的FPU支持,ffast-math的初始化必须需要先开FPU协处理器,所以__libc_init_array需要在FPU协处理开了的环境中使用。
如图所示,hc32f460运行在200Mhz时,Flash可以靠着CACHE运行在200Mhz不影响系统效率,SRAM只有SRAMH的32KB才能运行在200Mhz(对比stm32f4的RAM都是高速RAM,且单独的一片CCRAM运行效率更快),其余152KB的RAM只能运行在100Mhz,而系统运行时变量、堆栈等都存储在SRAM上,特别是堆按内存配置,会在SRAMB上,这样函数调用都会引起MCU需要空等一周期的SRAM读写等,为了程序高效运转,可以直接只当这个MCU只有32KB的SRAM即可,就都是在200Mhz运行了,这里把链接脚本两块RAM分出来(原始文件是用在一起的),程序运行时的都使用SRAMH,低速日志10ms以上运行一次放在大空间低速SRAMA和SRAMB上,控制电机的实时中断速度才能跑满。
性能优化后,使用200Mhz主频,使用segger Systemview测量,电机高速运行时效果如下图。ADC2中断是FOC实时控制中断(周期62.5微秒),SPI是电机绝对编码器读写中断,TIM0中断是1ms应用层实时控制中断。可以看到优化后,FOC控制时间大约20.7微秒左右,暂用了33%左右的CPU运算资源。
一般而言,bldc电机控制需要可以设置死区的三组互补PWM来驱动全桥电路来控制旋转,且在测试项目中电流采用电阻下桥采样,需要保证ADC在PWM下桥开的时刻来采样。
华大的各种外设配置都非常灵活,支持各种特殊的使用场景,但是不管是手册还是示例代码都没有写清楚,每种不同的配置方式,具体的差异和实际的情况是怎么样的,这导致要达到理想的效果,几乎把所有的配置都调测了一遍,相当耗时,这边直接上调通的配置,具体细节是啥含义,这得等官方后续完善手册来解释。
比较难的TIM4的OCMR配置,用例程提供的配置根本跑不通,如下图可以看到配置这个寄存器配置可以弄成任意效果的PWM输出。
这里不纠结含义了,直接上跑通了的配置
/* TMR4_1_VL OC channel: compare mode OCMR */
unTmr4OcOcmrl.OCMRx = (uint32_t)0x0690069f;
TMR4_OC_SetLowChCompareMode(CM_TMR4_1, TMR4_OC_CH_VL, unTmr4OcOcmrl);
将OCMRL.OCMR 支持配置成 0x0690069f
/* TMR4_1_U PWM: initialize */
(void)TMR4_PWM_StructInit(&stcTmr4PwmInit);
stcTmr4PwmInit.u16Mode = TMR4_PWM_MD_DEAD_TMR;
stcTmr4PwmInit.u16ClockDiv = TMR4_PWM_CLK_DIV1;
stcTmr4PwmInit.u16Polarity = TMR4_PWM_OXH_HOLD_OXL_HOLD;
(void)TMR4_PWM_Init(CM_TMR4_1, TMR4_PWM_CH_U, &stcTmr4PwmInit);
然后PWM输出改成死区模式,硬件互补PWM输出。
然后测试开环输出正弦电压,发现电流波形如下图:
初步出来一个正弦波的样子了,但是有相当多的毛刺。
起初以为ADC配置问题,后续终于找方向,OC配置的时候要将
/* TMR4_1_WL OC channel initialize */
stcTmr4OcInit.u16CompareValue = (uint16_t)1562;
stcTmr4OcInit.u16OcInvalidPolarity = TMR4_OC_INVD_LOW;
stcTmr4OcInit.u16CompareValueBufCond = TMR4_OC_BUF_COND_IMMED;
stcTmr4OcInit.u16CompareModeBufCond = TMR4_OC_BUF_COND_IMMED;
stcTmr4OcInit.u16BufLinkTransObject = 0;
(void)TMR4_OC_Init(CM_TMR4_1, TMR4_OC_CH_WL, &stcTmr4OcInit);
改为:
/* TMR4_1_WL OC channel initialize */
stcTmr4OcInit.u16CompareValue = (uint16_t)1562;
stcTmr4OcInit.u16OcInvalidPolarity = TMR4_OC_INVD_LOW;
stcTmr4OcInit.u16CompareValueBufCond = TMR4_OC_BUF_COND_VALLEY;
stcTmr4OcInit.u16CompareModeBufCond = TMR4_OC_BUF_COND_VALLEY;
stcTmr4OcInit.u16BufLinkTransObject = 0;
(void)TMR4_OC_Init(CM_TMR4_1, TMR4_OC_CH_WL, &stcTmr4OcInit);
IMMED输出模式会造成,PWM上下桥同时打开短路,需要使用BUF_VALLEY,缓冲波谷更新模式。
匀速运行终于出现一个正常的电机电流正弦波形了,跑一个自检程序查看日志终于正常了,调试了一周有余。
- 移植stm32f4的官方flash模拟eeprom程序,hc32f460写只能按照4字节颗粒度,stm32f4是可以2字节颗粒度写的,库是按2字节读写来做的,需要改为4字节读写。
- spi、uart配合DMA发送时,第一字节无法用DMA来发?
|