WordPress更改網(wǎng)站地址抖音seo排名系統(tǒng)哪個好用
STM32 DMA學習日記
寫于2024/9/28晚
文章目錄
- STM32 DMA學習日記
- 1. DMA簡介
- 2. I/O方式
- 2.1 程序查詢方式
- 2.2 程序中斷方式
- 2.3 DMA方式
- 3.DMA框圖
- 4. 相關(guān)寄存器
- 4.1 DMA中斷狀態(tài)寄存器(DMA_ISR)
- 4.2 DMA中斷標志清除寄存器(DMA_IFCR)
- 4.3 DMA通道x傳輸數(shù)量寄存器(DMA_CNDTRx)
- 4.4 DMA通道x配置寄存器(DMA_CCRx)
- 4.5 DMA通道x外設(shè)地址寄存器(DMA_CPARx)
- 4.6 DMA通道x存儲器地址寄存器(DMA_CMARx)
- 5.例程解析
- 5.1 DMA相關(guān)HAL庫驅(qū)動介紹
1. DMA簡介
DMA,全稱為:Direct Memory Access,即直接存儲器訪問。DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復現(xiàn)場的過程,通過硬件為 RAM 與 I/O 設(shè)備開辟一條直接傳送數(shù)據(jù)的通路,能使 CPU 的效率大為提高。是計算機的4種I/O方式中的一種。
2. I/O方式
輸入/輸出系統(tǒng)實現(xiàn)主機與I/O設(shè)備之間的數(shù)據(jù)傳送,可以采用不同的控制方式,各種方式在代價、性能、解決問題的著重點等方面各不相同,常用的I/O方式有程序查詢、程序中斷、DMA和通道等,其中前兩種方式更依賴于CPU中程序指令的執(zhí)行。下面我們來簡介一下計算機組成原理中的4中I/O方式中的前三種。
2.1 程序查詢方式
信息交換的控制完全由CPU執(zhí)行程序?qū)崿F(xiàn),程序查詢方式接口中設(shè)置一個數(shù)據(jù)緩沖寄存器(數(shù)據(jù)端口)和一個設(shè)備狀態(tài)寄存器(狀態(tài)端口)。主機進行I/O操作時,先發(fā)出詢問信號,讀取設(shè)備的狀態(tài)并根據(jù)設(shè)備狀態(tài)決定下一步操作究竟是進行數(shù)據(jù)傳送還是等待。
程序查詢方式的工作流程如下(見圖7.2):
- ①CPU執(zhí)行初始化程序,并預置傳送參數(shù)。
- ②向I/O接口發(fā)出命令字,啟動I/O設(shè)備。
- ③從外設(shè)接口讀取其狀態(tài)信息。
- ④CPU不斷查詢I/O設(shè)備狀態(tài),直到外設(shè)準備就緒。
- ⑤傳送一次數(shù)據(jù)。
- ⑥修改地址和計數(shù)器參數(shù)。
- ⑦判斷傳送是否結(jié)束,若未結(jié)束轉(zhuǎn)第③步,直到計數(shù)器為0
在這種控制方式下,CPU一旦啟動I/O,就必須停止現(xiàn)行程序的運行,并在現(xiàn)行程序中插入一段程序。程序查詢方式的主要特點是CPU有“踏步”等待現(xiàn)象,CPU與I/O串行工作。這種方式的接口設(shè)計簡單、設(shè)備量少,但CPU在信息傳送過程中要花費很多時間來查詢和等待,而且在一段時間內(nèi)只能和一臺外設(shè)交換信息,效率大大降低。
2.2 程序中斷方式
程序中斷方式的思想:CPU在程序中安排好在某個時機啟動某臺外設(shè),然后CPU繼續(xù)執(zhí)行當前的程序,不需要像查詢方式那樣一直等待外設(shè)準備就緒。一旦外設(shè)完成數(shù)據(jù)傳送的準備工作,就主動向CPU發(fā)出中斷請求,請求CPU為自己服務。在可以響應中斷的條件下,CPU暫時中止正在執(zhí)行的程序,轉(zhuǎn)去執(zhí)行中斷服務程序為外設(shè)服務,在中斷服務程序中完成一次主機與外設(shè)之間的數(shù)據(jù)傳送,傳送完成后,CPU返回原來的程序,如圖7.3所示。
2.3 DMA方式
DMA方式是一種完全由硬件進行成組信息傳送的控制方式,它具有程序中斷方式的優(yōu)點,即在數(shù)據(jù)準備階段,CPU與外設(shè)并行工作。DMA方式在外設(shè)與內(nèi)存之間開辟一條“直接數(shù)據(jù)通道”,信息傳送不再經(jīng)過CPU,降低了CPU在傳送數(shù)據(jù)時的開銷,因此稱為直接存儲器存取方式。
由于數(shù)據(jù)傳送不經(jīng)過CPU,也就不需要保護、恢復CPU現(xiàn)場等煩瑣操作。
這種方式適用于磁盤、顯卡、聲卡、網(wǎng)卡等高速設(shè)備大批量數(shù)據(jù)的傳送,它的硬件開銷比較大。在DMA方式中,中斷的作用僅限于故障和正常傳送結(jié)束時的處理。
DMA控制器的組成
在DMA方式中,對數(shù)據(jù)傳送過程進行控制的硬件稱為DMA控制器(DMA接口)。當I/O設(shè)備需要進行數(shù)據(jù)傳送時,通過DMA控制器向CPU提出DMA傳送請求,CPU響應之后將讓出系統(tǒng)總線,由DMA控制器接管總線進行數(shù)據(jù)傳送。其主要功能如下:
- 接受外設(shè)發(fā)出的DMA請求,并向CPU發(fā)出總線請求。
- CPU響應并發(fā)出總線響應信號,DMA接管總線控制權(quán),進入DMA操作周期。
- 確定傳送數(shù)據(jù)的主存單元地址及長度,并自動修改主存地址計數(shù)和傳送長度計數(shù)。
- 規(guī)定數(shù)據(jù)在主存和外設(shè)間的傳送方向,發(fā)出讀寫等控制信號,執(zhí)行數(shù)據(jù)傳送操作。
- 向CPU報告DMA操作結(jié)束。
DMA方式和中斷方式的區(qū)別
DMA方式和中斷方式的重要區(qū)別如下:
①中斷方式是程序的切換,需要保護和恢復現(xiàn)場;而DMA方式不中斷現(xiàn)行程序,無需保護現(xiàn)場,除了預處理和后處理,其他時候不占用任何CPU資源。
②對中斷請求的響應只能發(fā)生在每條指令執(zhí)行結(jié)束時(執(zhí)行周期后);而對DMA請求的響應可以發(fā)生在任意一個機器周期結(jié)束時(取指、間址、執(zhí)行周期后均可)。
③中斷傳送過程需要CPU的干預;而DMA傳送過程不需要CPU的干預,因此數(shù)據(jù)傳輸率非常高,適合于高速外設(shè)的成組數(shù)據(jù)傳送。④DMA請求的優(yōu)先級高于中斷請求。
⑤中斷方式具有處理異常事件的能力,而DMA方式僅局限于大批數(shù)據(jù)的傳送。
⑥從數(shù)據(jù)傳送來看,中斷方式靠程序傳送,DMA方式靠硬件傳送。
3.DMA框圖
STM32F103ZET6 有兩個 DMA 控制器,DMA1 和 DMA2,本章,我們僅針對 DMA1 進行介紹。
下面先來學習 DMA 控制器框圖,通過學習 DMA 控制器框圖會有一個很好的整體掌握,同時對之后的編程也會有一個清晰的思路。
圖中,我們標記了 3 處位置,起作用分別是:
① DMA 請求
如果外設(shè)想要通過 DMA 來傳輸數(shù)據(jù),必須先給 DMA 控制器發(fā)送 DMA 請求,DMA 收到請求信號之后,控制器會給外設(shè)一個應答信號,當外設(shè)應答后且 DMA 控制器收到應答信號之后,就會啟動 DMA 的傳輸,直到傳輸完畢。
STM32F103 共有 DMA1 和 DMA2 兩個控制器,DMA1 有 7 個通道,DMA2 有 5 個通道,不同的 DMA 控制器的通道對應著不同的外設(shè)請求,這決定了我們在軟件編程上該怎么設(shè)置,具體見表 29.1.1.1DMA 請求映像表。
② 通道
DMA 具有 12 個獨立可編程的通道,其中 DMA1 有 7 個通道,DMA2 有 5 個通道,每個通道對應不同的外設(shè)的 DMA 請求。雖然每個通道可以接收多個外設(shè)的請求,但是同一時間只能接收一個,不能同時接收多個。
③ 仲裁器
當發(fā)生多個 DMA 通道請求時,就意味著有先后響應處理的順序問題,這個就由仲裁器管理。仲裁器管理 DMA 通道請求分為兩個階段。第一階段屬于軟件階段,可以在 DMA_CCRx寄存器中設(shè)置,有 4 個等級:非常高,高,中和低四個優(yōu)先級。第二階段屬于硬件階段,如果兩個或以上的 DMA 通道請求設(shè)置的優(yōu)先級一樣,則他們優(yōu)先級取決于通道編號,編號越低優(yōu)先權(quán)越高,比如通道 0 高于通道 1。在大容量產(chǎn)品和互聯(lián)型產(chǎn)品中,DMA1 控制器擁有高于 DMA2 控制器的優(yōu)先級。
4. 相關(guān)寄存器
4.1 DMA中斷狀態(tài)寄存器(DMA_ISR)
該寄存器是查詢當前 DMA 傳輸?shù)臓顟B(tài),我們常用的是 TCIFx 位,即通道 DMA 傳輸完成與否的標志。注意此寄存器為只讀寄存器,所以在這些位被置位之后,只能通過其他的操作來清除。
4.2 DMA中斷標志清除寄存器(DMA_IFCR)
該寄存器是用來清除 DMA_ISR 的對應位的,通過寫 0 清除。在 DMA_ISR 被置位后,我們必須通過向該寄存器對應的位寫 1 來清除。
4.3 DMA通道x傳輸數(shù)量寄存器(DMA_CNDTRx)
4.4 DMA通道x配置寄存器(DMA_CCRx)
該寄存器控制著 DMA 很多相關(guān)信息,包括數(shù)據(jù)寬度、外設(shè)及存儲器寬度、通道優(yōu)先級、增量模式、傳輸方向、中斷允許、使能等,所以說 DMA_CCRx 是 DMA 傳輸?shù)暮诵目刂萍拇嫫鳌?/p>
4.5 DMA通道x外設(shè)地址寄存器(DMA_CPARx)
該寄存器是用來存儲 STM32 外設(shè)的地址,比如我們平常使用串口 1,那么該寄存器必須寫入 0x40013804(其實就是&USART1_DR)。其他外設(shè)就可以修改成其他對應外設(shè)地址就好了。
4.6 DMA通道x存儲器地址寄存器(DMA_CMARx)
DMA通道x存儲器地址寄存器用來存放存儲器的地址,該寄存器和 DMA_CPARx差不多,所以就不列出來了。舉個應用的例子,在程序中,我們使用到一個 g_sendbuf[5200]數(shù)組來做存儲器,那么我們在 DMA_CMARx 中寫入&g_sendbuf 即可。
5.例程解析
5.1 DMA相關(guān)HAL庫驅(qū)動介紹
驅(qū)動函數(shù) | 關(guān)聯(lián)寄存器 | 功能描述 |
---|---|---|
__HAL_RCC_DMAx_CLK_ENABLE(…) | RCC_AHBENR | 使能DMAx時鐘 |
HAL_DMA_Init(…) | DMA_CCR | 初始化DMA |
HAL_DMA_Start_IT(…) | DMA_CCR/CPAR/CMAR/CNDTR | 開始DMA傳輸 |
__HAL_LINKDMA(…) | 用來連接DMA和外設(shè)句柄 | |
HAL_UART_Transmit_DMA(…) | CCR/CPAR/CMAR/CNDTR/USART_CR3 | 使能DMA發(fā)送,啟動傳輸 |
__HAL_DMA_GET_FLAG(…) | DMA_ISR | 查詢DMA傳輸通道的狀態(tài) |
__HAL_DMA_ENABLE(…) | DMA_CCR(EN) | 使能DMA外設(shè) |
__HAL_DMA_DISABLE(…) | DMA_CCR(EN) | 失能DMA外設(shè) |
DMA外設(shè)相關(guān)結(jié)構(gòu)體:DMA_HandleTypeDef 和 DMA_InitTypeDef
typedef struct __DMA_HandleTypeDef
{DMA_Channel_TypeDef *Instance; /*!< Register base address 寄存器基地址 */DMA_InitTypeDef Init; /*!< DMA communication parameters DMA參數(shù) */ } DMA_HandleTypeDef;
typedef struct
{uint32_t Direction /* DMA傳輸方向 */uint32_t PeriphInc /* 外設(shè)地址(非)增量 */uint32_t MemInc /* 存儲器地址(非)增量*/uint32_t PeriphDataAlignment /* 外設(shè)數(shù)據(jù)寬度 */uint32_t MemDataAlignment /* 存儲器數(shù)據(jù)寬度 */uint32_t Mode /* 操作模式 */uint32_t Priority /* DMA通道優(yōu)先級 */} DMA_InitTypeDef;
以DMA方式傳輸串口數(shù)據(jù)配置步驟
- 使能DMA時鐘:
__HAL_RCC_DMA1_CLK_ENABLE
- 初始化DMA:
HAL_DMA_Init
函數(shù)初始化DMA相關(guān)參數(shù)__HAL_LINKDMA
函數(shù)連接DMA和外設(shè) - 使能串口的DMA發(fā)送,啟動傳輸:
HAL_UART_Transmit_DMA
- 查詢DMA傳輸狀態(tài):
__HAL_DMA_GET_FLAG
查詢通道傳輸狀態(tài)__ HAL_DMA_GET_COUNTER
獲取當前傳輸剩余數(shù)據(jù)量 - DMA中斷使用:
HAL_NVIC_EnableIRQ
HAL_NVIC_SetPriority
編寫中斷服務函數(shù)xxx_IRQHandler
DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */
extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 *//*** @brief 串口TX DMA初始化函數(shù)* @note 這里的傳輸形式是固定的, 這點要根據(jù)不同的情況來修改* 從存儲器 -> 外設(shè)模式/8位數(shù)據(jù)寬度/存儲器增量模式** @param dmax_chy : DMA的通道, DMA1_Channel1 ~ DMA1_Channel7, DMA2_Channel1 ~ DMA2_Channel5* 某個外設(shè)對應哪個DMA, 哪個通道, 請參考<<STM32中文參考手冊 V10>> 10.3.7節(jié)* 必須設(shè)置正確的DMA及通道, 才能正常使用! * @retval 無*/
void dma_init(DMA_Channel_TypeDef* DMAx_CHx)
{if ((uint32_t)DMAx_CHx > (uint32_t)DMA1_Channel7) /* 大于DMA1_Channel7, 則為DMA2的通道了 */{__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2時鐘使能 */}else {__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1時鐘使能 */}__HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle); /* 將DMA與USART1聯(lián)系起來(發(fā)送DMA) *//* Tx DMA配置 */g_dma_handle.Instance = DMAx_CHx; /* USART1_TX使用的DMA通道為: DMA1_Channel4 */g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* DIR = 1 , 存儲器到外設(shè)模式 */g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外設(shè)非增量模式 */g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存儲器增量模式 */g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外設(shè)數(shù)據(jù)長度:8位 */g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 存儲器數(shù)據(jù)長度:8位 */g_dma_handle.Init.Mode = DMA_NORMAL; /* 外設(shè)流控模式 */g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等優(yōu)先級 */HAL_DMA_Init(&g_dma_handle);
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/DMA/dma.h"const uint8_t TEXT_TO_SEND[] = {"正點原子 STM32 DMA 串口實驗"}; /* 要循環(huán)發(fā)送的字符串 */
#define SEND_BUF_SIZE (sizeof(TEXT_TO_SEND) + 2) * 200 /* 發(fā)送數(shù)據(jù)長度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. */uint8_t g_sendbuf[SEND_BUF_SIZE]; /* 發(fā)送數(shù)據(jù)緩沖區(qū) */
extern DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */
extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 */int main(void)
{uint8_t key = 0;uint16_t i, k;uint16_t len;uint8_t mask = 0;float pro = 0; /* 進度 */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 設(shè)置時鐘, 72Mhz */delay_init(72); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */dma_init(DMA1_Channel4); /* 初始化串口1 TX DMA */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);len = sizeof(TEXT_TO_SEND);k = 0;for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集數(shù)據(jù) */{if (k >= len) /* 入換行符 */{if (mask){g_sendbuf[i] = 0x0a;k = 0;}else{g_sendbuf[i] = 0x0d;mask++;}}else /* 復制TEXT_TO_SEND語句 */{mask = 0;g_sendbuf[i] = TEXT_TO_SEND[k];k++;}}i = 0;while (1){key = key_scan(0);if (key == KEY0_PRES) /* KEY0按下 */{printf("\r\nDMA DATA:\r\n");lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);lcd_show_string(30, 150, 200, 16, 16, " %", BLUE); /* 顯示百分號 */HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE);/* 等待DMA傳輸完成,此時我們來做另外一些事情,比如點燈 * 實際應用中,傳輸數(shù)據(jù)期間,可以執(zhí)行另外的任務 */while (1){if ( __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4)) /* 等待 DMA1_Channel4 傳輸完成 */{__HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TC4);HAL_UART_DMAStop(&g_uart1_handle); /* 傳輸完成以后關(guān)閉串口DMA */break;}pro = DMA1_Channel4->CNDTR; /* 得到當前還剩余多少個數(shù)據(jù) */len = SEND_BUF_SIZE; /* 總長度 */pro = 1 - (pro / len); /* 得到百分比 */pro *= 100; /* 擴大100倍 */lcd_show_num(30, 150, pro, 3, 16, BLUE);} lcd_show_num(30, 150, 100, 3, 16, BLUE); /* 顯示100% */lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示傳送完成 */}i++;delay_ms(10);if (i == 20){LED0_TOGGLE(); /* LED0閃爍,提示系統(tǒng)正在運行 */i = 0;}}
}