由于做项目的需要的原因,我们这次使用了 DMA +IDLE 模式的接受模式,即任意的长度的数据仅仅需要一个IDLE中断就可以接受完成。 在tx方面我们采用了DMA 环形buffer的方式。下面详细说
1. RX Mode
RX Mode 比较简单 我们只是使能了IDLE mode 和DMA的buffer即可
下面是两个函数:
void DMA_Init_Rx(u32 Source_addr, u32 Destination_addr) { DMA_InitTypeDef DMA_RX; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); DMA_DeInit(DMA1_Stream1); DMA_RX.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_RX.DMA_Memory0BaseAddr = Destination_addr; DMA_RX.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_RX.DMA_PeripheralBaseAddr = Source_addr; DMA_RX.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_RX.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_RX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_RX.DMA_Priority = DMA_Priority_Medium; DMA_RX.DMA_Mode = DMA_Mode_Normal; DMA_RX.DMA_Channel = DMA_Channel_4; DMA_RX.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_RX.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_RX.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream1, &DMA_RX); DMA_Cmd(DMA1_Stream1,ENABLE); } void Usart3_Init(uint32_t BaudRate) { GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//enable the GPIOB pins clock RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //USART3 clock setting GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); // Configure USART3 Tx (PB.10) as alternate function push-pull GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;//PB.10 USART3_TX PB.11 USART3_RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // MOD GPIO_Init(GPIOB, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 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); USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); //其中这个地方我们需要使能IDLE 模式 USART_ClearFlag(USART3, USART_FLAG_TC|USART_FLAG_IDLE); //needs to clear the TC flag USART_ClearFlag(USART3, USART_FLAG_RXNE); //needs to clear the TC flag USART_DMACmd(USART3, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE); USART_Cmd(USART3, ENABLE); /* Enable the USARTx Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 9; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); Ring_Buffer_Init(&ring_buff_tx, g_tx_buff, sizeof(g_tx_buff), DMA_Buff); }
在中断函数中我们需要注意如何把IDLE的位清除
我们需要首先读USART DR的内容 然后再读USART->SR的内容,之后就可以清除了
下面是代码
/* USART interrupt using IDLE mode. */ void USART3_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken; if(USART_GetFlagStatus(USART3,USART_FLAG_ORE)!=0) { USART_ReceiveData(USART3); USART_ClearFlag(USART3, USART_FLAG_ORE); } if(USART_GetITStatus(USART3, USART_IT_IDLE)) //no matter how many data received from serial port. the interrupt is only triggered at IDLE moment. { DMA_Cmd(DMA1_Stream1, DISABLE); DMA_Init_Rx((u32)&USART3->DR, (u32)g_rx_buff); //relocate the RX buffer address. DMA_Cmd(DMA1_Stream1, ENABLE); uint8_t clr = USART3->DR; //according to the data sheet, IDLE mode needs to be clear DR followed by SR. clr = USART3->SR; clr = clr; //for get rid of the warning USART_ClearITPendingBit(USART3, USART_IT_IDLE); xQueueSendFromISR(Usart_Queue, g_rx_buff, &xHigherPriorityTaskWoken); if(xHigherPriorityTaskWoken){ portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } }
后面是发送的内容:
我们这里采用的是双cache的模式 即:
首先采用一个环形列表去接受数据,然后用一个DMA_BUFF 从这个环形的数组去拷贝数据, 然后被DMA发送出去。DMA的速度要远远的比CPU慢 所以说我们需要缓存把数据存起来,然后再CPU干别的事情的时候 利用DMA把数据发送出去。
为什么用环形列表?
回答:环形列表的优点在于我们可以不用删除里面的内容,我们只要知道头指针和尾部指针的位置我们就可以知道里面的数据到底有多少了。
下面的代码就是我的环形指针的结构体:
typedef struct { char* ring_buff; /*this pointer point to the buff regarded as ring buffer*/ char* dma_buff; /*this pointer point to the dma buff dma buff will copy the data from ring_buff per when DMA is not busy*/ uint16_t Front_Index; /*recode the front index of the buffer always point to the initia valid data*/ uint16_t Rear_Index ; /*recode the rear index of the buffer, always point to the end of the valid data*/ uint16_t Ring_Buffer_Size; /*recode the size of the buffer*/ bool Busy_Flag; }ring_buff;
Ring_buffer_Size 会记录buffer的大小。
busy flag: 这个是一个互锁的变量,当我们在主程序把数据放入ring buffer的时候我们不希望此时DMA找ring buffer要数据 这样子会破坏全局变量,所以我们用这个busy flag去告诉cpu什么时候可以放置数据 什么时候可以用DMA去发送数据。
/******************************************************************** File Name: ring_buff.c Author: Jack Shi Description: This file presents the first cache ----ring buffer. Principle: For DMA receive, there are two Cache, one is ring buffer, the other one is DMA_buffer. The ring buffer restore the string from DMA_printf. And DMA_Buffer will fetch data from ring_buffer and send to DMA bus. ____ / \ Dma_printf => | | => DMA_TX_BUFF => DMA_BUS /___ / Function List: bool Ring_Buffer_Init(ring_buff* ring_buffer, uint8_t* ring_buff, uint16_t ring_buff_len, uint8_t* dma_buff) void Ring_Buffer_Clr(ring_buff* ring_buffer) uint16_t Ring_Buf_Len(ring_buff* ring_buffer) uint16_t Ring_Buff_Put(ring_buff* ring_buffer, uint8_t* buff, uint16_t len) uint16_t Ring_Buff_Get(ring_buff* ring_buffer, uint8_t* buff, uint16_t len) bool Ring_Buff_IsEmpty(ring_buff* ring_buffer) Version: v1.0 ********************************************************************/ #include "ring_buff.h" #include <string.h> #include "common.h" /******************************************************************** Function Name: bool Ring_Buffer_Init(ring_buff* ring_buff, uint8_t* buff, uint16_t len) Description: Generate a empty ring buffer Input: ring_buff* ring_buff:structure of ring buff; uint8 *buff: give a buff to make ring buffer the length of this buffer Return: if buff is not NULL return true else return NULL Others: ********************************************************************/ bool Ring_Buffer_Init(ring_buff* ring_buffer, char* ring_buff, uint16_t ring_buff_len, char* dma_buff) { ring_buffer->Front_Index = 0; //point to the header address ring_buffer->Rear_Index = 0; //point to the header address ring_buffer->Ring_Buffer_Size = ring_buff_len; //get the length of the buffer size ring_buffer->dma_buff = dma_buff; //get the dma_buffer pointer ring_buffer->ring_buff = ring_buff; //get the ring buffer pointer return (ring_buffer->ring_buff != NULL) && (ring_buffer->dma_buff != NULL); } /******************************************************************** Function Name: void Ring_Buffer_Clr(ring_buff* ring_buff) Description: clear the data in the ring buffer Input: ring buffer structure Return: Others: ********************************************************************/ void Ring_Buffer_Clr(ring_buff* ring_buffer) { ring_buffer->Front_Index = ring_buffer->Rear_Index = 0; //clear the data in ring buffer } /******************************************************************** Function Name: uint16_t ring_buf_len(ring_buff* ring_buff) Description: get the valid data length Input: Return: get the valid data length Others: ********************************************************************/ uint16_t Ring_Buf_Len(ring_buff* ring_buffer) { return ring_buffer->Rear_Index - ring_buffer->Front_Index; //get the valid data length } /******************************************************************** Function Name: uint16_t Ring_Buff_Put(ring_buff* ring_buff, uint8_t* buff, uint16_t len) Description: put data inside the ring buffer Input: structure ring buff uint16 buff: the data int len: the length of data Return: how many data is written in ringbuffer Others: ********************************************************************/ uint16_t Ring_Buff_Put(ring_buff* ring_buffer, char* buff, uint16_t len) { uint16_t i; uint16_t space; //space left in buffer /***************get the blank space of ring buffer*************************************/ space = ring_buffer->Front_Index <= ring_buffer->Rear_Index ? \ ring_buffer->Ring_Buffer_Size + ring_buffer->Front_Index - ring_buffer->Rear_Index : \ ring_buffer->Front_Index - ring_buffer->Rear_Index ; len = MIN(len,space); /*how many data can be written*/ i = MIN(len, ring_buffer->Ring_Buffer_Size - (ring_buffer->Rear_Index%ring_buffer->Ring_Buffer_Size) ); //get the space later from rear index /* copy the data to the space after rear_index*/ memcpy(ring_buffer->ring_buff + (ring_buffer->Rear_Index % ring_buffer->Ring_Buffer_Size), buff, i); /*if data length is more than the length after rear index then write the rest of data from the initial address of ring buff*/ if(len > i){ memcpy(ring_buffer->ring_buff, buff + i, len - i); } /*get the real Rear index position.*/ ring_buffer->Rear_Index = (ring_buffer->Rear_Index + len) % ring_buffer->Ring_Buffer_Size; return len; } /******************************************************************** Function Name: uint16_t Ring_Buff_Get(ring_buff* ring_buff, uint8_t* buff, uint16_t len) Description: get data from ring buffer Input: Return: return how many data has been got from ring buffer Others: ********************************************************************/ uint16_t Ring_Buff_Get(ring_buff* ring_buffer, char* buff, uint16_t len) { uint16_t i = 0; uint16_t space = 0; /* get the how many valid data are there in the ring buffer */ space = (ring_buffer->Rear_Index >= ring_buffer->Front_Index ? \ ring_buffer->Rear_Index - ring_buffer->Front_Index : \ ring_buffer->Rear_Index + ring_buffer->Ring_Buffer_Size - ring_buffer->Front_Index) ; len = MIN(len, space); //get how many data can be read out /*how many data can be read out from front index to end*/ i = MIN(len, ring_buffer->Ring_Buffer_Size - (ring_buffer->Front_Index%ring_buffer->Ring_Buffer_Size)); if(i != 0){ /*collect all the data from front to end*/ memcpy(buff, ring_buffer->ring_buff + ring_buffer->Front_Index % ring_buffer->Ring_Buffer_Size, i); /*copy valid data from initial address to end of the string.*/ if(i < len){ memcpy(buff+i, ring_buffer->ring_buff, len - i); } /*update the new front index*/ ring_buffer->Front_Index = (ring_buffer->Front_Index + len) % ring_buffer->Ring_Buffer_Size; } return len; } /* To check ring buffer is empty */ bool Ring_Buff_IsEmpty(ring_buff* ring_buffer) { return ring_buffer->Front_Index == ring_buffer->Rear_Index; }
上面的程序阐明了ringbuffer 的用法。在这里我们需要注意的是ringbuffer在构成上面也是一个数组,我们当数组快溢出的时候我们需要考虑如果把index在回归到合法的index中,此外就是尾指针和头指针之间的大小问题,这个在程序中都有体现,请看代码。
当我们构造了这个环形列表之后我们就可以当DMA触发的时候就可以从这里把数据给到DMA_buff里面。底下的程序表示的是当DMA中断发生之后,我们可以从ringbuffer里面查看还是否有没有输出的数据 如果有则把他取出来继续的输出 如果没有就返回到主程序。
/*This is for TX mode*/ void DMA1_Stream3_IRQHandler(void) { uint16_t len; if(DMA_GetITStatus(DMA1_Stream3, DMA_IT_TCIF3) == SET) { DMA_ClearITPendingBit(DMA1_Stream3, DMA_IT_TCIF3); len = Ring_Buff_Get(&ring_buff_tx, DMA_Buff, sizeof(DMA_Buff)); if(len) //if ring buffer is no empty, fetch data and transfer data. { DMA_Cmd(DMA1_Stream3, DISABLE); DMA_SetCurrDataCounter(DMA1_Stream3, len); DMA_Cmd(DMA1_Stream3,ENABLE); } } ring_buff_tx.Busy_Flag = DMA_TX_NOBUSY; }
下面的程序阐述了 printf 如何和ringbuff连接在一起
void send(char* buff, uint16_t len) { Ring_Buff_Put(&ring_buff_tx, (char*)buff, len); } /******************************************************************** Function Name: void putstr(char* str, bool Is_Empty) Description: put string in to ring buffer Input: 1. str: string 2. Is_Empty will check if the ring buffer is empty before new data put in Return: void Others: please not the DMA_TX_BUSY This is the lock between DMA interrupt and putstring. it is set in putstr function and clear at the DMA interrupt ********************************************************************/ void putstr(char* str, bool Is_Empty) { send(str, strlen(str)); if(Is_Empty) { if(ring_buff_tx.Busy_Flag == DMA_TX_NOBUSY){ //这里需要判定DMA此时是不是在忙。如果忙则等一等。 DMA_Cmd(DMA1_Stream3, DISABLE); Ring_Buff_Get(&ring_buff_tx, DMA_Buff, strlen(str)); DMA_SetCurrDataCounter(DMA1_Stream3, strlen(str)); DMA_Cmd(DMA1_Stream3,ENABLE); ring_buff_tx.Busy_Flag = DMA_TX_BUSY; } } } /******************************************************************** Function Name: void Dma_printf(const char* format, ...) Description: this function has two parts: 1. copy all the characters into ring buffer 2. if ring buffer is empty, enable the DMA to trigger at the first time Input: string want to print. Return: void Others: ********************************************************************/ void Dma_printf(const char* format, ...) { char buff[200] = {0}; va_list arg; va_start(arg, format); vsprintf(buff,format,arg); va_end(arg); putstr( buff, Ring_Buff_IsEmpty(&ring_buff_tx) ); //put the string into ring buffer. }