本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发
目录
前言
本篇博客会在下面两篇的代码及理解的基础上修改。阅读本篇之前建议先阅读以下两篇博客:
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);
}