蓝桥杯嵌入式开发指南 05-ADC 配置(R37)

实例

通过配置 ADC,采集 R37 电位器的电压,并显示到 LCD 显示屏上。

电路分析


该电位器的可调端接到了 M PB0 引脚,查阅 STM32F1 系列数据手册,PB0 引脚可复用为 ADC12_IN8。

工程建立

本次工程在上一次的工程上继续修改,复制上一次的工程作为本次的工程基础。

添加库函数

本例中使用到了 ADC 采集功能,故要添加以下库函数:

  • stm32f10x_adc.c

初始化 ADC1

将 PB0 配置为模拟输入,使能 PB 和 ADC1 时钟,配置 ADC 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void ADC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;

//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_ADC1,ENABLE);
//设置ADC时钟分频(本步骤用于提高ADC采集稳定性,可以忽略)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置电位器引脚PB0-AIN
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);

//配置ADC参数
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //独立扫描模式
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //关闭连续扫描
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //关闭外部触发
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_NbrOfChannel=1; //单通道转换
ADC_Init(ADC1,&ADC_InitStructure); //初始化ADC参数

//使能ADC
ADC_Cmd(ADC1, ENABLE);

//校准ADC
ADC_ResetCalibration(ADC1); //使能ADC1复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待ADC复位校准寄存器完成校准
ADC_StartCalibration(ADC1); //启动ADC1校准程序
while(ADC_GetCalibrationStatus(ADC1)); //等待ADC1校准程序完成校准
}

特别提醒


在初始化 ADC 的时候,需要注意以下两点:

  1. RCC_ADCCLKConfig (RCC_PCLK2_Div6); 这条语句的用于降低 ADC 采集速度,其作用实际上就是对 ADC 时钟进行 6 分频,提升 AD 采集的精确度。
  2. 在进行 ADC 校准前,一定要先使能 ADC,再进行校准,否则会导致程序在校准的地方卡死。

获取 ADC 数据

初始化完成后,通过软件触发 AD 转换的方式获取 AD 值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//参数channel:ADC1的采集通道
u16 ADC_Get(u8 channel)
{
//定义变量用于临时保存获取到的数据
u16 temp;
//配置ADC规则组通道初始化并传入参数channel
ADC_RegularChannelConfig(ADC1,channel,1,ADC_SampleTime_239Cycles5);
//软件触发AD转换
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
//等待AD转换结束
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==0);
//获取AD转换结果
temp=ADC_GetConversionValue(ADC1);
//结束AD转换
ADC_SoftwareStartConvCmd(ADC1,DISABLE);
//返回AD转换结果
return temp;
}

定义全局变量 ADC_Flag 作为触发标志:

1
u8 ADC_Flag=0;

修改定时器 1 中断,配置为每 500ms 触发一次 AD 转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void TIM1_UP_IRQHandler(void)
{
static u16 cnt=0,adc_cnt=0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET) //1ms到
{
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中断等待标志
if(++cnt>=50) //计时是否到50ms
{
cnt=0; //清空计时
Flag50ms=1; //1s到
if(++adc_cnt>=10)
{
adc_cnt=0;
ADC_Flag=1;
}
}
}
}

结果处理和显示

此时我们可以通过调用 u16 ADC_Get (u8 channel); 来获取 AD 采集到的数字量,查阅手册可知,STM32F103RBT6 的 ADC 精度为 12 为,也就是 即采集到的 AD 数字量在 0-4096 之间,当板载参考电压为 3.3V 时,每 1 数字量对应的电压为 。若要显示电压值,则应修改主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int main(void)
{
//定义变量用于保存ADC值
float ADC_Value=0;

SysTick_Config(SystemCoreClock/1000);
Delay_Ms(200);

STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
Timer1_Init();
LED_Init();
Key_Init();

//初始化AD
ADC1_Init();
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line1,(u8*)" Welcome!");

while(1)
{
if(Flag50ms)
{
Flag50ms=0;
Key_Scan();
}
sprintf((char*)Display_String," LED1-I/O:%d",L1_Status);
LCD_SetTextColor(Blue);
LCD_DisplayStringLine(Line2,Display_String);

if(ADC_Flag) //ADC转换间隔是否到达?
{
//清空ADC标志位
ADC_Flag=0;
//获取ADC数字量并转换为电压
ADC_Value=ADC_Get(8)*3.3/4096;
//将电压数据写入字符串中
sprintf((char*)Display_String," ADC:%.2fV",ADC_Value);
//在LCD第三行显示采集到的电压值
LCD_DisplayStringLine(Line3,Display_String);
}
}
}

下载测试

将程序下载到开发板,可以看到第三行显示了 ADC 采集到的 R37 的电压值,调整电位器,显示的数值随调整发生改变。