嵌入式技术论坛
直播中

王芳

7年用户 1427经验值
私信 关注
[问答]

超时判断串口接收完成时,超时时间选择多少合适?

因为串口接收的时候,会把一条数据分成几段来接收。所以采用定时器超时判断接受完一条完整数据。思路是接收到数据后,启动定时器,写入超时时间T,如果超过T,触发定时器超时函数,说明T时间内没有收到数据,完成了一条数据的接收。
这是串口配置代码,9600波特率DMA接收。

struct serial_configure pconfig;
/* 初始化串口 /
if (env_dev == RT_NULL)
{
env_dev = rt_device_find(ENV_NAME);
RT_ASSERT(env_dev);
pconfig.baud_rate = BAUD_RATE_9600;
pconfig.data_bits = DATA_BITS_8;
pconfig.stop_bits = STOP_BITS_1;
pconfig.parity = PARITY_NONE;
pconfig.bit_order = BIT_ORDER_LSB;
pconfig.invert = NRZ_NORMAL;
pconfig.bufsz = 4096;
pconfig.reserved = 0;
/
设置串口参数 */
rt_device_control(env_dev, RT_DEVICE_CTRL_CONFIG, &pconfig);
/*设置串口的回调函数 */
rt_device_set_rx_indicate(env_dev, env_recv_ind);
}
if(RT_EOK ==rt_device_open(env_dev, RT_DEVICE_FLAG_DMA_RX ))
{}
接收回调里释放信号量

rt_err_t env_recv_ind(rt_device_t dev, rt_size_t size)
{
/* release semaphore to let finsh thread rx data */
rt_uint8_t ch = 0;
if (size > 0)
rt_sem_release(&env_rx);
return RT_EOK;
}

接收线程里读取不到信号量,则不执行下面的内容,读到信号量,则开始接收数据,把接收的数据保存在数组里,后续处理。

timeout_s.sec = 0; /* 秒 */
timeout_s.usec = 300000;
if (timeout == 1 && old_recv_cnt != 0 && recv_cnt == 0)
{
timeout = 0;
old_recv_cnt = 0;
LOG_D("接收定时溢出");
tmp_ptr = recv_buff;
rt_device_control(hw_dev, HWTIMER_CTRL_STOP, RT_NULL);
// dump_hex(recv_buff, cnt);//调试用
rt_memcpy(USART3_RX_BUF,recv_buff,sizeof(USART3_RX_BUF));//数据给USART3_RX_BUF
USART3_RX_LEN = cnt;//得到有效数据长度
rt_sem_release(&recv_comp);
recv_ok = 1;
cnt = 0;
cnt_test = 0;
}
if (rt_sem_take(&env_rx,1000) != RT_EOK)
{
continue;
}
else
{
rt_event_send(&feed_dog_event, EVENT_FLAG3);
}
rt_event_send(&feed_dog_event, EVENT_FLAG3);
while (recv_cnt = rt_device_read(env_dev, -1, tmp_ptr, 4096) )
{
// LOG_I("recv_cnt = %d", recv_cnt);
rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) ;
cnt += recv_cnt;
tmp_ptr += recv_cnt;
old_recv_cnt = recv_cnt;
}

重点在这里,我设置的时间是300ms,经过测试,设置时间短了,经常不能完整接收一条数据。但是300ms也太长了,串口发送数据,中间的间隔,怎么也不至于300ms,但是少于300ms,就接受不完整,300ms,就接收完整。

裸机时候开个定时器,好像超时时间10ms就够了。现在如果一条数据分成5段,则前面4段的等待时间是[0,300]ms,最后一段等待时间是300ms,接收一条完整数据最少要等待300+ms。效率太低了。

我需要修改代码哪个地方,来避免这种情况呢?

回帖(10)

李娟

2023-1-16 17:19:28
给你参考我的代码吧,我采用的中断接收,dma接收也可以使用。但要注意间隔时间

思想是:接收到一个字符后,就开始等待timeoutByte个时间片,
在这个时间片内接收到数据认为数据没有接收完毕,如果没有收到认为接收成功

9600波特率timeoutByte可以为5ms - 20ms

modbus规定的报文间隔要大于3.5个字符,你可以算下需要延时多少。

