DS18B20 - Now now resistor needed!

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

ChemE

Well-Known Member
Joined
Mar 3, 2008
Messages
596
Reaction score
20
Location
Columbia, SC
It has been a very long time since I brewed or posted here but I just recently learned how to eliminate the need for 4.7kOhm resistors when using DS18B20 temperature sensors. Essentially we make some changes to a few OneWire routines to use the pull-up resistors in the microprocessor instead of having to screw around with soldering a resistor across the power and data pins. Below is my latest greatest code for working with these temperature sensors. I've added a routine which lets you change the resolution of the temperature readings from the default 12 bits to 9, 10, or 11 bits. These readings happen faster and are a little less precise but still plenty precise enough for brewing except perhaps the 9 bit readings. Note, every time you change the precision of a DS18B20 you are writing to its EEPROM which is only good for 50,000 writes so make the change you want and then comment that line of code back out and reload the sketch so it doesn't get run every time the board restarts. This code is very low level and VERY compact which leaves plenty of room on the uC for whatever else you may wish to do. The down side is it will be just about impossible for newbies to understand but there is literally nothing that needs changing. This can identify the ROM of any DS18B20 you feed it and start making temperature readings right away; just shove the sensor in Ground/Pin 13/Pin 12 and load the sketch.

Finally, I use conditional compilation to make adding or removing big chunks of code fast and easy. If you don't want to measure how long it takes to perform temperature measurements, set the value of CLOCK to 0; if you do set the value to 1. If you want to see serial output showing you what is going on set DEBUG to 1; if you don't set it to 0. Enjoy!

Code:
// Solder-free method of detecting the ROM of a DS18B20 - spread the 3 legs of the sensor wide enough to fit into GND, 13, and 12 
// and place the sensor in these pins with the flat side facing the LED on the Uno and the round side facing away from the Uno.  
// Then upload and open a serial monitor with a baud rate of 9600.
#include <util/delay.h>

// ====================================================== Pre-Compiler Definitions ====================================================== 
#define DEBUG 1   // Controls the inclusion or exclusion of serial debug information
#define CLOCK 1   // Controls whether or not temperature reading duration is timed

// Direct port manipulation needed to conduct the OneWire bus
#define   PowerPin              PB4                       // Pin 12 - we will be using this pin to supply Vcc to the DS18B20
#define   POWER_TEMP_PROBE      PORTB |= _BV(PowerPin)    // Define method for powering the DB18B20
#define   DEPOWER_TEMP_PROBE    PORTB &= ~_BV(PowerPin)   // Define method for depowering the DB18B20
#define   Pin                   PB5                       // Set up pin 13 as the data pin
#define   DIRECT_MODE_OUTPUT    DDRB |= _BV(Pin)          // Much faster and smaller version of pinMode(Pin, OUTPUT)
#define   DIRECT_MODE_INPUT     DDRB &= ~_BV(Pin)         // Much faster and smaller version of pinMode(Pin, INPUT)
#define   DIRECT_WRITE_HIGH     PORTB |= _BV(Pin)         // Much faster and smaller version of digitalWrite(Pin, HIGH)
#define   DIRECT_WRITE_LOW      PORTB &= ~_BV(Pin)        // Much faster and smaller version of digitalWrite(Pin, LOW)
#define   DIRECT_READ           PINB & _BV(Pin) ? 1 : 0   // One line if else statement using the format [test ? true return : false return]

// Delay values needed for conducting a OneWire bus
#define   clk_div               1                         // This code assumes a processor frequency of 16MHz but this can be lowered as long as clk_div is updated
#define   DELAY_A               6/clk_div                 // Delay values obtained from [url]http://www.maximintegrated.com/app-notes/index.mvp/id/126[/url]
#define   DELAY_B               64/clk_div
#define   DELAY_C               60/clk_div
#define   DELAY_D               10/clk_div
#define   DELAY_E               9/clk_div
#define   DELAY_F               55/clk_div
#define   DELAY_G               0/clk_div
#define   DELAY_H               480/clk_div
#define   DELAY_I               72/clk_div
#define   DELAY_J               410/clk_div

// DS18B20 command codes
#define   READROM               0x33                      // Read the ROM of a OneWire device; there must only be one OneWire device on the bus!
#define   STARTCONVO            0x44                      // Tells device to take a temperature reading and put it on the scratchpad
#define   READSCRATCH           0xBE                      // Read from the scratchpad
#define   WRITESCRATCH          0x4E                      // Write to the scratchpad
#define   COPYSCRATCH           0x48                      // Tells the DS18B20 to copy the contents of the scratchpad to EEPROM
#define   SKIPROM               0xCC                      // Tells all OneWire sensors on the bus that the next command applies to them
#define   MATCHROM              0x55                      // Tells all OneWire sensors on the bus to listen for a specific ROM next

#define   myubbr                (F_CPU/clk_div/16/9600-1) // Baud rate for UART

