RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)篇) 第10章 串口通信

嵌入式大雜燴 ? 2023-05-12 22:25 ? 次閱讀

開發(fā)環(huán)境:

MDK:Keil 5.30

開發(fā)板:GD32F207I-EVAL

MCU:GD32F207IK

1 串口簡介

USART(Universal Synchronous Asynchronous Receiver and Transmitter,通用同步-異步接收發(fā)射器)提供了一種靈活的方法與使用工業(yè)標(biāo)準(zhǔn)NRZ異步串行數(shù)據(jù)格式的外部設(shè)備之間進行全雙工數(shù)據(jù)交換。USART利用分?jǐn)?shù)波特率發(fā)生器提供寬范圍的波特率選擇。它支持同步單向通信和半雙工單線通信,也支持LIN(局部互連網(wǎng)),智能卡協(xié)議和IrDA(紅外數(shù)據(jù)組織)SIR ENDEC規(guī)范,以及調(diào)制解調(diào)器(CTS/RTS)操作。它還允許多處理器通信。使用多緩沖器配置的DMA方式,可以實現(xiàn)高速數(shù)據(jù)通信。

雖然USART既可以同步又可以異步,但是常見的最常用的就是使用功能的異步功能,如果作為異步通信就是UART(Universal Asynchronous Receiver and Transmitter),可以說,UART是USART的子集,但是同步通信相比異步通信多了一根時鐘同步信號線。

下面簡單介紹下同步和異步。

在同步通訊中,收發(fā)設(shè)備雙方會使用一根信號線表示時鐘信號,在時鐘信號的驅(qū)動下雙方進行協(xié)調(diào),同步數(shù)據(jù),見下圖。通訊中通常雙方會統(tǒng)一規(guī)定在時鐘信號的上升沿或下降沿對數(shù)據(jù)線進行采樣。

1683895220147axge4u4qtc

在異步通訊中不使用時鐘信號進行數(shù)據(jù)同步,它們直接在數(shù)據(jù)信號中穿插一些同步用的信號位,或者把主體數(shù)據(jù)進行打包,以數(shù)據(jù)幀的格式傳輸數(shù)據(jù),見下圖,某些通訊中還需要雙方約定數(shù)據(jù)的傳輸速率,以便更好地同步。

1683895220804pmuvbhvx11

在同步通訊中,數(shù)據(jù)信號所傳輸?shù)膬?nèi)容絕大部分就是有效數(shù)據(jù),而異步通訊中會包含有幀的各種標(biāo)識符,所以同步通訊的效率更高,但是同步通訊雙方的時鐘允許誤差較小,而異步通訊雙方的時鐘允許誤差較大。

從上面的介紹可以看出,USART以同步方式通信需要時鐘同步信號,但不需要額外的起始、停止位,可以實現(xiàn)更快的傳輸速度。但USART控制起來更復(fù)雜,因此本文主要講解以異步通信。

異步串行通信以字符為單位,即一個字符一個字符地傳送 。

16838952212104nl83id006

串口外設(shè)的架構(gòu)圖看起來十分復(fù)雜,實際上對于軟件開發(fā)人員來說,我們只需要大概了解串口發(fā)送的過程即可。從下至上,我們看到串口外設(shè)主要由三個部分組成,分別是波特率控制、收發(fā)控制和數(shù)據(jù)存儲轉(zhuǎn)移。

  • 波特率控制

波特率,即每秒傳輸?shù)亩M制位數(shù),用b/s(bps)表示,通過對時鐘的控制可以改變波特率。在配置波特率時,我們向波特比率寄存器 USART_BAUD寫入參數(shù),修改了串口時鐘的分頻值USARTDIV。USART_BAUD寄存器包括兩部分,分別是INTDIV(USARTDIV 的整數(shù)部分)和FRADIV(USARTDIV 的小數(shù))部分,最終,計算公式為 USARTDIV= INTDIV+(FRADIV/16)。

USARTDIV 是對串口外設(shè)的時鐘源進行分頻的,USART0/5的系統(tǒng)時鐘為PCLK2, USART1/2和UART3/4/6/7的系統(tǒng)時鐘為PCLK1,串口的時鐘源經(jīng)過 USARTDIV 分頻后分別輸出作為發(fā)送器時鐘及接收器時鐘,控制發(fā)送和接收的時序。在使能USART之前,必須在時鐘控制單元使能系統(tǒng)時鐘。

