I am running into a timing issue with a serial port in C. Long story short, I send a command to a device at one baud rate (say 9600) and expect a response from it at another (say 38400). I have no control over this protocol. A prototype of the situation would look like the following:
open() serial port
set baud rate to 9600 using termios struct
write() command
change baud rate to 38400 using termios struct
read() response
I am running into a problem where the device does not understand the command I sent because the baud rate is changing to 38400 before it completes the write. I am pretty certain write works fine because it returns the number of bytes I intend to write. I tried adding a usleep(100000) after the call to write and while that works sometimes, I cannot guarantee the entire message will be transmitted at 9600 before I change the baud and read a response. I also tried flushing the buffer with tcflush(fd, TCOFLUSH) but I do not want to discard any data so this is also not the correct way.
How can I force write all the serial data and be guaranteed it is written before the next call to change the baud rate to 38400? This seems to be happening at the chip level so is my only hope to include the FTDI libraries (it is an FTDI chip) and access the registers to see when the data is done being transmitted? Thanks.
Use WaitCommEvent with a mask that includes EV_TXEMPTY to wait for the message to be sent out by the driver.
Why not just set the transmitter to 9600 and the receiver to 38400? Most common serial port hardware support this.
// fd = file descriptor of serial port. For example:
// int fd = open ("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_SYNC);
int
somefunction (int fd)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0)
{
error ("error %d from tcgetattr: %s", errno, strerror (errno));
return -1;
}
cfsetospeed (&tty, B9600); // set tty transmitter to 9600
cfsetispeed (&tty, B38400); // set receiver to 38400
if (tcsetattr (fd, TCSADRAIN, &tty) != 0) // waits until pending output done before changing
{
error ("error %d from tcsetattr", errno);
return -1;
}
return 0;
}
I've amended my code to use TCSADRAIN instead of TCSANOW so that the rate change does not occur until after all pending output has been sent.
You should be using tcdrain() or the TCSADRAIN optional action for tcsetattr() instead of tcflush
I used the Windows FTDI API and found their entire model to be annoyingly asynchronous. With a normal serial port I would expect you could do the math on your message length (in bits, including start, stop, parity) and baud rate and have a reasonable estimate of how long to sleep until your message is transmitted. However, with the FTDI part you're dealing with USB latencies and unknown queuing in the FTDI part itself. If your device's replies come in under 1ms it might not even be possible to turn the FTDI around reliably between your TX and RX phases.
Would it be possible to hook up the RX to a different UART? That would greatly simplify your problem.
If not, you might consider using a special cable that connects your TX back to your RX so that you can see your message go out before you cut over. With a diode you could avoid the device also seeing its own transmissions. 3/4 duplex?
Related
I have written some C code for aarch64-based SoC (Rockchip RK3399) with Debian 9 LXDE, to receive data from a GPS module. The GPS module is connected to "ttyS4" port in my SoC. I have created a pthread to receive data from the GPS module. I'm using the termios library. So my flow goes as follows:
Initialize the UART port (baud rate, parity, stop bits etc.).
Create a thread to receive the data from the module.
Now I need to change the baud rate of the UART upon receiving the baud rate from a external source. I am able to receive the new baud rate which I need to set it to the port.
How do I set the new baud rate to the port? Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Or is there any other simple way or function to set the UART to the port ?
My Initialization code:
int Gpsfd;
struct termios Gps_termios, Gps_old;
void GpsPortInit(void)
{
char path[12] = "/dev/ttyS4";
//open GSM_termios for tx/rx
Gpsfd = open(path, O_RDWR | O_NOCTTY);
if (Gpsfd < 0)
printf("port failed to open\n");
//save current attributes
tcgetattr(Gpsfd, &Gps_old);
bzero(&Gps_termios, sizeof(Gps_termios));
Gps_termios.c_cflag = CLOCAL | CREAD | CS8;
if (!strcmp(g_sParameters.RS232BaudRate, "9600"))
{
Gps_termios.c_cflag |= B9600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "19200"))
{
Gps_termios.c_cflag |= B19200;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "57600"))
{
Gps_termios.c_cflag |= B57600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "115200"))
{
Gps_termios.c_cflag |= B115200;
}
Gps_termios.c_iflag = IGNPAR;
Gps_termios.c_oflag = 0;
Gps_termios.c_lflag = 0;
Gps_termios.c_cc[VTIME] = 0;
Gps_termios.c_cc[VMIN] = 1;
//clean the line and set the attributes
tcflush(Gpsfd, TCIFLUSH);
tcsetattr(Gpsfd, TCSANOW, &Gps_termios);
}
This is the baudrate change function suggested below:
int set_baudrate(speed_t speed)
{
//struct termios tty;
if (tcgetattr(Gpsfd, &Gps_termios) < 0) {
printf("Error from tcgetattr1: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&Gps_termios, speed);
cfsetispeed(&Gps_termios, speed);
if (tcsetattr(Gpsfd, TCSANOW, &Gps_termios) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(Gpsfd, TCIOFLUSH); /* discard buffers */
return 0;
}
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do I set the new baud rate to the port?
In Linux, userspace does not have direct access to hardware such as a UART, so your program is constrained to use the serial terminal and the termios API.
That is confirmed by your use of /dev/ttyS4 (rather than a device node named /dev/uart4).
Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
That should not matter.
The pthread is merely "reading" from the termios buffer, rather than directly accessing any hardware.
However your program needs to be robust and able to cope with possible mangled messages.
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Closing the file descriptor would deny your program further access to the serial terminal, so that does not make sense.
Userspace does not have direct access to hardware such as a UART, so your program is constrained to use the termios API.
Or is there any other simple way or function to set the UART to the port ?
Use the termios API to change the baudrate of the serial terminal, e.g.:
int set_baudrate(int fd, speed_t speed)
{
struct termios tty;
int rc1, rc2;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
rc1 = cfsetospeed(&tty, speed);
rc2 = cfsetispeed(&tty, speed);
if ((rc1 | rc2) != 0 ) {
printf("Error from cfsetxspeed: %s\n", strerror(errno));
return -1;
}
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(fd, TCIOFLUSH); /* discard buffers */
return 0;
}
The above should be similar to the initialization that you used to "initialize the UART port (baud rate, parity, stop bits etc.) ... using the termios library".
That is, the code above adheres to Setting Terminal Modes Properly
.
Note that the tcsetattr() call could use the argument TCSAFLUSH instead of TCSANOW. That would "change attributes when output has drained; also flush pending input" according to the termios.h man page.
However waiting for the output to drain seems unnecessary, as changing the baudrate implies the current baudrate is incompatible with the other end of the serial link. IOW garbage is presumably being transmitted and garbage is presumably being received.
So immediately change the baudrate (i.e. use TCSANOW), and then discard the contents of the receive buffer.
Addendum: review of your initialization code
Your initialization code is low quality, is not portable, and may not reliably setup the serial terminal.
Starting with a zeroed-out termios structure (instead of the existing values) is contrary to recommended practice as described in Setting Terminal Modes Properly
and Serial Programming Guide for POSIX Operating Systems.
See this answer for concise code for proper serial terminal initialization to blocking non-canonical mode.
This is the baudrate change function suggested below:
Note that the routine has been updated in my answer to enhance error reporting.
You neglect to show exactly how you use/call this routine.
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do you coordinate any baudrate changes with the other end of the serial link?
You also neglect to describe your test procedures, what baudrates you are using, how the other end is setup.
Any of these omissions could reveal the reason(s) why this set_baudrate() routine "is not working" for you.
I am getting successful reads of a fd in Linux, but with 0 bytes read which means EOF has been reached. I should be reading 19 bytes in every read.
The project is a motor driver that sends out packets of 19 bytes to drive 2 DC motors, and it also needs to read the same size packet coming from the motor with updated position, command, and status info.
I open the fd like this:
mc_fd = InitPort("/dev/ttyS1", "COM2", O_NONBLOCK | O_RDWR | O_SYNC, B115200);
Here is the function to initialize the port:
int InitPort( char *port, char *name, int oflags, speed_t baudRate ) {
int fd; // File descriptor
fd = open(port, oflags); // Open the port like a file
assert(fd > 0); // Open returns -1 on error
struct termios options; // Initialize a termios struct
tcgetattr(fd, &options); // Populate with current attributes
cfsetospeed (&options, baudRate); // Set baud rate out
cfsetispeed (&options, baudRate); // Set baud rate in (same as baud rate out)
options.c_cflag &= ~CSIZE; // Clear bit-length flag so it can be set
//8N1 Serial Mode
options.c_cflag |= CS8; // Set bit-length: 8
options.c_cflag &= ~PARENB; // Set parity: none
options.c_cflag &= ~CSTOPB; // Set stop bit: 1
options.c_cflag &= ~CRTSCTS; // Set flow control: none
options.c_iflag &= ~ICANON; // Enable canonical input
options.c_oflag &= ~OPOST; // Disables all output processing (prevents CR in output)
options.c_cflag |= (CLOCAL | CREAD);// Enable receiver, and set local mode
tcsetattr(fd, TCSANOW, &options); // Set new attributes to hardware
return fd;
}
Originally, I only used the O_RDWR flag and reads of the fd would fail with EAGAIN (or EWOULDBLOCK). I have been trying synchronization and non-blocking settings to see if I could receive packets. At least now I am reading successfully (I think).
I am able to write packets out at 120Hz, and reads of the fd return "success" at the same rate, although with 0 bytes read.
How do I get read() to read the incoming packets?
Here is the read code along with the output from the terminal:
bytesRead = read( mc_fd, readPacket, MC_PACKET_SIZE );
printf("\npacket: %019X\n", &readPacket);
perror("error type ");
printf("bytes read = %d\n", bytesRead);
packet: 00000000000B63B4140
error type : Success
bytes read = 0
The 8-digit hex number in the least significant part of the packet is always similar to that shown, and is not what is expected in the packet.
This is Debian running on an embedded linux SBC (single board computer). I am able to read other file descriptors in the program with no problems. I am still fairly new to Linux and may be missing something obvious. Thanks!
...but with 0 bytes read which means EOF has been reached.
Incorrect.
You're reading a serial terminal in non-blocking mode.
A return code of zero simply means that no data was available from the terminal at that time.
That's what your program (which you should have posted) has to deal with when you use non-blocking mode.
How do I get read() to read the incoming packets?
Use blocking mode (i.e. remove the O_NONBLOCK option from the open()) if you do not want to see a return code of zero (or errno set to EAGAIN).
But don't expect the read() syscall to message-align the packets for you unless you have text in canonical mode.
Study this answer.
The 8-digit hex number in the least significant part of the packet is always similar to that shown, and is not what is expected in the packet.
You've posted so little of your code (which is grounds for closing the question), but you try to read the data into readPacket, which seems to be a (byte?) array.
But then you treat readPacket as if it was an integer in the printf().
Printing the address of the array address (or the address of an integer variable) accomplishes nothing (i.e. "8-digit hex number ... is always similar to that shown"). You have not displayed anything that might have been received.
If you're using a little-endian, 32-bit processor, accessing a byte array as a long interger will reverse the order of the bytes of each word (i.e. "not what is expected"), and only access the first four bytes, which could be represented by eight hexadecimal digits .
I am still fairly new to Linux ad may be missing something obvious.
Although Linux is one of those OSes where (almost) "everything is a file", those "files" may not be equal. In particular the device file that your program accesses, i.e. /dev/ttyS1, is a serial terminal device. Serial terminals require additional device configuration, which is performed with the termios structure.
Since you have posted only a few lines of your program, and make no mention of any termios concepts other than baudrate, your program cannot be evaluated.
Addendum
Now that you've posted some initialization code, a few more mistakes are evident.
Regardless of your programming experience, the following inconsistency between what the code does and the comment is a flaw that can prolong debugging.
options.c_iflag &= ~ICANON; // Enable canonical input
Clearing the ICANON flag enables non-canonical input, the opposite of what the comment states.
You have not described the 19 bytes of data, so whether canonical mode is appropriate cannot be determined.
Your termios initialization is well written (i.e. you use the proper Boolean operators instead of direct assignments), but is incomplete (based on the existing code as executed for non-canonical mode).
All of the necessary flags for non-canonical mode can be configured by simply using the cfmakeraw() routine.
Your code does not initialize the VMIN and VTIME parameters, but since the combination of non-canonical and non-blocking modes disables that feature, it doesn't matter.
Since you have done a poor job of describing what you are trying to do, the appropriate corrections cannot be suggested.
I should be reading 19 bytes in every read. [...]
"/dev/ttyS1"
On a serial port with only 16 byte fifo or less? I don't think that will work.
Serial ports are character devices for a good reason - you want to code in a way that reads work anywhere from zero bytes (if a timeout is enabled), one byte and up to the size of the internal fifo. Don't expect them to stay in neat packets even if you (try to) send them that way.
I'm testing the CAN interface on an embedded device (SOC / ARM core / Linux) using SocketCAN, and I want to send data as fast as possible for testing, using efficient code.
I can open the CAN device ("can0") as a BSD socket, and send frames with "write". This all works well.
My desktop can obviously generate frames faster than the CAN transmission rate (I'm using 500000 bps). To send efficiently, I tried using a "select" on the socket file descriptor to wait for it to become ready, followed by the "write". However, the "select" seems to return immediately regardless of the state of the send buffer, and "write" also doesn't block. This means that when the buffer fills up, I get an error from "write" (return value -1), and errno is set to 105 ("No buffer space available").
This mean I have to wait an arbitrary amount of time, then try the write again, which seems very inefficient (polling!).
Here's my code (C, edited for brevity):
printf("CAN Data Generator\n");
int skt; // CAN raw socket
struct sockaddr_can addr;
struct canfd_frame frame;
const int WAIT_TIME = 500;
// Create socket:
skt = socket(PF_CAN, SOCK_RAW, CAN_RAW);
// Get the index of the supplied interface name:
unsigned int if_index = if_nametoindex(argv[1]);
// Bind CAN device to socket created above:
addr.can_family = AF_CAN;
addr.can_ifindex = if_index;
bind(skt, (struct sockaddr *)&addr, sizeof(addr));
// Generate example CAN data: 8 bytes; 0x11,0x22,0x33,...
// ...[Omitted]
// Send CAN frames:
fd_set fds;
const struct timeval timeout = { .tv_sec=2, .tv_usec=0 };
struct timeval this_timeout;
int ret;
ssize_t bytes_writ;
while (1)
{
// Use 'select' to wait for socket to be ready for writing:
FD_ZERO(&fds);
FD_SET(skt, &fds);
this_timeout = timeout;
ret = select(skt+1, NULL, &fds, NULL, &this_timeout);
if (ret < 0)
{
printf("'select' error (%d)\n", errno);
return 1;
}
else if (ret == 0)
{
// Timeout waiting for buffer to be free
printf("ERROR - Timeout waiting for buffer to clear.\n");
return 1;
}
else
{
if (FD_ISSET(skt, &fds))
{
// Ready to write!
bytes_writ = write(skt, &frame, CAN_MTU);
if (bytes_writ != CAN_MTU)
{
if (errno == 105)
{
// Buffer full!
printf("X"); fflush(stdout);
usleep(20); // Wait for buffer to clear
}
else
{
printf("FAIL - Error writing CAN frame (%d)\n", errno);
return 1;
}
}
else
{
printf("."); fflush(stdout);
}
}
else
{
printf("-"); fflush(stdout);
}
}
usleep(WAIT_TIME);
}
When I set the per-frame WAIT_TIME to a high value (e.g. 500 uS) so that the buffer never fills, I see this output:
CAN Data Generator
...............................................................................
................................................................................
...etc
Which is good! At 500 uS I get 54% CAN bus utilisation (according to canbusload utility).
However, when I try a delay of 0 to max out my transmission rate, I see:
CAN Data Generator
................................................................................
............................................................X.XX..X.X.X.X.XXX.X.
X.XX..XX.XX.X.XX.X.XX.X.X.X.XX..X.X.X.XX..X.X.X.XX.X.XX...XX.X.X.X.X.XXX.X.XX.X.
X.X.XXX.X.XX.X.X.X.XXX.X.X.X.XX.X.X.X.X.XX..X..X.XX.X..XX.X.X.X.XX.X..X..X..X.X.
.X.X.XX.X.XX.X.X.X.X.X.XX.X.X.XXX.X.X.X.X..XX.....XXX..XX.X.X.X.XXX.X.XX.XX.XX.X
.X.X.XX.XX.XX.X.X.X.X.XX.X.X.X.X.XX.XX.X.XXX...XX.X.X.X.XX..X.XX.X.XX.X.X.X.X.X.
The initial dots "." show the buffer filling up; Once the buffer is full, "X" starts appearing meaning that the "write" call failed with error 105.
Tracing through the logic, this means the "select" must have returned and the "FD_ISSET(skt, &fds)" was true, although the buffer was full! (or did I miss something?).
The SockedCAN docs just say "Writing CAN frames can be done similarly, with the write(2) system call"
This post suggests using "select".
This post suggests that "write" won't block for CAN priority arbitration, but doesn't cover other circumstances.
So is "select" the right way to do it? Should my "write" block? What other options could I use to avoid polling?
After a quick look at canbusload:184, it seems that it computes efficiency (#data/#total bits on the bus).
On the other hand, according to this, max efficiency for CAN bus is around 57% for 8-byte frames, so you seem not to be far away from that 57%... I would say you are indeed flooding the bus.
When setting a 500uS delay, 500kbps bus bitrate, 8-byte frames, it gives you a (control+data) bitrate of 228kbps, which is lower than max bitrate of the CAN bus, so, no bottleneck here.
Also, since in this case only 1 socket is being monitored, you don't need pselect, really. All you can do with pselect and 1 socket can be done without pselect and using write.
(Disclamer: hereinafter, this is just guessing since I cannot test it right now, sorry.)
As of why the behavior of pselect, think that the buffer could have byte semantics, so it tells you there is still room for more bytes (1 at least), not necessarily for more can_frames. So, when returning, pselect does not inform you can send the whole CAN frame. I guess you could solve this by using SIOCOUTQ and the max size of the Rx buffer SO_SNDBUF, but not sure if it works for CAN sockets (the nice thing would be to use SO_SNDLOWAT flags, but it is not changable in Linux's implementation).
So, to answer your questions:
Is "select" the right way to do it?
Well, you can do it both ways, either (p)select or write, since you are only waiting for one file descriptor, there is no real difference.
Should my "write" block? It should if there is no single byte available in the send buffer.
What other options could I use to avoid polling? Maybe by ioctl'ing SIOCOUTQ and getsockopt'ing SO_SNDBUF and substracting... you will need to check this yourself. Alternatively, maybe you could set the send buffer size to a multiple of sizeof(can_frame) and see if it keeps you signaling when less than sizeof(can_frame) are available.
Anyhow, if you are interested in having a more precise timing, you could use a BCM socket. There, you can instruct the kernel to send a specific frame at a specific interval. Once set, the process run in kernel space, without any system call. In this way, user-kernel buffer problem is avoided. I would test different rates until canbusload shows no rise in bus utilization.
select and poll worked for me right with SocketCan. However, carefull configuration is require.
some background:
between user app and the HW, there are 2 buffers:
socket buffer, where its size (in bytes) is controlled by the setsockopt's SO_SNDBUF option
driver's qdisc, where its size (in packets) is controlled by the "ifconfig can0 txqueuelen 5" command.
data path is: user app "write" command --> socket buffer -> driver's qdisc -> HW TX mailbox.
2 flow control points exist along this path:
when there is no free TX mailboxe, driver freeze driver's qdisc (__QUEUE_STATE_DRV_XOFF), to prevent more packets to be dequeued from driver's qdisc into HW. it will be un-freezed when TX mailbox is free (upon TX completion interrupt).
when socket buffer goes above half of its capacity, poll/select blocks, until socket buffer goes beyond half of its capacity.
now, assume that socket buffer has room for 20 packets, while driver's qdisc has room for 5 packets. lets assume also that HW have single TX mailbox.
poll/select let user write up to 10 packets.
those packets are moved down to socket buffer.
5 of those packets continue and fill driver's qdisc.
driver dequeue 1st packet from driver's qdisc, put it into HW TX mailbox and freeze driver's qdisc (=no more dequeue). now there is room for 1 packet in driver's qdisc
6th packet is moved down successfully from socket buffer to driver's qdisc.
7th packet is moved down from socket buffer to driver's qdisc, but since there is no room - it is dropped and error 105 ("No buffer space available") is generated.
what is the solution?
in the above assumptions, lets configure socket buffer for 8 packets. in this case, poll/select will block user app after 4 packets, ensuring that there is room in driver's qdisc for all of those 4 packets.
however, socket buffer is configured to bytes, not to packet. translation should be made as the following: each CAN packet occupy ~704 bytes at socket buffer (most of them for the socket structure). so, to configure socket buffer to 8 packet, the size in bytes should be 8*704:
int size = 8*704;
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
I'm trying to implement a timeout for some hardware transmissions, to add security to a big project. I already implemented timeout using select for UART transmission, but I don't know how to add a timeout in a SPI transmission.
This is my reading code:
int spi_read(int fd, char command, char* buffer, int size, int timeout)
{
struct spi_ioc_transfer xfer[2];
int status;
memset(buffer, 0, sizeof(buffer));
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = (unsigned int)(&command);
xfer[0].len = 1;
xfer[1].rx_buf = (unsigned int)buffer;
xfer[1].len = size;
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if(status < 0)
return NOERROR;
else
return EHWFAULT1;
}
It sends a byte sized command and receives a response of certain size (in half duplex mode). How can I implement a timeout in the response? Can it be implemented using select? Should I separe both transactions and use select or better use an alarm?
Then, I have the same question for a full duplex mode, which is implemented too using ioctl. Can you give me any hints?
In hardware the SPI master does not 'wait' for a response. By definition, the SPI master provides the clock cycles and the slave must reply. The concept of waiting for a response doesn't apply to the SPI bus. (I'm assuming you're operating the SPI master)
(deeper in the protocol, the SPI might poll the hardware to see if it's done/ready; but the SPI bus itself is getting an immediate answer every time).
To clarify: the SPI clocks in whatever is on the SPI MISO pin. Whatever level is on the MISO pin is the reply, even if the slave is not explicitly driving a level. The only way to detect a non responsive slave is to pullup/pulldown the MISO in a way that can not be interpreted as a valid message.
I wrote an application that must use serial ports on Linux, especially ttyUSB ones. Reading and writing operations are performed with standard select()/read() loop and write(), and there is probably nothing wrong in them, but initialization code (or absence of some part of it) damages something in the tty subsystem. Here it is:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
termios tio = {0};
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
Another tcsetattr(_fd, TCSANOW, &_termios) sits in the destructor, but it is irrelevant.
With or without this termios initialization, strange things happen in system after the application exits. Sometimes plain cat (or hd) exits immediately printing nothing or same stuff each time, sometimes it is waiting and not displaying any of the data that is surely sent onto the port; and close() (read() too, but not every time) emits a strange WARNING to dmesg referring to usb-serial.c.
I checked the hardware and firmware tens of times (even on different machines) and I am sure it is working as intended; moreover, I stripped the firmware to just print same message over and over.
How can I use serial port without destroying anything? Thanks.
Hitting a WARN_ON line might mean that you've hit a kernel bug. I know that there has been much work on improving the USB-serial driver lately; I suggest trying a newer kernel, and/or asking on the linux-usb#vger.kernel.org mailing list.
I am not sure what is wrong with your snippet of code there but this might come in handy, if you haven't already seen it: Serial Programming Guide for POSIX Operating Systems
I had to do some serial port interfacing quite recently and this library worked fine, that might serve as another example.
Just as a side note really, your error check on open isn't quite right - error conditions are signalled by a return value of -1. (0 is a perfectly valid fd, usually connected to stdin.)
You might want to try:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
- termios tio;
+ termios tio;
+ memcpy(&tio, &_termios, sizeof(struct termios));
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
This makes it so that any unexpected fields of termios on your system get somewhat reasonable values.
Okay. This may not be a perfect solution... it definitely isn't. I just threw out FT232 converter (fried it, actually), and used CP2102-based one. It just works now (and also is 6 times cheaper).