Based on STM32F4, the data sending and receiving processing of serial port 1+DMA interrupt+circular queue is realized

Based on STM32F4, the data sending and receiving processing of serial port 1+DMA interrupt+circular queue is realized

Introduction to usage

The serial port plus ring queue basically satisfies the data sending and receiving processing of ordinary single-chip microcomputers, and it is simple and reliable to implement. However, in actual projects, multiple serial ports will be used in conjunction and it is hoped that the data sending and receiving process will not take up too much CPU time. At this time, DMA will be very useful.
Recently, when working on a project, the 6-way serial port of STM32F407 was used as the interface of 485 communication. Due to too many communication interruptions, the coupling of sending and receiving was caused, and there was a compatibility problem with the external 485 automatic sending and receiving circuit. The external 485 circuit would cause data transmission and reception at both ends. The data is abnormal, so that the MCU handles the wrong data and causes a series of problems. Therefore, DMA is used this time to reduce the processing time of interrupts and data in interrupts.

overall plan

The overall plan is: open the serial port 1 of STM32F4 to send and receive DMA function, receive and use the DMA transmission completion interrupt function to push each byte into the ring queue. The DMA transmission of data is realized in the main function process, and the DMA transmission completion queue realizes the clearing of the interrupt flag.
Check the reference manual and select channel 4 of data stream 5/7 of DMA2.

串口1RX
接收DMA
缓存字节
接收队列
应用程序1
应用程序2
发送队列
发送缓存
发送DMA
串口1TX

Implementation of a circular queue

In order to facilitate porting, it is encapsulated in the form of C++, and the definition of the class is as follows:

class CQueue   
{
    
    
	public:
	CQueue(uint8_t *puch_DATA, uint16_t uin_BufLen);
		CQueue(){
    
    };
		~CQueue(){
    
    };	
		
		//data
public:
		BOOL   b_ModSetEn;
		INT32U uin_front;//队首
		INT32U uin_rear;//队尾
		INT32U uin_length;//数据长度
		INT32U uin_MaxLength;//

		INT8U  *queueBuf;//队列数组地址			

private:
		INT16U ul_ErrorChk;	//异常记录				
				
		//Function
public:
		BOOL IsEmpty(void);//检查是否为空队列
		BOOL IsFull(void);//检查是否为满队列 
		BOOL PopData(INT8U * BUF);//出队操作
		BOOL PushData(INT8U DATA);// 入队操作
		BOOL SetQueue(uint8_t *puch_DATA, uint16_t uin_BufLen);// 队列设置

};

Constructor:

CQueue::CQueue(uint8_t *puch_DATA, uint16_t uin_BufLen)// 
{
    
    
		queueBuf = puch_DATA;//队列数组地址	
		uin_length = 0;//数据长度
		uin_MaxLength =uin_BufLen;//数据长度
		uin_front = uin_MaxLength-1;//队首
		uin_rear = uin_MaxLength-1;//队尾
	
		memset(puch_DATA,0,uin_MaxLength);
}

Enqueue operation:

BOOL CQueue::PushData(INT8U DATA)//入队操作
{
    
    
	if(IsFull()) return false;
    queueBuf[uin_rear] = DATA;
	uin_rear = (uin_rear +1 )% uin_MaxLength;//转圈圈起来
	uin_length++;
	return true;
}	

Dequeue operation:

BOOL CQueue::PopData(INT8U * BUF)//出队操作
{
    
    
	if(IsEmpty()) return false;
	*BUF = queueBuf[ uin_front ] ;
	uin_front = (uin_front +1 )% uin_MaxLength;//转圈圈起来
	uin_length--;
	return true;
}

I used the simplest method, and there are many on the Internet, just refer to it.

Serial port configuration

Use serial port 1 for configuration, and the baud rate can be set.

