【问题标题】:Linux termios modifying first character after serial port read()Linux termios 修改串口 read() 后的第一个字符
【发布时间】:2013-03-18 08:30:38
【问题描述】:

我的 termios 设置正在修改使用 read() 从串行端口读取的第一个字符。我有一个微控制器与一个 linux 盒子交谈。微控制器响应从 linux 机器发送的命令。设置如下:

  • 微控制器 (PIC24F) RS485 端口 RS485 到 USB 转换器 Ubuntu PC。

当我运行诸如Cutecom 之类的终端程序时,一切都按计划进行。我向 PIC 发送了一个命令字符并得到了响应,但是当我使用我的命令行程序时,第一个字符正在被修改。这是我的代码:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#define DEVICE "/dev/ttyUSB0"
#define SPEED B38400 

int main()
{
    struct termios tio; //to hold serial port settings
    struct termios stdio; //so we can accept user input
    struct termios old_stdio; //save the current port settings
    int tty_fd; //file descriptor for serial port
    int res, n, res2, read1, wri;
    char buf[255];
    char buf2[255]; 

    //save the current port settings
    tcgetattr(STDOUT_FILENO,&old_stdio); 

    //setup serial port settings
    bzero(&tio, sizeof(tio));
    tio.c_iflag = 0;
    tio.c_iflag = IGNPAR | IGNBRK | IXOFF;
    tio.c_oflag = 0;
    tio.c_cflag = CS8 | CREAD | CLOCAL; //8n1 see termios.h 
    tio.c_lflag = ICANON;

    //open the serial port
    tty_fd=open(DEVICE, O_RDWR | O_NOCTTY); 

    //set the serial port speed to SPEED
    cfsetospeed(&tio,SPEED); 

    //apply to the serial port the settings made above
    tcsetattr(tty_fd,TCSANOW,&tio); 

    for(n = 5; n > 0; n--)
    {
    printf("Please enter a command: ");
    (void)fgets(buf2, 255, stdin);
    (void)write(tty_fd, buf2, strlen(buf2));                   
    printf("Ok. Waiting for reply.");
    res = read(tty_fd, buf, 255);       
    printf("Read:%d START%d %d %d %d %dFINISH\n",res,buf[0],buf[1],buf[2],buf[3],
    buf[4]);              
    }

    //close the serial port 
    close(tty_fd); 

    //restore the original port settings
    tcsetattr(STDOUT_FILENO,TCSANOW,&old_stdio); 

    return EXIT_SUCCESS; 
}

这是我得到的结果示例。

  • 当 PIC 发送“00000\n”时,输出为:读取:6 START-16 48 48 48 48FINISH
  • 当 PIC 发送“23456\n”时,输出为:读取:6 START-14 51 52 53 54FINISH
  • 当 PIC 发送“34567\n”时,输出为:读取:6 START-14 52 53 54 55FINISH
  • 当 PIC 发送“45678\n”时,输出为:读取:6 START-12 53 54 55 56FINISH
  • 当 PIC 发送“56789\n”时,输出为:读取:6 START-12 54 55 56 57FINISH

由于某些原因,第一个字符被某些 termios 设置弄乱了。它必须是 termios 设置,因为上面相同的测试输入在我运行 Cutecom 时完全返回。我一遍又一遍地阅读手册页,在输入控件上尝试了所有不同的设置,但无论我做什么都无法解决这个问题。

为了一个简单的修复,我可以将我的数据转移到 1 个字符上,但希望避免这样做。

有没有人遇到过这样的问题或知道该怎么做?

非常感谢。

28/3/13 很好的建议奥斯汀。对于那些对这里感兴趣的人,这里有两个输出:

  • 首先是我程序中的 termios 设置

    速度 38400 波特;第 0 行;第 0 列;线 = 0; 内部 = ;退出 = ;擦除 = ;杀死 = ; eof = ; eol = ; eol2 = ;开关 = ;开始 = ;停止 = ; 怀疑 = ; rprnt = ;错误 = ;下一个 = ; 冲洗 = ;最小值 = 0;时间=0; -parenb -parodd cs8 -hupcl -cstopb cread clocal -crtscts ignbrk -brkint ignpar -parmrk -inpck -itrip -inlcr -igncr -icrnl -ixon ixoff -iuclc -ixany -imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig icanon -iexten -echo -echo -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

  • 还有cutecom使用的设置

    速度 38400 波特;第 0 行;第 0 列;线 = 0; 内部 = ^C;退出 = ^\;擦除 = ^?;杀死 = ^U; eof = ^D; eol = ; eol2 = ;开关 = ;开始 = ^Q;停止 = ^S;悬念 = ^Z; rprnt = ^R; 错误 = ^W;下一个 = ^V;冲洗 = ^O;最小值 = 60;时间=1; -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts ignbrk -brkint -ignpar -parmrk -inpck -itrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten -echo -echo -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

