筆者博客鏈接:蠟筆小新沒有博客
希望可以和志同道合的朋友多交流!
串口通訊(Serial Communication)是一種設備間非常常用的串行通訊方式,因為它簡單便捷,因此大部分電子設備都支持該通訊方式,其通訊協(xié)議可分層為協(xié)議層和物理層。物理層規(guī)定通信協(xié)議中具有機械、電子功能的特性,從而確保原始數(shù)據(jù)在物理媒體的傳播;協(xié)議層主要規(guī)定通訊邏輯,統(tǒng)一雙方的數(shù)據(jù)打包、解包標準。通俗的講物理層規(guī)定我們用嘴巴還是肢體交流,協(xié)議層規(guī)定我們用中文還是英文交流。下面分析一下串口通訊協(xié)議的物理層和協(xié)議層。
物理層
==1.通訊結(jié)構(gòu)==
串口通訊的物理層的主要標準是==RS-232標準==,其規(guī)定了信號的用途、通訊接口及信號的電平標準,其通訊結(jié)構(gòu)如下:

在設備內(nèi)部信號是以TTL電平標準傳輸?shù)?,設備之間是通過RS-232電平標準傳輸?shù)?,而且TTL電平需要經(jīng)過電平轉(zhuǎn)換芯片才能轉(zhuǎn)化為RS-232電平,RS-232電平轉(zhuǎn)TTL電平也是如此。
==2.電平標準==
根據(jù)使用的電平標準不同,串口通訊可分為 ==RS-232標準 ==及==TTL標準==,具體標準如下:
在電子電路中常使用TTL的電平標準,但其抗干擾能力較弱,為了增加串口的通訊距離及抗干擾能力,使用RS-232電平標準在設備之間傳輸信息,經(jīng)常使用==MA3232芯片==對TTL電平及RS-232電平進行相互轉(zhuǎn)換。
協(xié)議層
==1.數(shù)據(jù)包==
串口通訊的數(shù)據(jù)包由發(fā)送設備通過自身的TXD接口傳輸?shù)浇邮赵O備得RXD接口,在協(xié)議層中規(guī)定了數(shù)據(jù)包的內(nèi)容,具體包括起始位、主體數(shù)據(jù)(8位或9位)、校驗位以及停止位,通訊的雙方必須將數(shù)據(jù)包的格式約定一致才能正常收發(fā)數(shù)據(jù)。
==2.波特率==
由于異步通信中沒有時鐘信號,所以接收雙方要約定好波特率,即每秒傳輸?shù)拇a元個數(shù),以便對信號進行解碼,常見的波特率有4800、9600、115200等。STM32中波特率的設置通過串口初始化結(jié)構(gòu)體來實現(xiàn)。
==3.起始和停止信號==
數(shù)據(jù)包的首尾分別是起始位和停止位,數(shù)據(jù)包的起始信號由一個邏輯0的數(shù)據(jù)位表示,停止位信號可由0.5、1、1.5、2個邏輯1的數(shù)據(jù)位表示,雙方需約定一致。STM32中起始和停止信號的設置也是通過串口初始化結(jié)構(gòu)體來實現(xiàn)。
==4.有效數(shù)據(jù)==
有效數(shù)據(jù)規(guī)定了主題數(shù)據(jù)的長度,一般為8或9位,其在STM32中也是通過串口初始化結(jié)構(gòu)體來實現(xiàn)的。
==5.數(shù)據(jù)校驗==
在有效數(shù)據(jù)之后,有一個可選的數(shù)據(jù)校驗位。由于數(shù)據(jù)通信相對更容易受到外部干擾導致傳輸數(shù)據(jù)出現(xiàn)偏差,可以在傳輸過程加上校驗位來解決這個問題。校驗方法有奇校驗(odd)、偶校驗(even)、 0 校驗(space)、 1 校驗(mark)以及無(noparity)。這些也都可以在串口初始化結(jié)構(gòu)體中實現(xiàn)的。
USART簡介
USART(通用同步異步收發(fā)器)是一個串行通信設備,可以靈活地與外部設備進行全雙工數(shù)據(jù)交換。有別于 USART 還有一個UART,它是在 USART 基礎上裁剪掉了同步通信功能,只有異步通信。簡單區(qū)分同步和異步就是看通信時需不需要對外提供時鐘輸出,我們平時用的串口通信基本都是 UART。USART 在 STM32 應用最多莫過于“打印”程序信息,一般在硬件設計時都會預留一USART 通信接口連接電腦,用于在調(diào)試程序是可以把一些調(diào)試信息“打印”在電腦端的串口調(diào)試助手工具上,從而了解程序運行是否正確、如果出錯哪具體哪里出錯等等。
STM32中一共有5個USART,如示:
USART的USB轉(zhuǎn)串口原理圖如下:
USART1的發(fā)送和接收端口是事先連接好的,如果要使用其他USART只需要將相應的發(fā)送接收端口按圖連接好即可。
USART有多個中斷請求事件:
開發(fā)板與上位機的連接
開發(fā)板與上位機之間通過USB線連接,所以在上位機上要配置一個==USB轉(zhuǎn)串口== 的驅(qū)動,以便把USB傳輸過來的電平轉(zhuǎn)換為TTL電平,TTL電平才能與串口調(diào)試助手建立聯(lián)系。一般使用==CH341驅(qū)動==作為win10下的USB轉(zhuǎn)串口,驅(qū)動安裝成功的情況下接入USB會在計算機的設備管理器的端口中發(fā)現(xiàn)串口:
(win7系統(tǒng)一般選擇CH340作為USB轉(zhuǎn)串口驅(qū)動。)
代碼講解:
固件庫編程的一大好處就是我們可以根據(jù)固件庫函數(shù)來學習外設的相關(guān)知識,而且固件庫函數(shù)的編寫都是建立在對底層寄存器操作上的,所以通過講解代碼可以更好理解串口通訊相關(guān)知識。
一.初始化結(jié)構(gòu)體
typedef struct {
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字長
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校驗位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
USART初始化結(jié)構(gòu)體中的相應變量都對應著數(shù)據(jù)包中的相對內(nèi)容。
二.NVIC配置中斷優(yōu)先級
我們在串口接收信息時采用了觸發(fā)中斷事件,所以要配置一下串口中斷的優(yōu)先級:
NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中斷控制器組選擇 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART為中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 搶斷優(yōu)先級*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子優(yōu)先級 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中斷 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
中斷相關(guān)的知識之前詳細講過,此處就不再累贅講述。
中斷知識鏈接
三.USART配置函數(shù)講解
USART配置函數(shù)的主要作用是打開串口與相應的GPIO引腳,配置好相應串口信息與GPIO引腳的工作模式,以便信息的傳輸與接收。
void DEBUG_USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 第一步:初始化GPIO */
// 打開串口GPIO的時鐘
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 將USART Tx的GPIO配置為推挽復用模式
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);
// 將USART Rx的GPIO配置為浮空輸入模式
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);
/* 第二步:配置串口的初始化結(jié)構(gòu)體 */
// 打開串口外設的時鐘
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 配置串口的工作參數(shù)
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 針數(shù)據(jù)字長
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;
// 配置工作模式,收發(fā)一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(DEBUG_USARTx, &USART_InitStructure);
/*--------------------------------------------------------*/
// 串口中斷優(yōu)先級配置
NVIC_Configuration();
// 使能串口接收中斷
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
/*--------------------------------------------------------*/
/* 第三步:使能串口 */
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
==第一步==:打開了GPIO的時鐘,設置發(fā)送和接收引腳的信息,將Tx(發(fā)送引腳)配置為推挽復用模式用來發(fā)送數(shù)據(jù),Rx(接收引腳)配置為浮空輸入模式用來接收數(shù)據(jù)。
==第二步==:首先打開USART1 的時鐘,根據(jù)USART初始化結(jié)構(gòu)體成員配置相關(guān)的信息,之后利用初始化函數(shù)將初始化結(jié)構(gòu)體中的信息寫入相應寄存器中,然后的話就是引用NVIC_Configuration()函數(shù)配置串口中斷優(yōu)先級,打開相應的串口接收中斷,中斷接收函數(shù)的參數(shù)如下:
==第三步== :最后相當于打開總電源——使能串口
USART配置函數(shù)完成后代表,USART1 的接收和發(fā)送準備工作已經(jīng)準備就緒,接下來就是,串口與上位機之間的信息傳遞了,信息的發(fā)送和接收都有相對于的函數(shù)。
四.傳輸數(shù)據(jù)的函數(shù):
開發(fā)板與上位機之間的數(shù)據(jù)傳輸可以有多種方法,下面一一介紹:
1.發(fā)送一個字節(jié)
以==USART_SendData(pUSARTx,ch);== 函數(shù)為基礎建立的函數(shù)可以向上位機發(fā)送一個字節(jié)的數(shù)據(jù),利用==FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)== 讀取發(fā)送數(shù)據(jù)寄存器的狀態(tài)來 等待發(fā)送寄存器將數(shù)據(jù)成功發(fā)送。
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 發(fā)送一個字節(jié)數(shù)據(jù)到USART */
USART_SendData(pUSARTx,ch);
/* 等待發(fā)送數(shù)據(jù)寄存器為空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
2.發(fā)送字符串
本質(zhì)是利用上面的字節(jié)發(fā)送函數(shù)逐位發(fā)送字符串中的內(nèi)容
void USART_SendString(USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
while(*(str+k)!='\0')
{
USART_SendData(pUSARTx, *(str+k));
/* 等待發(fā)送數(shù)據(jù)寄存器為空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
k++;
}
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); /* TC:傳輸完成標志 */
}
3.重定向printf函數(shù)發(fā)送字符串
關(guān)于重定向的知識之前總結(jié)過,鏈接:重定向知識。重定向后的printf()函數(shù)功能強大,具有向串口調(diào)試助手打印數(shù)據(jù)的功能,==使用方法和c語言時一樣==,比如printf("歡迎來到小全全的串口實驗\n");就可以將“歡迎來到小全全的串口實驗”這句話發(fā)送到上位機中,而且換行符“\n”還具有換行作用。
/* 重定向printf函數(shù) */
int fputc(int ch, FILE *f)
{
USART_SendData( DEBUG_USARTx, (uint8_t) ch);
/* 等待發(fā)送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return ch;
}
4.重定向getchar函數(shù)接收字符
具體操作與重定向后的printf函數(shù)類似,比如可以通過如下代碼==向上位機發(fā)送已經(jīng)接收到的數(shù)據(jù)==:
x=getchar();
printf("接收到的字符是:%c\n",x);
重定義如下:
///重定向c庫函數(shù)scanf到串口,重寫向后可使用scanf、getchar等函數(shù)
int fgetc(FILE *f)
{
/* 等待串口輸入數(shù)據(jù) */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
在使用此函數(shù)作為接收數(shù)據(jù)時記得關(guān)閉串口得接收中斷?。?!
5.通過中斷接收
在stm32f10x_it.c中編寫USART1中斷源相對應得中斷函數(shù),利用了固件庫函數(shù)中的
USART_ReceiveData(DEBUG_USARTx);接收函數(shù)
USART_SendData(DEBUG_USARTx, x);發(fā)送函數(shù)
USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE);判斷標志位函數(shù)
/* #define DEBUG_USART_IRQn USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler */
void DEBUG_USART_IRQHandler(void)
{
uint16_t x;
/* 判斷是否收到中斷信號 */
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) == SET)
{
x = USART_ReceiveData(DEBUG_USARTx);
USART_SendData(DEBUG_USARTx, x);
}
}
結(jié)語
以固件庫函數(shù)編程的思路講解,未能顧及到眾多寄存器的講解,我認為進行固件庫編程本身就是學習操作寄存器的過程,很多時候我們不需要知道如何操作寄存器,只要了解如何操作固件庫函數(shù)即可。(吹爆固件庫編程)