本次實驗我們是模仿正點原子的寄存器Demo來深入學習DMA的工作原理并來純手工敲寫DMA實驗。DMA是可以存儲器與存儲器,外設與存儲器之間進行數據傳輸的。這里我們 以串口1作為外設,BUFF[xx]字符數組作為存儲器的外設與存儲器之間的數據傳輸實驗。
那怎么看中文參考手冊呢? 第一步:看手冊的核心要點。 1、 簡介是表示用最簡單的語言來描述那種模塊的大致內容,所以這個我們必須看。
2、 模塊的特性我們也需要重點關注的,因為這里描述的東西在我們配置時極大可能需要了解的又可能疏忽的點。
3、 模塊處理過程是很重要的,我們根據理解它的處理過程來配置程序的。
4、 看到這個配置過程字眼,我們要知道,這是重點中的重點,因為我們的是根據這個爹來配置程序的。
第二步:理解了上面的描述后,我們就要理解一下這個DMA的工作框圖。 紅色線:這表示我們在平常串口通信的時候我們是將usart_dr寄存器的值放入定義好的buff[xx]緩沖區里作為接收操作,而將字符串逐個的值賦予usart_dr寄存器作為輸出操作。 從字面理解來說,這樣我們就要將MCU騰出的時間出進行串口數據接收和數據發送了,這樣就消耗了MCU的一部分資源了。 藍色線:第一條線表示MCU與DMA的連線,是用來配置DMA用的。第二條線表示外設或存儲器與DMA的數據交互的,可以理解成它就是一個小型的MCU,能直接處理存儲器與寄存器之間的數據交互,不需要STM32這個MCU的參與,這大大減小了MCU資源占用。
第三步:配置的大致我們都知道后,我們要知道一個要點,但凡我們使用一個模塊的東西去操作另一個模塊的時候,這兩個模塊之間是沒有線路直接相連的,而且還有這么多個通道那種,那么我們就要查看映射表了。 藍色框就是我們要使用的外設了,它在DMA1模塊里的通道4。
第四步:開始寫寄存器操作的程序。(我們看著剛剛那個配置過程來寫程序)
配置DMA的基地址,上門圖可知,DMA這個模塊里面每個通道都有自己對應的寄存器,那么我們配置寄存器也要相應的分開配置那個通道的,所以我們基地址需要分開來。 上面程序里為什么還要分DMA_BASE呢?這需要我們去看手冊一個地方,如下圖所示: DMA_ISR和DMA_IFCR這兩個寄存器和下面的是有很明顯的區別的,下面的沒4個對應一個通道,里面除了起始地址不一樣外,大小都一樣,所以配置時可以共用一個結構體,而那個兩個就只能用一個不同的結構體獨立出來了,也就是為什么用DMA_BASE的原因了。 寄存器的基地址在哪里找呢?如下圖可以查找到模塊的起始地址。 而每個通道的基地址就在DMA寄存器印象和復位這個表中查找,模塊起始地址加上表左邊的偏移量就是基地址了。 這個結構體就按照DMA寄存器印象和復位表來寫的。這里就步重復解釋了。 - 指針 (每個通道結構體和那個特別一點的只有兩個寄存器的結構體)
有以上的環境基礎下,我們就可以開始配置DMA了。 我們需要寄存器那些位呢?配置哪個寄存器就進入哪個寄存器查詢相關位的功能,列入下圖2 圖1 圖2 好了下面開始配置 這個配置程序基本是根據手冊中描述通道配置來編寫的 (1打開時鐘 2設置外設 3設置存儲器 4傳輸的數量 5方向、是否循環模式、外設是否增量、存儲器是否增量、外設數據寬、存儲器數據寬、優先級、什么模式啟動) 這里我就說一些重點,簡單的自己看手冊,只有自己學會看手冊才學到東西。 sendlen=BUFFLEN; 這是用來保存你要傳輸的數據長度,用來觸發你每次傳輸都是傳輸這個長度的數據。 循環模式:中文參考手冊有詳細解釋。 為什么要把外設和存儲器的位寬都設置8位:首先我們要理解一個過程DMA傳輸是將我們存儲器的值全部發送出去為止,那么8位為一個字節,在這個傳輸的過程就是每次取存儲器1個字節數據傳輸到外設,而外設也是一次取1個字節數據,那么實現數據一一對應了。 這個沒什么好說的,就是關閉上一次已經傳輸完成的;重置它的傳輸量,因為每次傳輸數據它都會以傳輸一個數據遞減1的形式遞減至0,并且它不會自己重載回去(循環模式就可以遞減至0又重載);開啟新的DMA傳輸。 配置完后就將我們準備好的存儲器寫滿內容,然后進行DMA傳輸,在串口中觀看。 1、 自己定義一個存儲器,記得要大點,假設我們配置的是波特率為115200,那么就說明數據傳輸速率115200bit/s = 14400字節/s,而我們用34000個字節的數據進行傳輸的話,一個DMA傳輸過程大概花費的時間是34000/14400=2.36s左右。 2、 我們使用串口作為外設 3、 串口1在DMA通道1,外設為串口USART_DR,存儲器SendBuff,存儲器的數據長度。 4、弄個什么循環的把存儲器SendBuff的數據灌滿。例如下圖 5、 開啟數據傳輸,上面紅框框里的是我們將本來使用復用的PA9口作為發送端換成了DMA->USART1_TX上(這個東西就類似與GPIO口復用成串口等這樣) 上面虛線這行代碼是用不了的,若想顯示它傳輸的百分比,就需要用OLED屏或者使用串口2等方式顯示(原因是因為DMA直接控制USART1_DR寄存器的,不需要MCU參與,導致USART1_DR這個一直被占用,就打印出傳輸過程的百分比量了)
完整程序:
- #include "stdio.h"
- #include "gpio.h"
- #include "USART1.h"
- #include "delay_ms.h"
- #include "clock.h"
- #include "USART1.h"
- #include "key.h"
- #include "DMA.h"
-
- const u8 Text_Buff[]="鄧家文使用STM32F103 DMA串口實驗";
- u16 str_len=sizeof(Text_Buff); //str_len將要發送字符的長度
- u8 SendBuff[34000]; //發送數據緩沖區 DMA( 寄存器->存儲器SendBuff )
- u16 KeyKind=0; //key_kind按鍵類
-
- float pro=0; //進度條
- u16 i,mask,tt; //循環體使用的臨時變量
-
-
- int main(void)
- {
- RCC_Config(9); //72MHz
- delay_init1(72); //打開延時
- GPIO_Init(); //初始化LED口
- USART_INIT(72,115200); //初始化串口
- key_init(); //初始化按鍵
- DMA_INIT(DMA_Channel4,(u32)&USART1->USART_DR,(u32)SendBuff,34000); //寄存器,存儲器,存儲器大小
-
- for(i=0;i<34000;i++)//填充數據到SendBuff
- {
- if(tt>=str_len)//加入換行符
- {
- if(mask)
- {
- SendBuff[i]=0x0a;
- tt=0;
- }else
- {
- SendBuff[i]=0x0d;
- mask++;
- }
- }else//復制TEXT_TO_SEND語句
- {
- mask=0;
- SendBuff[i]=Text_Buff[tt];
- tt++;
- }
- }
- while(1)
- {
- KeyKind=key_scan(0);
- if(KeyKind==2)
- {
- GPIOB->GPIO_ODR^=1<<5;
- USART1->USART_CR3=1<<7; //使能串口1的DMA發送
- DMA_OneSend(DMA_Channel4); //開啟一次發送
-
- while(1)
- {
- if(DMA->DMA_ISR&(1<<13)) //等待通道1傳輸完成
- {
- DMA->DMA_IFCR|=1<<13; //清除通道1傳輸完成標志
- break;
- }
- pro=DMA_Channel4->DMA_CNDTR;//獲取當前剩余數據量
- pro=1-pro/34000; //獲取百分比
- pro*=100; //得到整數的百分比
- printf("residue:%f\r\n",pro);
- }
- }
- }
-
- }
復制代碼
二、DMA.h
- #include "stdio.h"
- #include "gpio.h"
- #include "USART1.h"
- #include "delay_ms.h"
- #include "clock.h"
- #include "USART1.h"
- #include "key.h"
- #include "DMA.h"
-
- const u8 Text_Buff[]="鄧家文使用STM32F103 DMA串口實驗";
- u16 str_len=sizeof(Text_Buff); //str_len將要發送字符的長度
- u8 SendBuff[34000]; //發送數據緩沖區 DMA( 寄存器->存儲器SendBuff )
- u16 KeyKind=0; //key_kind按鍵類
-
- float pro=0; //進度條
- u16 i,mask,tt; //循環體使用的臨時變量
-
-
- int main(void)
- {
- RCC_Config(9); //72MHz
- delay_init1(72); //打開延時
- GPIO_Init(); //初始化LED口
- USART_INIT(72,115200); //初始化串口
- key_init(); //初始化按鍵
- DMA_INIT(DMA_Channel4,(u32)&USART1->USART_DR,(u32)SendBuff,34000); //寄存器,存儲器,存儲器大小
-
- for(i=0;i<34000;i++)//填充數據到SendBuff
- {
- if(tt>=str_len)//加入換行符
- {
- if(mask)
- {
- SendBuff[i]=0x0a;
- tt=0;
- }else
- {
- SendBuff[i]=0x0d;
- mask++;
- }
- }else//復制TEXT_TO_SEND語句
- {
- mask=0;
- SendBuff[i]=Text_Buff[tt];
- tt++;
- }
- }
- while(1)
- {
- KeyKind=key_scan(0);
- if(KeyKind==2)
- {
- GPIOB->GPIO_ODR^=1<<5;
- USART1->USART_CR3=1<<7; //使能串口1的DMA發送
- DMA_OneSend(DMA_Channel4); //開啟一次發送
-
- while(1)
- {
- if(DMA->DMA_ISR&(1<<13)) //等待通道1傳輸完成
- {
- DMA->DMA_IFCR|=1<<13; //清除通道1傳輸完成標志
- break;
- }
- pro=DMA_Channel4->DMA_CNDTR;//獲取當前剩余數據量
- pro=1-pro/34000; //獲取百分比
- pro*=100; //得到整數的百分比
- printf("residue:%f\r\n",pro);
- }
- }
- }
-
- }
復制代碼
三、DMA.c
- #include "DMA.h"
- #include "delay_ms.h"
-
- u16 sendlen; //保存DMA每次數據傳送的長度,也就是最開始的傳輸量,100%
-
- //CPAR外設地址
- //CMAR存儲器地址
- //BUFFLEN數據傳輸的長度或量
- void DMA_INIT(DMA_ChannelType*DMA_Chx,u32 CPAR,u32 CMAR,u16 BUFFLEN)
- {
- RCC->RCC_AHBENR=1<<0; //開啟DMA1的時鐘
- delay_ms1(5); //等待DMA時鐘穩定
- DMA_Chx->DMA_CPAR=CPAR; //指向外設寄存器地址
- DMA_Chx->DMA_CMAR=CMAR; //指向存儲器地址
- sendlen=BUFFLEN; //保存DMA傳輸數據量
- DMA_Chx->DMA_CNDTR=BUFFLEN; //傳輸數據量 每次傳輸都會自動遞減,直到0后會重載配置的長度數值BUFFLEN
- DMA_Chx->DMA_CCR=0x00000000; //將配置寄存器的所有位清除(復位)
- DMA_Chx->DMA_CCR|=1<<4; //從存儲器讀出數據
- DMA_Chx->DMA_CCR|=0<<5; //普通模式,不循環
- DMA_Chx->DMA_CCR|=0<<6; //外設地址非增量模式, 這個是讀數據,選擇讀出的位置大小是固定的,也就是不會隨地址改變,所以非增量
- DMA_Chx->DMA_CCR|=1<<7; //存儲器地址增量模式 這個是寫數據,需要將讀出來的數據依次的寫入對應的地方,所以增量
- DMA_Chx->DMA_CCR|=0<<8; //外設數據寬度為8位 也就是一次讀出1個字節
- DMA_Chx->DMA_CCR|=0<<10; //存儲器數據寬度8位 也就是一次寫入1個字節
- DMA_Chx->DMA_CCR|=1<<12; //中等優先級
- DMA_Chx->DMA_CCR|=0<<14; //啟動非存儲器到存儲器模式
- }
-
- //啟動一次DMA傳輸,上面定義的是一次傳輸1個字節
- void DMA_OneSend(DMA_ChannelType*DMA_Chx)
- {
- DMA_Chx->DMA_CCR&=~(1<<0); //先關閉上一次的DMA傳輸,因續上一次傳輸1個字節后并未關閉,若步關閉回影響下一次的傳輸,而且不能實現起停傳輸效果
- DMA_Chx->DMA_CNDTR=sendlen; //傳輸數據量
- DMA_Chx->DMA_CCR|=1<<0; //開啟下一次的DMA傳輸
- }
-
復制代碼
以上的的Word格式文檔51黑下載地址:
帶你模仿正點原子到寄存器編寫--DMA.docx
(1.52 MB, 下載次數: 14)
2021-4-29 02:28 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
|