暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一起撸linux内核34-记linux串口接收gps数据错乱

囧囧妹 2022-10-08
167

点击上方蓝字【囧囧妹】一起学习,一起成长!

最近在调试异构核串口232通信时遇到一个之前没注意的知识点,还没彻底解决透先记录下。

问题是这样的,异构核的A核有一路串口ttyS0连接了gpsgps采用ubx数据格式每秒上报一次数据,正常现象应该是A核的测试程序每秒打印一次ubx格式数据,正确的数据格式如下,具体字节含义可自行百度:
    b5 62 01 07 5c 00 3c 96 20 12 e6 07 09 1c 0c 1c 
    1b 3f 70 bb 46 04 a6 16 cb 1d 00 00 e4 00 00 00
    00 00 00 00 00 00 00 00 00 00 98 bd ff ff ff ff
    ff ff 00 0d c7 df 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 20 4e 00 00 80 a8
    12 01 0f 27 00 00 0e e2 49 34 00 00 00 00 3e fe
    5a 00 dd 7b
    当我写完A核的串口接收程序后发现接收的数据是这样的:
      00 00 84 05 00 00 f8 ff ff ff 08 00 00 00 e1 ff 
      ff ff 0c 00 00 00 6a 60 18 01 6c 00 00 00 80 a8
      12 01 6e 00 00 00 0e e2 49 34 6a 60 18 01 43 fd
      3c 00 4b a1 b5 62 01 07 5c 00 2c a6 a8 20 e6 07
      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
      0a 08 08 0b 2d 3f 18 00 00 00 01 ff d2 1d 03 01
      ea 1c 62 de 4c 45 28 ff dd 17 7e 01 02 00 88 24
      02 00 b2 00 00 83 05 00 00 f6 ff ff ff 03 00 00
      00 e4 ff ff ff 0b 00 00 00 6a 60 18 01 67 00 00
      00 80 a8 12 01 6d 00 00 00 0e e2 49 34 6a 60 18
      01 43 fd 3c 00 86 50 b5 62 01 07 5c 00 20 a8 a8 

      完全杂乱无章,在分析后发现我接收到的数据有正确的数据也有莫名其妙的字节,也会有丢帧的现象,然后开始调试。


      最开始我怀疑是丢包了,尽管波特率设置成115200且ubx每秒只会上传一帧100个字节理论绝不会丢包但我还是怀疑了丢包,首先我尝试在用户态做了kfifo用来缓存ubx数据,然后通过一个线程只读取ubx数据直接放到kfifo,在另一个线程从kfifo中取数据并对数据进行打印,发现在kfifo中读取到的还是不对,这时我感觉可能是gps芯片出来的数据就不对,我把gps的串口又接出来一路,通过u-center来监听数据,运行我的串口程序,发现u-center接收到的是对的,而我接收到的是乱的,如图(左侧u-center,右侧我接收数据)

      这时我怀疑了内核驱动,通过设备树找到了串口驱动实现在drivers\tty\serial\8250文件夹,通过阅读8250_core.c没有发现什么问题,通过打印中断计数发现也很稳定,这时我把问题定位到串口驱动数据到tty框架以及线路规程可能出了问题,但是tty很成熟,把问题先定位到线路规程,然后重点开始看串口的属性设置,有问题代码如下:

        /** @brief serial_set_attr
        * 设置串口
        * @param serial_dev[in] - gnss串口句柄
        * @param baudrate[in] - 波特率
        * @param data_bit[in] - 数据位
        * @param parity[in] - 校验位
        * @param stop_bit[in] - 停止位
        * @return 0-成功;其它错误码
        * @discri
        * @note
        */
        static uint32_t serial_set_attr(void *serial_dev, uint32_t baudrate,
        uint32_t data_bit, char parity, uint32_t stop_bit){
        struct termios opt;
        serial_dev_st *pdev = nil;


        if(serial_dev == nil){
        return ERR_HANDLE;
        }


        pdev = (serial_dev_st *)serial_dev;
        //获取串口属性
        tcgetattr(pdev->fd, &opt);
        //设置波特率
        serial_set_baudrate(&opt, baudrate);
        opt.c_cflag |= CLOCAL | CREAD;
        //设置数据位
        serial_set_databit(&opt, data_bit);
        //设置校验
        serial_set_parity(&opt, parity);
        //设置停止位
        serial_set_stopbit(&opt, stop_bit);


        tcflush(pdev->fd, TCIFLUSH);


        //设置等待时间和最小接收字符
        opt.c_cc[VTIME] = 11;
        opt.c_cc[VMIN] = 0;
        //关闭回显
        opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | NOFLSH);


        //写入内核属性
        return (tcsetattr(pdev->fd, TCSANOW, &opt));
        }

        在分析后发现关闭回显时清空了ICANON以及相关bit位,ICANON是做什么的呢?按着man手册的说法线路规程应该是有规范式和非规范式两种输出模式,设置ICANON则允许使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的缓冲,此时收到特殊字符会按着特殊字符在c_lflag中的定义对缓冲区进行处理,清除ICANON标志使用非规范式输出所有数据都当作标准数据进行输出。


        按现象看如果置位了ICANON应该会出现我现在的现象,但是我在程序中清除了该位,然后在配置c_lflag前后加了打印看看该值,通过打印发现默认还配置了ISIG,查看man手册发现ISIG的含义是当接收到字符 INTR, QUIT, SUSP, 或 DSUSP 中的任意一个时,则产生相应的信号,以前确实也没注意过这个标志,然后我清除该标志再试发现读取两帧缓冲区的错误帧后结果正常了。

        这也就是说ISIG标志会在收到特殊字符后也会对缓冲区进行处理,特殊字符的定义可参考网上的stty参数详解(http://www.ouvps.com/?p=547)c_lflag 本地标志总结如下,宏定义在内核源码include/uapi/asm-generic/termbits.h

          /* c_lflag bits */
          #define ISIG 0000001 //当接收到字符 INTR, QUIT, SUSP, 或 DSUSP 任何一个时则产生相应的信号
          #define ICANON 0000002 //使能规范式模式。允许使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的缓冲
          #define XCASE 0000004 //(不属于 POSIX; Linux 下不被支持) 如果同时设置了 ICANON,终端只有大写。输入被转换为小写,除了以 \ 前缀的字符。输出时,大写字符被前缀 \,小写字符被转换成大写
          #define ECHO 0000010 //回显输入字符
          #define ECHOE 0000020 //如果同时设置了 ICANON,字符 ERASE 擦除前一个输入字符,WERASE 擦除前一个词
          #define ECHOK 0000040 //如果同时设置了 ICANON,字符 KILL 删除当前行
          #define ECHONL 0000100 //如果同时设置了 ICANON,回显字符 NL,即使没有设置 ECHO
          #define NOFLSH 0000200 //禁止在产生 SIGINT, SIGQUIT 和 SIGSUSP 信号时刷新输入和输出队列
          #define TOSTOP 0000400 //向试图写控制终端的后台进程组发送 SIGTTOU 信号
          #define ECHOCTL 0001000 //(不属于 POSIX) 如果同时设置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信号被回显为 ^X, 这里 X 是比控制信号大 0x40 的 ASCII 码。例如,字符 0x08 (BS) 被回显为 ^H
          #define ECHOPRT 0002000 //(不属于 POSIX) 如果同时设置了 ICANON 和 IECHO,字符在删除的同时被打印
          #define ECHOKE 0004000 // (不属于 POSIX) 如果同时设置了 ICANON,回显 KILL 时将删除一行中的每个字符,如同指定了 ECHOE 和 ECHOPRT 一样
          #define FLUSHO 0010000 //(不属于 POSIX; Linux 下不被支持) 输出被刷新。这个标志可以通过键入字符 DISCARD 来开关
          #define PENDIN 0040000 //(不属于 POSIX; Linux 下不被支持)当读取下一个字符时,输入队列中的所有字符都将被重新打印。
          #define IEXTEN 0100000 //启用实现自定义的输入处理。这个标志必须与 ICANON 同时使用,才能解释特殊字符 EOL2,LNEXT,REPRINT 和 WERASE,IUCLC 标志才有效
           #define EXTPROC 0200000

          觉得不错,点击“分享”,“赞”,“在看”传播给更多热爱嵌入式的小伙伴吧!

          文章转载自囧囧妹,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论