STM32f103串口通信詳解
原理分析
首先,我們從串口通信的物理層和協議層來分別分析,
- 物理層
對于串口通信的物理層的標準變化有很多種,在這兒,我主要是講解 RS-232 標準,
這兒是以 RS-232 標準的常見設備通信結構圖:

由圖可以看到,兩個信號的 DB9 介面,通過串口信號線連接在一起,信號線是使用的 RS-232 標準傳輸資料信號,但是 RS-232 標準信號不能直接被控制器識別使用,所以我們需要使用一個電平轉換芯片轉換成 TTL 標準信號,才能實作通信,
為什么需要轉換呢? RS-232 與 TTL 有什么區別呢?

上圖是兩種標準電平信號的區別,

上圖是兩種電平標準表示同一個信號的對比圖,
對于 DB9 這種串口線我就不過多介紹了,我們只需要了解到下圖的連線方式就可以了,

這兒要記住, RXD 與 TXD是交叉連接的, - 協議層
串口通信的資料包又發送設備通過自身的 TXD 介面傳輸到接收設備的 RXD 介面,在串口通訊的協議層中,資料結構又下圖所示,起始位,資料位,校驗位,停止位,

波特率
由于異步通信之中是沒有時鐘信號的,所以需要在兩個設備之間約定好波特率(及每個碼元的長度),
通訊的起始和停止信號
串口通訊的一個資料包從起始信號開始,到停止信號結束,起始信號由一個邏輯 0 表示,停止信號可以由 0.5 , 1 , 1.5 , 或者 2 來表示( 2 個位),只要兩臺設備約定一致就行,
有效資料
起始位之后就是有效資料(主體資料),一般 5 , 6 , 7 , 8 位長,
資料校驗
由于資料通信容易受到外部干擾而導致資料偏差,可以通過加入校驗位來解決,一般來說,有奇偶校驗, 01 校驗,或者無校驗,
奇校驗要求有效資料加上校驗位中 1 的個數位奇數,而偶校驗就要求為偶數, 0 校驗校驗位總位為 0 , 1 校驗校驗位總為 1,
現在開始介紹 STM32 的 USART 原理

接下來我們來具體分析這張圖:
功能引腳


從這兒我們可以看到,在 STM32 中,它有這么一些功能引腳:
TX :發送資料的引腳
RX:接收資料的引腳
SW_RX:資料接收引腳,只用于單線和智能卡模式,屬于內部引腳,沒有具體的外部引腳
nRTS:請求以發送, n 代表低電平有效,如果使能這個引腳,當準備接收資料時就會變成低電平,當接收暫存器已經滿了,就會置高,
nCTS:清除以發送,低電平有效,若使能,當發送資料之前會檢測一下,若是低電平,則可以發送資料,若是高電平,則在當前資料發送完后停止發送,
SCLK:發送器時鐘輸出引腳,僅適用于同步模式
因為每個芯片對應的 USART 引腳不一樣,所以需要去芯片資料自行查閱,
資料暫存器



在 STM32 中 USART 的資料暫存器只有低九位有效用來存放接收和發送的資料,并且第九位資料是否有效取決于 USART 控制暫存器決定,

發送和接收的資料都是放在資料暫存器中,所以資料暫存器實際上包含了兩個暫存器,一個是用于發送的可寫暫存器 TDR ,一個是用于接收的可讀 RDR,當進行讀寫操作時,資料都是放在這個資料暫存器當中,
TDR 和 RDR 都是介于系統總線和移位暫存器之間,串行通信是一個位一個位傳輸的, 發送時把 TDR 內容轉移到發送移位暫存器,然后把移位暫存器資料每一位發送出去,接收 時把接收到的每一位順序保存在接收移位暫存器內然后才轉移到 RDR,
控制器

當 USART_CR1 暫存器發送使能置 1 ,也就是 TE 置 1 時,啟動資料發送(此時可以往資料暫存器里面寫入資料,),發送移位暫存器的 資料會在 TX 引腳輸出,當 RE 置 1 ,使能 USART 接收, RX 口開始搜索起始位,當確定到起始位后,將接收到的資料放到資料暫存器里,并把 RXNE 位置 1 ,如果是同步模式, SCLK 也會輸出時鐘信號,

停止位時間長短可以通過 USART 控制暫存器2控制,


小數波特率的生成

USART 的接收好和發送使用同一個波特率,計算公式為:

f(CK) 為 USART 時鐘,USARTDIV是一個存放波特率的暫存器

校驗控制
在 STM32F103 系列的 USART 還支持奇偶校驗位,當使用奇偶校驗時,資料位加上校驗位一共 9 位,此時的 CR1 暫存器 M 位需要置 1 ,設定為 9 位資料位,
中斷控制
當然,對于串口通信中,有多個串口中斷請求事件,分別代表了各種狀態下的情況,
所有 USART 的控制都在下列暫存器里,
狀態暫存器 SR


資料暫存器 DR


波特比例暫存器 BRR

控制暫存器 CR1



控制暫存器 CR2


控制暫存器 CR3


保護時間和預分頻暫存器 GTPR


