STM32单片机增加全局内存增大导致ADC数据丢失,明明两个不相干的两个部分,为什么会相互干扰?

背景:

  • 技术是讲道理的,没有那么多的奇奇怪怪的问题。有的话都是没有找到根本原因,最近项目需要,全局的配置结构变量增加,然后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 不要同时写在两个区,否则链接器会把数据撒得到处都是。

相关推荐
余生皆假期-2 小时前
YuanHub 源码分析【六】MIT 模式
笔记·单片机·嵌入式硬件
玩转单片机与嵌入式2 小时前
别再只把 MCU 当控制器:新一代芯片正在把 AI 推理搬到设备端
人工智能·单片机·嵌入式硬件
三佛科技-134163842123 小时前
迷你除湿机方案开发,基于FT61E145-TRB单片机方案
单片机·嵌入式硬件·物联网·智能家居
czhaii3 小时前
STC15W408AS单片机不锈钢切割机C语言
单片机·嵌入式硬件
CHINA红旗下3 小时前
如何使用vscode开发STM32
ide·vscode·stm32
嵌入式小杰4 小时前
一阶低通滤波入门教程:从原理到单片机 C 代码实现
c语言·开发语言·stm32·单片机·算法
嵌入式小杰4 小时前
一阶卡尔曼滤波入门教程:从原理到单片机 C 代码实现
c语言·单片机
济6174 小时前
FreeRTOS传感器采集任务 ——SensorTask 传感器采集任务整体实现
stm32·单片机·嵌入式·freertos