[应用相关] STM32 ADC DMA数据不稳定的解决方案

[复制链接]
1020|7
 楼主| t60yz 发表于 2022-4-30 19:57 | 显示全部楼层 |阅读模式
在项目开发中,经常需要用到ADC采样的做电压检测,而且多通道ADC检测的情况比较多,所以本篇基于此要求采用了ADC DMA的方法,下面先给出基础代码(STM32F030)!
#define ADC1_DR_Address            0x40012440

  1. #define ADC1_DR_Address            0x40012440
  2. //对应需要检测的ADC通道个数
  3. #define ADC_DMA_BUFFER_SIZE     2

  4. //按照通道顺序依次存放得到的ADC值
  5. __IO uint16_t RegularConvData_Tab[ADC_DMA_BUFFER_SIZE] = {0, 0};

  6. /**
  7.   * [url=home.php?mod=space&uid=247401]@brief[/url]  DMA channel1 configuration
  8.   * @param  None
  9.   * @retval None
  10.   */
  11. void DMA_Config(void)
  12. {
  13.     DMA_InitTypeDef   DMA_InitStructure;
  14.     /* DMA1 clock enable */
  15.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

  16.     /* DMA1 Channel1 Config */
  17.     DMA_DeInit(DMA1_Channel1);
  18.     DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  19.     DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;
  20.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  21.     DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUFFER_SIZE;
  22.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  23.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  24.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  25.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  26.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  27.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  28.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  29.     DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  30.     /* DMA1 Channel1 enable */
  31.     DMA_Cmd(DMA1_Channel1, ENABLE);
  32. }

  33. void ADC_DMA_Config(void)
  34. {
  35.     ADC_InitTypeDef     ADC_InitStructure;
  36.     GPIO_InitTypeDef    GPIO_InitStructure;

  37.     DMA_Config();

  38.     /* ADC1 DeInit */
  39.     ADC_DeInit(ADC1);
  40.     /* ADC1 Periph clock enable */
  41.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

  42.     /* Configure ADC Channel11 and channel10 as analog input */
  43.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  44.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

  45.     RCC_AHBPeriphClockCmd(P_N_TEST_CLK, ENABLE);
  46.     GPIO_InitStructure.GPIO_Pin = P_N_TEST_PIN ;
  47.     GPIO_Init(P_N_TEST_PORT, &GPIO_InitStructure);

  48.     RCC_AHBPeriphClockCmd(DC5V_TEST_CLK, ENABLE);
  49.     GPIO_InitStructure.GPIO_Pin = DC5V_TEST_PIN ;
  50.     GPIO_Init(DC5V_TEST_PORT, &GPIO_InitStructure);

  51.     /* Initialize ADC structure */
  52.     ADC_StructInit(&ADC_InitStructure);

  53.     /* Configure the ADC1 in continuous mode withe a resolution equal to 12 bits  */
  54.     ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  55.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  56.     ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  57.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  58.     ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
  59.     ADC_Init(ADC1, &ADC_InitStructure);

  60.     ADC_ChannelConfig(ADC1, DC5V_TEST_channel,ADC_SampleTime_239_5Cycles);
  61.     ADC_ChannelConfig(ADC1, P_N_TEST_channel, ADC_SampleTime_239_5Cycles);

  62.     /* ADC Calibration */
  63.     ADC_GetCalibrationFactor(ADC1);

  64.     /* ADC DMA request in circular mode */
  65.     ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);

  66.     /* Enable ADC_DMA */
  67.     ADC_DMACmd(ADC1, ENABLE);

  68.     /* Enable the ADC peripheral */
  69.     ADC_Cmd(ADC1, ENABLE);

  70.     /* Wait the ADRDY flag */
  71.     while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY));

  72.     /* ADC1 regular Software Start Conv */
  73.     ADC_StartOfConversion(ADC1);
  74.    
  75. }

ADC按照通道顺序循环采样并转换数据,然后DMA自动将对应的数据搬运至RegularConvData_Tab[]数组中。
 楼主| t60yz 发表于 2022-4-30 19:58 | 显示全部楼层