void UART1_DMAConfig(INT32U Buart)
{
    
    
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;

  RCC_AHB1PeriphClockCmd(PC_USART_RX_GPIO_CLK | PC_USART_TX_GPIO_CLK, ENABLE);
  /* 使能 USART 时钟 */
  RCC_APB2PeriphClockCmd(PC_USART_CLK, ENABLE);
  /* GPIO初始化 */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  /* 配置Tx引脚为复用功能  */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = PC_USART_TX_PIN;
  GPIO_Init(PC_USART_TX_GPIO_PORT, &GPIO_InitStructure);
  /* 配置Rx引脚为复用功能 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = PC_USART_RX_PIN;
  GPIO_Init(PC_USART_RX_GPIO_PORT, &GPIO_InitStructure);

  /* 连接 PXx 到 USARTx_Tx*/
  GPIO_PinAFConfig(PC_USART_RX_GPIO_PORT, PC_USART_RX_SOURCE, PC_USART_RX_AF);
  /*  连接 PXx 到 USARTx__Rx*/
  GPIO_PinAFConfig(PC_USART_TX_GPIO_PORT, PC_USART_TX_SOURCE, PC_USART_TX_AF);

  /* 配置串PC_USART 模式 */
  /* 波特率设置:PC_USART_BAUDRATE */
  USART_InitStructure.USART_BaudRate = Buart;
  /* 字长(数据位+校验位):8 */
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  /* 停止位:1个停止位 */
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  /* 校验位选择:不使用校验 */
  USART_InitStructure.USART_Parity = USART_Parity_No;
  /* 硬件流控制:不使用硬件流 */
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  /* USART模式控制:同时使能接收和发送 */
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  /* 完成USART初始化配置 */
  USART_Init(PC_USART, &USART_InitStructure);

  /* 使能串口接收中断 */
  UART_DMA_RxConfig(DMA2_Stream5,DMA_Channel_4,(uint32_t)&USART1->DR,(uint32_t)&auch_RxData,1);          
  UART_DMA_TxConfig(DMA2_Stream7,DMA_Channel_4,(uint32_t)(&USART1->DR),(uint32_t)auch_TxData,1);
  /* 使能串口收发DMA */
  USART_DMACmd(USART1, USART_DMAReq_Rx , ENABLE);                                       //串口DMA开启
  USART_DMACmd(USART1, USART_DMAReq_Tx , ENABLE);                                       //串口DMA开启	
  /* 使能串口 */
  USART_Cmd(PC_USART, ENABLE);
}

The configuration of the serial port is very conventional, just copy a section from the wildfire tutorial. There are also some macro definitions below:

#define PC_USART                             USART1
#define PC_USART_CLK                         RCC_APB2Periph_USART1
#define PC_USART_BAUDRATE                    115200  //串口波特率

#define PC_USART_RX_GPIO_PORT                GPIOA
#define PC_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define PC_USART_RX_PIN                      GPIO_Pin_10
#define PC_USART_RX_AF                       GPIO_AF_USART1
#define PC_USART_RX_SOURCE                   GPIO_PinSource10

#define PC_USART_TX_GPIO_PORT                GPIOA
#define PC_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define PC_USART_TX_PIN                      GPIO_Pin_9
#define PC_USART_TX_AF                       GPIO_AF_USART1
#define PC_USART_TX_SOURCE                   GPIO_PinSource9

#define PC_USART_IRQHandler                  USART1_IRQHandler
#define PC_USART_IRQ                 				USART1_IRQn

#define DMA_USART_RX_IRQ 						 DMA2_Stream5_IRQn
#define DMA_USART_Rx_IRQHandler                  DMA2_Stream5_IRQHandler
#define UART_RX_DMA_STREAM						DMA2_Stream5
#define UART_RX_DMA_CHANNEL						DMA_Channel_4

#define DMA_USART_TX_IRQ 						DMA2_Stream7_IRQn
#define DMA_USART_Tx_IRQHandler                 DMA2_Stream7_IRQHandler
#define UART_TX_DMA_STREAM						DMA2_Stream7
#define UART_TX_DMA_CHANNEL						DMA_Channel_4

Serial port receiving DMA configuration

The receiving DMA can be interrupted by the transmission completion. The DMA_IT_TCIF5
insert image description here
specific configuration code of the interrupt flag UART_DMA_RxConfig(DMA2_Stream5,DMA_Channel_4,(uint32_t)&USART1->DR,(uint32_t)&auch_RxData,1)is as follows:

