6
votes

My termios setup is modifying the first character read from the serial port using read(). I have a microcontroller talking to a linux box. The microcontroller responds to commands sent from the linux machine. The setup is as follows:

  • microcontroller(PIC24F) RS485 port <--> RS485 to USB converter <--> Ubuntu PC.

When I run a terminal program such as Cutecom everything works as planned. I send a command character to the PIC and I get a response however when I use my command line program the first character is being modified. Here is my code:

#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; 
}

Here is an example of results I am getting.

  • When the PIC sends "00000\n" the output is: Read: 6 START-16 48 48 48 48FINISH
  • When the PIC sends "23456\n" the output is: Read: 6 START-14 51 52 53 54FINISH
  • When the PIC sends "34567\n" the output is: Read: 6 START-14 52 53 54 55FINISH
  • When the PIC sends "45678\n" the output is: Read: 6 START-12 53 54 55 56FINISH
  • When the PIC sends "56789\n" the output is: Read: 6 START-12 54 55 56 57FINISH

For some reason the first character is getting messed up by some termios setting. It must be the termios settings as the same test inputs above are returned exactly when I run Cutecom. I have read the manual pages over and over, trying all different settings on the input control but no matter what I do cannot shakes this problem.

For an easy fix I can just shift my data across 1 character but want to avoid doing this.

Has anyone experienced such a problem or have any idea what to do about it?

Many thanks.

28/3/13 Great suggestion Austin. For those who are interested here are the two outputs:

  • First are the termios settings in my program

    speed 38400 baud; rows 0; columns 0; line = 0; intr = ; quit = ; erase = ; kill = ; eof = ; eol = ; eol2 = ; swtch = ; start = ; stop = ; susp = ; rprnt = ; werase = ; lnext = ; flush = ; min = 0; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread clocal -crtscts ignbrk -brkint ignpar -parmrk -inpck -istrip -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 -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

  • And the settings that cutecom uses

    speed 38400 baud; rows 0; columns 0; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 60; time = 1; -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts ignbrk -brkint -ignpar -parmrk -inpck -istrip -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 -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

I am still going through it all and will update the post when I make progress.

29/3/13 Still have the same problem. I even found the source code to Cutecom and followed the termios settings they use. Still the problem exists. That first character is corrupted!!!!

  • Here are the Termios settings from my program. Cannot set flush for some reason.

    speed 38400 baud; rows 0; columns 0; line = 0; intr = ^?; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ; min = 60; time = 1; -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts ignbrk -brkint -ignpar -parmrk -inpck -istrip -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 -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

  • And my new code:

    #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
    }
    

I am totally lost as to what can be done or where I should take it from here.

2
Is there a reason why you're only reporting the first 5 bytes read when the read says that 6 were read? Do you have the means to monitor what is sent over the RS-482 and USB combination to see whether the microcontroller is really sending the bytes you expect or whether the information is being modified before it goes on the wire? I've not worked with RS-482 (but I remember RS-232, not too fondly), but are there control characters or message framing that could be getting in the way? It is as if the first byte read is modified by expected & 0xEE...Jonathan Leffler
Hi Jonathan, the reason I am not reporting the 6th byte is because it is a \n. As I am using canonical mode for termios, the read returns when it hits a \n. I would be looking on the other side of termios if it wasn't for the successful operation of my setup using Cutecom (which is running on the same Linux machine). I know assumptions are dangerous, but in this situation I think it is safe to assume my PIC, 485-to-USB converter is operating OK due to it working as it should inside Cutecom. Could you please elaborate on "expected & 0xEE" as I am not sure where it is coming from. Regards.Mitch Gulliver
Other tests I ran consistently showed that 64 was being subtracted from the first character. If I added 64 to the result read() would store in buf[0] I would get the correct ASCII value. The confusing thing about this was the ASCII value for the correct value did not have the the 6th bit set. So thinking that somewhere a &= 0x1011111 was carried out didm't support that idea. I tried a few operations on the value but couldn't identify any sort of pattern.Mitch Gulliver
The 0xEE is a mistake; my mental bit twiddling was wrong and it should be 0xDE. The value 0xDE comes from observing that when the first byte is '0', 0x30, you're getting 0x10, and 0x30 & 0xDE == 0x10 (16). Similarly, when the first byte is '2' (0x32), you're getting 14 or 0x0E; 0x3E & 0xDE = 0x0E. For '3' (0x33), 0x33 & 0xDE == 0x0E (14); for '4' (0x34), 0x34 & 0xDE == 0x0C (12); for '5' (0x35), 0x35 & 0xDE == 0x0C (12). So, it is as if the first byte is being ANDed with 0xDE. I can't think of a good reason why it would be happening, but that does explain the pattern you see.Jonathan Leffler
Test code: #include <stdio.h> 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 < NUM_VALUES; i++) { unsigned char r = values[i].read; unsigned char s = values[i].sent; printf("%d: sent '%c' = %2d = 0x%.2X; '%c' & 0x%2.X = 0x%.2X; read = %d = 0x%.2X\n", i, s, s, s, s, mask, s & mask, r, r); } return 0; }Jonathan Leffler

