AVR One Wire Implementation
From Wiki
[edit]
Implementing the Dallas Semiconductor one wire (MicroLan) interface on an Atmel AVR AT90S2313.
[edit]
Things you'll need before you start
- An accurate udelay implementation. Please see my udelay page for information about how to do this, with source code examples.
- 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.
- Some one wire parts. I used a DS1822 one wire temperature sensor. I got mine from DigiKey.com
- 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.
- 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.
- 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.
- A PC running Linux or Windows with some avr compilers. I used RedHat Linux 8.0 with avr-gcc.
[edit]
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);
}
}
