1. 程式人生 > >STM32 UART串口通訊編程方法

STM32 UART串口通訊編程方法

STM32 串口編程 uart

在對通訊時間要求比較高的時候,就需要自己對UART的通訊底層直接進行操作。我以STM32單片機為例,講一下比較快速的UART編程方法。——其實不止是STM32這麽處理,我以前使用過51的單片機,TI的MSP單片機,三菱的16位單片機,都可以采用這種方法。

基本的處理思路如下:

1. UART接收的處理方法

打開UART的接收中斷,每收到一個字節就放到接收緩沖區,同時更新接收指針。當連續100ms沒有收到接收字符,則認為本次幀接收完畢,置位幀接收完成標誌,由主程序進行處理。

2. UART發送的處理方法

將需要發送的數據放到發送緩沖區,設置發送長度。然後發送第一個字節,並打開發送中斷。在發送中斷中判斷是否已經發送了指定長度的數據。如果沒有發送完成,則繼續發送;發送完成,則關閉發送中斷。

以上方法,說起來比較簡單,主要是容錯的處理,以及細節的考慮。以下我以STM32單片機為例進行說明。

1. 定義需要的變量

uint8_t gcRXDBuffer[50], gcRXDPointer, gcRXDLength;    //接收的緩沖區、接收指針、接收的幀長度
uint8_t gcTXDBuffer[50], gcTXDPointer, gcTXDLength;    //發送的緩沖區,發送指針,發送的長度
uint8_t gcInRXDMode, gcInTXDMode;                //是否處於接收或發送的狀態標誌,在需要切入低功耗模式,或關閉其他功能時需要

2. 初始化UART寄存器,以LL庫為例,HAL庫也可以。其實這部分功能使用STM32CUBEMX自己生成就行,不用給自己編寫。

/* 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. 自己增加的初始化,初始化變量,並打開接收中斷。
/************************************
*不帶流控的USART2函數****************
*/
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. 以上的初始化就完成了,下面看中斷處理函數的編程方法。

/**
* @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個函數,有些復雜。如果你一次就能將數據組織完成,就可以寫簡單點。

在對實時處理要求更嚴格的時候,會在接收中斷中直接處理幀頭的判斷(是否是正確的幀頭,不是則接收指針直接清零),並根據幀長度字節,判斷接收是否完成,然後直接調用通訊處理函數。這樣的處理方法最快速,但封裝不好,不易維護。不是必須的時候,不建議這麽使用。


STM32 UART串口通訊編程方法