news 2026/8/20 21:27:53

Cortex-M3-STM32F1 开发:(四十六)ADC 实例 4 :单通道 ADC 过采样实例,使用 DMA,两种编程方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cortex-M3-STM32F1 开发:(四十六)ADC 实例 4 :单通道 ADC 过采样实例,使用 DMA,两种编程方式
上一篇下一篇
ADC 实例 3 :多通道 ADC 采集实例,使用 DMA

目 录

  • 3.4)单通道 ADC 过采样实例,使用 DMA
    • ① 背景:
    • ② 原理:
    • ③ 代价:
    • ④ 上限:
    • ⑤ 公式与计算:
    • ⑥ 举例:
    • ⑦ 实验:
    • ⑧ 代码与实验结果:

3.4)单通道 ADC 过采样实例,使用 DMA

方程推导过程:https://max.book118.com/html/2018/0506/165038217.shtm

提高 ADC 分辨率一般来说有三种方法:

  • 采用 H7 这种分辨率为 16 位的单片机;
  • 外接专用的高分辨率 ADC 采集板,然后单片机用串口或IIC这些通信接口读取;
  • 过采样+取平均提高等效分辨率

① 背景:

ADC 的有效分辨率并非仅由其量化位数决定,而是受到系统噪声水平的显著影响。当噪声幅度接近或超过量化步长时,部分低位信息将被噪声淹没。通过降低噪声功率或采用过采样与平均等方法提高信噪比,可有效降低噪声地板(让更小的信号变化能“浮出噪声地板”),从而提升系统的最小可分辨信号变化量,即提高等效分辨率。

这个多出来的是“等效分辨率”,反映更小更稳定的读数,而不是 ADC 真正能区分的最小电压单位。

② 原理:

由独立随机变量方差叠加规律(类似大数定理)可得,取均值可以降低类白噪声(均匀量化噪声+热噪声)的方差,提高 SNR 。而想要取均值的同时提高分辨率,就需要在取均值之前进行过采样(类白噪声:均值为 0 ,样本间不相关)。

③ 代价:

过采样和求均值对 SNR 和测量分辨率的改善是以增加 CPU 时间和降低数据吞吐率为代价的。

④ 上限:

过采样和取平均,本质上只能消除 ADC 测量中的随机抖动。当系统噪声主要来自热噪声或量化噪声时,增加平均次数可以让 ADC 的读数更细腻,从而获得比标称位数更高的等效分辨率。但当随机噪声已经被压到很低之后,剩下的误差主要来自低频噪声、非线性和参考电压不稳定等因素,这些误差无法通过平均消除,此时继续过采样只能让数值看起来更稳定,而无法再分辨更小的电压变化。

以 STM32F103 的 12 位 ADC 为例,在 3.3 V 参考条件下,其理论最小分辨电压为约 0.8 mV,但在实际系统中,未进行过采样时 ADC 输出抖动通常达到 2~3 LSB。通过 16~64 倍过采样与平均,可将随机噪声有效降低至亚 LSB 量级,使等效分辨率提升至约 11.5 bit。然而,当随机噪声被充分抑制后,低频噪声、非线性误差及参考电压波动成为主要限制因素,此时继续增加平均次数已难以进一步提高系统的有效分辨能力。

⑤ 公式与计算:

结合原理,根据要增加的分辨率位数计算过采样频率方程
f o s = 4 w ⋅ f s \Large f_{os} = 4^w ⋅ f_sfos=4wfs
其中f o s f_{os}fos是过采样频率,w ww是希望增加的分辨率位数,f s f_sfs是原采样频率。

