開發(fā)環(huán)境:
MDK:Keil 5.30
開發(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ā)生器提供寬范圍的波特率選擇。它支持同步單向通信和半雙工單線通信,也支持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),可以說(shuō),UART是USART的子集,但是同步通信相比異步通信多了一根時(shí)鐘同步信號(hào)線。
下面簡(jiǎn)單介紹下同步和異步。
在同步通訊中,收發(fā)設(shè)備雙方會(huì)使用一根信號(hào)線表示時(shí)鐘信號(hào),在時(shí)鐘信號(hào)的驅(qū)動(dòng)下雙方進(jìn)行協(xié)調(diào),同步數(shù)據(jù),見下圖。通訊中通常雙方會(huì)統(tǒng)一規(guī)定在時(shí)鐘信號(hào)的上升沿或下降沿對(duì)數(shù)據(jù)線進(jìn)行采樣。
在異步通訊中不使用時(shí)鐘信號(hào)進(jìn)行數(shù)據(jù)同步,它們直接在數(shù)據(jù)信號(hào)中穿插一些同步用的信號(hào)位,或者把主體數(shù)據(jù)進(jìn)行打包,以數(shù)據(jù)幀的格式傳輸數(shù)據(jù),見下圖,某些通訊中還需要雙方約定數(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ì)于軟件開發(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寫入參數(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ò)向寄存器寫入 各種控制參數(shù)來(lái)控制發(fā)送和接收,如奇偶校驗(yàn)位、停止位等,還包括對(duì)USART 中斷的控制;串口的狀態(tài)在任何時(shí)候都可以從狀態(tài)寄存器中查詢得到。其中停止位的配置如下圖所示。
- 發(fā)送配置步驟:
1.在USART_CTL0寄存器中置位UEN位,使能USART;
2.通過(guò)USART_CTL0寄存器的WL設(shè)置字長(zhǎng);
3.在USART_CTL1寄存器中寫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寄存器寫數(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。在多級(jí)緩存通信方式(DENT=1)下,直接向TC寫0,也能清TC。
- 接收配置步驟:
1.寫USART_CTL0寄存器的WL位去設(shè)置字長(zhǎng);
2.在USART_CTL1寄存器中寫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è)有效的起始脈沖便開始接收碼流。在接收一個(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ī)開發(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沒有什么區(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姑且可以稱之為一個(gè)與SPI,I2C對(duì)等的“協(xié)議”,而USART則不是一個(gè)協(xié)議,而是更應(yīng)該理解為一個(gè)實(shí)體。相比于同步通訊,UART不需要統(tǒng)一的時(shí)鐘線,接線更加方便。但是,為了正常的對(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引腳處于高電平。在單線和智能卡模式里,此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);
值得注意的是,不僅要打開串口的時(shí)鐘,還需要打開相應(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)剛開始配置外設(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)向該寄存器寫數(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 寫入一個(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ò)枚舉類型定義的:
/* 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ù)寫入某個(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ù),需要通過(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ù)寫入某個(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)象
將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息。
5 串口接收數(shù)據(jù)(中斷方式)
5.1 串口接收實(shí)現(xiàn)
中斷方式相對(duì)于與普通方式,還需要開啟中斷并且初始化 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)先級(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ù)寫入某個(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ù)。這里可以對(duì)比下標(biāo)準(zhǔn)庫(kù)的流程。
5.2 實(shí)驗(yàn)現(xiàn)象
將程序編譯好下載到板子中,打開串口助手,按下圖設(shè)置相應(yīng)參數(shù),按下板子的復(fù)位按鍵,在接收區(qū)可以看到如下信息。
-
寄存器
+關(guān)注
關(guān)注
31文章
5336瀏覽量
120230 -
串口通信
+關(guān)注
關(guān)注
34文章
1624瀏覽量
55508 -
USART
+關(guān)注
關(guān)注
1文章
195瀏覽量
30835 -
GD32
+關(guān)注
關(guān)注
7文章
403瀏覽量
24328
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論