背景:
- 技术是讲道理的,没有那么多的奇奇怪怪的问题。有的话都是没有找到根本原因,最近项目需要,全局的配置结构变量增加,然后ADC的NTC温度异常分析。略有所获,随手记录。
- 近期需求修改一个全局变量,增加变量结构成员。预计增加2k多字节的数据。来检查一下已经用了多少。

- 现在是135.41kB,我增加的3k来算,也就是138.41kB,我的MCU是STM32F429,内存配置是192kB+64kB的组合,没啥问题,直接加。

- #if 1包住的就是我的目标代码。#else包住的就是原来的。 其他代码不改。重新编译

- 编译后显示内存是139.58kB.
- 看起来都挺顺利的。我烧录到设备上发现,NTC温度监测就报警了,温度值是125℃,怎么可能,一定是修改导致的,我烧录回之前的版本,确实问题就消失了。这个就是我最初的问题,我只是改了ts_sysconfig结构,NTC属于ADC采集,也是独立模块的。理论上八竿子都打不着的地方。我的耳边回响着我之前领导的话,"技术是讲道理的,没有那么多的奇奇怪怪的问题。有的话都是没有找到根本原因",我的求知欲开始增加,到底是什么原因,明明编译通过了,内存看起来也正常。为啥改了这个,会影响其他功能。
- 因为特征很明显,增加了全局结构,全局变量变大,编译后也确实显示变大了,但是没有超过MCU的内存,还有一定的富余,回滚回去,问题会消失。不会是内存不足的问题。debug一下,看看为啥温度值变成125℃吧。

- Adc值全部都是0,adc3_result也是0.根据查表逻辑,

- 比最小的adc值还小,取的是125℃,所以是125℃不是真的,只是adc值为0的结果。
那为什么ADC值会为0呢,原始值也好,平均的结果值也好都是0,只能说ADC3通道没有取到数据,sysconfig结构的全局内存改回去就可以了,说明初始化,还有硬件是没有问题的。
我们来看一下ADC3的实现。
c
void HAL_ADC_ConvCpltCallback ( ADC_HandleTypeDef* hadc )
{
int i, j;
uint32_t sum = 0;
uint32_t avg;
HAL_ADC_Stop ( hadc);
if( hadc->Instance == ADC1 )
{
for ( i = 0; i < ADC1_ITEM_COUNT; i++ ) /* 遍历所有adc采样项 */
{
sum = 0;
for ( j = 0; j < ADC1_BUFF_SIZE; j++ ) /* 求和 */
sum = sum + ( uint32_t ) adc1_buf[j][i];
avg = sum / ADC1_BUFF_SIZE; /* 计算平均值 */
adc1_result[i] = ( uint16_t ) avg;
}
HAL_ADC_Start_DMA ( hadc, ( uint32_t * ) adc1_buf, ADC1_BUFF_SIZE * ADC1_ITEM_COUNT );
}
else if( hadc->Instance == ADC3 )
{
for ( i = 0; i < ADC3_ITEM_COUNT; i++ ) /* 遍历所有adc采样项 */
{
sum = 0;
for ( j = 0; j < ADC3_BUFF_SIZE; j++ ) /* 求和 */
sum = sum + ( uint32_t ) adc3_buf[j][i];
avg = sum / ADC3_BUFF_SIZE; /* 计算平均值 */
adc3_result[i] = ( uint16_t ) avg;
}
HAL_ADC_Start_DMA ( hadc, ( uint32_t * ) adc3_buf, ADC3_BUFF_SIZE * ADC3_ITEM_COUNT );
}
}
- 这个看到ADC3是DMA传输的,也就是说DMA失效了,什么情况内存变大导致DMA失效了呢。查阅了一些资料,说DMA失效和内存变量的存储位置,有关系,我们通过编译后的map文件,查看一些变量的分布情况。


- 这个是不行的,存储的是0x1000xxxxx地址上


- 这个是正常的,增加全局内存前的。
- 观察上述数据,很明显,增加了全局内存,地址从0x20000xxxxx变成了0x100000xxxxx
这个就是就是问题的根本原因了。0x1000xxxx是CMM的内存,我们adc走的是DMA传输,CMM的内存不支持DMA,所以当内存分布在0x2000xxx上是正常的,我们需要要固定adc3_buf和adc3_result的内存分布,固定在SRAM里面,地址从0x20000xxx里面放。 - 目前有两个方法,一个是对这两个变量增加一个指定位置,
__attribute__((section(".dma_ram"))),还有一个就是工程里面设置,我现在内存还算充足,我就直接用工程禁用了CMM,也就是IRAM2.

- 修改前

-
去掉勾选即可,然后重新编译,你就会发现所有的数据都在0x2000xxxx里面了。
再烧录到设备上,完美解决。扩展一下IRAM1就是实际芯片的SRAM,IRAM2就是实际芯片的CCM。如果都勾选,sct文件就会变成这样。

-
.ANY (+RW +ZI) 在两个区域各写一份,链接器就认为两边都能放。
链接器分配策略是"最优匹配"------它根据变量大小、地址对齐等找最合适的区。两个区都满足时,随机分配一部分变量去CCM。adc1_buf/adc3_buf就这么被塞进了CCM。
禁用后就变成
只放IRAM1.就正常了。
-
既然有IRAM1和IRAM2,存在即合理,什么数据适合放CCM,什么数据适合放SRAM?
-
通用原则:
-
CCM (0x10000000, 64KB) CPU独占访问,无总线竞争,速度更快。DMA 不可访问。
适合放:
频繁访问的小数据结构 --- 中断里的关键变量、时间敏感的状态机 需要零等待的临时数据 --- DSP滤波中间结果、实时环路计算缓存
中断处理函数内的栈数据 --- 减少总线冲突 Flash读取的临时缓冲区 --- W25QXX操作时CPU侧缓存(不是DMA侧)
-
SRAM (0x20000000, 192KB) CPU和DMA共享,总线竞争时略慢。但DMA可访问。
适合放:
所有DMA缓冲区 --- UART/CAN/ADC/SPI的收发缓存 ← 绝对不能放CCM 网络协议栈缓冲 --- TCP/UDP socket buffer 文件系统缓存 --- FatFS的 sector buffer 大块数据 --- 图像缓冲、录音缓冲、日志缓冲区
这颗芯片上的判断基准 只有两个问题:
这东西会被DMA读写吗? → 会 → SRAM
它很小(几KB以内)且需要最高速访问? → 是 → CCM
-
如果要用CCM怎么搞?
-
正确的 scatter 写法:
RW_IRAM1 0x20000000 0x00030000 { .ANY (+RW +ZI) // 默认全部SRAM }
RW_IRAM2 0x10000000 0x00010000 { *(+ZI) // 空的,不放任何东西 }
如果想手动指定某个变量进CCM:
RW_IRAM2 0x10000000 0x00010000 { W25QXX_BUF.o (+ZI) //
只有W25QXX_BUF进CCM }
记住:.ANY 不要同时写在两个区,否则链接器会把数据撒得到处都是。