灵活的串口队列发送

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

如何解决。

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工程在微信关注公众号(芯片家)发送串口就可以获取下载链接了。实际体验这个结构很方便好用。

<think>好的,用户想知道如何在LabVIEW中使用队列进行串口通信。首先,我需要回忆一下LabVIEW中的队列操作和串口通信的基本知识。队列在LabVIEW中常用于多线程的数据传递,特别是在生产者/消费者模式中,用来解耦数据生产者和处理者,避免数据丢失或阻塞。 用户提到的引用[4]中提到了使用队列来处理串口数据,这应该是关键。需要结合VISA串口读取和队列操作。首先,用户可能需要配置串口参数,比如波特率、数据位等,这和普通的串口通信设置一样。然后,在读取数据时,将数据放入队列中,再由另一个循环或线程从队列中取出处理。 接下来要考虑的是如何在LabVIEW中实现队列的创建、入队和出队操作。LabVIEW有队列相关的函数,比如创建队列、入队元素、出队元素等。需要确保在生产者循环(串口读取)中将数据不断入队,而在消费者循环中处理这些数据。 另外,用户可能关心如何避免数据更新延迟的问题,引用[1]提到数据更新时间超过5秒,可能和串口配置或数据处理方式有关。使用队列可以解耦读取和处理步骤,可能提高效率,但需要正确设置队列大小和超时时间,防止队列溢出或阻塞。 还要注意引用[3]提到的串口通讯速度,优化设置是关键。比如,确保每次读取的字节数合适,避免频繁的小数据读取增加开销。同时,生产者循环应尽可能高效,减少不必要的延迟。 示例程序的结构可能包括两个并行的循环:一个负责读取串口数据并入队,另一个负责从队列中取出数据处理。需要确保队列的引用正确传递,并在程序结束时释放队列引用,防止内存泄漏。 可能的问题点包括队列的初始化是否正确,错误处理是否到位,以及如何处理队列超时或满的情况。需要指导用户正确使用队列函数,并处理可能的错误情况。 最后,用户可能需要具体的步骤和示例代码,所以需要分步骤说明,并提供一个简单的VI示例,说明如何创建队列、读取串口数据、入队和出队操作。同时,强调配置串口参数的重要性,以及队列在提高通信效率中的作用。</think>在LabVIEW中通过队列实现串口通信,主要利用生产者/消费者模式解耦数据读取与处理过程,从而提升系统响应速度和稳定性。以下是具体实现方法: ### 一、实现步骤 1. **创建队列引用** - 使用`创建队列`函数(位于`数据通信→队列操作`)初始化队列,设置队列数据类型为字符串或字节数组,队列大小根据数据量调整 2. **配置串口参数** - 通过VISA配置串口(`VISA Configure Serial Port`)设置波特率、数据位、停止位等参数,例如:$9600\ \text{波特率},\ 8\ \text{数据位},\ 1\ \text{停止位}$[^1][^3] 3. **生产者循环(数据读取入队)** ```text While循环 ├─ VISA读取 → 获取串口数据 ├─ 元素入队 → 将数据压入队列 └─ 超时设置 → 防止阻塞(建议1ms) ``` 4. **消费者循环(数据处理出队)** ```text While循环 ├─ 元素出队 → 获取队列数据 ├─ 数据处理 → 解析/显示/存储 └─ 错误处理 → 处理超时/空队列情况 ``` ### 二、示例程序框架 ![队列通信程序框图示意] 左半部分(生产者): ```text VISA资源名称 → VISA配置 → VISA读取 → 元素入队 → 循环间隔控制 ``` 右半部分(消费者): ```text 元素出队 → 数据解析 → 波形图表显示 → 文件存储 ``` ### 三、关键优化点 1. **双缓冲机制** $$缓冲区大小 = \frac{数据包速率}{采样周期} \times 数据包长度 \times 1.5$$ 通过公式计算合理缓冲区大小,避免数据溢出[^3] 2. **队列超时设置** - 入队操作设置非阻塞模式(超时0ms) - 出队操作设置合理等待时间(建议10-50ms) 3. **错误处理链** ```text VISA错误输出 → 队列错误输入 → 统一错误处理终端 ``` ### 四、典型应用场景 1. 多设备并行通信时分配独立队列 2. 需要数据持久化存储的监测系统 3. 实时性要求较高的工业控制系统[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值