stm32 adc 多通道切换与采样精度

一、运行环境

  • stm32f070
  • STM32F0xx_StdPeriph_Lib

二、多采样通道切换

在之前的项目中,涉及有多个采样通道的采样,一直采用adc + dma的方式。后来因为需求的问题,要改成每次只采样一个通道。但是却发现,无论切换哪个通道,读取回来的参数都是一样的,明显跟实际不符合。

库函数提供的切换通道的函数
ADC_ChannelConfig ( ADC1, ADC_Channel_11, ADC_SampleTime_239_5Cycles )
里面的实现有一点与我所理解的不一样:
ADCx->CHSELR |= (uint32_t)ADC_Channel;
这里用的是 或 而不是赋值符号。所以每次切换通道的时候,上一次的通道其实还在采样。
我的修改方法:

ADC_ChannelConfig ( ADC1, ADC_Channel_11, ADC_SampleTime_239_5Cycles )
ADC1->CHSELR = ADC_Channel_11

三、采样精度问题

3. 1 一些基础概念

  • VDDA: 模拟供电引脚(外部输入)
  • VSSA: 模拟地
  • Vrefint:内部参考电压。Vrefint 在单片机内部直接接到ADC_IN17,通过采样该通道的电压,可以计算出Vrefint。
最小值典型值最大值
1.2v1.23v1.25v

3.2 遇到的问题

adc 采样回来的电压跟实际电压偏差比较大。

问题分析:
  1. 采样时间短。
  2. 单片机供电电压不是稳定的3.3v。单片机虽然有模拟供电引脚,但是为了省成本,模拟供电引脚接了VDD。
解决办法:
采样时间的问题

一开始的采样周期选择的是ADC_SampleTime_13_5Cycles,但是误差一直比较大,选择最大的采样周期(ADC_SampleTime_239_5Cycles )后,误差才达标。

针对供电电压不稳的问题
  1. 使用标准电压芯片作为VDD,VDDA。

  2. 或者想办法计算出真正的模拟输入电压,也就是VDDA。
    2. 1 官方提供的计算VDDA 的公式
    在这里插入图片描述
    2.2 其中VREFINT_CAL 是每个芯片出厂的时候,厂家校正好的参数,存放在flash中的某个只读区域。针对我这款单片机,存放地址是0x1FFF F7BA- 0x1FFF F7BB 长度为两个byte
    在这里插入图片描述

    2.3 VREFINT_DATA 是从ADC_CHN17 读取到的内部参考电压adc值。
    2.4 我现在测试使用的单片机的实测数据:VDDA = 3.3v * 1524 /1615 ,即是3.144v。用万用表测到的vdd参数是3.150v。精确到0.01v。
    2.5 示例代码

#define BSP_ADC_RD_VREFINT_CAL()           (*(__IO uint16_t *)(0x1FFFF7BA)) //VREF_CAL 值

uint16_t bsp_adc_read_vrefint_data(void)
{
    ADC_ChannelConfig ( ADC1, ADC_Channel_Vrefint, ADC_SampleTime_239_5Cycles );
    ADC1->CHSELR = ADC_Channel_Vrefint;
    ADC_VrefintCmd ( ENABLE );
    ADC_StartOfConversion ( ADC1 );
    while (  ADC_GetFlagStatus ( ADC1, ADC_FLAG_EOC ) == RESET )
        ;
    return ADC_GetConversionValue ( ADC1 );
}
uint16_t bsp_adc_calc_vdda(void)
{
    return (uint16_t)(3.3 * BSP_ADC_RD_VREFINT_CAL() /  bsp_adc_read_vrefint_data());
}

四、参考文章

STM32如何通过内部VREF得到实际的VDDA值

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的 STM32 ADC通道采样和输出的代码示例: ``` #include "stm32f4xx.h" #define ADC1_DR_ADDRESS ((uint32_t)0x4001204C) void ADC_Configuration(void) { ADC_InitTypeDef ADC_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; GPIO_InitTypeDef GPIO_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOC, &GPIO_InitStructure); DMA_InitStructure.DMA_Channel = DMA_Channel_0; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_ADDRESS; DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADC_Values; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = 4; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream0, &DMA_InitStructure); ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 4; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_15Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_15Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_15Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4, ADC_SampleTime_15Cycles); ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); DMA_Cmd(DMA2_Stream0, ENABLE); } int main(void) { ADC_Configuration(); while (1) { // Wait for ADC conversion to complete while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // Read ADC values uint16_t adc1 = ADC_GetConversionValue(ADC1); uint16_t adc2 = ADC_GetConversionValue(ADC1); uint16_t adc3 = ADC_GetConversionValue(ADC1); uint16_t adc4 = ADC_GetConversionValue(ADC1); // Output ADC values printf("ADC1: %d, ADC2: %d, ADC3: %d, ADC4: %d\n", adc1, adc2, adc3, adc4); } } ``` 这个代码示例使用了 STM32F4 的 ADC1 模块,采样了 PC0、PC1、PC2 和 PC3 四个通道的模拟信号,使用 DMA2 将采样结果传输到内存中,然后输出到串口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gdut_llkkyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值