STM32G474之ADC多通道采集和DMA传输,在ADC转换中,使用软件触发ADC转换,然后采用DMA循环搬运多个ADC通道中的数据,自动读取AD数据。
1、测试程序
#if DMA_Interrupt ==1
//需要测试DMA中断
DMA_HandleTypeDef hDMA; //DMA句柄
uint32_t LED_cnt;
//用来测试DMA能否进入中断
//为了能看到DAM是否能产生中断,这里使用LED_cnt作为“软计数器”
#endif
uint32_t ADC1_RESULT[2];
//虽然ADC的值为16位,但是HAL库用32为存放,否则会导致内存溢出
//DAM在搬运数据时,也要使用32位,否则读到的ADC值会超过0xFFF,这个HAL库中的问题
void ADC1_Init(void);
void DMA1_Read_ADC_Value_Use_SOFTWARE_Triger(void);
void ADC1_Init(void)
{
ADC_HandleTypeDef hadc1; //ADC1句柄
ADC_ChannelConfTypeDef ADC1_ChanConf; //定义ADC1相关结构体
#if DMA_Interrupt ==0
//不用测试DMA中断
DMA_HandleTypeDef hDMA; //DMA句柄
#endif
__HAL_RCC_ADC12_CLK_ENABLE();
//使能ADC1时钟
//设置RCC->AHB2ENR寄存器bit13(ADC12EN位),ADC12EN=1,使能ADC1或ADC2时钟
__HAL_RCC_DMA1_CLK_ENABLE(); //DMA1时钟使能
__HAL_RCC_DMAMUX1_CLK_ENABLE();//设置RCC->AHB1ENR寄存器bit2(DMAMUX1EN位),令DMAMUX1EN=1,DMAMUX1时钟使能
///
hadc1.Instance=ADC1; //选中ADC1
hadc1.Init.ClockPrescaler=ADC_CLOCK_SYNC_PCLK_DIV4;
//4分频,ADCCLK=PCLK2/4=170/4=42.5MHZ
//设置ADCx_CCR寄存器bit17:16(CKMODE[1:0]]),CKMODE[1:0]=11,adc_hclk/4
hadc1.Init.Resolution=ADC_RESOLUTION_12B;
//12位模式
//设置ADC_CFGR寄存器bit4:3(RES[1:0]),令RES[1:0]=00b,AD转换结果为12位
hadc1.Init.DataAlign=ADC_DATAALIGN_RIGHT;
//右对齐
//设置ADC_CFGR寄存器bit15(ALIGN位),令ALIGN=0,AD转换结果为“右对齐”
hadc1.Init.ScanConvMode=ADC_SCAN_ENABLE;
//由于我们是多通道,所以用“扫描模式”
//转换以顺序模式执行,扫描方向是向上:从排名1到排名“n”。
hadc1.Init.ContinuousConvMode=ENABLE;
//打开连续转换
//设置ADC_CFGR寄存器bit13(CONT位),令CONT=1,ADC采用连续转换模式
hadc1.Init.DiscontinuousConvMode=DISABLE;
//不用“间断模式”;注意:不连续采样模式,只适用于“扫描模式”
//设置ADC_CFGR寄存器bit16(DISCEN位),令DISCEN=0,禁止不连续采样模式
hadc1.Init.NbrOfDiscConversion=0;
//设置ADC_CFGR寄存器bit19:17(DISCNUM[2:0]),令DISCNUM[2:0]=0,不连续采样通道数为0
hadc1.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_RISING;
//上升沿触发
//设置ADC->CFGR寄存器bit11:10(EXTEN[1:0]),EXTEN[1:0]=01b,硬件触发检测为上升沿
hadc1.Init.DMAContinuousRequests=ENABLE;
//设置ADC_CFGR寄存器bit0(DMAEN),令DMAEN=1,使用DMA连续请求数据
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
//数据溢出覆盖;当过载发生时,使用ADC_DR的新值覆盖
//设置ADC_CFGR寄存器bit12(OVRMOD),令OVRMOD=1,当检测到溢出时,将用最后一个转换结果覆盖ADC_DR寄存器
hadc1.Init.SamplingMode=ADC_SAMPLING_MODE_NORMAL;
//设置ADC_CFGR2寄存器bit27(SMPTRIG),令SMPTRIG=0,禁用“ADC轮询采样模式”
hadc1.Init.GainCompensation = 0;
//ADC增益设置为0
//设置ADC_CFGR2寄存器bit16(GCOMP),令GCOMP=0,常规ADC工作模式
hadc1.Init.OversamplingMode = DISABLE;
//设置ADC_CFGR2寄存器bit0(ROVSE),令ROVSE=0,不使用数据溢出覆盖功能,禁止过采样
hadc1.Init.NbrOfConversion=2;
//转换通道个数,3个转换在规则序列中
//设置ADC_SQR1寄存器的bit3:0(L[3:0]),令L[3:0]=2-1,表示“正则通道序列长度”为2,有2个AD转换
hadc1.Init.LowPowerAutoWait = DISABLE;
//关闭低功耗模式
//配置ADC_CR寄存器bit29(DEEPPWD位),令DEEPPWD=0,ADC not in Deep-power down
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
//ADC触发源选择“软件触发AD转换”
//设置HRTIM->ADC1R寄存器bit5(ADC1EEV1位),ADC1EEV1=0,使用“内部软件”触发一次ADC转换
// hadc1.Init.ExternalTrigConv=ADC_EXTERNALTRIG_T1_TRGO;
//ADC group regular conversion trigger from external peripheral: TIM1_TRGO
//ADC触发源选择“TIM1_TRGO”作为AD转换触发源
hadc1.Init.EOCSelection=DISABLE;
//关闭EOC中断
//配置ADC_IER寄存器bit3(EOSIE位),EOSIE=0禁用ADC转换完成产生中断
HAL_ADC_Init(&hadc1);//初始化
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
//采用“单端输入”校准,而不是“差动输入”校准
ADC1_ChanConf.Channel=ADC_CHANNEL_VREFINT;//内参考电压(Vrefint),连接到“ADC1_INP18通道”
ADC1_ChanConf.Rank=ADC_REGULAR_RANK_1;
//设置ADC_SQR1寄存器bit10:6(SQ1[4:0]),SQ1[4:0]=3,即AD通道3的序号为1
//AD转换顺序排列:配置通道3位于“第1个序列”
ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_640CYCLES_5;
//采样时间
//设置ADC_SMPR1寄存器bit8:6(SMP2[2:0]),640.5 ADC clock cycles
//4分频,ADCCLK=PCLK2/4=170/4=42.5MHZ,640.5/42.5=15.07us
ADC1_ChanConf.OffsetNumber=ADC_OFFSET_NONE; //无偏移数量
ADC1_ChanConf.Offset = 0; //偏移量=0
ADC1_ChanConf.SingleDiff=ADC_SINGLE_ENDED; //采用“单端输入”,而不是“差动输入”
HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf);//通道配置
ADC1_ChanConf.Channel=ADC_CHANNEL_TEMPSENSOR_ADC1;//内部温度传感器(VTS),连接到“ADC1_INP16通道”
ADC1_ChanConf.Rank=ADC_REGULAR_RANK_2;
//AD转换顺序排列:配置通道2位于“第2个序列”
//设置ADC_SQR1寄存器bit16:12(SQ2[4:0]),SQ2[4:0]=2,即AD通道2的序号为2
ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_640CYCLES_5;
//采样时间
//设置ADC_SMPR1寄存器bit5:3(SMP1[2:0]),640.5 ADC clock cycles
//4分频,ADCCLK=PCLK2/4=170/4=42.5MHZ,640.5/42.5=15.07us
ADC1_ChanConf.OffsetNumber=ADC_OFFSET_NONE; //无偏移数量
ADC1_ChanConf.Offset = 0; //偏移量=0
ADC1_ChanConf.SingleDiff=ADC_SINGLE_ENDED; //采用“单端输入”,而不是“差动输入”
HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf); //通道配置
hDMA.Instance = DMA1_Channel1; //使用DMA1的通道1
hDMA.Init.Request = DMA_REQUEST_ADC1; //DMA需求来源ADC1
hDMA.Init.Direction = DMA_PERIPH_TO_MEMORY;
//设置DMA_CCRx寄存器bit4(DIR位),令DIR=0,数据传输方向:外设到内存
hDMA.Init.PeriphInc = DMA_PINC_DISABLE;
//设置DMA_CCRx寄存器bit6(PINC位),令PINC=0,外设地址不增加
hDMA.Init.MemInc = DMA_MINC_ENABLE;
//设置DMA_CCRx寄存器bit7(MINC位),令MINC=1,内存地址增加
hDMA.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
//设置DMA_CCRx寄存器bit9:8(PSIZE[1:0]位),令PSIZE[1:0]=10,外设数据宽度32bits
//在“ADC多通道DMA采集”中,不能使用DMA_PDATAALIGN_HALFWORD配置,否则会造成读到的数据是32位的
hDMA.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
//设置DMA_CCRx寄存器bit11:10(MSIZE[1:0]]位),令MSIZE[1:0]=10,内存数据宽度32bits
//在“ADC多通道DMA采集”中,不能使用DMA_MDATAALIGN_HALFWORD,否则会造成读到的数据是32位的
hDMA.Init.Mode = DMA_CIRCULAR;
//DMA_CIRCULAR:“DAM循环搬运数据”
//DMA_NORMAL;若启动一次DMA,则DMA搬运一次数据
//设置DMA_CCRx寄存器bit5(CIRC位),令CIRC=1,这里设置“DAM循环搬运数据”
hDMA.Init.Priority = DMA_PRIORITY_VERY_HIGH ;
//设置DMA_CCRx寄存器bit13:12(PL[1:0]),令PL[1:0]=11b,DMA抢占优先级最高
HAL_DMA_Init(&hDMA);
//初始化DMA
__HAL_LINKDMA(&hadc1,DMA_Handle,hDMA);
//将ADC1与DMA联系起来
//把hadc1结构体的参数赋给DMA
#if DMA_Interrupt ==1
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
//DMA1_Channel1_IRQn抢占优先级为0,响应优先级为0
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);//使能DMA1_Channel1_IRQn中断
LED_cnt=0;
#endif
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC1_RESULT,2);
//启动DMA“循环搬运2个数据”,并保存到ADC1_RESULT[2]中
//通过调用HAL_DMA_Start(),然后调用DMA_SetConfig()配置DMA_CPARx,DMA_CMARx和DMA_CNDTRx
//DMA_CPARx寄存器bit31:0(MA[31:0]),MA[31:0]保存的是外设地址:ADC1_DR
//DMA_CMARx寄存器bit31:0(MA[31:0]),MA[31:0]保存的是内存首地址:ADC1_RESULT[]数组的首地址
//DMA_CNDTRx寄存器bit15:0(NDT[15:0]),NDT[15:0]保存的是有3个ADC通道在等待传输
HAL_ADC_Start(&hadc1);
//因为DMA是循环搬运数据的,所以只需要启动一次AD转换,就可以实现连续执行ADC转换,并搬运数据
//所以这里用HAL_ADC_Start()启动
}
#if DMA_Interrupt ==1
//DMA通道1中断服务程序
void DMA1_Channel1_IRQHandler(void)
{
if(__HAL_DMA_GET_FLAG(&hDMA, DMA_FLAG_TC1)!=RESET)
{
__HAL_DMA_CLEAR_FLAG(&hDMA, DMA_FLAG_TC1);
//清除DMA1通道1的中断标志位
LED_cnt++;
if(LED_cnt>10000)
{
//4分频,ADCCLK=PCLK2/4=170/4=42.5MHZ,640.5/42.5=15.07us
//15.07*2*10000=301.4毫秒
LED_cnt=0;
LED1_Toggle(); //LED1引脚输出电平翻转
}
}
HAL_DMA_IRQHandler(&hDMA);
}
#endif
//打印读到的ADC数据
void DMA1_Read_ADC_Value_Use_SOFTWARE_Triger(void)
{
float f;
printf("ADC1_RESULT[0]=0x%X\r\n",ADC1_RESULT[0]);
printf("ADC1_RESULT[1]=0x%X\r\n",ADC1_RESULT[1]);
f=ADC1_RESULT[0];f=f/4096;f=f*3300;
printf("Vrefint=%0.1fmV\r\n",f);
f=ADC1_RESULT[1];f=f*3300;f=f/4096;
f=CalculateInternalTemperature(f);
printf("Temperature=%0.1f\r\n",f);
}
2、ADC.h程序
#ifndef __ADC_H__
#define __ADC_H__
#include "stm32g4xx_hal.h"
#include "main.h"
//#define DMA_Interrupt 0 //不用测试DMA中断
#define DMA_Interrupt 1 //测试DMA中断
#define TS_CAL1 (float)( *((__IO uint16_t *)0x1FFF75A8) ) //单位为mV
#define TS_CAL2 (float)( *((__IO uint16_t *)0x1FFF75CA) ) //单位为mV
#define TS_TEMP_CAL1 (float)30.0 //温度为30.0度时,其AD值为TS_CAL1
#define TS_TEMP_CAL2 (float)130.0 //温度为130.0度,其AD值为TS_CAL2
//(TS_TEMP_CAL2 - TS_TEMP_CAL1)/(TS_CAL2 - TS_CAL1))表示“度/mV”
//#define CalculateInternalTemperature(TS_DATA) (TS_TEMP_CAL2 - TS_TEMP_CAL1)/(TS_CAL2 - TS_CAL1) * ( (TS_DATA*1) - TS_CAL1) + TS_TEMP_CAL1
//Vref+ = 3.0V
#define CalculateInternalTemperature(TS_DATA) (TS_TEMP_CAL2 - TS_TEMP_CAL1)/(TS_CAL2 - TS_CAL1) * ( (TS_DATA*1.1) - TS_CAL1) + TS_TEMP_CAL1
//当Vref+ = 3.3V,3.3/3.0=1.1,这个1.1系数是这么来的
//if VREF+ = 3.3 V and TS_CAL data are acquired at VREF+= 3.0 V, TS_DATA must be replaced by TS_DATA x (3.3/3.0).
extern void ADC1_Init(void);
extern void DMA1_Read_ADC_Value_Use_SOFTWARE_Triger(void);
#endif /* __ADC_H */
3、串口输出结果
4、LED灯闪烁周期