AVR One Wire Implementation

From Wiki

Jump to: navigation, search


Implementing the Dallas Semiconductor one wire (MicroLan) interface on an Atmel AVR AT90S2313.

Things you'll need before you start

  1. An accurate udelay implementation. Please see my udelay page for information about how to do this, with source code examples.
  2. A good Oscilliscope. I have a Tektronix TDS320 that I got used on for about $250. Trying to figure out if your 15us delay is really 15us is extremely difficult without one.
  3. Some one wire parts. I used a DS1822 one wire temperature sensor. I got mine from DigiKey.com
  4. An AVR, hopefully on a development board like the STK500. Again, go to DigiKey for the parts. I used a AT90S2313, running on a 6Mhz clock.
  5. Read up on the one wire specs Here's a link for the PDF datasheet for the DS1822, which has a good description of the protocol, including some nice diagrams.
  6. A Proto-board to hook up the DS1822 to the AVR. Please note that the DS1822 and DS1820 are not binary compatible. The code below is for a DS1822 (budget) temperature sensor. There is also some code for the DS1820 chip. See the references from Dallas Semiconductor for more details about the differences between these chips.
  7. A PC running Linux or Windows with some avr compilers. I used RedHat Linux 8.0 with avr-gcc.

Writing the code

Okay, now that you've got all that done, its pretty easy! Here's the source code for a program that reads the temperature from a single DS1822, and displays the current temperature, in binary, on PORTB. Please note that the I've hooked up the DS1822 to pin 3 of port D. This is easy to change in the source code though. Here's a link to the complete source code example.. After getting this all to work, it was pretty easy to add some serial logging, to make a serial temperature logger.

NOTE: This code uses the old cbi(), sbi() style of bit access, and will need to be updated to conform to the new style pin access implemented in avr-libc

void reset_onewire() {
    // set D0 to output
    sbi(DDRD, PD3);
    sbi(PORTD, PD3);
    cbi(PORTD, PD3);
    udelay(550);
    // set D3 back to input
    cbi(DDRD, PD3);
    udelay(500);
}

void write_onewire(uint8_t bit) {
    // set D3 to output
    sbi(DDRD, PD3);
    sbi(PORTD, PD3);
    if (bit == 0) {
        cbi(PORTD, PD3);
        udelay(65); // hold low for at least 60us
        sbi(PORTD, PD3);
    } else { // bit == 1
        cbi(PORTD, PD3);
        udelay(10); // < 15us for 1 bit
        sbi(PORTD, PD3);
    }
    cbi(DDRD, PD3); // set D0 to input
    if (bit == 1)
        udelay(60); // just to pad out the rest of the slot to >60us
}

uint8_t read_onewire() {
    uint8_t result;
    // set D3 to output
    sbi(DDRD, PD3);
    sbi(PORTD, PD3);
    // hold D3 low for just greater than 1us.  Note: Only for 6Mhz crystals
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);
    cbi(PORTD, PD3);

    cbi(DDRD, PD3); // get ready to read input
    udelay(10); // wait for thermo to respond (<15us)
    result = (inp(PIND) & (1<<PD3)); // PD3 = 1st pin
    result = result ? 1 : 0; // always return either 1 or 0
    udelay(60); // make sure the slot is >60us long
    return result;
}

void write_onewire_byte(uint8_t command) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        write_onewire(command & 1);
        command >>= 1;
    }
}

uint8_t read_onewire_byte() {
    uint8_t r,i;
    r = 0;
    for (i = 0; i < 8; i++) {
        r |= (read_onewire() << (i));
    }
    return r;
}

void convert_temp() {
    reset_onewire();
    write_onewire_byte(0xCC); // skip ROM command
    write_onewire_byte(0x44); // convert T
}

uint16_t read_temp() {
    uint8_t scratchpad[9];
    uint8_t i;
    for (i = 0; i < 9; i++) scratchpad[i] = 0;
    reset_onewire();
    write_onewire_byte(0xCC); // skip ROM
    write_onewire_byte(0xBE); // read scratchpad (9 bytes)
    for (i = 0; i < 9; i++) {
        scratchpad[i] = read_onewire_byte();
    }
    // Just return the temp. in Celcius from the scratchpad.
    return (scratchpad[1] << 8) | scratchpad[0];
}



int main(void) {
    wdt_disable();

    outb (0xFF,DDRB); /* PORTB is all outbut */
    outb (0x00,DDRD); /* PORTD is all input */
    outb (~0x00, PORTB);

    uint8_t scratchpad[9];
    uint8_t i;

    for (i = 0; i < 9; i++) scratchpad[i] = 0;

    while (1) {
        uint16_t celcius;
        uint16_t fahrenheit;

        convert_temp();
        celcius = read_temp();
        fahrenheit = ((celcius * 9) / 5) + (32 << 4);
        outb(~((fahrenheit>>4)&0xFF), PORTB);
    }
}
Personal tools