天天看点

stm32的DMA+ADC多通道数据采集1前言2常用的几个使能函数介绍3几个关键点4代码编写5 结果

1前言

硬件:stm32f103rct6,输入大容量产品。

软件:keil MDK5.0

固件库:stm32f1标准外设库。

调试软件:友善串口助手

stm32有多达16个通道,常用的采样方法有两种,一是分时采集每个通道的数据,用查询或者中断的方式采集完一个通道的数据,将通道设置为下一个,依次采集,这种方法思路简单,但是效率不高,适合初学者。二是利用DMA功能采集多个规则通道的数据,注意是规则通道,因为规则通道是多通道共用一个数据寄存器ADCx->DR存放结果,而注入通道有多个寄存器,注入采样以后再讲。下面进入正题。

2常用的几个使能函数介绍

我们用到adc和dma外设,首先要对其使能,下面是几个重要的使能函数,因为有些使能函数使能一次即可,有些使能函数转换完后自动清零,要重新使能一次,这里是个大坑,如果你不再次使能,采样根本不行。

ADC_Cmd(ADC1, ENABLE),设置ADC_CR2的ADON位,让adc上电,这个函数使能一次即可。

ADC_DMACmd(ADC1, ENABLE) 设置ADC_CR2的DMA位,使能ADC的DMA请求,使能一次即可。

DMA_Cmd(DMA1_Channel1, DISABLE) 设置DMA1对应的ADC通道,通道1使能,使能一次即可,但需要注意的是这个函数与DMA_SetCurrDataCounter(DMA1_Channel1, 2) 相关,要设置dma的传输量,这个函数的第二个参数必须为DISABLE。

ADC_SoftwareStartConvCmd(ADC1, ENABLE) 使能ADC_CR1的SWSTART位,当adc完成一次转换后,这个位自动变为0,要重新打开adc,切记,这函数每完成一次adc采样后都要再调用一次,表示用软件触发的方式开始adc采样。

3几个关键点

1 因为是多通道,要开启adc的扫描模式(连续模式是否开始视自己的需求而定)。

2 ADC有自己的分频控制器,不要忘记打开。可以调用RCC_ADCCLKConfig函数。

3 因为是多通道,要设置每个通道的采样先后顺序,调用ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5)函数。

4 因为ADC的DR是16位的,所以DMA的外设数据宽度为halfword。

5 如果开启了DMA的中断一定不要忘记中断使能。

6 如果DMA不采用回环模式,每次传送完数据后,DMA_CNDTRx寄存器清零,要重装这个寄存器的值,在重装前确保DMAde通道是关闭的,否则重装无效。

4代码编写

4.1 初始化ADC,并设置采样通道的次序。

void ADC1_init(void)

{

ADC_InitTypeDef ADC_InitStruct;

GPIO_InitTypeDef io;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//这里一定不要忘记,这里有单独的ADC分频控制器

io.GPIO_Mode = GPIO_Mode_AIN;//PA0 PA1分别作为ADC1的通道0 通道1
io.GPIO_Pin = (GPIO_Pin_0|GPIO_Pin_1);
GPIO_Init(GPIOA, &io);	

ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_InitStruct.ADC_ScanConvMode = ENABLE;//开启扫描模式
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//close continuous mode
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_NbrOfChannel = 2;//两个通道

ADC_Init(ADC1, &ADC_InitStruct);


ADC_Cmd(ADC1, ENABLE);//adc使能
ADC_DMACmd(ADC1, ENABLE);//dma请求使能

ADC_ResetCalibration(ADC1);//复位校准
while(ADC_GetResetCalibrationStatus(ADC1));//等待复位转换结束
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//等待开始校准结束

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);//设置采样通道的次序,有几个通道调用几次这个函数
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5);
}
           

通道的总数量用ADC_InitStruct.ADC_NbrOfChannel = X设置,同时下面要调用几次ADC_RegularChannelConfig()函数,设置通道的采样次序。

4.2 DMA初始化

