小白學習STM32(四)(基於cubemx+MDK)串列埠通訊(查詢+中斷模式)

2020-08-12 16:09:01

UART串列埠通訊

序列介面相關知識點

並行通訊、序列通訊的概念。
單工、半雙工、全雙工的概念。
非同步序列通訊:通訊雙方在沒有同步時鐘的前提下,將一個字元(包括特定的附加位)按位元進行傳輸的通訊方式。
波特率:每秒鐘傳輸的二進制位數,如9600bps。
TTL電平<—->RS232:MAX3232 SP3232
串列埠<———>USB介面:CH340 CP2012

STM32晶片的串列埠UASRT功能十分強大,但對於日常程式設計而言,使用最多的還是非同步序列通訊。
串列埠1:USART1_TXPA9複用,USART1_RXPA10複用。
串列埠2:USART2_TXPA2複用,USART2_RXPA3複用。

HAL庫中串列埠發送的重要函數

////查詢方式,阻塞式發送函數(初學者,推薦使用)(就是必須等數據傳送完後微控制器纔去做其他事)
HAL_StatusTypeDef  HAL_UART_Transmit(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size, uint32_t Timeout);

參數1:UATR的別名    如 :   UART_HandleTypeDef huart1;   別名就是huart1  
參數2*pData,待發送數據緩衝區的指針。
參數3:Size,待發送數據的位元組數。
參數4:Timeout,超時時間值。
返回值:HAL_StatusTypeDef,函數執行狀態。
舉例:   HAL_UART_Transmit(&huart1, (uint8_t *)data, 3, 0xffff);   //串列埠1發送三個位元組數據,最大傳輸時間0xffff
typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;
----------------------------------------------------------------------------
////中斷方式,非阻塞式發送函數
HAL_StatusTypeDef  HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size);

參數1:UATR的別名    如 :   UART_HandleTypeDef huart1;   別名就是huart1 
參數2*pData,待發送數據緩衝區的指針。
參數3:Size,待發送數據的位元組數。
返回值:HAL_StatusTypeDef,函數執行狀態。
舉例: HAL_UART_Transmit_IT(&huart1,(uint8_t *)data, 3);//向串列埠1發送三個位元組數的數據

////串列埠發送完畢中斷回撥函數  
void HAL_UART_TxCpltCallback(UART_HandleTypeDef  *huart); //串列埠發送中斷回撥函數
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef  *huart)//串列埠發送一半中斷回撥函數(不常用)
應用舉例:使用非阻塞式的串列埠發送函數,將發送緩陣列dat_Txd中的前5個數據發送到USART1,在數據發送完成後,翻轉PB9引腳的輸出電平。   
//使用中斷,非阻塞方式 
HAL_UART_Transmit_IT(&huart1,  dat_Txd, 5);

void HAL_UART_TxCpltCallback(UART_HandleTypeDef  *huart(換成用到的串列埠,例如&huart1)) 
{         
    if(huart->Instance == USART1);         
    {             
        HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);    
    }
}

 
//使用查詢,阻塞方式 
HAL_UART_Transmit(&huart1,  dat_Txd, 5, 10000);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);

HAL庫中串列埠接收的重要函數

/// 查詢方式,阻塞式接受函數
HAL_StatusTypeDef  HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData,  uint16_t Size, uint32_t Timeout);
參數1:huart,串列埠範例的指針。
參數2*pData,數據接收據緩衝區的指針。
參數3:Size,待接收數據的位元組數。
參數4:Timeout,超時時間值。
返回值:HAL_StatusTypeDef,函數執行狀態。
----------------------------------------------------------------------------
非阻塞式接收函數(推薦使用)
功能:串列埠中斷接收,以中斷方式接收指定長度數據。
大致過程是,設定數據存放位置,接收數據長度,然後使能串列埠接收中斷。接收到數據時,會觸發串列埠中斷。再然後,串列埠中斷函數處理,直到接收到指定長度數據,而後關閉中斷,進入中斷接收回撥函數,不再觸發接收中斷。(只觸發一次中斷)
HAL_StatusTypeDef  HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size);

參數1:huart,串列埠範例的指針。
參數2*pData,數據接收據緩衝區的指針。
參數3:Size,待接收數據的位元組數。
返回值:HAL_StatusTypeDef,函數執行狀態。
HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1);   //中斷接收一個字元,儲存到value中

////串列埠接收完畢中斷回撥函數
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);//串列埠接收中斷回撥函數
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef  *huart);//串列埠接收一半回撥函數(不常用)
應用舉例:使用非阻塞式的串列埠接收函數,接收USART1中的一個位元組,將其儲存在dat_Rxd變數中,在數據接收完成後,判斷該位元組,若爲0x5A,則翻轉PB8引腳的輸出電平。
//使用中斷,非阻塞方式
HAL_UART_Receive_IT(&huart1,  &dat_Rxd, 1);
  
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart(換成用到的串列埠,例如&huart1))  
{        
    if(huart->Instance == USART1)    
    {       
        if(dat_Rxd == 0x5A)      
         HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
    }
}

串列埠中斷處理常式

HAL_UART_IRQHandler(UART_HandleTypeDef *huart); 
參數:UATR的別名    如 :   UART_HandleTypeDef huart1;   別名就是huart1

功能:對接收到的數據進行判斷和處理 判斷是發送中斷還是接收中斷,然後進行數據的發送和接收,在中斷服務函數中使用

