STM32學習筆記(6)——USART串口通信


一、基礎知識

本節主要寫一下通信的一些基礎知識,簡單過一遍,防忘。

1. 通信基本知識

(1)數據傳送方式

分類:串行和並行。


(2)數據通信方向

分類:全雙工、半雙工和單工。

(3)數據同步方式

分類:同步和異步。

(4)通信速率

比特率(Bitrate):每秒鍾傳輸的二進制位數,單位為比特每秒(bit/s)。

波特率(Baudrate):表示每秒鍾傳輸的碼元個數,單位為波特(B),沒有每秒!(常用的波特率:4800、9600、19200、38400、43000、56000、57600、115200)

碼元:舉個例子,當我們用一個二進制數(0或1)表示信息時,0或1為一個碼元,它只有一個位數;當用兩個二進制數(00,01,10,11)表示信息時,譬如01,就是一個碼元,它有兩個位數。

2. 串口通信協議

(1)物理層

物理層:規定通訊系統中具有機械、電子功能部分的特性,確保原始數據在物理媒體的傳輸。其實就是硬件部分

RS-232標准

RS232標准串口主要用於工業設備直接通信。電平轉換芯片一般有MAX3232,SP3232。

TTL電平標准是5V表示邏輯1,0V表示邏輯0;而為了增加串口通信的遠距離傳輸以及抗干擾能力,RS-232使用-15V表示邏輯1,+15V表示邏輯0。

信號通過DB9串口連接器傳輸入設備,使用的是RS-232標准電平,因此還有電平轉換芯片將其轉為TTL電平。

USB轉串口

在這里插入圖片描述

USB轉串口主要用於設備跟電腦通信,電平轉換芯片一般有CH340等,所以電腦要安裝CH340電平轉換芯片的驅動程序(之后我們會用到)。

原生串口轉串口

原生的串口通信主要是控制器跟串口的設備或者傳感器通信,不需要經過電平轉換芯片來轉換電平,直接用TTL電平通信。

(2)協議層

串口通信一般是以格式傳輸數據,即一幀一幀傳輸,每幀包含有起始信號、數據信息、停止信息,可能還有校驗信息。

串口數據包的組成:

  • 起始位:1個邏輯0表示。
  • 有效數據:在起始位后緊接着的就是有效數據,有效數據的長度常被約定為5、 6 、 7 或 8 位長。
  • 校驗位:用於數據的抗干擾性。

五種校驗方法:

奇校驗(odd,有效數據和校驗位中“ 1”的個數為奇數)

偶校驗(even,有效數據和校驗位中“ 1”的個數為偶數)

0校驗(space,校驗位總為0)

1校驗(mark,校驗位總為1)

無校驗(noparity,不包含校驗位)

  • 結束位:由 0.5、 1、 1.5 或 2 個邏輯1的數據位表示。

二、USART串口通信詳解

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)為通用同步異步收發器,支持同步單向通信和半雙工單線通信。還有一種“閹割版”——UART(Universal Asynchronous Receiver/Transmitter),去掉了同步,為異步收發器。

【以下內容來自STM32中文參考手冊25章通用同步異步收發器(USART)】USART 滿足外部設備對工業標准NRZ異步串行數據格式的要求,並且使用了小數波特率發生器,可以提供多種波特率,使得它的應用更加廣泛。USART支持同步單向通信和半雙工單線通信;還支持局域互連網絡 LIN、智能卡(SmartCard)協議與lrDA(紅外線數據協會) SIR ENDEC規范。USART支持使用DMA,可實現高速數據通信。

1. USART功能框圖

本節內容可參考STM32中文參考手冊25.3節USART功能概述。我們將框圖分為4個部分進行講解。

(1)引腳

  • TX:發送數據輸出
  • RX:接收數據串行輸入
  • SCLK(位於最右邊):發送器時鍾輸出,僅同步通信時使用
  • nRTS:請求發送(Request To Send)
  • nCTS:允許發送(Clear To Send)
  • SW_RX:數據接收引腳,屬於內部引腳。

