【STM32】ADC模拟数字转换(规则组多通道)+ DMA数据转运(外设到存储器)

本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发

目录

前言

ADC规则组扫描模式+DMA

定义变量

规则组配置

ADC初始化

连续模式

扫描模式

规则组通道个数

ADC初始化框架

DMA初始化

ADC和DMA使能

软件触发转运

代码框架

ADC扫描转换与DMA循环转运

单次扫描+单轮转运


前言

本篇博客会在下面两篇的代码及理解的基础上修改。阅读本篇之前建议先阅读以下两篇博客:

【STM32】ADC模拟数字转换-规则组单通道-CSDN博客

【STM32】DMA数据转运(存储器到存储器)-CSDN博客

ADC规则组扫描模式+DMA

之前学习了ADC规则组单通道,这种方式只能一次触发一个通道的ADC转换,虽然可以通过每次触发前改变序列上的通道号来对通道实现轮询的转换,但每次触发也只能转换一个通道的模拟量,不能一次ADC触发实现多个模数转换,效率较低。

本篇来学习ADC规则组的多通道转换,一次触发ADC转换可以实现转换多个通道,已知ADC规则组数据寄存器只有一个,当配置为多通道转换时,每个序列上的通道会依次把数据存在数据寄存器中。规则组转换完成一次时(标志位EOC为1),最终数据寄存器上的数据是最后一个指定的序列上的通道转换的值。前面通道的数据都会覆盖,因为组内每一个序列上通道转换完成时,没有标志位,无法通过软件实现检查标志位控制CPU来实现转运数据,那该怎么办呢?

可以通过ADC发出DMA请求,每一个序列上的通道转换完成都会发出DMA请求。ADC序列1的通道转换完成发出DMA请求,触发DMA转运,DMA就会把ADC规则组数据寄存器位置上的数据转运到指定地址,序列2的通道转换完成,数据覆盖上一次转化的数据,再次发出DMA请求,DMA转运数据(转运相当于复制了数据再转运,原数据依然在寄存器中)

图片来源于[8-1] DMA直接存储器存取_哔哩哔哩_bilibili
参考手册

DMA请求 因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用 DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据。 只有在规则通道的转换结束时才产生DMA请求,并将转换的数据从ADC_DR寄存器传输到用户指定的目的地址。

注: 只有ADC1和ADC3拥有DMA功能。由ADC2转化的数据可以通过双ADC模式,利用ADC1的 DMA功能传输。

定义变量

ADC分辨率为12位,但规则组通道数据寄存器为16位,我这里数据设置右对齐,定义无符号16位储存规则组4个通道的值

关于数据对齐的解释这里不再赘述

uint16_t AD_Value[4]; //存放DMA转运的ADC转换数据

规则组配置

将ADC1的通道0 ,1,2,3分别放在序列1,2,3,4的位置,采样时间都为55.5个ADC周期

cpp 复制代码
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);

ADC初始化

连续模式

触发ADC转换一次就可使ADC连续不停的转换,从序列1的通道到指定的序列号上的通道转换完成后,再次从序列1的通道转换

cpp 复制代码
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续模式使能

扫描模式

扫描模式用于规则组多通道,从第一个序列到指定序列,依次扫描,就是依次转换模拟量

    ADC_InitStructure.ADC_ScanConvMode = ENABLE;

规则组通道个数

指定的序列号,共有几个通道要转换

ADC_InitStructure.ADC_NbrOfChannel = 4;

ADC初始化框架

cpp 复制代码
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_NbrOfChannel = 4;

DMA初始化

cpp 复制代码
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//ADC1规则组数据寄存器的32位地址(基地址)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//半字,16位
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设基地址指针自增失能,不用自增,固定指(uint32_t)&ADC1->DR
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器32位基地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//半字,16位
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//转运数据寄存器地址指针自增
    DMA_InitStructure.DMA_BufferSize = 4;//一次DMA触发的转运次数
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式,与ADC连续模式对应
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//存储器到存储器失能,也是软件触发失能。用硬件触发,本代码硬件触发为ADC1

ADC和DMA使能

cpp 复制代码
    DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA1的通道1
	ADC_DMACmd(ADC1, ENABLE);//使能ADC发出DMA请求,与硬件触发DMA对应
    ADC_Cmd(ADC1, ENABLE);//ADC上电

软件触发转运

软件触发ADC转换模拟量,不用硬件触发

cpp 复制代码
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//与ADC软件触发对应

代码框架

ADC扫描转换与DMA循环转运

ADC开启连续转换模式,ADC规则组内转换完成就会再次重复转换。DMA开启循环转运模式。这样就不需要每次都软件触发ADC转换,且待DMA置转运完成标志位,再去取数据了。只需要软件触发一次,就可ADC连续转换,DMA循环转运到存储器,初始化后数据直接读取寄存器即可

cpp 复制代码
uint16_t AD_Value[4]; //存放DMA转运的ADC转换数据

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
		
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_InitStructure.ADC_NbrOfChannel = 4;
	ADC_Init(ADC1, &ADC_InitStructure);
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 4;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

单次扫描+单轮转运

将上面代码主函数的软件触发ADC删除;再将ADC连续模式失能,软件触发转换后,规则组转换扫描一次就停止;DMA转运循环模式失能,ADC规则组每个通道转换完,发出DMA请求后,转运次数减一,直到转运次数为0,一轮转运就结束,ADC各个通道的数据就可以取出来了

cpp 复制代码
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //ADC扫描模式失能

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA转运一轮

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能软件触发ADC转换

获取ADC数据

获取ADC数据,需要触发ADC转换和改变转运次数 ,等到DMA转运完成后就可取出数据

cpp 复制代码
void AD_GetValue(void)
{	
	DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, 4);
    DMA_Cmd(DMA1_Channel1, ENABLE);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);	
}
相关推荐
售后宝13 分钟前
服务机器人三甲坎德拉:用智能化开启售后服务新篇章
经验分享·制造
blessing。。26 分钟前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
yuwinter35 分钟前
鸿蒙HarmonyOS学习笔记(2)
笔记·学习·harmonyos
志-AOX36 分钟前
Python编程艺术:优雅与实用的完美平衡(推导式)
经验分享
LZXCyrus1 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
志-AOX1 小时前
AI安全:从现实关切到未来展望
经验分享
游走于计算机中摆烂的2 小时前
启动前后端分离项目笔记
java·vue.js·笔记
嵌新程2 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012302 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)2 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件