1683895221580zntbwdjtxe

  • 收發(fā)控制

圍繞著發(fā)送器和接收器控制部分,有好多個寄存器 :STAT0、USART_CTL0、USART_CTL1、USART_CTL2和 STAT1,即USART 的三個控制寄存器(Control Register)及一個狀態(tài)寄存器(Status Register)。通過向寄存器寫入 各種控制參數(shù)來控制發(fā)送和接收,如奇偶校驗位、停止位等,還包括對USART 中斷的控制;串口的狀態(tài)在任何時候都可以從狀態(tài)寄存器中查詢得到。其中停止位的配置如下圖所示。

1683895221949vzwsmuzlua

  • 發(fā)送配置步驟:

1.在USART_CTL0寄存器中置位UEN位,使能USART;

2.通過USART_CTL0寄存器的WL設(shè)置字長;

3.在USART_CTL1寄存器中寫STB[1:0]位來設(shè)置停止位的長度;

4.如果選擇了多級緩存通信方式,應(yīng)該在USART_CTL2寄存器中使能DMA (DENT位);

5.在USART_BAUD寄存器中設(shè)置波特率;

6.在USART_CTL0寄存器中設(shè)置TEN位;

7.等待TBE置位;

8.向USART_DATA寄存器寫數(shù)據(jù);

9.若DMA未使能,每發(fā)送一個字節(jié)都需重復(fù)步驟7-8;

10.等待TC=1,發(fā)送完成。

1683895222355c03jht49yj

在禁用USART或進入低功耗狀態(tài)之前,必須等待TC置位。先讀USART_STAT0然后再寫USART_DATA可將TC位清0。在多級緩存通信方式(DENT=1)下,直接向TC寫0,也能清TC。

  • 接收配置步驟:

1.寫USART_CTL0寄存器的WL位去設(shè)置字長;

2.在USART_CTL1寄存器中寫STB[1:0]位來設(shè)置停止位的長度;

3.如果選擇了多級緩存通信方式,應(yīng)該在USART_CTL2寄存器中使能DMA(DENR位);

4.在USART_BAUD寄存器中設(shè)置波特率;

5.在USART_CTL0寄存器中置位UEN位,使能USART;

6.在USART_CTL0中設(shè)置REN位。

接收器在使能后若檢測到一個有效的起始脈沖便開始接收碼流。在接收一個數(shù)據(jù)幀的過程中會檢測噪聲錯誤,奇偶校驗錯誤,幀錯誤和過載錯誤。

當(dāng)接收到一個數(shù)據(jù)幀, USART_STAT0寄存器中的RBNE置位,如果設(shè)置了USART_CTL0寄存器中相應(yīng)的中斷使能位RBNEIE,將會產(chǎn)生中斷。在USART_STAT0寄存器中可以觀察接收狀態(tài)標(biāo)志。

軟件可以通過讀USART_DATA寄存器或者DMA方式獲取接收到的數(shù)據(jù)。不管是直接讀寄存器還是通過DMA,只要是對USART_DATA寄存器的一個讀操作都可以清除RBNE位。

在接收過程中,需使能REN位,不然當(dāng)前的數(shù)據(jù)幀將會丟失。

以上對串口通信進行了簡單介紹,為了方便各位讀者朋友更好的理解,在這里筆者將引入一個新的思想--系統(tǒng)分層思想。既然各位對著有意于嵌入式,那么必須得有對整個系統(tǒng)的架構(gòu)要有一定的認(rèn)知。對GD32裸機開發(fā),我們可以將分為三層:物理層、協(xié)議層和應(yīng)用層。前文講了這么多也是對串口協(xié)議進行分析,常用的物理層的串口通信標(biāo)準(zhǔn)有232和485。

【注】UART和USART的區(qū)別

USART(universal synchronous asynchronous receiver and transmitte): 通用同步異步收發(fā)器,USART是一個串行通信設(shè)備,可以靈活地與外部設(shè)備進行全雙工數(shù)據(jù)交換。

UART(universal asynchronous receiver and transmitter): 通用異步收發(fā)器,異步串行通信口(UART)就是我們在嵌入式中常說的串口,它還是一種通用的數(shù)據(jù)通信議。從名字上可以看出,USART在UART基礎(chǔ)上增加了同步功能,即USART是UART的增強型。

當(dāng)我們使用USART在異步通信的時候,它與UART沒有什么區(qū)別,但是用在同步通信的時候,區(qū)別就很明顯了:大家都知道同步通信需要時鐘來觸發(fā)數(shù)據(jù)傳輸,也就是說USART相對UART的區(qū)別之一就是能提供主動時鐘。如GD32的USART可以提供時鐘支持ISO7816的智能卡接口

USART是指單片機的一個端口模塊,可以根據(jù)需要配置成同步模式(SPI,I2C),也可以將其配置為異步模式,后者就是UART。所以說UART姑且可以稱之為一個與SPI,I2C對等的“協(xié)議”,而USART則不是一個協(xié)議,而是更應(yīng)該理解為一個實體。相比于同步通訊,UART不需要統(tǒng)一的時鐘線,接線更加方便。但是,為了正常的對信號進行解碼,使用UART通訊的雙方必須事先約定好波特率,即每個碼元的長度。

關(guān)于串口的深入理解,請參看筆者文章:

https://blog.bruceou.cn/2021/01/detailed-explanation-of-stm32-serial-communication/555/

2 串口通信的寄存器描述

串口常用的寄存器有狀態(tài)寄存器(USART_STATx)、數(shù)據(jù)寄存器(USART_DATA)、波特比率寄存器(USART_BAUD)、控制寄存器 (USART_CTLx)。

1683895222737bd67lzcxas

1683895223168dhpfo23z8c

1683895223522iwiaspele1

1683895223895iajpcvmzo3

3 串口硬件

串口的接口通過三個引腳與其他設(shè)備連接在一起。任何USART雙向通信至少需要兩個腳:接收數(shù)據(jù)輸入(RX)和發(fā)送數(shù)據(jù)輸出(TX)。

  • RX:接收數(shù)據(jù)串行輸入。通過采樣技術(shù)來區(qū)別數(shù)據(jù)和噪音,從而恢復(fù)數(shù)據(jù)。
  • TX :發(fā)送數(shù)據(jù)輸出。當(dāng)發(fā)送器被禁止時,輸出引腳恢復(fù)到它的I/O端口配置。當(dāng)發(fā)送器被激活,并且不發(fā)送數(shù)據(jù)時,TX引腳處于高電平。在單線和智能卡模式里,此I/O 口被同時用于數(shù)據(jù)的發(fā)送和接收。

1683895224390602dmuuzev

板子使用串口0,接口用的232,但對于軟件來說,都是一樣的。

4 串口發(fā)送(重定向printf)

4.1 串口發(fā)送實現(xiàn)

下面筆者就用標(biāo)準(zhǔn)庫來操作串口0。

1.串口配置

  • 串口0時鐘使能

串口1是掛載在 APB2 下面的外設(shè),所以使能函數(shù)為:

rcu_periph_clock_enable(RCU_USART0);

值得注意的是,不僅要打開串口的時鐘,還需要打開相應(yīng)GPIO的時鐘,最終的代碼如下:

rcu_periph_clock_enable(RCU_GPIOA);
  • 配置串口GPIO

這個比較簡單,前面的章節(jié)已經(jīng)講過了,只需要注意的是,這里的GPIO不再是普通GPIO,要配置成復(fù)用功能,因此TX和RX分別配置成GPIO_MODE_AF_PP和GPIO_MODE_IN_FLOATING。

  • 串口復(fù)位

當(dāng)外設(shè)出現(xiàn)異常的時候可以通過復(fù)位設(shè)置,實現(xiàn)該外設(shè)的復(fù)位,然后重新配置這個外設(shè)達(dá)到讓其重新工作的目的。一般在系統(tǒng)剛開始配置外設(shè)的時候,都會先執(zhí)行復(fù)位該外設(shè)的操作。復(fù)位的是在函數(shù)usart_deinit()中完成:

void usart_deinit(uint32_t usart_periph);

比如我們要復(fù)位串口0,方法為:

usart_deinit(USART0);
  • 串口參數(shù)初始化

串口初始化是以下函數(shù)設(shè)置:

void usart_baudrate_set(uint32_t usart_periph, uint32_t baudval); //設(shè)置波特率
void usart_word_length_set(uint32_t usart_periph, uint32_t wlen); //設(shè)置傳輸字長
void usart_stop_bit_set(uint32_t usart_periph, uint32_t stblen); //設(shè)置停止位
void usart_parity_config(uint32_t usart_periph, uint32_t paritycfg); //設(shè)置校驗位
void usart_hardware_flow_rts_config(uint32_t usart_periph, uint32_t rtsconfig); //設(shè)置RTS流控
void usart_hardware_flow_cts_config(uint32_t usart_periph, uint32_t ctsconfig); //設(shè)置CTS流控
void usart_receive_config(uint32_t usart_periph, uint32_t rxconfig); //設(shè)置接收使能
void usart_transmit_config(uint32_t usart_periph, uint32_t txconfig); //設(shè)置發(fā)送使能

從上面的初始化格式可以看出初始化需要設(shè)置的參數(shù)為:波特率,字長,停止位,奇偶校驗位,硬件數(shù)據(jù)流控制,模式(收,發(fā))。 我們可以根據(jù)需要設(shè)置這些參數(shù)。

  • 串口使能

串口使能是通過函數(shù)usart_enable()來實現(xiàn)的,這個很容易理解,使用方法是:

usart_enable(USART0);

到此,串口初始化的基本配置就算完成了,完整初始化代碼如下:

/*
    brief      configure COM port
    param[in]  com_typedef_enum com_id, uint32_t baudval
    param[out] none
    retval     none
*/
void com_init(com_typedef_enum com_id, uint32_t baudval)
{
    /* enable GPIO clock */
    rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);

    /* enable USART clock */
    rcu_periph_clock_enable(COM_CLK[com_id]);

    /* connect port to USARTx_Tx */
    gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);

    /* connect port to USARTx_Rx */
    gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);

    /* USART configure */
    usart_deinit(COM_USART[com_id]);
    usart_baudrate_set(COM_USART[com_id], baudval);
    usart_word_length_set(COM_USART[com_id], USART_WL_8BIT);
    usart_stop_bit_set(COM_USART[com_id], USART_STB_1BIT);
    usart_parity_config(COM_USART[com_id], USART_PM_NONE);
    usart_hardware_flow_rts_config(COM_USART[com_id], USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(COM_USART[com_id], USART_CTS_DISABLE);
    usart_receive_config(COM_USART[com_id], USART_RECEIVE_ENABLE);
    usart_transmit_config(COM_USART[com_id], USART_TRANSMIT_ENABLE);
    usart_enable(COM_USART[com_id]);
}

2.數(shù)據(jù)發(fā)送與接收

GD32 的發(fā)送與接收是通過數(shù)據(jù)寄存器USART_DATA來實現(xiàn)的,這是一個雙寄存器。當(dāng)向該寄存器寫數(shù)據(jù)的時候,串口就會自動發(fā)送,當(dāng)收到數(shù)據(jù)的時候,也是存在該寄存器內(nèi)。

GD32庫函數(shù)操作USART_DATA寄存器發(fā)送數(shù)據(jù)的函數(shù)是:

void usart_data_transmit(uint32_t usart_periph, uint16_t data);

通過該函數(shù)向串口寄存器 USART_DR 寫入一個數(shù)據(jù)。

GD32庫函數(shù)操作USART_DATA寄存器讀取串口接收到的數(shù)據(jù)的函數(shù)是:

uint16_t usart_data_receive(uint32_t usart_periph);

通過該函數(shù)可以讀取串口接受到的數(shù)據(jù)。

3.串口狀態(tài)

串口的狀態(tài)可以通過狀態(tài)寄存器USART_STAT0讀取。

狀態(tài)寄存器的其他位我們這里就不做過多講解,大家需要可以查看中文參考手冊。

在我們固件庫函數(shù)里面,讀取串口狀態(tài)的函數(shù)是:

FlagStatus usart_flag_get(uint32_t usart_periph, usart_flag_enum flag);

這個函數(shù)的第二個入口參數(shù)非常關(guān)鍵, 它是標(biāo)示我們要查看串口的哪種狀態(tài), 比如上面講解的TBE(讀數(shù)據(jù)寄存器非空)以及 TC(發(fā)送完成)。例如我們要判斷讀寄存器是否非空(TBE), 操作庫函數(shù)的方法是:

usart_flag_get (USART0, USART_FLAG_TBE);

我們要判斷發(fā)送是否完成(TC),操作庫函數(shù)的方法是:

usart_flag_get (USART0, USART_FLAG_TC);

這些標(biāo)識號是通過枚舉類型定義的:

/* USART flags */
typedef enum {
    /* flags in STAT0 register */
    USART_FLAG_CTSF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 9U),      /*!< CTS change flag */
    USART_FLAG_LBDF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 8U),      /*!< LIN break detected flag */
    USART_FLAG_TBE = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 7U),       /*!< transmit data buffer empty */
    USART_FLAG_TC = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 6U),        /*!< transmission complete */
    USART_FLAG_RBNE = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 5U),      /*!< read data buffer not empty */
    USART_FLAG_IDLEF = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 4U),     /*!< IDLE frame detected flag */
    USART_FLAG_ORERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 3U),     /*!< overrun error */
    USART_FLAG_NERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 2U),      /*!< noise error flag */
    USART_FLAG_FERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 1U),      /*!< frame error flag */
    USART_FLAG_PERR = USART_REGIDX_BIT(USART_STAT0_REG_OFFSET, 0U),      /*!< parity error flag */
    /* flags in STAT1 register */
    USART_FLAG_BSY = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 16U),      /*!< busy flag */
    USART_FLAG_EB = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 12U),       /*!< end of block flag */
    USART_FLAG_RT = USART_REGIDX_BIT(USART_STAT1_REG_OFFSET, 11U)        /*!< receiver timeout flag */
} usart_flag_enum;

另外,筆者在此給出輸出格式的說明,請讀者朋友參考。

格式 說明
%d 按照十進制整型數(shù)打印
%6d 按照十進制整型數(shù)打印,至少6個字符寬
%f 按照浮點數(shù)打印
%6f 按照浮點數(shù)打印,至少6個字符寬
%.2f 按照浮點數(shù)打印,小數(shù)點后有2位小數(shù)
%6.2f 按照浮點數(shù)打印,至少6個字符寬,小數(shù)點后有2位小數(shù)
%x 按照十六進制打印
%c 打印字符
%s 打印字符串

接下來就可以實現(xiàn)串口的發(fā)送了,這里對發(fā)送函數(shù)進行封裝。

/**
  * @brief  串口發(fā)送一個字節(jié)數(shù)據(jù) 
  * @param  ch:待發(fā)送字符
  * @retval None
  */
void usart_send_byte(uint8_t ch)
{
    /* 發(fā)送一個字節(jié)數(shù)據(jù)到USART */
    usart_data_transmit(USART0,ch);

    /* 等待發(fā)送完畢 */
    while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET);	
}

/**
  * @brief  串口發(fā)送指定長度的字符串
  * @param  str:待發(fā)送字符串緩沖器
  *         strlen:指定字符串長度
  * @retval None
  */
void usart_sendStr_length(uint8_t *str,uint32_t strlen)
{
    unsigned int k=0;
    do 
    {
        usart_send_byte(*(str + k));
        k++;
    } while(k < strlen);
}

/**
  * @brief  串口發(fā)送字符串,直到遇到字符串結(jié)束符
  * @param  str:待發(fā)送字符串緩沖器
  * @retval None
  */
void usart_send_string(uint8_t *str)
{
	  unsigned int k=0;
    do 
    {
        usart_send_byte(*(str + k));
        k++;
    } while(*(str + k)!='');
}

這樣就方便多了,然后再主函數(shù)中調(diào)用發(fā)送函數(shù)。

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    char str[20];

    //systick init
    sysTick_init();

    //usart init 115200 8-N-1
    com_init(COM1, 115200);

    usart_send_string((uint8_t*)"This is COM1
");

    /* sprintf函數(shù)把格式化的數(shù)據(jù)寫入某個字符串  */
    sprintf(str,"20%02d-%02d-%02d",22,05,15); 

    usart_send_string((uint8_t*)str);

    while(1)
    {

    }
}

