I am having serious trouble with rs232 communications in linux so i wrote this test program to make sure it was not other parts of my program interfering with the serial communications.
The program however does not work so as i feared it is the serial port code that is the problem.
I have a laptop with centos running the program and that is connected to a computer with windows xp running hyperterminal. the code executes ok according to the error checking but nothing is showing up in huperterminal.
The serial pore setup I am trying to achive is 115200 baud rate, 8 databits, 1 stopbit and mark parity.
This is the program:
#include <termios.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <ncurses.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int port, serial, i;
unsigned long nobw;
char buf[10];
struct termios options;
port = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
if (port == -1)
perror("open_port: Unable to open /dev/ttyS0 - ");
else
printf("port open \n");
tcgetattr(port, &options); // get current settings
cfsetispeed(&options, 115200); // set baud rate
cfsetospeed(&options, 115200); // set baud rate
options.c_cflag &= ~CSIZE; // Mask the character size bits
options.c_cflag |= CS8; // 8 bit data
options.c_cflag &= ~PARENB; // set parity to no
options.c_cflag &= ~PARODD; // set parity to no
options.c_cflag |= CSTOPB;//set mark parity by using 2 stop bits
options.c_cflag |= (CLOCAL | CREAD);
options.c_oflag &= ~OPOST;
options.c_lflag &= 0;
options.c_iflag &= 0; //disable software flow controll
options.c_oflag &= 0;
tcsetattr(port, TCSANOW, &options);// save the settings
ioctl(port, TIOCMGET, &serial);
serial |= TIOCM_DTR; // set DTR to high
ioctl(port, TIOCMSET, &serial);
for(i = 0; i < 10; i++)
{
buf[i] = i;
}
for(i = 0; i < 10; i++)
{
errno = 0;
nobw = write(port, buf, 1);
if(nobw == -1)
perror("WriteComm:");
else
printf("sent character %d \n", i);
}
return 0;
}
This is all done from tutorials on the interned i have little idea what im doing, can you see where i have gone wrong ?
also if anyone knows how to do space parity that would also be appreciated.
Maybe worth checking if the setup works without your code? :)
If you connect using minicom on the linux side and hyperterminal on the windows side, can you pass data back and forth?
Related
I have an application that interfaces with serial ports. Sometimes when I close a serial port using close(int fd), the call to close() takes 30 seconds or so.
Is there a way I can configure the system to forgo this delay when closing the file descriptor?
I open my port by fd = open(addr, O_RDWR );
Most of the code configuing the port is shown below:
rc = tcgetattr(fd, &opts);
if(rc)
{
printf("TC get attr failed %d \n", errno);
return rc;
}
// Configure parity
opts.c_cflag &= ~PARENB;
if(com_port->parity_)
{
opts.c_cflag |= PARENB;
if(com_port->parity_ >1)
{
opts.c_cflag = PARODD;
}
}
// Configure stop bits
opts.c_cflag &= ~CSTOPB; // Clear stop bit for default setting
switch(com_port->stop_bit_)
{
case 1:
//default is 1 stop bit for POSIX
break;
case 2:
opts.c_cflag |= CSTOPB;
break;
default:
//Use the default setting for POSIX
printf("Warning using default stop bit\n");
break;
}
// Configure bytes size
opts.c_cflag &= ~CSIZE; // Clear byte size bit
opts.c_cflag |= (CS8); // Set byte size bit
// Configure flow control
opts.c_cflag &= ~CRTSCTS; // flow control disabled
opts.c_cflag |= CREAD | CLOCAL; // allow read and ignore ctrl lines
// Disable canonical mode
opts.c_lflag &= ~ICANON;
// Disable echoing
opts.c_lflag &= ~ECHO;
opts.c_lflag &= ~ECHOE;
opts.c_lflag &= ~ECHONL;
// Disable signal chars
opts.c_lflag &= ~ISIG;
// Disable flow control
opts.c_iflag &= ~(IXON | IXOFF | IXANY);
// Disable special handling
opts.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
// Configure output modes
opts.c_oflag &= ~OPOST;
opts.c_oflag &= ~ONLCR;
// Default timeouts
opts.c_cc[VTIME] = 10;
opts.c_cc[VMIN] = 0;
I close the file with this code:
int CloseSerialPort(SerComPort* com_port)
{
if(com_port->port_handle_ > 0)
{
tcflush(com_port->port_handle_ TCIOFLUSH);
}
return close(com_port->port_handle_);
}
I try set in c++ some serial port, but when I try function tcsetatrr, its return error -1. Port opens without problems.
char port_name[] = "/dev/ttyS1";
int port = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY);
if(port < 0){
std::cout << "Cant open port" << std::endl;
return;
}
struct termios settings;
tcgetattr(port, &settings);
cfgetispeed(&settings);
//std::cout << settings.c_cflag;
//Baudrate
cfsetispeed(&settings, B115200);
cfsetospeed(&settings, B115200);
//Data bits
settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8;
//Parity
settings.c_cflag |= ~PARENB;
//Stop bit
settings.c_cflag &= ~CSTOPB;
//Flow control
settings.c_cflag |= ~CRTSCTS;
settings.c_iflag &= ~(IXON | IXOFF | IXANY);
int er = tcsetattr(port, TCSANOW, &settings);
if (er<0) {
fprintf(stderr, "Error openinig: %s\n", strerror(errno));
}
close(port);
output:
Input/output error
How can I fix that? I was running code as root. The problem is not in the configuration of the settings structure because after commenting it out, I get the same error
The most likely answer is that you don't really have a serial device /dev/ttyS1. Try /dev/ttyS0 instead. I have the same deal on my PC.
I see a few additional problems here.
You don't check if tcgetattr() succeeds, so don't be so sure you even get to tcsetattr().
Calling settings.c_cflag |= ~PARENB; or settings.c_cflag |= ~CRTSCTS; is most likely not what you want to do (will raise all flags except the selected one).
I am using non-blocking IO where I simply try to write and read to a serial port. Reading of the serial port works as expected whereas writing doesn't.
This is how I've set up the serial line. As you can see, it is setup to non-blocking and canonical. Maybe some of the flags are redundant, but they are mostly taken from an example here: Serial setup example
tSerial::tSerial(const char *serialPort, bool echo)
{
// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
m_serialPort = open(serialPort, O_RDWR);
// Create new termios struct, we call it 'tty' for convention
struct termios tty;
// Read in existing settings, and handle any error
if (tcgetattr(m_serialPort, &tty) != 0)
{
spdlog::error("[tSerial] error {} from tcgetattr: {}", errno, strerror(errno));
throw std::runtime_error("Failed to get existing serial settings");
}
tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size
tty.c_cflag |= CS8; // 8 bits per byte (most common)
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
// tty.c_lflag &= ~ICANON;
tty.c_cflag |= ICANON;
if (!echo)
{
tty.c_lflag &= ~ECHO; // Disable echo
}
else
{
tty.c_lflag |= ECHO; // Enable echo
}
// tty.c_lflag &= ~ECHOE; // Disable erasure
// tty.c_lflag &= ~ECHONL; // Disable new-line echo
// tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// Set in/out baud rate to be 115200
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
// Save tty settings, also checking for error
if (tcsetattr(m_serialPort, TCSANOW, &tty) != 0)
{
spdlog::error("[tSerial] error {} from tcsetattr: {}", errno, strerror(errno));
throw std::runtime_error("Failed to set new serial settings");
}
// Set non-blocking
int flags;
if ((flags = fcntl(m_serialPort, F_GETFL, 0)) == -1)
{
flags = 0;
}
fcntl(m_serialPort, F_SETFL, flags | O_NONBLOCK);
}
This is how I try to write. I am using select to wait for the IO resource to become available since trying to write to it directly gives the error Resource temporarily unavailable. But even waiting 20 seconds, the device is still unavailable.
void tSerial::writeSerial(std::string message)
{
int n;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(m_serialPort, &rfds);
spdlog::debug("[tSerial] writing command to serial {}", message);
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
int retval = select(1, NULL, &rfds, NULL, &tv);
if (retval == -1)
{
spdlog::error("[tSerial] select error");
}
else if (retval)
{
n = write(m_serialPort, message.c_str(), message.length());
tcflush(m_serialPort, TCIOFLUSH);
if (n < 0)
{
spdlog::error("[tSerial] error writing: {}", strerror(errno));
}
}
else
{
spdlog::error("[tSerial] Resource unavailable after 20 seconds wait");
}
}
I am using select to wait for the IO resource to become available ... But even waiting 20 seconds, the device is still unavailable.
Your program is not behaving as you expect because you have not properly programmed the select() call:
int retval = select(1, NULL, &rfds, NULL, &tv);
Per the man page the first argument "should be set to the highest-numbered file descriptor in any of the three sets, plus 1."
Since you are passing the value 1, the only file descriptor that the select() could check is stdin (which is read-only and will never be ready for output) and never (the file descriptor for) the opened serial terminal.
Instead the syscall needs to be
select(m_serialPort + 1, ...);
Your code has additional issues.
(1) Use of nonblocking mode is questionable.
Seems like you prefer to add extra code to your program to wait instead of letting the OS do it for you. If your program does not utilize its time slice effectively and efficiently, then you would be better off having the OS manage the system resources.
(2) Use of tcflush() after the write() is nonsensical and wrong!
You probably meant to use tcdrain().
Study the man pages.
(3) When you copied & modified the code you introduced at least one bug.
ICANON is in the c_lflag member, and not in c_cflag.
(4) Good coding practice is using variable names that make sense.
Reusing a variable name intended for the read argument as the write argument is sloppy and confusing:
fd_set rfds;
...
int retval = select(..., NULL, &rfds, NULL, &tv);
Instead of rfds, your program should be using something like wfds for the third argument.
... trying to write to it directly gives the error Resource temporarily unavailable
The problem when using select() is easily explainable.
For the above issue you need to provide a minimal, reproducible example, i.e. a complete program.
I am having trouble selecting correct settings for the serial port to be opened.
Information I have is the following:
Synchronization: Asynchronous method
Communication method: Full duplex transmission
Communication speed: 9600 bps (bits per second)
Transmission code: 8-bit data
Data configuration: Start bit 1, data 8-bit + parity 1, stop bit 1
Error control: Horizontal (CRC) and vertical (even number) parities
1 byte configuration
PC should not use a control signal (DTR, DSR, RTS and CTS) at the time of this connection.
What I have is something like:
bool configurePort(void) {
struct termios port_settings;
bzero(&port_settings, sizeof(port_settings));
tcgetattr(fd, &port_settings);
cfsetispeed(&port_settings, B9600);
cfsetospeed(&port_settings, B9600);
port_settings.c_cflag &= ~CSIZE;
port_settings.c_cflag |= CS8;
// parity bit
//port_settings.c_cflag &= ~PARENB;
//port_settings.c_cflag &= ~PARODD;
// hardware flow
port_settings.c_cflag &= ~CRTSCTS;
// stop bit
//port_settings.c_cflag &= ~CSTOPB;
port_settings.c_iflag = IGNBRK;
port_settings.c_iflag &= ~(IXON | IXOFF | IXANY);
port_settings.c_lflag = 0;
port_settings.c_oflag = 0;
port_settings.c_cc[VMIN] = 1;
port_settings.c_cc[VTIME] = 0;
port_settings.c_cc[VEOF] = 4;
tcsetattr(fd, TCSANOW, &port_settings);
return true;
}
Tried various modifications but nothing seems to work.
The device is connected over USB-serial (ttyUSB0) and I have permissions.
It opens device, sends (?) data but never gets anything back...
Can someone point me what should be done?
Try with this:
bool configurePort(void) {
struct termios port_settings;
bzero(&port_settings, sizeof(port_settings));
if(tcgetattr(fd, &port_settings) < 0) {
perror("tcgetattr");
return false;
}
cfmakeraw(&port_settings);
cfsetispeed(&port_settings, B9600);
cfsetospeed(&port_settings, B9600);
//input
port_settings.c_iflag &= ~(IXON | IXOFF | IXANY); //disable flow control
//local
port_settings.c_lflag = 0; // No local flags
//output
port_settings.c_oflag |= ONLRET;
port_settings.c_oflag |= ONOCR;
port_settings.c_oflag &= ~OPOST;
port_settings.c_cflag &= ~CRTSCTS; // Disable RTS/CTS
port_settings.c_cflag |= CREAD; // Enable receiver
port_settings.c_cflag &= ~CSTOPB;
tcflush(fd, TCIFLUSH);
if(tcsetattr(fd, TCSANOW, &port_settings) < 0) {
perror("tcsetattr");
return false;
}
int iflags = TIOCM_DTR;
ioctl(fd, TIOCMBIC, &iflags); // turn off DTR
return true;
} //configure port
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