Using SOCAT for serial communication to USB on Linux - c++

I'd like a little clarification on what socat does to the serial communication, because for some reason I can only get the communication to my Arduino to work when using socat.
This is the command I use to set up socat:
socat -d -v -x PTY,link=/tmp/serial,wait-slave,raw /dev/ttyACM0,raw
The code I used in C++ is:
int file;
file = open("/tmp/serial", O_RDWR | O_NOCTTY | O_NDELAY);
if (file == -1)
{
perror("open_port: Unable to open \n");
}
else
fcntl(file, F_SETFL, 0);
struct termios options;
//* Get the current options for the port...
tcgetattr(file, &options);
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
options.c_oflag = 0;
//options.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OLCUC | OPOST);
options.c_cflag |= (CLOCAL | CREAD);
cfmakeraw(&options);
tcsetattr(file, TCSANOW, &options);
sprintf(sendpack,"$C000C000C000C000\r");
num = write(file,sendpack,18);
tcflush(file, TCIOFLUSH); // clear buffer
So when I run this through socat the arduino reacts as it is supposed to, but if I change the open() line from /tmp/serial to /dev/ttyACM0 the arduino no longer performs at all. Can someone exlplain to me what socat is doing to the data to make things work, and how I can change my C code to do the same thing? I cannot use socat in my final implementation because it causes a delay that would make driving my robot unpredictable.

Related

c++ application quits without error on serial data receive

