[APM32F4]

APM32外设ADC的配置和应用

[复制链接]
981|3
手机看帖
扫描二维码
随时随地手机跟帖
susutata|  楼主 | 2022-5-19 17:07 | 显示全部楼层 |阅读模式
本帖最后由 susutata 于 2022-10-27 14:59 编辑

#申请原创#
@21小跑堂
APM32外设ADC的配置和应用


01 ADC简介

数转换器,ADC(Analog to Digital Converter),是一个将模拟信号转换为数字信号的器件(电路),例如将温度、湿度、压力、位置等信息转换为数字信号。但由于数字信号本身不具有实际意义,仅仅表示一个相对大小。所以ADC都需要一个参考模拟量(REF)作为转换的标准。
STM32外设ADC的配置和应用-绘图-ADC.png


## ADC分类
ADC按工作原理可以分成直接ADC和间接ADC。主要有以下几种:并联比较型ADC;逐次逼近型ADC;双积分型ADC。
其中逐次逼近型ADC是一种直接ADC。由于其采样速率中等,分辨率中等,且位数较多时使用元器件较少等原因(成本较低),所以被广泛应用于集成ADC中。
ADC分类.png

## ADC转换原理
A/D转换的作用是将时间、幅值连续的模拟信号转换为时间、幅值离散的数字信号。所以,A/D转换一般要经过采样、保持、量化及编码四个过程。


## ADC转换步骤
##
# 采样和保持
采样是指在时间上将模拟信号离散化,即是将时间上连续的信号转为一系列等时间间隔的信号离散序列。其中离散信号脉冲的幅度取决于输入模拟量。下图列举了一个模拟信号从采样到保持的过程。
> 采样需要满足采样原理: 采样频率大于模拟信号中最高频率成分的两倍时,采样值才能不失真的反映原来模拟信号。

SH.png

### 量化和编码
量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。而编码则是按照一定的规律,把量化后的值用二进制数字表示。下图列举了12bits ADC FSR为3.3V时的量化到编码的过程。

>参数介绍

n:分辨率,用于对输入进行量化的位数
FSR: Full-Scale Range,满量程
LSB: Least Significant Bit,最低有效位
MSB: Most Significant Bit,最高有效位
STM32外设ADC的配置和应用-绘图-量化.png

#### 分辨率
理论上,n位输出的ADC能区分2^n个不同等级的模拟输入电压。如上图所示,能分辨的最小输入电压步长LSB = FSR / 2^n = 806uV。


#### 量化误差
量化误差是由于量化过程引入的误差,通常是以输出误差的最大值形式标出。表示ADC实际输出的数字量和理论输出数字量之间的误差。使用“四舍五入法”时,ADC 转换器的量化误差是 ±½ LSB。
202205100948203.png

### 转换时间
转换时间是指ADC从转换控制信号触发开始,到输出端得到稳定的数字信号所经过的时间。该时间受ADC类型、ADC时钟和外部输入阻抗等因素影响。

02 APM32的ADC
APM32中的ADC是逐次逼近型ADC(Successive Approximation ADC),是逐个产生比较电压VREF,并逐次与输入电压分别比较,以逐渐逼近的方式进行A/D转换的。

SAR ADC的转换原理是把输入的模拟信号按规定的时间间隔采样(采样),并与一系列标准的数字信号相比较,数字信号逐次收敛,直至两种信号相等为止(量化),最后输出代表此信号的二进制数(编码)。

## ADC结构
结构上主要包括采样保持电路(S/H),比较器(COMPARATOR,COMP),SAR逻辑控制电路、时钟(CLOCK)和时序(TIMING)控制电路及DAC电路。

STM32外设ADC的配置和应用-绘图-SAR ADC结构.png

### S/H电路
被采样的脉冲宽度一般是很短的,在下一个采样脉冲到来之前,要暂时保持所采得的样值脉冲幅度,以便进行后续转换。所以,在采样电路之后要加保持电路。下图是一个简单的采样保持电路配置框图。
STM32外设ADC的配置和应用-绘图-SH电路.png

### DAC电路
大多SAR ADC的DAC都使用电容式DAC来提供内在的跟踪/保持功能。电容式DAC是采用电荷再分配原理来产生模拟输出电压的。电容式DAC由N个具有二进制权重值的电容器阵列再加上一个“虚拟LSB”电容器组成。
> 电容器阵列容量总量要等于2C。
STM32外设ADC的配置和应用-绘图-ADC转换 采样状态 - 副本.png

## 转换步骤
转换步骤数等于 ADC的分辨率,比如10bits ADC就有10个转换步骤,每个 ADC 时钟产生一个数据位。以下步骤以10bits ADC为例。


### 采样状态
该状态下,电容充电至电压VIN。SA切换至VIN,采样期间SB开关闭合。
STM32外设ADC的配置和应用-绘图-ADC转换 采样状态.png

### 保持状态
该状态下,输入断开,电容保持输入电压。SB开关打开,然后S1-S11切换至接地,且SA切换至VREF。
STM32外设ADC的配置和应用-绘图-ADC转换 保持状态.png

### 量化和编码状态
该状态下,每个 ADCCLK 执行一个步骤,每一步完成后 ADC 输出一位数。采用二分法进行逐次逼近到 ADC 的精度(位数)。整个转换过程如下图所示。
STM32外设ADC的配置和应用-绘图-STM32转换.png

整个逐次逼近的步骤如下面的二叉树所示。

202205100957778.png

#### 例子
比如2.5V输入到以3.3V为参考电压的SAR ADC中,则转换过程如下所示。

第一个逼近步骤时,MSB先设置为1。DAC以1/2 REF去和VIN比较,若VIN > 1/2 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步。

STM32外设ADC的配置和应用-绘图-SAR ADC转换2.png
第二个逼近步骤时,MSB往后移动1个bit,再以3/4 REF去和VIN进行比较,若VIN > 3/4 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步,一直到所有bit确定,然后输出编码值。

STM32外设ADC的配置和应用-绘图-SAR ADC转换3.png
以此类推到ADC的精度为止。

## 转换时间
APM32中的ADC转换时间 = 采样周期 + 转换周期

### 采样周期
采样周期设置决定,要注意的是,该值需要和外部电路的输入阻抗匹配。从而保证在采用阶段,采样保持电容有足够的时间充电。
> 如前述采样保持电路所示,其中有一个采样保持电容C。


### 转换周期
该值取决于ADC的转换精度,APM32F4xx的SAR ADC默认为12bits,可配置为10、8、6bits。

Snipaste_2022-05-19_15-44-23.png

## 转换数值
ADC 转换的数值 = (VIN x 2^n) / VREF,n为ADC的分辨率。以上述10bits的ADC为例,则

ADC 转换的数值 = (VIN x 1024) / VREF




03 ADC的配置和应用
## 硬件设计
### 输入通道
本例子采用通道0,1,2,都采用排针接出。ADC采用容易受到外界干扰,使用时注意引脚间的抗干扰设计,以及避免ADC引脚和其他功能电路共用的情况。

#
## 输入电压范围
本例默认VSSA和VREF-接入GND,而VDDA和VREF+接入VDD。所以ADC的电压输入范围是0V~3.3V。

## 单通道转换
配置ADC CH0通道为单次连续转换模式,并开启中断。
/*!
* @brief       ADC Init
*
* @param       None
*
* @retval      None
*/
void ADC_Init(void)
{
    GPIO_Config_T   gpioConfig;
    ADC_Config_T    adcConfig;

    /** Enable GPIOA clock */
    RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);

    /** ADC channel 0 configuration */
    GPIO_ConfigStructInit(&gpioConfig);
    gpioConfig.mode    = GPIO_MODE_AN;
    gpioConfig.pupd    = GPIO_PUPD_NOPULL;
    gpioConfig.pin     = GPIO_PIN_0;
    GPIO_Config(GPIOA, &gpioConfig);

    /** Enable ADC clock */
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);

    /** ADC configuration */
    ADC_Reset();
    ADC_ConfigStructInit(&adcConfig);
    adcConfig.resolution          = ADC_RESOLUTION_12BIT;
    adcConfig.continuousConvMode  = ENABLE;
    adcConfig.dataAlign           = ADC_DATA_ALIGN_RIGHT;
    adcConfig.extTrigEdge         = ADC_EXT_TRIG_EDGE_NONE;
    adcConfig.scanConvMode        = DISABLE;
    ADC_Config(ADC1, &adcConfig);

    /** ADC channel 10 Convert configuration */
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_112CYCLES);

    /** Enable complete conversion interupt */
    ADC_EnableInterrupt(ADC1, ADC_INT_EOC);

    /** NVIC configuration */
    NVIC_EnableIRQRequest(ADC_IRQn, 1, 1);

    /** Enable ADC */
    ADC_Enable(ADC1);

    /** ADC start conversion */
    ADC_SoftwareStartConv(ADC1);
}

在中断服务函数中回调以下函数,并转换成对应电压。
> 该电压单位为mV,计算过程中没有保留精度。

/*!
* @brief       ADC interrupt service routine
*
* @param       None
*
* @retval      None
*/
void ADC_Isr(void)
{
    uint16_t adcData = 0;
    uint16_t voltage = 0;

    if (ADC_ReadStatusFlag(ADC1, ADC_FLAG_EOC))
    {
        ADC_ClearStatusFlag(ADC1, ADC_FLAG_EOC);
        adcData = ADC_ReadConversionValue(ADC1);
        voltage = (adcData * 3300) / 4095;
        printf("voltage : %d mV\r\n", voltage);
    }
}