下面筆者還要介紹一種常用的串口打印方式I/O重定向,也就是使用printf打印數(shù)據(jù)到終端,但是我們的裸機系統(tǒng)沒有終端,因此如果想讓printf / scanf向USART0發(fā)送、獲取數(shù)據(jù),需要通過代碼指定C標(biāo)準(zhǔn)庫輸入/輸出函數(shù)的控制終端設(shè)備,也就是使用功能I/O重定向。

在stdio.h有相應(yīng)的接口。

/*
    * dynamically allocates a buffer of the right size for the
    * formatted string, and returns it in (*strp). Formal return value
    * is the same as any other printf variant, except that it returns
    * -1 if the buffer could not be allocated.
    *
    * (The functions with __ARM_ prefixed names are identical to the
    * ones without, but are available in all compilation modes without
    * violating user namespace.)
    */
extern _ARMABI int fgetc(FILE * /*stream*/) __attribute__((__nonnull__(1)));
   /*
    * reads at most one less than the number of characters specified by n from
    * the stream pointed to by stream into the array pointed to by s. No
    * additional characters are read after a new-line character (which is
    * retained) or after end-of-file. A null character is written immediately
    * after the last character read into the array.
    * Returns: s if successful. If end-of-file is encountered and no characters
    *          have been read into the array, the contents of the array remain
    *          unchanged and a null pointer is returned. If a read error occurs
    *          during the operation, the array contents are indeterminate and a
    *          null pointer is returned.
    */
extern _ARMABI int fputc(int /*c*/, FILE * /*stream*/) __attribute__((__nonnull__(2)));

下面我們以實現(xiàn)printf打印數(shù)據(jù)到USART(即重定義fputc函數(shù))的實現(xiàn)過程。

/**
  * @brief  重定向c庫函數(shù)printf到USART1
  * @param  None
  * @retval 
  */
int fputc(int ch, FILE *f)
{
    /*清除標(biāo)志位*/
    usart_flag_clear(USART0,USART_FLAG_TC);

    /* 發(fā)送一個字節(jié)數(shù)據(jù)到USART0 */
    usart_data_transmit(USART0, (uint8_t) ch);

    /* 等待發(fā)送完畢 */
    while (usart_flag_get(USART0, USART_FLAG_TC) == RESET);	

    return (ch);
}

scanf同理。

/**
  * @brief  重定向c庫函數(shù)scanf到USART0
  * @param  None
  * @retval None
  */
int fgetc(FILE *f)
{
    /* 等待串口0輸入數(shù)據(jù) */
    while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET);

    return (int)usart_data_receive(USART0);
}

接下來就可使用printf和scanf函數(shù)了。

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    char str[20];

    //systick init
    sysTick_init();

    //usart init 115200 8-N-1
    com_init(COM1, 115200);

    printf("This is COM1
");	

    /* sprintf函數(shù)把格式化的數(shù)據(jù)寫入某個字符串  */  
    sprintf(str,"20%02d-%02d-%02d",22,05,15); 

    printf("%s",str);

    while(1)
    {

    }
}

完整代碼請查看配套程序,另外還需添加微庫以便支持printf。具體設(shè)置參看本節(jié)后文的小貼士部分。

我們來總結(jié)下串口發(fā)送的流程:

1.初始化硬件,時鐘;

2.USART 的GPIO初始化,USART參數(shù)初始化;

3.重定向printf

4.打印輸出

4.2 實驗現(xiàn)象

將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息

1683895224964aialnlhpsm

5 串口接收數(shù)據(jù)(中斷方式)

5.1 串口接收實現(xiàn)

中斷方式相對于與普通方式,還需要開啟中斷并且初始化 NVIC以及中斷服務(wù)函數(shù)。

  • 開啟中斷

在接收到數(shù)據(jù)的時候(RBNE讀數(shù)據(jù)寄存器非空),我們要產(chǎn)生中斷,那么我們開啟中斷的方法是:

