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

ops/2025/2/24 11:33:34/

一、实验介绍

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_datamemset((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);//开启外部触发转换ADCADC1->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第一个转换为通道0ADC1->SQR3 &= ~(0XF << 0);//设置ADC第二个转换为通道1ADC1->SQR3 |= (1 << 5);//设置ADC第三个转换为通道2ADC1->SQR3 |= (2 << 10);//设置ADC第四个转换为通道3ADC1->SQR3 |= (3 << 15);//设置ADC第五个转换为通道4ADC1->SQR3 |= (4 << 20);//设置ADC第六个转换为通道5ADC1->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;//配置通道0sConfig.Channel = ADC_CHANNEL_0;//通道0第一个转换sConfig.Rank = ADC_REGULAR_RANK_1;//采样周期采用239.5sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;//配置ADC通道一为第一个转换HAL_ADC_ConfigChannel(&hadc, &sConfig);//配置通道1sConfig.Channel = ADC_CHANNEL_1;//通道1第二个转换sConfig.Rank = ADC_REGULAR_RANK_2;HAL_ADC_ConfigChannel(&hadc, &sConfig);//配置通道2sConfig.Channel = ADC_CHANNEL_2;//通道1第二个转换sConfig.Rank = ADC_REGULAR_RANK_3;HAL_ADC_ConfigChannel(&hadc, &sConfig);//配置通道3sConfig.Channel = ADC_CHANNEL_3;//通道1第二个转换sConfig.Rank = ADC_REGULAR_RANK_4;HAL_ADC_ConfigChannel(&hadc, &sConfig);//配置通道4sConfig.Channel = ADC_CHANNEL_4;//通道1第二个转换sConfig.Rank = ADC_REGULAR_RANK_5;HAL_ADC_ConfigChannel(&hadc, &sConfig);//配置通道5sConfig.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.ppmy.cn/ops/160963.html

相关文章

计算机毕业设计SpringBoot+Vue.js个性化图书推荐系统(源码+LW文档+PPT+讲解+开题报告)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

CodeGPT 使用教程(适用于 VSCode)

CodeGPT 使用教程&#xff08;适用于 VSCode&#xff09; CodeGPT 是一个 VSCode 插件&#xff0c;可以让你在代码编辑器中直接调用 GPT 进行代码补全、优化、调试等操作。以下是详细的安装和使用步骤&#xff1a; 1. 安装 CodeGPT 方式 1&#xff1a;从 VSCode 插件市场安装…

Redis 缓存穿透、击穿、雪崩:问题与解决方案

在使用 Redis 作为缓存中间件时&#xff0c;系统可能会面临一些常见的问题&#xff0c;如 缓存穿透、缓存击穿 和 缓存雪崩。这些问题如果不加以解决&#xff0c;可能会导致数据库压力过大、系统响应变慢甚至崩溃。本文将详细分析这三种问题的起因&#xff0c;并提供有效的解决…

抓包工具 wireshark

1.什么是抓包工具 抓包工具是什么&#xff1f;-CSDN博客 2.wireshark的安装 【抓包工具】win 10 / win 11&#xff1a;WireShark 下载、安装、使用_windows抓包工具-CSDN博客 3.wireshark的基础操作 Wireshark零基础使用教程&#xff08;超详细&#xff09; - 元宇宙-Meta…

重订货点和安全库存

重订货点 重订货点是指当库存水平下降到某个特定值时&#xff0c;系统会自动触发采购或生产订单。其目的是确保在物料消耗完之前&#xff0c;能够及时补充库存。 安全库存 安全库存是为应对未来物资供应或需求的不确定性因素&#xff08;如突发性订货、交货期突然延期等&…

算法的复杂性分析以及时间复杂度的表示方法

算法复杂性是算法运行所需的计算机资源量。 需要的时间资源的量称为时间复杂度&#xff0c;TT(N,I) 需要的空间资源的量称为空间复杂度&#xff0c;TT(N,I) N代表问题的规模&#xff0c;I代表输入&#xff08;实例&#xff09; 空间复杂度与时间复杂度的分析方法类同&#…

TCP初始化序列号为什么要不一样

区分不同的连接(包括一些历史连接)、确保数据的顺序性、防止重放攻击&#xff08;时间戳&#xff09; 初始化序列号 ISN M F(localhost, localport, remotehost, remoteport)。 M是一个32位的计时器&#xff0c;这个计时器每隔 4 微秒加1&#xff0c;循环一次4.55小时F 是一…

Linux 内核中关于 CPU 编号和拓扑管理

CPU 拓扑结构定义 // topology.h struct cpu_topology {int thread_id; // SMT IDint core_id; // 核心 IDint package_id; // 物理 CPU IDint die_id; // Die IDcpumask_t thread_sibling; // SMT 线程掩码cpumask_t core_sibling; // 核心掩码 };CPU 在线…