引腳對應的編號如下:

(2)數據寄存器

數據寄存器(USART_DR)只有低9位有效,實際上它包含一個發送數據寄存器USART_TDR和一個接收數據寄存器USART_RDR。TDR和RDR都是介於系統總線和移位寄存器之間。這里比較特別:一個地址對應了兩個物理內存

當進行發送操作時,往USART_DR寫入數據會自動存儲在 TDR內,然后把內容轉移到發送移位寄存器,最后通過模塊發送到TX引腳;

當進行讀取操作時,信息從RX引腳進入,通過模塊后存入接受移位寄存器,然后把內容轉移到RDR內,最后USART_DR提取RDR數據。

(3)控制器

本部分內容可參考STM32中文參考手冊25.6節USART寄存器描述。

USART有專門控制發送的發送器、控制接收的接收器,還有喚醒單元、中斷控制等。

控制寄存器1(USART_CR1)的TE位負責使能發送器,發送器就會“叫醒”發送移位寄存器。

控制寄存器1(USART_CR1)的RX位負責使能接收器,接收器就會“叫醒”接收移位寄存器。

控制寄存器1的TXEIE或RXNEIE置1可以產生中斷。

控制寄存器1其他位:TXE:發送移位寄存器為空,發送單個字節時使用。TC:發送完成,發送多個字節數據時候使用。TXIE:發送完成中斷使能。

控制寄存器1的M位設置的是字長,該位定義了數據字的長度,由軟件對其設置和清零。設置0:一個起始位,8個數據位;設置1:一個起始位,9個數據位。之前已經說過,數據寄存器(USART_DR)只有低9位有效,並且第9位數據是否有效要取決於控制寄存器1的M位設置。

控制寄存器2(USART_CR2)的STOP[1:0]位用於設置停止位,可選0.5個、1個、1.5個、2個停止位。默認使用1個停止位。2個停止位適用於正常USART模式、單線模式和調制解調器模式。0.5和1.5個停止位用於智能卡模式。

(4)波特率

通過波特率寄存器(USART_BRR)可設置波特率。由於計算出的分頻因子有小數,因此寄存器分為兩個部分:

DIV_Mantissa[11:0]:USARTDIV的整數部分,這12位定義了USART分頻器除法因子(USARTDIV)的整數部分。

DIV_Fraction[3:0]:USARTDIV的小數部分,這4位定義了USART分頻器除法因子(USARTDIV)的小數部分。

波特比率的計算公式為:

fCK:串口的時鍾,需要注意不同USART有不同AHB時鍾頻率。

USARTDIV:分頻器除法因子,為無符號的定點數,最后為寫入寄存器的值。

例如設置波特率為115200的計算過程:

使用十進制小數轉換為二進制小數的方法進行。

2. USART的結構體定義和相關庫函數

本節內容位於頭文件stm32f10x_usart.h中。

USART的初始化結構體定義:

typedef struct
{
    //波特率設置
  uint32_t USART_BaudRate;          
    //字長設置
  uint16_t USART_WordLength; 
    //停止位設置
  uint16_t USART_StopBits;
    //校驗位設置
  uint16_t USART_Parity;             
    //USART模式設置
  uint16_t USART_Mode;               
    //硬件流控制選擇
  uint16_t USART_HardwareFlowControl; 
} USART_InitTypeDef;

USART的相關常用且重要庫函數(可查看庫函數手冊):

void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

3. 配置USART的流程

void USART_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	// 1. 開啟USART時鍾和對應的引腳時鍾
	// USART1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// 2. 配置USART引腳
	// USART1-TX發送 GPIOA.9 復用推挽模式
	GPIO_InitStructure.GPIO_Pin = USART1_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStructure);
	
	// USART1-RX接收 GPIOA.10 浮空輸入模式
	GPIO_InitStructure.GPIO_Pin = USART1_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 3. 配置USART1串口參數
	USART_InitStructure.USART_BaudRate = USART_BAUDRATE;			// 3.1 配置波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		// 3.2 配置數據字長
	USART_InitStructure.USART_StopBits = USART_StopBits_1;			// 3.3 配置停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;				// 3.4 配置校驗位
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	// 3.5 配置工作模式
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  // 3.6 配置硬件控制流
	
	USART_Init(USART1, &USART_InitStructure);						// 4. 初始化串口
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//USART中斷使能
	USART_Cmd(USART1, ENABLE);	 									// 5. 使能串口
}

