STM32—串口通訊詳解

筆者博客鏈接:蠟筆小新沒有博客
希望可以和志同道合的朋友多交流!

串口通訊(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ù)即可。(吹爆固件庫編程)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 姓名:周崇杰 學號:16140120059 專業(yè):機械設計制造及其自動化 轉(zhuǎn)載自:http://blog.csd...
    CJbaby閱讀 3,656評論 0 3
  • USART為通用同步/ 異步收發(fā)器。stm32F103RC內(nèi)置了3個通用同步/異步收發(fā)器(USART1、USART...
    簡小黑閱讀 9,131評論 0 0
  • 有一次做一個東西,為了盡量不占用CPU的處理數(shù)據(jù)時間,所以就使用DMA接收串口的數(shù)據(jù),但是呢問題來了.,,,,,怎...
    楊奉武閱讀 3,370評論 0 1
  • 1. 前言 ??USART是通用(U)同步(S)異步(A)收(R)發(fā)(T)器。??STM32F103VGT6上有3...
    dogo_L1L閱讀 3,079評論 0 0
  • 2018年7月7日 周六 晴 假期第一天,上午徐琦去學畫畫,九點多老師打電話說徐琦不舒服在學校里吐...
    徐安然兒閱讀 128評論 0 0

友情鏈接更多精彩內(nèi)容