CC2640R2F BLE5.0 CC2640R2F UART驅動
UART驅動
這一節我們講一下UART驅動的分層實現,UART APIs以及如何呼叫UART APIs來實現基本的串列埠列印。
概述
UART用於晶片和串列埠之間的資料傳輸,UART驅動程式經過多層的封裝簡化了應用程式對UART外設的讀寫操作,應用程式開發者只需要呼叫封裝好的驅動介面就可以操作串列埠進行讀寫了。當然UART也有多種操作模式,例如:阻塞,非阻塞,輪詢以及文字/二進位制模式,我們可以通過相應的引數配置來選擇需要的模式繼而進行資料傳輸。
UART驅動的分層實現
雖然我們在應用層直接呼叫幾個驅動介面就可操作UART進行讀寫,但是在驅動程式內部從介面函式到底層硬體操作是通過了多層封裝的。如圖1所示是UART驅動程式的分層實現圖:
圖1 UART驅動程式的分層實現
由圖1我們可以看到,應用程式開發者只需要直接呼叫中介軟體層的驅動介面(例如:UART_init,UART_open等等)就可以實現UART驅動功能,這裡的中介軟體層就是我們程式中的UART.c和UART.h所在層。這一層規範統一了應用程式的呼叫介面,也就是說對於TI不同型別的晶片平臺它們在這一層給出的介面都是一樣的。應用層都是呼叫相同的介面來實現UART功能,這樣做的好處在於增強了程式的可移植性,不管你的平臺怎麼換,我的應用程式都是不變的,因為呼叫的介面相同。
中介軟體層往下就是業務邏輯層,從業務邏輯層開始往下根據不同的晶片平臺其介面封裝實現就不盡相同了。這裡我們以CC26XX晶片平臺為例,業務邏輯層就位於UARTCC26XX.c和UARTCC26XX.h所在的層。這一層主要是呼叫下一層驅動庫中的函式進行一些邏輯操作,實現相應驅動功能介面的封裝。需要注意的是這一層封裝的驅動介面函式被全部放在一個函式指標結構體中,如List1所示,中介軟體層不直接呼叫這些驅動介面,而是通過一個配置檔案(CC2640R2_LAUNCHXL.c)將裝有驅動介面指標的結構體指標註冊到UART_config中,如List2所示,這樣中介軟體層通過呼叫UART_config中的結構體指標就可以呼叫業務邏輯層的驅動介面了。
List1:業務邏輯層驅動介面指標結構體
const UART_FxnTable UARTCC26XX_fxnTable = {
UARTCC26XX_close,
UARTCC26XX_control,
UARTCC26XX_init,
UARTCC26XX_open,
UARTCC26XX_read,
UARTCC26XX_readPolling,
UARTCC26XX_readCancel,
UARTCC26XX_write,
UARTCC26XX_writePolling,
UARTCC26XX_writeCancel
};
List2:UART_config中的驅動介面結構體指標註冊
const UART_Config UART_config[CC2640R2_LAUNCHXL_UARTCOUNT] = { { .fxnTablePtr = &UARTCC26XX_fxnTable,//業務邏輯層介面函式結構體指標註冊 .object = &uartCC26XXObjects[CC2640R2_LAUNCHXL_UART0], .hwAttrs = &uartCC26XXHWAttrs[CC2640R2_LAUNCHXL_UART0] }, };
業務邏輯層再往下就是驅動庫層(driver library),業務邏輯層是直接呼叫這一層的介面函式來實現相應功能的。驅動庫層位於uart.c和uart.h所在的層,這一層就開始與硬體接觸,進行相應暫存器操作來實現串列埠驅動了。
UART的驅動配置
在上文中我們已經提到UART的配置陣列UART_config[],它位於相應晶片平臺的配置檔案中,這裡我們以CC26XX晶片平臺為例,其配置檔案為CC2640R2_LAUNCHXL.c。如List3所示,是CC2640R2_LAUNCHXL.c中關於UART的配置程式碼段。
List3:UART的配置程式碼段
/* * =============================== UART =============================== */ #include <ti/drivers/UART.h> #include <ti/drivers/uart/UARTCC26XX.h> UARTCC26XX_Object uartCC26XXObjects[CC2640R2_LAUNCHXL_UARTCOUNT]; const UARTCC26XX_HWAttrsV2 uartCC26XXHWAttrs[CC2640R2_LAUNCHXL_UARTCOUNT] = { { .baseAddr = UART0_BASE, .powerMngrId = PowerCC26XX_PERIPH_UART0, .intNum = INT_UART0_COMB, .intPriority = ~0, .swiPriority = 0, .txPin = CC2640R2_LAUNCHXL_UART_TX, .rxPin = CC2640R2_LAUNCHXL_UART_RX, .ctsPin = PIN_UNASSIGNED, .rtsPin = PIN_UNASSIGNED } }; const UART_Config UART_config[CC2640R2_LAUNCHXL_UARTCOUNT] = { { .fxnTablePtr = &UARTCC26XX_fxnTable, .object = &uartCC26XXObjects[CC2640R2_LAUNCHXL_UART0], .hwAttrs = &uartCC26XXHWAttrs[CC2640R2_LAUNCHXL_UART0] }, }; const uint_least8_t UART_count = CC2640R2_LAUNCHXL_UARTCOUNT;
我們可以看到UART_config[]陣列中的元素有三個引數,分別是.fxnTablePtr
,.object
,.hwAttrs
,下面我們分別來看一下這三個引數的意義。
- .fxnTablePtr
.fxnTablePtr
裡面放的就是我們驅動具體的實現函式,這些驅動函式就是來自業務邏輯層。我們看到這裡它被賦值成UARTCC26XX_fxnTable
的指標,這個結構體就是List1中所示的業務邏輯層的驅動函式列表,在這裡進行賦值配置之後,中間層的介面函式就可以連結使用它們了。 - .object
.object
是用來存放UART的各種引數資料的,例如控制引數,讀寫引數等。 - .hwAttrs
.hwAttrs
是用來存放UART硬體配置引數的陣列,如List3所示,這些硬體引數是需要我們在使用UART之前配置好的。
UART驅動介面函式
我們在應用程式層實現UART功能的時候能夠呼叫中介軟體層的介面函式有11個。其功能,形參以及返回值如下表所示
void UART_init(void)
- 函式功能:根據配置資訊對UART模組進行初始化
- 形參:無
- 返回值:無
- 注意事項:在呼叫此函式之前,UART_config引數必須配置完成,在呼叫該函式之後才能呼叫其他UART介面函式
void UART_Params_init(UART_Params *params)
- 函式功能:將UART_Params結構體中的引數初始化為預設值
- 形參:
params
,UART_Params型別的結構體指標,存放UART的相關引數 - 返回值:無
- 注意事項:該函式將UART_Params結構體中的引數全部初始化為預設值,在接下來的程式中你可以根據具體應用的需要修改這些引數值
UART_Handle UART_open(uint_least8_t index, UART_Params *params)
- 函式功能:初始化並開啟指定的UART外設介面
- 形參:
index
UART外設介面的索引;params
UART_Params型別的結構體指標,存放UART的相關引數 - 返回值:
UART_Handle
如果成功開啟UART外設介面就返回該介面配置陣列(UART_config)的控制代碼,如果發生錯誤或者需要開啟的UART外設介面已經被開啟,則會返回NULL指標
int_fast16_t UART_control(UART_Handle handle, uint_fast16_t cmd, void *arg)
- 函式功能:通過給定的UART_Handle來處理相應的命令事務
- 形參:
handle
UART_open中返回的控制代碼;cmd
需要處理的命令事務;arg
不用命令事務可能需要的不同型別的引數指標 - 返回值:處理特定命令返回的值,如果操作失敗,會返回一個負值
- 注意事項:必須在UART_open呼叫之後才能呼叫此函式
void UART_close(UART_Handle handle)
- 函式功能:關閉UART_Handle指定的UART外設介面
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼,以此來關閉外設介面 - 返回值:無
- 注意事項:必須在UART_open()呼叫之後才能呼叫此函式,在呼叫該函式之前必須先呼叫UART_readCancel()或UART_writeCancel()分別取消正在進行的非同步讀取或寫入
int_fast32_t UART_write(UART_Handle handle, const void *buffer, size_t size)
- 函式功能:通過使能中斷來向串列埠寫入資料
- 形參:
handle
UART_open中返回的控制代碼;buffer
一個只讀指標,其中包含了將要寫入的資料;size
需要寫入資料的位元組數 - 返回值:返回已經寫入串列埠的位元組數,如果發生錯誤,則返回UART_ERROR.
- 注意事項:在UART_MODE_CALLBACK下返回值始終為0
int_fast32_t UART_writePolling(UART_Handle handle, const void *buffer, size_t size)
- 函式功能:利用輪詢的方式寫入資料,不使能中斷,與UART_write()的使用是互斥的。
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼;buffer
一個只讀指標,其中包含了將要寫入的資料;size
需要寫入資料的位元組數 - 返回值:返回已經寫入UART外設介面的位元組數,如果發生錯誤,則返回UART_ERROR.
- 注意事項:在所有的資料都被寫入UART外設介面之前,該函式不會返回值
void UART_writeCancel(UART_Handle handle)
- 函式功能:取消UART_write()函式呼叫的功能
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼 - 返回值:無
- 注意事項:在函式只適用於在UART_MODE_CALLBACK模式下取消非同步UART_write()操作。
int_fast32_t UART_read(UART_Handle handle, void *buffer, size_t size)
- 函式功能:通過使能中斷來讀取UART外設介面的資料
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼;buffer
需要讀取資料的目的地址;size
需要讀取資料的位元組數 - 返回值:返回從UART外設介面讀取的位元組數,如果發生錯誤則返回UART_ERROR
- 注意事項:在UART_MODE_BLOCKING模式下,UART_read()會阻止任務的執行,直到緩衝區的資料被讀完。在 UART_MODE_CALLBACK模式下,不會阻止任務的執行。UART_read()與UART_readPolling()互斥,本來同時使用
int_fast32_t UART_readPolling(UART_Handle handle, void *buffer, size_t size)
- 函式功能:使用輪詢的方式讀取資料,不使能中斷,與UART_write()的使用是互斥的
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼;buffer
需要讀取資料的額緩衝區地址;size
需要讀取資料的位元組數 - 返回值:返回從UART外設介面讀取的位元組數,如果發生錯誤則返回UART_ERROR
- 注意事項:在指定的資料被讀取完之前,UART_readPolling不會返回
void UART_readCancel(UART_Handle handle)
- 函式功能:取消UART_read()函式的功能
- 形參:
handle
從UART_open()中返回的UART外設介面配置陣列的控制代碼 - 返回值:無
- 注意事項:僅適用於在UART_MODE_CALLBACK模式下取消非同步UART_read()函式的功能
利用UART串列埠實現資料的列印
下面我們呼叫中介軟體層提供的UART介面來建立一個獨立的執行緒,實現“hello world!”的串列埠列印。這裡我們直接給出主執行緒的介面呼叫例程,如List4所示:
List4:UART外設介面列印主執行緒程式碼實現
/* * ======== uartprintf.c ======== */ #include <stdint.h> #include <stddef.h> /* Driver Header files */ #include <ti/drivers/UART.h> /* Example/Board Header files */ #include "Board.h" /* * ======== mainThread ======== */ void *mainThread(void *arg0) { char input; const char string[] = "hello world!\r\n"; UART_Handle uart; UART_Params uartParams; /* Call driver init functions */ UART_init(); /* Create a UART with data processing off. */ UART_Params_init(&uartParams); uartParams.writeDataMode = UART_DATA_BINARY; uartParams.readDataMode = UART_DATA_BINARY; uartParams.readReturnMode = UART_RETURN_FULL; uartParams.readEcho = UART_ECHO_OFF; uartParams.baudRate = 115200; uart = UART_open(Board_UART0, &uartParams); if (uart == NULL) { /* UART_open() failed */ while (1); } UART_write(uart, string, sizeof(string)); while(1){ UART_read(uart, &input, 1); UART_write(uart, &input, 1); } }
- 我們可以看到在mainThread中首先呼叫
UART_init()
來初始化UART外設介面配置,這裡的配置資訊是在配置檔案CC2640R2_LAUNCHXL.c中設定的,上文UART的驅動配置中有講到,所以在我們呼叫UART_init()
之前,必須要在CC2640R2_LAUNCHXL.c中完成所有的配置。 - 呼叫
UART_Params_init()
將uartParams結構體中的引數全部初始化為預設值。 - 初始化UART_Params引數後,根據需要我們可以對一些引數進行重新賦值。
- 在UART外設介面引數設定完成之後,我們就可以呼叫UART_open()來開啟我們指定的UART外設介面了,這裡我們需要指定某個定義的UART外設介面,在我們的例程中是開啟的Board_UART0,如果指定UART外設介面開啟成功,則會返回一個控制代碼,以後我們就通過這個控制代碼來執行UART外設介面的相關操作了。如果UART外設介面開啟失敗或者指定的UART外設介面已經被開啟使用則會返回一個空指標。
- 成功開啟UART外設介面之後我們就可以操作UART外設介面讀寫資料了,這裡我們首先呼叫
UART_write()
將字串hello world!
打印出來。然後進入無限迴圈一直執行接收串列埠資料,又將串列埠資料打印出來的操作。
下面我們看一下如何編譯,利用串列埠工具除錯串列埠功能:
- 新建一個
.c
檔案,將該段程式碼拷貝到.c
檔案中,這裡我們給該.c
檔案命名為uartdebug.c
。 - 將
uartdebug.c
檔案儲存在C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\drivers\uartecho
目錄下。 - 在
C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\drivers\uartecho\tirtos\iar
資料夾下開啟uartecho.eww
IAR工程,這時我們在IAR可以看到如圖2所示的工程目錄。 - 選中
uartcho.c
檔案,點選右鍵,選擇remove,這時你可以看到uartcho.c
檔案被移出工程。 - 在工程目錄下選中
source files
資料夾,選擇Add條目下的Add Files...,然後將我們存放的uartdebug.c
新增進工程專案, 如圖3所示。 - 選中工程檔案
uartecho-Debug
點選右鍵,選擇Rebuild All
編譯工程。 - 保證已經下載了藍芽協議棧映象檔案的除錯板接入電腦,點選“編譯除錯”按鈕,如圖4所示,將程式下載到除錯板中。
- 開啟串列埠除錯工具,接入相應串列埠,點選執行按鈕,如圖4所示。
- 在串列埠除錯介面,可以看到
hello world!
被打印出來,如圖5所示。 - 利用串列埠除錯工具向串列埠傳送字串
test
,如圖6所示。 - 我們在程式最後的無限迴圈裡不斷在讀取串列埠資料,並將其打印出來,上一步傳送了
test
之後,字串就會被讀取然後在串列埠除錯工具中打印出來,如圖7所示。 - 至此,我們就利用串列埠進行了資料的收發
加入我們
文章所有程式碼、工具、文件開源。加入我們QQ群 591679055獲取更多支援,共同研究CC2640R2F&BLE5.0。