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:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With