此上,就是所有關于 STM32F103 的 USART 的所有暫存器,接下來來實操韌體庫函式吧!
代碼
#define DEBUG_USART_IRQ USART1_IRQn //宏定義中斷源,方便移植,增強可讀性,
#define DEBUG_USART_TX_GPIO_PORT GPIOA //宏定義TX的GPIO,
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 //宏定義TX口的引腳,
#define DEBUG_USART_RX_GPIO_PORT GPIOA //宏定義RX的GPIO,
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 //宏定義RX口的引腳,
#define DEBUG_USART_BAUDRATE 115200 //宏定義波特率,
#define DEBUG_USARTx USART1 //宏定義串口,
static void NVIC_Configuration(void) //這兒是USART的中斷配置,static代表限制了此函式只能在本檔案之中使用,
{
NVIC_InitTypeDef NVIC_InitStructure; //定義一個NVIC的結構體,
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置優先級分組,
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; //配置USART為中斷源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //選擇中斷主優先級,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //配置子優先級,
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中斷,
NVIC_Init(&NVIC_InitStructure); //初始化NVIC結構體,
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定義一個GPIO結構體,
USART_InitTypeDef USART_InitStructure; //定義一個USART結構體,
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 打開串口GPIO的時鐘,
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 打開USART的時鐘,
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; //配置發送引腳,
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //模式選擇推挽復用模式,
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //配置引腳速度,
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); //初始化發送引腳,
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; //配置接收引腳,
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //模式選擇浮空輸入模式,
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); //初始化接收引腳,
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; //配置波特率,
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //配置資料字長,
USART_InitStructure.USART_StopBits = USART_StopBits_1; //配置停止位,
USART_InitStructure.USART_Parity = USART_Parity_No ; //配置校驗位,
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //關閉硬體流控制,
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //配置作業模式為接收和發送,
USART_Init(DEBUG_USARTx, &USART_InitStructure); //初始化USART結構體,
NVIC_Configuration(); //串口中斷優先級配置,
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); //使能串口接收中斷,
USART_Cmd(DEBUG_USARTx, ENABLE); //使能串口,
}
以上,就是串口的所有配置了,此時串口已經打開,我們需要怎么來使用呢?可以直接使用韌體庫里面的函式,
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch) //發送一個位元組的資料
{
USART_SendData(pUSARTx, ch); //發送一個位元組資料到USART的資料暫存器,進行發送,
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待發送資料暫存器為空,資料發送出去后才退出發送函式,
}
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num) //發送一個八位資料的陣列,
{
uint8_t i; //定義一個數來記錄陣列個數,
for(i=0; i<num; i++) //i記錄發送資料組數,當陣列全部發送完成,退出回圈,
{
Usart_SendByte(pUSARTx, array[i]); //每次發送一個位元組資料到USART資料暫存器,
}
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC)==RESET); //等待發送完成后退出函式,
}
void Usart_SendString( USART_TypeDef * pUSARTx, char *str) //發送字串,
{
unsigned int k=0; //定義一個數,記錄字串長度,
do
{
Usart_SendByte(pUSARTx, *(str + k)); //字符是有ASCII表表達,所以每一個字符都是一個八位的資料,
k++; //記錄資料已發送長度,
} while(*(str + k)!='\0'); //判斷下一個字符是否為'\0',
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); //等待發送完成,
}
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch) //發送16位資料,
{
uint8_t temp_h, temp_l; //將16位資料拆分為兩個8位資料,
temp_h = (ch&0XFF00)>>8; //取出高八位,
temp_l = ch&0XFF; //取出低八位,
USART_SendData(pUSARTx,temp_h); //發送高八位,
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待高8位發送完成,
USART_SendData(pUSARTx,temp_l); //發送低八位,
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); //等待低8位發送完成,
}
以上就是串口的發送函式了,讀取資料就要去中斷里面讀取,中斷函式全部在 stm32f10x_it.c 檔案里,
#define DEBUG_USART_IRQHandler USART1_IRQHandler //宏定義串口中斷函式名字,
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp; //定義一個8位資料來保存資料,
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE)!=RESET) //確認資料暫存器不為空,讀取資料,
{
ucTemp = USART_ReceiveData(DEBUG_USARTx); //讀取資料,可以使用全域變數來保存,
}
}
以上,就是關于串口的發送和讀取了,不過,對于寫習慣了 C 語言的人來說,更加習慣用 C 語言里面的輸入輸出吧,那串口可以使用這個來進行發送和接收資料嗎?當然可以!只需要將函式重定向就可以了,
int fputc(int ch, FILE *f) //重定向c庫函式printf到串口,重定向后可使用printf函式,
{
USART_SendData(DEBUG_USARTx, (uint8_t) ch); //發送一個位元組資料到串口,
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); //等待發送完畢,
return (ch);
}
int fgetc(FILE *f) //重定向c庫函式scanf到串口,重寫向后可使用scanf、getchar等函式,
{
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET); //等待串口輸入資料,
return (int)USART_ReceiveData(DEBUG_USARTx);
}
是不是很簡單,相信大家都已經學會了 STM32 的串口使用了吧,
謝謝大家的閱讀!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/275062.html
標籤:其他