三、實例:單片機向PC機(上位機)發送數據

注意,電腦需要安裝CH340驅動和串口調試助手工具,並在工具中設置好波特率、校驗位等參數。單片機要用USB(注意是接到單片機USB232接口)連接電腦而不是用STLINK!!!

1. 發送一個字節

void USART_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	USART_SendData(pUSARTx, data); //發送數據
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待發送完成
}

在main函數中進行調用:

USART_Config();
USART_SendByte(USART1, 0x34);

若選擇16進制顯示,則串口軟件會顯示34。否則會按照ASCII碼轉換為相應的字符。

2. 發送兩個字節

這里需要說明一下,由於一次只能發送一個字節(4位),所以需要把數據源分段發送。

void USART_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
	uint8_t temp_h, temp_l;
	temp_h = (data & 0xFF00) >> 8; //截取高4位
	temp_l = data & 0x00FF; //截取低4位
	
	USART_SendData(pUSARTx, temp_h);
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	USART_SendData(pUSARTx, temp_l);
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

3. 串口發送8位數據的數組

void USART_SendArray(USART_TypeDef* pUSARTx, uint8_t* Array, uint8_t num)
{
	uint8_t i;
	for( i = 0; i < num; i++)
	{
		USART_SendData(pUSARTx, Array[i]);
		while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	}
}

4. 串口發送字符串

中文字符也行。

void USART_SendStr(USART_TypeDef* pUSARTx, uint8_t* str)
{
	while( *(str) != '\0')
	{
		USART_SendData(pUSARTx, *(str++));
		while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	}
}

5. 重定向printf函數輸出到串口

注意需要在魔法棒Target勾選Use MicroLIB,然后重新編譯一次。

MicroLib是對標准C庫進行了高度優化之后的庫,供MDK默認使用,相比之下,MicroLIB的代碼更少,資源占用更少。

//重定向c庫函數printf到串口,重定向后可使用printf、putchar函數
int fputc(int ch, FILE *f)
{
	/* 發送一個字節數據到串口 */
	USART_SendData(USART1, (uint8_t)ch);
		
	/* 等待發送完畢 */
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);		
	
	return (ch);
}

在main函數中調用以下語句時會輸出到串口軟件:

printf("我永遠喜歡伊蕾娜!!!\n");
putchar('A');

四、實例:單片機從PC機(上位機)接收數據

1. 重定向scanf函數輸入到串口

//重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar函數
int fgetc(FILE *f)
{
	/* 等待串口輸入數據 */
	while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

	return (int)USART_ReceiveData(USART1);
}

2. 使用中斷服務函數

//stm32f10x_it.c
void USART1_IRQHandler(void)
{
	uint8_t a;
	if( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET ) //如果接收寄存器接收到數據
	{
		a = USART_ReceiveData(USART1); 
		USART_SendData(USART1, a); //將接收的數據發送
	}
}

需要配置NVIC:

void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQ;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

在main函數調用scanf函數,PC輸入數據時,單片機會回復同樣的數據給PC。

3. 上位機控制LED

功能實現:輸入1時實現LED0反轉,輸入2時實現LED1反轉,輸入其他向上位機提示輸入有誤。部分代碼如下:

int main(void)
{	
	uint8_t ch;
	USART_Config();
	LED_Init();

	while(1)
	{
		ch = getchar();
		printf("你選擇了%c燈!\n", ch);
		switch(ch)
		{
			case '1':
				LED0 = !LED0;
				break;
			case '2':
				LED1 = !LED1;
				break;
			default:
				printf("輸入有誤!請重新輸入!\n");
				break;
		}
		
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM