本帖最后由 susutata 于 2022-10-27 14:59 编辑
#申请原创#
@21小跑堂
APM32外设ADC的配置和应用
01 ADC简介
模数转换器,ADC(Analog to Digital Converter),是一个将模拟信号转换为数字信号的器件(电路),例如将温度、湿度、压力、位置等信息转换为数字信号。但由于数字信号本身不具有实际意义,仅仅表示一个相对大小。所以ADC都需要一个参考模拟量(REF)作为转换的标准。
## ADC分类ADC按工作原理可以分成直接ADC和间接ADC。主要有以下几种:并联比较型ADC;逐次逼近型ADC;双积分型ADC。 其中逐次逼近型ADC是一种直接ADC。由于其采样速率中等,分辨率中等,且位数较多时使用元器件较少等原因(成本较低),所以被广泛应用于集成ADC中。
## ADC转换原理
A/D转换的作用是将时间、幅值连续的模拟信号转换为时间、幅值离散的数字信号。所以,A/D转换一般要经过采样、保持、量化及编码四个过程。
## ADC转换步骤
### 采样和保持
采样是指在时间上将模拟信号离散化,即是将时间上连续的信号转为一系列等时间间隔的信号离散序列。其中离散信号脉冲的幅度取决于输入模拟量。下图列举了一个模拟信号从采样到保持的过程。
> 采样需要满足采样原理: 采样频率大于模拟信号中最高频率成分的两倍时,采样值才能不失真的反映原来模拟信号。
### 量化和编码
量化是用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。而编码则是按照一定的规律,把量化后的值用二进制数字表示。下图列举了12bits ADC FSR为3.3V时的量化到编码的过程。
>参数介绍
n:分辨率,用于对输入进行量化的位数
FSR: Full-Scale Range,满量程
LSB: Least Significant Bit,最低有效位
MSB: Most Significant Bit,最高有效位
#### 分辨率
理论上,n位输出的ADC能区分2^n个不同等级的模拟输入电压。如上图所示,能分辨的最小输入电压步长LSB = FSR / 2^n = 806uV。
#### 量化误差
量化误差是由于量化过程引入的误差,通常是以输出误差的最大值形式标出。表示ADC实际输出的数字量和理论输出数字量之间的误差。使用“四舍五入法”时,ADC 转换器的量化误差是 ±½ LSB。
### 转换时间
转换时间是指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电路。
### S/H电路
被采样的脉冲宽度一般是很短的,在下一个采样脉冲到来之前,要暂时保持所采得的样值脉冲幅度,以便进行后续转换。所以,在采样电路之后要加保持电路。下图是一个简单的采样保持电路配置框图。
### DAC电路
大多SAR ADC的DAC都使用电容式DAC来提供内在的跟踪/保持功能。电容式DAC是采用电荷再分配原理来产生模拟输出电压的。电容式DAC由N个具有二进制权重值的电容器阵列再加上一个“虚拟LSB”电容器组成。
> 电容器阵列容量总量要等于2C。
## 转换步骤
转换步骤数等于 ADC的分辨率,比如10bits ADC就有10个转换步骤,每个 ADC 时钟产生一个数据位。以下步骤以10bits ADC为例。
### 采样状态
该状态下,电容充电至电压VIN。SA切换至VIN,采样期间SB开关闭合。
### 保持状态
该状态下,输入断开,电容保持输入电压。SB开关打开,然后S1-S11切换至接地,且SA切换至VREF。
### 量化和编码状态
该状态下,每个 ADCCLK 执行一个步骤,每一步完成后 ADC 输出一位数。采用二分法进行逐次逼近到 ADC 的精度(位数)。整个转换过程如下图所示。
整个逐次逼近的步骤如下面的二叉树所示。
#### 例子
比如2.5V输入到以3.3V为参考电压的SAR ADC中,则转换过程如下所示。
第一个逼近步骤时,MSB先设置为1。DAC以1/2 REF去和VIN比较,若VIN > 1/2 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步。
第二个逼近步骤时,MSB往后移动1个bit,再以3/4 REF去和VIN进行比较,若VIN > 3/4 REF,则保持MSB = 1(反之则MSB = 0)。等待下一个ADCCLK,执行下一步,一直到所有bit确定,然后输出编码值。
以此类推到ADC的精度为止。
## 转换时间
APM32中的ADC转换时间 = 采样周期 + 转换周期。
### 采样周期
由采样周期设置决定,要注意的是,该值需要和外部电路的输入阻抗匹配。从而保证在采用阶段,采样保持电容有足够的时间充电。
> 如前述采样保持电路所示,其中有一个采样保持电容C。
### 转换周期
该值取决于ADC的转换精度,APM32F4xx的SAR ADC默认为12bits,可配置为10、8、6bits。
## 转换数值
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);
}
|