之前没学过数模电,对A/D D/A转换一窍不通,也百度了很多资料大都深奥难懂。。算了,先自以为是一下吧,等以后学了专业课再说。。(寒假回家一定要学。。恩 就这么决定了)看了那么多资料,感觉 A/D转换就是将电压(或者是其他模拟量:如 压力,图像等)转换为数字,D/A就是反过来,而ADC就是A/D转换器,他可以采集外部电压转化为数字。本节实验通过ADC采集外部电压转换为数字显示在屏幕上。
STM32 拥有 1~3 个 ADC( STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用,也可以使用双重模式(提高采样率)。 STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 18
个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。
STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换,
在注入通道被转换完成之后,规则通道才得以继续转换。
通过一个形象的例子可以说明: 假如你在家里的院子内放了 5 个温度探头,室内放了3个温度探头;
你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3 个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置
AD 循环扫描的通道,然后在释放按钮后需再次配置 AD 循环扫描的通道。
但本节只用到规则通道,因为是单次转换模式。。大概可以理解为我们现在只测量一个地方的电压值。。
配置ADC步骤如下:
1) 开启
PA 口和
ADC1 时钟,设置
PA1 为模拟输入。
STM32F103RCT6 的 ADC 通道 1 在 PA1 上,所以,我们先要使能 PORTA 的时钟,然后设置 PA1 为模拟输入。 使能 GPIOA 和 ADC 时钟用 RCC_APB2PeriphClockCmd 函数,设置 PA1的输入方式,使用 GPIO_Init 函数即可。这里我们列出 STM32 的 ADC 通道与 GPIO
对应表:
2)复位
ADC1,同时设置
ADC1 分频因子。
3)初始化
ADC1 参数,设置
ADC1 的工作模式以及规则序列的相关信息。
4)使能
ADC 并校准。
5)读取
ADC 值。
配置ADC的文件adc.c
#include "adc.h" void Adc_Init(void) { ADC_InitTypeDef ADC_ist; GPIO_InitTypeDef GPIO_ist; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE ); //72M/6=12,ADC 最大时间不能超过 14M RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置 ADC分频因子6 //PA1 作为模拟通道输入引脚 GPIO_ist.GPIO_Pin=GPIO_Pin_1; GPIO_ist.GPIO_Mode=GPIO_Mode_AIN;//模拟输入 GPIO_Init(GPIOA,&GPIO_ist); ADC_DeInit(ADC1);//复位 ADC1,将外设 ADC1 的全部寄存器重设为缺省值 ADC_ist.ADC_Mode= ADC_Mode_Independent;//ADC 独立模式 ADC_ist.ADC_ScanConvMode=DISABLE;//单通道模式 ADC_ist.ADC_ContinuousConvMode=DISABLE;//单次转换模式 //转换由软件而不是外部触发启动 ADC_ist.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; ADC_ist.ADC_DataAlign=ADC_DataAlign_Right;//ADC 数据右对齐 ADC_ist.ADC_NbrOfChannel=1;//顺序进行规则转换的 ADC 通道的数目 ADC_Init(ADC1,&ADC_ist); ADC_Cmd(ADC1,ENABLE);//使能指定的 ADC1 ADC_ResetCalibration(ADC1);//开启复位校准 while(ADC_GetResetCalibrationStatus(ADC1));//等待复位校准结束 ADC_StartCalibration(ADC1);//开启 AD 校准 while(ADC_GetCalibrationStatus(ADC1));//等待校准结束 } //获得 ADC 值 //ch:通道值 0~3 u16 Get_Adc(u8 ch) { //设置指定 ADC 的规则组通道设置它们的转化顺序和采样时间 ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换功能 while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//等待转换结束 return ADC_GetConversionValue(ADC1);//返回最近一次 ADC1 规则组的转换结果 } u16 Get_Adc_Average(u8 ch,u8 times) { u32 tem_val=0; u8 i; for(i=0;i<times;i++) { tem_val+=Get_Adc(ch); delay_ms(5); } return tem_val/times; }
adc.h
#ifndef _ADC_H #define _ADC_H #include "sys.h" #include "delay.h" void Adc_Init(void); u16 Get_Adc(u8 ch); u16 Get_Adc_Average(u8 ch,u8 times); #endif
主函数
#include "led.h" #include "delay.h" #include "sys.h" #include "usart.h" #include "lcd.h" #include "adc.h" void init() { delay_init(); //延时函数初始化 uart_init(9600); //串口初始化为9600 LED_Init(); //初始化与LED连接的硬件接口 LCD_Init(); Adc_Init(); POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,40,200,24,24,"ADC Test ^-^"); LCD_ShowString(60,70,200,16,16,"Medium difficulty"); LCD_ShowString(60,90,200,16,16,"2015/1/24"); LCD_ShowString(60,110,200,16,16,"By--Mr yh"); //显示提示信息 POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(60,130,200,16,16,"ADC_CH0_VAL:"); LCD_ShowString(60,150,200,16,16,"ADC_CH0_VOL:0.000V"); } int main(void) { u16 adcnum; float tem; init(); while(1) { adcnum=Get_Adc_Average(ADC_Channel_1,10); LCD_ShowxNum(156,130,adcnum,4,16,0);//显示ADC的值 tem=(float)adcnum*(3.3/4096); adcnum=tem; LCD_ShowxNum(156,150,adcnum,1,16,0);//显示电压值的整数位 tem-=adcnum; tem*=1000; LCD_ShowxNum(172,150,tem,3,16,0x80);//显示ADC的值的小数位 LED0=!LED0; delay_ms(250); } }
获得了ADC的值之后。。 再转换成电压值的公式就看不懂了。。orz
不过有一个地方需要注意 LCD_ShowxNum()的用法
再次翻出它的源码
//显示数字,高位为0,还是显示 //x,y:起点坐标 //num:数值(0~999999999); //len:长度(即要显示的位数) //size:字体大小 //mode: //[7]:0,不填充;1,填充0. //[6:1]:保留 //[0]:0,非叠加显示;1,叠加显示. void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode) { u8 t,temp; u8 enshow=0; for(t=0;t<len;t++) { temp=(num/LCD_Pow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { if(mode&0X80)LCD_ShowChar(x+(size/2)*t,y,'0',size,mode&0X01); else LCD_ShowChar(x+(size/2)*t,y,' ',size,mode&0X01); continue; }else enshow=1; } LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,mode&0X01); } }
看到最后一个参数的说明, mode 是一个8位的变量,第7位为0代表不填充,1代表填充。
一开始对填充这个概念没什么理解,于是将两种结果(填充和不填充)烧进去看了一下,发现显示0.001 的时候,如果选不填充,它会显示0. 1(点和1之间有2个空格),如果是填充就会显示0.001 (正常显示)所以我对填充的理解是:假如一个数6, 你想显示006,那么需要设置数的长度为3,填充模式