usart_interrupt_enable(USART0, USART_INT_RBNE); /* 使能串口0接收中斷 */

在發(fā)送數(shù)據(jù)結(jié)束的時候( TC, 發(fā)送完成) 要產(chǎn)生中斷,那么方法是:

usart_interrupt_enable(USART0, USART_INT_TBE);

開啟NVIC中斷以及優(yōu)先級。

nvic_irq_enable(USART0_IRQn, 0, 0);
  • 中斷服務(wù)函數(shù)
/*!
    rief      this function handles USART0 exception
    param[in]  none
    param[out] none
    
etval     none
*/
void USART0_IRQHandler(void)
{
    uint8_t ch;
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE))
    {
        /* read one byte from the receive data register */
        ch = (uint8_t)usart_data_receive(USART0);

        printf( "%c", ch );    //將接受到的數(shù)據(jù)直接返回打印
    }
}

在中斷服務(wù)程序中,接收到數(shù)據(jù)后立即輸出。

主函數(shù)代碼如下:

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    char str[20];

    //systick init
    sysTick_init();

    //usart init 115200 8-N-1
    com_init(COM1, 115200, 0, 1);

    printf("This is COM1
");	

    /* sprintf函數(shù)把格式化的數(shù)據(jù)寫入某個字符串  */  
    sprintf(str,"20%02d-%02d-%02d",22,05,15); 

    printf("%s
",str);

    while(1)
    {

    }
}

總結(jié)下串口接收的編程流程:

1.硬件初始化,時鐘初始化;

2.串口GPIO初始化,串口參數(shù)配置;

3.在main()函數(shù)中使能中斷接收;

4.編寫中斷回調(diào)函數(shù),處理接收的數(shù)據(jù),

【注】中斷接收函數(shù)只能觸發(fā)一次接收中斷,所以我們需要在中斷回調(diào)函數(shù)中再次調(diào)用中斷接收函數(shù)。這里可以對比下標(biāo)準(zhǔn)庫的流程。

5.2 實驗現(xiàn)象

將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息。

1683895226111qa7506d0p8

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5336

    瀏覽量

    120228
  • 串口通信
    +關(guān)注

    關(guān)注

    34

    文章

    1624

    瀏覽量

    55507
  • USART
    +關(guān)注

    關(guān)注

    1

    文章

    195

    瀏覽量

    30835
  • GD32
    +關(guān)注

    關(guān)注

    7

    文章

    403

    瀏覽量

    24326
收藏 人收藏

    評論

    相關(guān)推薦

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 1 開發(fā)環(huán)境搭建

    設(shè)計的GD32F207I-EVAL開發(fā)板。 GD32F207I-EVAL開發(fā)板使用 GD32F207IK作為主控制器,主頻120MHz、集成
    的頭像 發(fā)表于 05-07 23:35 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>1<b class='flag-5'>章</b> <b class='flag-5'>開發(fā)</b>環(huán)境搭建

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 4 GD32啟動流程詳解(Keil版)

    ,所有的一切都需要由開發(fā)者來設(shè)置,這里處理器是沒有堆棧,沒有中斷,更沒有外圍設(shè)備,這些工作是需要軟件來指定的,而且不同的CPU類型、不同大小的內(nèi)存和不同種類的外設(shè),其初始化工作都是不同的。本文將以GD32F207IK (基于Cortex-M3)為例進行講解。
    的頭像 發(fā)表于 05-10 09:00 ?1.7w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>4<b class='flag-5'>章</b> <b class='flag-5'>GD32</b>啟動流程詳解(Keil版)

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 7 定時器

    系統(tǒng)滴答定時器一般用來提供“心跳”作用,而GD32定時器最基本功能也是定時,可以設(shè)置不同時間長度的定時。定時器除了最基本的定時功能外,定時器與GPIO有掛鉤使得它可以發(fā)揮強大的作用,比如可以輸出
    的頭像 發(fā)表于 05-11 09:00 ?1.2w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>7<b class='flag-5'>章</b> 定時器

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 8 定時器

    開發(fā)環(huán)境: MDK:Keil 5.30 開發(fā)板:GD32F207I-EVAL MCU:GD32F207IK 1 PWM輸出的工作原理 脈沖寬度調(diào)制(PWM) ,是英文“Pulse Wi
    的頭像 發(fā)表于 05-12 22:14 ?7926次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>8<b class='flag-5'>章</b> 定時器

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 12 ADC

    GD32F2系列有 3 個逐次逼近型的ADC,精度為 12 位,有18個多路復(fù)用通道,可以轉(zhuǎn)換來自16個外部通道和2個內(nèi)部通道的模擬信號。其中ADC0 和 ADC1都有 16 個外部通道, ADC2
    的頭像 發(fā)表于 05-16 09:03 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>12<b class='flag-5'>章</b> ADC

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 14 內(nèi)部溫度傳感器

    GD32 有一個內(nèi)部的溫度傳感器,可以用來測量 CPU 及周圍的溫度(TA)。該溫度傳感器在內(nèi)部和 ADCx_IN16 輸入通道相連接,此通道把傳感器輸出的電壓轉(zhuǎn)換成數(shù)字值。溫度傳感器模擬輸入
    的頭像 發(fā)表于 05-17 08:58 ?5335次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>14<b class='flag-5'>章</b> 內(nèi)部溫度傳感器

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 15 低功耗

    GD32的工作電壓(VDD)為2.0~3.6V。通過內(nèi)置的電壓調(diào)節(jié)器提供所需的1.8V電源。當(dāng)主電源VDD掉電后,通過VBAT腳為實時時鐘(RTC)和備份寄存器提供電源。
    的頭像 發(fā)表于 05-17 08:59 ?8048次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>15<b class='flag-5'>章</b> 低功耗

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 16 RTC

    開發(fā)環(huán)境: MDK:Keil 5.30 開發(fā)板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RTC簡介
    的頭像 發(fā)表于 05-18 22:14 ?7151次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>16<b class='flag-5'>章</b> RTC

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 17 看門狗

    開發(fā)環(huán)境: MDK:Keil 5.30 開發(fā)板:GD32F207I-EVAL MCU:GD32F207IK GD32 有兩個看門狗, 一個是
    的頭像 發(fā)表于 06-03 16:00 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>17<b class='flag-5'>章</b> 看門狗

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 22 SPI

    SPI,是Serial Peripheral interface的縮寫,顧名思義就是串行外圍設(shè)備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。是一種高速全雙工的通信總線,它由
    的頭像 發(fā)表于 05-24 09:04 ?9390次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>22<b class='flag-5'>章</b> SPI

    【圖書分享】《STM32庫開發(fā)實戰(zhàn)指南

    GPIO入門之流水燈 4 深入分析流水燈例程 5 調(diào)試程序第二部分 庫開發(fā)中級 
    發(fā)表于 03-13 17:01

    如何快速開發(fā)GD32和涂鴉CBU模組通信?

    如何從0開始上手GD32系列單片機?如何通過手機APP控制燈的亮滅?如何快速開發(fā)GD32和涂鴉CBU模組通信?
    發(fā)表于 01-27 06:25

    GD32 MCU原理及固件庫開發(fā)指南》+讀后感

    。 2介紹GD32 MCU快速入門與開發(fā)平臺搭建的方法,包括對軟硬件開發(fā)平臺、調(diào)試工具、GD32
    發(fā)表于 06-06 21:52

    GD32開發(fā)實戰(zhàn)指南(基礎(chǔ)) 19 程序加密

    GD32通過讀取芯片唯一ID號來實現(xiàn)程序的保護,防止被抄襲。96位的產(chǎn)品唯一身份標(biāo)識所提供的參考號碼對任意一個GD32微控制器
    的頭像 發(fā)表于 05-20 09:10 ?4139次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>19<b class='flag-5'>章</b> 程序加密

    GD32 串口接受異常的幾個原因

    前面我們介紹過GD32 485發(fā)送時出現(xiàn)異常的最常見原因,有小伙伴反饋想要知道GD32 串口接受異常的可能原因,今天我們就來安排。
    的頭像 發(fā)表于 01-18 09:57 ?4217次閱讀
    <b class='flag-5'>GD32</b> <b class='flag-5'>串口</b>接受異常的幾個原因
    RM新时代网站-首页