【STM32学习笔记】ADC用法总结:轮询模式、DMA模式、扫描模式以及连续转换模式
文章目录
一、ADC简介
ADC(Analog-Digital Converter)模拟-数字转换器,可以将引脚上的连续变化的模拟电压转换为数字变量,建立模拟电路到数字电路的桥梁。
12 位 ADC 是逐次趋近型模数转换器。它具有多达 19 个复用通道,可测量来自 16 个外部 源、两个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续 采样模式下进行。ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。(以STM32F407系列)
二、ADC框图
ADC的框图还是很好理解的,这里不做详细介绍。
三、ADC实验代码解析
实验一:轮询单次采集
1、实验描述
使用STM32F407ZGTx的ADC1的5通道(IN5),使用轮询的方式测量输入的电压值,并通过串口发送出来
2、配置CubeMX
2.1选择芯片
2.2配置调试接口
2.3选择外部高速时钟
2.4配置时钟
2.5设置ADC
2.6开启串口,发送测量值
注:有关串口更详细的介绍,请见:【STM32学习笔记】串口总结:串口轮询模式、串口中断模式、串口DMA模式以及串口接收不定长数据
2.7配置路径
2.8配置debuge
3、逻辑代码
定义变量:
/* USER CODE BEGIN PV */
uint32_t ver = 0;
float voltage;
char message[20] = "";
/* USER CODE END PV */
在main函数中启动规则序列,等待转换完成,获取转换结果,并将结果转换为电压:
while (1)
{
//启动规则序列
HAL_ADC_Start(&hadc1);
//轮询查看等待转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
//获取转换结果
ver = HAL_ADC_GetValue(&hadc1);
//将结果转换为电压
voltage = ver * 3.3f/4095.0f;
sprintf(message, "ADC: %d %.2f V", ver, voltage);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
4、实验结果:
实验二:采集内置温度传感器的温度值
1、实验描述
与实验一类似,该实验采集内置温度传感器的温度值
2、配置CubeMX
只需将ADC1的通道配置为内部温度通道,其余配置与实验一相同
3、逻辑代码:
while (1)
{
uint32_t ver;
//启动规则序列
HAL_ADC_Start(&hadc1);
//等待转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
//获取转换结果
ver = HAL_ADC_GetValue(&hadc1);
//将结果转换为电压
voltage = ver * 3.3f/4095.0f;
//将电压值转换为温度
temperate = (voltage-0.76f)/0.0025f + 25.0f;
sprintf(message, "ADC: %d %.2fV", ver, temperate);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
4、实验结果
实验三:ADC连续转换模式
1、实验描述
使用ADC的连续转换模式,可以随时采集外部电压
2、配置CubeMX
在实验一的基础上启动连续转换即可
3、逻辑代码
因为开启了连续转换模式,启动函数HAL_ADC_Start就不能放在while循环里了
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//启动规则序列
HAL_ADC_Start(&hadc1);
//等待转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//获取转换结果
ver = HAL_ADC_GetValue(&hadc1);
//将结果转换为电压
voltage = ver * 3.3f/4095.0f;
sprintf(message, "ADC: %d %.2f V", ver, voltage);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
4、实验结果
实验四:多通道扫描模式
1、实验描述
多通道扫描模式,结合DMA运输数据
2、配置CubeMX
3、逻辑代码
3.1代码分析
ADC的DMA启动函数为:
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
/*
1、参数hadc:ADC句柄的指针
2、参数pData:指向接收缓冲区的指针
3、参数Length:要接收数据的数量
*/
进入该函数定义,发现设置了中断回调
点击进入ADC_DMAConvCplt,即DMA传输完成中断,其调用HAL_ADC_ConvCpltCallback(hadc)中断回调函数。
以上函数细节可自行翻阅源码。
所以使用HAL_ADC_Start_DMA开启ADC转换后,我们可以在DMA传输完成中断回调函数里进行数据处理。
3.2代码展示
/* USER CODE BEGIN PV */
uint16_t ver[2];
float voltage;
char message[20] = "";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//¿ªÆôת»»
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 2);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if(hadc == &hadc1){
sprintf(message, "%d %d", ver[0], ver[1]);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
}
}
/* USER CODE END 4 */
4、实验结果
从结果可以看出,两个数为一组,依次输出,ver[0]为输出电压的转换值,ver[1]为输出内部温度的转换值。
5、实验变式1
将DMA接收数组的大小由2变为4,即ver[2]改为ver[4],即接收到4个数据进入传输完成中断函数HAL_ADC_ConvCpltCallback
那么用一维数组接收数据,接收的数据与采样次数以及转化通道是怎么对应的呢?对应关系如下:
采样0-通道0 | 采样0-通道1 | 采样1-通道0 | 采样1-通道1 | 采样2-通道0 | 采样2-通道1 | … |
---|
代码修改:
/* USER CODE BEGIN PV */
uint16_t ver[4];
float voltage;
char message[20] = "";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//¿ªÆôת»»
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 4);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if(hadc == &hadc1){
sprintf(message, "%d %d %d %d", ver[0], ver[1], ver[2], ver[3]);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
}
}
/* USER CODE END 4 */
输出结果:
从结果可以看出,四个数为一组,依次输出,ver[0]为输出电压的转换值,ver[1]为输出内部温度的转换值,为第一次规则组转换结果;ver[2]为输出电压的转换值,ver[3]为输出内部温度的转换值,为第二次规则组转换结果。
6、实验变式2
将DMA接收数组由一维变为二维,即ver[4]改为ver[ADC_Length] [2],即接收完二维数组的所有数据就会进入传输完成中断回调函数HAL_ADC_ConvCpltCallback;那么用二维数组接收数据,接收的数据与采样次数以及转化通道是怎么对应的呢?对应关系如下:
DMA将ADC数据寄存器(如ADC1->DR)的值按顺序复制到ver中
采样0-通道0 | 采样0-通道1 |
---|---|
采样1-通道0 | 采样1-通道1 |
采样2-通道0 | 采样2-通道1 |
… | … |
二维数组的本质:内存中是连续存储的,等同于ver[ADC_Length*2]
这样做的好处是将通道0的采集数据放在一列,通道1的数据放在一列,在ADC_Length次采集后,处理数据时相较于一维数组更容易处理。
代码修改:
/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[20] = "";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//¿ªÆôת»»
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if(hadc == &hadc1){
sprintf(message, "%d %d %d %d %d %d", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
}
}
/* USER CODE END 4 */
输出结果:
实验五:多通道连续转换
1、实验描述
在实验四的基础上,开启ADC多通道扫描模式+连续转换模式
2、配置CubeMX
打开连续转换模式后,相应的DMA设置也应该打开循环模式
3、逻辑代码
因为开启了连续转换模式,启动函数就不能放在while循环里了,同时也不需要在中断回调函数里处理数据。可以随时处理数据
/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[30] = "";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//¿ªÆôÁ¬Ðø×ª»»ºó£¬Æô¶¯º¯Êý²»ÄÜ·ÅÔÚwhileÑ»·ÁË
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//不需要在中断函数里处理数据了
sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
4、实验结果
实验六:定时器触发ADC
1、实验描述
以上5个实验都是使用软件启动,此实验我们采用定时器触发ADC的方式,1s触发一次,基于实验5的双通道扫描模式
2、配置CubeMX
使用tim2,查阅手册,挂载在APB1上,84M
配置好TIM2为1s
开启定时器2的中断
3、逻辑代码
/* USER CODE BEGIN PV */
uint16_t ver[3][2];
float voltage;
char message[30] = "";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
//开启定时器
HAL_TIM_Base_Start_IT(&htim2);
//kai
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ver, 6);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
// HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim == &htim2){
sprintf(message, "%d %d %d %d %d %d\r\n", ver[0][0], ver[1][0], ver[2][0], ver[0][1], ver[1][1],ver[2][1]);
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
}
}
/* USER CODE END 4 */
4、实验结果
5、注:
该实验使用轮询方式,在while循环里使用HAL_ADC_GetValue(&hadc1)获取ADC的值,可以更充分地看到1秒出一个数