void UART_DMA_RxConfig(DMA_Stream_TypeDef *DMA_Streamx,u32 channel,u32 addr_Peripherals,u32 addr_Mem,u16 Length)                                       //DMA的初始化
{
    
    
	NVIC_InitTypeDef NVIC_InitStructure;
	DMA_InitTypeDef UARTDMA_InitStructure;

	if((u32)DMA_Streamx>(u32)DMA2)//DMA1?DNA2
	{
    
    
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 	
	}
	else 
	{
    
    
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1
	}                 

  DMA_ClearFlag(UART_RX_DMA_STREAM, DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5 | DMA_FLAG_TCIF5);
                                                                                                             
  /* DMA2 Stream5  or Stream6 disable */
  DMA_Cmd(UART_RX_DMA_STREAM, DISABLE);                                                     
  while (DMA_GetCmdStatus(UART_TX_DMA_STREAM) != DISABLE){
    
    }//保证DMA可设置
  /* DMA2 Stream3  or Stream6 Config */
  DMA_DeInit(UART_RX_DMA_STREAM);

  UARTDMA_InitStructure.DMA_Channel = channel;//通道
  UARTDMA_InitStructure.DMA_PeripheralBaseAddr = ( uint32_t)(addr_Peripherals) ;//外设地址
  UARTDMA_InitStructure.DMA_Memory0BaseAddr =   ( uint32_t)addr_Mem; //       缓存地址
  UARTDMA_InitStructure.DMA_DIR =  DMA_DIR_PeripheralToMemory;//方向-外设到内存
  UARTDMA_InitStructure.DMA_BufferSize = Length; //传输长度
  UARTDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  UARTDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  UARTDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte ;
  UARTDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ;//以字节8位方式传输
  UARTDMA_InitStructure.DMA_Mode = DMA_Mode_Normal   ;//传输模式正常模式
  UARTDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  UARTDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  UARTDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  UARTDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ;
  UARTDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(UART_RX_DMA_STREAM, &UARTDMA_InitStructure);

  NVIC_InitStructure.NVIC_IRQChannel = DMA_USART_RX_IRQ ;                                                //中断分组
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  DMA_ITConfig(UART_RX_DMA_STREAM, DMA_IT_TC, ENABLE);//开启传输完成中断触发   //DMA中断时能
  /* DMA2 Stream3  or Stream6 enable */
  USART_DMACmd(USART1, USART_DMAReq_Rx , ENABLE);      //串口DMA开启
  DMA_Cmd(UART_RX_DMA_STREAM, ENABLE);     //DMA开启  
}

The point to pay attention to is that the DMA data flow and channel must be confirmed correctly. The addresses of the peripherals and memory passed in are 32 bits. The transmission word length, the auto-increment of peripherals and memory, and the opening flag of the interrupt are also key.
The receiving interrupt is as follows. Entering the transmission interrupt proves that the global variable auch_RxDatahas obtained a byte of data, and then judges the queue status, pushes the data into the queue, clears the interrupt flag and re-enables the DMA channel.

void DMA2_Stream5_IRQHandler()
{
    
    
  if(DMA_GetITStatus(UART_RX_DMA_STREAM  , DMA_IT_TCIF5)) 
  {
    
    
    if (!Uart1RxQueue.IsFull()) //若缓冲队列未满,开始传输
      {
    
    
        //往缓冲区写入数据,如使用串口接收、dma写入等方式
         Uart1RxQueue.PushData(auch_RxData);
      }
      else
      {
    
    
        return ;
      }

    DMA_ClearITPendingBit(UART_RX_DMA_STREAM , DMA_IT_TCIF5);  //清中断标志
	DMA_SetCurrDataCounter(UART_RX_DMA_STREAM, 1);//设置数据传输长度
	DMA_Cmd(UART_RX_DMA_STREAM, ENABLE);  //重新使能DMA通道
  }
  return ;
}

The interrupt is cleared here because of the requirements in the manual
insert image description here

insert image description here

insert image description here
Rewriting the transfer length and enabling is also a practical requirement for the register:
insert image description here

insert image description here

insert image description here
The actual measurement here only needs to enable the channel, which is consistent with the description of the data item register. Or do not re-enable here, just change the transmission mode to cycle modeUARTDMA_InitStructure.DMA_Mode = DMA_Mode_Circular

Serial port send DMA configuration

When sending, the configuration should be divided into two parts, the configuration part and the sending enable part, because once it is enabled, the sending will start.
The configuration part is as follows:
first define some global variables

