接收方使用串口空闲中断的提前是:发送方要连续发送。如果接收方超过一个字节周期接收不到数据视为空闲,则发送方的发送线程不能被打断超过一个字节周期。如果是115200波特率的话,这个周期大概是86us。被打断超过这个时间是很容易的,比如在另一个线程打印了一条日志。
解释方法有很多,以下是几种方案:
提高发送线程的优先级,并保证比他高优先级的线程和中断函数中不执行长时间的操作,比如打印日志。更简单一点,将发送线程优先级提至最高,那只需要保证中断函数中不执行长时间操作即可。不建议关中断,115200波特率的一个字节是86us,十几个字节能达到1ms,这是相当长的时间。关中断的话,可能会丢失很多数据。
使用DMA发送。
接收方使用软件来检测空闲,而不是硬件。这样可以放宽实时要求,比如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
= (rt_uint8_t)data;
}
return i;
}
接收方使用串口空闲中断的提前是:发送方要连续发送。如果接收方超过一个字节周期接收不到数据视为空闲,则发送方的发送线程不能被打断超过一个字节周期。如果是115200波特率的话,这个周期大概是86us。被打断超过这个时间是很容易的,比如在另一个线程打印了一条日志。
解释方法有很多,以下是几种方案:
提高发送线程的优先级,并保证比他高优先级的线程和中断函数中不执行长时间的操作,比如打印日志。更简单一点,将发送线程优先级提至最高,那只需要保证中断函数中不执行长时间操作即可。不建议关中断,115200波特率的一个字节是86us,十几个字节能达到1ms,这是相当长的时间。关中断的话,可能会丢失很多数据。
使用DMA发送。
接收方使用软件来检测空闲,而不是硬件。这样可以放宽实时要求,比如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
= (rt_uint8_t)data;
}
return i;
}
举报