目录
1 项目功能
2 ADC工作原理
3 电路原理图
4 AT32 ADC应用
4.1 ADC基础操作流程
4.2 ADC触发
4.3 ADC采样和转换时间
4.4 读取ADC转换结果
5 示例程序
热敏电阻测量温度
模拟数字转换器(Analog-to-digital converter, ADC, A/D)是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。常见的 ADC 有并行比较型、逐次逼近型、双积分型、Sigma-Delta 型等。
并行比较型ADC工作原理
并行比较型ADC只需要一次比较就能得到转换结果,速度快。但需要电阻和比较器比较多,3位ADC即需要2^3=8个电阻、7个比较器;12位ADC需要2^12=4096个电阻和4095个比较器,成本高。所以常常在高速ADC中采用并行比较电路。
逐次逼近型ADC工作原理
逐次逼近型ADC需要的电阻和比较器比较少,但多了一个DAC(DAC需要的是电阻和运放并不多),生产成本低于并型比较型ADC。但AD转换需要进行多次比较,比始12位AD就需要最多进行12次比较才能得到转换结果,转换时间较长,一般用于中速ADC。逐次逼近型ADC平衡了生产成本和转换时间,许多单片机内部都集成了逐次逼近型ADC。
AT32F4具有3个12 位 ADC 是一种逐次逼近型模拟数字转换器。它有多达 23 个通道,可测量 21 个外部和 2 个内部信号源(内部参考电压源和内部温度传感器)。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
AT32F4的ADC 的输入时钟不得超过 28 MHz,它是由 PCLK2 经分频产生。ADC 时钟在最大频率 28MHz 时转换时间为 0.5 μs(系统时钟为 240MHz 时,ADC 时钟最大频率为 20M,转换时间则为 0.7 μs)。
ADC2 与 ADC1 不同之处在于:
a. ADC2没有连接内部温度传感器(Temp.sensor)与内部参考电压(V INTRV )。
b. ADC2没有DMA请求,如果需要DMA,可以和ADC1组成主从式ADC,共享ADC1的DMA请求。
ADC3 与 ADC1 不同之处在于:
a. ADC3没有连接内部温度传感器(Temp.sensor)与内部参考电压(V INTRV )。
b. ADC3的外部模拟输入通道管脚数目不同,参考手册19.4.1章详述各个ADC的模拟通道。
c. ADC3的触发来源不同,参考手册19.4.2.2章详列各个ADC的触发来源。
AT32的ADC模拟信号通道输入
每个 ADC 拥有多达 18 个模拟信号通道输入,以 ADC_INx 表示,x=0 至 17。
a. ADC1_IN0 至 ADC1_IN15 为外部模拟输入,ADC1_IN16 为内部温度传感器,ADC1_IN17为内部参考电压。
b. ADC2_IN0 至 ADC2_IN15 为外部模拟输入,ADC2_IN16 与 ADC2_IN17 为 V SS 。
c. ADC3_IN0 至 ADC3_IN3、ADC3_IN10 至 ADC3_IN13 为外部模拟输入,其余为 V SS 。
R11为热敏电阻NTC,负温度系数,B值为3950K。NTC 热敏电阻温度计算公式:
Rt = R *EXP(B*(1/T1-1/T2))
R是热敏电阻在T2常温下的标称阻值。10K的热敏电阻25℃的值为10K(即R=100K)。
T2=(273.15+25)
EXP是e的n次方
B值是热敏电阻的重要参数
通过转换可以得到温度T1与电阻Rt的关系T1=1/(ln(Rt/R)/B+1/T2)
对应的摄氏温度t=T1-273.15
ADC对应的引脚应该初始化成模拟输入模式。
ADC 的基础操作流程如下图所示,建议第一次上电后进行校准,以提升采样与转换准确度。待校准完成后可靠触发引起 ADC 采样转换,转换结束后即可读取数据。
启动一次AD转换的过程称为触发,分为软件触发和外部触发,处部触发又分为定时器触和外部引脚触发。
一次完整的AD转换过程分为采样和转换两个过程,采样和转换。采样时间可以设置,可以在以下8个采样周期中选择,1.5、7.5、13.5、28.5、41.5、55.5、71.5和239.5,单位为ADCCLK周期。AT32内部为12位逐次逼近型ADC,转换周期固定为12.5。
例如选择 1.5 周期,一次转换需要 1.5+12.5=14 个 ADCCLK 周期。如果选择最高的ADCCLK为28MHz,则一次完整的AD转换需要14/28=0.5uS。
采样周期越长,ADC的输入阻抗越大,输入阻抗越大,对于原始被测信号的影响就越小。
通常会在测量电路中加入运放来提高阻抗匹配特性,掌上实验室V8对于外部信号测量电路就加入了运放。
R12和R16组成的电阻分压电路把原始信号缩小11倍,经一级电压跟随器接入AD1;该信号再放大11倍接AD0。该设计好处就是提高了AIN的输入范围,把AD转换分成了两档,0~3V和0~30V。当AIN<3V时,通过AD0读取;当AIN>=3V时,通过AD1读取。R14和C35、R13和C34分别组成两个RC滤波电路。
每个 ADC 拥有自己的 ADC 状态寄存器(ADCx_STS):通道转换结束标志(CCE)置1,说明本次转换已经结束,该标志可以通过对该位写“0”或读转换结果寄存器清零。
读ADC转换结果一般有三种方式,查询、中断和DMA。查询就是程序主动读取CCE标志,当该位为1时,就可以读取转换结果;中断则是AD转换结束后会产生相应中断,在中断响应函数中读取转换结果;DMA方式则是转换结束后由DMA控制器直接把结果转移到指定内存中。中断和DMA方式需在AD转开始前进行配置。
ADC数据为12位,数据寄存器为16位,所以转换结果有两种存放模式左对齐和右对齐模式,如下图,常用是右对齐,方便软件处理。
本例用到的驱动包有adc、crm、gpio、misc和tmr。项目包含以下几个文件
下面只给出了main.c文件的完整代码,其它文件请参考掌上实验室V8系列教程(七)I2C应用 HP203B
main.c
#include "at32f403a_407_conf.h"
#include "display.h"
#include "math.h"
uint32_t gSysTick = 0;
/**
* @brief system clock config program
* @note the system clock is configured as follow:
* system clock = hick / 12 * pll_mult
* system clock source = pll (hick)
* sclk = 64000000
* ahbdiv = 1
* ahbclk = 64000000
* apb1div = 2
* apb1clk = 32000000
* apb2div = 2
* apb2clk = 32000000
* pll_mult = 16
* pll_range = LE72MHZ (less than 72 mhz or equal to 72 mhz)
* @param none
* @retval none
*/
void system_clock_config(void)
{
/* reset crm */
crm_reset();
/* enable hick */
crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);
/* wait till hick is ready */
while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET)
{
}
/* config pll clock resource */
crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_16, CRM_PLL_OUTPUT_RANGE_LE72MHZ);
/* enable pll */
crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
/* wait till pll is ready */
while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET)
{
}
/* select pll as system clock source */
crm_sysclk_switch(CRM_SCLK_PLL);
/* wait till pll is used as system clock source */
while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL)
{
}
/* config ahbclk */
crm_ahb_div_set(CRM_AHB_DIV_1);
/* config apb2clk */
crm_apb2_div_set(CRM_APB2_DIV_2);
/* config apb1clk */
crm_apb1_div_set(CRM_APB1_DIV_2);
/* update system_core_clock global variable */
system_core_clock_update();
}
/**
* TMR6初始化 周期为1ms
*/
void tmr6_init(void)
{
/* enable tmr14 clock */
crm_periph_clock_enable(CRM_TMR6_PERIPH_CLOCK, TRUE);
/* 64000000/64/1000 = 1000Hz*/
tmr_base_init(TMR6, 1000 - 1, 64 - 1);
tmr_cnt_dir_set(TMR6, TMR_COUNT_UP);
/* overflow interrupt enable */
tmr_interrupt_enable(TMR6, TMR_OVF_INT, TRUE);
/* tmr1 overflow interrupt nvic init */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(TMR6_GLOBAL_IRQn, 0, 0);
/* enable tmr1 */
tmr_counter_enable(TMR6, TRUE);
}
/**
* TMR6中断响应函数
*/
void TMR6_GLOBAL_IRQHandler(void)
{
if(tmr_flag_get(TMR6, TMR_OVF_FLAG) != RESET)
{
gSysTick++;
display_scan();
tmr_flag_clear(TMR6, TMR_OVF_FLAG);
}
}
/**
* ADC相关的初始化
*/
void ad_init(void)
{
//AD引脚
gpio_init_type gpio_init_struct;
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
gpio_init_struct.gpio_pins = GPIO_PINS_0;
gpio_init_struct.gpio_mode = GPIO_MODE_ANALOG;
gpio_init(GPIOB, &gpio_init_struct);
//AD时钟
crm_adc_clock_div_set(2); //apb2clk = 32M, adcclk = 32/2 = 16M
crm_periph_clock_enable(CRM_ADC1_PERIPH_CLOCK, TRUE);
//AD采样参数
adc_base_config_type adc_base_struct;
adc_base_struct.sequence_mode = FALSE;
adc_base_struct.repeat_mode = FALSE;
adc_base_struct.data_align = ADC_RIGHT_ALIGNMENT; //右对齐
adc_base_struct.ordinary_channel_length = 1;
adc_base_config(ADC1, &adc_base_struct);
adc_ordinary_channel_set(ADC1, ADC_CHANNEL_8, 1, ADC_SAMPLETIME_239_5);//最长采样时间提高输入阻抗
adc_ordinary_conversion_trigger_set(ADC1, ADC12_ORDINARY_TRIG_SOFTWARE, TRUE);
adc_enable(ADC1, TRUE);
//AD自检
adc_calibration_init(ADC1);
while(adc_calibration_init_status_get(ADC1));
adc_calibration_start(ADC1);
while(adc_calibration_status_get(ADC1));
}
/**
* 测量并显示温度
*/
void t_measure_display(void)
{
uint16_t ad;
float v,rt,t;
//启动转换
adc_ordinary_software_trigger_enable(ADC1, TRUE);
//等待转换完成
while(adc_flag_get(ADC1,ADC_CCE_FLAG)==RESET);
//读取转换结果并转换为温度
ad = adc_ordinary_conversion_data_get(ADC1);
v = 3000.0*ad/4095; //单位mV
rt = (3300 / v - 1) * 10000 ; // 3300(avcc) / v = (r + 10k) / 10k
t = 1.0 / ( log(rt/10000) / 3950 + 1.0/(273.15+25) ) - 273.15;
//显示温度(单位:0.1度)
display_dec_int(t*10);
}
int main(void)
{
//系统时钟设置为64M
system_clock_config();
//显示相关引脚初始化
DISP_gpio_pins_init();
//1ms中断
tmr6_init();
//AD相关初始化
ad_init();
uint32_t tick = gSysTick;
while(1){
if(gSysTick - tick >= 500){
t_measure_display();
tick = gSysTick;
}
}
}