# UART驱动 # 这一节我们讲一下UART驱动的分层实现,UART APIs以及如何调用UART APIs来实现基本的串口打印。 ## 概述 ## UART用于芯片和串行端口之间的数据传输,UART驱动程序经过多层的封装简化了应用程序对UART外设的读写操作,应用程序开发者只需要调用封装好的驱动接口就可以操作串口进行读写了。当然UART也有多种操作模式,例如:阻塞,非阻塞,轮询以及文本/二进制模式,我们可以通过相应的参数配置来选择需要的模式继而进行数据传输。 ## UART驱动的分层实现 ## 虽然我们在应用层直接调用几个驱动接口就可操作UART进行读写,但是在驱动程序内部从接口函数到底层硬件操作是通过了多层封装的。如图1所示是UART驱动程序的分层实现图: ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/qudongfencengtu.png) 图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:业务逻辑层驱动接口指针结构体 ```C 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中的驱动接口结构体指针注册 ```C 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的配置代码段 ```C /* * =============================== UART =============================== */ #include #include 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个。其功能,形参以及返回值如下表所示 >**提示**:本地文档链接在`C:\ti\simplelink_cc2640r2_sdk_1_40_00_45\docs\tidrivers\doxygen\html\_u_a_r_t_8h.html` 1. `void UART_init(void)` * 函数功能:根据配置信息对UART模块进行初始化 * 形参:无 * 返回值:无 * 注意事项:在调用此函数之前,UART_config参数必须配置完成,在调用该函数之后才能调用其他UART接口函数 2. `void UART_Params_init(UART_Params *params)` * 函数功能:将UART_Params结构体中的参数初始化为默认值 * 形参:`params`,UART_Params类型的结构体指针,存放UART的相关参数 * 返回值:无 * 注意事项:该函数将UART_Params结构体中的参数全部初始化为默认值,在接下来的程序中你可以根据具体应用的需要修改这些参数值 3. `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指针 4. `int_fast16_t UART_control(UART_Handle handle, uint_fast16_t cmd, void *arg)` * 函数功能:通过给定的UART_Handle来处理相应的命令事务 * 形参:`handle`UART_open中返回的句柄;`cmd`需要处理的命令事务;`arg`不用命令事务可能需要的不同类型的参数指针 * 返回值:处理特定命令返回的值,如果操作失败,会返回一个负值 * 注意事项:必须在UART_open调用之后才能调用此函数 5. `void UART_close(UART_Handle handle)` * 函数功能:关闭UART_Handle指定的UART外设接口 * 形参:`handle`从UART_open()中返回的UART外设接口配置数组的句柄,以此来关闭外设接口 * 返回值:无 * 注意事项:必须在UART_open()调用之后才能调用此函数,在调用该函数之前必须先调用UART_readCancel()或UART_writeCancel()分别取消正在进行的异步读取或写入 6. `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 7. `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外设接口之前,该函数不会返回值 8. `void UART_writeCancel(UART_Handle handle)` * 函数功能:取消UART_write()函数调用的功能 * 形参:`handle`从UART_open()中返回的UART外设接口配置数组的句柄 * 返回值:无 * 注意事项:在函数只适用于在UART_MODE_CALLBACK模式下取消异步UART_write()操作。 9. `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()互斥,本来同时使用 10. `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不会返回 11. `void UART_readCancel(UART_Handle handle)` * 函数功能:取消UART_read()函数的功能 * 形参:`handle`从UART_open()中返回的UART外设接口配置数组的句柄 * 返回值:无 * 注意事项:仅适用于在UART_MODE_CALLBACK模式下取消异步UART_read()函数的功能 ## 利用UART串口实现数据的打印 ## 下面我们调用中间件层提供的UART接口来建立一个独立的线程,实现“hello world!”的串口打印。这里我们直接给出主线程的接口调用例程,如List4所示: List4:UART外设接口打印主线程代码实现 ```C /* * ======== uartprintf.c ======== */ #include #include /* Driver Header files */ #include /* 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); } } ``` 1. 我们可以看到在**mainThread**中首先调用`UART_init()`来初始化UART外设接口配置,这里的配置信息是在配置文件CC2640R2_LAUNCHXL.c中设置的,上文**UART的驱动配置**中有讲到,所以在我们调用`UART_init()`之前,必须要在CC2640R2_LAUNCHXL.c中完成所有的配置。 2. 调用`UART_Params_init()`将uartParams结构体中的参数全部初始化为默认值。 3. 初始化UART_Params参数后,根据需要我们可以对一些参数进行重新赋值。 4. 在UART外设接口参数设置完成之后,我们就可以调用UART_open()来开启我们指定的UART外设接口了,这里我们需要指定某个定义的UART外设接口,在我们的例程中是打开的**Board_UART0**,如果指定UART外设接口打开成功,则会返回一个句柄,以后我们就通过这个句柄来执行UART外设接口的相关操作了。如果UART外设接口打开失败或者指定的UART外设接口已经被打开使用则会返回一个空指针。 5. 成功打开UART外设接口之后我们就可以操作UART外设接口读写数据了,这里我们首先调用`UART_write()`将字符串`hello world!`打印出来。然后进入无限循环一直执行接收串口数据,又将串口数据打印出来的操作。 下面我们看一下如何编译,利用串口工具调试串口功能: 1. 新建一个`.c`文件,将该段代码拷贝到`.c`文件中,这里我们给该`.c`文件命名为`uartdebug.c`。 2. 将`uartdebug.c`文件保存在`C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\drivers\uartecho`目录下。 3. 在`C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\drivers\uartecho\tirtos\iar`文件夹下打开`uartecho.eww`IAR工程,这时我们在IAR可以看到如图2所示的工程目录。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/IARproject1.png) 4. 选中`uartcho.c`文件,点击右键,选择**remove**,这时你可以看到`uartcho.c`文件被移出工程。 5. 在工程目录下选中`source files`文件夹,选择**Add**条目下的**Add Files...**,然后将我们存放的`uartdebug.c`添加进工程项目, 如图3所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/IARproject2.png) 6. 选中工程文件`uartecho-Debug`点击右键,选择`Rebuild All`编译工程。 7. 保证已经下载了蓝牙协议栈镜像文件的调试板接入电脑,点击“编译调试”按钮,如图4所示,将程序下载到调试板中。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/program.png) 8. 打开串口调试工具,接入相应串口,点击运行按钮,如图4所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/debug.png) 9. 在串口调试界面,可以看到`hello world!`被打印出来,如图5所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/helloworld.png) 10. 利用串口调试工具向串口发送字符串`test`,如图6所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/test.png) 11. 我们在程序最后的无限循环里不断在读取串口数据,并将其打印出来,上一步发送了`test`之后,字符串就会被读取然后在串口调试工具中打印出来,如图7所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/peripheral_driver/uart/images/testprintf.png) 12. 至此,我们就利用串口进行了数据的收发 ## 加入我们 ## 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http://shang.qq.com/wpa/qunwpa?idkey=d94f12d37c3b37892af4b757c6dc34bea140f3f3128a8d68e556a3d728148e85)获取更多支持,共同研究CC2640R2F&BLE5.0。

CC2640R2F&BLE5.0-乐控畅联 © Copyright 2017, 成都乐控畅联科技有限公司.