Program multithreaded_logger

RT Penguin

Current source code: multithreaded_logger.c.

Makefile: Makefile.

Doxygen documentation: here.
The documentation is generated with the configuration file Doxyfile in the subdirectory doxygen/working/, and the output is placed in the subdirectory doxygen/output/, via "doxygen doxygen/working/Doxyfile".


Multithreaded_logger is a C program and a hybrid of
a) a logging program which logs serial data (RS-232, -422, -485, CAN, Ethernet, ...) and
b) a transient recorder/digitizer, for logging parallel data from the input pins of serial ports and/or parallel ports, of type parallel port, with direct port access for a low latency of about 1.5 µs or the 4 input pins of a serial port with ioctl() with a higher latency but good portability.
With a direct port access to toggle an output pin before every read access it is easy to read converted data synchronous, e.g. the output of an analog to digital converter (ADC).
The program uses one thread per input because select() and pselect() can not handle more than one port with i) low latency and ii) correct order of the incoming bytes.
For orthogonality there is also a timer thread which triggers the parallel reading threads periodically, typically every 34 µs under a Linux Kernel with the Preempt-RT patch. The time resolution at data from serial ports is at worst case below 40 µs at onboard ports and port on PCI(e) cards.
For writing the log file to the disk periodically there is also a flushing thread. So for k serial ports and y= parallel ports the program uses 2 + k + ( y ? y+1 : 0) threads.


