開發(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è)備之間進(jìn)行全雙工數(shù)據(jù)交換。USART利用分?jǐn)?shù)波特率發(fā)生器提供寬范圍的波特率選擇。它支持同步單向通信和半雙工單線通信,也支持LIN(局部互連網(wǎng)),智能卡協(xié)議和IrDA(紅外數(shù)據(jù)組織)SIR ENDEC規(guī)范,以及調(diào)制解調(diào)器(CTS/RTS)操作。它還允許多處理器通信。使用多緩沖器配置的DMA方式,可以實(shí)現(xiàn)高速數(shù)據(jù)通信。
雖然USART既可以同步又可以異步,但是常見的最常用的就是使用功能的異步功能,如果作為異步通信就是UART(Universal Asynchronous Receiver and Transmitter),可以說,UART是USART的子集,但是同步通信相比異步通信多了一根時(shí)鐘同步信號線。
下面簡單介紹下同步和異步。
在同步通訊中,收發(fā)設(shè)備雙方會使用一根信號線表示時(shí)鐘信號,在時(shí)鐘信號的驅(qū)動(dòng)下雙方進(jìn)行協(xié)調(diào),同步數(shù)據(jù),見下圖。通訊中通常雙方會統(tǒng)一規(guī)定在時(shí)鐘信號的上升沿或下降沿對數(shù)據(jù)線進(jìn)行采樣。
在異步通訊中不使用時(shí)鐘信號進(jìn)行數(shù)據(jù)同步,它們直接在數(shù)據(jù)信號中穿插一些同步用的信號位,或者把主體數(shù)據(jù)進(jìn)行打包,以數(shù)據(jù)幀的格式傳輸數(shù)據(jù),見下圖,某些通訊中還需要雙方約定數(shù)據(jù)的傳輸速率,以便更好地同步。
在同步通訊中,數(shù)據(jù)信號所傳輸?shù)膬?nèi)容絕大部分就是有效數(shù)據(jù),而異步通訊中會包含有幀的各種標(biāo)識符,所以同步通訊的效率更高,但是同步通訊雙方的時(shí)鐘允許誤差較小,而異步通訊雙方的時(shí)鐘允許誤差較大。
從上面的介紹可以看出,USART以同步方式通信需要時(shí)鐘同步信號,但不需要額外的起始、停止位,可以實(shí)現(xiàn)更快的傳輸速度。但USART控制起來更復(fù)雜,因此本文主要講解以異步通信。
異步串行通信以字符為單位,即一個(gè)字符一個(gè)字符地傳送 。
串口外設(shè)的架構(gòu)圖看起來十分復(fù)雜,實(shí)際上對于軟件開發(fā)人員來說,我們只需要大概了解串口發(fā)送的過程即可。從下至上,我們看到串口外設(shè)主要由三個(gè)部分組成,分別是波特率控制、收發(fā)控制和數(shù)據(jù)存儲轉(zhuǎn)移。
- 波特率控制
波特率,即每秒傳輸?shù)亩M(jìn)制位數(shù),用b/s(bps)表示,通過對時(shí)鐘的控制可以改變波特率。在配置波特率時(shí),我們向波特比率寄存器 USART_BAUD寫入參數(shù),修改了串口時(shí)鐘的分頻值USARTDIV。USART_BAUD寄存器包括兩部分,分別是INTDIV(USARTDIV 的整數(shù)部分)和FRADIV(USARTDIV 的小數(shù))部分,最終,計(jì)算公式為 USARTDIV= INTDIV+(FRADIV/16)。
USARTDIV 是對串口外設(shè)的時(shí)鐘源進(jìn)行分頻的,USART0/5的系統(tǒng)時(shí)鐘為PCLK2, USART1/2和UART3/4/6/7的系統(tǒng)時(shí)鐘為PCLK1,串口的時(shí)鐘源經(jīng)過 USARTDIV 分頻后分別輸出作為發(fā)送器時(shí)鐘及接收器時(shí)鐘,控制發(fā)送和接收的時(shí)序。在使能USART之前,必須在時(shí)鐘控制單元使能系統(tǒng)時(shí)鐘。
- 收發(fā)控制
圍繞著發(fā)送器和接收器控制部分,有好多個(gè)寄存器 :STAT0、USART_CTL0、USART_CTL1、USART_CTL2和 STAT1,即USART 的三個(gè)控制寄存器(Control Register)及一個(gè)狀態(tài)寄存器(Status Register)。通過向寄存器寫入 各種控制參數(shù)來控制發(fā)送和接收,如奇偶校驗(yàn)位、停止位等,還包括對USART 中斷的控制;串口的狀態(tài)在任何時(shí)候都可以從狀態(tài)寄存器中查詢得到。其中停止位的配置如下圖所示。
- 發(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ā)送一個(gè)字節(jié)都需重復(fù)步驟7-8;
10.等待TC=1,發(fā)送完成。
在禁用USART或進(jìn)入低功耗狀態(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位。
接收器在使能后若檢測到一個(gè)有效的起始脈沖便開始接收碼流。在接收一個(gè)數(shù)據(jù)幀的過程中會檢測噪聲錯(cuò)誤,奇偶校驗(yàn)錯(cuò)誤,幀錯(cuò)誤和過載錯(cuò)誤。
當(dāng)接收到一個(gè)數(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寄存器的一個(gè)讀操作都可以清除RBNE位。
在接收過程中,需使能REN位,不然當(dāng)前的數(shù)據(jù)幀將會丟失。
以上對串口通信進(jìn)行了簡單介紹,為了方便各位讀者朋友更好的理解,在這里筆者將引入一個(gè)新的思想--系統(tǒng)分層思想。既然各位對著有意于嵌入式,那么必須得有對整個(gè)系統(tǒng)的架構(gòu)要有一定的認(rèn)知。對GD32裸機(jī)開發(fā),我們可以將分為三層:物理層、協(xié)議層和應(yīng)用層。前文講了這么多也是對串口協(xié)議進(jìn)行分析,常用的物理層的串口通信標(biāo)準(zhǔn)有232和485。
【注】UART和USART的區(qū)別
USART(universal synchronous asynchronous receiver and transmitte): 通用同步異步收發(fā)器,USART是一個(gè)串行通信設(shè)備,可以靈活地與外部設(shè)備進(jìn)行全雙工數(shù)據(jù)交換。
UART(universal asynchronous receiver and transmitter): 通用異步收發(fā)器,異步串行通信口(UART)就是我們在嵌入式中常說的串口,它還是一種通用的數(shù)據(jù)通信議。從名字上可以看出,USART在UART基礎(chǔ)上增加了同步功能,即USART是UART的增強(qiáng)型。
當(dāng)我們使用USART在異步通信的時(shí)候,它與UART沒有什么區(qū)別,但是用在同步通信的時(shí)候,區(qū)別就很明顯了:大家都知道同步通信需要時(shí)鐘來觸發(fā)數(shù)據(jù)傳輸,也就是說USART相對UART的區(qū)別之一就是能提供主動(dòng)時(shí)鐘。如GD32的USART可以提供時(shí)鐘支持ISO7816的智能卡接口。
USART是指單片機(jī)的一個(gè)端口模塊,可以根據(jù)需要配置成同步模式(SPI,I2C),也可以將其配置為異步模式,后者就是UART。所以說UART姑且可以稱之為一個(gè)與SPI,I2C對等的“協(xié)議”,而USART則不是一個(gè)協(xié)議,而是更應(yīng)該理解為一個(gè)實(shí)體。相比于同步通訊,UART不需要統(tǒng)一的時(shí)鐘線,接線更加方便。但是,為了正常的對信號進(jìn)行解碼,使用UART通訊的雙方必須事先約定好波特率,即每個(gè)碼元的長度。
關(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)。
3 串口硬件
串口的接口通過三個(gè)引腳與其他設(shè)備連接在一起。任何USART雙向通信至少需要兩個(gè)腳:接收數(shù)據(jù)輸入(RX)和發(fā)送數(shù)據(jù)輸出(TX)。
- RX:接收數(shù)據(jù)串行輸入。通過采樣技術(shù)來區(qū)別數(shù)據(jù)和噪音,從而恢復(fù)數(shù)據(jù)。
- TX :發(fā)送數(shù)據(jù)輸出。當(dāng)發(fā)送器被禁止時(shí),輸出引腳恢復(fù)到它的I/O端口配置。當(dāng)發(fā)送器被激活,并且不發(fā)送數(shù)據(jù)時(shí),TX引腳處于高電平。在單線和智能卡模式里,此I/O 口被同時(shí)用于數(shù)據(jù)的發(fā)送和接收。
板子使用串口0,接口用的232,但對于軟件來說,都是一樣的。
4 串口發(fā)送(重定向printf)
4.1 串口發(fā)送實(shí)現(xiàn)
下面筆者就用標(biāo)準(zhǔn)庫來操作串口0。
1.串口配置
- 串口0時(shí)鐘使能
串口1是掛載在 APB2 下面的外設(shè),所以使能函數(shù)為:
rcu_periph_clock_enable(RCU_USART0);
值得注意的是,不僅要打開串口的時(shí)鐘,還需要打開相應(yīng)GPIO的時(shí)鐘,最終的代碼如下:
rcu_periph_clock_enable(RCU_GPIOA);
- 配置串口GPIO
這個(gè)比較簡單,前面的章節(jié)已經(jīng)講過了,只需要注意的是,這里的GPIO不再是普通GPIO,要配置成復(fù)用功能,因此TX和RX分別配置成GPIO_MODE_AF_PP和GPIO_MODE_IN_FLOATING。
- 串口復(fù)位
當(dāng)外設(shè)出現(xiàn)異常的時(shí)候可以通過復(fù)位設(shè)置,實(shí)現(xiàn)該外設(shè)的復(fù)位,然后重新配置這個(gè)外設(shè)達(dá)到讓其重新工作的目的。一般在系統(tǒng)剛開始配置外設(shè)的時(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è)置校驗(yàn)位
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ù)為:波特率,字長,停止位,奇偶校驗(yàn)位,硬件數(shù)據(jù)流控制,模式(收,發(fā))。 我們可以根據(jù)需要設(shè)置這些參數(shù)。
- 串口使能
串口使能是通過函數(shù)usart_enable()來實(shí)現(xiàn)的,這個(gè)很容易理解,使用方法是:
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來實(shí)現(xiàn)的,這是一個(gè)雙寄存器。當(dāng)向該寄存器寫數(shù)據(jù)的時(shí)候,串口就會自動(dòng)發(fā)送,當(dāng)收到數(shù)據(jù)的時(shí)候,也是存在該寄存器內(nèi)。
GD32庫函數(shù)操作USART_DATA寄存器發(fā)送數(shù)據(jù)的函數(shù)是:
void usart_data_transmit(uint32_t usart_periph, uint16_t data);
通過該函數(shù)向串口寄存器 USART_DR 寫入一個(gè)數(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);
這個(gè)函數(shù)的第二個(gè)入口參數(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 | 按照十進(jìn)制整型數(shù)打印 |
%6d | 按照十進(jìn)制整型數(shù)打印,至少6個(gè)字符寬 |
%f | 按照浮點(diǎn)數(shù)打印 |
%6f | 按照浮點(diǎn)數(shù)打印,至少6個(gè)字符寬 |
%.2f | 按照浮點(diǎn)數(shù)打印,小數(shù)點(diǎn)后有2位小數(shù) |
%6.2f | 按照浮點(diǎn)數(shù)打印,至少6個(gè)字符寬,小數(shù)點(diǎn)后有2位小數(shù) |
%x | 按照十六進(jìn)制打印 |
%c | 打印字符 |
%s | 打印字符串 |
接下來就可以實(shí)現(xiàn)串口的發(fā)送了,這里對發(fā)送函數(shù)進(jìn)行封裝。
/**
* @brief 串口發(fā)送一個(gè)字節(jié)數(shù)據(jù)
* @param ch:待發(fā)送字符
* @retval None
*/
void usart_send_byte(uint8_t ch)
{
/* 發(fā)送一個(gè)字節(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ù)寫入某個(gè)字符串 */
sprintf(str,"20%02d-%02d-%02d",22,05,15);
usart_send_string((uint8_t*)str);
while(1)
{
}
}
下面筆者還要介紹一種常用的串口打印方式I/O重定向,也就是使用printf打印數(shù)據(jù)到終端,但是我們的裸機(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)));
下面我們以實(shí)現(xiàn)printf打印數(shù)據(jù)到USART(即重定義fputc函數(shù))的實(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ā)送一個(gè)字節(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ù)寫入某個(gè)字符串 */
sprintf(str,"20%02d-%02d-%02d",22,05,15);
printf("%s",str);
while(1)
{
}
}
完整代碼請查看配套程序,另外還需添加微庫以便支持printf。具體設(shè)置參看本節(jié)后文的小貼士部分。
我們來總結(jié)下串口發(fā)送的流程:
1.初始化硬件,時(shí)鐘;
2.USART 的GPIO初始化,USART參數(shù)初始化;
3.重定向printf
4.打印輸出
4.2 實(shí)驗(yàn)現(xiàn)象
將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息。
5 串口接收數(shù)據(jù)(中斷方式)
5.1 串口接收實(shí)現(xiàn)
中斷方式相對于與普通方式,還需要開啟中斷并且初始化 NVIC以及中斷服務(wù)函數(shù)。
- 開啟中斷
在接收到數(shù)據(jù)的時(shí)候(RBNE讀數(shù)據(jù)寄存器非空),我們要產(chǎn)生中斷,那么我們開啟中斷的方法是:
usart_interrupt_enable(USART0, USART_INT_RBNE); /* 使能串口0接收中斷 */
在發(fā)送數(shù)據(jù)結(jié)束的時(shí)候( 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ù)寫入某個(gè)字符串 */
sprintf(str,"20%02d-%02d-%02d",22,05,15);
printf("%s
",str);
while(1)
{
}
}
總結(jié)下串口接收的編程流程:
1.硬件初始化,時(shí)鐘初始化;
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 實(shí)驗(yàn)現(xiàn)象
將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息。
-
寄存器
+關(guān)注
關(guān)注
31文章
5372瀏覽量
121306 -
串口通信
+關(guān)注
關(guān)注
34文章
1628瀏覽量
55760 -
USART
+關(guān)注
關(guān)注
1文章
195瀏覽量
30996 -
GD32
+關(guān)注
關(guān)注
7文章
416瀏覽量
24508
發(fā)布評論請先 登錄
相關(guān)推薦
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第1章 開發(fā)環(huán)境搭建
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第4章 GD32啟動(dòng)流程詳解(Keil版)
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第7章 定時(shí)器
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第8章 定時(shí)器
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第12章 ADC
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第14章 內(nèi)部溫度傳感器
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第15章 低功耗
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第16章 RTC
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第17章 看門狗
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第22章 SPI
【圖書分享】《STM32庫開發(fā)實(shí)戰(zhàn)指南》
如何快速開發(fā)GD32和涂鴉CBU模組通信?
《GD32 MCU原理及固件庫開發(fā)指南》+讀后感
GD32開發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第19章 程序加密
GD32 串口接受異常的幾個(gè)原因
![<b class='flag-5'>GD32</b> <b class='flag-5'>串口</b>接受異常的幾個(gè)原因](https://file1.elecfans.com/web2/M00/BD/13/wKgZomWohVaATmpjAAAthlcn32E729.png)
評論