STM32 UART serial communication programming method

    When the communication time requirements are relatively high, you need to directly operate the communication bottom layer of the UART. Let me take the STM32 microcontroller as an example to talk about a relatively fast UART programming method. ——In fact, it is not only handled by STM32. I have used 51 microcontrollers, TI's MSP microcontrollers, and Mitsubishi's 16-bit microcontrollers before. This method can be used.

    The basic processing ideas are as follows:

    1. UART reception processing method

        Open the receive interrupt of the UART, put it into the receive buffer every time a byte is received, and update the receive pointer at the same time. When the received character is not received for 100ms in a row, it is considered that the current frame reception is completed, the frame reception completion flag is set, and the main program handles it.

    2. Processing method of UART transmission

        Put the data to be sent into the send buffer and set the send length. Then the first byte is sent, and the transmit interrupt is turned on. In the sending interrupt, it is judged whether the data of the specified length has been sent. If the transmission is not completed, continue to send; if the transmission is completed, close the transmission interrupt.

     The above methods are relatively simple to say, mainly for fault-tolerant processing and consideration of details. Below I take the STM32 microcontroller as an example to illustrate.

    1. Define the required variables

uint8_t gcRXDBuffer[50], gcRXDPointer, gcRXDLength; //Received buffer, receive pointer, received frame length
uint8_t gcTXDBuffer[50], gcTXDPointer, gcTXDLength; //Send buffer, send pointer, send length
uint8_t gcInRXDMode, gcInTXDMode; //Whether it is in the status flag of receiving or sending, it is required when it needs to switch to low power mode or close other functions

    2. Initialize the UART register. Take the LL library as an example, the HAL library can also be used. In fact, this part of the function can be generated by using STM32CUBEMX, you don't need to write it yourself.

/* USART2 init function */
static void MX_USART2_UART_Init(void)
{
  LL_USART_InitTypeDef USART_InitStruct;
  LL_GPIO_InitTypeDef GPIO_InitStruct;

  /* Peripheral clock enable */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
  
  /**USART2 GPIO Configuration  
  PA9   ------> USART2_TX
  PA10   ------> USART2_RX 
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_9;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LL_GPIO_PIN_10;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USART2 interrupt Init */
  NVIC_SetPriority(USART2_IRQn, 0);
  NVIC_EnableIRQ(USART2_IRQn);

  USART_InitStruct.BaudRate = 9600;
  USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
  USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
  USART_InitStruct.Parity = LL_USART_PARITY_NONE;
  USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
  USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
  USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
  LL_USART_Init(USART2, &USART_InitStruct);
  LL_USART_DisableOverrunDetect(USART2);
  LL_USART_ConfigAsyncMode(USART2);
  LL_USART_Enable(USART2);

}
    3. Increase the initialization by yourself, initialize the variables, and open the receive interrupt.
/************************************
*USART2 function without flow control ****************
*/
void uart2Init(void)
{
	gcRXDPointer=0;
	gcRXDLength=0;
	gcTXDPointer=0;
	gcTXDLength=0;
	gcInRXDMode=0;
	gcInTXDMode = 0;
	
	LL_USART_EnableIT_RXNE(USART2);
	//LL_USART_EnableIT_TXE(USART2);
}

    4. The above initialization is completed. Let's look at the programming method of the interrupt handler function.

/**
* @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26.
*/
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
	uint8_t ucTemp;
  /* USER CODE END USART2_IRQn 0 */
  /* USER CODE BEGIN USART2_IRQn 1 */
	if(LL_USART_IsActiveFlag_RXNE(USART2))
	{    //如果是接收中断
		gcUartCounter=0;    //全局变量,每次收到一个自己就清零,到100ms没有更新认为接收完成。       
		ucTemp=LL_USART_ReceiveData8(USART2);
		gcRXDBuffer[gcRXDPointer++]=ucTemp;    //接收到的数据放入缓冲区
		gcInRXDMode=1;                         //当前在接收模式
	}
	else if(LL_USART_IsActiveFlag_TXE(USART2))
	{     //如果是发送中断
		gcTXDPointer++;
		if(gcTXDPointer<gcTXDLength)
		{   //还没有发送完成,继续发送
			LL_USART_TransmitData8(USART2, gcTXDBuffer[gcTXDPointer]);
		}
		else
		{    //发送完成了,复位发送的变量
			gcTXDLength=0;
			gcTXDPointer=0;
			LL_USART_DisableIT_TXE(USART2);  //关闭发送中断
			gcInTXDMode=0;  //退出发送模式
			delayms(1);     //有流控时需要延时关闭流控,比如RTS,或者485中断的发送引脚。
			RTS_HIGH();
		}
	}
  /* USER CODE END USART2_IRQn 1 */
}

    5. 在系统的1ms SysTick中断中,判断是否100ms没有收到数据了。

	gcUartCounter++;   //这个变量在接收中断中不断清零
	if(gcUartCounter>=100)
	{   //100ms没有收到数据了,如果有数据,则打包帧
		gcUartCounter=100;
		gcInRXDMode=0;  //退出接收模式
		if(gcRXDPointer>=3)
		{  //根据协议长度,3是可以改动的。
			gcRXDLength=gcRXDPointer;  //将长度放入gcRXDLength,由主程序处理			gcRXDPointer=0;		}
	}

    6. 以上程序中,接收数据部分就完成了。在主程序,或主业务中,判断gcRXDLength就知道是否有数据需要处理。

    7. 在需要发送数据的时候:

/************************************
清空发送缓冲区的函数,需要重新组织发送时调用。*/
void TxdClearBuff(void)
{
	gcTXDPointer=0;
	gcTXDLength=0;
}
/************************************
如果需要多次组织数据,就一次次调用Push函数,将发送数据送入发送缓冲区。*/
void TxdPushToBuff(uint8_t *buffer, unsigned int length)
{
	
	memcpy(gcTXDBuffer+gcTXDLength, buffer, length);
	gcTXDLength+=length;
}
/************************************
组织完数据后,调用TxdSend,进行发送。*/
void TxdSend(void)
{
	LL_USART_TransmitData8(USART2, gcTXDBuffer[0]);
	gcTXDPointer=0;    
	LL_USART_EnableIT_TXE(USART2);   //打开发送中断。
	gcInTXDMode=1;                   //进入发送模式。
}

    以上发送需要使用3个函数,有些复杂。如果你一次就能将数据组织完成,就可以写简单点。

   在对实时处理要求更严格的时候,会在接收中断中直接处理帧头的判断(是否是正确的帧头,不是则接收指针直接清零),并根据帧长度字节,判断接收是否完成,然后直接调用通讯处理函数。这样的处理方法最快速,但封装不好,不易维护。不是必须的时候,不建议这么使用。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325346490&siteId=291194637