灵活的串口队列发送

不知道你有没有这种疑惑,如果在程序中有多个任务,每个人任务都会调用同一个串口发送数据,那会不会在上一次发送还没完毕之后就开始了下一次发送,那是将上次的数据发完还是直接开始新的数据发送呢。当然得等到上次数据发完啦。不然发送的数据很大概率会丢失一部分,导致错误解析失败。

如何解决。

1调整结构,在某一个函数中专门用来发送,例如modbus的解析,会在解析到相应的数据后统一调用串口发送,而不是到处发送,send乱飞。

2增加一个标志,判断当前的发送有没有结束,如果没有结束就等待当前发送结束,在发送。嗯。。。。那会不会有点笨,不是得一直延时,等待,查询。。。

3当然还有一个最简单的办法,那就是最原始的while轮训发送。但是这样会很大程度牺牲效率。尤其大数据量的时候更加明显。

假如可以这样,在发送时先将数据存起来,在某一个地方专门取出来发送,输入加1,还没发完但是又有新的数据要发送的时候,就像新的数据存起来,输入再加1。搞个队列,当上一条数据发送完,输出加1,当输入只要不等于输出,说明此时有新的待发送数据,然后从队列中取出对应的队列数据开始发送即可,新的数据不影响当前数据的发送,发送内容只会填充到队列,再由发送进程来取出。

那么可以这样设计,定义一个结构体。


typedef struct
{
    uint8_t* pdata;//发送地址指针
    UART_HandleTypeDef *uart_p;//绑定的硬件串口指针
    uint8_t send_in_item;//填充序列
    uint8_t send_out_item;//发送序列
    enum state uart_state;//串口状态
    uint16_t len[MAX_ITEM];//发送长度记录
    uint8_t send_buff[MAX_ITEM][MAX_LEN];//128字节发送缓存  32组
} uart_tx_st;
uart_tx_st cmd;

定义状态枚举

enum state
{
    ready,
    runing,
} ;

几个功能函数

初始化。绑定硬件串口,将输入输出的序列号清零。


void usart_send_init(UART_HandleTypeDef *huart)
{
    cmd.uart_p = huart;
    cmd.send_out_item = 0;
    cmd.send_in_item = 0;
}

发送实体,获取字符串的长度,并将字符串复制到发送队列。并记录长度以及输入序列号

void usart_send_str(char *p, uart_tx_st *uart)
{
    uint8_t len = strlen((char *)p);
    memcpy((char *)&cmd.send_buff[cmd.send_in_item][0], p, len);
    uart->len[uart->send_in_item] = strlen((char *)&cmd.send_buff[cmd.send_in_item][0]);
    uart->send_in_item = get_circlr_in(MAX_ITEM);
}

发送进程实体

判断输入输出的序列是否相等,在空闲状态下则启动一次发送,改变串口状态,设置dma发送的硬件地址,数据地址,以及长度。

void usart_send_task(uart_tx_st *uart)
{
    if(uart->send_in_item != uart->send_out_item && uart->uart_state == ready)
        {
            uart->uart_state = runing;
            HAL_UART_Transmit_DMA(uart->uart_p, (uint8_t *)&uart->send_buff[uart->send_out_item][0], uart->len[uart->send_out_item]);
        }
}

在数据队列满了的时候需要从头开始下一次存放,所以序列号会重新开始计算。

uint8_t get_circlr_in(uint8_t max)
{
    cmd.send_in_item = cmd.send_in_item < (max - 1) ? cmd.send_in_item + 1 : 0;
    return cmd.send_in_item  ;
}
uint8_t get_circlr_out(uint8_t max)
{
    cmd.send_out_item = cmd.send_out_item < (max - 1) ? cmd.send_out_item + 1 : 0;
    return cmd.send_out_item  ;
}

在发送完毕时,输出序列号加1,并将串口状态恢复成ready。

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    cmd.send_out_item = get_circlr_out(MAX_ITEM );
    cmd.uart_state = ready;
}

在freertos中创建三个任务,一个任务循环执行发送进程usart_send_task。一个任务定时100ms发送,一个任务定时200ms发送。因为发送进程需要更加频繁的进行,所以定时间隔很短,如果定时时间长了,会导致队列一直是满的,取的速度比存的速度慢,导致数据不能及时发送,在裸机上,这里可以放在while(1)中。

  for(;;)
        {
            osDelay(1);
            usart_send_task(&cmd);
       }
       for(;;)
        {
            HAL_GPIO_TogglePin( GPIOC, GPIO_PIN_13);
            usart_send_str("StartTask02\r\n", &cmd );
            osDelay(100);
        } 
         for(;;)
        {
            osDelay(200);
            usart_send_str("StartTask03\r\n", &cmd );
        }

效果

图片

在调试时的状态
在这里插入图片描述

可以看到StartTask02比StartTask03频率高一倍,当前发送时不会影响上一次发送。可以在调试窗口看到数据内容都在队列中存放。发送时使用dma,在大数量时,也不会影响程序运行,输出频率很高,内容很多时,可以尝试提高波特率或者增加队列缓存大小。这种方式比较简单且灵活的解决了程序中异步打印存在的问题,实现方式直观且简单,易于理解。当然也可以使用环形队列来进一步提高内存的利用率,但处理会更加复杂,也难以调试,不够直观。没必要啊主要是,这样多简单,浪费了一点点,怎么了。

完整的cube工程在微信关注公众号(芯片家)发送串口就可以获取下载链接了。实际体验这个结构很方便好用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值