## 多通道扫描
### 定义公共信息
/** save adc data*/
#define ADC_CH_SIZE     3
#define ADC_DR_ADDR     ((uint32_t)ADC1_BASE + 0x4C)

uint16_t adcData[ADC_CH_SIZE];

### 配置DMA
/*!
* @brief       DMA Init
*
* @param       None
*
* @retval      None
*/
void DMA_Init(void)
{
    /** DMA Configure */
    DMA_Config_T dmaConfig;

    /** Enable DMA clock */
    RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);

    /** size of buffer*/
    dmaConfig.bufferSize = ADC_CH_SIZE;
    /** set memory Data Size*/
    dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
    /** Set peripheral Data Size*/
    dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
    /** Enable Memory Address increase*/
    dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
    /** Disable Peripheral Address increase*/
    dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
    /** Reset Circular Mode*/
    dmaConfig.loopMode = DMA_MODE_CIRCULAR;
    /** set priority*/
    dmaConfig.priority = DMA_PRIORITY_HIGH;
    /** read from peripheral*/
    dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY;
    /** Set memory Address*/
    dmaConfig.memoryBaseAddr = (uint32_t)&adcData;
    /** Set Peripheral Address*/
    dmaConfig.peripheralBaseAddr = ADC_DR_ADDR;

    dmaConfig.channel = DMA_CHANNEL_0;
    dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
    dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_FULL;
    dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
    dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;

    DMA_Config(DMA2_Stream0, &dmaConfig);

    DMA_Enable(DMA2_Stream0);
}

### 配置ADC


这里默认APB2的CLK PCLK2为60MHz。那么当设置ADC分辨率为12bits,ADCCLK = PCLK2 / 2 = 30MHz时。结合前述“转换时间”章节中讲到的计算方式,可以得知单次
ADC转换时间 = 采样周期 + 转换周期
          = 3 x ADCCLK + 12 x ADCCLK
          = 15 ADC CLK
          = 15 / 30MHz
          = 0.5 us

每次ADC转换间隔为20 x ADCCLK。

/*!
* @brief       ADC Init
*
* @param       None
*
* @retval      None
*/
void ADC_Init(void)
{
    GPIO_Config_T gpioConfig;
    ADC_Config_T  adcConfig;
    ADC_CommonConfig_T adcCommonConfig;

    /** RCM Enable*/
    RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
    /** GPIO Configuration */
    GPIO_ConfigStructInit(&gpioConfig);
    gpioConfig.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
    gpioConfig.mode = GPIO_MODE_AN;
    gpioConfig.pupd = GPIO_PUPD_NOPULL;
    GPIO_Config(GPIOA, &gpioConfig);

    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);

    /** ADC Configuration */
    ADC_Reset();
    adcCommonConfig.mode = ADC_MODE_INDEPENDENT;
    adcCommonConfig.prescaler =  ADC_PRESCALER_DIV2;
    adcCommonConfig.accessMode = ADC_ACCESS_MODE_DISABLED;
    adcCommonConfig.twoSampling = ADC_TWO_SAMPLING_20CYCLES;
    ADC_CommonConfig(%adcCommonConfig);

    ADC_ConfigStructInit(&adcConfig);
    /** Set resolution*/
    adcConfig.resolution = ADC_RESOLUTION_12BIT;
    /** Set dataAlign*/
    adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
    /** Set scanDir*/
    adcConfig.scanConvMode = ENABLE;
    /** Set convMode continous*/
    adcConfig.continuousConvMode = ENABLE;
    /** Set extTrigEdge*/
    adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
    /** Set nbrOfConversion*/
    adcConfig.nbrOfChannel = ADC_CH_SIZE;

    ADC_Config(ADC1, &adcConfig);
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_480CYCLES);
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 2, ADC_SAMPLETIME_480CYCLES);
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_2, 3, ADC_SAMPLETIME_480CYCLES);

    DMA_Init();

    ADC_EnableDMA(ADC1);
    ADC_EnableDMARequest(ADC1);

    /** Enable ADC*/
    ADC_Enable(ADC1);
    ADC_SoftwareStartConv(ADC1);
}


使用特权

评论回复
kai迪皮| | 2022-5-19 19:42 | 显示全部楼层
很清晰,很透彻。o( ̄▽ ̄)d

使用特权

评论回复
Fanexs168| | 2022-5-23 15:32 | 显示全部楼层
给力,好东西~有空好好看下

使用特权

评论回复
WoodData| | 2022-5-24 11:16 | 显示全部楼层
学习学习

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

17

主题

27

帖子

3

粉丝