第一次接觸DMA是在學校學習ARM9裸板程序的時候,想起來都時隔快2年了。現在來看看STM32平臺的DMA,一樣,在標準外設庫的支持下,STM32的DMA編程十分簡單,但是既是學習,那還是花點時間看看DMA的相關概念及原理的了解下。
1. DMA簡介
DMA是Direct Memory Access的簡稱,是直接存儲器訪問的意思。DMA是STM32單片機的外設之一,主要功能是用來搬移數據的。通過DMA搬移數據不需要CPU直接參與控制,也不需要中斷處理方式那樣保留現場和恢復現場。在傳輸數據的時候,CPU可以干其他事情。
無使用DMA的數據傳輸:
使用DMA后的數據傳輸:
DMA數據傳輸支持從外設到存儲器、存儲器到外設、存儲器到存儲器(這里所講的存儲器可以是SRAM,也可以是FLASH)。DMA控制器包含了DMA1控制器和DMA2控制器,分別由7和5個通道作為數據傳輸。每個通道專門用來管理來自一個或者多個外設對存儲器訪問的請求,還有一個仲裁器用于協調各個外設對DMA傳輸請求的優先權。注意,DMA2只存在于大容量或互聯型的STM32單片機中。
2. DMA功能框圖
2.1 STM32外設對DMA的請求及通道
請求及通道對應圖中的標號1和標號2:STM32外設想要通過DMA來傳輸數據,需先給DMA控制器發送DMA請求,控制器在收到外設的DMA請求之后會給外設一個應答信號,外設應答且DMA控制器收到外設的應答后,DMA啟動傳輸,直至傳輸完畢。
為什么需要發出請求,應答和接收應答這幾個繁瑣的步驟?由圖中藍色框框可以看出,DMA傳輸和CPU是共用系統總線的,要啟動DMA傳輸的前提是系統總線是空閑的,換句話說是CPU沒有占用系統總線,所以啟動DMA傳輸前需要以上幾個應答機制,其最底層是DMA控制器和CPU正為系統總線作出協調。DMA1有7個通道,DMA2有5個通道,不同的外設請求要通過對應的DMA通道發給DMA控制器。將不同的外設請求傳輸至對應的通道,這個是我們在軟件編程上設置的。
DMA1開放的通道及對應請求:
DMA2開放的通道及對應請求:
雖然每個通道可以接收多個外設的請求,但是同一時間內只能接收一個。
2.2 仲裁器
仲裁器對應圖中的標號3:當DMA控制器的多個通道發生DMA請求時,就需要仲裁器管理響應處理的順序。仲裁器通過軟件和硬件來管理DMA請求:軟件指的是我們寫的代碼,在DMA_CCRx(x指通道號)寄存器中設置,有4個等級,非常高(DMA_Priority_VeryHigh)、高(DMA_Priority_High)、中(DMA_Priority_Medium)和低(DMA_Priority_Low)。硬件則是指若有兩個或以上的DMA通道請求設置的優先級一樣,則它們的響應順序取決于通道編號,編號低者優先級高,在有DMA2的STM32中,DMA1控制器擁的響應優先級高于DMA2。
2.3 配置DMA控制器
配置DMA控制器,無非就是下圖這幾個寄存器:
前面說到,DMA數據傳輸機制并不需要CPU的參與,但是DMA控制器要正常工作,數據要正確傳輸,需有三個必要條件:源地址、目的地址和數據大小,對于數據分批傳輸的情況,數據大小這個條件還包含每次傳輸的大小及單位。
(1)源地址和目的地址
DMA的傳輸數據的方向有三個:從外設到存儲器、從存儲器到外設、從存儲器到存儲器。DMA_CCR的BIT[4]DIR就是用于配置數據傳輸方向的:
取值為0表從外設到存儲器,取值為1表從存儲器到外設。外設地址在DMA_CPAR寄存器配置,存儲器地址在DMA_CMAR寄存器配置。
(2)傳輸數據的大小及單位
以串口向電腦發送數據為例(存儲器->外設方向),開發板軟件可以一次性給電腦發送大量數據,具體多少在DMA_CNDTR配置:
DMA_CNDTR低16位有效,一次最多只能傳輸65535個數據。
數據要正確傳輸,源、目標存儲的數據寬度必須一致。串口數據寄存器是8位的,也就是外設數據寬度設置寄存器DMA_CCRx的BIT[9:8]PSIZE取值為0:
存儲器的數據寬度設置寄存器DMA_CCRx的BIT[11:10]MSIZE取值也為0:
DMA傳輸數據,還需要設置源地址上的數據發送指針和目的地址數據存放指針的增量模式。開發板串口向電腦發送數據,假設要發送的數據很多,那么存儲器(源地址)上數據發送指針每次發送完畢需要加1,而串口數據寄存器則不需要,因為該寄存器只有一個,數據寄存器上的數據傳送到電腦后被清空了(就算不清空,數據直接覆蓋也沒關系)。外設的地址指針增量模式由DMA_CCRx的PINC配置,存儲器的地址指針則由MINC配置。
(3)傳輸結束
DMA中斷狀態寄存器DMA_ISR可以設置每個DMA通道傳輸過半、傳輸完成和傳輸錯誤示產生對應標志,
在DMA_CCRx位1、2、3可以設置發生傳輸過半、傳輸完成和傳輸錯誤時產生中斷:
另外補充一點,位0用于使能DMA傳輸
傳輸完成分兩種模式:一次傳輸和循環傳輸,一次傳輸指傳輸一次后就停止,要再傳輸需要關閉DMA使能后重新配置后才能繼續傳輸。循環傳輸則是一次傳輸完成后又恢復第一次傳輸時的配置循環傳輸,如此循環。設置位在DMA_CCRx寄存器的CIRC。
3. DMA功能模塊描述結構體
標準庫的一貫風格,在stm32f10x_dma.h文件中定于可DMA_InitTypeDef初始化結構體,DMA_Init()函數定義在stm32f10x_dma.c中。
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; //外設地址
uint32_t DMA_MemoryBaseAddr; //存儲器地址
uint32_t DMA_DIR; //傳輸方向
uint32_t DMA_BufferSize; //傳輸的數據的數目大小
uint32_t DMA_PeripheralInc; //外設地址的增量模式
uint32_t DMA_MemoryInc; //存儲器地址的增量模式
uint32_t DMA_PeripheralDataSize; //外設數據寬度
uint32_t DMA_MemoryDataSize; //存儲器數據寬度
uint32_t DMA_Mode; //模式選擇
uint32_t DMA_Priority; //通道優先級
uint32_t DMA_M2M; //存儲器到存儲器模式
}DMA_InitTypeDef;
(1)DMA_PeripheralBaseAddr:外設地址,若是存儲器到存儲器模式,此成員設置為其中一個存儲器的地址,否則設置為外設的地址。
(2)DMA_MemoryBaseAddr:存儲器地址,一般設置為程序中存放數據的容器(數組)的首地址。
(3)DMA_DIR:傳輸方向,可設置為外設到存儲器,存儲器到外設。注意這里沒有存儲器到存儲器的選項,當使用存儲器到存儲器時,只需要把其中一個存儲器當做外設使用。
(4)DMA_BufferSize:設定待傳輸數據數目。(5)DMA_PeripheralInc:外設地址增量模式,若取值為DMA_PeripheralInc_Enable表使能外設地址自動遞增功能。一般外設都是只有一個數據寄存器,所以不會使能該位。
(6)DMA_MemoryInc:若配置為DMA_MemoryInc_Enable表使能存儲器地址自動遞增功能。一般存儲器都是我們自定義的,區域內存放多個數據,所以一般使能該位。
(7)DMA_MemoryDataSize:外設數據寬度,可選8位(字節)、16位(半字)、32位(字)
(8)DMA_MemoryDataSize:存儲器數據寬度,可選8位(字節)、16位(半字)、32位(字)
(9)DMA_Mode:傳輸模式選擇,一次傳輸或循環傳輸
(10)DMA_Priority:通道優先級設置,非常高、高、中、低可選
(11)DMA_M2M:存儲器到存儲器模式
4. 編程常用函數
4.1 DMA時鐘使能
4.2 初始化DMA功能模塊描述結構體
函數原型:void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
DMAy_Channelx指定哪一個DMA通道,DMA_InitStruct就是前面解析的描述結構體
使用示例:DMA_Init(DMAy_Channel1, &DMA_InitStruct);
4.3 使能外設DMA發送
以啟動DMA發送功能為例:
函數原型:void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState
NewState)
使用示例:
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
外設的DMA傳輸需要相應的設置,而存儲器是不需要的。存儲器到存儲器,在DMA_InitTypeDef結構體中有DMA_M2M成員需要開啟。
4.4 使能DMA通道,開啟DMA傳輸
函數原型:void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
使用示例:DMA_Cmd(DMAy_Channel1, ENABLE);
使能之后,DMA控制器開始工作,在合適的時機(CPU無占據總線)開始DMA控制下的數據傳輸。
4.5 查詢DMA傳輸狀態
在DMA傳輸過程中,我們可以通過函數來查詢傳輸通道的狀態:
函數原型:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
假設要查詢DMA通道4傳輸是否完成:
DMA_GetFlagStatus(DMA1_FLAG_TC4);
返回值為RESET表示傳輸尚未完成,SET表傳輸完成。
獲取當前剩余數據量大小的函數:
函數原型:uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
使用示例,獲取DMA通道4還有多少數據沒有傳輸:
DMA_GetCurrDataCounter(DMAy_Channel4);
5. 編程實踐
5.1 DMA傳輸–存儲器到存儲器模式
硬件平臺是正點原子MiniSTM32,板載有兩個LED分別為紅色和綠色。
程序功能實現把STM32內置的FLASH數據拷貝到內置的SRAM中:定義一個const靜態變量為源數據,使用DMA傳輸將源數據拷貝到目標地址中,比對源數據和目標數據是否相同,若相同亮綠色LED燈,反之亮紅色LED燈。
DMA的編程核心在于
(1)使能DMA時鐘
(2)配置DMA初始化結構體參數
(3)使能DMA,開始進行數據傳輸
(4)等待數據傳輸完成
工程結構為:
BSP_LED.c實現配置LED引腳:
#include
void LED_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitTypeStu;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);
//3?ê??ˉPA8í?íìê?3?
GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_8;
GPIO_InitTypeStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitTypeStu);
GREEN_LED_OFF;
GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitTypeStu);
RED_LED_OFF;
}
BSP_USART.c實現配置USART1功能:
#include "BSP_USART.h"
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
};
FILE __stdout;
_sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40) == RESET);
USART1->DR = (u8) ch;
return ch;
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStu;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStu.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStu.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStu.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStu.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStu);
}
void USART_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStu;
USART_InitTypeDef USART_InitStu;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
GPIO_InitStu.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStu.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStu);
GPIO_InitStu.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStu.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStu);
USART_InitStu.USART_BaudRate = 115200;
USART_InitStu.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStu.USART_Parity = USART_Parity_No;
USART_InitStu.USART_StopBits = USART_StopBits_1;
USART_InitStu.USART_WordLength = USART_WordLength_8b;
USART_InitStu.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStu);
NVIC_Configuration();
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
void USART_SendChar(USART_TypeDef* pUSARTx, uint8_t c)
{
USART_SendData(pUSARTx, c);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
void USART_SendString(USART_TypeDef* pUSARTx, char* str)
{
uint32_t n = 0;
while (*(str + n) != '\0')
{
USART_SendChar(pUSARTx, *(str + n));
n++;
}
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
以上在前面相關文章都已經寫過,下來看BSP_LED.c,實現對DMA功能的配置:
#include
void DMA_Configuration(const uint32_t *SrcBuf, uint32_t *destBuf, uint32_t BUF_SZ)
{
DMA_InitTypeDef DMA_InitTypeStu;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeStu.DMA_BufferSize = BUF_SZ;
DMA_InitTypeStu.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitTypeStu.DMA_M2M = DMA_M2M_Enable;
DMA_InitTypeStu.DMA_MemoryBaseAddr = (uint32_t)SrcBuf;
DMA_InitTypeStu.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitTypeStu.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitTypeStu.DMA_Mode = DMA_Mode_Normal;
DMA_InitTypeStu.DMA_PeripheralBaseAddr = (uint32_t)destBuf;
DMA_InitTypeStu.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitTypeStu.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitTypeStu.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel1, &DMA_InitTypeStu);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
uint8_t BufCmp(const uint32_t* pSrc, uint32_t* pDest, uint32_t len)
{
int i;
for (i = 0; i < len; i++)
{
if (*(pSrc + i) != *(pDest + i))
return 1;
}
return 0;
}
main()函數:
#include
#include
#include
#define BUF_SZ 32
const uint32_t SrcBuf[BUF_SZ] = {0x12, 0x23, 0x45, 0x86, 0x45, 0x63, 0x89, 0x87,
0x22, 0x23, 0x26, 0x27, 0x28, 0x31, 0x33, 0x86,
0x12, 0x23, 0x45, 0x86, 0x45, 0x63, 0x89, 0x87,
0x22, 0x23, 0x26, 0x27, 0x28, 0x31, 0x33, 0x86};
uint32_t destBuf[BUF_SZ];
void delay_time(void)
{
int i, j;
for (i = 0; i < 500; i++)
for (j = 0; j < 6000; j++);
}
int main(void)
{
int i;
LED_Configuration();
USART_Configuration();
DMA_Configuration(SrcBuf, destBuf, BUF_SZ);
delay_time();
#if 0
for (i = 0; i < BUF_SZ; i++)
{
printf("SrcBuf[%d] = %u\r\n", i, SrcBuf[i]);
}
printf("\r\n");
for (i = 0; i < BUF_SZ; i++)
{
printf("destBuf[%d] = %u\r\n", i, destBuf[i]);
}
#endif
#if 1
if (BufCmp(SrcBuf, destBuf, BUF_SZ) == 0)
{
GREEN_LED_ON;
RED_LED_OFF;
}
else
{
GREEN_LED_OFF;
RED_LED_ON;
}
#endif
while (1);
return 0;
}
5.2 存儲器到串口(外設)模式
//BSP_DMA.c
void DMA_Configuration(const uint32_t *SrcBuf, uint32_t *destBuf, uint32_t BUF_SZ)
{
DMA_InitTypeDef DMA_InitTypeStu;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeStu.DMA_BufferSize = BUF_SZ;
DMA_InitTypeStu.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitTypeStu.DMA_M2M = DMA_M2M_Disable;
DMA_InitTypeStu.DMA_MemoryBaseAddr = (uint32_t)SrcBuf;
DMA_InitTypeStu.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitTypeStu.DMA_MemoryInc = DMA_MemoryInc_Enable;
//DMA_InitTypeStu.DMA_Mode = DMA_Mode_Normal;
DMA_InitTypeStu.DMA_Mode = DMA_Mode_Circular; //循環發送
DMA_InitTypeStu.DMA_PeripheralBaseAddr = (uint32_t)destBuf; //(uint32_t)(USART1_BASE + 0x04);
DMA_InitTypeStu.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitTypeStu.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitTypeStu.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel4, &DMA_InitTypeStu);
DMA_Cmd(DMA1_Channel4, ENABLE);
}
//main.c
int main(void)
{
LED_Configuration();
USART_Configuration();
DMA_Configuration((const uint32_t*)SrcBuf, (uint32_t*)(&USART1->DR), BUF_SZ); //(uint32_t*)(USART1_BASE + 0x04)
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
while(1)
{
GREEN_LED_ON;
RED_LED_OFF;
delay_time();
GREEN_LED_OFF;
RED_LED_ON;
delay_time();
}
return 0;
}
DMA控制下,SRAM上的數據不斷向USART1的DR寄存器發送,這個過程CPU是不需要參與的,所以LED閃爍也一直在進行。
DMA傳輸其實很簡單,被我說得有點啰嗦了。關鍵在于概念的理解吧,庫函數的使用還是關鍵,寄存器操作基本不需要用到。
上一篇:stm32系統存儲器中自舉程序和flash中bootloader的區別
下一篇:stm32庫中地址映射
推薦閱讀
史海拾趣