硬件:stm32f103rct6,输入大容量产品。
软件:keil MDK5.0
固件库:stm32f1标准外设库。
调试软件:友善串口助手
stm32有多达16个通道,常用的采样方法有两种,一是分时采集每个通道的数据,用查询或者中断的方式采集完一个通道的数据,将通道设置为下一个,依次采集,这种方法思路简单,但是效率不高,适合初学者。二是利用DMA功能采集多个规则通道的数据,注意是规则通道,因为规则通道是多通道共用一个数据寄存器ADCx->DR存放结果,而注入通道有多个寄存器,注入采样以后再讲。下面进入正题。
我们用到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采样。
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通道是关闭的,否则重装无效。
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()函数,设置通道的采样次序。
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中断。这是新手很容易犯的错误。
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通道,切记。
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC1))
{
dma1FinishFlag = 1;
DMA_ClearFlag(DMA1_FLAG_TC1);
}
}
没啥好说的,在中断里置位在main里面查询,查询到置1就处理数据。
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,不要忘记&。
编译下载程序,在串口助手输出结果如下所示:
本例程用了两个通道,上图输出了俩通道的电压,一个通道接0v,一个人通道接1v。