Serial Port write, wait for transmission complete with the function tcdrain()
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Serial Port write, wait for transmission complete with the function tcdrain()
Hi everybody! It's my first post, so please tell if I'm doing something wrong.
So, in C programming, after writing to the serial port (/dev/ttyUSB0 - USB to serial converter!) with the function "write" I want to check when the transmit buffer is empty, all bytes transmitted, with the function "tcdrain" wich should wait until all bytes are transmitted and then return.
The problem is, if I write up to 128 bytes it returns immediately, if I write between 128 and 256 bytes it returns weird (like, for every byte above 128 it waits for 2 bytes to be tramsmitted!), only above 256 bytes it returns correctly after all bytes are sent.
I have measured the time with gettimeofday(), and flushed the port before write (just to be sure). I also used slow baud-rate of 9600, to have better resolution in time-measure. The function "tcdrain" returns 0, so no error and no other signal interrupts it. I considered the duration for one byte to transmit: 10bits x 1/baud-rate ~ (1ms 41us)
Anyone know the reason?
Any other way to check for transmission complete on the serial port?
And here the code I used:
Code:
tcflush(port, TCOFLUSH);
gettimeofday(&timeStart, NULL);
write(port, &buffer, cnt);
tcdrain(port);
gettimeofday(&timeStop, NULL);
s = timeStop.tv_sec - timeStart.tv_sec;
if ( timeStop.tv_usec < timeStart.tv_usec )
{
us = 1E6 + timeStop.tv_usec - timeStart.tv_usec;
s--;
}
else
us = timeStop.tv_usec - timeStart.tv_usec;
ms = us / 1000;
us = us - 1000 * ms;
printf("transmision time of %d bytes: %ds, %dms, %dus\n", cnt, s, ms, us);
close(port);
Well, I know the serial port is obsolete, USB is state of the art now, but still...
Anyone can help me here to determine a transmission complete?
I guess it has to do with the fact that it's no hardware port, but a driver and USB-Serial converter, but if it works with over 256 bytes, it should somehow work with less too, I guess...
I hope the serial port isn't obsolete. I think I would chalk it all up to whatever the particular serial port driver reports. Maybe you can look at the ioctl API for the driver, and see if it actually provides any way to report the information you are asking for. If it isn't present, then the behavior probably can't be considered meaningful.
theNbomr: thanks for the answer!
"look at the ioctl API" ? Well, I'm new to linux, I'm new to C-programming, new to life!!!
Let me see if I get this right..., I don't know how the kernel installs a driver, but I found the "pl2303.c" driver-file for my device in "/usr/src/linux-3.11.10-7/drivers/usb/serial/" (the .h file is also there), and in this file there is a structure of type "usb_serial_driver", named "pl2303_device" with all the functions that "the kernel may call???". Is that how it works?
The functions listed in this structure are, of course, defined above in the file.
Represent these functions all the device's capabilities?
Well, while nobody else answers to me, or explains me how things work, I've checked the driver-file from a ftdi-device and compared it with my pl2303.c file.
Now, I think I was right in what I said above, that structure really represents all the device's capabilities, the functions that the device supports, and to my despair, the ftdi driver has an extra (not just one!) function "tx_empty" with respect to my pl2303 device. So now I guess if I'd call tcdrain() on a ftdi-USB-serial port it would return corectly when the Tx buffer is empty.
What I don't understand is, why doesn't tcdrain() return an error on my device and how is it implemented? Because, like I said, if I write more than 256 bytes, it returns correctly - does it just approximate the transmission time like I do to verify it?
Actually, I may have misspoken about the use of ioctl, since there are other functions in that driver, which appear to serve at least some of the purpose you're seeking. At least it provoked you to find the proper place to look, so kudos to you on taking that initiative. You've discovered the mechanism of various layers within the kernel's driver structure to extend interfaces between the layers. The struct usb_serial_driver instantiates entries to all of the services that the pl2303 driver implements.
There are at least two layers of kernel code where data can get buffered. I'm not sure what code makes up the next higher layer, but it seems possible that it uses it's own metrics of buffer status, in the absence of information from lower in the chain. I doubt very much that it tries to estimate anything based on bit rates.
So, here are the conclusions I got:
Having a USB-serial converter, the port is double buffered: the system buffers the port-file (treats it like a tty) and the USBuC buffers the hardware port, in my case, for pl2303, it is a 256 bytes buffer (both rx and tx). So when I write to the file, the system checks if the tty-serial file is ready (available space in USBuC-buffer) and if so, it sends the data to the USBuC - 256 bytes can be therefor sent in one operation. Because the pl2303 device has no mechanism (as far as I could understand) for signaling a transmission complete, the system will consider a transmission complete when there is available space in the USBuC buffer. So if I write 259 bytes to /dev/ttyUSBx, as soon as 3 bytes are transmitted, the system will write the remaining 3 bytes in the USBuC-buffer, and so, consider the transmission completed.
Now, the problem with the "weird return" from tcdrain(), comes from the fact that also the function tcflush() has no effect on the USBuC-buffer, it only flushes the data that is still waiting to be sent to the USBuC.
And also reading the number of bytes in the transmit buffer with ioctl(.., TIOCOUTQ, ..) will return the number of bytes waiting to be sent to the USBuC - so, everything related to the tty-file, and not the physical, serial port.
So, all in all, there are differences between different USB-serial converters, and they count!!!
Ah ya! And I still can't detect a transmission complete on my pl2303!
Now, the system, the usb-serial driver, can clearly read how many bytes are free in the USBuC buffer, and so decide when to send the remaining bytes to be written.
My question is, how can I have access to that info? Why isn't then another implementation for detecting transmission complete? Are there other low-level functions for this?
In the manpage for write(2) it states the following:
Code:
write() writes up to count bytes from the buffer pointed buf to the file referred to by the file descriptor fd.
The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource
limit is encountered (see setrlimit(2)), or the call was interrupted by a signal handler after having written less than count bytes. (See also pipe(7).)
My understanding is that the limit size for most typical serial USB drivers is 4096, but I've usually concentrated on the receiver to ensure that I had sufficient size allowances to be able to read data from a port. The only times I've run into transmit problems has been if there was a bug in my code or the physical resource had some form of problem.
When I perform calls to write(2) I check the return value; if it is negative, I check errno(3) and if it is any of EAGAIN, EINTR, or ENOSPC I retry the entire write operation. If the return value is not equal to my original argument for size_t count, then I retry the write operation, minus the bytes already written.
I don't use tcflush() or tcdrain() mainly because I've never had the occasion to need to look deeper at read(3) and write(3).
I understand some possible needs to guarantee that data has been sent. Can you share why this is particularly important for your case?
Yes rtmistler, in my case, it is important to know when a transmission has completed, because on the same serial port 2 devices may share the conexion. It is a board with a Master-uC, who is connected to the UART, and an external UART, connected through laches enabled by the Master-uC to the same port. Through signaling with the DTR and DCD lines, the Master-uC and the PC can synchronize themselves. So, for example, when an external event takes place and the Master-uC wants to send a message to the PC, it sets the DCD line and waits until the PC sets the DTR line, to signal that the Master-uC may take control of the port. In the same way when the PC sends a message to the Master-uC, it must wait for the transmission to be completed and then release the DTR line, so the Master-uC will enable the external UART. Also the 2 devices may use different settings, so before I can change settings I must know that all data has been sent. And of course, I cannot just wait long enough for the data to be out.
I have no problem with the buffer size.
Thanks for the reply.
In that case I think you'll have to seek out source for the serial driver, or a serial driver which you can use and modify that to accommodate your requirements. I doubt the native Linux driver can provide that level of services to you; it may know within itself the point you're looking for, but I do not think it exposes this knowledge outside of the driver; therefore you'd have to modify the existing or replace it with one you either have written, or can modify to accomplish this goal.
And sorry, to back up a bit, I did check the forum choice here because I was wondering if it was embedded, or a particular board; this is the Non-Nix forum. Therefore, what OS, if not Linux? Are you programming within a general purpose OS, an RTOS, or directly to an embedded microprocessor? I'm guessing that you are programming within a general purpose OS such as Linux, Android, Windows, MAC, or Unix, because the RTOS case or direct to an embedded micro there would be direct access to registers on the chip for the UART and some definitions to assist you there in knowing how to derive this data, if not just see it from those registers. I've not necessarily accessed registers directly using Linux or Windows; however that's close to what the drivers do; the issue would be in the variations of hardware capabilities or in reverse the uniformity of hardware interfaces for specific things. For instance, all certain chips within a given embedded chip manufacturer's line may have similar registers for all their UARTS, and similar offsets/access methods for those registers so that you could write a driver to check the TX register empty flag or bit. For an x86 platform you'd need to know where the UART technology was coming from, that may be the bridge chip for the system, or it may be an added component such as a USB to serial converter, and therefore the knowledge in that case may lie within the USB to serial driver code.
Sorry, a lot of gabbing and not an answer there. I'm just thinking that if you are using Linux, Unix, Windows, etc; you can't accomplish this without modifying the driver code or writing your own custom driver.
Ufff..., well I am using Linux OpenSuse on a normal x86 machine, and, as I mentioned up in the thread, I use an USB-Serial converter based on the pl2303 chip. The programming is in C and only for my machine, so portability is not an issue for me. Just normal PC, no development boards, nothing embedded.
About modifying a generic driver, or better the one for pl2303, I don't trust myself, I'm not very experienced in C.
In the end, I'll just have to get a compromise, alternative, dirty solutions...
The other option is to always transmit more than 256 bytes so as to have tcdrain() work as reliably as you feel it does for that case. An inefficient way to be efficient. I worry that you'd find another case that didn't work as expected.
Yeah, not familiar with that particular chip, more FT232 USB to serial and I have modified that driver. But I rebuild my kernel and include the module within it. I have also loaded drivers as modules and therefore rebuilt the .so and updated that so one does not need to rebuild the kernel. I prefer to have drivers like that in the kernel and care less about the kernel size and more about efficiency of the driver; so I only experiment with them as modules if that's my best option and then rebuild them into the kernel when I'm done. If you're not inclined to go that far, fully understood. The rewards are potentially getting what you specifically needed, the drawbacks are a potentially lengthy list.
Sorry, perhaps another viewer may have additional information about system calls which I'm lacking on.
The other option is to always transmit more than 256 bytes so as to have tcdrain() work as reliably
That is also out of question!
Well, I didn't explained myself clear in the former posts. The reason why at the begining the function tcdrain() was returning the right time was because tcflush() only flushes the bytes in the tty-buffer, but has no effect whatsoever on the transmit buffer inside the USB-chip. So when I tested with 256 bytes, the tx buffer on chip was already full (256 bytes) from a former write, and 'cause of tcdrain() it waited for their transmission and then it wrote the new 256 bytes. It was quite a mess, as I tested with 256, 512 and 4096 bytes - until I did some new research.
May I ask why you modified your driver? Was it just experimenting or some other reason? Is it tricky?
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.