I'm developing a receiver for a small hardware project. I'm working on a small board that uses UART to transfer data.
The receiver code is shown in full below, I'll explain the problematic bits separately shortly.
#define TTY "/dev/ttys002"
#include <stdio.h>
#include <string.h>
#include <unistd.h> //Unix standard functions
#include <fcntl.h> //File controls
#include <errno.h> //Error numbers
#include <assert.h>
#include <termios.h> //Posix terminal
int open_port(const char * tty) {
int fd;
fd = open(tty, (O_RDWR | O_NOCTTY | O_NDELAY));
assert("__failed to open port__" && fd != -1);
//Block until read
fcntl(fd, F_SETFL, 0);
return fd;
}
void configure_port(int fd) {
struct termios options;
//Get current options
tcgetattr(fd, &options);
//9600 Baud
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
//Receive & local mode
options.c_cflag |= (CLOCAL | CREAD);
//Raw output
options.c_oflag &= ~OPOST;
//No hardware flow control
options.c_cflag &= ~CRTSCTS;
//No parity, 1 stop bit
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
//8 data bits
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
//Write options back, set them immediately
tcsetattr(fd, TCSANOW, &options);
}
int main(int argc, const char * argv[]) {
int fd = open_port(TTY);
const size_t count = 8;
char buf[count + 1];
ssize_t n;
configure_port(fd);
while (1) {
n = read(fd, buf, count);
buf[count] = '\0';
if (n > 0) {
printf("%s\n", buf);
}
}
return 0;
}
Since I don't have my hardware at hand currently, I decided to test my receiver over a regular tty (#define TTY "/dev/ttys002"). To test it, I simply compiled and ran the above code and then opened a separate terminal and:
echo "text" >> /dev/ttys002
All this works fine and well and I get all the data I'm echoing into the tty.
However, the problem arises when I input a long message into the tty:
echo "this is a longer test message" >> /dev/ttys002
I receive the whole message as a single string in my program output. Why is this so? I would have expected the text to be split up into blocks of 8 chars (const size_t count = 8;).
If it's of importance, I'm using this guide as my go-to for the configuration.
Edit: Please see the comments for further discussion on the issue.
IMHO, your message is split into blocks of eight chars : n = read(fd, buf, count); cannot give more than count bytes at a time.
But as you do not configure the tty line in RAW mode, is is still in line buffered mode. So the underlying driver blocks the first read until it has a full line terminated with a \n (or exceeding the buffer capacity).
Then the read returns with the first 8 bytes, and next reads immediately return with 8 bytes too since data is available in driver buffer.
You should look at non canonical mode in man termios, if you want to use the raw input mode :
options.c_cflags &= ~ICANON;
/* optionally if defaults are not appropriates */
options.c_cc[VMIN] = vmin; /* default is 1 */
options.c_cc[VTIME] = vtime; /* default is 0 */
But anyway, you read chararacters and never add the terminating null, so buf has no reason to be null terminated. You should only print the number of characters actually read:
const size_t count = 8;
char buf[count];
ssize_t n;
configure_port(fd);
while (1) {
n = read(fd, buf, count);
if (n > 0) {
printf("%.*s\n", n, buf); /* only print the n characters */
}
}
It seems that your problem has the same name that this site :)
You're allocating 8 bytes of data on the stack for your buf variable. Then, in read function, you're writing "this is a longer test message" string, which length is much more than 8 bytes. When you're using printf function with "%s", printf will stop printing when it will reach a null character(\0) because it expects your string to be null-terminated.
In your case, you should make a check whether your buf variable is null-terminated. If it's not, you should make the last byte of the bufto be a null character
What you are looking for is a pseudo tty (PTY).
A simple way to create a PTY is to use socat(1) like so:
socat pty,link=/tmp/testpty stdio
This will create a PTY and attach the current terminal's stdio to its master side. Whatever data you put in here will be sent to the slave side of the PTY.
Now connect to the slave side from your program and it should work as you expect:
#define TTY "/tmp/testpty"