目录
需求3:毒气传感器 & 光敏传感器在同一个ADC利用查询等待方式获取数据
需求4:毒气传感器 & 光敏传感器 &温度传感器在同一个ADC利用查询等待方式获取数据
需求5:毒气传感器 & 光敏传感器 &温度传感器在同一个ADC利用中断方式获取数据
一、什么是ADC
12 位 ADC 是逐次逼近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部源、两个内部源和 VBAT 通道的信号;这些通道的 A/D 转换可在单次、连续、扫描或不连续采样模式下进行; ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
什么是12 位逐次逼近型ADC?
CPU只能处理数字量,我们要处理模拟量,需要将模拟量转换成数字量,在处理此过程就是ADC转换;芯片中有一个转换器可以将模拟信号转换成数字信号,就是ADC控制器
12位:ADC的采样深度
什么是采样深度?
总结:ADC的采样深度越深,转换的结果越精细;采样深度是衡量ADC性能的重要指标
ADC转换分为两个过程:采样+转换
采样:采集模拟量
转换:将模拟量转换为数字量
采样的原理:逐次逼近型(曹冲称象)
主要参数:
1、参考电压 每一个ADC转换器都会有一个参考电压 -------STM32f407 3.3v
2、精度 8bit 10bit 12bit 14bit 16bit
STM32f407中集成的是12bit精度(如果芯片内部集成的精度不够,可以外接高精度ADC 转换芯片)
3、ADC的分辨率 指它能够产生的不同数字输出的数量,通常以位数来表示。对于一 个N位的ADC,其分辨率是2的N次方。因此,12位ADC的分辨率是2^12,即4096。分辨率也就是精度。
ADC的输入电压范围被均匀地划分为这些离散的步骤,每个步骤对应一个唯一的数字输出值。例如,如果ADC的参考电压是3.3V,那么每个步骤大约代表 3.3v / 4096份 (约0.805mv)的电压变化分辨率。
电压转换公式:Vx=(3.3/4096)*数字量
(数字量/4096)*100可得到转换后数字量所占分辨率的百分比,相比于电压,更直观的看出转换后的状态。
总结:通过ADC转换后的结果就是数字量(份数)
二、如何配置ADC
1、ADC框图
2、功能说明
2.1 ADC转换开关
ADC控制器使能的情况下,将 ADON 位置 1 时,会将 ADC 从掉电模式中唤醒
SWSTART 位置 1 开始转换,规则通道
JSWSTART 位置 1 开始转换,注入通道
相关寄存器:ADC_CR2
2.2 ADC时钟
提供ADC转换所需要的时钟脉冲----APB2
2.3 通道选择
通过寄存器配置转换个数和转换排序
规则组:
设置有几个转换
第一个转换的通道号
第二个转换的通道号
..............
注入组同理。
相关寄存器:ADC_SQRx/ADC_JSQRx
规则:ADC_SQRx
L代表着有几个通道转换
SQx:第几个转换,写入的是通道号
若不写入通道号则无法对该通道进行转换
注入:ADC_JSQR
注入同理。
特殊说明:
测量内部温度 只能使用ADC1_IN16
内部参考电压 只能用ADC1_IN17
测量VBT 只能用ADC1_IN18
2.4 单次转换模式
在单次转换模式下, ADC 执行一次转换
CONT 位为 0 时,可通过以下方式启动此模式:
将 ADC_CR2 寄存器中的 SWSTART 位置 1(仅适用于规则通道)
将 JSWSTART 位置 1(适用于注入通道)
外部触发(适用于规则通道或注入通道)
完成所选通道的转换之后:
如果转换了规则通道:
— — — | 转换数据存储在 16 位 ADC_DR 寄存器中 EOC(转换结束)标志置 1 EOCIE 位置 1产生中断 |
如果转换了注入通道:
— — — | 转换数据存储在 16 位 ADC_JDR1 寄存器中 JEOC(注入转换结束)标志置 1 JEOCIE 位置 1 时将产生中断 |
然后, ADC 停止。
单次:CONT位 0
连续:CONT位 1
2.5 连续转换模式
在连续转换模式下, ADC 结束一个转换后立即启动一个新的转换
CONT 位为 1 时,可通过以下方式启动此模式:
将 ADC_CR2 寄存器中的 SWSTART 位置 1(仅适用于规则通道)
将 JSWSTART 位置 1(适用于注入通道)
外部触发(适用于规则通道或注入通道)
完成所选通道的转换之后:同单次转换
单次与连续的区别:单次转换只执行一次,而连续转换就是会循环转换
2.6 扫描模式
此模式用于扫描一组模拟通道
当只有一个转换的时候,应该用非扫描模式。
当有多于1个转换的时候,就要用扫描模式。
可以和单次转换、连续转换进行组合:
单次扫描模式
连续扫描模式
2.7 注入通道管理
触发注入
要使用触发注入,必须将 ADC_CR1 寄存器中的 JAUTO 位清零。
自动注入
如果将 JAUTO 位置 1,则注入组中的通道会在规则组通道之后自动转换
说明:
2.8 不连续采样模式
此模式适用于分组转换
相关寄存器:ADC_CR1
规则组
可将 ADC_CR1 寄存器中的 DISCEN 位置 1 来使能此模式
注入组
可将 ADC_CR1 寄存器中的 JDISCEN 位置 1 来使能此模式
2.9 数据对齐
右对齐
相关寄存器:ADC_CR2
2.10 预分频
一般选择2分频,如果数据偏差过大就选定在所在的频率范围内
相关寄存器:ADC_CCR
2.11 外部触发转换和触发极性
可以通过外部事件(例如,定时器捕获、 EXTI 中断线)触发转换 ----- 少用
相关寄存器:ADC_CR2
规则组:
注入组:
3、程序设计
3.1 查询等待方式
总体思路
ADC通道初始化配置函数
{
/*IO控制器配置*/
/*ADC控制器配置*/
}
获取数据转换函数
{
//开始转换
//等待转换完成
//获取转换数据
//返回数字量
}
详细思路
ADC通道初始化配置函数
{
/*IO控制器配置*/
//端口时钟使能
//端口模式选择----模拟模式
/*ADC控制器配置*/
//ADC控制器时钟使能
//CR1
//CR2
//SMPRx
//SQRx
//CCR
}
u16获取数据转换函数
{
//开始转换
//等待转换完成
//获取转换数据 把数据寄存器的值赋给一个变量
//返回数字量
}
3.2 中断方式
总体思路
ADC通道初始化配置函数
{
/*IO控制器配置*/
/*ADC控制器配置*/
/*NVIC控制器配置*/
}
中断服务函数
{
//判断是否为转换完成信号
//清除中断标志位
//获取转换数据 把数据寄存器的值赋给一个变量
}
详细思路
ADC通道初始化配置函数
{
/*IO控制器配置*/
//端口时钟使能
//端口模式选择----模拟模式
/*ADC控制器配置*/
//ADC控制器时钟使能
//CR1
//CR2
//SMPRx
//SQRx
//CCR
/*NVIC控制器配置*/
}
中断服务函数
{
//判断是否为转换完成信号
//清除中断标志位
//获取转换数据 把数据寄存器的值赋给一个变量
}
定时中断--->开始转换
三、具体使用ADC
需求1:光敏传感器利用查询等待方式获取数据
每100毫秒转换一次,通过串口打印转换数据(ADC1)
分析:
光敏----------PC2---------adc1_ch12
光敏电阻通常具有四个接口:AO(模拟信号输出)、DO(开关信号输出)、GND(接地)、VCC(电源正极)。接线时,需要将AO接口连接到单片机的AD转换模块,用于读取光线强度的模拟信号;DO接口则用于输出开关信号,当光线超过设定的阈值时,DO接口输出低电平(或高电平,具体取决于电路设计)。电源VCC通常接3.3V电源,GND接电源负极。
光敏电阻所用ADC初始化配置函数:
/*
函数名:adc1_ch12_light_sensor
函数功能:ADC1通道12转换初始化
返回值:void
形参:void
函数说明:
光敏----------PC2---------adc1_ch12
*/
void adc1_ch12_lightsensor_init(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式选择----模拟模式
GPIOC->MODER &= ~(3 << 4);
GPIOC->MODER |= (3 << 4);
/*ADC控制器配置*/
//ADC控制器时钟使能
RCC->APB2ENR |= (1 << 8);
//CR1
ADC1->CR1 &= ~(1 << 8);//扫描模式 单个通道使用
ADC1->CR1 &= ~(3 << 24);//分辨率---12位精度
//CR2
ADC1->CR2 &= ~(1 << 1);//单次转换模式
ADC1->CR2 |= (1 << 10);//结束转换选择
ADC1->CR2 &= ~(1 << 11);//右对齐
//SMPRx
ADC1->SMPR1 &= ~(7 << 6);
ADC1->SMPR1 |= (7 << 6);//采样时间
//SQRx
ADC1->SQR1 &= ~(0xf << 20);//规则通道序列长度---共1个转换
ADC1->SQR1 &= ~(0x1f << 0);
ADC1->SQR3 |= (12 << 0);//写入通道12
//CCR
ADC->CCR &= ~(3 << 16);
ADC->CCR |= (1 << 16);//预分频 4分频
//使能ADC转换
ADC1->CR2 |= (1 << 0);
}
获取数据函数:
/*
函数名:get_lightsensor_data
函数功能:光敏传感器获取数据函数
返回值:void
形参:void
函数说明:
在环境光线亮度达不到设定阈值时, DO 端输出高电平,
当外界环境光线亮度超过设定阈值时, DO 端输出低电平
*/
u16 get_lightsensor_data(void)
{
u16 adc1_data = 0;
//开始转换
ADC1->CR2 |= (1 << 30);//开始转换规则通道
//等待转换完成
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
adc1_data = ADC1->DR;
//返回数字量
return adc1_data;
}
二次封装关敏传感器初始化配置函数:
/*
函数名:lightsensor_init
函数功能:光敏传感器初始化
返回值:void
形参:void
函数说明:
在环境光线亮度达不到设定阈值时, DO 端输出高电平,
当外界环境光线亮度超过设定阈值时, DO 端输出低电平
*/
void lightsensor_init(void)
{
adc1_ch12_lightsensor_init();
}
需求2:毒气传感器利用查询等待方式获取数据
每100毫秒转换一次,通过串口打印转换数据(ADC2)
分析:
毒气----------PC0---------adc2_ch10
毒气传感器通常具有四个接口:AO(模拟信号输出)、DO(开关信号输出)、GND(接地)、VCC(电源正极)。接线时,需要将AO接口连接到单片机的AD转换模块,用于读取毒气浓度的模拟信号;DO接口则用于输出开关信号,当毒气浓度超过设定的阈值时,DO接口输出高电平(或低电平,具体取决于电路设计)。电源VCC通常接3.3V电源,GND接电源负极。
光敏电阻所用ADC初始化配置函数:
/*
函数名:adc1_ch10_gassensor
函数功能:ADC1通道10转换初始化
返回值:void
形参:void
函数说明:
毒气----------PC0---------adc2_ch10
*/
void adc2_ch10_gassensor_init(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式选择----模拟模式
GPIOC->MODER &= ~(3 << 0);
GPIOC->MODER |= (3 << 0);
/*ADC控制器配置*/
//ADC控制器时钟使能
RCC->APB2ENR |= (1 << 9);
//CR1
ADC2->CR1 &= ~(1 << 8);//扫描模式 单个通道使用
ADC2->CR1 &= ~(3 << 24);//分辨率
//CR2
ADC2->CR2 &= ~(1 << 1);//单次转换模式
ADC2->CR2 |= (1 << 10);//结束转换选择
ADC2->CR2 &= ~(1 << 11);//右对齐
//SMPRx
ADC2->SMPR1 &= ~(7 << 6);
ADC2->SMPR1 |= (7 << 6);//采样时间
//SQRx
ADC2->SQR1 &= ~(0xf << 20);//规则通道序列长度
ADC2->SQR1 |= (10 << 0);//写入通道10
//CCR
ADC->CCR &= ~(3 << 16);
ADC->CCR |= (1 << 16);//预分频 4分频
//使能ADC转换
ADC2->CR2 |= (1 << 0);
}
获取数据函数:
/*
函数名:get_gassensor_data
函数功能:毒气传感器获取数据函数
返回值:void
形参:void
函数说明:
*/
u16 get_gassensor_data(void)
{
u16 adc2_data = 0;
//开始转换
ADC2->CR2 |= (1 << 30);//开始转换规则通道
//等待转换完成
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
adc2_data = ADC2->DR;
//返回数字量
return adc2_data;
}
二次封装关敏传感器初始化配置函数:
/*
函数名:gassensor_init
函数功能:毒气传感器初始化
返回值:void
形参:void
函数说明:
*/
void gassensor_init(void)
{
adc2_ch10_gassensor_init();
}
需求3:毒气传感器 & 光敏传感器在同一个ADC利用查询等待方式获取数据
敏和毒气都进行转换.同一个ADC要有两个转换
分析:
光敏----------PC2---------adc1_ch12
毒气----------PC0---------adc1_ch10
扫描模式,通道排序
光敏电阻和毒气传感器所用ADC初始化配置函数(两个通道):
/*
函数名:adc1_ch12_light_sensor
函数功能:ADC1通道10和12转换初始化
返回值:void
形参:void
函数说明:
光敏和毒气都进行转换.同一个ADC要有两个转换
光敏----------PC2---------adc1_ch12
毒气----------PC0---------adc1_ch10
*/
void adc1_sensor_init(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式选择----模拟模式
GPIOC->MODER &= ~(3 << 4);
GPIOC->MODER |= (3 << 4);
GPIOC->MODER &= ~(3 << 0);
GPIOC->MODER |= (3 << 0);
/*ADC控制器配置*/
//ADC控制器时钟使能
RCC->APB2ENR |= (1 << 8);
//CR1
ADC1->CR1 |= (1 << 8);//扫描模式 多个通道使用
ADC1->CR1 &= ~(3 << 24);//分辨率
//CR2
ADC1->CR2 &= ~(1 << 1);//单次转换模式
ADC1->CR2 |= (1 << 10);//结束转换选择
ADC1->CR2 &= ~(1 << 11);//右对齐
//SMPRx
ADC1->SMPR1 &= ~(7 << 6);
ADC1->SMPR1 |= (7 << 6);//通道12光敏采样时间
ADC1->SMPR1 &= ~(7 << 0);
ADC1->SMPR1 |= (7 << 0);//通道10毒气采样时间
//SQRx
ADC1->SQR1 &= ~(0xf << 20);//规则通道序列长度
ADC1->SQR1 |= (1 << 20);//两个通道两次转换
ADC1->SQR3 |= (12 << 0) | (10 << 5);//写入通道10、12
//CCR
ADC->CCR &= ~(3 << 16);
ADC->CCR |= (1 << 16);//预分频 4分频
//使能ADC转换
ADC1->CR2 |= (1 << 0);
}
获取数据函数:
/*
函数名:get_light_or_gassensor_data
函数功能:光敏&毒气传感器获取数据函数
返回值:void
形参:void
函数说明:
*/
ADC_t get_light_or_gassensor_data(void)
{
ADC_t data;
//开始转换
ADC1->CR2 |= (1 << 30);//开始转换规则通道
//等待转换完成
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
data.adc_light = ADC1->DR;
//等待转换完成
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
data.adc_gas = ADC1->DR;
return data;
}
二次封装光敏和毒气传感器初始化配置函数:
/*
函数名:gas_light_sensor_init
函数功能:毒气&光敏传感器初始化
返回值:void
形参:void
函数说明:
*/
void gas_light_sensor_init(void)
{
adc1_sensor_init();
}
需求4:毒气传感器 & 光敏传感器 &温度传感器在同一个ADC利用查询等待方式获取数据
光敏、毒气和芯片内部温度都进行转换,ADC1要有三个转换
分析:
芯片内部温度必须用ADC1_IN16 不涉及IO口
温度使能配置
光敏电阻、毒气传感器和芯片内部传感器所用ADC初始化配置函数(三个通道):
/*
函数名:adc1_3sensor_init
函数功能:ADC1通道10、12和16转换初始化
返回值:void
形参:void
函数说明:
光敏和毒气都进行转换.同一个ADC要有两个转换
光敏----------PC2---------adc1_ch12
毒气----------PC0---------adc1_ch10
温度----------------------adc1_IN16
*/
void adc1_3sensor_init(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1 << 2);
//端口模式选择----模拟模式
GPIOC->MODER &= ~(3 << 4);
GPIOC->MODER |= (3 << 4);
GPIOC->MODER &= ~(3 << 0);
GPIOC->MODER |= (3 << 0);
/*ADC控制器配置*/
//ADC控制器时钟使能
RCC->APB2ENR |= (1 << 8);
//CR1
ADC1->CR1 |= (1 << 8);//扫描模式 多个通道使用
ADC1->CR1 &= ~(3 << 24);//分辨率
//CR2
ADC1->CR2 &= ~(1 << 1);//单次转换模式
ADC1->CR2 |= (1 << 10);//结束转换选择
ADC1->CR2 &= ~(1 << 11);//右对齐
//SMPRx
ADC1->SMPR1 &= ~(7 << 6);
ADC1->SMPR1 |= (7 << 6);//通道12光敏采样时间
ADC1->SMPR1 &= ~(7 << 0);
ADC1->SMPR1 |= (7 << 0);//通道10毒气采样时间
ADC1->SMPR1 &= ~(7 << 18);
ADC1->SMPR1 |= (7 << 18);//通道16毒气采样时间
//SQRx
ADC1->SQR1 &= ~(0xf << 20);//规则通道序列长度
ADC1->SQR1 |= (2 << 20);//三个通道三次转换
ADC1->SQR3 |= (12 << 0) | (10 << 5) | (16 << 10);//写入通道10、12、16
//CCR
ADC->CCR &= ~(3 << 16);
ADC->CCR |= (1 << 16);//预分频 4分频
ADC->CCR |= (1<<23);//使能温度传感器
//使能ADC转换
ADC1->CR2 |= (1 << 0);
}
光敏&毒气&温度传感器获取数据函数:
/*
函数名:get_3sensor_data
函数功能:光敏&毒气&温度传感器获取数据函数
返回值:void
形参:void
函数说明:
*/
ADC_t get_3sensor_data(void)
{
ADC_t data;
u16 temp;
//开始转换
ADC1->CR2 |= (1 << 30);//开始转换规则通道
//等待转换完成 光敏
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
data.adc_light = ADC1->DR;
//等待转换完成 毒气
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
data.adc_gas = ADC1->DR;
//等待转换完成 温度
while(!(ADC1->SR & (1 << 1)));
//获取转换数据
temp = ADC1->DR;
data.adc_temp = (temp * (3.3/4096)*1000 - 760) / 2.5 + 25;
return data;
}
芯片内部温度的计算公式:
Vsense指的是ADC转换后的电压值
需求5:毒气传感器 & 光敏传感器 &温度传感器在同一个ADC利用中断方式获取数据
光敏电阻、毒气传感器和芯片内部传感器所用ADC中断初始化配置函数(三个通道):
/*
函数名:adc1_ch12_Interrupt_init
函数功能:ADC1通道10、12、16转换中断初始化
返回值:void
形参:void
函数说明:
光敏----------PC2---------adc1_ch12
毒气----------PC0---------adc1_ch10
温度----------------------adc1_IN16
*/
void adc1_ch12_10_16_Interrupt_init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<2);
//端口模式配置 --- 模拟模式
GPIOC->MODER &= ~((3<<4)|(3<<0));
GPIOC->MODER |= ((3<<4)|(3<<0));
/*ADC控制器配置*/
//ADC时钟使能
RCC->APB2ENR |= (1<<8);
//CR1
ADC1->CR1 &= ~(3<<24); //12位分辨率
ADC1->CR1 |= (1<<8); //扫描模式
ADC1->CR1 |= (1<<5); //EOC中断使能
//CR2
ADC1->CR2 &= ~(1<<11); //数据右对齐
ADC1->CR2 &= ~(1<<1); //单次转换模式
ADC1->CR2 |= (1<<10); //EOCS位,每个通道转换完成EOC都会置1
//SMPRx
ADC1->SMPR1 |= (7<<6); //通道12 480个周期的采样时间
ADC1->SMPR1 |= (7<<0); //通道10 480个周期的采样时间
ADC1->SMPR1 |= (7<<18); //通道16 480个周期的采样时间
//SQRx
ADC1->SQR1 &= ~(0xf<<20);
//ADC1->SQR1 |= (2<<20); //3个转换通道
ADC1->SQR1 |= (2<<20); //3个转换通道
ADC1->SQR3 &= ~(0x1f<<0);
ADC1->SQR3 |= (12<<0); //第一个转换 通道12
ADC1->SQR3 &= ~(0x1f<<5);
ADC1->SQR3 |= (10<<5); //第二个转换 通道10
ADC1->SQR3 &= ~(0x1f<<10);
ADC1->SQR3 |= (16<<10); //第三个转换 通道16
//CCR
ADC->CCR &= ~(3<<16); //2分频ADC 42MHz
ADC->CCR |= (1<<23); //使能温度传感器
/*NVIC*控制器配置*/
//优先级分组 ----在主函数
//计算优先级编码值
u32 pri= NVIC_EncodePriority (5,0,2);
//设置具体中断源
NVIC_SetPriority(ADC_IRQn, pri);
//使能NVIC响应通道
NVIC_EnableIRQ(ADC_IRQn);
//使能ADC1
ADC1->CR2 |= (1<<0);
}
中断服务函数:
一开始的想法
看似逻辑没问题但是还是卡住,为什么?
因为在同一个ADC多个通道转换的过程中,每一个通道都是用同一个中断服务函数的,并且只要开启转换,ADC控制器就会依次进行转换(转换时间不固定);ADC控制器转换完成(EOC置1),然后又去执行下一次转换(每100ms),导致内核执行打印(约需要100ms)时被卡死了,所以可以在全部转换完成后再统一打印
那为什么能打印前两个?
因为第一个ADC转换的时间长了,到第二个转换触发中断后内核进行打印的时候,又触发了第三个转换的中断,当内核到达第三个中断时,ADC转换又触发了中断,依次进行,进而导致内核卡死
优化后的程序
/*
函数名:ADC_IRQHandler
函数功能:ADC转换中断服务函数
返回值:void
形参:void
函数说明:
*/
ADC_t data;
u8 ADC_cnt = 0;
void ADC_IRQHandler(void)
{
//判断是否为转换完成信号
if(ADC1->SR & (1 << 1))
{
ADC_cnt ++;
//清除中断标志位
ADC1->SR &= ~(1 << 1);
if(ADC_cnt == 1)//光敏
{
data.adc_light = 100 - ADC1->DR / 4096.0 * 100;//获取转换数据 把数据寄存器的值赋给一个变量
}
else if(ADC_cnt == 2)//毒气
{
data.adc_gas = ADC1->DR / 4096.0 * 100;
}
else if(ADC_cnt == 3)
{
data.adc_temp = (ADC1->DR * (3.3/4096)*1000 - 760) / 2.5 + 25;
printf("光敏数据:%d%%\r\n",data.adc_light);
printf("毒气数据:%d%%\r\n",data.adc_gas);
printf("芯片内部温度:%.2f℃\r\n",data.adc_temp);
ADC_cnt = 0;
}
}
}
定时中断:
if(Tim7_cnt[4] >= 100)
{
ADC1->CR2 |= (1 << 30);//开始转换规则通道
Tim7_cnt[4] = 0;
}