有人使用STM32H7系列的ADC模块,定时器触发ADC,数据通过DMA传输到内存。对某通道连续转换几次后求个平均值。他却发现ADC结果虽没有什么问题,但一批数据出来后就纹丝不动了。DMA传输本来设计成的Circular模式,感觉好像工作在Normal模式,结果显然有点不合理。
鉴于这个现象和所用芯片,估计是因为Cache使用方面的原因,客户也的确使能了Cache。具体怎么回事呢?我们一起来看看。
我这边使用H743Nucleo板和ST免费的STM32CubeIDE。STM32H743片内有个Vrefint信号,电压一般在1.2v左右,用它做ADC的输入信号来测试。用LPTIM触发ADC转换,每读到5个数据就求个平均值。
我这里定义了一个6字大小的数组,uint32_t AdcDataViaDMA [6];前5个位置放实时ADC数据,第6个位置即AdcDataViaDMA [5]存放换算后的最终Vrefint电压平均值,单位是mv。
我们使用STM32CubeMx进行配置。重点看下ADC的配置:[注:ADC是16位的]
生成初始化代码后,添加用户代码。我把数组AdcDataViaDMA【】指定在片内RAM2区域。
__attribute__((section(".AdcDataViaDMA"))) uint32_t AdcDataViaDMA [6];
HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED );
HAL_ADC_Start_DMA(&hadc3,(uint32_t*)&AdcDataViaDMA [0], 5);
HAL_LPTIM_PWM_Start(&hlptim2, Period, Pulse);
然后编译调试。同样出现数据纹丝不动的现象。我每次在传输完成中断回调函数里做数据处理。相关处理代码如下图所示:
TIMER不停触发ADC,DMA传输也是循环的,按理数据应该动态改变。即使变化不大,也不至于纹丝不动。【除AdcDataViaDMA [5]外,其它均为AD转换值。】
那是什么原因呢?目前我们是开启了Cache的。
现在数据的大致流程就是,ADC转换结果出来后,DMA将数据写入SRAM,然后CPU读取SRAM里的数据。因为开启了D-Cache,当CPU针对SRAM做读操作时发生读Allocate,相应内存地址的数据被拷贝一份到Cache里。下次CPU再去相应SRAM地址去读取时,因D-Cache里已经有了一份有效数据,即发生Cache命中事件,CPU就直接从Cache读数据,而不读取SRAM了。
所以,尽管后面时间里ADC因TIMER不停触发而产生新数据并被DMA传输到SRAM,但CPU除了第一次外总是从D-Cache取数据,导致该数据永远就是第一次读到的数据而不变。
既然这样,我们可以在CPU每次针对SRAM做了读操作后,对D-Cache相应内容做无效处理,迫使CPU每次要取数据时因Cache Miss而发生读Allocate操作并更新Cache数据,进而保证CPU读到跟内存SRAM里一致的Cache数据。
我在DMA完成中断回调函数里加上一句针对D-Cache作无效操作的代码。如下图红线标注的代码。
然后进行测试,立即可以看到不断实时变化的数据了。【下图我随机截取的几个结果】
不过,请注意这里针对D-Cache相应地址做了无效化处理的代码要放在最前面。你可以将该句试着放在对AdcDataViaDMA [5]赋0语句的后面验证下,看看会有什么现象,要看细致点,不然发现不了问题。顺便提醒,目前所用SRAM区域在没有做MPU配置的情况下,默认为write back加write allocate Cache属性,我在前面贴图时已经圈出来了。有兴趣的话,可以基于上述代码自行进一步探究下,此处就不延伸了。
另外,除了上面的解决办法外,还有就是将上面内存数组所在区域做MPU设置,关闭那部分区域的Cacheable属性。
当这样配置后,之前那句针对Cache无效化的代码就不需要了。生成代码进行测试,结果跟预期一致,数据保持实时动态变化,结果也正常。
上面主要就开启D-Cache后,发生了主设备数据读取不一致的问题给出了些解决办法及思路,同时也算是给出些应用提醒,以后碰到类似问题时可以参考。当然,有人或许会说,完全不开启芯片的D-Cache功能也可以解决问题。诚然,但我们需要综合考虑开发便利性和芯片的性能发挥,尽量就问题做些针对性的处理以充分发挥芯片性能。