stm32 DMA
stm32 DMA
- 介紹的定義
- 存儲器到存儲器
- main.c
- 存儲器到外設
- main.c
- usart_dma.c
- usart_dma.h
- 外設到存儲器
- main.c
- usart_dma.c
- usart_dma.h
- 中斷函數
- 對比理解
介紹的定義
存儲器:用來存儲程序代碼和數據。
易失性存儲器:存儲器斷電后,它存儲的數據內容丟失。易失性存儲器存取速度快,如內存。
非易失性存儲器:存儲器斷電后,它存儲的數據內容不丟失。非易失性存儲器可長期保存數據,如硬盤。
RAM(Random Access Memory):隨機存儲器。當存儲器中的消息被讀取或寫入時,所需要的時間與這段信息所在的位置無關。也就是說,RAM 讀取其內部任意地址的數據,時間都是相同的。根據RAM 的存儲機制,分為動態隨機存儲器 DRAM;靜態隨機存儲器 SRAM。
DRAM(Dynamic RAM):動態隨機存儲器。DRAM 的存儲單元以電容的電荷來表示數據,有電荷代表 1,無電荷代表 0。由于代表 1 的電容會放電,代表 0 的電容會吸收電荷,因此需要定期刷新操作,刷新操作會對電容進行檢查,若電量大于滿電量的 1/2,則認為其代表 1,并把電容充滿電;若電量小于 1/2, 則認為其代表 0,并把電容放電。 DRAM 的結構簡單,所以生產相同容量的存儲器,DRAM 的成本更低,集成度更高。外部擴展的內存一般使用 DRAM。
SRAM(Static RAM):靜態隨機存儲器。 SRAM 的存儲單元以鎖存器來存儲數據,這種電路結構不需要定時刷新充電,就能保持狀態。SRAM 一般用于 CPU 內部的高速緩存(Cache)。
SDRAM(Synchronous DRAM):同步通訊方式的DRAM,使用時鐘同步的通訊速度更快。SDRAM 只在上升沿表示有效數據,在 1 個時鐘周期內,只能表示 1 個有數據。
DDR SDRAM(Double Data Rate SDRAM):在時鐘的上升沿及下降沿各表示一個數據,也就是說在 1 個時鐘周期內可以表示 2 位數據,在時鐘頻率同樣的情況下, 提高了一倍的速度。
ROM(Read Only Memory):英文是只能讀的存儲器,后來人們設計出可以寫入數據的 ROM。用于指代非易失性半導體存儲器。
MASK ROM:存儲在它內部的數據是在出廠時使用特殊工藝固化的,生產后就不可修改,用在生產量大,數據不需要修改的場合。
EEPROM(Electrically Erasable Programmable ROM):電可擦除存儲器。可重復擦寫,擦除和寫入都是直接使用電路控制,不需要使用外部設備。而且可以以字節為單位修改數據,無需整個芯片擦除?,F在主要使用的 ROM 芯片都是 EEPROM。
FLASH:閃存,有人稱為flash ROM,也是可重復擦寫的儲器,容量一般比 EEPROM 大得多;在擦除時,一般以多個字節為單位。有的 FLASH 存儲器以 4096 個字節為扇區,最小的擦除單位為一個扇區。FLASH 存儲器又分為 NOR FLASH 和 NAND FLASH。NOR 與 NAND 的共性是在數據寫入前都需要有擦除操作。FLASH 的擦除次數都是有限的(10 萬次左右),當使用接近壽命的時候,可能會出現寫操作失敗。
NOR FLASH: NOR 的地址線和數據線分開,可以按“字節”讀寫數據;假如 NOR 上存儲了代碼指令,CPU 給 NOR 一個地址,NOR 就能向 CPU 返回一個數據讓 CPU 執行,中間不需要額外的處理操作。功能上可以認為 NOR 是一種斷電后數據不丟失的 RAM,但他的讀寫速度比 RAM 要慢得多。NOR FLASH 一般應用在代碼存儲的場合,如嵌入式控制器內部的程序存儲空間。
NAND FLASH:NAND 的數據和地址線共用,只能按“塊”來讀寫數據;若代碼存儲在 NAND 上,可以把它先加載到 RAM 存儲器上,再由 CPU 執行。NAND FLASH 一般應用在大數據量存儲的場合,包括 SD 卡、U 盤以及固態硬盤等。
DMA:直接存儲器存取,是單片機的一個外設,主要功能是搬數據,但是不需要占用 CPU(傳輸數據的時候,CPU 可以干其他的事情)。數據傳輸支持從外設到存儲器、存儲器到外設、存儲器到存儲器,這里的存儲器可以是 SRAM 或者是 FLASH。DMA 控制器獨立于內核,屬于一個單獨的外設。
存儲器到存儲器
main.c
先定義一個靜態的源數據,存放在內部 FLASH,然后使用 DMA 傳輸把源數據拷貝到目標地址上(內部 SRAM),最后對比源數據和目標地址的數據,看看是否傳輸準確。RGB 彩色燈用于指示程序狀態,如果 DMA 傳輸成功設置 RGB 彩色燈為藍色,如果 DMA 傳輸出錯設置 RGB 彩色燈為紅色。
void DMA_Config(void)函數是根據DMA_InitTypeDef這個結構體來編寫的,整體上,關于DMA的函數和結構體定義,都在stm32f10x_dma.c和stm32f10x_dma.h文件里面,利用庫函數編程其實說白了就是結合外設對應的那兩個文件來編程。
main整體流程:使能 DMA 時鐘;配置 DMA 數據參數;使能 DMA,進行傳輸;等待傳輸完成,并對源數據和目標地址數據進行比較。
main里面DMA_GetFlagStatus函數是獲取DMA事件標志位的當前狀態,參數是DMA_FLAG_TC意味著獲取DMA 數據傳輸完成這個標志位,DMA傳輸完成后,退出循環,運行之后程序。
main里面Buffercmp函數,aSRC_Const_Buffer是FLASH中存儲的數據,aDST_Buffer是內部的SRAM中存儲的數據,BUFFER_SIZE是數據大小。
TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE); #include "stm32f10x.h" #include "./led/bsp_led.h"// 當使用存儲器到存儲器模式時候,通道可以隨便選,沒有硬性的規定 #define DMA_CHANNEL DMA1_Channel6 #define DMA_CLOCK RCC_AHBPeriph_DMA1// 傳輸完成標志 #define DMA_FLAG_TC DMA1_FLAG_TC6// 要發送的數據大小 #define BUFFER_SIZE 32/* 定義aSRC_Const_Buffer數組作為DMA傳輸數據源* const關鍵字將aSRC_Const_Buffer數組變量定義為常量類型* 表示數據存儲在內部的FLASH中*/ const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80}; /* 定義DMA傳輸目標存儲器* 存儲在內部的SRAM中 */ uint32_t aDST_Buffer[BUFFER_SIZE];#define SOFT_DELAY Delay(0x0FFFFF); void Delay(__IO u32 nCount); uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength); void DMA_Config(void); /*** @brief 主函數* @param 無 * @retval 無*/ int main(void) {/* 定義存放比較結果變量 */uint8_t TransferStatus;/* LED 端口初始化 */LED_GPIO_Config();/* 設置RGB彩色燈為紫色 */LED_PURPLE; /* 簡單延時函數 */Delay(0xFFFFFF); /* DMA傳輸配置 */DMA_Config(); /* 等待DMA傳輸完成 */while(DMA_GetFlagStatus(DMA_FLAG_TC)==RESET){} /* 比較源數據與傳輸后數據 */TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);/* 判斷源數據與傳輸后數據比較結果*/if(TransferStatus==0) {/* 源數據與傳輸后數據不相等時RGB彩色燈顯示紅色 */LED_RED;}else{ /* 源數據與傳輸后數據相等時RGB彩色燈顯示藍色 */LED_BLUE;}while (1){ } }void Delay(__IO uint32_t nCount) //簡單的延時函數 {for(; nCount != 0; nCount--); }void DMA_Config(void) {DMA_InitTypeDef DMA_InitStructure;// 開啟DMA時鐘RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);// 外設DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;// 存儲器DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;// 方向:外設到存儲器(這里的外設是內部的FLASH) DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;// 傳輸大小 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;// 外設(內部的FLASH)地址遞增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;// 內存地址遞增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 外設數據單位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 內存數據單位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // DMA模式,一次或者循環模式DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 優先級:高 DMA_InitStructure.DMA_Priority = DMA_Priority_High;// 使能內存到內存的傳輸DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;// 配置DMA通道 DMA_Init(DMA_CHANNEL, &DMA_InitStructure);//清除DMA數據流傳輸完成標志位DMA_ClearFlag(DMA_FLAG_TC);// 使能DMADMA_Cmd(DMA_CHANNEL,ENABLE); }/*** 判斷指定長度的兩個數據源是否完全相等,* 如果完全相等返回1,只要其中一對數據不相等返回0*/ uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength) {/* 數據長度遞減 */while(BufferLength--){/* 判斷兩個數據源是否對應相等 */if(*pBuffer != *pBuffer1){/* 對應數據源不相等馬上退出函數,并返回0 */return 0;}/* 遞增兩個數據源的地址指針 */pBuffer++;pBuffer1++;}/* 完成判斷并且對應數據相對 */return 1; }//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;/*********************************************END OF FILE**********************/存儲器到外設
main.c
先定義一個數據變量,存于 SRAM 中,然后通過 DMA 的方式傳輸到串口的數據寄存器,然后通過串口把這些數據發送到電腦的上位機顯示出來。結果就是串口一直收到P,然后小燈一直閃,這也就說明,DMA 傳輸過程不占用 CPU 資源,可以一邊傳輸一邊運行其他任務。
// DMA 存儲器到外設(串口)數據傳輸實驗#include "stm32f10x.h" #include "bsp_usart_dma.h" #include "bsp_led.h"extern uint8_t SendBuff[SENDBUFF_SIZE]; static void Delay(__IO u32 nCount); /*** @brief 主函數* @param 無* @retval 無*/ int main(void) {uint16_t i;/* 初始化USART */USART_Config(); /* 配置使用DMA模式 */USARTx_DMA_Config();/* 配置RGB彩色燈 */LED_GPIO_Config();//printf("\r\n USART1 DMA TX 測試 \r\n");/*填充將要發送的數據*/for(i=0;i<SENDBUFF_SIZE;i++){SendBuff[i] = 'P';}/*為演示DMA持續運行而CPU還能處理其它事情,持續使用DMA發送數據,量非常大,*長時間運行可能會導致電腦端串口調試助手會卡死,鼠標亂飛的情況,*或把DMA配置中的循環模式改為單次模式*/ /* USART1 向 DMA發出TX請求 */USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);/* 此時CPU是空閑的,可以干其他的事情 */ //例如同時控制LEDwhile(1){LED1_TOGGLEDelay(0xFFFFF);} }static void Delay(__IO uint32_t nCount) //簡單的延時函數 {for(; nCount != 0; nCount--); } /*********************************************END OF FILE**********************/usart_dma.c
對于USARTx_DMA_Config函數,數據是從存儲器到串口,所以設置存儲器為源地址,串口的數據寄存器為目標地 址,要發送的數據有很多且都先存儲在存儲器中,則存儲器地址指針遞增,串口數據寄存器只有一個,則外設地址不變。
#include "bsp_usart_dma.h"uint8_t SendBuff[SENDBUFF_SIZE];/*** @brief USART GPIO 配置,工作參數配置* @param 無* @retval 無*/ void USART_Config(void) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 打開串口GPIO的時鐘DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);// 打開串口外設的時鐘DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);// 將USART Tx的GPIO配置為推挽復用模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);// 將USART Rx的GPIO配置為浮空輸入模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);// 配置串口的工作參數// 配置波特率USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;// 配置 針數據字長USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置停止位USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置校驗位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(DEBUG_USARTx, &USART_InitStructure); // 使能串口USART_Cmd(DEBUG_USARTx, ENABLE); }/***************** 發送一個字節 **********************/ void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch) {/* 發送一個字節數據到USART */USART_SendData(pUSARTx,ch);/* 等待發送數據寄存器為空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }/****************** 發送8位的數組 ************************/ void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num) {uint8_t i;for(i=0; i<num; i++){/* 發送一個字節數據到USART */Usart_SendByte(pUSARTx,array[i]); }/* 等待發送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); }/***************** 發送字符串 **********************/ void Usart_SendString( USART_TypeDef * pUSARTx, char *str) {unsigned int k=0;do {Usart_SendByte( pUSARTx, *(str + k) );k++;} while(*(str + k)!='\0');/* 等待發送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET){} }/***************** 發送一個16位數 **********************/ void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch) {uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch&0XFF00)>>8;/* 取出低八位 */temp_l = ch&0XFF;/* 發送高八位 */USART_SendData(pUSARTx,temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 發送低八位 */USART_SendData(pUSARTx,temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }///重定向c庫函數printf到串口,重定向后可使用printf函數 int fputc(int ch, FILE *f) {/* 發送一個字節數據到串口 */USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 等待發送完畢 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); return (ch); }///重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 int fgetc(FILE *f) {/* 等待串口輸入數據 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USARTx); }/*** @brief USARTx TX DMA 配置,內存到外設(USART1->DR)* @param 無* @retval 無*/ void USARTx_DMA_Config(void) {DMA_InitTypeDef DMA_InitStructure;// 開啟DMA時鐘RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 外設:串口數據寄存器地址*/DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;// 內存地址(要傳輸的變量的指針)DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;// 方向:從內存到外設 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 傳輸大小 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;// 外設地址不增 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模式,一次或者循環模式DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 優先級:中 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 禁止內存到內存的傳輸DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;// 配置DMA通道 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // 使能DMADMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); }usart_dma.h
這里面是USART 和 DMA 的宏定義。
#ifndef __USARTDMA_H #define __USARTDMA_H#include "stm32f10x.h" #include <stdio.h>// 串口工作參數宏定義 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200// USART GPIO 引腳宏定義 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd#define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10// 串口對應的DMA請求通道 #define USART_TX_DMA_CHANNEL DMA1_Channel4 // 外設寄存器地址 #define USART_DR_ADDRESS (USART1_BASE+0x04) // 一次發送的數據量 #define SENDBUFF_SIZE 5000void USART_Config(void); void USARTx_DMA_Config(void);#endif /* __USARTDMA_H */外設到存儲器
main.c
實驗結果就是,用電腦向開發板串口發送數據,數據會返回到電腦。
// DMA 外設(串口)到存儲器數據傳輸實驗#include "stm32f10x.h" #include "bsp_usart_dma.h" #include "bsp_led.h" static void Delay(__IO u32 nCount); /*** @brief 主函數* @param 無* @retval 無*/ int main(void) {/* 初始化USART */USART_Config(); /* 配置使用DMA模式 */USARTx_DMA_Config();/* 配置RGB彩色燈 */LED_GPIO_Config();printf("\r\nDMA外設到存儲器模式,用電腦向開發板串口發送數據,數據會返回到電腦。\r\n");/* USART1 向 DMA發出RX請求 */USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);//用電腦向開發板串口發送數據,數據會返回到電腦。while(1){LED1_TOGGLEDelay(0xFFFFF);} } static void Delay(__IO uint32_t nCount) //簡單的延時函數 {for(; nCount != 0; nCount--); } /*********************************************END OF FILE**********************/usart_dma.c
#include "bsp_usart_dma.h"uint8_t ReceiveBuff[RECEIVEBUFF_SIZE];/*** @brief USART GPIO 配置,工作參數配置* @param 無* @retval 無*/ void USART_Config(void) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStruct;// 打開串口GPIO的時鐘DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);// 打開串口外設的時鐘DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStruct.NVIC_IRQChannel = DEBUG_USART_IRQ;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;NVIC_Init(&NVIC_InitStruct);// 將USART Tx的GPIO配置為推挽復用模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);// 將USART Rx的GPIO配置為浮空輸入模式GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);// 配置串口的工作參數// 配置波特率USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;// 配置 針數據字長USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置停止位USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置校驗位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(DEBUG_USARTx, &USART_InitStructure); //使能空閑中斷USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE);// 使能串口USART_Cmd(DEBUG_USARTx, ENABLE); }/***************** 發送一個字節 **********************/ void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch) {/* 發送一個字節數據到USART */USART_SendData(pUSARTx,ch);/* 等待發送數據寄存器為空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }/****************** 發送8位的數組 ************************/ void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num) {uint8_t i;for(i=0; i<num; i++){/* 發送一個字節數據到USART */Usart_SendByte(pUSARTx,array[i]); }/* 等待發送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); }/***************** 發送字符串 **********************/ void Usart_SendString( USART_TypeDef * pUSARTx, char *str) {unsigned int k=0;do {Usart_SendByte( pUSARTx, *(str + k) );k++;} while(*(str + k)!='\0');/* 等待發送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET){} }/***************** 發送一個16位數 **********************/ void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch) {uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch&0XFF00)>>8;/* 取出低八位 */temp_l = ch&0XFF;/* 發送高八位 */USART_SendData(pUSARTx,temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 發送低八位 */USART_SendData(pUSARTx,temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }///重定向c庫函數printf到串口,重定向后可使用printf函數 int fputc(int ch, FILE *f) {/* 發送一個字節數據到串口 */USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 等待發送完畢 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); return (ch); }///重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 int fgetc(FILE *f) {/* 等待串口輸入數據 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USARTx); }/*** @brief USARTx TX DMA 配置,內存到外設(USART1->DR)* @param 無* @retval 無*/ void USARTx_DMA_Config(void) {DMA_InitTypeDef DMA_InitStructure;// 開啟DMA時鐘RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 外設:串口數據寄存器地址*/DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;// 內存地址(要傳輸的變量的指針)DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuff;// 方向:從外設到內存 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;// 傳輸大小 DMA_InitStructure.DMA_BufferSize = RECEIVEBUFF_SIZE;// 外設地址不增 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模式,一次或者循環模式 // DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 優先級:中 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 禁止內存到內存的傳輸DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;// 配置DMA通道 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // 使能DMADMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); }usart_dma.h
#ifndef __USARTDMA_H #define __USARTDMA_H#include "stm32f10x.h" #include <stdio.h>// 串口工作參數宏定義 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200// USART GPIO 引腳宏定義 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd#define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10#define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler// 串口對應的DMA請求通道 #define USART_TX_DMA_CHANNEL DMA1_Channel5 // 外設寄存器地址 #define USART_DR_ADDRESS (USART1_BASE+0x04) // 一次發送的數據量 #define RECEIVEBUFF_SIZE 5000void USART_Config(void); void USARTx_DMA_Config(void); void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num); #endif /* __USARTDMA_H */中斷函數
由于在usart_dma.c的USART_Config函數中使能了 USART 接收中斷,當 USART 接收到數據就會執行 DEBUG_USART_IRQHandler函數。里面有USART_GetITStatus函數:
USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET這使jym想到,存儲器到存儲器也有個類似的DMA_GetFlagStatus函數:
DMA_GetFlagStatus(DMA_FLAG_TC)==RESETUSART_GetITStatus()和USART_GetFlagStatus()的區別:
USART_GetITStatus常在串口中斷函數中使用,USART_GetFlagStatus常在做串口輪詢時使用。
USART_GetITStatus在判斷相應位是否置1(讀SR寄存器)前會先判斷相應位的中斷是否使能(讀CR寄存器);
在void USART_Config(void)函數里可以看到使能了空閑中斷:
//使能空閑中斷USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE);USART_GetFlagStatus直接判斷相應位是否置1(讀SR寄存器),而不會判斷相應中斷是否開啟,通常可用于沒開啟相應中斷時進行判斷
串口數據是通過DMA傳到ReceiveBuff(在SRAM里),然后由于開了USART 的IDLE接收中斷(當接收到1個字節,就會產生RXNE中斷,當接收到一幀數據,就會產生IDLE中斷),中斷里面通過Usart_SendArray函數向電腦傳ReceiveBuff里面的數據,這樣就達到了串口接收數據,串口數據通過DMA傳到ReceiveBuff(在SRAM里),ReceiveBuff里面的數據返回到電腦這樣一個過程。
void DEBUG_USART_IRQHandler(void) {uint16_t t;if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET) //檢查中斷是否發生{ DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE); //關閉DMA傳輸t = DMA_GetCurrDataCounter(USART_TX_DMA_CHANNEL); //獲取剩余的數據數量Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t); //向電腦返回數據(接收數據數量 = SENDBUFF_SIZE - 剩余未傳輸的數據數量)DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,RECEIVEBUFF_SIZE); //重新設置傳輸的數據數量DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE); //開啟DMA傳輸USART_ReceiveData(DEBUG_USARTx); //讀取一次數據,不然會一直進中斷USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE); //清除串口空閑中斷標志位}}對比理解
jym對USART_DMACmd函數的理解:
USART_DMACmd函數用于控制 USART 的 DMA 請求的啟動和關閉。它接收三個參數,第一個參數用于設置串口外設;第二個參數設置串口的具體 DMA 請求,包括串口發送請求USART_DMAReq_Tx和接收請求 USART_DMAReq_Rx;第三個參數用于設置啟動請求 ENABLE 或者關閉請求 DISABLE;
運行該函數后 USART 的 DMA 發送傳輸就開始了,如果是串口發送請求(請求發送到串口),即USART_DMAReq_Tx,存儲器的數據會發送到串口,如下面的存儲器到外設中的代碼;如果是串口接收請求(請求存儲器接收串口數據),即USART_DMAReq_Rx,串口數據會傳給存儲器。
存儲器到外設:
/* USART1 向 DMA發出TX請求 */USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);外設到存儲器:
/* USART1 向 DMA發出RX請求 */USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);其實對于DMA設置,首要理解的就是這個DMA_InitTypeDef結構體。jym采用三種方式定義了這個結構體,下面把每個參數單獨摘出來,通過類比來理解。
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;DMA_PeripheralBaseAddr:一般設置為外設的數據寄存器地址,如果是存儲器到存儲器模式則設置為其中一個存儲器地址。
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;//外設到存儲器 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;//存儲器到外設 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;//存儲器到存儲器DMA_MemoryBaseAddr:一般設置為自定義存儲區的首地址。
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuff;//外設到存儲器 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;//存儲器到外設 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;//存儲器到存儲器DMA_DIR:傳輸方向選擇,可選外設到存儲器、存儲器到外設;并沒有存儲器到存儲器的方向選擇, 當使用存儲器到存儲器時,只需要把其中一個存儲器當作外設使用即可;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外設到存儲器 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存儲器到外設 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//存儲器到存儲器 #define DMA_DIR_PeripheralDST ((uint32_t)0x00000010) #define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)剩下的jym不類比了,因為太多了。其實要想知道DMA把數據從哪傳到哪,關鍵就是上面這三個參數的設置。
總結
- 上一篇: 网站需要数据库服务器吗,网站需要独立的服
- 下一篇: Altium Designer20原理图