STM32的DMA可以完成外设到内存,内存到外设的直接数据传输。使用DMA传输即可让数据绕开CPU,数据不需要进出SRAM。在DMA传输过程中,CPU可以进行其他操作,DMA与CPU分时使用系统总线。
于是我就想到,DMA能不能完成外设到外设的直接数据传输呢?因此我尝试着做了本次实例。
ADC单通道连续采集数据,通过DMA传输给串口发送给上位机。DMA控制器使用系统总线,直接将ADC数据寄存器的数据传输给串口发送数据寄存器,对串口发送数据寄存器的写操作将触发串口传输,从而将数据发送给上位机。
为了让ADC1转换与串口发送同步,将DMA传输模式设置为常规(一次传输),即完成指定数量数据的传输后,DMA将自动关闭,而不再响应DMA请求(当传输模式为循环模式时,DMA配置完成后,当有DMA请求信号时,DMA便开始工作,不停地通过系统总线传输数据),打开ADC的转换完成中断,并且在中断函数中重新开启DMA。这样。ADC每完成一个数据的转换,便触发一次DMA传输,传输完成后DMA自动关闭,下一次ADC转换完成时,重新开启DMA,如此反复便实现了ADC1转换与串口发送的同步。而在整个过程中,CPU可以做其他工作。
下面为核心代码:
ADC1 GPIO配置(ADC1通道8对应PB0的复用功能):
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB外设时钟 (STM32在对外设寄存器操作之前需要开启相应外设时钟) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //设置为模拟输入 GPIO_Init(GPIOB, &GPIO_InitStructure); //模拟输入
ADC1模式配置:
ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1外设时钟 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模 ADC_InitStructure.ADC_ScanConvMode = DISABLE ; //禁止扫描模式,扫描模式用于多通道采集 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换模式,即不停地进行ADC转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目1 ADC_Init(ADC1, &ADC_InitStructure); ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); //打开ADC1转换完成中断 RCC_ADCCLKConfig(RCC_PCLK2_Div8); //配置ADC时钟,为PCLK2的8分频率 ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_55Cycles5); //配置ADC1通道8,通道转换顺序为1, 转换时间为55.5个时钟周期 ADC_DMACmd(ADC1, ENABLE); //打开ADC1的DMA请求,即ADC转换完成后将触发DMA开始传输 ADC_Cmd(ADC1, ENABLE); //打开ADC1 ADC_ResetCalibration(ADC1); //复位校准寄存器 while(ADC_GetResetCalibrationStatus(ADC1)); //等待校准寄存器复位完成 ADC_StartCalibration(ADC1); //ADC校准 while(ADC_GetCalibrationStatus(ADC1)); //等待ADC校准完成 ADC_SoftwareStartConvCmd(ADC1,ENABLE); //打开ADC软件触发
DMA1配置:
DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1外设时钟 DMA_DeInit(DMA1_Channel1); //默认设置 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; //Peripheral指向 ADC1数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&USART1->DR; //Memory指向 USART1发送数据寄存器 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向由 ADC——>USART1 DMA_InitStructure.DMA_BufferSize = 2; //发送两个数据 (由于ADC精度为12位,数据寄存器为16位,分两个字节发送) DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;// Peripheral按字节自增1指向ADC1数据寄存器 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // Memory指向串口发送数据寄存器不变(串口数据宽度为8位) DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度1字节 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度1字节 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //一次传输 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止M2M DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); //开启DMA1通道1,ADC1的DMA通道为DMA1通道1
附DMA请求映射:
串口USART1 GPIO配置(USART1 Tx对应PA9的复用功能,USART1 Rx对应PA10的复用功能):
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //配置为复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //配置悬浮输入 GPIO_Init(GPIOA, &GPIO_InitStructure);
串口USART1 模式配置:
USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启USART1外设时钟 USART_InitStructure.USART_BaudRate = 1228800; //设置波特率为1228800 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //设置数据字长为8bit USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1bit USART_InitStructure.USART_Parity = USART_Parity_No ; //无奇偶校验 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //设置为发送和接收模式 USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); //开启USART1
NVIC中断配置:
NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断优先级组1(1位抢占优先级,3位从优先级) NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn; //ADC1,ADC2中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//先占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
ADC1转换完成中断服务程序(在stm32f10x_it.c中编辑):
void ADC1_2_IRQHandler(void) { if(ADC_GetITStatus(ADC1,ADC_IT_EOC)) { /*重启DMA*/ DMA_Cmd(DMA1_Channel1,DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1,2); //设置还要转换的数据个数 DMA_Cmd(DMA1_Channel1,ENABLE); ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); //清除转换完成标志 } }
在stm32f10x_conf.h(stm32f10x.h头文件中包含stm32f10x_conf.h)中需要取消的注释:
#include "stm32f10x_adc.h" //配置ADC所需的头文件 //#include "stm32f10x_bkp.h" //#include "stm32f10x_can.h" //#include "stm32f10x_cec.h" //#include "stm32f10x_crc.h" //#include "stm32f10x_dac.h" //#include "stm32f10x_dbgmcu.h" #include "stm32f10x_dma.h" //配置DMA所需的头文件 //#include "stm32f10x_exti.h" //#include "stm32f10x_flash.h" //#include "stm32f10x_fsmc.h" #include "stm32f10x_gpio.h" //配置GPIO所需的头文件 //#include "stm32f10x_i2c.h" //#include "stm32f10x_iwdg.h" //#include "stm32f10x_pwr.h" #include "stm32f10x_rcc.h" //配置时钟所需的头文件 //#include "stm32f10x_rtc.h" //#include "stm32f10x_sdio.h" //#include "stm32f10x_spi.h" //#include "stm32f10x_tim.h" #include "stm32f10x_usart.h" //配置USART所需的头文件 //#include "stm32f10x_wwdg.h" #include "misc.h" //配置NVIC所需的头文件
需要添加进工程库文件有:
测试结果:
当AD模拟输入Vdd时,上位机串口接收到如下数据:
当AD模拟输入Vss时,上位机串口接收到如下数据:
后来发现数据有问题,DMA将数据传输给USART1->DR后,串口需要时间将DR并行转移给发送移位寄存器,在这期间USART1->DR不能改变。即在下次写USART1->之前需要while (!(USART1->SR & USART_FLAG_TXE));检测转移完成。发现利用DMA将数据传输至外设时候,外设必须为高速外设,或者有缓冲区才行。