ChemE
Well-Known Member
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!
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);
}