当前位置: 首页 > article >正文

STM32的HAL库开发---多通道ADC采集(DMA读取)实验

一、实验介绍

1、功能描述 通过DMA读取数据

通过ADC1通道0/1/2/3/4/5PA0/1/2/3/4/5)采集测试电压,并显示ADC转换的数字量及换算后的电压值

2、确定最小刻度

VREF+ = 3.3V ---> 0V ≤ VIN ≤  3.3V --->最小刻度 = 3.3 / 4096 ,F1的分辨率是12位的,也就是把3.3V分为4096份。F4/F7/H7还可以自己配置分辨率,例如H7可以把分辨率配置为16位的,也就是把3.3V进行65536等分。

3,确定转换时间

采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us。例如配置为最长的采样时间239.5个采样周期,那么采样时间就是239.5 + 12.5  = 252个时钟周期。配置ADC的时钟的12M,则转换时间为252 * (1 / 12000000) = 21us,采样时间设置的越大,准确度越高,设置的越小,准确度越低。

4、模式组合

由于使用了DMA搬运,所以使用连续转换模式,多个通道,需要使用扫描模式

二、编程实战

使用ADC对PA0~PA6通道进行采集,然后DMA搬运到一个还有18个数的数组里,然后对应六个通道得3次采集结果。

1、寄存器版本

dma.c源程序

#include "./BSP/DMA/dma.h"
#include <string.h>

uint16_t ADC_data[18];
//配置ADC1的DMA1的通道1请求
void DMA_Init(void)
{
	//开启DMA1时钟
	RCC->AHBENR |= (1 << 0);
	
	//MEM2MEM 设置为非存储器到存储器模式
	DMA1_Channel1->CCR &= ~(1 << 14);
	
	//PL 设置通道优先级为中
	DMA1_Channel1->CCR |= (1 << 12);
	
	//MSIZE 设置存储器数据宽度为16位
	DMA1_Channel1->CCR |= (1 << 10);
	
	//PSIZE 设置外设数据宽度为16位
	DMA1_Channel1->CCR |= (1 << 8);
	
	//MINC 设置存储器增量模式
	DMA1_Channel1->CCR |= (1 << 7);
	
	//PINC 设置外设不增量模式
	DMA1_Channel1->CCR &= ~(1 << 6);
	
	//CIRC 不执行循环模式
	//DMA1_Channel1->CCR &= ~(1 << 5);
	DMA1_Channel1->CCR |= (1 << 5);
	
	//DIR 从外设读取
	DMA1_Channel1->CCR &= ~(1 << 4);	
	
	//设置传输数量
	DMA1_Channel1->CNDTR = 18;
	
	//设置DMA的外地址
	DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
	
	//清空ADC_data
	memset((void*)ADC_data,0,18);
	
	DMA1_Channel1->CMAR = (uint32_t)ADC_data;
	
	//EN 开启DMA  这个必须放在最后  不然CNDTR寄存器不能写入
	DMA1_Channel1->CCR |= (1 << 0);
}

adc.c源程序

#include "./BSP/ADC/adc.h"

//配置PA0 ---- PA5多通道采集ADC 使用DMA搬运
void ADC_Init(void)
{
	//开启ADC1时钟
	RCC->APB2ENR |= (1 << 9);
	
	//由于是多个通道  需要开启扫描模式
	ADC1->CR1 |= (1 << 8);
	
	//开启外部触发转换ADC
	ADC1->CR2 |= (1 << 20);
	
	//EXTSEL 设置软件触发ADC转换
	ADC1->CR2 |= (7 << 17);
	
	//ALIGN 数据右对齐
	ADC1->CR2 &= ~(1 << 11);
	
	//DMA 开启DMA转换
	ADC1->CR2 |= (1 << 8);
	
	//CONT 开启连续转换模式
	ADC1->CR2 |= (1 << 1);
	
	//设置ADC1通道0转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 0);
	
	//设置ADC1通道1转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 3);
	
	//设置ADC1通道2转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 6);
	
	//设置ADC1通道3转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 9);
	
	//设置ADC1通道4转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 12);
	
	//设置ADC1通道5转换时间为239.5个周期
	ADC1->SMPR2 |= (7 << 15);
	
	//这里注意这个L位  如果设置为0代表有一个转换
	//所以这里想转换6个通道设置为5即可  不要设置为6
	
	//L 设置总共6一个转换通道
	ADC1->SQR1 |= (5 << 20);
	
	//设置ADC第一个转换为通道0
	ADC1->SQR3 &= ~(0XF << 0);
	
	//设置ADC第二个转换为通道1
	ADC1->SQR3 |= (1 << 5);
	
	//设置ADC第三个转换为通道2
	ADC1->SQR3 |= (2 << 10);
	
	//设置ADC第四个转换为通道3
	ADC1->SQR3 |= (3 << 15);
	
	//设置ADC第五个转换为通道4
	ADC1->SQR3 |= (4 << 20);
	
	//设置ADC第六个转换为通道5
	ADC1->SQR3 |= (5 << 25);
	
	//EXTTRIG 开启GPIOA时钟
	RCC->APB2ENR |= (1 << 2);
	
	//设置PA0为输入模式
	GPIOA->CRL &= ~(0XF << 0);
	
	//设置PA1为输入模式
	GPIOA->CRL &= ~(0XF << 4);
	
	//设置PA2为输入模式
	GPIOA->CRL &= ~(0XF << 8);
	
	//设置PA3为输入模式
	GPIOA->CRL &= ~(0XF << 12);
	
	//设置PA4为输入模式
	GPIOA->CRL &= ~(0XF << 16);
	
	//设置PA5为输入模式
	GPIOA->CRL &= ~(0XF << 20);
	
	//ADON 开启ADC功能
	ADC1->CR2 |= (1 << 0);
	
	//RSTCAL 初始化ADC校准寄存器
	ADC1->CR2 |= (1 << 3);
	
	//等待ADC校准寄存器初始化完毕
	while(ADC1->CR2 & (1 << 3));
	
	//开启ADC校准
	ADC1->CR2 |= (1 << 2);
	
	//等待ADC校准完成
	while(ADC1->CR2 & (1 << 2));
	
	//SWSTART 开启规则组转换 软件触发
	ADC1->CR2 |= (1 << 22);
}

这里主意寄存器设置转换通道个数得时候,0代表转换一个通道,5代表转换6个通道。

2、库函数版本

dma.c原函数

#include "./BSP/DMA/dma.h"
#include <string.h>

extern ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma;
//配置ADC1的DMA1的通道1请求
void DMA_Init(void)
{
	//开启DMA1时钟
	__HAL_RCC_DMA1_CLK_ENABLE();
	
	//配置DMA1通道  因为ADC1连接在DMA1的通道1  这个在使用手册可以查找到
	hdma.Instance = DMA1_Channel1;
	
	//外设到内存
	hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
	//内存为16位
	hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
	//内存递增
	hdma.Init.MemInc = DMA_MINC_ENABLE;
	//循环搬运 
	//当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。
	hdma.Init.Mode = DMA_CIRCULAR;
	//外设16位
	hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
	//外设地址不递增
	hdma.Init.PeriphInc = DMA_PINC_DISABLE;
	//通道1优先级为中
	hdma.Init.Priority = DMA_PRIORITY_MEDIUM;

	//配置DMA的通道1为外设到内存
	HAL_DMA_Init(&hdma);
	
	//将DMA句柄于ADC句柄连接起来 可以理解为将这个DMA句柄拷贝到ADC句柄里的DMA句柄上
	__HAL_LINKDMA(&hadc,DMA_Handle,hdma);
}

//标志DMA数据搬运完
uint8_t state = 0;
//void DMA1_Channel1_IRQHandler(void)
//{
//	if(DMA1->ISR & (1 << 1))
//	{
//		state = 1;
//		//清除中断标志位
//		DMA1->IFCR |= (1 << 1);
//	}
//	DMA1->IFCR |= (1 << 0);
//}

adc.c源程序

#include "./BSP/ADC/adc.h"
#include "string.h"

