Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
287 views
in Technique[技术] by (71.8m points)

c - Reading serial data in linux byte-for-byte at 56K reliably

I'm trying to create a function with the most minimal delay possible that checks to see if the serial port has data and if it does, it reads every single byte and prints each byte in hex format until no more bytes are available. If there is no data, the function must return right away.

This is my code:

int fd=open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);  

// Trying to set correct options here
struct termios o;
tcgetattr(fd,&o);
cfsetispeed(&o,57600);
cfsetospeed(&o,57600);
/* 8 bits, no parity, 1 stop bit */
o.c_cflag &= ~PARENB;o.c_cflag &= ~CSTOPB;o.c_cflag &= ~CSIZE;o.c_cflag |= CS8;
/* no hardware flow control */
o.c_cflag &= ~CRTSCTS;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* disable input/output flow control, disable restart chars */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
o.c_cc[VMIN] = 0; //to prevent delay in read();
o.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &o);
tcflush(fd, TCIFLUSH);

char sp[1]; //hold 1 byte
int bytes=read(fd,sp,1); //Good news: this function doesn't lock
if (bytes > 0){
    //this is never reached even if a byte is 
    //present on the serial line. why?
    printf("Read: ");
    while(bytes > 0){
        printf("%X ",sp[0]);
        bytes=read(fd,sp,1);
    }
}
fclose(fd);

Is there a way to fix this?

This whole function (minus the serial port options section) will eventually run in an endless loop as I am constantly scanning my port for data then printing it. Then later I'll add some more functionality where I'll write data to the port at predefined times regardless of what data is received.

P.S. not sure if this is of help, but the target device I'm doing I/O with is custom hardware around an 8051 microcontroller and its serial fifo buffers are only 1 byte whereas the PC's buffers I think are 14 or 16 bytes.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you know beforehand the times when you need to write to the device, you can use select() or poll() to wait for input until the next time you wish/intend to write.

A much more simple and robust approach — because your reads and writes are not in a defined sequence, and your hardware is full duplex — is to use separate threads for reading and writing. Basically, you use blocking reads and writes (c_cc[VMIN] = 1, c_cc[VTIME] = 0 for reading, O_NONBLOCK not among file open flags). You should allow larger buffers, though; reads will return all that have been received thus far, but with those settings wake up the reader whenever there is at least one char received. For writing, I do recommend you do a tcdrain(fd); after each write that completes a command/message to the device, to ensure it is sent on the wire by the kernel. (Do remember that writes to the serial port can be short; you need a write loop.)

In all cases, the kernel on the host side will cache data sent and received. Depending on the hardware and driver, even a blocking write() may return earlier than when all of the data is actually on the wire. The hardware and the kernel driver, not the host software, is responsible for correct timing of the serial data.

Using one-byte buffer on the host side will not affect the microcontroller at all, you'll just do more syscalls than is necessary, wasting CPU resources and possibly slow down your program a bit. At 57600 baud, with 8 data bits, no parity, 1 stop bit, and the implicit start bit, the actual data rate is 46080 bits/second (±5% typically allowed), or 5760 bytes per second. The microcontroller will always have about 1 s /5760 ? 0.0001736 seconds, or over 173 microseconds, to process each incoming byte. (I'd design my firmware to not allow higher priority interrupts etc. to delay processing for more than 100 microseconds or so even in the worst case, to ensure no chars are dropped. If you receive the chars in an interrupt handler, I'd use a small circular buffer, and an indicator character, or , so that if such character is received, a flag is raised for the main program to notice that a new complete command has been received. The circular buffer should be long enough to hold two full commands, or more if some commands may take longer to parse/process/handle.)

If the host operating system wakes up the process 1 ms after of receiving the first character, additional four or five characters have arrived in the mean time. Because this latency can be much higher on some systems, I'd use a much larger buffer, say up to 256 chars, to avoid doing superfluous syscalls when the kernel is for some reason delayed in waking up the reader thread. Yes, it will often read just 1 char, and that is fine; but when the system is overloaded, you don't want to add to that load by doing hundreds of superfluous syscalls when one would suffice.

Remember, the termios interface, with VMIN=1, VTIME=0, will cause the blocking read to be woken up as soon as possible, whenever even a single character is received. It is just that you cannot ensure your program is constantly running, unless you waste about 100% of CPU power by spinning in place (and if you do, nobody will want to run your program). Depending on the system, there may be a delay in waking up the blocking read, during which time more data may be received, so using a larger read() buffer is definitely sensible.

Similarly, you can safely use as large writes as you want (up to a limit of about 2 GiB), although most serial drivers can return short counts, so you'll need a loop there anyway. The tcdrain(fd) on the serial port descriptor will block until all written data has been actually transmitted, so you'll probably want to use it. (If you do not, you can just write more data; the kernel driver will take care of the details, and not reorder/mess data up.)

Using two threads, one for reading, one for writing, may sound daunting/odd, but it is actually the simplest way of achieving robust communications. You can even use pthread_setcancelstate(), pthread_setcanceltype() and optionally pthread_testcancel(), to write the thread functions so that you can simply cancel the threads (using pthread_cancel()) to stop them, even if they have critical sections (like adding a received message to some queue protected by a mutex).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...