Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
DS18B20 1-Wire implementation for Atmel AVR

AVR C 1-Wire + DS18B20 temperature sensor

This implements Maxim's One Wire / 1-Wire protocol for AVR microcontrollers from the DS18B20 datasheet and Maxim's other application notes.

Usage

Single DS18B20 device on bus

With just one DS18B20 on the 1-Wire bus, you can ignore slave addresses:

#include "pindef.h"
#include "onewire.h"
#include "ds18b20.h"

// ...

// This is a hacked together interface to pass around a port/pin combination by reference
// Everything in pindef.h compiles to quite inefficient asm currently.
const gpin_t sensorPin = { &PORTD, &PIND, &DDRD, PD3 };

// Send a reset pulse. If we get a reply, read the sensor
if (onewire_reset(&sensorPin)) {

    // Not passing a specific address
    onewire_skiprom(&sensorPin);
    
    // Start a temperature reading
    ds18b20_convert(&sensorPin);
    
    // Wait for measurement to finish (750ms for 12-bit value)
    _delay_ms(750);
    
    // Get the raw 2-byte temperature reading
    int16_t reading = ds18b20_read_single(&sensorPin);
    
    if (reading != kDS18B20_CrcCheckFailed) {
        // Convert to floating point (or keep as a Q12.4 fixed point value)
        float temperature = ((float) reading) / 16;
    } else {
        // Handle bad temperature reading CRC 
        // The datasheet suggests to just try reading again
    }
}

Multiple devices on the bus

With more than one DS18B20 on the bus, you'll need to query the unique 64-bit address of each device and address them directly when sending other commands.

#include "onewire.h"

// ...

// Prepare a new device search
onewire_search_state search;
onewire_search_init(&search);

// Search and dump temperatures until we stop finding devices
while (onewire_search(&sensorPin, &search)) {
    if (!onewire_check_rom_crc(&search)) {
        // Handle ROM CRC check failed
        continue;
    }

    // Do something with the address
    // eg. memcpy(addresses[slaveIndex], search.address, 8);
}

To send commands to a specific slave, use onewire_match_rom instead of onewire_skiprom:

onewire_match_rom(&sensorPin, address);

Search ROM implementation

I found the Search ROM flowchart in Maxim's datasheets and application notes a little hard to follow. Below is a diagram I threw together to maintain sanity implementing this. It goes with p51-54 from Maxim's application note 937: Book of iButton® Standards.

Also see application note 187: 1-Wire Search Algorithm. The implementation described there adds additional complexity/functionality to the search process, but also includes a (fairly verbose) implementation in C.

