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

評(píng)論