Because of the threads, the program is made thread safe. So global variables are used only when necessary and only one thread does write to a global variable. For global variables bigger than a word (https://en.wikipedia.org/wiki/Word_(computer_architecture)) atomic read and write would be necessary, so i avoided them. If more than one thread writes to a variable, a mutex or semaphor would be necessary, so i avoided this.

Thread Stucture Each class of threads has another priority, due to the time criticality of the class. For efficiency the priorities are choosen without leaps and the timer thread, for triggering the reading of parallel digital data by port reading, has real time priority (best possible priority, will be sheduled first (in user space)).

The thread structure (left picture) is at the program start as in the common Boss/Worker model: The main() thread is the Boss and creates one reading thread for every specified port (func_serial_read(), func_parallel_read(), func_parallelport_read()), the timer thread (func_timer()) when there is at least one parallel reading thread (func_parallel_read(), func_parallelport_read()), and the flushing thread only when there is something to do (instead of exit()). The optional threads have a dashed rectangle, the non-optional a solid. After the start and warmup the structure is more complicated, a mixture of the Boss/Worker, Peer and Pipeline model.


An important point is that the data are logged defregmented: The input from the parallel ports is always printed as one block, and the evaluation/defragmentation waits if not all parallel reading threads have committed their data from the last polling, because they need some microseconds to do this and do not always have the same speed. The data from serial ports are also buffered and printed with a latency of some milliseconds, because serial data are generally fragmented; the data packet often do come in n > 1 parts, especially when they are relative long, and the defragmentation can be detected by the small time differences between the n packets. The buffering is also necessary for the correct time order of the data, because at every defragmentation of serial data the time stamp is changed to the one from the last part and finished at the timeout at the end (waiting for maybe more data of the same data packet).
The program can easily be modified to use ports which are very similar to parallel ports, e.g.:


Other Tools:

A bash script to list all threads of the program, showing including the priorities and CPU times: showml.sh.
Example output with main (light-weight process (LWP) 10717), two serial reading threads (LWP 10733, 10735), the timing thread for polling (LWP 10737), one from a parallel port reading thread (LWP 10746) and the thread for flushing the log file (LWP 10750), at 30 kHz polling frequency after a few hours:

F S UID        PID  PPID   LWP  C NLWP PRI  NI ADDR SZ WCHAN    RSS PSR STIME TTY          TIME CMD
4 S root      1609   787  1609  1    6 -37   - - 317723 hrtime 1201252 0 Apr25 ?       03:23:24 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
1 S root      1609   787  1632  0    6 -38   - - 317723 poll_s 1201252 0 Apr25 ?       00:01:10 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
1 S root      1609   787  1634  0    6 -38   - - 317723 poll_s 1201252 0 Apr25 ?       00:01:11 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
1 S root      1609   787  1636 10    6 -40   - - 317723 hrtime 1201252 1 Apr25 ?       18:52:45 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
5 S root      1609   787  1645 15    6 -39   - - 317723 sigtim 1201252 0 Apr25 ?       1-04:39:08 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
1 S root      1609   787  1649  0    6 -36   - - 317723 sigtim 1201252 1 Apr25 ?       00:00:09 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10

USER       PID  PPID TT       CMD                         CLS PRI RTPRIO
root      1609   787 ?        ./multithreaded_logger 34 4  RR 136     96

pid 1609's current scheduling policy: SCHED_RR
pid 1609's current scheduling priority: 96

top - 08:34:44 up 7 days, 16:13,  3 users,  load average: 0.40, 0.43, 0.41
Threads:   6 total,   0 running,   6 sleeping,   0 stopped,   0 zombie
%Cpu(s):  7.0 us,  8.1 sy,  0.0 ni, 84.8 id,  0.1 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   3843228 total,  2588396 used,  1254832 free,   401704 buffers
KiB Swap:  3004828 total,        0 used,  3004828 free.   612648 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 1645 root     -99 -20 1270892 1.146g   2120 S 13.3 31.3   1719:08 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
 1636 root      rt -20 1270892 1.146g   2120 S 13.3 31.3   1132:45 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
 1609 root     -97 -20 1270892 1.146g   2120 S  6.6 31.3 203:24.46 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
 1634 root     -98 -20 1270892 1.146g   2120 S  0.0 31.3   1:11.24 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
 1632 root     -98 -20 1270892 1.146g   2120 S  0.0 31.3   1:10.78 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10
 1649 root     -96 -20 1270892 1.146g   2120 S  0.0 31.3   0:09.37 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10

The script can easily be modified to view the threads of other processes.
See also Linux Processes: Structure, Hangs and Core Dumps.


A C++ program to list all availible serial ports, source code: findser.cpp.
But the program does not find RocketPort ports, which are at /dev/ttyR*.
Other ways are checking /dev/tty* with stty, checking the boot messages with "dmesg | grep tty" or grepping in /sys and using an alias (in ~/.bashrc):

# list all availible serial ports, works also with USB converters and in an Oracle VM VirtualBox
alias findserial='ls -l /sys/class/tty* | grep /p | grep -v virt | grep -v ../../devices/platform/'


A bash script to estimate the lowest and highest possible baud rate of a serial port, with the serial port name (e.g. ttyS0) as command line parameter: tty_speedtest.sh.
This script, and the findser program, also works with serial converters and modems, e.g. with an USB-RS-232 converter at ttyUSB0 or the Samsung SGH D900i at ttyACM0.


A bash script to list all availible parallel ports: findpar.sh.
It can not find parallel ports at USB adapters, which are usually at /dev/usb/lp* from the driver usblp.
But such adapters are rare and more than a thousand times slower as a parallel port onboard or on a PCI or PCIe card. And an USB alternative is using the parallel input pins of USB serial adapters (CTS =Clear to Send, RI = Ring Indicator, DSR = Data Set Ready and DCD = Data Carrier Detect, simple signals at RS-232, differential signals at RS-422/485). Therefore multithreaded_logger reads the level of these signals via ioctl(), not port access because that does not work at USB adapters.
But USB adapters should generally be avoided because USB often is not stable, so that sometimes an adapter can not be found or vanishes at ttyUSB0 and comes back at ttyUSB1.


A C program to measure the time of a port access by doing 1e6 (one million) readings/writings and see how long it takes, thus calculating how much time a port access takes at average, source code: simpleportbenchmark.c.
The port latency is important for measuring the time of level changes with a high time resolution and for hard real time applications, where it makes a big difference if the latency of the port is about 1.5 µs (PCI/PCIe/Expresscard) or 15 ms (USB adapter).


The C program swave12a.c for toggling the parallel port pins, with three parameters: Priority, timer period and base address.
Example with priority 98, timer period 50,000 nanoseconds and base address 0x3008:

nice --20 ionice -c Realtime -n 0 ./swave12a 98 50000 0x3008

With a timer period of 50,000 ns the output is:
10 kHz on pin 2 = D0
5 kHz on D1
2 kHz on D2
500 Hz on D3
250 Hz on D4
100 Hz on D5
40 Hz on D6
20 Hz on D7
8 Hz on C0
4 Hz on C1
1.5 Hz on C2
0.5 Hz on C3.
So it produces square waves for oscilloscopes and blinking for the eyes or tones for the ears. It causes about 9 % CPU load on a 3 GHz CPU.
A video, with green LEDs at the data pins (D*), yellow LEDs at the status pins (with integrated pullups) and red LEDs at the control pins (C*):


The sound from D0 (10 kHz) under load with an RT Kernel (4.13-rt3):
The sound (and sometimes litte background noise) with an lowlatency kernel (4.0.5-040005-lowlatency):
With a non-RT kernel the latencies of many microseconds do cause clicks.
At the LED blinking there is no visible difference between RT and non-RT kernels.



A C program to read a byte from an I/O port, command line version of inb(), source code: inb.c.
The pramameter, the port address, can be decimal, hexadecimal or octal.


A C program to write a byte to an I/O port, command line version of outb(), source code: outb.c.
The pramameters, byte and port address, can be decimal, hexadecimal or octal.


A C program to show environment parameters like the size of an int, a pointer etc. and the Endianess because this is important for ports wider than 8 bits and generally at reading/writing more than 8 bits at a time, source code: environment_test.c.


A C program to set the 8 data pins of a parallel port as input pins, with the parallel port number as command line parameter, source code: setpar_bppin.c.
The program set also sets the port to mode bidirectional mode aka Byte mode aka PS/2 mode aka Tristate mode.
If you can set the parallel port type to Mode Bidirection in the BIOS, you should do so before to make sure this mode is available.
This program is made to prepare the parallel port(s) for multithreaded_logger or other programs which do read from the parallel port(s) data pins (D0-D7).
If the first arguments, the parallel port number, starting with 0, has a negative sign, this flag sets the port to write mode. See also the online help of the program.


Example script with configuration for reading from two serial ports and one parallel port, including nice and ionice setting for the program: test3.sh. This script also prints some environment data, uses a lock file, configures the first parallel port 8 data pins as inputs and creates a log file for its output.



Multithreaded_logger is a multi threaded program for logging (data acquisition, sniffing) under Linux from 1 to many (>32) devices, serial and/or parallel ports, with one posix thread for every device, with defragmentation of serial data packets and with polling for parallel ports (all 13 input pins).
It can also use the 4 input pins of a serial port as a small parallel port.
The defragmentation of serial data is necessary because read functions like select. do deliver the incoming data often not in the real structure, which can be seen with an oscilloscope, even if they use only one serial port. Therefore the program calculates the time differences between the packets (delivered from select) and puts the successive packets together (defragmentation) if they are so close that they must be two parts of the same packet.
Another point is that one select function can read data from more than one device, but in this configuration the order of simultaneous incoming data is often wrong and this is a known bug of logging programs which work this way, e.g. jpnevulator. So this program here uses the select function, but for only one device and it does it with one posix thread for every device. Therefore, and for simultaneously reading parallel data via polling and printing the data into the log file, this program is multi threaded.


Picture of an example implementation

This picture shows an Esprimo E9900, CPU: Intel Core i3 520 with 35 W TDP, two Cores with 3066 MHz, no Hyperthreading (which is good because for hard real time HT is always deactivated), in a dust-tight switch cabinet with a cheap PCI parallel port card for logging 13 parallel signals every 35 microseconds and all data from two RS-422 serial ports, via the onboard RS-232 ports and level converters.
The internal Ethernet connects the PC with the UPS, IP-Watchdog, one Jacquard Machine and a temporarily connected Notebook via the managed Ethernet Switch.
This cabinet worked for half a year in a weaving mill in Switzerland in 2016 wihtout problems, with periodic uploading of logged data via rsync and passwordless SSH to a SSH server, had remote access via a reverse ssh proxy and other services like periodic downloading of the UPS logs via ftp, shutdown with delayed UPS shutdown at AC power loss for more than 15 minutes, and autostart when AC power is back, because the AC power came from the jacquard machine nearby. The two serial and thirteen parallel data were logged from a weaving machine and a jacquard machine nearby.

The onboard serial ports usually, and in this PC, have a 16550A compatible UART, which means a 16-byte FIFO buffer with a programmable interrupt trigger of 1, 4, 8, or 14 bytes received. Lower is better for low latency, but that requires a modification of the kernel driver and causes more IRQ and CPU load. At high baudrates, e. g. the brainboxes high speed serial cards at 15 or 18 MegaBaud, you have more than two bytes per microsecond and therefore the interrupt trigger must be (much) higher than one, or you should switch to polling, e. g. every 40 microseconds. The serial cards (PCI, PCIe, ExpressCard) often have FIFOs bigger than 16, usually 32 or 128 Bytes.

The four external fans in the cabinet made air circulation inside the dust-tight cabinet to avoiding hotspots and overheating.


Some pictures of cheap hardware for data acquisition, e.g. for measuring computers

This picture shows the backside of an old PC of type HP Compaq D530 with one parallel port (red/pink 25 pin port) and one serial port (green 9 pin port) in the I/O shield. They are inclusive, do cost nothing (extra).


The right pictures shows the slot bracket which has one pin as 12 V output with up to 1.35 A, up to 16.2 Watt (Exsys EX-43092-S Rev. B, PCI, RS-232, costs: used about 5 Euro in 2015), but this replaces the ring indicator function, so this input pin is not implemented here. Other cards, e.g. the EX-41250 (low profile PCI 2x RS-232, 1x parallel port) or some cards from brainboxes, have an option to use 12 or 5 V output voltage on one or more pins or to use no output voltage. Cards with opto-isolated serial ports usually have no supply voltage output, e.g. the Brainboxes PX-368 (PCIe, 4 Port RS422/485).
The left pictures shows a 16x serial port PCI card from RocketPort (96460-5 Rocket 16 PCI), as one example for a cheap card with many serial ports, and a very cheap used card e.g. on Ebay for about 30 Euro (2015). This card is for RS-232 only, but others have an option to set the mode to RS-232 or RS-422. This card can be used for baud rates from 300 baud to 921.6 kbaud, others from 50 baud to 15 Mbaud (Brainboxes CC-530, RS-232) or 18 Mbaud (Brainboxes CC-525, RS-422).


The right picture shows a DB-9 adapter (Pollin 810087, only 1.95 Euro 2015) with screw terminals, for connecting a serial port with screwed wires and with hot plugging.
The left picture shows a PCI card with two parallel ports for under 10 Euro (2015), the Logilink PC0014. The card is a low profile card, but the slot brackets are full-size.


The right picture shows the Interface Development Tools FTDI UM232R, about 15 Euro (2015) from the top.
The left picture shows the UM232R with a tested adapter for toggling the 4 input pins (CTS, DCO, DSR, RI) with little switches.


The left picture shows the PCIe card TP35 aka 9815EL-4 and has four parallel ports, so that with five cards in you computer you have 20 parallel ports, 260 input pins (or up to 240 output pins). If that is not enough you can take a Motherboard with 11 or more PCIe slots, e.g. the Supermicro X10DRX with 10 PCI-E 3.0 x8 slots and one PCI-E 2.0 x4 (in x8) slot, or you can use expander boxes. The card does cost about 50 Euro (2016-04), but the costs per parallel port are not high, only 12.5 Euro.



The RS-232 and parallel port pin out can be found on Wikipedia:
http://en.wikipedia.org/wiki/RS-232 http://en.wikipedia.org/wiki/Parallel_port.


Some pictures of cheap hardware for output, e.g. for control computers

This picture shows three types of relays, one electromagnetic relay and two solid state relays (SSR). They do cost about 3 Euro per piece.
All can be switched off and on with one parallel port pin, but generally that does work only when the relay input is connected to one pin and the +5 V supply.


The JGX-3380A is a relay for switching 3 current phases of 480 V and up to 80 Amperes. It does cost about 25 Euro and can also be switched on/off with one parallel port pin. It also can be used for switching only a 50 Watt light bulb at only one current phase of 230 V AC.


Examples

Example 1

Command line:

./multithreaded_logger --help

./multithreaded_logger version 2015-12-23 (compiled Dec 26 2015, 10:58:31).
Usage: ./multithreaded_logger    
You can specify up to 32 devices with device flags.
Example:
 ./multithreaded_logger 200 2 ttyS0 1 ttyUSB0 3 0 0x10
for logging the serial data from the UART /dev/ttyS0 and /dev/ttyUSB0 and the parallel data
from the /dev/ttyUSB0 and the first parallel port (/dev/parport0) with a polling time of
200 microseconds, which means 5 kHz polling frequency, and printing the parallel data into the
log file only at the start and at rising edges.
Polling time: 1 ... 2,147,483,648 microseconds.
Filter types: 1: no filtering, 2: Only rising edges, 3: Only falling edges,
  4: All edges (all changes) and first value (elimination of all redundant date, data compression),
  5: Only when at least one bit is zero (not set), 6: Only when at least one bit is one (set),
  with negative sign: Print the bits as series of bits in (hexadecimal) bytes and not as
  single nibble (with value 0 for actual and previous value zero, 1 for actual and previous
  value one, 2 for falling edge and 3 for rising edge, so BIT0 is the current state and BIT1
  indicates an edge.
Device flags: 1 for UART input, 2 for parallel input from serial port (RI, DSR, DCD, CTS),
 0x10 for parallel input from parallel port (all 13 input pins).
For every reading, writing and timing thread a (posix) thread works, so
at total  +1)*2 threads are working for this program.
For configuration of the serial devices use a program like stty.
The current posix threads implementation is:    Native POSIX Threads Library by Ulrich Drepper et al


Example 2

This example logs the serial data from /dev/ttyS0 (device-number 0), ttyS1 and the parallel data (device-number -1) from the first serial port, with a polling interval of 34 microseconds and filtering, so that parallel data are logged only at the first polling and after this start only when there is a change, so that the data are lossless compressed.
The header and the footer have a lot of metadata and statistics. The first (zeroth) error bit counter is for serial devices the counter of bit 0 in the line status register, which shows that the data is ready, that means that a byte was received in UART and the receiver buffer is ready for reading.

The command line is:

nice --20 ionice -c Realtime -n 0 ./multithreaded_logger 34 4 ttyS0 1 ttyS1 1 0 0x10

This line is executed with the script test3.sh (above) and the output is also teeed to the log file here.

Log file:

multithreaded_logger vers. 2015-12-21, last modified Tue Dec 22 08:03:40 2015, compiled with GCC version 4.9.2,
at Dec 22 2015 08:03:43, start at 2015-12-22 08:06:10 UTC.
Monitoring devices (number 0, 1, ...) with the flags: ttyS0 0x1 ttyS1 0x1 0 0x10
OS: Linux release 4.1.3-rt3 (version #2 SMP PREEMPT RT Tue Aug 11 06:59:47 UTC 2015) on x86_64, byte order little Endian
Posix version: 200809, the current posix threads implementation is: 	Native POSIX Threads Library by Ulrich Drepper et al
_POSIX_THREAD_PRIORITY_SCHEDULING is true, the POSIX real-time extensions are supported.
Parallel port parport0: Base address 12296 = 0x3008.
Used clock type: CLOCK_REALTIME with a resolution of 1 ns, scheduling time slice (sched_rr_get_interval): 0.100000000 s.
Polling interval [microseconds]: 34, Filter type: 4, main() loop interval [ms]: 200.
Date time daylight-saving-time device-number (hex)data:
2015-12-22 08:06:11.513216 0 -1  1 1 1 1 1 1 1 1 1 1 1 1 1
2015-12-22 08:07:00.656674 0 -1  2 2 2 2 2 2 2 2 2 2 2 2 2
2015-12-22 08:07:00.797094 0 -1  3 3 3 3 3 3 3 3 3 3 3 3 3
2015-12-22 08:07:01.087216 0 -1  2 2 2 2 2 2 2 2 2 2 2 2 2
2015-12-22 08:07:01.216382 0 -1  3 3 3 3 3 3 3 3 3 3 3 3 3
2015-12-22 08:07:01.349356 0 -1  2 2 2 2 2 2 2 2 2 2 2 2 2
2015-12-22 08:07:06.117163 0  0  00
2015-12-22 08:07:06.117163 0  1  00
2015-12-22 08:07:06.137154 0  1  01
2015-12-22 08:07:06.137164 0  0  01
2015-12-22 08:07:06.177139 0  1  02
2015-12-22 08:07:06.177141 0  0  02
2015-12-22 08:07:06.193476 0 -1  3 3 3 3 3 3 3 3 3 3 3 3 3
2015-12-22 08:07:06.217124 0  1  03
2015-12-22 08:07:06.217127 0  0  03
... (truncated)
2015-12-22 08:07:18.512999 0 -1  2 2 2 2 2 2 2 2 2 2 2 2 2
2015-12-22 08:07:18.665420 0 -1  3 3 3 3 3 3 3 3 3 3 3 3 3
2015-12-22 08:07:23.123456 0 Termination at 2015-12-22 08:07:23
Minimum and maximum number of new elements (structures) when main reads from posix threads buffers
(size 16384 elements) including fragmented ones: 4, 5883 (10501 reserve).
Minimum and maximum number of (new) elements in the main buffer
(size 32768 elements): 4775, 5899 (26869 reserve).
Total number of from the UARTs received bytes: 512, packets: 512, number of packet defragmentations: 0 = 0 %.
Minimum (serial) data packet length: 1, maximum length: 13.
Minimum time difference in microseconds of (serial) data packets: 19925, maximum: 41375.
Total number of parallel reads (samples): 2109003 = 2.11e+06
First parallel read thread, statistics of the polling timing in microseconds for 2109003 = 2.11e+06 time differences
(actual -nominal value), including the offset for signal handling, clock_gettime etc.:
  Average deviation of the polling in microseconds: 3.971.
  Minimum deviation: 3.579.
  Maximum deviation: 30.40, at index 1606001 = 1.61e+06.
Date and time of the maximum deviation (latency): 2015-12-22 08:07:06.117849 0
Filter statistics (delayed packets, to avoid non repeatable reads): 28750 because of fragmentation, 120181 because of maybe not committed packets.
Error counts for each ot the 4 threads (parity error etc., for serial read threads first the bit counts from the line status register):
256	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0
256	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0
0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0
0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0
0 overrun error(s) of the timer thread (polling).
0 overrun error(s) of the main thread 359 = 3.59e+02  nanosleeps.
Last received signal: 15, total signal count: 1.
Shutdown process bits: 0x0. A shutdown is not pending. Current runlevel (usually 5): 5.

The untruncated file can be found here.
In this example the 13 input pins of the parallel port are simply connected together (as one very redundant input) and the input at ttyS0 and ttyS1 is the same, so that the received data are the same and the receive times are very close, with a standard deviation of less than ten microseconds and a worst case value of less than the width of one bit at 19.2k 8E1, which is about 50 µs.
The LSB (Last Significant Bit = BIT0) of the nibble of the parallel data is the current state, and BIT1 indicates an edge, to make the data better human-readable.

In the LSR (Line Status Register) we have:

BIT0 Data ready/availible
BIT1 Overrun error
BIT2 Parity error
BIT3 Framing error
BIT4 Break Interrupt, break signal received
BIT5 Empty Transmitter Holding Register, transmit ready
BIT6 Empty Transmitter Shift Register and line is idle, transmitt complete
BIT7 Error in received FIFO, errorous data in FIFO

This tells us that in the example above the 256 bytes received at ttyS0 and ttyS1 were received as single bytes and without any error.

The polling intervall of 34 mikroseconds (29.412 kHz) could be used for months without an overrun error. That means with the old PC you can implement a control loop with this speed. Because of the multithreaded design, additional latencies, e. g. additional 1.4 Microseconds for a port write, and a dozen Mikroseconds for doing a complex calculation, e. g. path calculation, are no problem. You only have to make sure there is always one core free for the control loop thread. This is really fast because in 34 mikroseconds something which moves with the speed of the sound (in air) travels only 12 mm, half an inch.


Update 2015-02-20

I corrected the function for getting the base address of a parallel port from /proc/sys/dev/parport/parportX/base-addr and added a program for port benchmarking, for measuring the average time of one port access (reading or writing).


Update 2015-03

I added the set_latency_target(void) from Cyclictest and munlockall() at termination.


Update 2015-04

Added handling and logging of overrun errors of the timer thread (polling), corrected packet counter printing.
I found out that with select() it is difficult, almost impossible, to handle signals correctly and that select() has a race, even when used with only one device, see http://www.win.tue.nl/~aeb/linux/lk/lk-12.html#ss12.3.
So i will switch from select to pselect in multithreaded_logger.c in the next time.


Update 2015-05

The automatically loaded parallel port modules from linux are a little complicated: For finding the base addresses the program needs them but when using them they should be unloaded because they could disturb. Therefore the program uses the modules at the start, unloads them before accessing the parallel port(s) and loads them just before the exit. So the script freepar.sh, for unloading the modules, is not needed anymore.

Added priorities so that this program has nearly the same worst case latency as cyclictest (with one thread per CPU). With an old PC and old 3.0 RT kernel I can use two serial ports and one parallel port with a polling frequency of 10 kHz without overrun errors.


Update 2015-12

I made a new version which supports net devices like CAN bus adapters, automatically by the file name of the serial device.
That should work, but i had no time to test it with net devices.
The current version is tested with 0..1 parallel ports and 0...4 serial ports, standard configuration is 1 parallel port (13 input pins) at 30 kHz polling frequency plus 2 serial ports (at 19k2 8N1E).


Update 2016-01

I eliminated a bug which can occure when parallel and serial data are logged simultaneously and which filtered too much parallel data.


Update 2017-02

I added the picture with the tread structure (above) and made an article about hard real-time and multithreaded_logger in the (german) Linux-Magazin 03/2017: Harte Echtzeit mit Linux durch Preempt-RT zum Messen und Steuern.


Update 2017-03

I made the lecture 207 at the Chemnitz Linux Days 2017-03-12: https://chemnitzer.linux-tage.de/2017/en/programm/beitrag/207.


Update 2017-05

The program also works with slow signals and polling every 100,000 mikroseconds (instead of 35), and without hard realtime, e. g. the Ubuntu Kernel 4.10.0-20-lowlatency (version #22-Ubuntu SMP PREEMPT Thu Apr 20 10:18:38 UTC 2017) on x86_64. Cyclictest shows a worst-case latency of about 3 ms.
I use it for my mail box, with a cheap IR motion detector alarm set, Wireless Driveway Alert System ORNO OR-MA705. The detector (OR-MA-705OR) is at the end of the mail box, mounted with a magnet holder. The LED is disconnected for a longer battery life and to avoid attracting attention.

The electrical signal from the the radio receiver (OR-MA705OD) comes from the cathode of the LEDs (ground) and their series resistance (blinking signal). The input at the parallel port is secured with a 4.7 V Z-diode (5 Watt, type 1N5337B) and 1 kOhm series resistance:


The other 51 inputs of the TP35 card are unused.
The result is the logged LED blinking at motion detection, when something goes in or out of the mail box:

 
2017-05-20 04:46:16.418655 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 04:46:17.418655 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 04:46:17.718657 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 04:46:18.718656 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 04:46:19.018658 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 04:46:20.018658 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 04:46:20.318655 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 04:46:21.318654 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 10:46:04.218656 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 10:46:05.218657 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 10:46:05.518654 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 10:46:06.518653 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 10:46:06.818656 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 10:46:07.818655 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 10:46:08.018658 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 10:46:09.018657 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 15:50:17.518659 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 15:50:18.518662 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 15:50:18.818662 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 15:50:19.818661 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 15:50:20.118660 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 15:50:21.118659 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 15:50:21.418654 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 15:50:22.418654 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 19:19:16.418654 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 19:19:17.418656 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 19:19:17.718654 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 19:19:18.718653 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 19:19:19.018659 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 19:19:20.018659 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0
2017-05-20 19:19:20.218655 1 -1  0 0 0 0 0 0 0 3 0 0 0 0 0
2017-05-20 19:19:21.218655 1 -1  0 0 0 0 0 0 0 2 0 0 0 0 0

The input is at D7, pin 9, and has the value 3 at rising edges, value 2 at falling edges:
At night at 04:46:16 the newspaper was put in
At 10:46:04 i put it out
At 15:50:17 post came in
At 19:19:16 i put it out

I started the program with the options 100000 4 0 0x10.
At the receiver, an OR-MA-705OD, the DC input should be 6 V, but 5 V from USB is sufficient, because with (full) batteries the DC input is only 4.5 V.

To avoid toggling from the 12 open inputs i masked them in the source code.


Update 2018-01

After some boots the PCIe card TP35 was not found because the PCIe slot is a loose contact. So i made an improvisational card downholder by upcycling a little brown bottle with a compression spring and a srew which locks the screw into at the top:





Sitemap