030 - STM32学习笔记 - ADC(四) 独立模式多通道DMA采集
中断模式和DMA模式进行单通道模拟量采集,这节继续学习独立模式多通道DMA采集,使用到的引脚有之前使用的PC3(电位器),PA4(光敏电阻)、PA6(悬空,可通过杜邦线接3V3或者GND进行测试)。对应引脚通道如下表:
程序仍然在上节的基础上进行修改,先附上关于GPIO宏定义的部分:
//选择ADC1
#define R_ADC ADC1
#define R_ADC_CLK RCC_APB2Periph_ADC1
//电位器 PC3
#define R_ADC_GPIO_PORT1 GPIOC
#define R_ADC_GPIO_PIN1 GPIO_Pin_3
#define R_ADC_GPIO_CLK1 RCC_AHB1Periph_GPIOC
#define R_ADC_CHANNEL_PC3 ADC_Channel_13 //通道13
//光敏电阻 PA4
#define R_ADC_GPIO_PORT2 GPIOA
#define R_ADC_GPIO_PIN2 GPIO_Pin_4
#define R_ADC_GPIO_CLK2 RCC_AHB1Periph_GPIOA
#define R_ADC_CHANNEL_PA4 ADC_Channel_4 //通道4
//悬空 PA6
#define R_ADC_GPIO_PORT3 GPIOA
#define R_ADC_GPIO_PIN3 GPIO_Pin_6
#define R_ADC_GPIO_CLK3 RCC_AHB1Periph_GPIOA
#define R_ADC_CHANNEL_PA6 ADC_Channel_6 //通道6
在使用之前,先来对GPIO进行初始化配置,配置如下:
/** @brief 初始化ADC GPIO引脚
* @parm 无
* @retval 无
*/
static void R_ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(R_ADC_GPIO_CLK1 | R_ADC_GPIO_CLK2 | R_ADC_GPIO_CLK3,ENABLE); //开启ADC外设引脚时钟
//通用配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //配置引脚为模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //配置为无上下拉
//初始化引脚PC3
GPIO_InitStructure.GPIO_Pin = R_ADC_GPIO_PIN1; //配置引脚PC3引脚
GPIO_Init(R_ADC_GPIO_PORT1,&GPIO_InitStructure); //初始化引脚
//初始化引脚PA4
GPIO_InitStructure.GPIO_Pin = R_ADC_GPIO_PIN2; //配置引脚PC4引脚
GPIO_Init(R_ADC_GPIO_PORT2,&GPIO_InitStructure); //初始化引脚c
//初始化引脚PA6
GPIO_InitStructure.GPIO_Pin = R_ADC_GPIO_PIN3; //配置引脚PA6引脚
GPIO_Init(R_ADC_GPIO_PORT3,&GPIO_InitStructure); //初始化引脚
}
对于ADC来说,我们使用的是一个ADC,即ADC1,所以关于ADC Common的初始化部分不需要进行修改。这里代码就不网上贴了,不清楚了可以翻看上一节的内容。
下来再看ADC Init初始化结构体,这里有几处需要修改一下:
1、这次因为要对3个通道进行数据采集,因此需要将通道数改为3,为了方便后期更改,这里将通道数进行宏定义:
#define ADC_NOFChannel 3 //宏定义通道数,位于bsp_adc.h中
ADC_InitStructure.ADC_NbrOfConversion = ADC_NOFChannel; //转换通道为3,采用宏定义
2、通道数超过1个时,需要开启扫描模式,需要将扫描模式使能:
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //多通道下用到扫描模式
3、配置多通道的转换顺序,根据我们实际的需求,需要对多个通道的采集先后顺序进行排序,排序如下:
//配置ADC通道转换顺序为1,第一个转换,采集周期为3个周期
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PC3,1,ADC_SampleTime_3Cycles);
//配置ADC通道转换顺序为2,第二个转换,采集周期为3个周期
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PA4,2,ADC_SampleTime_3Cycles);
//配置ADC通道转换顺序为1,第二个转换,采集周期为3个周期
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PA6,3,ADC_SampleTime_3Cycles);
关于ADC初始化及DMA初始化的完整程序附在下面:
/** @brief 配置ADC引脚工作模式及DMA
* @parm 无
* @retval 无
*/
static void R_ADC_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
//-----------------DMA Init结构体参数初始化-----------------//
RCC_AHB1PeriphClockCmd(R_ADC_DMA_CLK,ENABLE); //开启DMA时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = R_ADC_DR_ADDR; //设置ADC外设基地址,为ADC数据寄存器地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_Value; //存储器地址,地址为内部SRAM变量
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //配置数据传输方向为外设到存储器
DMA_InitStructure.DMA_BufferSize = ADC_NOFCHANNEL; //配置缓冲区大小,大小取决于一次传输的量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设寄存器只有一个,不用递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址也只有一个,不递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据大小为半字(两个字节)
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //与外设相同
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环传输
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //传输优先级为高
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //采用直连,不适用FIFO
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; //FIFO禁止,下面不用配置
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_Channel = R_ADC_DMA_CHANNEL; //选择DMA通道
DMA_Init(R_ADC_DMA_STREAM,&DMA_InitStructure);
DMA_Cmd(R_ADC_DMA_STREAM,ENABLE);
RCC_APB2PeriphClockCmd(R_ADC_CLK,ENABLE); //开启ADC时钟
//-----------------ADC Common结构体参数初始化--------------//
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //设置模式为独立模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; //设置为4分频
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //禁止DMA直接访问模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_10Cycles; //设置采样间隔周期为10个周期
ADC_CommonInit(&ADC_CommonInitStructure);
//-----------------ADC Init结构体参数初始化--------------//
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //设置ADC采样分辨率为12位
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //多通道下用到扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //设置为连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //禁用外部边沿触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //设置为右对齐
ADC_InitStructure.ADC_NbrOfConversion = ADC_NOFCHANNEL; //转换通道为3,采用宏定义
ADC_Init(R_ADC,&ADC_InitStructure);
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PC3,1,ADC_SampleTime_3Cycles); //配置ADC通道转换顺序为1,第一个转换,采集周期为3个周期
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PA4,2,ADC_SampleTime_3Cycles); //配置ADC通道转换顺序为2,第二个转换,采集周期为3个周期
ADC_RegularChannelConfig(R_ADC,R_ADC_CHANNEL_PA6,3,ADC_SampleTime_3Cycles); //配置ADC通道转换顺序为1,第二个转换,采集周期为3个周期
//ADC_ITConfig(R_ADC,ADC_IT_EOC,ENABLE); //ADC转换结束产生中断,在中断服务程序中读取转换数值
ADC_DMARequestAfterLastTransferCmd(R_ADC, ENABLE);
ADC_DMACmd(R_ADC,ENABLE);
ADC_Cmd(R_ADC,ENABLE); //使能ADC
ADC_SoftwareStartConv(R_ADC); //开始ADC转换,由软件触发
}
这里我们在bsp_adc.c中定义一个数组,用来存放三个通道采集到的数据,再在main函数中用extern调用,再定义用来存放转换后的数组。main中程序如下:
extern __IO uint16_t ADC_Value[ADC_NOFCHANNEL];
float ADC_Vol[ADC_NOFCHANNEL] = {0};
int main(void)
{
DEBUG_USART1_Config();
R_ADC_Init();
SysTick_Init();
printf("\r\n---------------ADC实验(独立模式多通道DMA模式)----------------\r\n");
while(1)
{
Delay_ms(1000);
for(int i = 0;i <ADC_NOFCHANNEL;i++)
{
if(i == 0)
printf("-------------------电位器通道-------------------");
else if(i == 1)
printf("-------------------光感通道-------------------");
else
printf("-------------------悬空通道-------------------");
printf("\r\n ADC数据(未转换) = 0x%04X \r\n",ADC_Value[i]);
ADC_Vol[i] =(float)(ADC_Value[i]*3.3/4096); // 读取转换的 AD 值
printf("\r\n ADC数据(已转换) = %.2f V \r\n",ADC_Vol[i]);
}
}
}
实验效果如下:
1、电位器通道可以通过调整电位器阻值查看采集结果;
2、光感通道可以通过遮挡光敏电阻或者手机闪光灯照射查看采集结果(用手遮了一下光敏模块);
3、悬空通道可以接3.3V或者GND通道来查看采集结果(这里接到GND上的)。