int main() {  
  bool present = 0;
  uint8_t ROM[8] = { 0x28, 0xA, 0xA3, 0x83, 0x4, 0x0, 0x0, 0x63 };
  
  #if DEBUG
    #if CLOCK
      unsigned long start_time, end_time;
      // Timer 0 initialization from wiring.c for a ATmega 328P (Arduino Uno rev 3) + 12 bytes to sketch size
      TCCR0A = _BV(WGM01) | _BV(WGM00);      // set timer 0 prescale factor to 64
      TCCR0B = _BV(CS01) | _BV(CS00);        // set timer 0 prescale factor to 64
      TIMSK0 = _BV(TOIE0);                 // enable timer 0 overflow interrupt
    #endif
    
    // Initialize the UART
    UBRR0H = (unsigned char)(myubbr>>8);
    UBRR0L = (unsigned char)myubbr;
    UCSR0A = 0;//Disable U2X mode
    UCSR0B = (1<<TXEN0);//Enable transmitter
    UCSR0C = (3<<UCSZ00);//N81
    _delay_ms(100);
  #endif
  
  DDRB = B00010000;      // Set Pin 12 as an output
  POWER_TEMP_PROBE;      // This isn't neccesary but I show it to demonstrate how to make the project conserve energy
  
  // Set the sensor's resolution to 11 bits
  //SetResolution(11);
  
  for(;;) {  // Loop forever
    // Perform a OneWire reset pulse and see if we detect a presence pulse afterward
    present = reset();
    
    // If a one-wire device is present, attempt to read its ROM
    if (present) {
      write(READROM);
      for(uint8_t i=0;i<8;i++) {
        ROM[i]=read();
      }
    }
    
    #if DEBUG
      simpletx("Presence pulse: ");
      if(present) {
        simpletx("Detected");
      } else {
        simpletx("Not Detected");
      }
      
      simpletx("\tROM is: ");
      for(uint8_t i=0;i<8;i++) {
        simpletx("0x");
        txByteAsHex(ROM[i]);
        if (i!=7) simpletx(",");
      }
      simpletx("\t\t");
    #endif
    
    
    // If we detected a Dallas family sensor, let's go ahead and take a temperature reading
    if (ROM[0]=0x28) {  // The first byte of all dallas sensors is always 0x28
      reset();
      write(SKIPROM);
      write(STARTCONVO);
      
      #if CLOCK
        start_time = millis();
      #endif
      while(!read());  //_delay_ms(750);    // Can either wait 750 ms for the conversion to be done or else read until we get a 1 back from the DS18B20 meaning it is signaling complete
      #if CLOCK
        end_time = millis();
      #endif
      
      reset();
      write(SKIPROM);
      write(READSCRATCH);
      uint8_t tempLSB = read();
      uint8_t tempMSB = read();
      
      #if DEBUG
        simpletx("Temperature: ");
        txRawTempAsFloat( tempMSB<<8 | tempLSB );
        simpletx("F");
        
        #if CLOCK
          simpletx("\tConversion took "); 
          txInt(end_time-start_time);
          simpletx(" ms");
        #endif
        simpletx("\n");
      #endif
    }
    //DEPOWER_TEMP_PROBE;  // Rather than perform a reset to tell the probe to stop sending data, just cut the power and it will get the message!
    _delay_ms(5000);
  }  // End for
}  // End main

// ============================================================================================================================================================
// Sets the temperature measurement resolution of the DS18B20 to either 9, 10, 11, or 12 bits  If any other number is passed, the sensor will be set to 12 bits
// Only works if there is a single DS18B20 on the one wire network
// ============================================================================================================================================================
static inline void SetResolution(uint8_t resolution) {
  reset();
  write(SKIPROM);
  write(WRITESCRATCH);
  write(0x00);
  write(0x00);
  switch (resolution) {
    case 9: write(0x1F);  break;
    case 10: write(0x3F); break;
    case 11: write(0x5F); break;
    default: write(0x7F); break;
  }
  reset();
  write(SKIPROM);
  write(COPYSCRATCH);
  //_delay_ums(15);
}


static inline uint8_t read() {
  uint8_t r=0;
      
  noInterrupts();
  for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
    DIRECT_MODE_OUTPUT;
    DIRECT_WRITE_LOW;
    _delay_us(DELAY_A);
    DIRECT_MODE_INPUT;
    DIRECT_WRITE_HIGH;  // New line for no resistor modification / enable pull-up resistor
    _delay_us(DELAY_E);
    if (DIRECT_READ) r |= bitMask;
    _delay_us(DELAY_F);
  }
  interrupts();
  return r;
}

static inline void write(uint8_t v) {
  noInterrupts();
  for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
    DIRECT_WRITE_LOW;
    DIRECT_MODE_OUTPUT;
    if (bitMask & v) {
      _delay_us(DELAY_A);
      DIRECT_WRITE_HIGH;
      _delay_us(DELAY_B);
    } else {
      _delay_us(DELAY_C);
      DIRECT_WRITE_HIGH;
      _delay_us(DELAY_D);
    }
  }
  DIRECT_MODE_INPUT;
  interrupts();
}

