Reputation: 35
I have written the C program that should read UART's RxD port and display the results as soon as there is any information. To achieve this I'm using signal_handler SIGIO signal
Read program c code
#include <iostream>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <string.h>
#define BAUDRATE B19200
#define PORT "/dev/ttyO4"
#define _POSIX_SOURCE 1
int fd;
void signal_handler_IO(int status);
void set_port_settings();
char buff[255];
sig_atomic_t flag=0;
using namespace std;
int main ()
{
set_port_settings();
for(;;){
if(flag !=0)
{
//printf( "sync : 0x%X\n", buff[1]);
//printf ( "PID: 0x%X\n", buff[2]);
printf ( "D0: 0x%X\n", buff[4]);
printf ( "D1: 0x%X\n", buff[5]);
printf ( "D2: 0x%X\n", buff[6]);
printf ( "D3: 0x%X\n", buff[7]);
printf ( "D4: 0x%X\n", buff[8]);
printf ( "D5: 0x%X\n", buff[9]);
printf ( "D6: 0x%X\n", buff[10]);
printf ( "D7: 0x%X\n", buff[11]);
printf ( "CHK: 0x%X\n", buff[12]);
flag = 0;
}
}
}
void signal_handler_IO(int status)
{
if(flag !=1)
{
read(fd, &buff, sizeof(buff));
flag = 1;
}
}
void set_port_settings()
{
struct termios oldtio, newtio;
struct sigaction saio;
fd = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd<0) {perror(PORT); exit(-1);}
saio.sa_handler=signal_handler_IO;
sigemptyset(&saio.sa_mask);
saio.sa_flags=SA_RESTART;
sigaction(SIGIO, &saio,NULL);
fcntl (fd, F_SETOWN, getgid());
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd, &oldtio); perror("tsgetattr");
newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ; perror("c_cflag");
newtio.c_iflag = IGNPAR | IXON ; perror("c_iflag");
newtio.c_oflag = 0; perror("c_oflag");
newtio.c_lflag = ICANON | ISIG ; perror("c_lflag");
newtio.c_cc[VMIN]=8;perror("c_cc[VMIN]");
newtio.c_cc[VTIME]=1; perror("c_cc[VTIME]");
newtio.c_cc[VSTART]=0x55;
tcflush(fd, TCIFLUSH); perror("TCFLUSH");
tcsetattr(fd, TCSANOW, &newtio); perror("tcsetattr");
}
Problem that I have, is that when the program reads data and starts printing out the results, the information printed out is somehow correct just printed (or read in) in a wrong place.
I'm writing to the port using another C program. I've tried to do it from the same C program but was unsuccessful to write and read from the same C program. So I keep 2 shells open: on one shell I'm running write program, on another shell I'm running read program to display what it was able to read in.
Write program C code
#include <iostream> #include <termios.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/signal.h> #include <stdlib.h> #include <sys/ioctl.h> #define BAUDRATE9600 B19200 #define PORT "/dev/ttyO4" #define _POSIX_SOURCE 1 using namespace std; int main() { int fd; char buffer[255]; struct termios oldtio, newtio; struct sigaction saio; fd = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd<0) {perror(PORT); exit(-1);} tcgetattr(fd, &oldtio); newtio.c_cflag = BAUDRATE9600 | CS8 | CLOCAL | CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; tcflush(fd, TCIFLUSH); tcsetattr(fd, TCSANOW, &newtio); char SYNC [] = {0x55}; char PID [] = {0x97}; char data0 [] = {0x25}; char data1 [] = {0xFF}; char data2 [] = {0x00}; char data3 [] = {0x64}; char data4 [] = {0x01}; char data5 [] = {0xFF}; char data6 [] = {0xFF}; char data7 [] = {0xFC}; char checksum [] ={0xE0};
for (;;) {
ioctl(fd, TIOCSBRK); usleep(676); // 13 bits, 1 bit = 52us ioctl(fd,TIOCCBRK); usleep(260); // 5 bits write(fd, SYNC, sizeof(SYNC)); write(fd, PID, sizeof(PID)); write(fd, data0, sizeof(data0)); write(fd, data1, sizeof(data1)); write(fd, data2, sizeof(data2)); write(fd, data3, sizeof(data3)); write(fd, data4, sizeof(data4)); write(fd, data5, sizeof(data5)); write(fd, data6, sizeof(data6)); write(fd, data7, sizeof(data7)); write(fd, checksum, sizeof(checksum)); usleep(10000); close (fd); }
When I run both programs to check if the READ programs is working as it should, I can see that the data is read in, but is not exactly as it it's written to the port.
example of the data read in
d0: 0x7C
d1: 0x66
d2: 0x1
d3: 0xE0
d4: 0x4C
d5: 0x7C
d6: 0x8
d7: 0x60
CHK: 0x60
d0: 0x1
d1: 0xE0
d2: 0x4C
d3: 0x7C
d4: 0x8
d5: 0x60
d6: 0xFC
d7: 0x60
CHK: 060
I hope that somebody will be able to point where I have made a mistake and what should I do to be able to read from the UART port without the problem.
Upvotes: 0
Views: 35865
Reputation: 17077
Problem that I have, is that when the program reads data and starts printing out the results, the information printed out is somehow correct just printed (or read in) in a wrong place.
Unfortunately, because your read program does output something, you mistakenly think there is only one problem relating to data data alignment. Data or message alignment is just one of many problems in your programs.
The read and write programs improperly initialize the serial port to an incomplete (and therefore unknown) state. As commented by @Swanand the write program prematurely closes its file descriptor.
The read program is needlessly using async I/O events to perform read() syscalls and not checking the return code. The read program then unconditionally prints out the buffer regardless of whether there is actual read data.
Using a break condition on the serial link is an unorthodox method of message separation. Since the message seems to be framed with a starting "sync" byte and a terminating "checksum" byte, this message frame should be validated by the read program to ensure that message alignment is intact.
Some of the specific errors in your code:
Lack of proper and consistent formatting.
void set_port_settings()
{
struct termios oldtio, newtio;
newtio is an automatic variable, and therefore is not initialized.
The program selectively assigns values to a few structure elements, and leaves others undefined.
These undefined elements in the termios structure passed on through the tcsetattr() syscall can lead to unpredictable and/or unreliable operation of the serial port.
You should study Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems.
newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD ; perror("c_cflag");
newtio.c_iflag = IGNPAR | IXON ; perror("c_iflag");
This is an improper method of setting termios attributes.
Refer to the mentioned guides for proper technique.
You claimed you corrected these mistakes in this question, yet here they are still.
The unconditional perror() after each assignment is wrong since this isn't a syscall.
Enabling IXON seems irrelevant since this is the read program that isn't doing any output. (IXON enables soft flow-control for output.)
newtio.c_lflag = ICANON | ISIG ; perror("c_lflag");
Enabling ICANON seems incorrect since the data from the write program is not line-formatted text but binary data. This program should be setting up raw mode instead of canonical mode.
Enabling ISIG seems incorrect since the triggering character values in VINTR, VQUIT, VSUSP, or VDSUSP are all undefined in this uninitialized termios structure.
Since the write program is creating break conditions, a signal from a break, could be generated but has unwanted side effects, i.e. flushing the queues.
The unconditional perror() after each assignment is wrong since this isn't a syscall.
tcsetattr(fd, TCSANOW, &newtio); perror("tcsetattr");
The return code from this (and all other) system call needs to be checked.
The unconditional perror() call is wrong.
I have written the C program that should read UART's RxD port and display the results as soon as there is any information. To achieve this I'm using signal_handler SIGIO signal
Your program has no direct access to the UART's RxD port.
It's the serial port driver that reads the actual data from the UART RxD register.
Since you've setup the serial port for canonical input, the data is stored in the line discipline buffer, and then copied to a system buffer.
The read() syscall in your program retrieves data from the system buffer.
The SIGIO is not speeding up the read by your program.
void signal_handler_IO(int status)
{
if(flag !=1)
{
read(fd, &buff, sizeof(buff));
flag = 1;
}
}
This signal handler is not necessary if your program performed an ordinary blocking read(). Your premise for using nonblocking I/O and SIGIO seem to be incorrect. The program is not utilizing asynchronous I/O effectively, so you might as well simplify the program.
The return code from the read() needs to be checked. Besides checking for any error condition, the return code will indicate the number of bytes that have been read.
Your read program incorrectly assumes that a complete message of at least 11 bytes is received every time, and could cause display of stale values in the main loop.
Since the read program enables neither IGNBRK or BRKINT, a break condition is received by your program as a null byte, 0x00, along with the actual message data.
The read program needs to search for and maintain message alignment using the "sync" and "checksum" bytes. Your program currently has no method at all to test for or to achieve message alignment.
A basic algorithm for performing this are in this answer
Upvotes: 3
Reputation: 25663
If you read/write non ascii data to files you should open with 'O_BINARY'.
As next hint: Use your console to read or write data to see which program is faulty. Simply use 'echo' for that.
To see what your program internally reads or writes use 'strace -xfo dump your_prog' and look into the file 'dump' to see which characters was send/read from your uart.
The following instructions seems to be really senseless for me! There is no chance to get a sleep exactly to any kind of bit rates until you have a hard real time kernel and hardware. On x86 you will have deviations of more then 100ms! for a usleep if an other process is running. Every disk io will kill your timing for example.
ioctl(fd, TIOCSBRK);
usleep(676); // 13 bits, 1 bit = 52us
ioctl(fd,TIOCCBRK);
usleep(260); // 5 bits
For sending a break use
tcsendbreak()
Upvotes: 1