#include "crc.h"
// AVR
#include <util/crc16.h>
uint8_t crc8(uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
for (uint8_t i = 0; i < len; ++i) {
crc = _crc_ibutton_update(crc, data[i]);
}
return crc;
}
// C
#include <stdint.h>
/**
* Calculate the CRC8 for an array of bytes
*
* This uses the polynomial (x^8 + x^5 + x^4 + 1)
*
* @returns the computed CRC8
*/
uint8_t crc8(uint8_t* data, uint8_t len);
#include "ds18b20.h"
#include "crc.h"
#include "onewire.h"
// AVR
#include <util/delay.h>
// Command bytes
static const uint8_t kConvertCommand = 0x44;
static const uint8_t kReadScatchPad = 0xBE;
// Scratch pad data indexes
static const uint8_t kScratchPad_tempLSB = 0;
static const uint8_t kScratchPad_tempMSB = 1;
static const uint8_t kScratchPad_crc = 8;
static uint16_t ds18b20_readScratchPad(const gpin_t* io)
{
// Read scratchpad into buffer (LSB byte first)
static const int8_t kScratchPadLength = 9;
uint8_t buffer[kScratchPadLength];
for (int8_t i = 0; i < kScratchPadLength; ++i) {
buffer[i] = onewire_read(io);
}
// Check the CRC (9th byte) against the 8 bytes of data
if (crc8(buffer, 8) != buffer[kScratchPad_crc]) {
return kDS18B20_CrcCheckFailed;
}
// Return the raw 9 to 12-bit temperature value
return (buffer[kScratchPad_tempMSB] << 8) | buffer[kScratchPad_tempLSB];
}
uint16_t ds18b20_read_single(const gpin_t* io)
{
// Confirm the device is still alive. Abort if no reply
if (!onewire_reset(io)) {
return kDS18B20_DeviceNotFound;
}
// Reading a single device, so skip sending a device address
onewire_skiprom(io);
onewire_write(io, kReadScatchPad);
// Read the data from the scratch pad
return ds18b20_readScratchPad(io);
}
uint16_t ds18b20_read_slave(const gpin_t* io, uint8_t* address)
{
// Confirm the device is still alive. Abort if no reply
if (!onewire_reset(io)) {
return kDS18B20_DeviceNotFound;
}
onewire_match_rom(io, address);
onewire_write(io, kReadScatchPad);
// Read the data from the scratch pad
return ds18b20_readScratchPad(io);
}
void ds18b20_convert(const gpin_t* io)
{
// Send convert command to all devices (this has no response)
onewire_skiprom(io);
onewire_write(io, kConvertCommand);
}
#pragma once
#include "pindef.h"
// C
#include <stdint.h>
// Special return values
static const uint16_t kDS18B20_DeviceNotFound = 0xA800;
static const uint16_t kDS18B20_CrcCheckFailed = 0x5000;
/**
* Trigger all devices on the bus to perform a temperature reading
* This returns immedidately, but callers must wait for conversion on slaves (max 750ms)
*/
void ds18b20_convert(const gpin_t* io);
/**
* Read the last temperature conversion from the only probe on the bus
*
* If there is a single slave on the one-wire bus the temperature data can be
* retrieved without scanning for and targeting device addresses.
*
* Calling this with more than one slave on the bus will cause data collision.
*/
uint16_t ds18b20_read_single(const gpin_t* io);
/**
* Read the last temperature conversion from a specific probe
* Address must be a an array of 8 bytes (uint8_t[8])
*/
uint16_t ds18b20_read_slave(const gpin_t* io, uint8_t* address);
#include "onewire.h"
#include "crc.h"
#include <util/delay.h>
bool onewire_reset(const gpin_t* io)
{
// Configure for output
gset_output_high(io);
gset_output(io);
// Pull low for >480uS (master reset pulse)
gset_output_low(io);
_delay_us(480);
// Configure for input
gset_input_hiz(io);
_delay_us(70);
// Look for the line pulled low by a slave
uint8_t result = gread_bit(io);
// Wait for the presence pulse to finish
// This should be less than 240uS, but the master is expected to stay
// in Rx mode for a minimum of 480uS in total
_delay_us(460);
return result == 0;
}
/**
* Output a Write-0 or Write-1 slot on the One Wire bus
* A Write-1 slot is generated unless the passed value is zero
*/
static void onewire_write_bit(const gpin_t* io, uint8_t bit)
{
if (bit != 0) { // Write high
// Pull low for less than 15uS to write a high
gset_output_low(io);
_delay_us(5);
gset_output_high(io);
// Wait for the rest of the minimum slot time
_delay_us(55);
} else { // Write low
// Pull low for 60 - 120uS to write a low
gset_output_low(io);
_delay_us(55);
// Stop pulling down line
gset_output_high(io);
// Recovery time between slots
_delay_us(5);
}
}
// One Wire timing is based on this Maxim application note
// https://www.maximintegrated.com/en/app-notes/index.mvp/id/126
void onewire_write(const gpin_t* io, uint8_t byte)
{
// Configure for output
gset_output_high(io);
gset_output(io);
for (uint8_t i = 8; i != 0; --i) {
onewire_write_bit(io, byte & 0x1);
// Next bit (LSB first)
byte >>= 1;
}
}
/**
* Generate a read slot on the One Wire bus and return the bit value
* Return 0x0 or 0x1
*/
static uint8_t onewire_read_bit(const gpin_t* io)
{
// Pull the 1-wire bus low for >1uS to generate a read slot
gset_output_low(io);
gset_output(io);
_delay_us(1);
// Configure for reading (releases the line)
gset_input_hiz(io);
// Wait for value to stabilise (bit must be read within 15uS of read slot)
_delay_us(10);
uint8_t result = gread_bit(io) != 0;
// Wait for the end of the read slot
_delay_us(50);
return result;
}
uint8_t onewire_read(const gpin_t* io)
{
uint8_t buffer = 0x0;
// Configure for input
gset_input_hiz(io);
// Read 8 bits (LSB first)
for (uint8_t bit = 0x01; bit; bit <<= 1) {
// Copy read bit to least significant bit of buffer
if (onewire_read_bit(io)) {
buffer |= bit;
}
}
return buffer;
}
void onewire_match_rom(const gpin_t* io, uint8_t* address)
{
// Write Match Rom command on bus
onewire_write(io, 0x55);
// Send the passed address
for (uint8_t i = 0; i < 8; ++i) {
onewire_write(io, address[i]);
}
}
void onewire_skiprom(const gpin_t* io)
{
onewire_write(io, 0xCC);
}
/**
* Search procedure for the next ROM addresses
*
* This algorithm is bit difficult to understand from the diagrams in Maxim's
* datasheets and app notes, though its reasonably straight forward once
* understood. I've used the name "last zero branch" instead of Maxim's name
* "last discrepancy", since it describes how this variable is used.
*
* A device address has 64 bits. With multiple devices on the bus, some bits
* are ambiguous. Each time an ambiguous bit is encountered, a zero is written
* and the position is marked. In subsequent searches at ambiguous bits, a one
* is written at this mark, zeros are written after the mark, and the bit in
* the previous address is copied before the mark. This effectively steps
* through all addresses present on the bus.
*
* For reference, see either of these documents:
*
* - Maxim application note 187: 1-Wire Search Algorithm
* https://www.maximintegrated.com/en/app-notes/index.mvp/id/187
*
* - Maxim application note 937: Book of iButton® Standards (pages 51-54)
* https://www.maximintegrated.com/en/app-notes/index.mvp/id/937
*
* @see onewire_search()
* @returns true if a new address was found
*/
static bool _search_next(const gpin_t* io, onewire_search_state* state)
{
// States of ROM search reads
enum {
kConflict = 0b00,
kZero = 0b10,
kOne = 0b01,
};
// Value to write to the current position
uint8_t bitValue = 0;
// Keep track of the last zero branch within this search
// If this value is not updated, the search is complete
int8_t localLastZeroBranch = -1;
for (int8_t bitPosition = 0; bitPosition < 64; ++bitPosition) {
// Calculate bitPosition as an index in the address array
// This is written as-is for readability. Compilers should reduce this to bit shifts and tests
uint8_t byteIndex = bitPosition / 8;
uint8_t bitIndex = bitPosition % 8;
// Configure bus pin for reading
gset_input_hiz(io);
// Read the current bit and its complement from the bus
uint8_t reading = 0;
reading |= onewire_read_bit(io); // Bit
reading |= onewire_read_bit(io) << 1; // Complement of bit (negated)
switch (reading) {
case kZero:
case kOne:
// Bit was the same on all responding devices: it is a known value
// The first bit is the value we want to write (rather than its complement)
bitValue = (reading & 0x1);
break;
case kConflict:
// Both 0 and 1 were written to the bus
// Use the search state to continue walking through devices
if (bitPosition == state->lastZeroBranch) {
// Current bit is the last position the previous search chose a zero: send one
bitValue = 1;
} else if (bitPosition < state->lastZeroBranch) {
// Before the lastZeroBranch position, repeat the same choices as the previous search
bitValue = state->address[byteIndex] & (1 << bitIndex);
} else {
// Current bit is past the lastZeroBranch in the previous search: send zero
bitValue = 0;
}
// Remember the last branch where a zero was written for the next search
if (bitValue == 0) {
localLastZeroBranch = bitPosition;
}
break;
default:
// If we see "11" there was a problem on the bus (no devices pulled it low)
return false;
}
// Write bit into address
if (bitValue == 0) {
state->address[byteIndex] &= ~(1 << bitIndex);
} else {
state->address[byteIndex] |= (bitValue << bitIndex);
}
// Configure for output
gset_output_high(io);
gset_output(io);
// Write bit to the bus to continue the search
onewire_write_bit(io, bitValue);
}
// If the no branch points were found, mark the search as done.
// Otherwise, mark the last zero branch we found for the next search
if (localLastZeroBranch == -1) {
state->done = true;
} else {
state->lastZeroBranch = localLastZeroBranch;
}
// Read a whole address - return success
return true;
}
static inline bool _search_devices(uint8_t command, const gpin_t* io, onewire_search_state* state)
{
// Bail out if the previous search was the end
if (state->done) {
return false;
}
if (!onewire_reset(io)) {
// No devices present on the bus
return false;
}
onewire_write(io, command);
return _search_next(io, state);
}
bool onewire_search(const gpin_t* io, onewire_search_state* state)
{
// Search with "Search ROM" command
return _search_devices(0xF0, io, state);
}
bool onewire_alarm_search(const gpin_t* io, onewire_search_state* state)
{
// Search with "Alarm Search" command
return _search_devices(0xEC, io, state);
}
bool onewire_check_rom_crc(onewire_search_state* state)
{
// Validate bits 0..56 (bytes 0 - 6) against the CRC in byte 7 (bits 57..63)
return state->address[7] == crc8(state->address, 7);
}
#pragma once
#include "pindef.h"
// C
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/**
* State for the onewire_search function
* This must be initialised with onewire_search_init() before use.
*/
typedef struct onewire_search_state {
// The highest bit position where a bit was ambiguous and a zero was written
int8_t lastZeroBranch;
// Internal flag to indicate if the search is complete
// This flag is set once there are no more branches to search
bool done;
// Discovered 64-bit device address (LSB first)
// After a successful search, this contains the found device address.
// During a search this is overwritten LSB-first with a new address.
uint8_t address[8];
} onewire_search_state;
/**
* Send a reset pulse
*
* Returns true if One Wire devices were detected on the bus
*/
bool onewire_reset(const gpin_t* io);
/**
* Write a byte as described in Maxim's One Wire protocol
*/
void onewire_write(const gpin_t* io, uint8_t byte);
/**
* Read a byte as described in Maxim's One Wire protocol
*/
uint8_t onewire_read(const gpin_t* io);
/**
* Skip sending a device address
*/
void onewire_skiprom(const gpin_t* io);
/**
* Address a specific device
*/
void onewire_match_rom(const gpin_t* io, uint8_t* address);
/**
* Reset a search state for use in a search
*/
inline void onewire_search_init(onewire_search_state* state)
{
state->lastZeroBranch = -1;
state->done = false;
// Zero-fill the address
memset(state->address, 0, sizeof(state->address));
}
/**
* Look for the next slave address on the bus
*
* Before the first search call, the state parameter must be initialised using
* onewire_init_search(state). The same state must be passed to subsequent calls
* to discover all available devices.
*
* The caller is responsible for performing a CRC check on the result if desired.
*
* @returns true if a new address was found
*/
bool onewire_search(const gpin_t* io, onewire_search_state* state);
/**
* Look for the next slave address on the bus with an alarm condition
* @see onewire_search()
*/
bool onewire_alarm_search(const gpin_t* io, onewire_search_state* state);
/**
* Return true if the CRC byte in a ROM address validates
*/
bool onewire_check_rom_crc(onewire_search_state* state);
#include "pindef.h"
void gset_input_pullup(const gpin_t* pin) {
*(pin->ddr) &= ~_BV(pin->bit);
gset_output_high(pin);
}
void gset_input_hiz(const gpin_t* pin) {
*(pin->ddr) &= ~_BV(pin->bit);
gset_output_low(pin);
}
void gset_output(const gpin_t* pin) {
*(pin->ddr) |= _BV(pin->bit);
}
void gset_output_high(const gpin_t* pin) {
*(pin->port) |= _BV(pin->bit);
}
void gset_output_low(const gpin_t* pin) {
*(pin->port) &= ~_BV(pin->bit);
}
void gset_bit(const gpin_t* pin) {
*(pin->port) |= _BV(pin->bit);
}
void gclear_bit(const gpin_t* pin) {
*(pin->port) &= ~_BV(pin->bit);
}
uint8_t gread_bit(const gpin_t* pin) {
return *(pin->pin) & _BV(pin->bit);
}
#pragma once
// AVR
#include <avr/io.h>
// C
#include <stdint.h>
/**
* Generic AVR port pin
*/
typedef struct gpin_t {
// Pointers to PORT and PIN and DDR registers
volatile uint8_t *port;
volatile uint8_t *pin;
volatile uint8_t *ddr;
// Bit number in PORT
uint8_t bit;
} gpin_t;
void gset_input_pullup(const gpin_t* pin);
void gset_input_hiz(const gpin_t* pin);
void gset_output(const gpin_t* pin);
void gset_output_high(const gpin_t* pin);
void gset_output_low(const gpin_t* pin);
void gset_bit(const gpin_t* pin);
void gclear_bit(const gpin_t* pin);
uint8_t gread_bit(const gpin_t* pin);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.