【BUG】记STM32F030多通道ADC DMA读取乱序问题

STM32F0多通道ADC的校准顺序与DMA乱序问题的本质

声明:本段转载:https://www.cnblogs.com/chihirosan/p/5458673.html

  • 问题描述

    通过 uint16_t ConvData[8]保存DMA搬运的ADC转换数值,但是这个数组数值的顺序总是和ADC不是顺序对应的。比如用7个通道的ADC,

    • 当设置ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward,是对应顺序是:0->0,1->7,2->6...7->1 ;
    • 当设置ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward,是对应顺序是:0->7,1->0,2->1...7->6 。
  • 问题原因

    F0的ADC在使用之前需要校准。这个7位的校准值也是放在ADC_DR中的,它也会触发DMA请求。

    可以参照F0的ADC-DMA例程,先做ADC校准、然后再设置DMA,再使能ADC的DMA。

  • 实例代码

c 复制代码
void ADC1_DMA_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = 0x00ff;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    ADC_DeInit(ADC1); //ADC恢复默认设置

    ADC_StructInit(&ADC_InitStructure); //初始化ADC结构

    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //12位精度
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //规定模式装换工作在连续模式
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对其为右对齐
    ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; // ADC_ScanDirection_Backward; //ADC的扫描方向
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_ChannelConfig(ADC1, ADC_Channel_0, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_1, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_2, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_3, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_4, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_5, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_6, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */
    ADC_ChannelConfig(ADC1, ADC_Channel_7, ADC_SampleTime_239_5Cycles); /* Convert the ADC1 Channel 11 with 239.5 Cycles as sampling time */

    ADC_GetCalibrationFactor(ADC1); /* ADC Calibration */
    ADC_Cmd(ADC1, ENABLE); /* Enable ADCperipheral[PerIdx] */
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY))
        ; /* Wait the ADCEN falg */

    //设置DMA要在校准ADC之后
    DMA_DeInit(DMA1_Channel1); /* DMA1 Channel1 Config */
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) 0x40012440; //ADC1->DR; //外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) RegularConvData_Tab; //内存地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作为数据传输的来源
    DMA_InitStructure.DMA_BufferSize = 8; //
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器不变
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA_Priority设定DMA通道x的软件优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel1, ENABLE);/* DMA1 Channel1 enable */
    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
    ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular); /* Enable ADC_DMA */
    ADC_DMACmd(ADC1, ENABLE);

    ADC_StartOfConversion(ADC1); /* ADC1 regular Software Start Conv */

}
原问题现象与深层原因
  1. DMA在ADC中的应用

    • ADC多通道扫描时,DMA用于自动搬运转换结果。但STM32F0系列的ADC包含校准值存储机制
    • 校准阶段 :调用ADC_GetCalibrationFactor()时,会将一个7位的校准值写入ADC_DR寄存器(与转换结果共用同一地址),并触发DMA请求。
  2. DMA乱序的成因

    • 过早启用DMA(如在校准前)会导致DMA将校准值当做首个有效数据搬运,后续通道数据依次错位。
    • 数值错位实例
      • 若ADC通道0的值在ADC_DR中的实际存储顺序应为第1个,但因校准值的介入,DMA搬运时该值会出现在数组的第二个位置。
  3. 解决方案的工程意义

    • 校准后配置DMA :确保DMA仅搬运有效转换结果。关键代码如下:

      c 复制代码
      ADC_GetCalibrationFactor(ADC1);   // 先校准
      DMA_Init(DMA1_Channel1, ...);     // 后配置DMA
      ADC_DMACmd(ADC1, ENABLE);         // 最后使能ADC的DMA请求
  4. ADC扫描方向的补充解释
    ADC_ScanDirection参数定义通道扫描的物理顺序:

    • Upward模式:从通道编号低到高扫描(如0→1→2)。
    • Backward模式 :从编号高到低扫描(如2→1→0)。
      此参数需要与DMA数组的预期存储顺序一致,否则需软件层调整数组索引。
扩展思考:其他引发DMA错位的可能
  1. 未清除DMA缓存:DMA传送前后未重置缓存区,残留数据可能导致混淆。
  2. 中断抢占冲突:若ADC中断优先级低于其他中断,可能因响应延迟导致数据覆盖。

总结

  1. 编码规范的重要性:结构体声明的位置不仅是语法问题,更影响代码可移植性。
  2. 硬件机制的深度理解:结合芯片手册分析异常(如STM32F0校准值特性),能快速定位隐蔽问题。
相关推荐
来自晴朗的明天7 小时前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT7 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠7 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
xuxg200520 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
LJianK121 小时前
idea自带的数据库修改默认值有bug
bug
CODECOLLECT1 天前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen1 天前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制
全栈游侠1 天前
STM32F103XX 02-电源与备份寄存器
stm32·单片机·嵌入式硬件
Lsir10110_1 天前
【Linux】中断 —— 操作系统的运行基石
linux·运维·嵌入式硬件