使用该方法得到的ADC值有时候波动会比较大,如果不做滤波就直接采用的话,有可能会因为数据波动造成程序误判。如果将ADC值做中值滤波处理,即使有个别数据波动,对程序的影响则大幅度降低。因为DMA搬运新数据时会将旧数据覆盖掉,这里采用DMA中断处理,每发生一次DMA中断时将新的数据缓存起来,存够指定数量后再做中值滤波!void DMA_Config(void)函数加上DMA中断配置的代码如下

  1. /**
  2.   * @brief  DMA channel1 configuration
  3.   * @param  None
  4.   * @retval None
  5.   */
  6. void DMA_Config(void)
  7. {
  8.     DMA_InitTypeDef   DMA_InitStructure;
  9.     NVIC_InitTypeDef  NVIC_InitStructure;
  10.     /* DMA1 clock enable */
  11.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

  12.     /* DMA1 Channel1 Config */
  13.     DMA_DeInit(DMA1_Channel1);
  14.     DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  15.     DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RegularConvData_Tab;
  16.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  17.     DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUFFER_SIZE;
  18.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  19.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  20.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  21.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  22.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  23.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  24.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  25.     DMA_Init(DMA1_Channel1, &DMA_InitStructure);
  26.     /* DMA1 Channel1 enable */
  27.     DMA_Cmd(DMA1_Channel1, ENABLE);
  28.    
  29.     /* Enable the DMA1 Interrupt */
  30.     NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
  31.     NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
  32.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  33.     NVIC_Init(&NVIC_InitStructure);
  34.    
  35.     /* DMA1 interrupt */
  36.     DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
  37. }
 楼主| t60yz 发表于 2022-4-30 20:00 | 显示全部楼层
DMA中断处理函数如下
  1. //ADC DMA数据传输完成
  2. void DMA1_Channel1_IRQHandler(void)
  3. {
  4.     /* Check the status of the specified DMAy flag */
  5.     if ((DMA1->ISR & DMA1_FLAG_TC1) != (uint32_t)RESET)
  6.     {
  7.         ADC_DMA_INTERRUPT_HANDLER();
  8.         //清除标志位
  9.         DMA1->IFCR = DMA1_FLAG_TC1;
  10.     }
  11. }

  12. void ADC_DMA_INTERRUPT_HANDLER(void)
  13. {
  14.     u8 i;
  15.     static u8  times=0;
  16.     static u16 buffer[ADC_DMA_BUFFER_SIZE];
  17.     for (i=0;i<ADC_DMA_BUFFER_SIZE;i++) {
  18.         buffer[i] += RegularConvData_Tab[i];
  19.     }
  20.     if (++times >= 8) { //取8次平均值
  21.         for (i=0;i<ADC_DMA_BUFFER_SIZE;i++) {
  22.             CalAverConvData_Tab[i] = buffer[i]>>3;
  23.             buffer[i] = 0; //清零
  24.         }
  25.         times = 0;
  26.     }
  27. }
Pulitzer 发表于 2022-10-6 09:29 | 显示全部楼层

代码量小的时候用来做条件判断
Uriah 发表于 2022-10-6 16:30 | 显示全部楼层

单片机一般都有内部程序区和数据区
Bblythe 发表于 2022-10-6 19:29 | 显示全部楼层

只要内存占用量不超过 256.0 就可以用 small 模式编译
周半梅 发表于 2023-5-1 08:26 | 显示全部楼层

可以开始在项目中动手用
Pulitzer 发表于 2023-5-1 09:29 | 显示全部楼层

在孔璧内部作金属处理后,可以让内部的各层线路能够彼此连接。
周半梅 发表于 2023-5-1 10:32 | 显示全部楼层

small 模式下未指存储类型的变量默认为data型
童雨竹 发表于 2023-5-1 11:25 | 显示全部楼层

实际上根本不需要其他块
Wordsworth 发表于 2023-5-1 12:28 | 显示全部楼层

分别记录车牌区域的上下高度。然后通过RGB-HSV颜色转换
Clyde011 发表于 2023-5-1 13:31 | 显示全部楼层

不要根据不同的参数类型走不同的代码逻辑
万图 发表于 2023-5-1 15:27 | 显示全部楼层

内部由寄存器、控制器、运算器和时钟四部分组成,各部分之间通过电信号连通
Uriah 发表于 2023-5-1 16:30 | 显示全部楼层

时间片轮的设计思想
帛灿灿 发表于 2023-5-1 18:26 | 显示全部楼层

嵌入式软件开发偶尔要收集和分析 log
Bblythe 发表于 2023-5-1 19:29 | 显示全部楼层

通过访问寄存器来控制I2C1工作时钟的开启。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

189

主题

1191

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部