STM32F407ZG开发板学习(9)
模数转换器ADC
模数转换,顾名思义是引脚上的连续模拟电压转换为内存中存储的数字变量,ADC的工作就是为模拟电路到数字电路搭建桥梁。
简介
STM32F4xx系列一般都有 3 个 ADC ,这些 ADC 可以独立使用,也可以使用双重/三重模式(提高采样率)。
STM32F4xx系列使用12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
STM32F407ZGT6包含有 3 个 ADC 。 STM32F4 的 ADC 最大的转换速率为 2.4 Mhz ,也就是
转换时间为 1us (在 ADCCLK= 36 M, 采样周期为 3 个 ADC 时钟下得到),不建议让 ADC 的时钟超
过 36 M ,否则将导致结果准确度下降。
ADC转换包含了两种通道,注入通道和规则通道,简单来说,规则通道即普通情况下的转换,注入通道则相当于中断,可以打断规则通道下的转换,在注入通道转换完成后才重新回到规则通道的转换。
多重ADC模式
具体见官方手册。
可实现以下四种模式:
● 注入同时模式
● 规则同时模式
● 交替模式
● 交替触发模式
也可按以下方式组合使用上述模式:
● 注入同时模式 + 规则同时模式
● 规则同时模式 + 交替触发模式
寄存器
状态寄存器 SR
OVR 是溢出标志
比较重要的就是 STRT (规则通道)和 JSTRT (注入通道)两个转换开始的标志,以及 EOC (规则通道)和 JEOC (注入通道)转换结束的标志。
AWD 是模拟看门狗事件发生标志。
上述各位由硬件置1,需要软件清0。其中, EOC 在读取 DR 数据寄存器后也会清0。
控制寄存器 CR1 & CR2
只介绍一些实验会用到的位。
PS:
ADC的分辨率指的是模数转换器所能表示的最大数是多少,即ADC的位数,如果ADC是10位ADC,那么分辨率是2的10次方,即1024的分辨率,如果模拟量是温度,测量范围是0~100度,那么可以把100度分成1024份,每一份你都能感知,当温度有100/1024度的变化时,能测量出来。
ADC的采样率指ADC每秒钟会进行多少次的模拟量转数字量的操作,如10K/s就是说ADC每秒钟,就采集了10K个模拟量,并将模拟量转换为数字量。当采样声时,一般的采样率是44Kbps/s,当采样温度时,几K/s的采样率就够了。
带 EXT 的都是外部触发相关,具体见官方手册。
比较重要的有:
通用控制寄存器 CCR
ADC 采样时间寄存器 SMPR1 & SMPR2
内部温度传感器
简介
STM32F4有一个内部的温度传感器,可以用来测量 CPU 及周围的温度 。该温度传感器在内部和 ADC 1 _IN16(STM32F40xx/F41xx 系列)或 ADC1_IN18(STM32F42xx/F43xx系列) 输入通道相连接,此通道把传感器输出的电压转换成数字值。 STM32F4 的内部温度传感器支持的温度范围为: 40~125度。精度为± 1. 5 ℃左右。
STM32F4内部温度传感器的使用很简单,只要设置一下内部 ADC ,并激活其内部温度传感器通道。具体见实验。
光敏传感器
简介
光敏传感器是最常见的传感器之一,它的种类繁多,主要有:光电管、光电倍增管、光敏电阻、光敏三极管、太阳能电池、红外线传感器、紫外线传感器、光纤式光电传感器、色彩传感器、 CCD 和 CMOS 图像传感器等。光传感器是目前产量最多、应用最广的传感器之一,它在自动控制和非电量电测技术中占有非常 重要的地位。
光敏传感器是利用光敏元件将光信号转换为电信号的传感器,它的敏感波长在可见光波长附近,包括红外线波长和紫外线波长。光传感器不只局限于对光的探测,它还可以作为探测元件组成其他传感器,对许多非电量进行检测,只要将这些非电量转换为光信号的变化即可。
光敏二极管
光敏二极管也叫光电二极管。光敏二极管与半导体二极管在结构上是类似的 其管芯是一个具有光敏特征的 PN 结,具有单向导电性,因此工作时需加上反向电压。
无光照时,有很 小的饱和反向漏电流,即暗电流,此时光敏二极管截止。当受到光照时 饱和反向漏电流大大增加,形成光电流 它随入射光强度的变化而变化。当光线照射 PN 结时,可以使 PN 结中产生电子一空穴对,使少数载流子的密度增加。这些载流子在反向电压下漂移,使反向电流增加。因此可以利用光照强弱来改变电路中的电流。利用这个电流变化,我们串接一个电阻,就可以转换成电压的变化,从而通过ADC 读取电压值,判断外部光线的强弱。
实验
ADC配置步骤
- 开启 PA 口时钟 和 ADC1 时钟 ,设置 PA 5 为模拟输入。
特别注意,对于IO 口复用为 ADC 我们要设置模式为模拟输入,而不是复用功能,也不需要调用 GPIO_PinAFConfig 函数(此函数里没有复用为ADC的选项)来设置引脚映射关系。 - 设置 ADC 的通用控制寄存器 CCR ,配置 ADC 输入时钟分频,模式为独立模式等 。
即创建 ADC_CommonInitTypeDef 类型结构体进行初始化。 - 初始化 ADC1 参数,设置 ADC1 的转换分辨率, 转换方式,对齐方式, 以及规则序列等 相关信息。 即创建 ADC_InitTypeDef 类型结构体进行初始化。
- 开启 AD 转换器。即调用 Cmd 函数。
- 设置规则序列1的通道,开启 ADC 转换,在转换完成后即可读取 ADC 的值。
注意 ADC 测量的电压不应超过3.3V,否则可能会被烧坏。
内部温度传感器实验
配置步骤
- 配置ADC1,具体见上一节。其中STM32F407ZG 内部温度传感器设置在 ADC1 的 通道 16 上。
- 激活 ADC 的内部通道,通过 ADC_CCR 的 TSVREFE 位( bit23 )设置。即函数:
ADC_TempSensorVrefintCmd(ENABLE);// 使能内部温度传感器 - 读取 ADC 数据寄存器即温度传感器引脚的电压值,再通过如下公式:
T (℃) = { (Vsense - V25) / Avg_Slope } + 25
上式中:
V25 是 Vsense 在 25 度时的数值(典型值为: 0.76 )。
Avg_Slope 是 温度与 Vsense 曲线的平均斜率(单位为 mv/℃或 uv/℃)(典型值为2.5mV /℃)。
即可计算得到内部CPU周围的温度。
代码
ADC配置
void adc_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
//使能GPIOA和ADC1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PA5
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
//复位ADC1
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
ADC_TempSensorVrefintCmd(ENABLE); //使能内部温度传感器
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //不使用DMA
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; //分频数4
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; //两个采样间隔时间
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //非连续转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//ADC_InitStructure.ADC_ExternalTrigConv = ; //无外部触发
ADC_InitStructure.ADC_NbrOfConversion = 1; //一个转换在转换序列中
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //数据12bit
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描模式
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE); //开启ADC1
}
读取ADC数据及温度值转换
uint16_t adc_get(uint8_t channel)
{
//配置规则通道
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_480Cycles);
//软件触发
ADC_SoftwareStartConv(ADC1);
//等待转换完成
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
uint16_t adc_get_average(uint8_t channel, uint8_t times)
{
uint32_t temp = 0;
uint8_t t;
for(t = 0; t < times; t++)
{
temp += adc_get(channel);
delay_ms(5);
}
return temp / times;
}
short adc_get_temperature(void)
{
uint32_t adc_dr;
short result;
double temperature;
adc_dr = adc_get_average(ADC_Channel_16, 20);
temperature = (double)adc_dr / 4096 * 3.3; //电压值
temperature = (temperature - 0.76) / 0.0025 + 25; //转换为温度值
result = temperature *= 100; //扩大100倍
return result;
}
main.c
int main(void)
{
uint16_t adc_dr;
double temp;
float temperature;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //延时初始化
usart_init(); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
adc_init();
while(1)
{
adc_dr = adc_get(ADC_Channel_16);
printf("ADC初始值:%u\n\r", adc_dr);
printf("\n\r");
temp = (double)adc_dr/4096*3.3;
printf("电压值:%f\n\r", temp);
printf("\n\r");
temperature = (double)adc_get_temperature()/100;
printf("温度值:%f\n\r", temperature);
printf("\n\r");
printf("\n\r");
LED0 = !LED0;
delay_ms(500);
}
}
实验结果
光敏传感器
实验过程也就是配置ADC3读取通道5的数据,即可转换光敏电阻的电压值。开发板原理图如下:
代码
ADC3相关
void adc3_init(void)
{
//GPIOF的配置在light_sensor.c
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
//使能GPIOF和ADC3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
//复位ADC3
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3, DISABLE);
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //不使用DMA
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; //分频数4
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; //采样时间
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //非连续转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//ADC_InitStructure.ADC_ExternalTrigConv = ; //无外部触发
ADC_InitStructure.ADC_NbrOfConversion = 1; //一个转换在转换序列中
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //数据12bit
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描模式
ADC_Init(ADC3, &ADC_InitStructure);
ADC_Cmd(ADC3, ENABLE); //开启ADC3
}
uint16_t adc3_get(uint8_t channel)
{
//配置规则通道
ADC_RegularChannelConfig(ADC3, channel, 1, ADC_SampleTime_480Cycles);
//软件触发
ADC_SoftwareStartConv(ADC3);
//等待转换完成
while(!ADC_GetFlagStatus(ADC3, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC3);
}
light_sensor.c
void light_sensor_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
//GPIO_InitStructure.GPIO_OType = ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//GPIO_InitStructure.GPIO_Speed = ;
GPIO_Init(GPIOF, &GPIO_InitStructure);
adc3_init();
}
uint8_t light_lenser_get_value(void)
{
uint32_t adc_dr = 0;
uint8_t t;
for(t = 0; t < 10; t++)
{
adc_dr += adc3_get(ADC_Channel_5);
delay_ms(5);
}
adc_dr /= 10;
//这两行参考正点原子的写法,但我没想通这么处理的原因。。
if(adc_dr > 4000)adc_dr = 4000;
return (uint8_t)(100 - adc_dr/40);
}
main.c
int main(void)
{
uint8_t lensor;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //延时初始化
usart_init(); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
light_sensor_init();
while(1)
{
lensor = light_lenser_get_value();
printf("亮度值(0-100):%u\n\r", lensor);
printf("\n\r");
LED0 = !LED0;
delay_ms(250);
}
}
实验结果
正常情况下在75~80,用手机手电筒照射可以到97左右,不同程度遮挡也会使值变小。