筆者博客鏈接:蠟筆小新沒(méi)有博客
希望可以和志同道合的朋友多交流闽瓢!
串口通訊(Serial Communication)是一種設(shè)備間非常常用的串行通訊方式洒擦,因?yàn)樗?jiǎn)單便捷,因此大部分電子設(shè)備都支持該通訊方式,其通訊協(xié)議可分層為協(xié)議層和物理層爽航。物理層規(guī)定通信協(xié)議中具有機(jī)械、電子功能的特性乾忱,從而確保原始數(shù)據(jù)在物理媒體的傳播讥珍;協(xié)議層主要規(guī)定通訊邏輯,統(tǒng)一雙方的數(shù)據(jù)打包窄瘟、解包標(biāo)準(zhǔn)衷佃。通俗的講物理層規(guī)定我們用嘴巴還是肢體交流,協(xié)議層規(guī)定我們用中文還是英文交流蹄葱。下面分析一下串口通訊協(xié)議的物理層和協(xié)議層氏义。
物理層
==1.通訊結(jié)構(gòu)==
串口通訊的物理層的主要標(biāo)準(zhǔn)是==RS-232標(biāo)準(zhǔn)==,其規(guī)定了信號(hào)的用途图云、通訊接口及信號(hào)的電平標(biāo)準(zhǔn)惯悠,其通訊結(jié)構(gòu)如下:
在設(shè)備內(nèi)部信號(hào)是以TTL電平標(biāo)準(zhǔn)傳輸?shù)模O(shè)備之間是通過(guò)RS-232電平標(biāo)準(zhǔn)傳輸?shù)目⒖觯襎TL電平需要經(jīng)過(guò)電平轉(zhuǎn)換芯片才能轉(zhuǎn)化為RS-232電平克婶,RS-232電平轉(zhuǎn)TTL電平也是如此。
==2.電平標(biāo)準(zhǔn)==
根據(jù)使用的電平標(biāo)準(zhǔn)不同,串口通訊可分為 ==RS-232標(biāo)準(zhǔn) ==及==TTL標(biāo)準(zhǔn)==情萤,具體標(biāo)準(zhǔn)如下:
在電子電路中常使用TTL的電平標(biāo)準(zhǔn)鸭蛙,但其抗干擾能力較弱,為了增加串口的通訊距離及抗干擾能力筋岛,使用RS-232電平標(biāo)準(zhǔn)在設(shè)備之間傳輸信息娶视,經(jīng)常使用==MA3232芯片==對(duì)TTL電平及RS-232電平進(jìn)行相互轉(zhuǎn)換。
協(xié)議層
==1.數(shù)據(jù)包==
串口通訊的數(shù)據(jù)包由發(fā)送設(shè)備通過(guò)自身的TXD接口傳輸?shù)浇邮赵O(shè)備得RXD接口睁宰,在協(xié)議層中規(guī)定了數(shù)據(jù)包的內(nèi)容肪获,具體包括起始位、主體數(shù)據(jù)(8位或9位)柒傻、校驗(yàn)位以及停止位贪磺,通訊的雙方必須將數(shù)據(jù)包的格式約定一致才能正常收發(fā)數(shù)據(jù)。
==2.波特率==
由于異步通信中沒(méi)有時(shí)鐘信號(hào)诅愚,所以接收雙方要約定好波特率,即每秒傳輸?shù)拇a元個(gè)數(shù)劫映,以便對(duì)信號(hào)進(jìn)行解碼违孝,常見(jiàn)的波特率有4800、9600泳赋、115200等雌桑。STM32中波特率的設(shè)置通過(guò)串口初始化結(jié)構(gòu)體來(lái)實(shí)現(xiàn)。
==3.起始和停止信號(hào)==
數(shù)據(jù)包的首尾分別是起始位和停止位祖今,數(shù)據(jù)包的起始信號(hào)由一個(gè)邏輯0的數(shù)據(jù)位表示校坑,停止位信號(hào)可由0.5、1千诬、1.5耍目、2個(gè)邏輯1的數(shù)據(jù)位表示,雙方需約定一致徐绑。STM32中起始和停止信號(hào)的設(shè)置也是通過(guò)串口初始化結(jié)構(gòu)體來(lái)實(shí)現(xiàn)邪驮。
==4.有效數(shù)據(jù)==
有效數(shù)據(jù)規(guī)定了主題數(shù)據(jù)的長(zhǎng)度,一般為8或9位傲茄,其在STM32中也是通過(guò)串口初始化結(jié)構(gòu)體來(lái)實(shí)現(xiàn)的毅访。
==5.數(shù)據(jù)校驗(yàn)==
在有效數(shù)據(jù)之后,有一個(gè)可選的數(shù)據(jù)校驗(yàn)位盘榨。由于數(shù)據(jù)通信相對(duì)更容易受到外部干擾導(dǎo)致傳輸數(shù)據(jù)出現(xiàn)偏差喻粹,可以在傳輸過(guò)程加上校驗(yàn)位來(lái)解決這個(gè)問(wèn)題。校驗(yàn)方法有奇校驗(yàn)(odd)草巡、偶校驗(yàn)(even)守呜、 0 校驗(yàn)(space)、 1 校驗(yàn)(mark)以及無(wú)(noparity)。這些也都可以在串口初始化結(jié)構(gòu)體中實(shí)現(xiàn)的弛饭。
USART簡(jiǎn)介
USART(通用同步異步收發(fā)器)是一個(gè)串行通信設(shè)備冕末,可以靈活地與外部設(shè)備進(jìn)行全雙工數(shù)據(jù)交換。有別于 USART 還有一個(gè)UART侣颂,它是在 USART 基礎(chǔ)上裁剪掉了同步通信功能档桃,只有異步通信。簡(jiǎn)單區(qū)分同步和異步就是看通信時(shí)需不需要對(duì)外提供時(shí)鐘輸出憔晒,我們平時(shí)用的串口通信基本都是 UART藻肄。USART 在 STM32 應(yīng)用最多莫過(guò)于“打印”程序信息,一般在硬件設(shè)計(jì)時(shí)都會(huì)預(yù)留一USART 通信接口連接電腦拒担,用于在調(diào)試程序是可以把一些調(diào)試信息“打印”在電腦端的串口調(diào)試助手工具上嘹屯,從而了解程序運(yùn)行是否正確、如果出錯(cuò)哪具體哪里出錯(cuò)等等从撼。
STM32中一共有5個(gè)USART州弟,如示:
USART的USB轉(zhuǎn)串口原理圖如下:
USART1的發(fā)送和接收端口是事先連接好的,如果要使用其他USART只需要將相應(yīng)的發(fā)送接收端口按圖連接好即可低零。
USART有多個(gè)中斷請(qǐng)求事件:
開(kāi)發(fā)板與上位機(jī)的連接
開(kāi)發(fā)板與上位機(jī)之間通過(guò)USB線連接婆翔,所以在上位機(jī)上要配置一個(gè)==USB轉(zhuǎn)串口== 的驅(qū)動(dòng),以便把USB傳輸過(guò)來(lái)的電平轉(zhuǎn)換為TTL電平掏婶,TTL電平才能與串口調(diào)試助手建立聯(lián)系啃奴。一般使用==CH341驅(qū)動(dòng)==作為win10下的USB轉(zhuǎn)串口,驅(qū)動(dòng)安裝成功的情況下接入U(xiǎn)SB會(huì)在計(jì)算機(jī)的設(shè)備管理器的端口中發(fā)現(xiàn)串口:
(win7系統(tǒng)一般選擇CH340作為USB轉(zhuǎn)串口驅(qū)動(dòng)雄妥。)
代碼講解:
固件庫(kù)編程的一大好處就是我們可以根據(jù)固件庫(kù)函數(shù)來(lái)學(xué)習(xí)外設(shè)的相關(guān)知識(shí)最蕾,而且固件庫(kù)函數(shù)的編寫都是建立在對(duì)底層寄存器操作上的,所以通過(guò)講解代碼可以更好理解串口通訊相關(guān)知識(shí)老厌。
一.初始化結(jié)構(gòu)體
typedef struct {
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字長(zhǎng)
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校驗(yàn)位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
USART初始化結(jié)構(gòu)體中的相應(yīng)變量都對(duì)應(yīng)著數(shù)據(jù)包中的相對(duì)內(nèi)容瘟则。
二.NVIC配置中斷優(yōu)先級(jí)
我們?cè)诖诮邮招畔r(shí)采用了觸發(fā)中斷事件,所以要配置一下串口中斷的優(yōu)先級(jí):
NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中斷控制器組選擇 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART為中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 搶斷優(yōu)先級(jí)*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子優(yōu)先級(jí) */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中斷 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
中斷相關(guān)的知識(shí)之前詳細(xì)講過(guò)枝秤,此處就不再累贅講述壹粟。
中斷知識(shí)鏈接
三.USART配置函數(shù)講解
USART配置函數(shù)的主要作用是打開(kāi)串口與相應(yīng)的GPIO引腳,配置好相應(yīng)串口信息與GPIO引腳的工作模式宿百,以便信息的傳輸與接收趁仙。
void DEBUG_USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 第一步:初始化GPIO */
// 打開(kāi)串口GPIO的時(shí)鐘
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 將USART Tx的GPIO配置為推挽復(fù)用模式
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)體 */
// 打開(kāi)串口外設(shè)的時(shí)鐘
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 配置串口的工作參數(shù)
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 針數(shù)據(jù)字長(zhǎng)
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校驗(yàn)位
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)先級(jí)配置
NVIC_Configuration();
// 使能串口接收中斷
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
/*--------------------------------------------------------*/
/* 第三步:使能串口 */
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
==第一步==:打開(kāi)了GPIO的時(shí)鐘垦页,設(shè)置發(fā)送和接收引腳的信息雀费,將Tx(發(fā)送引腳)配置為推挽復(fù)用模式用來(lái)發(fā)送數(shù)據(jù),Rx(接收引腳)配置為浮空輸入模式用來(lái)接收數(shù)據(jù)痊焊。
==第二步==:首先打開(kāi)USART1 的時(shí)鐘盏袄,根據(jù)USART初始化結(jié)構(gòu)體成員配置相關(guān)的信息忿峻,之后利用初始化函數(shù)將初始化結(jié)構(gòu)體中的信息寫入相應(yīng)寄存器中,然后的話就是引用NVIC_Configuration()
函數(shù)配置串口中斷優(yōu)先級(jí)辕羽,打開(kāi)相應(yīng)的串口接收中斷逛尚,中斷接收函數(shù)的參數(shù)如下:
==第三步== :最后相當(dāng)于打開(kāi)總電源——使能串口
USART配置函數(shù)完成后代表,USART1 的接收和發(fā)送準(zhǔn)備工作已經(jīng)準(zhǔn)備就緒刁愿,接下來(lái)就是绰寞,串口與上位機(jī)之間的信息傳遞了,信息的發(fā)送和接收都有相對(duì)于的函數(shù)铣口。
四.傳輸數(shù)據(jù)的函數(shù):
開(kāi)發(fā)板與上位機(jī)之間的數(shù)據(jù)傳輸可以有多種方法滤钱,下面一一介紹:
1.發(fā)送一個(gè)字節(jié)
以==USART_SendData(pUSARTx,ch);== 函數(shù)為基礎(chǔ)建立的函數(shù)可以向上位機(jī)發(fā)送一個(gè)字節(jié)的數(shù)據(jù),利用==FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)== 讀取發(fā)送數(shù)據(jù)寄存器的狀態(tài)來(lái) 等待發(fā)送寄存器將數(shù)據(jù)成功發(fā)送脑题。
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 發(fā)送一個(gè)字節(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:傳輸完成標(biāo)志 */
}
3.重定向printf函數(shù)發(fā)送字符串
關(guān)于重定向的知識(shí)之前總結(jié)過(guò)件缸,鏈接:重定向知識(shí)。重定向后的printf()
函數(shù)功能強(qiáng)大叔遂,具有向串口調(diào)試助手打印數(shù)據(jù)的功能他炊,==使用方法和c語(yǔ)言時(shí)一樣==,比如printf("歡迎來(lái)到小全全的串口實(shí)驗(yàn)\n");
就可以將“歡迎來(lái)到小全全的串口實(shí)驗(yàn)”這句話發(fā)送到上位機(jī)中已艰,而且換行符“\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ù)類似,比如可以通過(guò)如下代碼==向上位機(jī)發(fā)送已經(jīng)接收到的數(shù)據(jù)==:
x=getchar();
printf("接收到的字符是:%c\n",x);
重定義如下:
///重定向c庫(kù)函數(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ù)時(shí)記得關(guān)閉串口得接收中斷@κ瘛4浴!
5.通過(guò)中斷接收
在stm32f10x_it.c
中編寫USART1中斷源相對(duì)應(yīng)得中斷函數(shù)辆它,利用了固件庫(kù)函數(shù)中的
USART_ReceiveData(DEBUG_USARTx);接收函數(shù)
USART_SendData(DEBUG_USARTx, x);發(fā)送函數(shù)
USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE)誊薄;判斷標(biāo)志位函數(shù)
/* #define DEBUG_USART_IRQn USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler */
void DEBUG_USART_IRQHandler(void)
{
uint16_t x;
/* 判斷是否收到中斷信號(hào) */
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) == SET)
{
x = USART_ReceiveData(DEBUG_USARTx);
USART_SendData(DEBUG_USARTx, x);
}
}
結(jié)語(yǔ)
以固件庫(kù)函數(shù)編程的思路講解,未能顧及到眾多寄存器的講解锰茉,我認(rèn)為進(jìn)行固件庫(kù)編程本身就是學(xué)習(xí)操作寄存器的過(guò)程呢蔫,很多時(shí)候我們不需要知道如何操作寄存器,只要了解如何操作固件庫(kù)函數(shù)即可飒筑。(吹爆固件庫(kù)編程)