如果接收數據,則會進行接收中斷處理常式

  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    UART_Receive_IT(huart);
  }

如果發送數據,則會進行發送中斷處理常式

if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }

串列埠查詢函數

HAL_UART_GetState();  判斷UART的接收是否結束,或者發送數據是否忙碌
while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)   //檢測UART發送結束

串列埠設定的一般步驟可以總結爲如下幾個步驟:

  1. 串列埠時鐘使能,GPIO時鐘使能。
  2. 設定引腳複用器對映:呼叫GPIO_PinAFConfig函數。
  3. GPIO初始化設定:要設定模式爲複用功能。
  4. 串列埠參數初始化:設定波特率,字長,奇偶校驗等參數。
  5. 開啓中斷並且初始化NVIC,使能中斷(如果需要開啓中斷才需要這個步驟)。
  6. 使能串列埠。
  7. 編寫中斷處理常式:函數名格式爲USARTxIRQHandler(x對應串列埠號)。

CUBEMX設定

CUBEMX中的基本設定例如時鐘啊之類的和之前的都是一樣的,唯一的不同就是在串列埠這裏,如下圖所示,設定好就行了

image-20200812160221324

重定義printf函數

功能描述:USART1收到PC機發來的數據後原封不動的返回給PC機顯示,但是C語言中printf函數預設輸出裝置是顯示器,要使用printf輸出到串列埠,需要將fputc裏面的輸出指向串列埠,這一過程就叫重定向

需要設定的只有USART1,設定過程非常簡單。

第一步:在 stm32f4xx_hal.c中包含#include <stdio.h>,同時宣告外部變數huart1

image-20200811141709794

第二步:在在 stm32f4xx_hal.c 中重寫fget和fput函數

image-20200811142100665

第三步:在main.c中新增

#define RXBUFFRSIZE 256
char RxBuffer[RXBUFFRSIZE];

然後就是測試了:在while(1)中新增

image-20200811143602054

最後得到的效果就這這樣,可以使用printf scanf getchar 函數

雖然使用printf函數很方便,但是我還是認爲需要深入理解串列埠需要用到HAL庫的底層函數,所以接下來我將使用HAL庫的函數進行學習

HAL庫串列埠函數使用方法

例程1:不使用中斷 使用查詢模式接受和發送數據

用到的函數

HAL_UART_Transmit(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size, uint32_t Timeout);

HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

部分需要修改的main.c

#include "main.h"
#include "usart.h"
#include "gpio.h"
char str[] = "USart1 is initializing.....\r\n";
char str1[] = "initialization is OK\r\n";
char data[12];
void SystemClock_Config(void);
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	if(HAL_OK == HAL_UART_Transmit(&huart1, (uint8_t*)str, sizeof(str), 0xFFFF)) //顯示初始化是否成功
	{
		HAL_Delay(1000);
		HAL_UART_Transmit(&huart1, (uint8_t*)str1, sizeof(str1), 0xFFFF); //成功標誌
	}
  while (1)
  {
	if (HAL_OK == HAL_UART_Receive(&huart1, (uint8_t*)data, 12, 0xFFFF)) //如果成功接收到了data,則將data輸出
	{
		HAL_UART_Transmit(&huart1, (uint8_t*)data, 12, 0xFFFF);
	}
		HAL_Delay(100);
  }
}

上述程式碼,在while(1),裡如果接受函數沒有接收到函數,則微控制器不會去做其他事,這顯然對專案設計來說是及其不方便的。故介紹第二種中斷方法

例程2:中斷方式,使用中斷更加高效的利用微控制器資源

因爲中斷接收和發送函數只能觸發一次接收中斷,所以我們需要在中斷回撥函數中再呼叫一次中斷接收和發送函數

具體流程:

1、初始化串列埠

2、在main中第一次呼叫接收中斷函數

3、進入接收中斷,接收完數據 進入中斷回撥函數

4、修改HAL_UART_RxCpltCallback中斷回撥函數,處理接收的數據,

5 回撥函數中要呼叫一次HAL_UART_Receive_IT和HAL_UART_Receive_IT函數,使得程式可以重新觸發接收和發送中斷

需要部分修改的main.c

#include "main.h"
#include "usart.h"
#include "gpio.h"
#define LED1_ON() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET)
#define LED1_OFF() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET)
#define LED2_ON() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET)
#define LED2_OFF() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET)
#define RXBUFFERSIZE  256     //最大接收位元組數
char RxBuffer[RXBUFFERSIZE];   //接收數據
uint8_t aRxBuffer;			//接收中斷緩衝
uint8_t Uart1_Rx_Cnt = 0;		//接收緩衝計數

  /* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
  /* USER CODE END 2 */c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
 
	if(Uart1_Rx_Cnt >= 255)  //溢位判斷
	{
		Uart1_Rx_Cnt = 0;
		memset(RxBuffer,0x00,sizeof(RxBuffer));
		HAL_UART_Transmit(&huart1, (uint8_t *)"數據溢位", 10,0xFFFF); 	
        
	}
	else
	{
		RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收數據轉存
	
		if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判斷結束位
		{
			HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //將收到的資訊發送出去
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//檢測UART發送結束
			Uart1_Rx_Cnt = 0;
			memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空陣列
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再開啓接收中斷
}

以上就是中斷模式的串列埠了(程式碼部分參考z小璇大神(https://blog.csdn.net/as480133937/article/details/99073783)的程式碼,小白的程式碼無法做到不定長接受和發送。