Canonicalization of serial port when receiving snapshot packets from many hosts

189 views Asked by At

I am planning to use a Pi4 to receive data coming from the UART of a wireless radio device ("Coordinator") programmed, via a Freescale MC9S08QE32, to serialize updates it receives from clients. I have 30 or so client nodes (same devices) sending updates every 5 seconds to this Coordinator. Once the Coordinator receives a client node update envelope, it writes to the RPi4 a string with its own unique format. That is currently just printf for debug.

if ((*((unsigned char*) &message[0])=='A') && (*((unsigned char*) &message[0]+1)=='Z') {    
        printf("AZ01\t%s\t%d\n",        // construct snapshot, appending type#, 2 points, terminator. 
                snap[t].point_a, 
                snap[t].point_b;
}

I am developing the RPi4 application that parses these strings, and want to simplify it as much as possible. I currently intend to terminate each write/update from the Coordinator with \n. It seems my lack of a full understanding of canonical/non-canonical mode seems to be a factor in my confusion. Does this termination character alone justify using Canonical mode? I don't mind using non-canonical, which is how the Pi is currently configured to run, but if I do pull anything beyond the termination character, I don't want to just discard it as doing so would almost certainly corrupt the next message.

That being said, I do control the UART writes from Coordinator to Pi. While I feel I understand VTIME and VMIN, what is unclear to me is if it would be possible for the Pi processor's internal buffer to somehow push this string out partially to a read() request before the \n is reached from being written to by the Coordinator. This, despite the strings being constructed from the source in their entirety before the Coordinator writes. Is the buffer not "locked out," per se, so if VMIN=5 and VTIME=0, there's still a risk of getting only 5-6 characters? I would set VMIN to a static size, but my messages vary with message type#.

struct termios tty;
memset(&tty, 0, sizeof(tty));
bytesRecv = read(serial_port, &rbuf, sizeof(rbuf)-1);   // Read bytes
tty.c_cc[VTIME] = 0;   // Wait 0 deciseconds
tty.c_cc[VMIN] = 5;    // Read 5 chars minimum from buffer

Are there any tips, best practices or architectural recommendations I should be mindful of here?

Edit 1: Full serial configuration

int serial_port = open("/dev/ttyAMA1", O_RDWR);
if (serial_port < 0) {
    printf("\nError %i from Open serial attempt: %s\n", errno, strerror(errno));
    errchk=1;
    }

// Configure the terminal's serial settings for correct synchronization of data from host node communication
struct termios tty;
memset(&tty, 0, sizeof(tty));

// Validate by reading in ttyAMA1's (serial_port's) existing settings
if (tcgetattr(serial_port,&tty) != 0) {
    printf("\nError %i from tcgetattr: %s\n", errno, strerror(errno));
    errchk=1;
    }

// Set the actual control parameter fields (c_cflags)

// Control modes
tty.c_cflag &= ~PARENB;         // Clear parity bit, disables priority
tty.c_cflag &= ~CSTOPB;         // Clear stop field, only one stop bit used in communication
tty.c_cflag &= ~CRTSCTS;        // Disable RTS/CTS hardware flow control
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;             // 8 bits per byte
tty.c_cflag |= (CREAD | CLOCAL);  // Turn on READ and ignore control lines like carrier detect for modems (CLOCAL=1)

// Local modes
tty.c_lflag &= ~ICANON;         // Set as Non-canonical unix mode (do not process input only when new line char is read)
tty.c_lflag &= ~ECHO;           // Disable echos
tty.c_lflag &= ~ECHOE;          // Disable erasure echo
tty.c_lflag &= ~ECHONL;         // Disable new-line echo
tty.c_lflag &= ~ISIG;           // Disable interpretation of INTR, QUIT, SUSP
tty.c_lflag &= ~IEXTEN;

// Input modes
tty.c_iflag &= ~(IXON | IXOFF | IXANY);                             // Turn off software flow control
tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);    // Disable special handling of bytes -- only raw data!
tty.c_iflag = 0; // new

// Output modes
tty.c_oflag &= ~OPOST;          // Prevent special interpretation of output bytes (e.g. newline char
tty.c_oflag &= ~ONLCR;          // Prevent conversion of newline to carriage return/line feed

// Timing settings for read() blocking (VMIN/VTIME)
tty.c_cc[VTIME] = 0;            // Wait 0 deciseconds = 0.0 second
tty.c_cc[VMIN] = 5;             // Read 5 chars minimum loaded into the buffer before returning from read()

// Baud rate settings
cfsetispeed(&tty, B115200);     // Set to 115.2 kBaud IN
cfsetospeed(&tty, B115200);     // Set to 115.2 kBaud OUT

tcflush(serial_port,TCIFLUSH);

// Save these TTY settings (and error check)
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
    printf("\nError %i from tcsetattr: %s\n", errno, strerror(errno));
    errchk=1;
    }