I am trying to set up serial port communication in a c++ application on Ubuntu 20. I am opening the serial port like this:
serialPort = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
if (serialPort == -1)
{
perror("FAILED ");
exit(1);
}
fcntl(serialPort, F_SETOWN, getpid());
fcntl(serialPort, F_SETFL, (FNDELAY | FASYNC));
struct termios options;
tcgetattr(serialPort, &options);
cfsetispeed(&options, (speed_t) B115200);
cfsetospeed(&options, (speed_t) B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag &= ~(IXON | IXOFF | IXANY | INPCK | ISTRIP | IGNBRK | BRKINT | PARMRK | IGNCR | ICRNL);
options.c_oflag &= ~OPOST;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
tcsetattr(serialPort, TCSANOW, &options);
I know the port is open and functional, because when I try to write to it, my other device does receive the data. But every time the other device writes data back, the application quits without an error message. Even when I am in debug mode, the debugger just drops and the application quits. This happens regardless of who sends data first, but it does happen consistently every time data is received. Because of this I can also not check the data using the application, since it exits before I would get the chance. Does anybody know what is going on?
For those interested, here is the code I use to write to the serial port:
void MotorBenchmarker::sendMessage(char* message, int size)
{
write(serialPort, message, size);
}
Edit 1:
I am using GDB debugger and I now see that the application is terminated with the following message:
Program terminated with signal SIGIO, I/O possible.
below which it says:
The program no longer exists.
SIGIO - I did not notice earlier, you did specify FASYNC flag, this means that any incoming data is reported through this signal. Since you are not handling it, the program crashes.
Either remove the flag and use read - blocking or non-blocking, or install the handler.

Set a callback function for I2C device to get data when IO event happens

I am trying to get data from a gyro using I2C interface and I can't figure out how can I set a event handler to check for IO instead of reading the FIFO buffer in an infinite loop.
I had done a similar thing using UART.
struct sigaction saio;
struct termios options;
uartFilestream = open(UART_DEV, O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode
saio.sa_handler = HandleIO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(uartFilestream, F_SETFL, FNDELAY);
fcntl(uartFilestream, F_SETOWN, getpid());
fcntl(uartFilestream, F_SETFL, O_ASYNC );
tcgetattr(uartFilestream, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; //<Set baud rate
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(uartFilestream, TCIFLUSH);
tcsetattr(uartFilestream, TCSANOW, &options);
HandleIO is called whenever IO happens on the the serial interface. Please tell me how can I do the same with I2C.
Here is what I did with I2C but it doesn't work.
fd = open("/dev/i2c-1", O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode
saio.sa_handler = test_func;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC );
tcgetattr(fd, &options);
options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; //<Set baud rate
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &options);

Failed to configure device ttyUSB0 (Arduino) on Ubuntu, C++

I can open serial port, but I can't correctly configure this port for write (/dev/ttyUSB0).
Piece of code C++:
int
Platform::initConnection( const char* devicePath, int baudRate )
{
int fd = 0;
int ret = 0;
struct termios terminalOptions; // POSIX structure for configurating terminal devices
fd = open( devicePath, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
//fd = open( devicePath, O_RDWR | O_NOCTTY );
if (fd == -1)
{
this->setFail();
this->setErrorStr( "Failed to open: " + (std::string)devicePath + ". " + (std::string)strerror(errno) );
return -1;
}
memset( &terminalOptions, 0, sizeof( struct termios ) ); // Cleaning up the structure
cfmakeraw(&terminalOptions); //
cfsetspeed(&terminalOptions, baudRate);
/*terminalOptions.c_cflag = CLOCAL; // If CLOCAL is set, the line behaves as if DCD is always asserted.
// It is used when your device is local
terminalOptions.c_cflag |= CS8; // Character size mask
terminalOptions.c_cc[VMIN] = 24; // 1 second timeout
terminalOptions.c_cc[VTIME] = 0; // */
terminalOptions.c_cflag &= ~CRTSCTS;
terminalOptions.c_cflag |= (CLOCAL | CREAD);
terminalOptions.c_iflag |= (IGNPAR | IGNCR);
terminalOptions.c_iflag &= ~(IXON | IXOFF | IXANY);
terminalOptions.c_oflag &= ~OPOST;
terminalOptions.c_cflag &= ~CSIZE;
terminalOptions.c_cflag |= CS8;
terminalOptions.c_cflag &= ~PARENB;
terminalOptions.c_iflag &= ~INPCK;
terminalOptions.c_iflag &= ~(ICRNL|IGNCR);
terminalOptions.c_cflag &= ~CSTOPB;
terminalOptions.c_iflag |= INPCK;
terminalOptions.c_cc[VTIME] = 0.001; // 1s=10 0.1s=1 *
terminalOptions.c_cc[VMIN] = 0;
ret = ioctl( fd, TIOCSETA, &terminalOptions ); // Configuring the device
if (ret == -1)
{
this->setFail();
this->setErrorStr( "Failed to configure device: " + (std::string)devicePath + ". " + (std::string)strerror(errno) );
return -1;
}
return fd;
}
Error:
Failed to configure device: /dev/ttyUSB0. Inappropriate ioctl for device
Arduino UNO uses chipset CH340.
I have no idea about the resolve this problem. I'm hope for your help. Thanks!
Update:
Log from dmesg
[11840.346071] usb 2-1.2: new full-speed USB device number 5 using ehci-pci
[11840.439832] usb 2-1.2: New USB device found, idVendor=1a86, idProduct=7523
[11840.439844] usb 2-1.2: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[11840.439850] usb 2-1.2: Product: USB2.0-Serial
[11840.440472] ch341 2-1.2:1.0: ch341-uart converter detected
[11840.442452] usb 2-1.2: ch341-uart converter now attached to ttyUSB0
Thanks to all. I found the solution on their own:
As autoreset on serial connection is activated by default on most boards, you need to disable this feature if you want to communicate directly with your board with the last command instead of a terminal emulator (arduino IDE, screen, picocom...). If you have a Leonardo board, you are not concerned by this, because it does not autoreset. If you have a Uno board, connect a 10 µF capacitor between the RESET and GND pins. If you have another board, connect a 120 ohms resistor between the RESET and 5V pins. See http://playground.arduino.cc/Main/DisablingAutoResetOnSerialConnection for more details.
Сhanged code
memset( &terminalOptions, 0, sizeof( struct termios ) );
tcgetattr(fd, &terminalOptions); //change
cfmakeraw(&terminalOptions);
cfsetspeed(&terminalOptions, baudRate);
terminalOptions.c_cflag = CLOCAL;
terminalOptions.c_cflag |= CS8;
terminalOptions.c_cc[VMIN] = 0;
terminalOptions.c_cc[VTIME] = 10;
terminalOptions.c_cflag = CLOCAL;
terminalOptions.c_cflag &= ~HUPCL; //change (disable hang-up-on-close to avoid reset)
ret = tcsetattr(fd, TCSANOW, &terminalOptions); //change
if (ret == -1)
{
this->setFail();
this->setErrorStr( "Failed to configure device: " + (std::string)devicePath + ". " + (std::string)strerror(errno) );
return -1;
}
return fd;
I also have arduino UNO and when I plug it via usb port it connects to /dev/ttyACM0 not ttyUSB0 you should also check ttyACM0 when you plug and unplug your arduino UNO.
It is also the case If you haven't installed arduino port driver

How to check serial port in linux work properly

I am going to use serial port in my C++ program linux ubuntu 10.4 ,this is my open port function :
int Recorder::OpenPort()
{
int intFd ;
struct termios options;
intFd=open("/dev/ttyS0", O_RDWR | O_NOCTTY| O_NDELAY);
if (intFd==-1){
perror("open_port: Unable to open /dev/ttyS0 - ");
}
fcntl(intFd, F_SETFL, FNDELAY); /*configuration the port*/
tcgetattr(intFd, &options);
//set baud rates at 115200
cfsetispeed(&options, B115200 );
cfsetospeed(&options, B115200);
//mask the character size to 8 bit data & no parity.CHARACTER SIZE SETTING
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
//setting hardware flow control
options.c_cflag |= CRTSCTS;
//Enable the receiver and set local mode .should always be enabled (enable receiver and dont change owner of port)
options.c_cflag |= (CLOCAL | CREAD);
//flush output buffer
options.c_lflag |= FLUSHO;
//choosing raw data disable echoing
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//not setting software flow control
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_oflag &= ~OPOST;
// //output changes :for mapping NL to CR-NL.
// options.c_oflag |= (OPOST | ONLCR );
//
// options.c_oflag |= (NL1 | CR1 | FFDLY);
tcsetattr(intFd, TCSANOW, &options);
return intFd;
}
and this is the sample where I check my serial number:
int serial=0;
ioctl(intFd, TIOCMGET, &serial);
//for ubuntu 7.04 and 10.04
if(serial==16390 || serial==16422 || serial==16454 || serial==16486){//no connection with serial port
i checked my program many times and serial is 16486 every times and it means I have no connection with serial port , I checked my serial cable and it was ok? so how can I Solve my problem?

Serial communication between linux and windows

I am sending data bytes from linux to windows in serial RS232 then everything is ok, only i have to handle 0xa send from linux, because windows read it as 0xd + 0xa.
but when i am sending data bytes from windows to linux some bytes are replaced as -
windows send - 0xd linux receive 0xa
windows send - 0x11 linux receive garbage tyte value in integer something 8200
plese explain what goes wrong when I send data from windows to Linux.
thanks in advance
Windows serial port initialize
char *pcCommPort = "COM1";
hCom = CreateFile( TEXT("COM1"),
GENERIC_READ | GENERIC_WRITE,
0, // must be opened with exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // must use OPEN_EXISTING
0, // not overlapped I/O
NULL // hTemplate must be NULL for comm devices
);
fSuccess = GetCommState(hCom, &dcb);
FillMemory(&dcb, sizeof(dcb),0);
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = CBR_115200; // set the baud rate
dcb.ByteSize = 8; // data size, xmit, and rcv
dcb.Parity = NOPARITY; // no parity bit
dcb.StopBits = ONESTOPBIT; // one stop bit
dcb.fOutxCtsFlow = false;
fSuccess = SetCommState(hCom, &dcb);
buff_success = SetupComm(hCom, 1024, 1024);
COMMTIMEOUTS cmt;
// ReadIntervalTimeout in ms
cmt.ReadIntervalTimeout = 1000;
cmt.ReadTotalTimeoutMultiplier = 1000;
cmt.ReadTotalTimeoutConstant=1000;
timeout_flag = SetCommTimeouts(hCom, &cmt);
windows write serial-
WriteFile(hCom, buffer, len, &write, NULL);
Linux serial initialize-
_fd_port_no = open("//dev//ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
tcgetattr(_fd_port_no, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CS8);
options.c_cflag|=(CLOCAL|CREAD);
options.c_cflag &=~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_cflag &= ~ CRTSCTS;
tcsetattr(_fd_port_no, TCSANOW, &options);
read serial linux-
while(read(_fd_port_no,buffer+_buffer_len,sizeof(buffer))>0)
{
_buffer_len = _buffer_len+sizeof(buffer);
}
Yes, as i told from Linux to windows only NL/CR problem detected but i solved it by byte replacing,
but do you have any idea about serila data send from windows to Linux (byte replacement policy).
Actually I have to send 200 KB file in 200 bytes blocks over serial so which byte could be replaced if send from Windows to Linux
If you are using the ReadFile and WrietFile on Windows and read and write in Linux, it shouldn't really matter what the line-endings are, other than "you have to translate it at some point after receiving it.
This doesn't look right:
while(read(_fd_port_no,buffer+_buffer_len,sizeof(buffer))>0)
{
_buffer_len = _buffer_len+sizeof(buffer);
}
You should take into account the size of the read returned by read.
And if sizeof(buffer) is the actual buffer you are reading into, adding +_buffer_len, when _buffer_len >= sizeof(buffer) will write outside the buffer.
Also slightly worried about this:
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_cflag &= ~ CRTSCTS;
Are you SURE you want a XOFF/CTRL-S (0x13) to stop flow? Usually that means that data with CTRL-S in it won't be allowed - which may not be an issue when sending text data, but if you ever need to send binary data it certainly will be. IXOFF also means that the other end will have to respond to XOFF and XON (CTRL-Q, 0x11) to stop/start the flow of data. Typically, we don't want this in modern systems....
Using RTS/CTS should be safe if you have the wiring correct between the two ends.
I think you have to flush before reading the flux from a serial port
tcflush(_fd_port_no TCIFLUSH);
furthemore Have you tried to see the flux with a console using the commande
cat < dev/ttyS0 ?
To avoid line ending conversions you might need to add:
options.c_iflag &= ~IGNCR; // turn off ignore \r
options.c_iflag &= ~INLCR; // turn off translate \n to \r
options.c_iflag &= ~ICRNL; // turn off translate \r to \n
options.c_oflag &= ~ONLCR; // turn off map \n to \r\n
options.c_oflag &= ~OCRNL; // turn off map \r to \n
options.c_oflag &= ~OPOST; // turn off implementation defined output processing
Also, the following line:
options.c_iflag |= (IXON | IXOFF | IXANY);
Will enable XON/XOFF processing, so the tty driver will process Ctrl-S (XOFF) and Ctrl-Q (XON) characters as flow control (which is probably why you see somethign unexpected when sending 0x11, which is Ctrl-Q). I'd expect that you'd want to turn those bits off:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
In fact, I think you might want to call cfmakeraw() after calling tcgetattr() which should disable all special handling of input and output characters.
Thanks all
This Change Solved my problem
fd_port_no = open("//dev//ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
tcgetattr(_fd_port_no, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CS8);
options.c_cflag|=(CLOCAL|CREAD);
options.c_cflag &=~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~ CRTSCTS;
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_lflag &= ~(ICANON | ISIG | ECHO | ECHONL | ECHOE | ECHOK);
options.c_cflag &= ~ OPOST;
tcsetattr(_fd_port_no, TCSANOW, &options);