extern DMA_HandleTypeDef hdma;
uint16_t ADC_data[18];
ADC_HandleTypeDef hadc;
//配置ADC1的通道1 PA1进行
void ADC_Init(void)
{
	hadc.Instance = ADC1;
	//配置ADC连续转换模式 就是ADC转换完成一次后会自动下一次转换
	hadc.Init.ContinuousConvMode = ENABLE;
	//转换结果采用右对齐
	hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
	//不开启间断模式
	hadc.Init.DiscontinuousConvMode = DISABLE;
	//ADC触发选用软件触发
	hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
	//设置ADC转换数量的 SQR1的L位
	hadc.Init.NbrOfConversion = 6;
	//设置间断模式写转换一次转换数量
	hadc.Init.NbrOfDiscConversion = 0;
	//多个通道开启扫描模式
	hadc.Init.ScanConvMode = ADC_SCAN_ENABLE;
	
	//设置ADC为软件触发转换
	HAL_ADC_Init(&hadc);
	
	//开启ADC校准
	HAL_ADCEx_Calibration_Start(&hadc);
	
	ADC_ChannelConfTypeDef sConfig;
	
	//配置通道0
	sConfig.Channel = ADC_CHANNEL_0;
	//通道0第一个转换
	sConfig.Rank = ADC_REGULAR_RANK_1;
	//采样周期采用239.5
	sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
	//配置ADC通道一为第一个转换
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	//配置通道1
	sConfig.Channel = ADC_CHANNEL_1;
	//通道1第二个转换
	sConfig.Rank = ADC_REGULAR_RANK_2;
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	//配置通道2
	sConfig.Channel = ADC_CHANNEL_2;
	//通道1第二个转换
	sConfig.Rank = ADC_REGULAR_RANK_3;
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	//配置通道3
	sConfig.Channel = ADC_CHANNEL_3;
	//通道1第二个转换
	sConfig.Rank = ADC_REGULAR_RANK_4;
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	//配置通道4
	sConfig.Channel = ADC_CHANNEL_4;
	//通道1第二个转换
	sConfig.Rank = ADC_REGULAR_RANK_5;
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	//配置通道5
	sConfig.Channel = ADC_CHANNEL_5;
	//通道1第二个转换
	sConfig.Rank = ADC_REGULAR_RANK_6;
	HAL_ADC_ConfigChannel(&hadc, &sConfig);
	
	memset((void*)ADC_data,0,18);
	
	//这里注意一定要取数据寄存器的地址
	//这句话最好写 不然下面那个会把半传输中断也开开  这里就会开一个中断 因为下面那个函数回把ADC半传输回调函数复制 
	//这句话不写也行 但是中断里边要清除所有位  不然卡在中断
	HAL_DMA_Start_IT(&hdma, (uint32_t)&ADC1->DR, (uint32_t)ADC_data, 18);
	
	//开启DMA传输 这个函数里边会把中断都打开
	HAL_ADC_Start_DMA(&hadc, (uint32_t* )ADC_data, 18);
}


void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
	//开启ADC1时钟
	__HAL_RCC_ADC1_CLK_ENABLE();
	
	//开启GPIOA时钟
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	GPIO_InitTypeDef GPIO_Init;
	
	GPIO_Init.Mode = GPIO_MODE_ANALOG;
	GPIO_Init.Pin = GPIO_PIN_0;
	GPIO_Init.Pull = GPIO_NOPULL;
	
	//设置PA0为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	GPIO_Init.Pin = GPIO_PIN_1;	
	//设置PA1为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	GPIO_Init.Pin = GPIO_PIN_2;	
	//设置PA2为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	GPIO_Init.Pin = GPIO_PIN_3;	
	//设置PA3为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	GPIO_Init.Pin = GPIO_PIN_4;	
	//设置PA4为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	GPIO_Init.Pin = GPIO_PIN_5;	
	//设置PA5为模拟输入模式
	HAL_GPIO_Init(GPIOA,&GPIO_Init);
	
	//使能DMA1通道1中断
	//HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
	
	//设置中断优先级
	HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,2,2);
}


http://www.kler.cn/a/558387.html

相关文章:

  • 【Spring+MyBatis】_图书管理系统(下篇)
  • 基于 Python+OpenCV 的疲劳检测系统设计与实现(源码+文档)
  • android studio kotlin项目build时候提示错误 Unknown Kotlin JVM target: 21
  • go 并发 gorouting chan channel select Mutex sync.One
  • 国产编辑器EverEdit - 在编辑器中对文本进行排序
  • 便捷批量字符一键查找替换工具
  • 从卡顿到丝滑:火山引擎DeepSeek-R1引领AI工具新体验
  • 传统的自动化行业的触摸屏和上位机,PLC是否会被取代?
  • 学习PostgreSQL专家认证
  • 蓝桥杯 Java B 组之背包问题、最长递增子序列(LIS)
  • 火语言RPA--Excel清空数据
  • AI前端开发助力自主创业:拥抱AI,开启你的创业之旅
  • Spring Boot ShardingJDBC分库分表(草稿)
  • 鸿蒙NEXT开发-应用数据持久化之键值型数据库
  • 【OS安装与使用】part6-ubuntu 22.04+CUDA 12.4运行MARL算法(多智能体强化学习)
  • SpringBoot启动失败之application.yml缩进没写好
  • brew Nushell mac升级版本
  • 前端排序算法完全指南:从理论到实践
  • Docker 的安全配置与优化(二)
  • 【微服务优化】ELK日志聚合与查询性能提升实战指南