// Set the actual control parameter fields (c_cflags)

// Control modes
tty.c_cflag &= ~PARENB;         // Clear parity bit, disables priority
tty.c_cflag &= ~CSTOPB;         // Clear stop field, only one stop bit used in 
tty.c_cflag &= ~CRTSCTS;        // Disable RTS/CTS hardware flow control
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;             // 8 bits per byte
tty.c_cflag |= (CREAD | CLOCAL);  // Turn on READ and ignore control lines like carrier detect for modems (CLOCAL=1)

// Local modes
tty.c_lflag &= ~ICANON;         // Set as Non-canonical unix mode
tty.c_lflag &= ~ECHO;           // Disable echos
tty.c_lflag &= ~ECHOE;          // Disable erasure echo
tty.c_lflag &= ~ECHONL;         // Disable new-line echo
tty.c_lflag &= ~ISIG;           // Disable interpretation of INTR, QUIT, SUSP
tty.c_lflag &= ~IEXTEN;

// Input modes
tty.c_iflag &= ~(IXON | IXOFF | IXANY);   // Turn off software flow control
tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);    // Disable special handling of bytes -- only raw data!
tty.c_iflag = 0; // new

// Output modes
tty.c_oflag &= ~OPOST;  // Prevent special interpretation of output bytes (e.g. newline char
tty.c_oflag &= ~ONLCR;  // Prevent conversion of newline to car return/line feed

// Timing settings for read() blocking (VMIN/VTIME)
tty.c_cc[VTIME] = 0;            // Wait 0 deciseconds = 0.0 second
tty.c_cc[VMIN] = 5;             // Read 5 chars minimum loaded into the buffer before returning from read()

// Baud rate settings
cfsetispeed(&tty, B115200);     // Set to 115.2 kBaud IN
cfsetospeed(&tty, B115200);     // Set to 115.2 kBaud OUT

tcflush(serial_port,TCIFLUSH);

// Save these TTY settings (and error check)
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
    printf("\nError %i from tcsetattr: %s\n", errno, strerror(errno));
    errchk=1;
    }
1

There are 1 answers

6
sawdust On

Does this termination character alone justify using Canonical mode?

As long as only lines of text are transferred and use of control characters are agreed upon, then yes.
Canonical mode can simplify your program as well as make it more efficient (e.g. blocked reads that fetch a complete line). See Canonical Mode Linux Serial Port.
Novice coders tend to misuse raw mode, and make too many syscalls. See parsing complete messages from serial port.

That said, you seem to misunderstand your situation:

I am planning to use a Pi4 to receive data coming from the UART of a wireless radio device

The typical "wireless radio device" (e.g. XBee module) does not necessarily provide just the payload (i.e. the text from your "client nodes"). More often each wireless datagram received is forwarded to the host as a message with additional information (e.g. sender ID).
This message packet has to be treated as binary data, so noncanonical mode must be used on the serial terminal that connects to this "wireless radio device".

Are there any tips, best practices or architectural recommendations I should be mindful of here?

Consult the programmer's manual of the radio device that you are using for details of the host interface protocol.
BTW the sample code you posted is nonsensical. For proper termios initialization see Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems