下面是串口DMA+環形緩衝區的實現,資料收發是非同步,不需要死等。關於環形緩衝區參考:
http://blog.csdn.net/jieffantfyan/article/details/53572103 實現原理
程式是在串口中斷收發方式的基礎上設計的,應用程式層通過環形緩衝區進行串口資料讀取,環形緩衝區作為一級緩衝,增加DMA作為二級緩衝。相對中斷方式這種設計可以減少串口進入中斷的次數,尤其是在高速傳輸速率的情況下。由於使用DMA收發資料時,必須預設好發送/接收地址、長度等資訊,軟體內為DMA開闢了16個位元組數組作為緩衝區。當發送環形緩衝區內有資料需要發送時,程式將前16位元組(如果不足則複製實際長度並重新設定DMA發送長度)複製到DMA發送緩衝區內並啟動發送,資料發送完成之後將餘下的資料按同樣的方法複製到DMA發送緩衝區直至資料發送完成。而對於接收來說,處理比發送麻煩一些,需要兩個中斷服務程式配合,當DMA接收完成16位元組之後,軟體將這些資料寫入到接收環形緩衝區內,以便應用程式讀取。因為軟體中將DMA接收緩衝長度設定為16位元組,即DMA必須連續接收到16位元組才會進入中斷服務程式,但多數情況下接收到的資料長度不可能全是16位元組的倍數,比如MCU只收到10位元組之後的一段時間內再也收不到資料了。對於這種情況可以使用定時器配合檢測,一旦發現長時間收不到串口的資料則將DMA接收緩衝區的資料全部提取出來。由於STM32單片機提供了空閑中斷,我們可以利用這個機制解決上述問題,當產生空閑中斷時,意味著已經沒有資料接收了,這時候則將DMA接收緩衝內的資料提取出來。 外部介面聲明
下面將串口的初始化、讀、寫介面抽象出來。
/****************************************************************************** * Copyright (C) 2016, roger * All rights reserved. * * 檔案名稱: tty.h * 摘 要:控制台驅動 * * 目前的版本: 3.0 * 作 者: roger * 完成日期: 2016-09-24 * * 取代版本: 2.0 * 原作者 : roger * 完成日期: 2015-07-08 ******************************************************************************/#ifndef _TTY_H_#define _TTY_H_#define TTY_BAUDRATE 115200 /*傳輸速率 ------------*/#define TTY_TXBUF_SIZE 256 /*發送緩衝區長度 -----*/#define TTY_RXBUF_SIZE 256 /*接收緩衝區長度 -----*/#define TTY_DMA_TX_LEN 10 /*DMA 發送緩衝區 ----*/#define TTY_DMA_RX_LEN 10 /*DMA 接收緩衝區 ----*/#define TTY_USE_DMA 1 /*啟用DMA -----------*//* Exported Structs ---------------------------------------------------------*/typedef struct { void (*init)(void); /*初始化 --------*/ unsigned int (*write)(void *buf, unsigned int len); /*資料寫 --------*/ unsigned int (*read) (void *buf, unsigned int len); /*讀資料 --------*/ void (*puts)(const char *str); /*輸入一個字串 */ void (*clr)(void); /*清除接收緩衝區 */ unsigned int (*buflen)(void); /*接收緩衝區的長度*/ void (*printf)(const char *format, ...); /*格式化列印 ----*/}tty_t;/* Exported variables ------------------------------------------------------- */extern const tty_t tty;#endif
介面實現 ##‘
/****************************************************************************** * Copyright (C) 2016, roger * All rights reserved. * * 檔案名稱: tty.c * 摘 要:列印串口驅動 * * 目前的版本: 3.0 * 作 者: roger * 完成日期: 2016-09-24 * * 取代版本: 2.0 * 原作者 : roger * 完成日期: 2015-07-08 ******************************************************************************//* Includes ------------------------------------------------------------------*/#include "tty.h"#include "ringbuffer.h"#include "stm32f4xx.h"#include <stdarg.h>#include <stdio.h>#include <string.h>static unsigned char rxbuf[TTY_TXBUF_SIZE]; /*接收緩衝區 ------------*/static unsigned char txbuf[TTY_RXBUF_SIZE]; /*發送緩衝區 ------------*/static ring_buf_t ringbuf_send, ringbuf_recv; /*收發緩衝區管理 ---------*/#if TTY_USE_DMA == 1 static unsigned char dma_tx_buf[TTY_DMA_TX_LEN];/*DMA發送緩衝區 ---------*/ static unsigned char dma_rx_buf[TTY_DMA_RX_LEN];/*DMA接收緩衝區 ---------*/ #endif/******************************************************************************* * 函數名稱:port_conf * 功能描述:列印串口配置(PD8->USART3_TX, PD9->USART3_RX) * 輸入參數:none * 返 回 值:none * 作 者:roger.luo ******************************************************************************/static void port_conf(void){ GPIO_InitTypeDef GPIO_InitStructure; /*console串口引腳配置 ----------------------------------------------------*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3); GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOD, &GPIO_InitStructure); }/******************************************************************************* * 函數名稱:DMA_Conf * 功能描述: 串口DMA配置(DMA1_Channel4_Stream1->USART3_RX, * DMA1_Channel4_Stream3->USART3_TX) * 輸入參數:none * 返 回 值:none * 作 者:roger.luo ******************************************************************************/#if TTY_USE_DMA == 1static void DMA_Conf(void){ DMA_InitTypeDef DMA_Structure; NVIC_InitTypeDef NVIC_InitStructure; /* Enable DMA clock */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); DMA_DeInit(DMA1_Stream1); DMA_DeInit(DMA1_Stream3); while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){} while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){} /*配置串口3接收流 */ DMA_Structure.DMA_Channel = DMA_Channel_4; /*DMA1通道4*/ DMA_Structure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR); DMA_Structure.DMA_Memory0BaseAddr = (uint32_t)dma_rx_buf; DMA_Structure.DMA_DIR = DMA_DIR_PeripheralToMemory; /*外設到記憶體*/ DMA_Structure.DMA_BufferSize = sizeof(dma_rx_buf); DMA_Structure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_Structure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_Structure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_Structure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_Structure.DMA_Mode = DMA_Mode_Circular; /*迴圈模式*/ DMA_Structure.DMA_Priority = DMA_Priority_Low; DMA_Structure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_Structure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_Structure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_Structure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream1, &DMA_Structure); /*配置串口3發送流 */ DMA_Structure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR); DMA_Structure.DMA_Memory0BaseAddr = (uint32_t)dma_tx_buf; DMA_Structure.DMA_DIR = DMA_DIR_MemoryToPeripheral; /*記憶體到外設*/ DMA_Structure.DMA_BufferSize = sizeof(dma_tx_buf); DMA_Structure.DMA_Mode = DMA_Mode_Normal; /*正常模式 -*/ DMA_Init(DMA1_Stream3, &DMA_Structure); /* Enable DMA Stream Transfer Complete interrupt */ DMA_ITConfig(DMA1_Stream1, DMA_IT_TC, ENABLE); //DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE); /* DMA Stream enable */ DMA_Cmd(DMA1_Stream1, ENABLE); /*使能接收流*/ /* Enable the DMA Stream IRQ Channel */ NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn; NVIC_Init(&NVIC_InitStructure); }#endif/******************************************************************************* * 函數名稱:uart_conf * 功能描述:TTY 串口配置 * 輸入參數:none * 返 回 值:none * 作 者:roger.luo ******************************************************************************/static void uart_conf(void){ USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; USART_DeInit(USART3); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); USART_InitStructure.USART_BaudRate = TTY_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(USART3, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); ring_buf_create(&ringbuf_send, txbuf, sizeof(txbuf));/*初始化環形緩衝區 --*/ ring_buf_create(&ringbuf_recv, rxbuf, sizeof(rxbuf)); #if TTY_USE_DMA == 1 USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE); /*開啟DMA請求 --------*/ USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); /*開啟空閑中斷處理DMA接收 -------*/ #else USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);#endif USART_ITConfig(USART3, USART_IT_ERR, ENABLE); USART_Cmd(USART3, ENABLE); }/******************************************************************************* * 函數名稱:init * 功能描述:列印驅動初始化 * 輸入參數:none * 返 回 值:none * 作 者:roger.luo ******************************************************************************/static void init(void){ port_conf(); uart_conf();#if TTY_USE_DMA == 1 DMA_Conf(); #endif }/******************************************************************************* * 函數名稱:send * 功能描述:向串口發送緩衝區內寫入資料 * 輸入參數:buf - 緩衝區 * len - 緩衝區長度 * 返 回 值:實際寫入長度(如果此時緩衝區滿,則返回len) * 作 者:roger.luo ******************************************************************************/static unsigned int send(void *buf, unsigned int len){#if TTY_USE_DMA == 1 unsigned int ret; ret = ring_buf_put(&ringbuf_send, buf, len); USART_ITConfig(USART3, USART_IT_TC, ENABLE); return ret;#else unsigned int ret; ret = ring_buf_put(&ringbuf_send, (unsigned char *)buf, len); USART_ITConfig(USART3, USART_IT_TXE, ENABLE); return ret; #endif }/******************************************************************************* * 函數名稱:recv * 功能描述:讀取tty接收緩衝區的資料 * 輸入參數:buf - 緩衝區 * len - 緩衝區長度 * 返 回 值:(實際讀取長度)如果接收緩衝區的有效資料大於len則返回len否則返回緩衝 * 區有效資料的長度 * 作 者:roger.luo ******************************************************************************/unsigned int recv(void *buf, unsigned int len){ return ring_buf_get(&ringbuf_recv, (unsigned char *)buf, len);}#if TTY_USE_DMA == 1/******************************************************************************* * 函數名稱:DMA1_Stream1_IRQHandler * 功能描述:TTY串口DMA接收完成中斷 * 輸入參數:none * 返 回 值:none ******************************************************************************/void DMA1_Stream1_IRQHandler(void){ if (DMA_GetITStatus(DMA1_Stream1, DMA_IT_TCIF1) != RESET) { ring_buf_put(&ringbuf_recv, dma_rx_buf, sizeof(dma_rx_buf)); DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1); } }/******************************************************************************* * 函數名稱:DMA1_Stream3_IRQHandler * 功能描述:TTY串口DMA發送完成中斷 * 輸入參數:none * 返 回 值:none ******************************************************************************//*void DMA1_Stream3_IRQHandler(void){ unsigned int len; if (DMA_GetITStatus(DMA1_Stream3, DMA_IT_TCIF3) != RESET) { DMA_ClearITPendingBit(DMA1_Stream3, DMA_IT_TCIF3); if ((len = ring_buf_get(&ringbuf_send, dma_tx_buf, sizeof(dma_tx_buf)))) { DMA_SetCurrDataCounter(DMA1_Stream3, len); DMA_Cmd(DMA1_Stream3, ENABLE); USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); } else sending = 0; } }*/#endif/******************************************************************************* * 函數名稱:USART3_IRQHandler * 功能描述:串口1收發中斷 * 輸入參數:none * 返 回 值:none ******************************************************************************/void USART3_IRQHandler(void){ #if TTY_USE_DMA == 1 uint16_t len, retry = 0; if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET || USART_GetITStatus(USART3, USART_IT_FE) != RESET) { /*擷取DMA緩衝區內的有效資料長度 --------------------------------------*/ len = sizeof(dma_rx_buf) - DMA_GetCurrDataCounter(DMA1_Stream1); DMA_Cmd(DMA1_Stream1, DISABLE); ring_buf_put(&ringbuf_recv,dma_rx_buf, len); /*將資料放入接收緩衝區*/ while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE && retry++ < 100){} /*複位DMA當前計數器值 ------------------------------------------------*/ DMA_SetCurrDataCounter(DMA1_Stream1, sizeof(dma_rx_buf)); DMA_Cmd(DMA1_Stream1, ENABLE); DMA_ClearFlag(DMA1_Stream1, DMA_FLAG_TCIF1); /*清除傳輸完成標誌,否則會進入傳輸完成中斷*/ len = USART3->DR; /*清除中斷標誌 -------*/ } if (USART_GetITStatus(USART3, USART_IT_TC) != RESET) { if ((len = ring_buf_get(&ringbuf_send, dma_tx_buf, sizeof(dma_tx_buf)))) { DMA_Cmd(DMA1_Stream3, DISABLE); DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3); DMA_SetCurrDataCounter(DMA1_Stream3, len); DMA_Cmd(DMA1_Stream3, ENABLE); } else { USART_ITConfig(USART3, USART_IT_TC, DISABLE); } }#else unsigned char data; if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { data = USART_ReceiveData(USART3); ring_buf_put(&ringbuf_recv,&data, 1); /*將資料放入接收緩衝區*/ } if (USART_GetITStatus(USART3, USART_IT_TXE) != RESET) { if (ring_buf_get(&ringbuf_send, &data, 1)) /*從緩衝區中取出資料---*/ { USART_SendData(USART3, data);