CB5654串口入门必看——程序设计逻辑与用法
USART串行接口是嵌入式开发中最为常用的接口之一,为了节约大家底层调试的时间,在此分享一下当前已掌握的串口的用法,大致介绍一下在例程中的程序设计思路,欢迎广大开发者们和研电赛的小伙伴们参考借鉴。 本人毕竟水平有限,如有疏漏之处还望读者可以在评论区指正,感谢大家。
代码均已在CB5654开发板上测试成功,内附源码参考。 ps.为了方便理解,对结构体的定义有一些细微的修改,请勿轻易修改源码中的定义。
1、串口的分配
在CB5654开发板上共有三个uart串口,为方便起见在下文中我们将称之为串口1、串口2和串口3(但其在程序中对应的索引idx分别为0、1、2)。
- 串口1连接了声卡。
- 串口2默认作为console打印调试信息。
- 串口3默认未使用,可以供开发者自由配置。由于例程中并未使用串口3,所以串口3部分的配置代码需要开发者自行编写。在下文中以串口3为例详细介绍了串口的配置方法。
2、串口配置的流程解析
第一步:设备注册
在以往STM32平台下开发时,串口配置的第一步通常是串口初始化,包括引脚复用功能选择以及串口参数的配置。但是在CB5654的官方例程中,我们可以点开app_main.c文件中main主函数第一个执行的函数board_base_init();,我们将会跳转到base_init.c文件,可以看到对于串口的操作为以下三句。
uart_csky_register(0); /* UART1 */
uart_csky_register(1); /* UART2 */
uart_csky_register(2); /* UART3 */
可以看到此处的初始化函数只是传递了串口的索引,并未传递串口的波特率等参数,可见其并不是真正的初始化函数。此函数称为 串口注册函数。该函数到底执行了哪些操作呢,让我们跳转到该函数的实现(uart_drv.c文件)。
void uart_csky_register(int uart_idx){ //参数为串口的索引号
driver_register(&uart_driver.drv, NULL, uart_idx);
}
让我们继续跳转到driver_register函数的实现(device.c文件),我们可以将其称之为 设备注册函数:
int driver_register(driver_t *drv, void *config, int idx)
在探究这个函数的内容之前,让我们先弄清楚这个函数接收的参数类型,即driver_t和uart_driver_t两个结构体类型的具体含义
该函数是一个通用的设备注册函数,需要传给他的是一个设备驱动对象的指针,通俗来说就是需要告诉它即将注册的是什么类型的设备,和这个设备的索引是多少。以注册串口3为例,此时调用该函数注册的设备对象名为“uart”,索引为2。即通过设备对象类型以及索引我们就可以得到一个确定的设备。
接下来我们具体看一下设备驱动类型结构体是如何定义的,也可以理解为设备“类”是如何定义的。由于c语言中并不存在class,但此处无疑采用的是面向对象的编程思想,我们可以理解为高级语言中类的底层实现。
先说一下源码中的命名规则。_t结尾的含义是类型的意思,例如:
uart_driver_t uart_driver; //定义了一个uart_driver_t类型的结构体,名叫uart_driver
//此处的uart_driver_t为类型名(类名),
//uart_driver为变量名(对象名)。
此处的driver_t类是所有具体设备的父类。
struct driver_t { //设备驱动类
slist_t node; //链表的节点,用于在设备链表中寻找设备是否已注册
char *name; //设备的名称
char *type; //设备的类型
int16_t ref;
int16_t device_id;
aos_dev_t * (*init)(driver_t *drv, void *config, int id); //函数指针
void (*uninit)(aos_dev_t *dev); //设定了该类的对象可以具有这5种内置方法
int (*lpm)(aos_dev_t *dev, int state);
int (*open)(aos_dev_t *dev);
int (*close)(aos_dev_t *dev);
};
考虑到可能有的小伙伴并未接触过函数指针,此处稍微讲解一下。我们知道在高级语言中可以在类中定义函数(方法),在c语言中以函数指针的形式来实现同样的效果。 aos_dev_t (init)(driver_t drv, void config, int id);指的是一个名为init的函数指针,其指向一个返回值为aos_dev_t,参数为(driver_t, 空指针, int id)的函数。
我们在可以在类外定义一个返回值为aos_dev_t,参数为(driver_t , void*, int id)的函数,然后在创建设备对象时让该指针指向该函数。也就是说在创建这个类型的对象时只是规定了其具有的函数的返回值和参数,即只规定了函数的类型,函数的具体实现还是通过人为来设计的。
不难理解,一个设备驱动对象确实应该具有初始化,反初始化,打开和关闭这四种对设备的操作。(lpm不知道是啥。。欢迎在小伙伴评论区科普)
在driver_t父类的基础上,源码中又通过继承定义了一个子类:
struct uart_driver_t {
//继承自父类的内容
driver_t drv;
//子类自身独有的方法
int (*config)(aos_dev_t *dev, uart_config_t *config);
int (*set_buffer_size)(aos_dev_t *dev, uint32_t size);
int (*send)(aos_dev_t *dev, const void *data, uint32_t size); //串口发送函数
int (*recv)(aos_dev_t *dev, void *data, uint32_t size, unsigned int timeout_ms); //串口接收函数
int (*set_type)(aos_dev_t *dev, int type);
void (*set_event)(aos_dev_t *dev, void (*event)(aos_dev_t *dev, int event_id, void *priv), void *priv);
} ;
在了解完类与继承以及函数指针的概念之后,我们就可以对串口的注册过程进行具体讲解了。
首先我们再来回忆一下函数的调用过程:
此时我们就可以打开driver_register函数
int driver_register(driver_t *drv, void *config, int idx){
//检查设备指针是否为空,设备是否定义了初始化函数
aos_check_param(drv && drv->init);
//检查设备是都已经注册并添加至设备链表中
if (device_find(drv->name, idx) != NULL) return -1;
//调用设备驱动的init函数,该函数返回一个设备对象
aos_dev_t *dev = drv->init(drv, config, idx);
//如果返回了一个正确的设备对象
if (dev) {
dev->id = idx; //写入设备的索引 (其实在init函数执行时已经写入过了)
//用随机数函数生成设备的唯一标志
((driver_t *)(dev->drv))->device_id = alloc_device_id();
LIST_LOCK(); //线程锁,锁定链表,不允许其他线程对链表进行修改
//将设备对象的地址添加到设备链表中
slist_add_tail(&dev->node, &device_list);
LIST_UNLOCK();//解锁
return 0;
}
return -1;
}
对于开发来说,了解到这里知道设备注册的本质是三个步骤即可:
- 创建了设备对象
- 分配了设备对象的内置方法
- 将其添加到设备链表中
第二步:串口的参数配置
好了到了应用环节了,也是大家最感兴趣的环节了,这部分就不做详细的讲解了与传统的串口配置原理基本一致。
串口的配置一般分为引脚复用功能选择和参数配置两个过程。
1、串口引脚的初始化
为了使layout更加灵活,芯片的每个引脚都具有多个功能,可以供开发者自由配置在哪些引脚上实现外设的接口。
翻看手册我们可以得知在板上引出的串口3的RXD3余TXD3引脚分别对应芯片的40和41号引脚同时它们还有另外一个名字分别叫PD8和PD7。
所以直接告诉大家怎么配置吧。。。
打开工程路径下工程名BoardsCB5654V7.2.2pinmux_init.c
static void board_pinmux_config(void){
...
drv_pinmux_config(PD8, PD8_UART3_RX);
drv_pinmux_config(PD7, PD7_UART3_TX);
...
}
在该函数下添加上这两句,就配置好了。。。
2、串口的参数配置
这里最好是新建两个文件分别放在app文件夹中的src和inc文件夹中
app_uart3.c
aos_dev_t *uart3_dev = NULL;
static uint16_t uart3_buf_size = 128;
void app_uart3_init(int idx, uint32_t baud, uint16_t buf_size){{
uart_config_t config;
uart3_dev = device_open_id("uart", idx);
aos_check(uart3_dev, EIO);
if (uart3_dev != NULL) {
uart_config_default(&config);
config.baud_rate = baud;
uart_config(uart3_dev, &config);
uart_set_type(uart3_dev, UART_TYPE_GENERAL);
uart3_buf_size = buf_size;
}
}
第三步:串口发送函数的编写
也写在app_uart3.c里就好
int uart3_send_s(const char* data, uint32_t size)
{
extern usart_handle_t dev_get_handler(aos_dev_t *dev);
if(g_uart3_handle == NULL) return -1;
int i;
for(i = 0; i < size; ++i)
csi_usart_putchar(dev_get_handler(g_uart3_handle), *(data+i));
//uart_send(g_uart3_handle, &data, size);
return 0;
}
理论上使用内置的uart_send(g_uart3_handle, &data, size);函数进行字符串发送是最安全的,但是这个函数不知道为啥在每次发出来的数据前面会带8个字节的乱码,小伙伴们可以试一试,如果解决了希望可以在评论区分享一下,感谢。
附上app_uart3.h的写法
#ifndef _APP_UART3_H_
#define _APP_UART3_H_
#include <yoc_config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <aos/kernel.h>
#include <aos/debug.h>
#include <devices/hal/uart_impl.h>
#include "drv/usart.h"
void app_uart3_init(int idx, uint32_t baud, uint16_t buf_size);
int uart3_send_s(const char* data, uint32_t size);
#endif
不知道为啥头文件显示不出来, 头文件分别是:
yoc_config.h
stdio.h
stdlib.h
string.h
aos/kernel.h
aos/debug.h
devices/hal/uart_impl.h
drv/usart.h
到这里关于串口3的配置以及发送函数已经讲解完毕,以下分享一些调试的小技巧。
将TXD2与电平转换芯片的跳线帽给拔掉,然后用杜邦线将TXD3与芯片引脚连起来就可以调试啦,不需要自己外接CH340。
如果有不理解的地方欢迎在评论区提问。
串口接收函数还未调试,等调试完成会立即添加,敬请期待。
也非常期待别的小伙伴们能够分享一些调试的经验与技巧。
感谢阅读,如果对你的开发有帮助的话请帮忙点下赞哟~
文章来源:芯片开放社区
文章链接:https://occ.t-head.cn/community/post/detail?spm=a2cl5.14300636.0.0.1b87180fOJm8Ux&id=3794062565815496704