This sketch makes the Arduino work as a GPIO interface for a computer, where input and output changes are transmitted in run-length encoding. Every time an input pin changes, the Arduino sends the new values of the input pins and the time since the last change. In a similar way, the Arduino can be programmed a sequence of output pin changes, each specified by the new values of the output pins and the time since the previous change.
As an example, sending RLINSTART (the byte 0x42) to the Arduino via the serial port (/dev/ttyACM0 or /dev/ttyUSB0), makes the Arduino starts listening for changes in its input pins and reporting them back to the computer:
The first reported change RLVALUE 255 0x00 (the three-byte sequence 0x41 0xff 0x00) means that the current values of the input pins is 0x00, all low (the number 255 has no meaning in this case). The second is RLVALUE 1 0x10 (the three-byte sequence 0x41 0x01 0x10), meaning that after a time slot the input pin D4, digital input 4, turned to high. The third is RLVALUE 3 0x00, signaling that the pin turned back to low after three time slots.
The default time slot is 16 microseconds, but can be programmed to be shorter or longer.
Output changes can be programmed in chunks. For example, the computer sends RLVALUE 3 0x00 and RLVALUE 2 0x10 to program all output lines to go low after three time slots, then one pin to go high after other two time slots. Only when the whole sequence of changes is complete the computer starts it by sending RLOUTSTART 0x10 to the Arduino, where 0x10 specify the initial value of the output pins.
RLOUTSTOP +---|---|---+ +---+ +------ RLVALUE 3 0x00 | | | | | RLVALUE 2 0x10 -----+ +---|---+ +---|---+ RLVALUE 1 0x00 RLVALUE 2 0x10 ^ V RLOUTSTART 0x10 ----------+ RLOUTSTOP
The Arduino signals back that the output sequence has been completed by sending RLOUTSTOP to the computer.
The Arduino interacts with the computer via the serial port at 115200 baud in raw (binary) mode using a set of commands; each is a single-byte opcode followed by zero, one or two bytes. The opcodes of the basic commands are as follows (more details below):
RLVALUE 0x41 RLINSTART 0x42 RLINSTOP 0x43 RLOUTSTART 0x44 RLOUTSTOP 0x45 RLTIMEOUT 0x47
The values in the RLVALUE and RLOUTSTART commands specify the values of the input or output pins, where each bit is a pin:
input values:7 | 6 | 5 | 4 | 8 |
13 | 12 | 11 | 10 | 9 |
For example, a change from low to high in pin 8 after three time slots while all other input pins are low is reported as RLVALUE 3 0x01. In the same way RLVALUE 5 0x10 requests the Arduino to change pin 12 to high and the other output pins to low after five time slots.
The values with AVR pin names are:
input values:D7 | D6 | D5 | D4 | B0 |
B5 | B4 | B3 | B2 | B1 |
By default, the time slot is 16 microseconds, but can be shortened or lengthened with the RLSTEP command (the byte 0xe4) sent from the computer to the Arduino before starting input or output.
RLSTEP argument | microseconds |
---|---|
0 | 0.5 |
1 | 1 |
2 | 2 |
3 | 4 |
4 | 8 |
5 | 16 |
6 | 32 |
7 | 64 |
8 | 128 |
For example, RLSTEP 0x02, the sequence of two bytes 0xe4 0x02, programs the time slot to be 2 microseconds both for input and output. Receving RLVALUE 3 0x10 from the arduino means that the input pins have changed to 0x10 after 6 microseconds.
Larger and shorter slot can be realized by the prescaler as used in the RLCONFIG command, below.
computer | RLSTEP 0x02 → | arduino |
RLINSTART → | ||
← RLVALUE 255 0x11 | ||
← RLVALUE 5 0x20 | ||
← RLVALUE 2 0x40 | ||
← RLVALUE 9 0x31 | ||
← RLVALUE 255 0x41 | ||
← RLVALUE 2 0xf1 | ||
RLINSTOP → |
The computer sends RLSTEP 0x02 to specify a time slot of 2 microseconds. Then it sends RLINSTART to tell the Arduino to report input pin changes. The Arduino immediately sends back RLVALUE 255 0x11 with the current values of the input pins and a fake duration of 255.
The next message RLVALUE 5 0x20 tells that after five time slots (10 microseconds) the state of the input pins changed to 0x20. The next two messages have a similar meaning.
The RLVALUE 255 0x41 message indicates that no change has been detected for 255 or more time slots, in this case 510 microseconds. The special value 255 for a time duration is used both for the initial input pin values and for the case where the change is too far for the previous one for the number of time slots to fit in a single byte.
If the system should be able to distinguish durations larger than this (for example, between a change after 520 miscroseconds and a achange after 600 microseconds) a larger step should be used.
The computer programs a sequence of changes in the output pins. They are performed only when the starting command RLOUTSTART 0x20 is received.
computer | RLSTEP 0x02 → | arduino |
RLOUTSTOP → | ||
RLVALUE 4 0x30 → | ||
RLVALUE 2 0x22 → | ||
RLVALUE 5 0x12 → | ||
RLVALUE 9 0x30 → | ||
RLVALUE 1 0x33 → | ||
RLOUTSTART 0x20 → | ||
← RLOUTSTOP |
As soon as the Arduino receives RLOUTSTART 0x20 it sets the output pin to the value 0x20: pin 12 is high, the others low. It then starts performing the programmed changes: after 4 time slots (8 microseconds) the output value changes to 0x30, meaning pin 12 and 13 high and the others low. After other 5 time slots (10 microseconds) the value changes to 0x22, meaning pin 12 and 2 high, the others low.
When the programmed sequence of changes is completed the Arduino sends back RLOUTSTOP to the computer. A new sequence of changes can be programmed: first the computer sends RLOUTSTOP, then the new sequence of changes and finally RLOUTSTART with the initial value of the output pins.
At startup, the Arduino makes some initializations. When completed, it sends RLREADY to the computer to signal that it is ready to take commands. This is a single-byte command: 0xD1. The computer should not attempt any interaction until this is received. Note that the serial buffer on the computer may contain bytes from a previous run.
When the computer sends RLDEVID, the byte 0xED, the Arduino answer with RLDEVID followed by exactly sixteen bytes. This is the identification string "runlen_gpio____\x00", which can be used to check whether RunLengthGPIO is installed on the device.
Sometimes, the lack of changes in an input pin for some slots is meaningful.
For example, in the uart protocol (ttl levels) a byte always begins with an high → low transition, but the end of a byte may not be marked by any transition. For example, 0xFF is transmitted as follows:
-----+ +---+---+---+---+---+---+---+---+---+--------- | | +---+ start 1 1 1 1 1 1 1 1 stop
The byte is transmitted with an high → low transition at the beginning, followed by a low → high change. The level then remains high for the remaining time allocated for the byte. If no other data is sent over the serial line, the Arduino sends only the RLVALUE messages of the first two changes, and nothing else.
The computer can request to be notified of a lack of changes by sending the command RLTIMEOUT len before starting reception. If the input pins do not change for at least len slots, the Arduino sends RLTIMEOUT back to the computer (one byte). This message signals that the input pins have not changed for at least len time slots. Note that it is not sent again at 2×len, 3×len, etc.
A change following a timeout is still notified by an RLVALUE message with the time since the last change. In other words, using a timeout does not affect the sequence of RLVALUE messages. For example, if:
then the sequence sent by the Arduino is RLVALUE 20 0x10 RLTIMEOUT RLVALUE 70 0x00. Apart from the RLTIMEOUT message in the middle, the sequence is the same as if no timeout had been set. While the timeout indicates at least 50 time slots of inactivity, this time is not to be included if summing up the durations reported in the RLVALUE messages to calculate the absolute time of changes.
If the change after the timeout is close to it, the RLTIMEOUT message may not been sent at all. In the above example, the Arduino could as well send RLVALUE 20 0x10 RLVALUE 70 0x00. The timeout is guaranteed to be sent if no other change follows.
The computer can disable the timeout message by sending RLTIMEOUT 0.
The command RLCONFIG byte allows configuring various features of the program.
prescaler | slot change |
---|---|
000 | (no change) |
001 | /8 |
010 | ×1 |
011 | ×8 |
100 | ×64 |
101 | ×256 |
For example, the time slot for the default scaler 5 is normally 16 microseconds, but with prescaler 101 it becomes 16×256 microseconds.
For example, if a first change is programmed after 100 time slots from the start and and a second after other 100, the first may actually be performed at time 102. Depending on this configuration bit:
No more than 255 changes can be programmed for a single output run. However, the computer may keep sending RLVALUE after RLOUTSTART. These are however not guaranteed to be taken, and not even to be performed at the right time.
In the same way, no more than 255 input changes can be internally stored. This is relevant because the serial port runs at 115200 baud, and each change requires three bytes to be sent, meaning 1000000/115200 * (1+8+1) * 3 = 260 microseconds. A continuous stream of fast changes may fill up the input buffer, resulting in unreported changes. Even changes as close as 6 microseconds to each other are captured, but not more than 255 consecutive ones.
Output changes closer than 6 microseconds are impossible. If output changes are closer than 12.5 microseconds from each other input is disabled until these changes are performed. Full duplex is possible for larger interval, and the relative error in both input and output timing is higher for closer changes.
When using input and output together, the command RLSTART effectively replaces both RLINSTART and RLOUTSTART, with the advantage of using the same time origin for both input and output.
The maximal correctly reported input interval is 0xffff × prescaler × clock_period, around 32.5 milliseconds for the default prescaler 8 and 4194 millisecond for the maximal prescaler 1024. Input changes further apart than this amount may be incorrectly reported with a length less than 255 instead of the correct 255.
There is no maximal time limit for output: arbitrarily long intervals can be realized by sequences of smaller ones; however, due to the way times are compared, each single interval cannot be longer than 0x7fff × prescaler × clock_period.
The scaler does not affect these limits.
These pins (AVR: B0, B1 and B2) are timed in a different, more accurate way than the others: using harware counters rather than interrupts. This improves accuracy of relative timings of the same pin, but also of any of the three pins in this group, both on input (9) and output (10 and 11). Activating the noise canceler on pin 9 (see above) shifts changes on it of four clock cycles ahead (0.25 microseconds).
On output, this only holds for changes further apart than about 15 microseconds. Faster changes are realized on these pins in the same way as the others. The same limits on the fastest changes (6 microseconds for half duplex, 12.5 for full duplex) holds for these pins: improved accuracy does not imply that faster changes are allowed; fast changes on pin 9 (AVR: B0) may even go undetected while they would not on the other input pins.
On the input pins, internal the pullup resistors of the microcontroller can be activated using the RLPULLUP val command. Each bits of val control each pullup resistor, using the same scheme as input: for example, RLPULLUP 0x20 activates the resistor on pin 5 (AVR name: D5).
Pins 6 and 7 are normal digital pins, but can be configured as the input of the analog comparator. The corresponding value as reported by RLVALUE messages has 10 in the two upper bits if the voltage on pin 7 is higher than that of pin 6, otherwise 01. Such conditions can be checked as val & 0x80 and val & 0x40.
The analog comparator is activated by bit 5 of the RLCONFIG command. If bit 6 is also set, pin 6 is inactive as input, and the comparison is between the voltage at pin 7 and a fixed internal value of 1.1V.
RLVALUE 0x41 RLINSTART 0x42 RLINSTOP 0x43 RLOUTSTART 0x44 RLOUTSTOP 0x45 RLSTART 0x46 RLTIMEOUT 0x47 RLREADY 0xD1 RLDEVID 0xED RLSTEP 0xE4 RLPULLUP 0xCC RLCONFIG 0xCF
Tested on the Arduino Uno R3, probabily works on every AVR-based board.
Makes pins 4 and 12 of an Arduino board running RunLengthGPIO a serial TTL-levels UART. In "read" mode, it interpreters the changes detected on pin 4 as serial data (UART TTL), and prints it on stdout; in "write" mode, turns stdin into its serial representation (UART TTL), a sequence of changes for pin 12.
See uart -h and the comments at the top of the source file for more details. With option -c, uses pins 8 and 9.
Tested on a real board, and works for all common baud rates from 1200 to 115200 with option -m (originalstart mode). For rates over 4800 reception of ascii data only works up to 42 characters at time.
Turns the data received from RunLengthGPIO into an image of how the input pin values changed over time.
Can be used directly with a real board, or with data saved to file by another program. The latter requires the program to save everything that is received from the board to a file. For example, uart.c does it when using option -o log.
Decodes the changes on an input pin according to the NEC infrared remote protocol. With a resistor and a photodiode, the Arduino acts as a receiver for such remotes.