Connect Single-line Devices Via UART
The 1-Wire® bus was devised by chip maker Dallas Semiconductor as a simple mechanism for connecting chips to basic sensor devices, such as thermometers. Multiple devices can be connected in sequence, many of them drawing what little power they need ‘parasitically’ from the bus itself. As such, many 1-Wire devices need only two pins or contacts: one for data, the other for Ground, which is the reference voltage.
This low-power operation makes 1-Wire sensors attractive to developers of imp-enabled devices, as does the need for just one pin for data. Though the imp doesn’t support 1-Wire communications directly, it can nonetheless play host to 1-Wire devices by emulating the bus.
Two techniques are available to enable a 1-Wire bus on the imp. The first is ‘bit-banging’, a process which involves using a single GPIO pin driven by software which generates the correct signalling. This can be hard to implement, however, because of the need to manage precise timings.
The second approach is much more straightforward: adapt the imp’s existing UART serial bus to behave as if it were a 1-Wire system. This takes advantage of UART’s own signalling and timing capabilities. The downside is that, because UART is a two-wire bus, this technique ties up two of the imp’s pins rather than one. However, because of its simplicity, this is the best approach for developers new to 1-Wire, and it is the approach detailed here.
1-Wire is a single-line, bi-directional half-duplex serial bus to which up to 63 peripheral devices can be connected to a single host. All transactions are initiated and controlled by the host, which doesn’t need to know what devices it may be connected to ahead of time. Instead, device discovery is an integral part of the 1-Wire architecture.
1-Wire communications take place in three phases: bus reset, device selection and data exchange. These are called the Reset, ROM Command and Function Command phases in 1-Wire documentation. The first brings all connected devices to a known, ready state. The second sees the host issue commands (see table below) to search the bus for a specific device: it iterates through devices’ unique IDs to find the one it wants, to which it then issues control and data-request commands — this is the third stage. If the host needs to communicate with a different 1-Wire device, it resets the bus and repeats the search process.
||Ignore device ID(s)
||Intended to be used if there is only one device
on the bus. However, it can also be used to send
subsequent commands to all devices
|Read ROM||0x33||Read a device’s ID||Used when there is only one device on the bus|
|Search ROM||0xF0||Begin enumerating IDs|
||Select a device with a
|Next 64 bits to be written will be the known ID
The host resets the 1-Wire bus by holding the line low for at least 480µs. It then lets go of the line, which, thanks to a pull-up resistor, goes high. It is now said to be in read mode. Some 15-60µs later, the devices simultaneously pull the line low for 60-240µs to signal their presence. By reading the line during this time, the host knows there is at least one device out there, but not how many there are in total. All the devices now release the line and are considered synchronized.
The host can now initiate a search to locate a specific device on the bus and, when it has done so, issue commands and read back data. We’ll look at this process in a moment, but first let’s examine the basic signalling.
All 1-Wire communications are based on specific time periods, called ‘time slots’. To write a bit, the host pulls the line low. A device will detect this, wait a specific period (15µs) then sample the line. If the host wants to write a 1 it releases the line quickly; the device sees the line is high and reads this as a 1. If the host holds the line low long enough, the device detects this and reads a 0.
To read the line, the host briefly pulls the line low. The device detects this and either allows the host to release the line back to high, or holds the line low itself. 15µs after pulling the line low, the host samples the line, reading high as 1 and low as 0.
Conveniently, UART’s 9600 Baud and 115,200 Baud rates deliver the read and write time slots used by 1-Wire during, respectively, its reset and data exchange phases, the latter in high-speed mode. The UART’s other settings are the same in both modes: eight bits per word, no parity and one stop bit.
Because 1-Wire involves checking whether the line is high or low after a set time, individual bits are signalled by sending out maximum and minimum values on the UART:
0x00, respectively. This generates signalling which the 1-Wire device can successfully interpret.
To explore 1-Wire connectivity on an imp, we’ll use Maxim Integrated’s DS18B20 temperature sensor. Maxim acquired Dallas Semiconductor in 2001. The imp will be the bus’ host; the DS18B20 will be the bus’ sole device. The DS18B20 has a single wire for data (DQ) which should be connected to two of an imp’s UART pins. In the following example, we’ll use an imp001 and its UART hardware.uart12, with pin 1 for transmission (TX) and pin 2 for reception (RX). However, you can use any imp-based hardware that exposes RX and TX UART pins.
Wiring up the imp (top) to the DS18B20 (bottom). In between: a 4.7kΩ pull-up resistor
and an 1N5815 Schottky diode (note the positioning of the polarity marker)
The UART TX pin needs to be connected to DQ through a device such as a Schottky diode which acts as an open-drain buffer. An imp’s UART TX pins are not open drain devices, but 1-Wire expects them to be, hence the need to add the buffer. UART RX connects to DQ directly.
The DS18B20’s reference Ground pin connects directly to one of the imp’s GND pins; the sensor’s VDD pin is wired to the imp’s 3V3 output. This power pin is also connected to DQ, RX and TX through a 4.7kΩ pull-up resistor to keep the line at logic high — the devices will pull the voltage low to exchange data.
The function onewireReset() switches the imp’s UART bus to 9600 Baud and writes the standard value
0xF0 to initiate 1-Wire’s reset and device detection signal. Devices should respond with any of a range of values, but reading the value
0xF0 indicates for certain that there are no devices on the bus. Any value other than
-1 (no data at all on the UART, usually a sign that there is no 1-Wire compatible bus present) prompts the function to raise the UART speed to 115,200 Baud, the 1-Wire high-speed data exchange rate.
The uart.flush() imp API call ensures that the code’s subsequent attempt to read the UART doesn’t complete before the write process has finished. This is necessary because though RX and TX are connected, the API read and write calls are buffered and will take a finite time to complete. The flush operation guarantees that all the transmitted data has entered the receive buffer before the program tries to read from the buffer.
1-Wire’s presence detection phase can be used to signal the absence of 1-Wire devices
1-Wire data transfer occurs at the bit level, but for convenience the code includes two functions, onewireWriteByte() and onewireReadByte(), which take and return eight-bit values. onewireWriteByte() serialises the byte bit by bit; onewireReadByte() reverses the process.
Both call the function onewireBit(), which uses UART to send the values
0x00 out on the wire depending on whether the transmitted bit is set or unset. Sending
0xFF over UART generates the correctly timed signal for a 1-Wire logic 1,
0x00 for 0.
Thanks to the way 1-Wire is timed — once again, we include the uart.flush() imp API call to make sure the byte is written by the UART before we read the bus — the function can immediately read the value on the UART bus, this time converting a detected
0x00 onto bit values 1 and 0, respectively. 1-Wire serializes bytes LSB first, MSB last.
With the sensor in place, we can begin to read the ambient temperature
These functions are called by awakeAndGetTemp(), which — because we know there is only one device likely to be connected — skips the multi-device search process and simply wakes the BS18B20, tells it to read the temperature and, after the 750mS that the device requires to sample the temperature, put the value in the sensor’s scratchpad RAM. The function then reads that value: the sensor writes the contents of the scratchpad to the bus. We’re only interested in the first two bytes so, once they have been read, the function resets the bus to end transmission.
The function calls itself every 30 seconds to get the latest reading.
Each 1-Wire device has a unique, unalterable and factory-programmed 64-bit ID number which serves as its address on the 1-Wire bus. The ID comprises an 8-bit ‘family’ ID, a 48-bit serial number and an 8-bit CRC checksum. This ID is used to locate a specific device on the bus: the host searches through every device’s ID by comparing the bits that make up those IDs, LSB first.
All the devices with a 0 at a certain digit in their ID can be separated from those with a 1 at that point. In the standard 1-Wire search algorithm, if we write a 0, say, all devices with an address that has a 1 at the current digit will ignore any future bus activity until the bus is reset.
Now the remaining devices are compared, this time using the next bit in sequence. Again, only those devices that share the value of that bit are kept in play; the rest join those already offline. The process repeats until the host is left with a single device. Any remaining bits in the ID are read to provide the host with that device’s full ID.
With one device’s ID known, this value can be saved, and the host repeats the search pattern as many times as necessary to find the IDs of all the remaining devices.
This device code builds on the earlier version with a couple of new functions, onewireDevices() and onewireSearch(). Together, these functions follow the search pattern outlined above. All the 1-Wire devices connected to the imp yield their 64-bit IDs, which are stored in an array, peripherals. For convenience, each ID is stored not as a 64-bit value but as an array of eight eight-bit integers.
With the device(s) listed this way, taking the temperature is just a matter of running through each of the saved devices and, if one is of the correct type — the DS18B20’s family code is
0x28 — its address is sent out over the bus immediately after the Match ROM command has been issued. Match ROM tells all the devices that the next 64 bits they read will be the ID of device the host wants to hear from. All the rest stop communicating until the reset signal is sent.
Finally, the function commands the nominated device to send back its current temperature reading, generated with a suitable command. The function reads the values now placed on the bus, uses them to calculate a Celsius value and logs it.
The approach outlined above provides an easy way to hook up one or more 1-Wire devices to any imp host. It’s worth noting, however, that it won’t suit all circumstances. While true 1-Wire buses are able to support up to 63 devices, this emulation of 1-Wire will in practice support rather fewer devices than that. Neither is it really suited to set-ups which require long wires running between the sensor(s) and the imp board.
In these instances, the raised capacitance of the bus will mean you won’t be able to apply the simple hack of using a pull-up resistor to keep the bus line high when idle. For such situations, a better option is to make use not of your imp’s UART facilities but its I²C support, to connect a dedicated 1-Wire bridge chip, such as Maxim’s own DS2482. Crucially, this chip has an active pull-up able to adjust to the capacitance of the bus.
Since this article was originally posted, we have refined the code described and released it as library, available to include in your own device code. You can find a list of the library’s methods and functionality here.
Electric Imp would like to thank Forum member Theo Habers (@theo) for his original code, which proved invaluable in interpreting the 1-Wire bus for the imp.