本篇文章由 VeriMake 旧版论坛中备份出的原帖的 Markdown 源码生成
原帖标题为:wujian100 编程——串口应用
原帖网址为:https://verimake.com/topics/113 (旧版论坛网址,已失效)
原帖作者为:Edwin(旧版论坛 id = 48,注册于 2020-05-21 13:58:16)
原帖由作者初次发表于 2020-07-06 00:17:16,最后编辑于 2021-08-05 19:55:46(编辑时间可能不准确)
截至 2021-12-18 14:27:30 备份数据库时,原帖已获得 1870 次浏览、3 个点赞、2 条回复
wujian100编程——串口应用
printf
wujian100里有printf("hello world!");在串口终端里就可以看见hello world!了。其实wujian100的sdk在board_init.c里初始化了串口,并且在startup.s里面发现在进入main之前调用这个函数对串口进行了初始化,所以这里的printf是通过串口打印出来的。
常用的库函数
wujian100在提供的sdk样例程序里提供了一个库,但是对于该库的说明没有:crying_cat_face: 本文按照常用的方式下选用一些函数按照本人的习惯进行使用总结。
关于串口的所有库函数,存放在wj_usi_wrap.c下,使用时需要添加 #include <drv_usi_usart.h>
:
初始化串口
该函数初始化串口,返回一个usart_handle_t类型的指针,该指针指向将要使用的串口。两个参数分别是:
idx: wujian100内的串口索引号,wujian100有三个uart0、uart1和uart2。
cb_event : 为指向串口中断处理函数,如果不用中断处理函数,配置为NULL
在串口不再使用时需要使用
该函数可以将初始化的串口释放,其实就是将初始化函数里生成的指针、地址指向了NULL。
配置串口
该函数配置串口,返回一个有符号整型数,如果小于0表示配置失败,返回0表示配置成功,该函数内部其实是配置了波特率、模式、奇偶校验、停止位、数据位数。例如:
csi_usart_config(usart, 115200, USART_MODE_ASYNCHRONOUS, USART_PARITY_NONE, USART_STOP_BITS_1, USART_DATA_BITS_8);
这条语句配置了usart(就是初始化函数返回的那个指针)波特率为115200,异步模式,无奇偶校验位,1位停止位,8位数据。
第一参数就是初始化函数返回的那个指针。其他参数在drv_usart.h里有各个参数可以配置的量,找到需要的填入对应的参数位置即可。
串口发送
1、发送字符
该函数比较简单,通过串口发送一个字符,两个参数一个是要使用的串口,一个是要发送的字符。返回值0表示发送成功,如果发送失败,程序会在该函数里死循环,而且只能发送一个字符。所以如果要发送字符或者字符串,使用printf是上策。
2、发送字符串
发送字符串首推printf。它的使用参见c语言的常用方式,这里要注意,它只能发送字符串(或者是一个字符的字符串:laughing: ),例如有一个变量i,想要在串口输出这个变量如果使用printf(i);这样会出现编译警告。应该将i格式化输出,printf("%d",i);如果i是整形的话。当然也可以按字符、浮点数发送等请自行尝试。
除了printf,发送字符串还有这个函数可以调用:
该函数返回值为0表示发送成功,不成功会返回错误码。该函数有三个参数第一个参数handle是指向要使用的串口的指针,第二参数data是指向要发送的内容的指针,第三个参数num是要发送的数据数量。
第二个参数data是指向要打印的内容的指针,所以,这个函数可以发送字符,字符串(数组存放),数据(变量存放)都是可以的,例如有变量i,要发送这个变量就可以直接发送了,不需要将i封装成字符串,只是要注意,在i作为第二个参数传入时要&i,就可以了。
该函数在发送数据时,我们怎么知道它发送完成了呢?特别是在发送大量数据时,我们怎么知道有么有发送完成呢?有两个方法。
a、查询发送完成标志来判断是否发送完成。通过调用函数:
该函数返回是一个结构体类型的变量。其中有个量就是发送忙标志。tx_busy。如果发送完成该结构体变量里的tx_busy为0,否则为1。
b、通过中断来判断是否发送完成。如果要使用中断来查询是否发送完成,需要在初始化时对于第二个参数cb_event不能是NULL,需要指向中断服务函数。例如我在中断中放一个全局变量tx_complete_flag,该变量在中断中改为1,表示发送完成。那么可以编写以下中断服务程序:
static void usart_event_cb(int32_t idx, uint32_t event)
{
tx_async_flag = 1;
}
接下来需要在初始化串口时这样做:
usart_handle_t usart;
usart = csi_usart_initialize(0, (usart_event_cb_t)usart_event_cb);
这样在发送完成时,硬件上会产生中断,进入usart_event_cb函数将tx_async_flag修改为1.我们通过全局变量tx_async_flag就可以知道是否发送完成。
串口接收
通过下面的函数可以通过串口接收数据:
该函数返回0表示接收函数运行成功。该函数有三个参数,第一个参数handle是指向要使用的串口的指针,第二参数data是指向接收的内容要存放的地方,第三个参数num是要接收的数据量。同样的我们怎么样才能知道接收完成呢?有下面两个方法。
a、查询接收完成标志来判断是否接收完成。通过调用函数:
该函数返回是一个结构体类型的变量。其中有个量就是发送忙标志。rx_busy。如果发送完成该结构体变量里的rx_busy为0,否则为1。
b、通过中断来判断是否接收完成。如果要使用中断来查询是否接收完成,需要在初始化时对于第二个参数cb_event不能是NULL,需要指向中断服务函数。例如我在中断中放一个全局变量rx_complete_flag,该变量在中断中改为1,表示接收完成。那么可以编写以下中断服务程序:
static void usart_event_cb(int32_t idx, uint32_t event)
{
rx_async_flag = 1;
}
我们发现和发送相似,使用中断方式下都可以进入中断,但是在初始化时,一个串口初始化是只有一个中断入口,所以无论发送还是接收,应该都是一个中断函数,那么在usart_event_cb函数中怎样才能知道是接收引起的中断还是发送引起的中断呢?此时可以通过usart_event_cb函数的第二个参数event的值知道是哪个中断,从而进行相应的操作。usart_event_cb改写如下:
static void usart_event_cb(int32_t idx, uint32_t event)
{
switch (event) {
case USART_EVENT_SEND_COMPLETE:
tx_async_flag = 1;
break;
case USART_EVENT_RECEIVE_COMPLETE:
rx_async_flag = 1;
break;
default:
break;
}
}
其中USART_EVENT_SEND_COMPLETE、USART_EVENT_RECEIVE_COMPLETE在文件drv_usart.h中声明了一个枚举类型中的量。可自行查看。他们分别表示发送完成中断,接收完成中断。
串口触发式接收
上节介绍了串口接收数据的方法,通过函数csi_usart_receive可以接收数据,在实际使用中,每次调用csi_usart_receive函数后,只有当有输入,并且接收完成后才会进入中断,或者通过查询标志位才能知道接收完成。例如我们想通过串口输入y让灯开始闪烁,输入n让灯停止闪烁,如果是如下代码:
这段代码根据中断标志判断是否接收完成,接收完成根据接收的值进行判断,控制灯的状态。这里要注意的是这里的csi_usart_receive(usart,g_data,1);
第三个参数,这里设置的是1,那么接收到一个字符就会进中断,如果设置成3,那么如果就收的数据不是三个,或者多余3个,那么就没有办法正常运行了,例如我们需要收到yes时候灯闪,收到no的时候灯停止闪,那么就会出问题,因为设置成3,当你使用串口调试助手发送yes时候能够让灯闪,但是发送no时就会有问题,一直不会进入中断,因为设置的是3,一直没有接收完成。如果串口调试助手发送数据多余设置值,这样代码一直在接收,那么接收部分出现接收上的顺序紊乱,所以一般我们不能多发数据。下面我们只讨论如果就收数据少于设置值的情况,即如果设置了3,却要接收no的情况。
上面的问题其实有一个简单方法,就是在串口助手发送时no后面多加一个空格,但是这个方法很不友好。其实wujian100硬件上有解决方法。wujian100在中断标志里有这样一个量USART_EVENT_RECEIVED,该中断是接收中断标志,但是这个中断只有在数据接收时触发,这时如果使用了串口硬件FIFO接收数据就可以解决上面问题,及使用如下函数:
该函数返回接收到的数据量。有三个参数,第一个参数handle是指向要使用的串口的指针,第二参数data是指向接收的内容要存放的地方,第三个参数num是要接收的数据量。这里的接收数据设置比实际接收的数据大了没有关系,因为接收数据存放在FIFO中,并且不需要去判断接收完成中断标志。
中断服务程序修改如下:
static usart_handle_t g_usart_handle;
static void usart_event_cb(int32_t idx, uint32_t event)
{
uint8_t g_data[15];
uint8_t ret;
switch (event) {
case USART_EVENT_SEND_COMPLETE:
tx_async_flag = 1;
break;
case USART_EVENT_RECEIVE_COMPLETE:
rx_async_flag = 1;
break;
case USART_EVENT_RECEIVED:
rx_trigger_flag = 1;
break;
default:
break;
}
}
这样串口在接收时就会触发USART_EVENT_RECEIVED中断,然后通过csi_usart_receive_query接收数据,该函数是通过硬件FIFO接收数据。数据存放在接收FIFO中,在csi_usart_receive_query中将FIFO中数据导出到需要存放的变量中。
主函数中就不需要调用接收函数了。修改为:
这样即使接收数据量设置为3,也能正常接收no。注意不要一次发送大于这个设置值,否则也会造成紊乱,有一定概率接收数据顺序不对。其次要注意这个和上一个接收不一样的是该中断是有接收(其实发送也会触发)就会进入中断,而csi_usart_receive函数是发送完成才会进入中断。这是有区别的。最后就是接收存放位置不一样,虽然看上去两个函数都是存放在数组里,其实csi_usart_receive_query函数是去硬件FIFO里将数据读入到数组中的。
思考
我们在调试代码时或者设计人机交互时多数情况都会使用串口,例如电机控制可以使用串口输出一个人机界面,通过串口输入不同指令,控制电机进行不同动作,那么使用以上介绍的函数就可以轻松设计出这样的人机调试界面,而且串口的交互不会影响电机的运作。
友情提醒
在文章开头截取了wujian100库里串口函数,还有一些函数可能会应用到,比如uint32_t csi_usart_get_tx_count(usart_handle_t handle)
可以返回发送的数据数量,这样的函数比较简单,就没有详细介绍了。但是有些库函数还是要看下源码再使用比较好,例如int32_t csi_usart_transfer(usart_handle_t handle, const void *data_out, void *data_in, uint32_t num)
这个函数在库里其实没有实现,它会返回一个错误,提示该函数没有实现。