本章节将通过使用采集红外测距模块测量阻挡物的距离与大家一起学习配置ADC和DMA模块。
ADC介绍与配置
ADC,Analog-to-Digital Converter的缩写,指模数转换器,是一种将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。ADC可以实现这个功能,不同的应用对ADC的精度有着不同的要求。
在MM32SPIN27系列芯片中,内嵌 2 个 12 位的模拟/数字转换器 (ADC),每个 ADC 可用多达 16 个外部通道,可以满足大部分产品需求。

MM32SPIN27中的12 位 ADC 是逐次逼近式的模拟-数字转换器 (SAR A/D 转换器),拥有高达 1Msps 转换速率,可以软件配置通道采样时间和分辨率。每个通道拥有独立的数据寄存器和一个共用的数据寄存器。
MM32SPIN27的ADC支持多种工作模式,包括单次转换模式、单周期扫描模式和连续扫描模式。扫描模式中可以选择通道扫描的顺序,选择采样顺序从高到低或从低到高。
A/D转换的启动方式有软件设定、外部引脚触发以及各个定时器启动。在触发信号产生后,ADC最多可以延时512个PLCK2时钟周期再开始采样。
在比较模式下提供了上限和下限两个比较寄存器。可通过软件设定 CMPCH 位选择监控通道。
ADC 的输入时钟由 PCLK2 经分频产生,通过正确配置ADC_ADCFG寄存器的ADCPRE位和ADCPRE位,可以自由地配置2分频到17分频。注意,输入时钟不得超过15MHz。
下面是一个简单的ADC配置函数:
void ADCInit(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //12位分辨率
ADC_InitStructure.ADC_PRESCARE =ADC_PCLK2_PRESCARE_8;//96M时钟8分频
ADC_InitStructure.ADC_Mode =ADC_Mode_Continuous_Scan;//连续扫描模式
ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right; //数据对齐方式,右对齐
ADC_InitStructure.ADC_ExternalTrigConv =ADC_ExternalTrigConv_T1_CC1;
//选择触发源,需要ADC_ExternalTrigConvCmd函数开启外部触发模式
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1,ADC_Channel_DisableAll, 0, ADC_SampleTime_13_5Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2,0, ADC_SampleTime_239_5Cycles);
//仅开启ADC1的通道2,采样时间为239.5个周期
ADC_DMACmd(ADC1,ENABLE); //启用DMA传输功能
ADC_Cmd(ADC1, ENABLE);
}这样就把PA2配置成了ADC1通道2的输入,每次转换完成后,数据将会保存在独立寄存器ADC1->ADDR2和共用寄存器ADC1->ADDATA中,同时ADC将会发出DMA传输请求。
下面我们来配置DMA传输。
DMA介绍与配置
DMA(DirectMemory Access,直接内存存取)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 任何干预,通过 DMA 数据可以快速地移动。这就节省了 CPU 的资源来做其他操作。
MM32SPIN27的DMA 控制器有 5个通道,每个通道都直接连接专用的硬件 DMA 请求(具体可以在用户手册中有描述),每个通道都同样支持软件触发。每个通道都有 3个事件标志:DMA 半传输, DMA 传输完成和 DMA 传输出错。
在5个通道的请求之间的优先权可以通过软件编程设置 (共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定 (请求 0 优先于请求 1,依此类推)。

下面我们来配置DMA将ADC采集的数据传输到数组中:
void DMAInit(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&(ADC1->ADDATA);//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&ADCValue; //内存地址
DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralSRC; //方向
DMA_InitStructure.DMA_BufferSize = 20; //传输数量
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);
//NVIC中断设定
DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);
NVIC_InitStruct.NVIC_IRQChannel =DMA1_Channel1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void DMA1_Channel1_IRQHandler(void)
{
ADC_SoftwareStartConvCmd(ADC1, DISABLE);
//DMA传输20个数据完成,标志置1
DMA_ClearITPendingBit(DMA1_IT_TC1);
ADCflag = 1;
}配置完成后,DMA控制器收到ADC产生的请求后,将会传输一次数据到ADCValue数组中,并执行一次 DMA_CNDTRx 寄存器的递减操作和内存地址自动增量操作,直到传输完成20个数据,关闭ADC转换,并将标志ADCflag置1。
接下来就需要根据不同的应用来处理数据了。
简单应用:2Y0A21YK0F红外测距模块
2Y0A21YK0F是一个有效距离为10cm~80cm的红外测距模块,由PSD(位置敏感探测器)、IRED(红外发射二极管)和信号处理电路组成。采用三角测距法,目标反射率、环境温度和工作时间的变化对距离检测影响较小,在V0输出与检测距离相对应的电压。


根据图4中给出的关系,将ADC中的数据进行滤波和计算,就可以得到所需的距离信息了。
程序配置:
void ADCFilter(void)
{
static u16 cntFilter;
static u32 lADCFilterValue = 0;
for(cntFilter = 0; cntFilter < 20;cntFilter++)
{
lADCFilterValue += ADCValue[cntFilter];
}
cntFilter = 0;
ADCFilterflag = 1;
ADCFilterValue = (lADCFilterValue /20 * 30 +ADCFilterValue * 70) / 100;
lADCFilterValue = 0;
}
void Get_ADCVolatge(void)
{
ADCVolatge = ((float)ADCFilterValue / 4095) * 5;//使用5V电源
}
int main(void)
{
char printBuf[64];
float Distance;
DMAInit();
ADCInit(ADC_Channel_2);
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件使能AD转换
Uart_ConfigInit(9600); //UART初始化
while(1){
if(ADCflag){
ADCflag = 0;
ADCFilter(); //滤波
if(ADCFilterflag){
ADCFilterflag = 0;
Get_ADCVolatge(); //寄存器度数转为电压
Distance = 0.3/ADCVolatge;//由电压计算距离,需要根据具体环境调整
UartSendGroup((u8*)printBuf,sprintf(printBuf,"Distance = %fm\r\n",Distance));
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件使能AD转换
}
}
}
}程序运行结果:

连接实验板与红外测距模块,编译下载运行程序,按照模块说明,使用一本白色封面的书本,接近再远离模块,通过不断变化距离,我们可以从UART串口看到程序将计算得到的结果(图5)。在这个过程中,ADC将模拟信号转换成数字信号,通过DMA传输到内存中,经过MCU的处理,最终得到了一个相对准确的距离数据。
来源: 灵动MM32MCU