我还在经历这一切,当我取得进展时会更新帖子。

29/3/13 仍然有同样的问题。我什至找到了 Cutecom 的源代码,并遵循了他们使用的 termios 设置。问题仍然存在。第一个字符已损坏!!!!

  • 这是我的程序中的 Termios 设置。由于某种原因无法设置刷新。

    速度 38400 波特;第 0 行;第 0 列;线 = 0; 内部 = ^?;退出 = ^\;擦除 = ^H;杀死 = ^U; eof = ^D; eol = ; eol2 = ;开关 = ;开始 = ^Q;停止 = ^S;悬念 = ^Z; rprnt = ^R; 错误 = ^W;下一个 = ^V;冲洗 = ;最小值 = 60;时间=1; -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts ignbrk -brkint -ignpar -parmrk -inpck -itrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten -echo -echo -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

  • 还有我的新代码:

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <sys/ioctl.h>
    
    #define DEVICE "/dev/ttyUSB0"
    #define SPEED B38400 
    
    int main()
    {
    struct termios tio; //to hold serial port settings
    struct termios stdio; //so we can accept user input
        struct termios old_stdio; //save the current port settings
        int tty_fd; //file descriptor for serial port
        int retval, res, n, res2, read1, wri;
        char buf[255];
        char buf2[255]; 
    
    
        tty_fd = open(DEVICE, O_RDWR | O_NDELAY);
        if(tty_fd < 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 1 complete.\n");
    
        tcflush(tty_fd, TCIOFLUSH);
    
        int f = fcntl(tty_fd, F_GETFL, 0);
        fcntl(tty_fd, F_SETFL, f & ~O_NDELAY);
    
        retval = tcgetattr(tty_fd, &old_stdio);
        if(retval != 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 2 complete.\n");
    
        struct termios newtio;
        retval = tcgetattr(tty_fd, &newtio);
        if(retval != 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 3 complete.\n");
    
        cfsetospeed(&newtio, SPEED);
        cfsetispeed(&newtio, SPEED);
    
        newtio.c_cflag = (newtio.c_cflag & ~CSIZE) | CS8;
        newtio.c_cflag |= CLOCAL | CREAD;
        newtio.c_cflag &= ~(PARENB | PARODD);
        newtio.c_cflag &= ~CRTSCTS;
        newtio.c_cflag &= ~CSTOPB;
    
        newtio.c_iflag = IGNBRK;
        newtio.c_iflag &= ~(IXON | IXOFF | IXANY);
    
        newtio.c_lflag = 0;
    
        newtio.c_oflag = 0;
    
        newtio.c_cc[VTIME] = 1;
        newtio.c_cc[VMIN] = 60;
        newtio.c_cc[VINTR] = 127; 
        newtio.c_cc[VQUIT] = 28;
        newtio.c_cc[VERASE] = 8;
        newtio.c_cc[VKILL] =  21;
        newtio.c_cc[VEOF] = 4;
        newtio.c_cc[VSTOP] = 19;
        newtio.c_cc[VSTART] = 17;
        newtio.c_cc[VSUSP] = 26;
        newtio.c_cc[VREPRINT] = 18;
        newtio.c_cc[VFLSH] = 15;
        newtio.c_cc[VWERASE] = 23;
        newtio.c_cc[VLNEXT] = 22;
    
    
        retval = tcsetattr(tty_fd, TCSANOW, &newtio);
        if(retval != 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 4 complete.\n");
    
        int mcs = 0;
        ioctl(tty_fd, TIOCMGET, &mcs);
        mcs |= TIOCM_RTS;
        ioctl(tty_fd, TIOCMSET, &mcs);
    
        retval = tcgetattr(tty_fd, &newtio);
        if(retval != 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 5 complete.\n");
    
        newtio.c_cflag &= ~CRTSCTS;
    
        retval = tcsetattr(tty_fd, TCSANOW, &newtio);
        if(retval != 0)
        {
            perror(DEVICE);
            exit(-1);
        }
        printf("Init 6 complete.\n");
    
    
        for(n = 5; n > 0; n--)
        {
        printf("Please enter a command: ");
        (void)fgets(buf2, 255, stdin);
        (void)write(tty_fd, buf2, strlen(buf2));
        printf("Ok. Waiting for reply\n");
        res = read(tty_fd, buf, 255);       
        printf("Read:%d START%d %d %d %d %dFINISH\n",res,buf[0],buf[1],buf[2], buf[3],
        buf[4]);              
        }
    
        //restore the original port settings
        tcsetattr(tty_fd, TCSANOW, &old_stdio); 
    
        close(tty_fd);
    
        return EXIT_SUCCESS; //return all good
    }
    

我完全不知道可以做什么,或者我应该从这里把它带到哪里。

【问题讨论】:

  • 当读取说读取了 6 个字节时,您是否只报告读取的前 5 个字节?您是否有办法监控通过 RS-482 和 USB 组合发送的内容,以查看微控制器是否真的在发送您期望的字节,或者信息是否在传输之前被修改?我没有使用过 RS-482(但我记得 RS-232,不太喜欢),但是是否有控制字符或消息框架可能会妨碍您?就好像读取的第一个字节被expected &amp; 0xEE修改了...
  • 嗨乔纳森,我没有报告第 6 个字节的原因是因为它是一个\n。由于我对 termios 使用规范模式,读取会在遇到 \n 时返回。如果不是因为我使用 Cutecom(在同一台 Linux 机器上运行)成功运行我的设置,我会寻找 termios 的另一面。我知道假设是危险的,但在这种情况下,我认为可以安全地假设我的 PIC 485 转 USB 转换器运行正常,因为它可以在 Cutecom 中正常工作。您能否详细说明“预期和 0xEE”,因为我不确定它来自哪里。问候。
  • 我运行的其他测试始终显示第一个字符减去了 64。如果我将 64 添加到结果 read() 将存储在 buf[0] 中,我将获得正确的 ASCII 值。令人困惑的是,正确值的 ASCII 值没有设置第 6 位。所以认为在某个地方执行了 &= 0x1011111 并不支持这个想法。我尝试了一些对值的操作,但无法识别任何类型的模式。
  • 0xEE 是一个错误;我的精神有点玩弄是错误的,应该是0xDE。值 0xDE 来自于观察到当第一个字节为 '0'、0x30 时,您将得到 0x10,并且 0x30 & 0xDE == 0x10 (16)。同样,当第一个字节为'2' (0x32) 时,您将得到 14 或 0x0E; 0x3E & 0xDE = 0x0E。对于'3' (0x33), 0x33 & 0xDE == 0x0E (14);对于'4' (0x34), 0x34 & 0xDE == 0x0C (12);对于'5' (0x35)、0x35 & 0xDE == 0x0C (12)。因此,就好像第一个字节与 0xDE 进行了与运算。我想不出为什么会发生这种情况的充分理由,但这确实解释了您看到的模式。
  • 测试码:#include &lt;stdio.h&gt; struct bits { unsigned char sent; unsigned char read; } values[] = { { '0', 16 }, { '2', 14 }, { '3', 14 }, { '4', 12 }, { '5', 12 }, }; enum { NUM_VALUES = sizeof(values) / sizeof(values[0]) }; unsigned char mask = 0xDE; int main(void) { for (int i = 0; i &lt; NUM_VALUES; i++) { unsigned char r = values[i].read; unsigned char s = values[i].sent; printf("%d: sent '%c' = %2d = 0x%.2X; '%c' &amp; 0x%2.X = 0x%.2X; read = %d = 0x%.2X\n", i, s, s, s, s, mask, s &amp; mask, r, r); } return 0; }

标签: c linux serial-port termios


【解决方案1】:

通过快速扫描您的代码,我看不出任何明显错误。如果您希望使用 8 位值,您可能需要考虑转移到 unsigned char buf[]

由于您在 Cutecom 中有一个工作程序,您可以使用它们的 termios 设置作为参考来调试您自己的程序。

Cutecom 在/dev/ttyUSB0 上运行,在另一个终端中运行以下命令来转储 tty 设置:

stty -a -F /dev/ttyUSB0

在运行程序时执行相同的操作并查找两种配置之间的差异。尝试在您的程序中设置终端设置以完全匹配为 Cutecom 报告的设置。

更新:

由于修复 termios 设置并没有解决问题,这里有一些进一步的尝试。我冒险猜测某处存在时间问题。在 Cutecom 控制台上打字时,您一次将 1 个字符发送到您的设备,字符之间有很多毫秒。使用您的程序时,输入命令后将发送一个完整的字符缓冲区,并以驱动程序允许的速度连续发送字符。也许您的 PIC 程序无法处理数据流的时序,或者需要两个停止位而不是一个,从而导致一些奇怪的返回码。

也许最好的起点是回到源头。拿起示波器或逻辑分析仪,验证 PIC 发送的数据是否正确。您必须了解位电平波形,允许开始位和停止位。比较 Cutecom 和您的程序的波形。如果使用逻辑分析仪,请确保使用的时钟是波特率的高倍数。例如 32 乘数。

另一种调试方法是使用strace 来验证驱动程序返回的字符实际上是不正确的,这不是您的程序的问题。使用strace,您将能够看到程序的原始读/写以及内核返回的内容。使用strace -o ~/tmp/strace_output.txt -ttt -xx your_program 在程序运行时转储所有系统调用。有时,仅仅跟踪程序的过程就会减慢它足以显示时序错误的程度。您可以将读/写的时间与 Cutecom 的 strace 进行比较。只是为了测试,您可以添加自己的 write() 函数,该函数发送一个字符串,但在每个字符之间有少量延迟。

【讨论】:

  • 这是比较这两个设置的一个很好的起点,但是我更改了代码以匹配设置,但我仍然无法让它工作。
  • @MitchGulliver 感谢您的更新,可惜到目前为止还没有成功。我已经用一些可能有助于调试的更多信息更新了我的答案。
  • 感谢奥斯汀菲利普斯。我需要完成一些 uni 工作,但是当我再次从事这个项目时,我会尝试你的建议。可能是时候购买示波器了。当我取得进展时,我会更新我的帖子。
【解决方案2】:

我终于解决了。这是修复它的原因:

  • (void)write(tty_fd, buf2, 1);

问题已解决,但不能 100% 确定它为什么会这样做。问题是我的程序在微控制器写入后附加了一个 \n。当我对Cutecom 和我的程序进行strace 时,我发现Cutecom 只写“1”,而我的程序会写“1\n”。我只是想得不够透彻,因为当使用 Cutecom 发送一个字符时,您在用户提示中键入例如 1,然后按 Enter。在 PIC 方面,我的程序如下所示:

while(1)
{
    WATCHDOG();

    if(flag == 1)
    {
        char *start = str2;
        RS485_TXEN1();
        indicator = UART1_getch(); //get character sent from PC
        switch(indicator)
        {
            case '1' :                       
                    UART1_putstr("00000\n");
                    DAC_Write( DAC_CH_2, 4095);
                    break;
            case '2' :                      
                    UART1_putstr("23456\n");
                    DAC_Write( DAC_CH_2, 0);
                    break;
            case '3' :
                    UART1_putstr("34567\n");
                    break;
            case '4' :                       
                    UART1_putstr("45678\n");
                    break;
            case '\n' :
                    UART1_putch('\n');
                    break;
            default  :         
                    UART1_putstr("56789\n");                  
                    break;
        }
        RS485_RXEN1();
        flag = 0;
    }
} 

当一个字符到达 UART RX 时,会产生一个中断。我在这个中断服务程序中设置了“标志”,然后在 main() 中服务接收到的命令。不确定第一个字符是如何被修改的,但看起来由于“case '\n' :”而发生了一些覆盖或写入中断。

最后进行了简单的修复,甚至学到了一些使用 Linux 系统和调试的宝贵经验。感谢所有提供建议的人。对于任何想要开始连接 linux 机器和微控制器的人来说,上述代码可能有助于您入门。

【讨论】:

  • (void)write(tty_fd, buf2, 1); -- 丢弃/忽略系统调用的返回码是不好的编程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多