void ADC1_DMA_config(DMA_Channel_TypeDef* DMAy_Channelx,u32 memAddr,u32 periphAddr,u8 bufSize)

{

DMA_InitTypeDef DMA_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能

//DMA_DeInit(DMAy_Channelx);

/* 初始化DMA */
DMA_InitStructure.DMA_PeripheralBaseAddr = periphAddr;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = memAddr;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设是源地址
DMA_InitStructure.DMA_BufferSize = bufSize;//一次DMA传输的字节量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不增加
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址增加
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//adc的DR是16位的
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//按half word
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,传输完一次后数据量不自动重载
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //
DMA_Init(DMAy_Channelx, &DMA_InitStructure);//DMA初始化

#if(ADC1_DMA_INT_EN)
{
	NVIC_InitTypeDef NVIC_InitStruct;
	
	NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel1_IRQn; //ADC1挂在DMA的通道1上
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	DMA_ITConfig(DMAy_Channelx,DMA_IT_TC,ENABLE);//DMA chaneel interrupt EN
}
#endif
}	
           

以上代码完成了DMA结构体配置,宏ADC1_DMA_INT_EN为1,表示使用DMA中断,下面配置了NVIC结构体,并开启DMA传输完成中断。因为ADC的DR是16位的所以设置外设数据宽度为halfword,传输数据量为2,

这里一定要注意,传输的数据量表示传送的次数,而传送次数由源地址数据宽度决定,因为有两个通道所以配置传输数据量为2,表示传送两次,实际传送了4个字节。这里一定要注意,如果配置为4则进不了DMA中断。这是新手很容易犯的错误。

4.3 设置DMA传送数量并用软件触发adc。

void app_ADC1DMA_enable(void)

{

DMA_Cmd(DMA1_Channel1, DISABLE);//DMA channel enable

DMA_SetCurrDataCounter(DMA1_Channel1, 2);

DMA_Cmd(DMA1_Channel1, ENABLE);

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发

}

因为没有开始DMA回环模式,每次传送完后,DMA_CNDTRx寄存器清零,这时不管DMA使能与否DMA都会停止,因此要重新设置传送的数据量,再此之前一定要先关闭DMA通道,切记。

4.4中断函数

void DMA1_Channel1_IRQHandler(void)

{

if(DMA_GetFlagStatus(DMA1_FLAG_TC1))

{

dma1FinishFlag = 1;

DMA_ClearFlag(DMA1_FLAG_TC1);

}

}

没啥好说的,在中断里置位在main里面查询,查询到置1就处理数据。

4.5 主函数。

nt main()

{

extern u8 dma1FinishFlag;

ST_USART1_RCV_BUF rxbuf={{0},0,0,0};

u32 temp = 0;

u8 i;

//float fval;

SysTick_Init(72);
NVIC_PriorityGroupConfig(2);
LED_Init();
usart1_config(9600);
ADC1_init();
ADC1_DMA_config(DMA1_Channel1,(u32)rxbuf.buf,(u32)&ADC1->DR,2);
app_ADC1DMA_enable();
while(1)
{
	temp = 0;
	if(dma1FinishFlag)
	{
		dma1FinishFlag = 0;//清空标志位
		printf("voltage1 is %f\n",(rxbuf.buf[1]<<8|rxbuf.buf[0])/4096.0*3.3);
		printf("voltage2 is %f\n",(rxbuf.buf[3]<<8|rxbuf.buf[2])/4096.0*3.3);
		app_ADC1DMA_enable();//开始下一次传输
		delay_ms(1000);
		PBout(11)=~PBout(11);
	}
}
}
           

要注意ADC1_DMA_config传参数时,外设地址是(u32)&ADC1->DR,不要忘记&。

5 结果

编译下载程序,在串口助手输出结果如下所示:

stm32的DMA+ADC多通道数据采集1前言2常用的几个使能函数介绍3几个关键点4代码编写5 结果

本例程用了两个通道,上图输出了俩通道的电压,一个通道接0v,一个人通道接1v。

继续阅读