1-Wire implementation for AVR
Brief tutorial how to add 1-Wire devices to your MCU.
Introduction
The 1-Wire protocol developed by Dallas Semiconductor (now Maxim Integrated) allows numerous sensors or peripheral components to be connected to a master via two or three lines. The special feature of this interface is that the data line (DQ) can be used simultaneously for the power supply (parasitic power supply), whereby the bus can work with only two lines:
The data line DQ
A Ground connection
If the data line is connected to the power supply via a pull-up resistor, bus lengths of 100 - 300 m are possible, depending on the cable used.
Let's take a look at how a 1-wire bus can be implemented on an AVR microcontroller (here an XMega256A3BU). Then let´s add a DS18B20 1-Wire temperature sensor as a 1-Wire device so we can test out the implementation.
1-Wire basics
The 1-Wire bus does not use a separate clock line for the data signals. Therefore, similar to the USART, a given timing has to be adhered to so that a functioning communication can take place. The 1-Wire has only one master at a time and the data line is provided with a weak pull-up resistor (usually a voltage of 3V or 5V is used).
The data line is designed as a bidirectional open-drain I / O port on a slave device, allowing both master and slaves to send data. The use of an open-drain driver for the data line creates a wired AND connection, which means that the master can only communicate with a single slave at a time. Also the master must also be able to generate a delay of 1 μs or 0.25 μs for the so-called overdrive mode, a fast mode for a higher data transfer rate of up to 142 Kbps.
A 1-Wire communication consists of four basic operations:
Transmit a logical 1
Transmit a logical 0
Read one bit
Perform a Reset
For every data transmission initiated by the master (regardless of whether reading or writing), a reset pulse is triggered first. Each connected slave outputs a so-called presence pulse at the end of the reset by pulling the data line low. This presence pulse signals to the master that a device is connected to the bus. If no pulse occur, no device is connected. After a reset, a general ROM command is sent to either identify the bus users or select a bus user. As soon as the participant has been selected, a device-specific command is issued.
Each of the basic operations consists of a fixed sequence of high / low levels and must also correspond to specific time specifications.
This results in the following sequence:
Parameter
Delay time (Standard) [µs]
Delay time (Overdrive) [µs]
A
6
1,0
B
64
7,5
C
60
7,5
D
10
2,5
E
9
1,0
F
55
7
G
0
2,5
H
480
70
I
70
8,5
J
410
40
Let´s take a look at the implementation of the driver.
Implementation of the 1-Wire driver
For the implementation I use my XMEGA-A3BU Xplained development board and as slaves a DS18B20 1-Wire temperature sensor. The implementation should be realized in general, so that the final driver can be used on different plattforms like the ATmega32 or the Raspberry Pi.
In the first step, the data line DQ, in this example the pin 0 of port E, must be initialized. The I / O must be switched as an output and set to high state to put a high level on the bus.
I use the internal pull-up resistor of the microcontroller as the required resistor for the 1-Wire bus.
Depending on the microcontroller used, the pull-up resistors are switched on differently. This line must be adapted accordingly to the microcontroller used.
After initialization, I give the bus participants some time to process the internal initialization. Then I perform a complete reset of all bus participants:
The structure of the reset function results from the timings of the specification and the sequence of a 1-wire reset:
In order to avoid timing problems due to interrupts, the SREG is stored immediately after the function call and the global interrupts are deactivated. After delay time I, DQ is switched as input and the bus status is read. If the bus is not low, the reset has failed and an appropriate error message is returned.
Analogous to the reset function, the functions for sending a bit / byte
or to read a bit / byte:
This completes the initialization of the bus and the functions for writing or reading the bus have been created. The master can now start to get the addresses of the connected bus subscribers so he can address individual bus subscribers and start to communicate with them.
A transmission via 1-wire strictly follows the same procedure and a wrong sequence automatically leads to a transmission error:
Initialize the bus participants with a reset
Transmit the ROM command (1-Wire specific command)
Transmit the function command (device specific command)
The individual bus subscribers can then be addressed via the addresses. Each 1-Wire slave has a 64-bit ROM which identifies the individual slaves and which is used by the master to address individual devices. This ROM includes
A 8 bit CRC (Bit [64:56])
A 48 bit serial number (Bit [55:8])
A 8 bit family code (Bit [7:0])
Two methods are provided to allow the master to access the information in the ROM:
Search ROM command (code 0xF0): A search algorithm that identifies the individual users of the bus and determines the user addresses.
Read ROM command (code 0x33): A simplier requests for the identification of a single bus participant. Significantly easier than Search ROM, but works only with a single bus participant.
The third point in the transmission differs depending on the family of the addressed target device and is not needed for the determination of the ROM codes.
Thus, if the ROM code of a single bus user is to be requested, the master must perform a reset, send the Read ROM command and then read the information from the ROM:
Where OneWire_ROM_t is a structure to store the information from the ROM:
The master is now able to identify a single 1-wire device on the bus. Here with a DS18B20 temperature sensor as an example:
If there are several (even different) subscribers on the bus, the Search ROM command must be used to identify the individual subscribers. How the search algorithm works is explained in Maxim Integrated in application note 187.
For the implementation of the search algorithm in the 1-Wire driver, I use a customized version of Maxim Integrated's reference implementation to meet my requirements:
Some 1-wire peripherals (such as the DS18B20) have the ability to switch to an alarm state through certain events. The 1-Wire protocol offers the possibility to search all devices with the alarm flag set. The search is carried out in the same way as the ROM search, except that the search for alarm devices with the command code 0xEC is started instead of 0xF0.
A complete search consists of four different functions:
Function
Description
OneWire_StartSearch
Initializes the algorithm, starts the search, and finds the first participant
OneWire_IsLast
Checks if the last participant was found
OneWire_SearchNext
Search the next participant
OneWire_StopSearch
Exit the search algorithm
After each byte received, a CRC is calculated to detect transmission errors. Since the calculation of the CRC 1-Wire is specific, each 1-Wire device uses the same CRC polynomial. Therefore, as in the reference implementation, the calculation of the CRC can be done via a fixed lookup table.
A complete search can be implemented as follows (again with a DS18B20 temperature sensor):
The search will only save participants who have the correct ID code (0x28). The search returns the number of DS18B20 temperature sensors found and stores the ROM codes in an array.
Since a microcontroller (usually) is a system with a static memory allocation, the size of the ROM array must be known. So you must know how many nodes are connected to the bus or how many nodes are searched for should.
The code to detect all connected DS18B20 sensors looks like this:
At the end of the ROM scan, the master knows the ROM codes of all bus subscribers and can begin to communicate with the individual subscribers.
As a rule, the master only communicates with one participant at a time. There are some exceptions that allow the master to send a command to all users (for example, to start a temperature measurement on a DS18B20), but no communication from slave to master can only be done one at a time. At the beginning of communication, the master must select the target device. The determined ROM codes are used for this purpose.
To do this, the master sends a match ROM command (0x55) followed by the ROM code followed by the device-specific command. The selection of the bus participant can be implemented as follows:
The function expects a pointer to the corresponding ROM code as the transfer value. If the address of the pointer is NULL, i. e. no ROM structure has been transferred, a skip ROM command is sent to address all bus users. If an address has been transferred, the match ROM instruction and the eight bytes of the ROM code are transmitted.
The target device is then selected and can subsequently be operated by the master with a function code. This function code (and possibly a few additional data bytes) are transmitted by the master one after the other. If the master wants to read the target device, it first sends the corresponding function code and then reads in the required number of data bytes. Here's a short example to write the scratchpad, the clipboard, of a DS18B20:
This completes the 1-wire driver and provides basic functionality to communicate with 1-wire devices.
The final 1-Wire driver, including an example implementation for DS18B20 temperature sensors for a XMega256A3BU, can be found in my GitLab repository.
Last updated