假设原分辨率为N NN,则新的等效分辨率为N + w N+wN+w,想要获得一次分辨率为N + w N+wN+w的数据,那么就要以原分辨率采样4 w 4^w4w次,然后将这4 w 4^w4w个结果求和再除以2 w 2^w2w结果可能是小数,在工程中,我们往往是将求和结果右移w ww,以此来达到除以2 w 2^w2w并取整的效果,许多技术文档都是这样写的。然后再按新的量纲(最小刻度=V R E F + ÷ 2 N + w V_{REF+}÷2^{N+w}VREF+÷2N+w,刻度数=2 N + w 2^{N+w}2N+w换算成真实电压值。

【注意】求和结果除以2 w 2^w2w其实这是一种人为拉大刻度数的方法,后面要换算成实际电压时,要根据新的量纲来换算。其实际效果其实就等于 ”求和结果除以4 w 4^w4w,用 flaot 来存储结果,然后按原量纲(最小刻度=V R E F + ÷ 2 N V_{REF+}÷2^{N}VREF+÷2N)换算“(这种其实是学术理论上更正确的计算方法) 。

⑥ 举例:

要想让 12 位分辨率的 ADC 提高 1 位分辨率,采样频率就要提高4 1 = 4 4^1=441=4倍。

即需要 4 次采集,然后将这 4 次采集结果求和,求和的结果再取平均(除以2 1 = 2 2^1=221=2,二进制运算相当于右移 1 位),就得到提高等效分辨率后的结果(等效 13 位分辨率)。

⑦ 实验:

实验简要:

  • 功能描述:
    • 通过 ADC1 通道1(PA1)过采样实现 16 位分辨率采集 3.3V 的电压,并显示 ADC 转换的数字量及换算后的电压值
    • 使用 DMA 传输
    • 由于过采样这个方法的上限,这 16 位中其实只有 11/12 位左右的真实准确度
  • 确定最小刻度:
    • V R E F + = 3.3 V V_{REF+} = 3.3VVREF+=3.3V0 V ≤ V I N ≤ 3.3 V 0V≤V_{IN}≤3.3V0VVIN3.3V→ 最小刻度 =3.3 / 65536 V 3.3/65536V3.3/65536V
  • 确定转换时间:
    • 采样时间设置为最小值 1.5 个 ADC 时钟周期,可以得到转换时间为 1.17us * 256= 299.52us(得到 1 个有效数据)
  • 模式组合:
    • 连续转换模式、不使用扫描模式

⑧ 代码与实验结果:

adc.c 文件代码:

#include"adc.h"#include"delay.h"#include"usart.h"#include"dma.h"/* 全局变量 */ADC_HandleTypeDef hadc1;DMA_HandleTypeDef hdma_adc1;uint16_tadc_dma_buf[ADC_DMA_BUF_SIZE];volatileuint8_tadc_dma_done=0;/* ADC1 初始化 */voidMX_ADC1_Init(void){ADC_ChannelConfTypeDef sConfig={0};__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_DMA1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/* PA1 → ADC1_CH1 */GPIO_InitTypeDef gpio={0};gpio.Pin=GPIO_PIN_1;gpio.Mode=GPIO_MODE_ANALOG;HAL_GPIO_Init(GPIOA,&gpio);/* DMA1_Channel1 */hdma_adc1.Instance=DMA1_Channel1;hdma_adc1.Init.Direction=DMA_PERIPH_TO_MEMORY;hdma_adc1.Init.PeriphInc=DMA_PINC_DISABLE;hdma_adc1.Init.MemInc=DMA_MINC_ENABLE;hdma_adc1.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;hdma_adc1.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;hdma_adc1.Init.Mode=DMA_NORMAL;hdma_adc1.Init.Priority=DMA_PRIORITY_MEDIUM;HAL_DMA_Init(&hdma_adc1);/* ADC1 */hadc1.Instance=ADC1;hadc1.Init.ScanConvMode=ADC_SCAN_DISABLE;hadc1.Init.ContinuousConvMode=ENABLE;hadc1.Init.DiscontinuousConvMode=DISABLE;hadc1.Init.ExternalTrigConv=ADC_SOFTWARE_START;hadc1.Init.DataAlign=ADC_DATAALIGN_RIGHT;hadc1.Init.NbrOfConversion=1;HAL_ADC_Init(&hadc1);__HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1);/* ADC 校准 */HAL_ADCEx_Calibration_Start(&hadc1);/* 通道配置 */sConfig.Channel=ADC_CHANNEL_1;sConfig.Rank=ADC_REGULAR_RANK_1;sConfig.SamplingTime=ADC_SAMPLETIME_1CYCLE_5;HAL_ADC_ConfigChannel(&hadc1,&sConfig);/* DMA 中断 */HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,2,0);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);}/* 启动一次 ADC DMA 采集 */voidADC_DMA_Start(void){adc_dma_done=0;HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_dma_buf,ADC_DMA_BUF_SIZE);}/* DMA 传输完成回调 */voidHAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc){if(hadc->Instance==ADC1){adc_dma_done=1;}}/* DMA 中断入口 */voidDMA1_Channel1_IRQHandler(void){HAL_DMA_IRQHandler(&hdma_adc1);}

adc.h 文件代码:

#ifndef__ADC_H#define__ADC_H#include"sys.h"#defineADC_ADD_RESOLUTION4// ADC新增分辨率#defineADC_OVERSAMPLE_TIMES(1<<(2*ADC_ADD_RESOLUTION))// ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样#defineADC_DMA_BUF_SIZE(10*ADC_OVERSAMPLE_TIMES)// ADC DMA采集 BUF大小, 应等于过采样次数的整数倍externADC_HandleTypeDef hadc1;externDMA_HandleTypeDef hdma_adc1;externuint16_tadc_dma_buf[ADC_DMA_BUF_SIZE];externvolatileuint8_tadc_dma_done;voidMX_ADC1_Init(void);voidADC_DMA_Start(void);#endif

main.c 文件代码:

#include"sys.h"#include"delay.h"#include"usart.h"#include"led.h"#include"key.h"#include"string.h"#include"dma.h"#include"adc.h"/* ==================================================================== */intmain(void){uint16_ti,j;uint32_tsum;floatv;/* 初始化 */HAL_Init();Stm32_Clock_Init(RCC_PLL_MUL9);delay_init(72);LED_Init();uart_init(115200);MX_ADC1_Init();/* 启动第一次 DMA 采集 */ADC_DMA_Start();while(1){if(adc_dma_done){printf("DMA采集完成,10个结果分别为:");for(i=0;i<ADC_DMA_BUF_SIZE/ADC_OVERSAMPLE_TIMES;i++){for(j=0;j<ADC_OVERSAMPLE_TIMES;j++){sum+=adc_dma_buf[(i*ADC_OVERSAMPLE_TIMES)+j];}v=sum>>ADC_ADD_RESOLUTION;v=v*3.3f/65535.0f;printf("%.6fV ",v);sum=0;}printf("\r\n");/* 再来一次 */ADC_DMA_Start();}LED0=!LED0;delay_ms(100);}}

实验结果:

从结果可以看出,与之前的 3.299194V 还是有一些差别的。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!