2 Answers

2
votes

I can't see anything obviously wrong with a quick scan of your code. You may want to consider moving to unsigned char buf[] if you're expecting to work with 8 bit values.

Since you have a working program in Cutecom, you can use their termios settings as a reference to debug your own program.

With Cutecom running on /dev/ttyUSB0, run the following command in another terminal to dump the tty settings:

stty -a -F /dev/ttyUSB0

Do the same when running your program and look for differences between the two configurations. Try setting up the terminal settings in your program to exactly match those reported for Cutecom.

Update:

Since fixing the termios settings hasn't resolved the issue, here are some further things to try. I'd hazard a guess that there is a timing problem somewhere. When typing on the Cutecom console, you're sending 1 character at a time to your device with many milliseconds between characters. When using your program, a complete buffer of characters will be sent after you enter a command, with the characters being sent back-to-back as fast as the driver allows. Maybe your PIC program can't handle the timing of the data stream, or expects two stop bits for example instead of one, resulting in some weird return codes.

Probably the best place to start is back at the source. Get hold of an oscilloscope or logic analyzer and verify that the data being sent by the PIC is in fact correct. You'll have to understand the bit level waveforms, allowing for start and stop bits. Compare the waveforms for both Cutecom and your program. If using a logic analyzer ensure the clock used is some high multiple of your baud rate. eg a 32 multiplier.

Another way to debug is by using strace to verify that the characters returned by the driver are in fact incorrect and that's it's not a problem with your program. Using strace, you'll be able to see the raw reads/writes of your program and what is returned by the kernel. Use strace -o ~/tmp/strace_output.txt -ttt -xx your_program to dump all the system calls while your program is running. Sometimes, just the process of stracing a program will slow it down enough to show timing errors. You could compare the timing of the read/writes with an strace of Cutecom. Just for testing, you could add your own write() function which sends a string but delays a small amount between each character.

0
votes

I finally worked it out. This is what fixed it:

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

Problem is fixed but not 100% sure as to why it was doing what it was. The problem was my program appending a \n to the micro controller write. When I did the strace on both Cutecom and my program this I found that Cutecom writes '1' only whereas my program would write "1\n". I just didn't think it through enough because when using Cutecom to send a character you type for example 1 in the user prompt and then hit enter. On the PIC side my program looks like this:

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;
    }
} 

When a character arrives on the UART RX an interrupt is generated. I set "flag" in this interrupt service routine and then service the received command in main(). Not sure how the first character was being modified like it was but looks like there was some overwriting or write interrupt occurring due to "case '\n' :".

Simple fix in the end and even learnt some valuable lessons working with a Linux system and debugging. Thanks to everyone that offered suggestions. To anybody wanting to get a start on interfacing a linux box and micro controller the above codes might be helpful in getting you started.