static INT8U auch_TxData[QUEUEMAX];//DMA发送部分操作的缓存
DMA_InitTypeDef UARTDMA_TXInitStructure;//DMA发送设置结构体 设置成全局方便直接修改
void UART_DMA_TxConfig(DMA_Stream_TypeDef *DMA_Streamx,u32 channel,u32 addr_Peripherals,u32 addr_Mem,u16 Length)                                       //DMA的初始化,省略串口初始化
{
    
    
NVIC_InitTypeDef NVIC_InitStructure;
//DMA_InitTypeDef UARTDMA_TXInitStructure;//这个要设置成全局的单独定义,为发送使能使用
if((u32)DMA_Streamx>(u32)DMA2)//DMA1?DNA2
{
    
    
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 	
}
else 
{
    
    
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1
}               
  DMA_ClearFlag(UART_TX_DMA_STREAM, DMA_FLAG_FEIF7 | DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7 | DMA_FLAG_TCIF7);
                                                                                                             
  /* DMA2 Stream5  or Stream6 disable */
  DMA_Cmd(UART_TX_DMA_STREAM, DISABLE);                                                     

  /* DMA2 Stream3  or Stream6 Config */
  DMA_DeInit(UART_TX_DMA_STREAM);
  while (DMA_GetCmdStatus(UART_TX_DMA_STREAM) != DISABLE){
    
    }//µÈ´ýDMA¿ÉÅäÖà 

  UARTDMA_TXInitStructure.DMA_Channel = channel;
  UARTDMA_TXInitStructure.DMA_PeripheralBaseAddr = ( uint32_t)(addr_Peripherals) ;
  UARTDMA_TXInitStructure.DMA_Memory0BaseAddr =   (uint32_t)(addr_Mem); // 
  UARTDMA_TXInitStructure.DMA_DIR =  DMA_DIR_MemoryToPeripheral;
  UARTDMA_TXInitStructure.DMA_BufferSize = Length; 
  UARTDMA_TXInitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  UARTDMA_TXInitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  UARTDMA_TXInitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte ;
  UARTDMA_TXInitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ;
  UARTDMA_TXInitStructure.DMA_Mode = DMA_Mode_Normal   ;
  UARTDMA_TXInitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  UARTDMA_TXInitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  UARTDMA_TXInitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  UARTDMA_TXInitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ;
  UARTDMA_TXInitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(UART_TX_DMA_STREAM, &UARTDMA_TXInitStructure);

  NVIC_InitStructure.NVIC_IRQChannel = DMA_USART_TX_IRQ ;                                                //中断分组
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  DMA_ITConfig(UART_TX_DMA_STREAM, DMA_IT_TC, ENABLE);                                           //DMA中断时能
//  DMA_Cmd(UART_TX_DMA_STREAM, ENABLE);                                                                     //DMA开启  
}

Send enable part:
This part is connected with the data pop-up of the ring queue. When sending data, push the data into the queue from other programs, and then call this part (for reference only, other projects can send data directly) . When all the data is popped into the defined global array auch_TxData[uin_cnt++], assign the transfer length and address to the DMA channel, and then turn it on for automatic transfer.

void Uart1_DMASendEnable()
{
    
    
  static uint16_t uin_cnt=0;
  while(Uart1TxQueue.IsEmpty() != 1) //若缓冲队列非空
  {
    
    
    //从缓冲区弹出字节
     Uart1TxQueue.PopData(&auch_TxData[uin_cnt++]);
  }
  {
    
    
    DMA_Cmd(UART_TX_DMA_STREAM,DISABLE);   //关闭
    UARTDMA_TXInitStructure.DMA_BufferSize = uin_cnt; //数据长度
    UARTDMA_TXInitStructure.DMA_Memory0BaseAddr =(uint32_t)(auch_TxData);//内存地址
    DMA_Init(UART_TX_DMA_STREAM, &UARTDMA_TXInitStructure);//变量写入寄存器
    DMA_Cmd(UART_TX_DMA_STREAM,ENABLE);//使能打开
    uin_cnt =0;  //长度清零
  }
  return ; 
}

After the transmission is completed, it will enter the interrupt:
only the interrupt flag is cleared in the interrupt to prepare for the next interrupt. This part can only be cleared by software, and the next interrupt cannot be entered without clearing.

void DMA2_Stream7_IRQHandler()
{
    
    
	if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=0)//必须软件清除中断才能正常下次发送
	{
    
    
		DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);
	}
}

Precautions

  1. DMA data flow and channel must be accurate
  2. Transceiver interrupt data processing and flag bit clearing
  3. For some special usage, you need to check the register manual more. It is best to check the performance of the register while debugging online and compare the usage instructions of the register.
  4. When configuring, the address parameters of peripherals and caches must be correct.

Summarize

This time, the DMA transceiver is interrupted. You can also try to use a separate receiving or sending interrupt in the project, and use it in combination. The actual test of this kind of interrupt DMA receiving and serial port interrupt receiving frame loss and rate is not much different. At the same time, if the serial port interrupts too much to send, you can use the query to send, and the effect of directly writing the data into the send data register is great. You can also try other methods on the Internet to combine serial port interrupts. The corresponding code can be downloaded here, https://download.csdn.net/download/weixin_43058521/85045191

Guess you like

Origin blog.csdn.net/weixin_43058521/article/details/123778749