串口在我印象中是从来不会丢包的,那是因为以前都是用的厂家提供的SDK,现在用MCU裸板开发,自己做驱动,如果驱动没做好,就会丢包。
今天来总结两个串口驱动层的丢包问题,一个是发数据丢包,即实际发出的数据比预期发的少;一个是收数据丢包,即实际收到的数据比对端发出的数据少。
1、发数据丢包
调试过程发现,当应用层连续两次调用驱动层的串口发数据接口去发数据时,对端wifi板收到的数据会比发出的少了1byte!这么诡异的问题,是不是wifi板驱动层有问题?因为毕竟自己写的代码总觉得是那么完美哈哈哈,然而wifi板是厂家提供的SDK,经过市场验证很久了,大概率不会有bug。
先来了解一下MCU这边串口发数据的过程,用的是51内核的某款芯片。
用户要用串口发数据时,把一个发送数据寄存器置1(即设置TI =1),然后底层会自动产生发数据的中断,
在发数据中断服务函数中,用户要做的是:
1、 把TI 清零,即TI =0;
2、 把要发的数据装载到数据寄存器(SBUF),每次只能装载1个byte。
串口的发送器就开始发送数据,当1byte数据发送完成时,硬件会自动把TI置1,再次产生写数据中断,在写数据中断里用户继续重复刚刚的操作。
那么数据发送是怎么结束的?当某次进入发数据中断,用户把TI清零,没有再往数据寄存器装载数据时,就不会再产生发数据中断,发送过程就结束了。
原来的代码大致如下,
//驱动层用于发数据的先进先出的环形fifo,大小256
char *send_buf_fifo[256];
//fifo写指针,指示fifo数据装到什么地方了,如果装到末尾了,又会返回头部从头部装
int tx_index;
//fifo读指针,指示当前数据发送发到哪了
int rx_index;
//驱动层发送数据接口实现
int uart_send(char *buf, int len)
{
//buf 是要发的数据缓存指针,len是要发的长度
把buf中数据拷贝到驱动层发数据的 fifo中,代码略
tx_index += len; // Fifo 写指针 往后移len;
tx_index = tx_index%256;
TI =1;//启动发送
}
//中断服务函数
void uart_isr()
{
if(TI )
{
TI = 0;
//fifo中数据还没有发完
if(tx_index != rx_index)
{
//把fifo中没发的数据中的第1个byte装载到串口数据寄存器;
SBUF= send_buf_fifo[rx_index];
rx_index++;
rx_index = rx_index %256; //Fifo读指针往后移;
}
}
调试发现,当应用层连续两次调用uart_send接口时,会发生丢包现象,wifi板收到的数据少了1 byte,如果应用层调用uart_send接口是每调用一次隔开一段时间再就不会出现丢包现象。
那么连续调用时,丢包是怎么发生的?连续两次调用uart_send函数,当第二次调用时,第一次调用uart_send函数发的数据驱动层应该还没有发完,应该还在一次次产生中断发数据。
此时应用层又调用uart_send函数,把数据装载到fifo中,这没问题,问题在于应用层把 TI = 1了。如果此时串口的硬件发送器正在发数据,此时应用层把TI置为1了,就会自动马上进入串口发数据中断中,刚刚正在发的那1byte数据还没有成功发出去,此时也就丢掉了。
正确的做法应该怎么样?uart_send函数中把 TI置为1之前先判断一下,此时驱动层是不是正在发数据过程中,即fifo中有原来的数据还没有发完,如果是,那么不用再次把TI置为1;
修改如下:
char sending_flag = 0; //发送标志位,指示当前是否正在发送数据过程中
//驱动层发送数据接口实现
int uart_send(char *buf, int len)
{
//buf 是要发的数据缓存指针,len是要发的长度
把buf中数据拷贝到驱动层发数据的 fifo中,代码略
tx_index += len; // Fifo 写指针 往后移len;
tx_index = tx_index%256;
//若没有在发送数据去启动发送,若正在发送中不用再次启动
if (sending_flag == 0)
{
TI =1;//启动发送
sending_flag = 1;
}
}
//中断服务函数
void uart_isr()
{
if(TI )
{
TI = 0;
//fifo中数据还没有发完
if(tx_index != rx_index)
{
//把fifo中没发的数据中的第1个byte装载到串口数据寄存器;
SBUF= send_buf_fifo[rx_index];
rx_index++;
rx_index = rx_index %256; //Fifo读指针往后移;
}
else //若fifo中数据已经发送完,清发送指示标志
{
sending_flag = 0;
}
}
这样修改之后连续两次调用发送接口,就不再有丢包的问题出现了。