基礎知識點
DMA
DMA(Direct Memory Access),即直接內存存儲,在一些數據的傳輸中,如串口、SPI等,采用DMA方式,傳輸過程不需要CPU參與,可用讓CPU有更多的時間處理其他的事情。
STM32F4的DMA通道選擇如下:
接下來的程序思路如下:
編程要點
DMA發送
串口DMA發送配置
由于是發送不定長的數據,先不需要配置發送的長度,在每次的發送時,再配置。
//=======================================
//串口DMA發送配置
//=======================================
void dma_uart_tx_init()
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時鐘使能
DMA_DeInit(Uart_Tx_DMAStream);//使用----->DMA2_Stream7
while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道選擇
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //目的:DMA外設地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff; //源:DMA存儲器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //方向:存儲器到外設模式
//DMA_InitStructure.DMA_BufferSize = BUF_SIZE; //長度:數據傳輸量(先不配置)
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_Medium; //DMA優先級:中等優先級
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //FIFO大小
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存儲器單次傳輸
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外設單次傳輸
DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
//中斷配置
DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE); //配置DMA發送完成后產生中斷
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;//
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7;//搶占優先級8
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA發送
DMA_Cmd (Uart_Tx_DMAStream,DISABLE);//先不要使能DMA!
}
DMA發送完成中斷
DMA發送完成后,觸發DMA發送完成中斷,這里可用釋放自定義的DMA發送完成信號量,表明下次的DMA傳輸可用進行。
//=======================================
//DMA發送完成中斷服務程序
//=======================================
void DMA2_Stream7_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
//printf("ooooooorn");
if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) //檢查DMA傳輸完成中斷 DMA_IT_TCIF7
{
DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7);
//printf("dma tx okrn");
if(uartDMATCSemaphore!=NULL)
{
//釋放二值信號量
xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken); //釋放DMA傳輸完成二值信號量
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的話進行一次任務切換
}
}
DMA發送函數接口
//=======================================
//串口DMA發送函數
//=======================================
void uart_DMA_send(u8 *str,u16 ndtr)
{
u8 i;
u8 *p=str;
while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE);//獲取信號量,等待DMA發送可用
DMA_Cmd(Uart_Tx_DMAStream, DISABLE); //關閉DMA傳輸
while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){} //確保DMA可以被設置
DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr); //數據傳輸量
for(i=0;i;i++)>
DMA接收
串口DMA接收配置
需要配置一個接收地址和一個接收長度,用于DMA接收數據的暫存。
//=======================================
//串口DMA接收配置
//=======================================
void dma_uart_rx_init()
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時鐘使能
DMA_DeInit(Uart_Rx_DMAStream);//使用----->DMA2_Stream5
while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道選擇
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //源:DMA外設地址
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ReceiveBuff; //目的:DMA存儲器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //方向:外設到存儲器模式
DMA_InitStructure.DMA_BufferSize = BUF_SIZE; //長度:數據傳輸量
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_Medium; //DMA優先級:中等優先級
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //FIFO大小
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存儲器單次傳輸
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外設單次傳輸
DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1的DMA接收
DMA_Cmd (Uart_Rx_DMAStream,ENABLE);//使能
}
串口空閑中斷
串口空閑中斷的作用與上一篇介紹的一樣,都是在發送完一串字符后被觸發,這次由于使用了DMA接收,所以接收的數據在DMA緩沖區,且接收的數據長度可用根DMA接收通道的總長度與剩余長度的差值來計算,將接收的數據復制出來使用即可,同時釋放自定義的串口空閑信號量,以便其它任務可用及時獲取串口接收到的數據。
//=======================================
//串口1空閑中斷服務程序,用于DMA接收
//=======================================
void USART1_IRQHandler(void)
{
uint8_t data;//接收數據暫存變量
BaseType_t xHigherPriorityTaskWoken;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空閑中斷
{
data = USART1->SR;
data = USART1->DR;
DMA_Cmd(Uart_Rx_DMAStream,DISABLE);//關閉DMA接收
while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){} //確保DMA可以被設置
rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream);//得到真正接收數據個數
DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE);//重新設置接收數據個數
//printf("rx_cnt:%drn",rx_cnt);
memcpy(rxbuf,ReceiveBuff,rx_cnt);//先復制出來,防止下次的數據來了之后將其覆蓋
DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//這里的各種標志還沒搞懂
DMA_Cmd(Uart_Rx_DMAStream,ENABLE); //開啟DMA接收
if(uartRxIDLESemaphore!=NULL)
{
//printf("nnnnnnnrn");
//釋放二值信號量
xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken);//釋放串口空閑中斷二值信號量
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的話進行一次任務切換
}
}
串口配置與測試任務
串口配置
基礎的GPIO配置,以及串口空閑中斷配置,并調用上面的串口DMA發送與接收配置。
//=======================================
//串口配置
//=======================================
void uart_init(u32 bound)
{
//GPIO端口設置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1時鐘
//串口1對應引腳復用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9復用為USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10復用為USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9與GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //復用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽復用輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化設置
USART_InitStructure.USART_BaudRate = bound; //波特率設置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字長為8位數據格式
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(USART1, &USART_InitStructure); //初始化串口1
//DMA Config
dma_uart_tx_init();//串口DMA發送配置
dma_uart_rx_init();//串口DMA接收配置
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//開啟串口空閑中斷
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; //搶占優先級8
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
USART_Cmd(USART1, ENABLE); //使能串口1
}
測試任務
創建DMA發送完成信號量和串口空閑信號量,并先釋放DMA發送完成信號量,用于第一次DMA發送時獲取信號量。然后測試兩條DMA發送不定長字符串,最后測試DMA接收不定長字符串。
//打印任務函數(測試任務)
void print_task(void *pvParameters)
{
//創建二值信號量
uartDMATCSemaphore = xSemaphoreCreateBinary();
uartRxIDLESemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(uartDMATCSemaphore);
u8 str1[]="ma nong ai xue xirn";
uart_DMA_send(str1,sizeof(str1));
u8 str2[]="xxpcb.github.iorn";
uart_DMA_send(str2,sizeof(str2));
BaseType_t err = pdFALSE;
while(1)
{
err=xSemaphoreTake(uartRxIDLESemaphore,5); //獲取信號量
if(err==pdTRUE) //獲取信號量成功
{
uart_DMA_send("receive:",sizeof("receive:"));
uart_DMA_send(rxbuf,rx_cnt);
uart_DMA_send("rn",sizeof("rn"));
rx_cnt=0;
}
}
}
實驗結果
通過串口助手,可以先接收到DMA發送的兩個字符串(第一條hello是測試串口的,不是DMA發的),然后通過串口調試助手發送兩次nice to meet you,測試DMA接收。
hello
ma nong ai xue xi
xxpcb.github.io
receive:nice to meet you
receive:nice to meet you
審核編輯:湯梓紅
-
串口
+關注
關注
14文章
1551瀏覽量
76422 -
dma
+關注
關注
3文章
560瀏覽量
100546 -
RTOS
+關注
關注
22文章
811瀏覽量
119593 -
FreeRTOS
+關注
關注
12文章
484瀏覽量
62139
發布評論請先 登錄
相關推薦
評論