0

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;
    }
Big Owls
  • 37
  • 6
  • The only time canonical mode *might* be considered is if you're literally using the serial line for an interactive shell (even then, the defaults are very, say *rustic*, to sane people in the 21st century). If you're using a serial line for generic data transfer, there is no way any setting other than non-blocking raw reads (controlled by an I/O-multiplexing event loop) could be considered anything but completely insane. – EOF Sep 28 '20 at 21:06
  • Thanks for the tip. I was never sure what is or isn't widely used. It sounds like I need to read(), inspect the contents, and somehow LIFO the bytes back if they do not contain my termination character. – Big Owls Sep 28 '20 at 21:32
  • No, you don't return the characters to the buffer, you keep your own buffer in your program. – EOF Sep 28 '20 at 21:34
  • The **read()** of a **/dev/tty*** device is merely fetching bytes from a system buffer for the serial terminal. Insisting that *"non-blocking raw reads"* is the only solution for every circumstance is bogus. It is not even widespead in practice. – sawdust Oct 03 '20 at 16:29

1 Answers1

-1

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

sawdust
  • 16,103
  • 3
  • 40
  • 50
  • You are correct in this case to assume XBee (if you were speculating on what I was using.) My plan was to condense all the OTA snapshots received by client nodes at the Coordinator and provide a more concise update packet between Coordinator and Pi. I had hoped to utilize some functions to actually pull the client's index/position in node_table to bypass the need to send 64-bit addresses OTA. I am using API mode on everything, but I also have serial console enabled on the Coordinator for debugging, so I don't know yet if I'll be able to even interact with the Pi yet. May need to change that. – Big Owls Sep 29 '20 at 06:36
  • Serial routine edited, I had just left a lot of it out. Maybe it still requires tweaks, though, as I was hoping to both use the Coordinator as a serial console output (debugging) and the Pi's UART interface. That may not be the case as I'm seeing a checkbox option to enable/disable. – Big Owls Sep 29 '20 at 06:52
  • I can see the concern about using more syscalls when reading in raw mode. However, since any sane program will use I/O multiplexing, does e.g. `poll()` not return when a single byte is readable if the serial file descriptor is set with `VMIN > 1`? – EOF Sep 29 '20 at 10:56
  • *"Serial routine edited..."* -- Why the duplicated code? The `tty.c_iflag = 0;` statement is improper. VMIN=5 and VTIME=0 can be problematic. Why configure for noncanonical mode when you were asking about canonical mode? BTW termios configuration (i.e. the code you posted) is typically only half of the problem of using a serial terminal. – sawdust Oct 02 '20 at 00:42
  • @EOF -- By "too many syscalls" I was referring to (a) using **read()** to request just one byte, as well as (b) use of nonblocking mode and then polling for the data. FYI **poll()** is a syscall just like **read()**. There are no I/O operations performed by **read()**, which is simply copying data between buffers. I don't agree with your criteria for a *"sane program"*. – sawdust Oct 02 '20 at 05:16
  • @sawdust -- Since it seems like you're somewhat familiar with these wireless _n to 1_ aggregator architectures, what do you feel is the best approach for handling _n_ clients constantly sending data to the Coordinator? To keep it on topic and relevant, my concern is that the tendency of the mesh network's capability to stagger/delay client updates at random intervals at some point may not permit much of a threshold for the Pi's non-canonical VTIME. I don't want to wait too long such that the buffer risks getting appended with another client update, but would still like to avoid char-by-char. – Big Owls Oct 27 '20 at 05:52