串口发送数据,接收方收到的数据分了几包。

发布于 2020-12-07 22:24:58

在使用串口发送数据的时候,一包数据大概260个字节左右,发送的时候是整包发的,但是好像在发送的过程中被打断了,接收方可以接收到,但是可以看到是分了几次才接收到的,这种情况怎么处理?
接收方用的是串口空闲中断来接收数据。
数据发送方,用的是rt的发送接口,代码如下:

rt_err_t my_uart_putchar(const char *name,const rt_uint8_t c)
{
    rt_size_t len = 0;
    rt_uint32_t timeout = 0;
    rt_device_t uart_device = RT_NULL;
    uart_device = rt_device_find(name);
    if (RT_NULL == uart_device)
    {
        rt_kprintf("when uart putchar, %s device not found!\n",name);
        return -RT_ERROR;
    }
    do
    {
        len = rt_device_write(uart_device, 0, &c, 1);
        timeout++;
    }
    while (len != 1 && timeout < 500);
    return RT_EOK;
}
void my_uart_putbytes(const char *name,const rt_uint8_t *s, rt_uint16_t len)
{
    for (int j = 0; j < len; ++j) {
        my_uart_putchar(name,s[j]);
    }
}

查看更多

关注者
1
被浏览
348
wenbodong
wenbodong 2020-12-08

接收方使用串口空闲中断的提前是:发送方要连续发送。如果接收方超过一个字节周期接收不到数据视为空闲,则发送方的发送线程不能被打断超过一个字节周期。如果是115200波特率的话,这个周期大概是86us。被打断超过这个时间是很容易的,比如在另一个线程打印了一条日志。

解释方法有很多,以下是几种方案:

  1. 提高发送线程的优先级,并保证比他高优先级的线程和中断函数中不执行长时间的操作,比如打印日志。更简单一点,将发送线程优先级提至最高,那只需要保证中断函数中不执行长时间操作即可。不建议关中断,115200波特率的一个字节是86us,十几个字节能达到1ms,这是相当长的时间。关中断的话,可能会丢失很多数据。
  2. 使用DMA发送。
  3. 接收方使用软件来检测空闲,而不是硬件。这样可以放宽实时要求,比如1ms接收不到数据视为本包结束。当然,这样会牺牲一点传输带宽。人家每86us就可以发新包,而你得等1ms。

下面贴出我日常使用的接收代码,即第3种方案。

初始化串口时通过rt_device_set_rx_indicate设置回调,在回调中发送一个数据事件。

/**
 * 硬件串口接收数据的回调函数
 *
 * @param stream 串口流
 * @param drv   串口设备
 * @param size  待接收的数据大小
 *
 * @return
 */
rt_err_t serial_stream_rx_notify(serial_stream_t *stream, rt_device_t dev, rt_size_t size)
{
    rt_event_send(stream->serial_rx_event, SERIAL_RX_EVENT);
    return RT_EOK;
}

这是接收一个字节的函数,其先调用rt_device_read读取,若有数据则直接返回。若无数据,则等待数据事件。等待成功了再读取数据,若超时则返回失败。

/**
 * 从串口流读取单个字节数据
 *
 * @param stream    串口流
 * @param timeout   超时时间
 *
 * @return 实际读取的字节数
 */
rt_int32_t serial_stream_read_byte(serial_stream_t *stream, rt_tick_t timeout)
{
    rt_uint8_t data;
    rt_err_t ret;
    rt_uint32_t event;

    while(RT_TRUE)
    {
        if(rt_device_read(stream->serial, 0, &data, 1) == 1)
        {
            return data;
        }

        ret = rt_event_recv(stream->serial_rx_event, SERIAL_RX_EVENT, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, timeout, &event);
        if(ret != RT_EOK)
        {
            return -1;
        }

    }
}

当然,你肯定不是想一个字节一个字节的接收的,所以请看下面这个函数。其负责接收一整包数据。请注意,有两个超时时间。begin_timeout是首字节超时,cont_timeout是字节间超时。cont_timeout是负责分包的,那为什么还要有个begin_timeout呢?想象一下,我并不知道对方什么时候会发数据,它可能1个小时只发1包数据,如果我使用1ms超时来接收,那将不停地收到空数据。虽然这也没啥问题,不过笔者不喜欢哈哈。如果长时间无数据也不需要做额外操作的话,可以把begin_timeout设置为RT_WAITING_FOREVER。

rt_size_t  serial_stream_read_bytes2(serial_stream_t *stream, rt_uint8_t *buf, rt_size_t size,
        rt_tick_t begin_timeout, rt_tick_t cont_timeout)
{
    rt_size_t i;
    rt_int32_t data;

    for(i = 0; i < size; i++)
    {

        data = serial_stream_read_byte(stream, i == 0 ? begin_timeout : cont_timeout);
        if(data == -1)
        {
            break;
        }
        buf[i] = (rt_uint8_t)data;

    }

    return i;
}
3 个回答
whj467467222
whj467467222 认证专家 2020-12-07

串口是字符型设备,每次发送数据一定是一个字节一个字节,你使用轮询的模式去发送,这个发送的动作还是在线程当中,你能保证你发送的时候不被其他高优先级的任务和其他中断打断吗?当然你可以使用关闭中断的方式来实现,但是这样就严重影响了 RTOS 的实时性。
所以分包是一个不可能避免的事情,你能做的就是如何去把分开的包给组合起来。

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览