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將數據傳輸至外設時候,外設必須為高速外設,或者有緩沖區才行。