Creating a serial port reading program for linear encoders

Code

The MDX motion platform requires 6 individual quadrature encoded linear-distance sensors for each of its muscles. The project already had a working sensor module with preprogrammed firmware.

The sensor module is made up of 3 parts the Teensy 4.0 Microcontroller, the 6 EIA/TIA 574:DE9F Connectors and the 6 quadrature encoded linear-distance sensors with DE9M connectors

Teensy 4.0 in the sensor module is a 32-bit arduino compatible ARM-based microcontroller 1. The sensor module communicates to the host over Serial Interface.

The 6 Linear-distance sensors are the Jing JC-800, but any similar sensors could be used in place.

Serial Communication

Serial Communication is the transmission of data one bit at a time in a sequential manner. This is in contrast to the act of transmitting multiple bits at a time on parallel communication channels, implemented in Parallel Communication. 2

While most rules for serial communication are the same for all classes of devices. Some classes of devices require specialised rules. These device specific rules can differ widely from device type to device type

Under Unix and Unix-based kernels they are usually implemented using ttyUSB, ttyS, or ttyACM and more. 3

ttyACM

Microcontrollers and embedded systems that require communication to a host device over USB needed a standardized and widely supported communication method.

Thus these devices started implementing the Abstract Control Model (ACM) over the USB Communication Device Class (CDC) along with the public switched telephone network (PSTN). Originally this specific ACM over USB-CDC/PSTN was only implemented by modems. But the way these new devices implement this functionality is partial. The devices fail to implement the ITU V.250 protocol required, and thus cannot respond to any command signals sent by the host; and may also get corrupted, if the host still sends any command signals. 4

This was a source of many issues for the encoder_io class

Note: Under the Linux kernel these devices are stored under /dev/ttyACM and use the cdc_acm drivers.

pySerial

pySerial is a python library for serial communication. It encapsulates the access for the serial ports using termios as the backend on Linux. 5

Originally due to the complexities faced using termios directly, research time was invested into embedding pySerial and Python code inside C++. Embedding python code into C++ would have allowed us to have extremely fast communication between the C++ program and the Python program without any volatile memory based i/o slowdowns.

While diving into the pySerial code, and discovering that pySerial uses termios as the backend for serial communication on Linux; a decision to learn from the pySerial usage of termios, and forgoing embedding pySerial into C++ was made β€” purely for the benefit of learning.

termios

termios is a C / C++ API that describes a general terminal interface that is provided to control asynchronous communication ports under a UNIX-based system. 6

General structure for communication with a serial device using termios is as follows 7

  1. open(2) the serial device using a standard UNIX system call

  2. Set the configuration parameters required by the serial device for communication using the termios struct

  3. Using UNIX system calls like read(2) and write(2) or others to communicate with the serial interface

  4. UNIX system call, close(2) when done.

Most notable here is the termios struct, used for the configuration of the parameters for the serial communication device. The termios struct, contains at least the following members :

tcflag_t	c_iflag;		/* input modes */
tcflag_t	c_oflag;		/* output modes */
tcflag_t	c_cflag;		/* control modes */
tcflag_t	c_lflag;		/* local modes */
cc_t		c_cc[NCCS];		/* special characters */

As previously mentioned the microcontrollers implementing the ACM over CDC do not implement the ITU V.250 protocol, as such they do not support all of termios struct configuration modes. They still abide by certain serial communication based configuration modes, and the supported modes vary by microcontroller serial interface implementations. Which makes it harder to figure out the configuration parameters required for accurate serial communication between the host and the microcontroller.

Necessary Device Specific Configuration

  1. open(2)

    1. O_NOCTTY flag which specifies that the opened terminal β€” in our case /dev/ttyACM0 β€” will not become the controlling terminal of the process

    2. O_RWDR flag which requests the opened terminal to allow read/write. We require this flag to be able to reset the sensor module

    3. O_SYNC flag guarantees that the call will only return when any/all data read from the terminal or the data written to terminal is actually read/written.

  2. fcntl(2) β€” required to manipulate the opened file descriptor, in this case /dev/ttyACM0

    1. F_SETFL flag with an arg value of 0, sets the opened port β€” for a given terminal, /dev/ttyACM0 β€” to blocking behaviour. This allows our process to be the only process interacting with the given port.
  3. Baud rate β€” one of the most important parameters for serial communication is not set in this case. Since the microcontroller doesn’t perform any modulation / demodulation functions, setting the baud rate on the host side has no effect. Microcontroller communicates at the programmed β€” during firmware flash of the sensor module β€” baudrate.

The firmware for the sensor module also had to be changed. Any instances of Serial.println() had to be replaced with Serial.print(β€œ\n”). This change had to be made due to windows using \r\n as the newline character over the \n newline character used by UNIX and UNIX-like operating systems. This created a lot of bugs in the program relating to non-standard ending of lines.

Another way to combat this issue would be to set the EOL (end of line) character manually to \r\n in the program. No one solution is better than the other, and the decision was a coin toss.

Result β€” encoder_io class

This class provides all the input / output functions that are implemented by the sensor module firmware. Can also distributed as a stand-alone library with minimal changes necessary for any encoder that communicates over serial port.

It follows all object oriented principles along with Resource Acquisition Is Initialization technique of C++. Is fully independent and extremely fast; and does only what is required for the scope of the overarching project β€” reducing dependencies, and long-term maintenance overhead.

Testing

4 test suites were created using GoogleTest along with relevant test cases and unit tests. Using sane test values, and regression testing.

Another 2 tests were designed toi validate the programme manually.