There never seems to be enough pins on a microcontroller to do everything you want, especially with really small processors. So it can be difficult to dedicate two or more precious pins for serial comms interfaces. For simple messaging, it turns out that one pin and some signalling can do the job.
I created the OnePin protocol and library to provide a lightweight software-only implementation for a single wire serial point-to-point link between two devices.
OnePin uses a single digital I/O line and ground to communicate
between a primary and a secondary device. The primary always initiates
and controls the communication.
This link interface is useful to implement simple message transfers
between a primary device and a less capable secondary device, such as
custom sensors or actuators with simple processor control.
OnePin borrows heavily from, and simplifies, the Dallas Semiconductor/Maxim 1-Wire® specification. Differences include:
- Links are point-to-point (one Primary and one Secondary) rather than
- Omitting the bus also eliminates the need for device identification
- Timing parameters and handshaking protocol are implemented differently.
OnePin is a communications link is between a Primary device (PRI) and a Secondary device (SEC). The PRI is assumed to have more computing power than the SEC, which is envisaged as a small embedded processor providing local control for a sensor or actuator.
OnePin operates using just one I/O pin and electrical ground per device pair, switching the direction of signal flow according to a timer based protocol implemented independently in the PRI and SEC. The electrical connections is dependent on the hardware implementation, which primarily needs to ensure that the digital HIGH and LOW signal voltages are are compatible between devices.
Electrically, the link is idle HIGH and communications is effected by pulling the link LOW. When the I/O pins are set to input mode in the PRI or SEC they are pulled HIGH using the processor’s internal pullup resistors (INPUT_PULLUP). However, a external pullup is also possible and compatible with the operations of the OnePin library.
The communications link timing is divided into fixed time slots (T) and all protocol timing is expressed as as multiples of T. There is no system clock – communications are synchronized to the falling edge of PRI.
PRI initiates every communication between the devices, down to bit level transfers for both directions. OnePin is also highly tolerant of SEC device failure, re-establishing communications as soon as both devices are available – when the PRI restarts communications SEC will respond and signal its presence.
Each protocol data transaction occurs in packet, with transfers occurring as sequential bits starting with the LSB (20). As all activity occurs on the same wire, communications are necessarily half duplex.
Data is transmitted in packets up to 32 bits long. The contents of these data packets is arbitrary determined for an application but known to both PRI and SEC at code compile time.
There are 5 basic signals used for communications (Write0, Write1, Read, Reset/Presence) described below. The timing (length) of the first part of the signal (the header or preamble) identifies the type of signal to follow.
Transmission of a packet begins with PRI initiating a Reset Signal, then:
- To write data to SEC, PRI sends a packet using the Write Signal for each bit.
- To read data from SEC, PRI initiates a Read Signal for each bit until a packet has been received.
Write 1 Signal
PRI pulls the low LOW for 0.5T and then HIGH for the rest of the time slot. On the rising edge SEC determines a 1 by the timing value.
Write 0 Signal
PRI pulls the link LOW for 1.5T and then HIGH for the remainder of the time slot. On the rising edge SEC determines a 0 by the timing value.
PRI pulls the link LOW for 2.5T and then HIGH for the remainder of the time slot. PRI reads the link 0.5T after the end of the time slot and waits T before further signalling if it detects a presence.
On the PRI rising edge, SEC sets the link to HIGH/LOW for 1/0 bit value for T time.
The reset signal is the start of every transaction.
PRI sets the link LOW for 5T and then sets it HIGH. PRI reads the link T after setting the rising edge to detect the presence of SEC. PRI then waits 1.5T before starting further signaling if SEC was detected.
On seeing the PRI rising edge, SEC sets the link LOW for 1.5T to signal its presence.
The Arduino library (available here) is implements the PRI side of the link as a C++ class MD_OnePin. This has primitives to read, write and reset the link interface (basically, all the signals), and is implemented using documented standard library calls (that is, using no assembler, timer or other hardware specific features). This part is designed to be portable across architectures.
The SEC is implemented as C code that processes the PRI signalling interface. The library’s example SEC implementations detect and time the PRI initiating signal using an Interrupt Service Routine (ISR), setting a flag for the main loop() to do most of the processing.
The majority of the SEC can be written using portable functions, but it is possible that for some applications a more low level approach may be required to implement the same, or similar, logic. In other words, the SEC node is application dependent.
The example SEC use an ISR tied to an external interrupt connected to the PRI digital I/O. However, the same could work off other suitable interrupt types, such as pin change interrupts.
The main loop() uses the flag set in the ISR to implement the remaining protocol response to receive or send data packets. In this way the majority of the processing occurs outside the ISR.
As the two ends of the link both read and write using the same wire, PRI and SEC orchestrate changing the respective I/O pin between INPUT and OUTPUT as the signal timing progresses.
The packet size from PRI to SEC and SEC to PRI can be different sizes, between 1 and 32 bits. PRI to SEC is expected to be larger as this flow may include configuration parameters, command codes, queries, etc. SEC to PRI is likely to consist of status and other ‘lightweight’ information. In any event, the bits per packet for both flows are defined in the MD_OnePin_Protocol.h header file, the class constructor for MD_OnePin() and the SEC code implementation.
At microsecond time intervals, higher level libraries introduce noticeable time delays when switching I/O pins between INPUT/OUTPUT and reading/writing writing to digital outputs. The PRI implementation measures an average value for this in begin() and adjusts timing parameters and delays in the code as appropriate.
Another source of potential issues is the timing gap between packet detection in SEC and the processing of the packet in loop(). For short T slots it is possible that PRI has moved on through the signal before SEC has started its processing. This is especially likely when there is a considerable computing disparity between PRI and SEC (for example, a 16Mhz clock compared to 8MHz).
All protocol message timing is defined in the MD_OnePin_Protocol.h header file. The base time slot T is defined as OPT and all other timing parameters are derived from this. Changing this single constant value is the correct way to fine-tune timing related communications issues.
Debugging two two sides of the link can be a bit tricky. The key point in debugging is to determine the timing interaction between the two sides. This makes any non-trivial debug output interfere with what is being measured. The library and example code uses a separate digital output to mark (via an I/O toggle) when key events happen. This allows the combined Serial signal and the output from PRI and SEC to be captured and analysed using a Digital Analyzer (see this previous post for a low cost option).