DMA簡介
直接存儲器存取(DMA)用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳輸。無須CPU干預,數據可以通過DMA快速地移動,這就節省了CPU的資源來做其他操作。換而言之就是當外設有數據發送給mcu,此時可以使用DMA接收到用戶定義空間(不占用cpu),接收完成在產生中斷發給mcu(才占用CPU)反正一樣。
當CPU和DMA同時訪問相同的目標(RAM或外設)時,DMA請求會暫停CPU訪問系統總線達若干個周期,總線仲裁器執行循環調度,以保證CPU至少可以得到一半的系統總線(存儲器或外設)帶寬。
兩個DMA控制器有12個通道(DMA1有7個通道,DMA2有5個通道),每個通道專門用來管理來自于一個或多個外設對寄存器訪問。
DMA 主要特性
● 12個獨立的可配置的通道(請求):DMA1有7個通道,DMA2有5個通道
● 每個通道都直接連接專用的硬件DMA請求,每個通道都同樣支持軟件觸發。這些功能通過
軟件來配置。
● 在同一個DMA模塊上,多個請求間的優先權可以通過軟件編程設置(共有四級:很高、高、
中等和低),優先權設置相等時由硬件決定(請求0優先于請求1,依此類推) 。
● 獨立數據源和目標數據區的傳輸寬度(字節、半字、全字),模擬打包和拆包的過程。源和目標地址必須按數據傳輸寬度對齊。
● 支持循環的緩沖器管理
● 每個通道都有3個事件標志(DMA半傳輸、DMA傳輸完成和DMA傳輸出錯),這3個事件標志
邏輯或成為一個單獨的中斷請求。
● 存儲器和存儲器間的傳輸
● 外設和存儲器、存儲器和外設之間的傳輸
● 閃存、SRAM、外設的SRAM、APB1、APB2和AHB外設均可作為訪問的源和目標。
● 可編程的數據傳輸數目:最大為65535
處理的方式
每次DMA傳送由3個操作組成:
● 從外設數據寄存器或者從當前外設/存儲器地址寄存器指示的存儲器地址取數據,第一次傳
輸時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設基地址或存儲器單元。
● 存數據到外設數據寄存器或者當前外設/存儲器地址寄存器指示的存儲器地址,第一次傳輸
時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設基地址或存儲器單元。
● 執行一次DMA_CNDTRx寄存器的遞減操作,該寄存器包含未完成的操作數目。
DMA 的配置步驟
1. 使能DMA時鐘
(是必不可少的)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAx,ENABLE); //使能 DMA 時鐘
2. 初始化DMA通道參數
(具體某個通道,根據上面的圖可以知道)
DMA 通道配置參數種類比較繁多,在"stm32f10x_dma.h""stm32f10x_dma.c"都封裝好了的;說白了就是配置DMA_InitTypeDef結構體的參數。
//初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);
typedef struct//結構體
{
uint32_t DMA_PeripheralBaseAddr;//設置DMA傳輸的外設基地址
/*要進行串口DMA傳輸,那么外設基地址為串口接受收發送數據存儲器USART1->DR的地址*/
uint32_t DMA_MemoryBaseAddr;//定義 DMA 內存基地址(我們定義存放DMA傳輸數據的內存地址)
uint32_t DMA_DIR;//作為數據傳輸的目的地還是來源(傳輸方向)
/* DMA_DIR_PeripheralDST 外設作為數據傳輸的目的地
DMA_DIR_PeripheralSRC 外設作為數據傳輸的來源*/
uint32_t DMA_BufferSize;//指定 DMA 通道的 DMA 緩存的大小,單位為數據單位
uint32_t DMA_PeripheralInc;//設定外設地址寄存器遞增與否
/*DMA_PeripheralInc_Enable 外設地址寄存器遞增
DMA_PeripheralInc_Disable 外設地址寄存器不變*/
uint32_t DMA_MemoryInc;//設定內存地址寄存器遞增與否
/*DMA_PeripheralInc_Enable 內存地址寄存器遞增
DMA_PeripheralInc_Disable 內存地址寄存器不變*/
uint32_t DMA_PeripheralDataSize;// 設定了外設數據寬度
/*DMA_PeripheralDataSize_Byte 數據寬度為 8 位
DMA_PeripheralDataSize_HalfWord 數據寬度為 16 位
DMA_PeripheralDataSize_Word 數據寬度為 32 位*/
uint32_t DMA_MemoryDataSize;//定了外設數據寬度
/*DMA_MemoryDataSize_Byte 數據寬度為 8 位
DMA_MemoryDataSize_HalfWord 數據寬度為 16 位
DMA_MemoryDataSize_Word 數據寬度為 32 位*/
uint32_t DMA_Mode;//工作模式
/*DMA_Mode_Circular 工作在循環緩存模式
DMA_Mode_Normal 工作在正常緩存模式*/
uint32_t DMA_Priority;//通道 x 的軟件優先級
/*DMA_Priority_VeryHigh DMA 通道 x 擁有非常高優先級
DMA_Priority_High DMA 通道 x 擁有高優先級
DMA_Priority_Medium DMA 通道 x 擁有中優先級
DMA_Priority_Low DMA 通道 x 擁有低優先級*/
uint32_t DMA_M2M;//使能 DMA 通道的內存到內存傳輸
/*DMA_M2M_Enable DMA 通道 x 設置為內存到內存傳輸
DMA_M2M_Disable DMA 通道 x 沒有設置為內存到內存傳輸*/
}DMA_InitTypeDef;
//后面完整程序有更清楚配置說明
3 )使能串口DMA發送或接收
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//發送
4 ) 使能DMA 通道x,啟動傳輸。
DMA_Cmd(DMA_CHx, ENABLE);
注:配置好了1 2 3步驟,在使能MDA通道就可以成功發送或接收一次數據。反而言之決定“DMA_Cmd()”函數的位置就能決定發送數據的時間。
5)查詢 DMA 傳輸狀態
就比如在中斷的時候,我們要判斷相對應的標志位。
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
/*
DMA_FLAG_GL1 通道 1 全局標志位
DMA_FLAG_TC1 通道 1 傳輸完成標志位
DMA_FLAG_HT1 通道 1 傳輸過半標志位
DMA_FLAG_TE1 通道 1 傳輸錯誤標志位
DMA_FLAG_GL2 通道 2 全局標志位
DMA_FLAG_TC2 通道 2 傳輸完成標志位
DMA_FLAG_HT2 通道 2 傳輸過半標志位
DMA_FLAG_TE2 通道 2 傳輸錯誤標志位
DMA_FLAG_GL3 通道 3 全局標志位
DMA_FLAG_TC3 通道 3 傳輸完成標志位
DMA_FLAG_HT3 通道 3 傳輸過半標志位
DMA_FLAG_TE3 通道 3 傳輸錯誤標志位
DMA_FLAG_GL4 通道 4 全局標志位
DMA_FLAG_TC4 通道 4 傳輸完成標志位
DMA_FLAG_HT4 通道 4 傳輸過半標志位
DMA_FLAG_TE4 通道 4 傳輸錯誤標志位
DMA_FLAG_GL5 通道 5 全局標志位
DMA_FLAG_TC5 通道 5 傳輸完成標志位
DMA_FLAG_HT5 通道 5 傳輸過半標志位
DMA_FLAG_TE5 通道 5 傳輸錯誤標志位
DMA_FLAG_GL6 通道 6 全局標志位
DMA_FLAG_TC6 通道 6 傳輸完成標志位
DMA_FLAG_HT6 通道 6 傳輸過半標志位
DMA_FLAG_TE6 通道 6 傳輸錯誤標志位
DMA_FLAG_GL7 通道 7 全局標志位
DMA_FLAG_TC7 通道 7 傳輸完成標志位
DMA_FLAG_HT7 通道 7 傳輸過半標志位
DMA_FLAG_TE7 通道 7 傳輸錯誤標志位
*/
獲取當前剩余數據量大小的函數:(有時候還是可以用到)
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
DMA實現USART1收發送數據程序
通過查資料,多次調試,最總完成了這次任務。
使用USART1中斷+MDA實現接收數據,PE4接的按鍵,按下一次通過MDA發送數據一次到USART1。
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,(uint8_t)ch);
return ch;
}
#define UART_RX_LEN 128
/*串口發送DMA緩存*/
uint8_t SendBuff[UART_RX_LEN]={"zxcvbnm,asdfghjkl"};
/*串口接收DMA緩存*/
uint8_t Uart_Rx[UART_RX_LEN] = {0};
uint8_t Data_Receive_Usart=0;
void delay_ms(u16 time)
{
u16 i = 0;
while(time--)
{
i = 12000;
while(i--);
}
}
//串口初始化
void Usart_Init(void)
{
GPIO_InitTypeDef GPIO_ITDef1;
GPIO_InitTypeDef GPIO_ITDef;
USART_InitTypeDef USART_ITDef;
//掛載時鐘(復用PA) 串口時鐘使能,GPIO 時鐘使能,復用時鐘使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);
//PA9 TXD初始化
GPIO_ITDef.GPIO_Pin = GPIO_Pin_9;//PA9 TXD
GPIO_ITDef.GPIO_Mode = GPIO_Mode_AF_PP;////復用推挽輸出
GPIO_ITDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_ITDef);
//PA10 TXD初始化
GPIO_ITDef1.GPIO_Pin = GPIO_Pin_10;//PA10 RXD
GPIO_ITDef1.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
GPIO_Init(GPIOA,&GPIO_ITDef1);
//USART初始化
USART_ITDef.USART_BaudRate = 115200;//波特率
USART_ITDef.USART_WordLength = USART_WordLength_8b;//發送數據長度
USART_ITDef.USART_StopBits = USART_StopBits_1; //一個停止位
USART_ITDef.USART_Parity = USART_Parity_No; //無奇偶校驗位
USART_ITDef.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
USART_ITDef.USART_Mode = USART_Mode_Tx| USART_Mode_Rx ;//發送模式
USART_Init(USART1,&USART_ITDef);
/*中斷配置*/
USART_ITConfig(USART1,USART_IT_TC,DISABLE);
USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
//配置UART1中斷
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //通道設置為串口1中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //中斷占先等級0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //中斷響應優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //打開中斷
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);//使能串口
}
void MDA_USART1_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 時鐘
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_DeInit(DMA1_Channel4); //將 DMA 的通道 1 寄存器重設為缺省值
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); //DMA外設基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; //DMA內存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //數據傳輸方向,從內存讀取發送到外設
DMA_InitStructure.DMA_BufferSize = UART_RX_LEN; //DMA通道的DMA緩存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數據寬度為8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數據寬度為8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x擁有中優先級
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設置為內存到內存傳輸
DMA_Init(DMA1_Channel4, &DMA_InitStructure); //根據DMA_InitStruct中指定的參數初始化DMA的通道USART1_Tx_DMA_Channel所標識的寄存器
/*DMA1通道5配置接收*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
//串口的發送和接收stm32都存在同一寄存器的;51和cc2530是發送和接收用單獨的寄存器存數據;我們要其他內設使用DMA,就得清楚當前設備在stm32存數據的寄存器。(stm32數據手冊都有介紹)
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart_Rx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = UART_RX_LEN;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
//主要通過按鍵來發送數據,所以這里沒有使能通道4(一旦使能就將發送一次數據)
/*使能接收通道5*/
DMA_Cmd(DMA1_Channel5,ENABLE);
//采用DMA方式發送
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //采用DMA方式接收
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
//啟動串口
USART_Cmd(USART1, ENABLE);
}
/*
* 啟動DMA數據發送功能
*size表示需要發送的DMA中數據的個數
*/
void uart_dma_send_enabl(uint16_t size)
{
DMA1_Channel4->CNDTR = (uint16_t)size;
DMA_Cmd(DMA1_Channel4, ENABLE);
}
void USART1_IRQHandler(void)
{
memset(SendBuff,0,UART_RX_LEN);//清空發送數組
uint32_t temp = 0;
uint16_t i = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
上一篇:STM32基于固件庫學習筆記(13)ADC讀取電壓值
下一篇:STM32F4學習筆記之GPIO(使用固件庫)
推薦閱讀
史海拾趣