首页  编辑  

安卓串口通信因为上位机严格按时间分帧导致的跳坑指南

Tags: /硬件开发/   Date Created:
安卓不是一个实时系统,因此在开发的时候,串口通信等不保证严格按照时序发送数据的。

下面是一个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毫秒,这在高实时性的场景是无法忍受的。
/*******************************************************************
* 名称:                UART0_Set
* 功能:                设置串口数据位,停止位和效验位
* 入口参数:        fd        串口文件描述符
*                              speed     串口速度
*                              flow_ctrl   数据流控制
*                           databits   数据位   取值为 7 或者8
*                           stopbits   停止位   取值为 1 或者2
*                           parity     效验类型 取值为N,E,O,,S
*出口参数:          正确返回为1,错误返回为0
*******************************************************************/
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[] = {115200192009600480024001200300};

    struct termios options;

    struct serial_struct serial;
    ioctl(fd, TIOCGSERIAL, &serial);
    serial.flags |= ASYNC_LOW_LATENCY;
    ioctl(fd, TIOCSSERIAL, &serial);

    /* tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。
        若调用成功,函数返回值为0,若调用失败,函数返回值为1.
    */
    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_lflag &= ~(ISIG | ICANON);

    //设置等待时间和最小接收字符
    options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
    options.c_cc[VMIN] = 1; /* 读取字符的最少个数为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);

    //激活配置 (将修改后的termios数据设置到串口中)
    if (tcsetattr(fd,TCSANOW,&options) != 0) {
        perror("com set error!\n");
        return (FALSE);
    }

    return (TRUE);
}