/**
* @Brief 串口接收数据
*
* @param serial
* @param buf 接受缓冲区
* @param bufSize 接受缓冲区大小
* @param timeout timeout时间后还没接收到一个字符认为失败
* @param timeoutByte 两个字符直接超过timeoutByte认为接收完成
* @Return int32_t
*/
int32_t uartRecvData(rt_device_t serial, char *buf, int32_t bufSize, int32_t timeout, int32_t timeoutByte)
{
    uint32_t len = 0;
    int16_t resut = 0;
    while (1)
    {
        if (rt_event_recv(&uartEvent, 1 << getUartChannel(serial),
                          RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
                          timeout, NULL) != RT_EOK)
            return len;
        timeout = timeoutByte;
        resut = rt_device_read(serial, 0, buf + len, bufSize - len);
        if (resut < 0)
            return 0;
        len += resut;
        if (len > bufSize)
            return 0;
    }
}
/**
* @brief 串口中断接收回调函数
*
* @param dev
* @param size
* @return rt_err_t
*/
static rt_err_t modbusServiceRxCallback(rt_device_t dev, rt_size_t size)
{
    rt_event_send(&uartEvent, 1 << getUartChannel(dev));
    return RT_EOK;
}
举报

李娟

2023-1-16 17:19:35
这样的思路效率相对比较低,每次中断都发送事件,在我的项目组用了多个串口modbus service
为了更好复用采用这种方式,且对效率完全不敏感,是有更好的实现方式的
举报

王芳

2023-1-16 17:19:45
串口接收到数据后触发下面的回调函数,发送事件,上面的函数收到事件就一个字节。那个bytetimeout是啥意思,imeout
举报

李娟

2023-1-16 17:19:52
注释写了timeout和timeoutByte的意思,
这个代码之前是用来做RS485收发的,服务端发送数据后,modbus从机不一定会返回数据(线路连接失败等)
假设timeout为100,表示的就是100ms还没有收到从机返回一个字符就认为接收失败,返回0
假设timeoutByte为10,表示的是接收到从机返回数据字符了,如果10ms内还没有收到新的返回数据数字,表示从机返回数据发送完毕,可以进行解析了
举报

李娟

2023-1-16 17:20:30
看了你的需求,个人感觉可以用中断接收加定时器来
每次进入接收回调函数

停止之前定时器。
把数据存入缓冲区(rtthread内置的有环形缓冲区),并记录接收了多少数据
启动定时器20ms(根据实际情况)
在定时器回调函数使用信号量或者事件通知应用层接收完毕
举报

李娟

2023-1-16 17:20:34
看了你的需求,个人感觉可以用中断接收加定时器来
每次进入接收回调函数

停止之前定时器。
把数据存入缓冲区(rtthread内置的有环形缓冲区),并记录接收了多少数据
启动定时器20ms(根据实际情况)
在定时器回调函数使用信号量或者事件通知应用层接收完毕
举报

李娟

2023-1-16 17:20:41
同时要注意定时器通知应用层后,要确保应用层代码和数据缓冲区资源互斥,
防止应用层代码访问缓冲区数据时,接收到新的串口数据。

或者直接定时器回调函数使用中断模式,在定时器中断中使用消息队列发送给应用层

突然想到rt-thread已经内置了接收缓冲区,串口接收回调中只需要停止之前定时器,再重新启动定时器就好
举报

李娟

2023-1-16 17:20:48
通过延时方式来判断数据是否接收成功的,还是不要用dma了。
dma空闲中断,如果你的数据很长,dma一直在搬运,那就不会触发空闲中断回调
这时想通过延时方式来判断就很不容易
举报

王芳

2023-1-16 17:20:57
你好,我不是通过rt_thread_delay做的。和你这个回复思路是一样的。
我的代码在问题里,DMA接收,接收到数据释放信号量,接收线程收到信号量后,重新开启定时器,把数据放在数组里,定时器超时时间是300ms,如果超时,说明接收完成,处理数据。
感觉思路跟你说的一样,但是这个时间太长了,300ms,我试过设置小一些,但是通信会失败。
举报

李娟

2023-1-16 17:21:22
延时的话最好不要用dma,不好算具体延时时间

举报

更多回帖

发帖
×
20
完善资料,
赚取积分