static inline uint8_t reset(void) {
  noInterrupts();
  DIRECT_MODE_INPUT;      
  DIRECT_WRITE_LOW;
  DIRECT_MODE_OUTPUT;
  _delay_us(DELAY_H);
  DIRECT_MODE_INPUT;
  DIRECT_WRITE_HIGH;  // New line for no resistor modification / enable pull-up resistor
  _delay_us(DELAY_I);
  uint8_t ret = !(DIRECT_READ);
  interrupts();
  _delay_us(DELAY_J);
  return ret;
}

static inline void simpletx( char * string ) {
  /*if (UCSR0B != (1<<TXEN0)) { //do we need to init the uart?
    UBRR0H = (unsigned char)(myubbr>>8);
    UBRR0L = (unsigned char)myubbr;
    UCSR0A = 0;//Disable U2X mode
    UCSR0B = (1<<TXEN0);//Enable transmitter
    UCSR0C = (3<<UCSZ00);//N81
    _delay_ms(30);
  }*/
  while (*string) {
    while ( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = *string++; //send the data
  }
}

static inline void txByteAsHex(uint8_t inp) {
  char snd[3];
  uint8_t tmp = inp>>4;
  
  if (tmp<10) {
    snd[0]=48+tmp;
  } else {
    snd[0]=55+tmp;
  }
  
  tmp=inp%16;
  if (tmp<10) {
    snd[1]=48+tmp;
  } else {
    snd[1]=55+tmp;
  }
  snd[2]='\0';
  simpletx(snd);
}

static inline void txInt(long inp) {
  long temp = inp;
  uint8_t numChars=0;
  boolean isNegative=false;
  
  // Check to see if there is a negative sign
  if(temp<0){
    isNegative=true;
    numChars++;
    temp*=-1;
  }
  
  do {
    numChars++;
    temp /= 10;
  } while ( temp );
  char buf[numChars];
  
  // Write the negative sign if present and the terminating null character
  temp=inp;
  buf[numChars]=0;
  if(isNegative) {
    temp*=-1;
    buf[0]='-';
  }
  
  int i = numChars - 1;
  do {
      buf[i--] = temp%10 + '0';
      temp /= 10;
  } while (temp);
  
  simpletx(buf);
}

// Converts the raw temperature from a DS18B20 directly to a string containing the temperature in °F with 1 decimal place
// avoids unnecessary floating point math, float variables, and casts, and 32-bit math
// TODO: May not work properly with temperatures below 32°F
static inline void txRawTempAsFloat(uint16_t raw) {  
  char buffer[6];

  uint8_t decimalPos = 2;  // default case of a temp between 0 and 99.9
  uint8_t nullPos = 4;     // default case of a temp between 0 and 99.9
  uint16_t temp;
  
  // Check to see if the temperature passed in is negative
  if (raw>>11) {
    // Can't get here unless one of the 5 most-significant bits are ones which means we have a negative number, convert it
    raw = ~(raw-1);    // Convert the two's compliment number back into one's compliment
    
    if (raw > 284) {  // This temperature is far enough negative in the celcius scale that it is also negative on the farhenheit scale
      decimalPos += 1;   // Account for the negative sign's place in the string
      nullPos += 1;      // Account for the negative sign's place in the string
      buffer[0] = '-';   // Write the negative sign in the string
    }
    temp = (9*raw)/8-320;                   // Keeps only 1 decimal place but uses 16-bit math
  } else {
    temp = (9*raw)/8+320;                   // Keeps only 1 decimal place but uses 16-bit math
  }


  // Convert the raw temperature into the temperature in Fx10 so that one decimal place is kept
  //uint32_t temp = (raw*1125ul+320000ul)/1000ul;  // Keeps all 4 decimal places but uses 32-bit math  
  if(temp>=1000) {  // We're looking at a positive number with three digits
    decimalPos += 1;
    nullPos += 1;
  }
  
  buffer[nullPos--] = '\0';
  do {
    if (nullPos==decimalPos) buffer[nullPos--] = '.';
    buffer[nullPos--] = temp % 10 + '0';
    temp /= 10;
  } while (temp);  
  
  simpletx(buffer);
}
 
If an admin sees this would you please change the name of this thread to "DS18B20 - Now No Resistor Needed" and delete this post. Thanks!
 
Very interesting, curious if this is something Elco could fit into the Brewpi AVR code....
 
I think that is correct unless other Dallas temperature sensors have the same commands and output their results in the same way. What else were you hoping that it may support?
 
You misunderstand the question, so let me rephrase it: if I connect five ds18b20 sensors in parallel and run this code, will all five sensors work?

Cheers!
 
Oh no, not at all. I use the SKIPROM command here which assumes there is only one sensor on the OneWire bus. But, it seems like it would be pretty fun to extend this so it did what you are asking. Let me work on it tomorrow and I'll post something back later this weekend.
 
Back
Top