I'm trying to write a little program in C that will read from the serial port using the select command so that it blocks and waits for input. It's working, except it keeps breaking up lines and I have no idea why. The device is programmed not to break up the lines and works fine with actual terminal programs. I've never done serial communication in C before, and I'm on a Mac, so it's all new to me. I really have no idea where to even look for what's going wrong.
I have some code that finds and lists serial ports. I'll leave that out for simplicity, so if there's a variable that doesn't make sense, that might be why. Here is the code that opens the port, sets attributes, and tries to read from it, complete with copied comments from Apple's site (sorry):
/* this is based on a combination of http://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c
* and https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html
*/
static int OpenSerialPort(const char *deviceFilePath, int speed)
{
int fileDescriptor = -1;
struct termios options;
memset(&options, 0, sizeof(options)); // init it
// Open the serial port read/write, with no controlling terminal,
// and don't wait for a connection.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1)
{
printf("Error opening serial port %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Note that open() follows POSIX semantics: multiple open() calls to
// the same file will succeed unless the TIOCEXCL ioctl is issued.
// This will prevent additional opens except by root-owned processes.
// See options(4) ("man 4 options") and ioctl(2) ("man 2 ioctl") for details.
if (ioctl(fileDescriptor, TIOCEXCL) == kMyErrReturn)
{
printf("Error setting TIOCEXCL on %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.
cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;
// The baud rate, word length, and handshake options can be set as follows:
cfsetspeed(&options, speed); // Set 19200 baud
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
options.c_iflag &= ~IGNBRK; // disable break processing
options.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
options.c_oflag = 0; // no remapping, no delays
options.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
options.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
options.c_cflag &= ~(PARENB | PARODD); // shut off parity
options.c_cflag |= false;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CRTSCTS;
// Cause the new options to take effect immediately.
if (tcsetattr(fileDescriptor, TCSANOW, &options) == kMyErrReturn)
{
printf("Error setting options attributes %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// turn on blocking
if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn)
{
printf("Error clearing O_NONBLOCK %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
goto error;
}
// Success:
return fileDescriptor;
// Failure:
error:
if (fileDescriptor != kMyErrReturn)
{
close(fileDescriptor);
}
return -1;
}
int main(void)
{
int fileDescriptor;
kern_return_t kernResult; // these are Apple-specific
io_iterator_t serialPortIterator; // Apple
char deviceFilePath[MAXPATHLEN];
fd_set fdset; // make a file descriptor set
FD_ZERO (&fdset); // init it
char buf[1000]; // some strings are big
kernResult = GetDevices(&serialPortIterator);
printf("Devices on this system:\n");
kernResult = ListDevicePaths(serialPortIterator, deviceFilePath, sizeof(deviceFilePath));
IOObjectRelease(serialPortIterator); // Release the iterator.
// Open the modem port, initialize the modem, then close it.
if (!deviceFilePath[0])
{
printf("No modem port found.\n");
return EX_UNAVAILABLE;
}
fileDescriptor = OpenSerialPort("/dev/cu.usbmodem1d1111", B230400);
FD_SET (fileDescriptor, &fdset); // add to file descriptor set
// now we're going to use select to only read from the file handle when there's data available
while (1)
{
if (select (FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) // this will block the program until something is on the line
{
printf("select error\n");
}
read(fileDescriptor, buf, 1000);
printf("%s\n", buf);
memset(buf, '\0', 1000);
}
// let's try to read from the serial port
/* for (int i = 0; i <= 10; i++)
{
char buf [100];
int n = read(fileDescriptor, buf, sizeof buf);
printf("%s\n", buf);
//usleep ((7 + 25) * 100);
}*/
close(fileDescriptor);
printf("Modem port closed.\n");
return EX_OK;
}
Expected output:
This is sample output.
Hello.
What I actually get in the above program:
Thi
s is sam
ple output.
Hel
lo.
Or something like that. It's different each time. Sometimes it works fine. It seems to be random.
So my questions are: What am I doing wrong? What code do I need to work on aside from just a blanket "all of it?" What am I not understanding? I admit I don't really understand how these libraries work, exactly. I am assuming (I know, I know) that they take care of flow control and errors and so on. But then again, the examples I've copied from didn't exactly explain that so I don't know. I just don't really know what's going on.