> 文档中心 > 联盛德 HLK-W801(三):在SDK粥中盛出UART

联盛德 HLK-W801(三):在SDK粥中盛出UART


联盛德 HLK-W801的UART的使用

这sdk有些乱,有的外设历程也乱,其中这个uart的相关api就挺乱的

文章目录

  • 联盛德 HLK-W801的UART的使用
    • 1. 开发环境
    • 2. 使用sdk初始化串口的步骤
      • 2.1 新建文件夹和文件
      • 2.2 解释代码
        • 2.2.1 初始化串口解析
        • 2.2.2 回调函数(坑)
        • 2.2.3 线程内用信号量超时来分包
    • 3. 运行结果

1. 开发环境

  • 系统:win10
  • 开发板:联盛德 HLK-W801开发板
  • SDK版本:wm_sdk_w80x_20211115
  • IDE:cdk-windows-V2.12.1

2. 使用sdk初始化串口的步骤

2.1 新建文件夹和文件

在工程下新建文件夹user,然后在新建一个uart.c和uart.h文件,添加到工程,如图:

在这里插入图片描述

注意:uaer文件夹下的其他文件不用管,那是我添加的其他测试文件。

在uart.h里面写下面程序:

#ifndef __UART_H_#define __UART_H_#ifdef __cplusplusextern "C"{#endifvoid create_my_uart_task(void);#ifdef __cplusplus}#endif#endif //__UART_H_

在uart.h里面写下面程序:

以串口1为例

#include "uart.h"#include "wm_uart.h"#include "wm_include.h"#include "wm_config.h"#include //task资源#define  UART_TASK_SIZE      1024static OS_STK uart_task_stk[UART_TASK_SIZE];//存储接收到的数据char rx_fifo_buf[1024] = {0};//存储收到数据的长度uint16_t rx_length = 0;//定义一个信号量tls_os_sem_t * sem_rx = NULL;//接收回调函数s16 my_uart_rx_callback(u16 len, void* user_data){//将接收到字符累加,这里很坑,下面章节会具体讲这个函数。rx_length += len;tls_os_sem_release(sem_rx);    return WM_SUCCESS;}//串口初始化void my_uart_init(){//初始化uart引脚,函数内部会自动开启串口时钟,wm_uart1_rx_config(WM_IO_PB_07);wm_uart1_tx_config(WM_IO_PB_06);//配置uart参数tls_uart_options_t opt;opt.baudrate = UART_BAUDRATE_B9600;//波特率opt.paritytype = TLS_UART_PMODE_DISABLED;//无奇偶校验opt.stopbits = TLS_UART_ONE_STOPBITS;//一个停止位opt.charlength = TLS_UART_CHSIZE_8BIT;//数据长度opt.flow_ctrl = TLS_UART_FLOW_CTRL_NONE;//没有流控制//初始化串口,这个函数内部会开启串口中断if (WM_SUCCESS != tls_uart_port_init(TLS_UART_1, &opt, 0)){printf("uart1 init error\n");}//为串口绑定接收回调函数tls_uart_rx_callback_register((u16) TLS_UART_1, (s16(*)(u16, void*))my_uart_rx_callback, NULL);    //发送完回调函数//tls_uart_tx_callback_register(2, (s16(*) (struct tls_uart_port *))tls_uart_free_tx_sent_data);}//uart线程void my_uart_task(void *sdata){uint16_t rx_len = 0;//用于判断信号量是否成功获取tls_os_status_t os_status  = TLS_OS_ERROR;//创建信号量,用于回调函数和线程之间通信tls_os_sem_create(&sem_rx, 0);//初始化串口my_uart_init();for(;;){os_status = tls_os_sem_acquire(sem_rx, 20);if(os_status){if(rx_length > 0){rx_len = tls_uart_read(TLS_UART_1, rx_fifo_buf, rx_length);rx_length = 0;tls_uart_write(TLS_UART_1, rx_fifo_buf, rx_len); //输出}}}}//创建uart线程void create_my_uart_task(void){tls_os_task_create(NULL, NULL,   my_uart_task,   NULL,   (void *)uart_task_stk,   /* task's stack start address */   UART_TASK_SIZE * sizeof(u32), /* task's stack size, unit:byte */   32,   0);}

然后在主函数中调用create_my_uart_task函数,如图:
在这里插入图片描述

2.2 解释代码

