模数转换器(Analog To Digital Converter)简称ADC(也可以写成A/D),是指将连续变化的模拟信号转换为离散的数字信号的器件。
直接存储器存取技术(Direct Memory Access)简称DMA。DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省CPU的资源可以做其他操作。DMA传输的本质是地址到地址的操作,可以把DMA理解为CPU的“秘书”。
ADC的参考电压是ADC的一个重要指标。STM32芯片只有100及以上引脚的型号才具备外部参考电压引脚VREF+和VREF-;64及小于64脚的型号,参考电压引脚在芯片内部默认连接到ADC的电源引脚VDDA和VSSA。
图 1 ADC电源和基准电源电路 |
由于ADC规则组多通道转换时,只能读取到最后一个通道的数据,因此ADC的多通道转换天生适合DMA模式,当每个通达转换完毕后,发送DMA请求,通过DMA直接传输到设定的内存缓存区中,从而解决了ADC多通道转换数据被覆盖的问题,同时CPU不需要频繁读取ADC的数据,大幅提高执行效率。
通过查阅STM32F1的参考手册,我们可以知道ADC1的DMA传输通道映射到了DMA1的Channel1。ADC1可由TIM3_TRGO事件触发转换。本文通过TIM3的更新事件触发ADC规则组4路通道转换。实际项目中,我们使用这4路ADC读取四个霍尔角度传感器的输出电压进而计算机械臂的角度,采用外部触发ADC规则组转换可以避免因ADC循环转换模式造成的资源浪费。接下来上代码。
void Timer_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period=1000-1;
TIM_TimeBaseStructure.TIM_Prescaler=72-1;
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_Cmd(TIM3, ENABLE);
TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update);//设置更新事件作为触发输出
}
void ADC_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72M/6=12M
//init GPIO
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//init DMA
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr=(unsigned int)&(ADC1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr=(unsigned int)&ADCBuffer[0];
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize=4;
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_M2M=DMA_M2M_Disable;
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);
//init ADC1
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode=ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO;//设置外部信号TIM3_TRGO出发
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel=4;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_6,1,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_7,2,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_8,3,ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_9,4,ADC_SampleTime_239Cycles5);
ADC_DMACmd(ADC1,ENABLE);
ADC_Cmd(ADC1,ENABLE);
//calibrate ADC1
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
//enable ADC1 external trigger
ADC_ExternalTrigConvCmd(ADC1,ENABLE);
}
在main函数中调用初始化函,TIM3、ADC、DMA配置好后,TIM3将每1ms触发一次ADC规则组通道转换,转换的结果将有DMA转移至预先定义的缓冲区ADCBuffer,只要及时读取ADCBuffer中的数据便可得到各ADC通道转换的数值。如果读取数据不及时,容易出现读取数据被覆盖的现象,为此可以将ADCBuffer及DMA通道缓存(DMA_BufferSize)设置成被测数据的2倍大小,当第一次规则组4通道依次转换完毕后,DMA将数据依次传输到缓冲区的前半区,同时设置DMA传输过半标志位,此时可以读取当前4通道转换的数据。同时规则组继续转换,当第二次4个通道依次转换完毕时,转换数据被存储到缓冲区的后半区,同时设置DMA传输完成标志位,此时可以读取第二次转换的数据。