安卓不是一个实时系统,因此在开发的时候,串口通信等不保证严格按照时序发送数据的。
下面是一个bug的跳坑过程。
安卓系统连接一个485串口板,其上位机,采用的是9600波特率,modbus RTU规约的方式通信,异步通信,无校验,1起始位,8数据位,1停止位
上位机采用时间分帧模式,即严格按照5毫秒一个字节,8个字节必须在40毫秒内按8毫秒间隔发送完成,同时,下一个数据包必须间隔100毫秒这种模式对数据包分片。
安卓发送数据的时候,并不会严格保证按以上方式发送数据,导致一些指令包发送后,上位机程序处理异常,比如对数据帧分片不正确,所以很多帧是不完整的,从而不响应安卓的请求。
但从串口调试工具来看,发送数据是正常的。
解决方法,打开串口后,设置串口选项为低延迟(即缓冲区满不满都立即发送,默认是缓冲区满了才发送),保证串口以低延迟模式发送数据:
struct serial_struct serial;
ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);
对于usb转串口设备,它硬件上一般有一个叫做latency_timer的定时器,当这个值设为lt时,表示数据会在设备上至少暂存lt 毫秒后再发送,只有在设备缓存写满的情况下才会忽略这个值而立即发送。latency_timer的取值范围一般为1到255之间,一般由设备驱动设置默认值。 ftdi芯片的串口设备,一般默认值会是16毫秒,这在高实时性的场景是无法忍受的。
int set_port(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)
{
int i;
int status;
int speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300};
struct termios options;
struct serial_struct serial;
ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);
if( tcgetattr( fd,&options) != 0)
{
perror("SetupSerial 1");
return(FALSE);
}
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
if (speed == name_arr[i])
{
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
options.c_cflag |= CLOCAL;
options.c_cflag |= CREAD;
switch(flow_ctrl)
{
case 0 :
options.c_cflag &= ~CRTSCTS;
break;
case 1 :
options.c_cflag |= CRTSCTS;
break;
case 2 :
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
options.c_cflag &= ~CSIZE;
switch (databits)
{
case 5 :
options.c_cflag |= CS5;
break;
case 6 :
options.c_cflag |= CS6;
break;
case 7 :
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n");
return (FALSE);
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E':
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S':
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB; break;
case 2:
options.c_cflag |= CSTOPB; break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_cc[VTIME] = 1;
options.c_cc[VMIN] = 1;
options.c_cflag &= ~HUPCL;
options.c_iflag &= ~INPCK;
options.c_iflag |= IGNBRK;
options.c_iflag &= ~ICRNL;
options.c_iflag &= ~IXON;
options.c_lflag &= ~IEXTEN;
options.c_lflag &= ~ECHOK;
options.c_lflag &= ~ECHOCTL;
options.c_lflag &= ~ECHOKE;
options.c_oflag &= ~ONLCR;
options.c_oflag &= ~ICANON;
tcflush(fd, TCIOFLUSH);
if (tcsetattr(fd,TCSANOW,&options) != 0) {
perror("com set error!\n");
return (FALSE);
}
return (TRUE);
}