前言:ADC主要用于采集外部管脚,先说一个场景描述下面规则通道和注入通道。假如你在家里的院子内放了 5 个温度探头,室内放了 3 个 温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则 通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3 个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通 道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温 度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要重新配置 AD 循环扫描的通道,然后在释放按钮 后需再次配置 AD 循环扫描的通道。上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但 在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 AD 转换的分组将简化事件处理的程序并提高事件处理的速度。
目录
一、ADC基本概念
Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。
1、ADC的作用
采集传感器的数据,测量输入电压,检查电池电量剩余,监测温湿度等。
典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。
- 量程:能测量的电压范围 (可以接电阻等方法)比如0-3.3V
- 分辨率:ADC的分辨率通常以输出二进制数的位数表示,位数越多,分辨率越高,一般来说分辨率越高,转化时间越长。 二进制比如0-2^10,10位精度
- 转化时间:模拟输入电压在允许的最大变化范围内,从转换开始到获得稳定的数字量输出所需要的时间称为转换时间
- STM32F40X有3个ADC,每个可配置 12 位、10 位、8 位或 6 位分辨率
- 每个ADC 有16个外部通道(管脚)。另外还有两个内部 ADC 源(温度、电压) 和 V BAT(电池) 通道挂在 ADC1上。
- 这些通道的 A/D 转换可以单次、连续、扫描或间断模式执行ADC具有独立模式、双重模式(2个一起配合采样)和三重模式,对于不同 AD转换要求几乎都有合适的模式可选
- ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
二、功能框图介绍
1、电压输入范围
- ADC 输入范围为:V REF- ≤ V IN ≤ V REF+ 。由 V REF- 、V REF+ 、V DDA 、V SSA 、这四个外部引脚决定。
- 我们在设计原理图的时候一般把 V SSA 和 V REF- 接地,把 V REF+ 和 V DDA 接 3.3V,得到ADC 的输入电压范围为:0~3.3V
- 如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了
2、输入通道
一共3个ADC,每个ADC 有16个外部通道,对应外部管脚GPIO。另外VReFINT电源模板、VBat备用电池和温度传感器3个挂在ADC1上,所以ADC1有19路。
3、转换顺序
(1)规则通道组:
顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道。相当正常运行的程序。最多16个通道。
规则通道和它的转换顺序在ADC_SQRx寄存器中选择,规则组转换的总数应写入ADC_SQR1寄存器的L[3:0]中
例如 :1、2、3对应外部管脚(例如连接的是某个传感器),假如希望按3、1、2通道顺序转换。
我们看到ADC_SQRx(寄存器),每5个位为一组,每一组可以填最大32的数,数字就是填通道编号。
SQR3[4:0]代表需要第一个转换的通道,配置为3,
SQR3 5-9代表第二次转换的通道 ,配置为1
SQR3 10-14代表第三次转换的通道,配置为2
最后要把通道总数填入L[3:0],配置为3次。
下面是具体中文参考手册寄存器描述:
(2)注入通道组: (优先级高于规则通道组)
注入,可以理解为插入,插队的意思,是一种不安分的通道。相当于中断。最多4个通道。
注入组和它的转换顺序在ADC_JSQR寄存器中选择。注入组里转化的总数应写入ADC_JSQR寄存器的L[1:0]中
4、转换时间
ADC 时钟ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大值是 36MHz,典型值为30MHz。对于 STM32F407我们一般设置PCLK2=HCLK/2=84MHz。所以程序一般使用 4分频或者 6分频,保证在正常工作范围之内。
ADC 的总转换时间 (分为采样时间和转换时间,其中采样时间可以配置不少于3个周期,转换时间一般都是12个周期)
Tconv = 采样时间 + 12个周期
最小采样时间: T = 3 + 12 = 15个周期 =0.42us(ADC时钟=36MHz下得到
5、数据寄存器
(1)规则数据寄存器 ADC_DR
因为 ADC 的最大精度是 12 位,ADC_DR 是16 位有效,这样允许 ADC存放数据时候选择左对齐或者右对齐,具体是以哪一种方式存放,由 ADC_CR2的 11 位 ALIGN 设置。
如果是左对齐还要移位,一般用右对齐
(2)使用 DMA
由于规则通道组只有一个数据寄存器,因此,对于多个规则通道的转换,使用 DMA 非常有帮助。这样可以避免丢失在下一次写入之前还未被读出的 ADC_DR 寄存器中的数据。在使能 DMA 模式的情况下(ADC_CR2 寄存器中的 DMA 位置 1),每完成规则通道组中的一个通道转换后,都会生成一个 DMA 请求。每一个注册通道组都对应一个数据寄存器,一共有4个,采集完后就去读就行了。
6、中断
一种可以通过轮询不断查看状态寄存器,一搬是SR里的EOC还有一种方式可以通过中断,在中断里获取数据
-1转换结束中断
规则通道和注入通道的数据转换结束后,都可以产生中断
-模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,中断里可以设置一下功能
-2溢出中断
如果发生 DMA传输数据丢失,会置位 ADC 状态寄存器 ADC_SR的 OVR位,如果同时使能了溢出中断,那在转换结束后会产生一个溢出中断。
-3DMA 请求
规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA请求, 把转换好的数据直接存储在内存里面。
7、触发源
8、ADC工作模式
单次模式:要想下一次转换,要重新启动ADC
连续转换
主要针对多个通道
- 如果单次模式+扫描模式,会先扫描通道1、2、3按顺序然后停止,否则不会顺序扫描
- 如果连续模式+扫描模式,会先扫描通道1、2、3按顺序然后循环
一次去转换其中几个,分为好几次触发,用得比较少
三、单通道采集实例
要求:利用ADC采集ADC1的通道5(pa5)来读取外部电压值,探索者 STM32F4 开发板没有设计参考电压源在上面,但是板上有几个可以提供测试的地方:1,3.3V 电源。2,GND。3,后备电池。注意:这里不能接到板上 5V 电源上去测试,这可能会烧坏 ADC!。
1、cubmx配置
配置串口打印,rcc时钟168mhz,pa5的adc输入,温度adc采集
ADC配置
2、分析初始化ADC配置的内容
3、编程思路
(1)采用轮询方式
main.c代码
int fputc(int ch,FILE *p)
{
while(!(USART1->SR & (1<<7)));
USART1->DR = (uint8_t) ch;
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t adc_value;
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("adc test\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_ADC_Start(&hadc1);//开启ADC1
if(HAL_ADC_PollForConversion(&hadc1, 100)== HAL_OK) //如果转换完成
{
adc_value = HAL_ADC_GetValue(&hadc1);
printf("adc_vaule = %d\r\n",adc_value);
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(2)采用中断方式
main.c函数中开启adc中断,增加一个延时函数
int fputc(int ch,FILE *p)
{
while(!(USART1->SR & (1<<7)));
USART1->DR = (uint8_t) ch;
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t adc_value;
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("adc IT test\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_ADC_Start_IT(&hadc1);//开启ADC1
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
uint32_t adc_value;
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1){ //回调函数放在了adc.c中,采样完成就中断打印
adc_value = HAL_ADC_GetValue(&hadc1);
printf("adc_value:%d\r\n",adc_value);
}
}
4、实验效果
(1)单次采样效果
STM32的ADC单次采集实验效果
(2)中断中采样效果
四、多通道采集实例
要求:利用ADC采集ADC1的通道5(pa5)来读取外部电压值,16读取内部温度,实现多路采集功能。
1.cubmx配置
同上,增加一个温度adc采集
开启扫描模式并2个通道序列
2、编程思路
定义一个for循环,每次循环采集两遍ADC,观察数值,一个代表温度,一个代表管脚电压,后续可以对adc值和温度之间进行一个转换。
main.c
int fputc(int ch,FILE *p)
{
while(!(USART1->SR & (1<<7)));
USART1->DR = (uint8_t)ch;
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t value;
int i = 0;
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("adc 2 test \r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_ADC_Start(&hadc1);
for(i = 0;i<2;i++)
{
if(HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
{
value = HAL_ADC_GetValue(&hadc1);
printf("adc value:%d\n",value);
}
HAL_Delay(1000);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}