2.2.1 初始化串口解析

  1. 初始化串口对应的引脚。按住[Ctrl]+鼠标左键,点击函数可以跳转到函数定义,由定义可知串口可以使用的引脚,同时可知,初始化uart引脚程序中,函数内部会自动开启串口时钟。

  2. 定义uart的配置结构体,为结构体设置参数

  3. 调用tls_uart_port_init函数配置串口,函数共三个参数,其中第三个在函数注释中声明,当使用串口2的时候,传0表示当普通串口使用,传1表示当作7816 模式。当使用其他串口,这个参数只能传0。

  4. 设置中断回调函数,回调函数定义为:

    s16(*rx_callback) (u16 len, void* user_data)

    函数有两个参数,通过观察中断函数:

    ATTRIBUTE_ISR void UART2_4_IRQHandler(void){...port->rx_callback(rxlen, port->priv_data);...}

    和串口的定义结构体:

    /** * @typedef struct tls_uart_port */typedef struct tls_uart_port{    u32 uart_no;      /**< uart number: 0 or 1 */    u32 uart_irq_no;      /**< uart interrupt number */    u32 plus_char_cnt;    TLS_UART_MODE_T uart_mode;      /**< uart work mode: interrupt mode or poll mode */    struct tls_uart_options opts;/**< uart config parameters */    int fcStatus;      /**< flow ctrl status,0 closed ,1 opened */    enum TLS_UART_RX_FLOW_CTRL_FLAG rxstatus;    u32 tx_fifofull;      /**< uart tx fifo trigger level */    TLS_UART_REGS_T volatile *regs;     /**< uart registers struct pointer */    struct tls_uart_icount icount;   /**< uart statistics information */    struct tls_uart_circ_buf recv;   /**< uart ring buffer */// struct tls_uart_circ_buf xmit;    struct dl_list tx_msg_pending_list;    struct dl_list tx_msg_to_be_freed_list;    u8 hw_stopped;    tls_os_sem_t *tx_sem;    char *buf_ptr;    u16 buf_len;    s16(*rx_callback) (u16 len, void* priv_data);    s16(*tx_callback) (struct tls_uart_port * port);    s16(*tx_sent_callback) (struct tls_uart_port * port);    bool tx_dma_on;bool rx_dma_on;void *priv_data;} tls_uart_port_t;

    可知第二个参数是当回调的时候会将串口结构体的最后一个参数传回去,这样做有一个好处,就是可以将所有的串口接收回调绑到一个函数上,在回调函数执行的时候,通过传回去的第二个参数,来判断是那个串口产生回调。

  5. 初始化串口还有另一种方法(这个方法我没有验证,不保证是对的,太累了),如下:

//另一种串口初始化void my_uart_init(){//初始化uart引脚,函数内部会自动开启串口时钟,wm_uart1_rx_config(WM_IO_PB_07);wm_uart1_tx_config(WM_IO_PB_06);    //当第二个参数为NULL的时候,串口会按默认参数初始化这个串口,具体请看函数tls_uart_port_init的实现    if (WM_SUCCESS != tls_uart_port_init(TLS_UART_1, NULL, 0)){printf("uart1 init error\n");}     //默认初始化可能不符合要求,那么就用sdk来修改uart参数    tls_uart_set_baud_rate(TLS_UART_1, UART_BAUDRATE_B9600)//波特率tls_uart_set_parity(TLS_UART_1, TLS_UART_PMODE_DISABLED);//无奇偶校验tls_uart_set_stop_bits(TLS_UART_1, TLS_UART_ONE_STOPBITS);//一个停止位    //为串口绑定接收回调函数tls_uart_rx_callback_register((u16) TLS_UART_1, (s16(*)(u16, void*))my_uart_rx_callback, NULL);}

2.2.2 回调函数(坑)

开始的时候,我是这样写的程序:

/*这是一个错误的示例*///接收回调函数s16 my_uart_rx_callback(u16 len){rx_length = len;tls_os_sem_release(sem_rx);    return WM_SUCCESS;}//uart线程函数...for(;;){    os_status = tls_os_sem_acquire(sem_rx, 0);    if(!os_status){ if(rx_length > 0){     rx_len = tls_uart_read(TLS_UART_1, rx_fifo_buf, rx_length);     tls_uart_write(TLS_UART_1, rx_fifo_buf, rx_len); //输出 }    } }...

当向串口发送数据包小于16byte的时候,能正常返回数据,当发送16byte字节以上的时候,会不停的返回乱七八糟的数据,且数据只有前15byte和发给它的是对的上的,后面就会不停的往回发数据。

后来经过不断尝试,打印输出,最后发现如下:

当用户发过来数据的时候,如果一次性数据小于16byte,那么,这个回调就会执行一次,并且len值就是接收到的数据个数。

但是当用户一次性发来的数据多余16byte,那个这个回调函数回调的次数会变得不可预知,但可以知道第一次回调len为16,但是第二次、三次就不知了,len可能是2,也可能是1,例如:您发了19个字节,这个回调可能会执行3次,len分别是16、1、2。也可能会执行2次,len为16、3。这样就导致您根本做不了数据分包,只能不断累加。如下:

//接收回调函数s16 my_uart_rx_callback(u16 len){rx_length += len;tls_os_sem_release(sem_rx);    return WM_SUCCESS;}

这种情况,有一种分包办法就是在线程里面操作。

2.2.3 线程内用信号量超时来分包

当回调函数收到包,那么就会释放信号量,线程内部等待这个信号量,信号量的返回值为枚举类型:

typedef enum tls_os_status {    TLS_OS_SUCCESS = 0,    TLS_OS_ERROR,    TLS_OS_ERR_TIMEOUT,} tls_os_status_t;

若是在20个时钟周期收到,那么返回值TLS_OS_SUCCESS,若超时就返回TLS_OS_ERR_TIMEOUT,通过这个就可以进行串口分包:

...for(;;){    os_status = tls_os_sem_acquire(sem_rx, 20);//等待信号量到来,等待20个系统时钟周期    //若发生错误,即在20个周期内未收到sem_rx信号    //这将表示这个串口在20个时钟周期内没有收到数据    if(os_status){ //如果串口有数据,表示一个包已经接收完了。 if(rx_length > 0){     //读出这个包的数据,注意这个包不能超过1024个字节     rx_len = tls_uart_read(TLS_UART_1, rx_fifo_buf, rx_length);     rx_length = 0;     tls_uart_write(TLS_UART_1, rx_fifo_buf, rx_len); //输出 }    } }...

这里的等待20个时钟周期,是我自己随便估计出来的,自已可以根据串口的波特率情况,修改这个参数。

但是原则上有两条:

  1. 值太小的话,会导致线程频繁运行,即使串口没数据,它也会定期运行,值越小单位时间运行的次数就越多,会影响mcu性能。
  2. 值太大,会导致连包,也就是明明是两个包,但是读的时候当成了一个包

所以合理的等待时间是很重要的。

3. 运行结果

在这里插入图片描述