LinuxQuestions.org
Help answer threads with 0 replies.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 04-22-2014, 08:31 AM   #1
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Rep: Reputation: Disabled
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);

Last edited by volbus; 04-22-2014 at 01:56 PM.
 
Old 04-23-2014, 12:29 PM   #2
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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...
 
Old 04-24-2014, 07:19 PM   #3
theNbomr
LQ 5k Club
 
Registered: Aug 2005
Distribution: OpenSuse, Fedora, Redhat, Debian
Posts: 5,399
Blog Entries: 2

Rep: Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908
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.
 
Old 04-25-2014, 04:44 AM   #4
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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?
 
Old 04-25-2014, 07:11 AM   #5
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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?
 
Old 04-25-2014, 10:27 AM   #6
theNbomr
LQ 5k Club
 
Registered: Aug 2005
Distribution: OpenSuse, Fedora, Redhat, Debian
Posts: 5,399
Blog Entries: 2

Rep: Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908Reputation: 908
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.
 
Old 04-27-2014, 09:54 AM   #7
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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!
 
Old 04-27-2014, 10:11 AM   #8
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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?
 
Old 04-29-2014, 07:39 AM   #9
rtmistler
Moderator
 
Registered: Mar 2011
Location: USA
Distribution: MINT Debian, Angstrom, SUSE, Ubuntu, Debian
Posts: 9,882
Blog Entries: 13

Rep: Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930
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?
 
Old 04-29-2014, 01:48 PM   #10
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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.
 
Old 04-29-2014, 02:07 PM   #11
rtmistler
Moderator
 
Registered: Mar 2011
Location: USA
Distribution: MINT Debian, Angstrom, SUSE, Ubuntu, Debian
Posts: 9,882
Blog Entries: 13

Rep: Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930
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.
 
Old 04-29-2014, 02:29 PM   #12
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
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...
 
Old 04-29-2014, 02:41 PM   #13
rtmistler
Moderator
 
Registered: Mar 2011
Location: USA
Distribution: MINT Debian, Angstrom, SUSE, Ubuntu, Debian
Posts: 9,882
Blog Entries: 13

Rep: Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930Reputation: 4930
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.
 
Old 04-29-2014, 02:56 PM   #14
volbus
LQ Newbie
 
Registered: Apr 2014
Location: BV / RO
Distribution: OpenSUSE 13.1
Posts: 27

Original Poster
Rep: Reputation: Disabled
Quote:
Originally Posted by rtmistler View Post
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?

Last edited by volbus; 04-29-2014 at 03:02 PM.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Problem to write to serial port os2 Programming 8 08-23-2016 02:48 AM
How to write to a serial port ? Aquarius_Girl Linux - General 2 10-20-2009 02:18 AM
TO write to a serial port payu21 Linux - Newbie 1 05-24-2009 09:23 AM
How to read serial port (/dev/ttyS0) without end of transmission seraph-seph Programming 1 10-22-2006 01:03 AM
Serial port Read Write SeanatIL Programming 2 07-14-2004 03:42 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 08:45 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration