Hello all,
I've been picking apart my code to do some optimization, and an obvious target was the read of 1Wire devices, the DS18B20 in particular. Currently, I use the OneWire library, without the DallasTemperature library, which I didn't much care for.
What all 1Wire read routines have in common is that they issue the Convert command prior to reading a measured value. For the DS18B20, this is the Convert T command, 0x44. Depending on the resolution, the conversion takes from 75-750ms, per the datasheet.
There are three main issues I had with the implementation using OneWire as in the examples:
- There was no provision for setting the resolution. While this is available in the DallasTemperature library, it just didn't make sense to me to rewrite all of my lean code around this library to get this one feature.
- Wait times are hardcoded. Per the datasheet, it is possible to check with the device for status after issuing the Convert T command to know exactly when the temperature conversion is complete. In most implementations, however, this wait time is hard-coded, and typically VERY generously. For example, in a read operation at 12 bits that actually takes <600ms, the hardcoded wait time is 1000ms. This is a 40% savings, ladies and gentlemen. Futhermore, most test code does not even adjust the delay time for different resolution. This is just insane. A ready that should take 60-75ms will implement a wait of 1000ms (?!)
- Everything is done synchronously. Nothing else can happen while we are waiting for this conversion This is just stupid and lazy coding, and the real reason I dug into this. If anything else in your program depends on timing, forget using the default 1Wire read code
So to easily work out the first two items, I created this example (code box 1 below). Run it after substituting your 1Wire pin, and you'll get something like this for reading 9, 10, 11, and 12 bit resolutions:
dsaddress:2838FF400500004E,
Conversion took: 76 ms
Raw Scratchpad Data:
50 1 0 0 1F FF 10 10 21
Temp (C): 21.00
dsaddress:2838FF400500004E,
Conversion took: 150 ms
Raw Scratchpad Data:
50 1 0 0 3F FF 10 10 51
Temp (C): 21.00
dsaddress:2838FF400500004E,
Conversion took: 298 ms
Raw Scratchpad Data:
50 1 0 0 5F FF 10 10 C1
Temp (C): 21.00
dsaddress:2838FF400500004E,
Conversion took: 596 ms
Raw Scratchpad Data:
4F 1 0 0 7F FF 1 10 37
Temp (C): 20.94A HUGE improvement over stock wait times. With the accuracy of the DS18B20, it really doesn't make much sense to use 12 bits, so 10 bits saves me loads in timing. I left in a bunch of original code that's been commented out so you can see how it was done previously.
Now, we can also separate conversion commands and reading the data back. If you have multiple sensors, you actually want to use the Skip ROM and Convert T commands to tell all devices on the bus to convert simultaneously, but that's another topic. In the meantime while conversion is taking place, we can do other stuff.
So we separate our read DS18B20 routine into find, set resolution, send conversion command, and finally read temperature. Between the last two, we just continually check in our loop to see that data is ready, and when it is, we read it. Pretty simple, but SUPER EFFECTIVE. You can see we lose a little due to overhead, but still, plenty fast, and we can do other stuff at the same time.
Enjoy!
C
Temp (C): 21.50
Elapsed time (ms): 99
Temp (C): 21.50
Elapsed time (ms): 170
Temp (C): 21.62
Elapsed time (ms): 321
Temp (C): 21.56
Elapsed time (ms): 618
Code box 1:
#include <OneWire.h>
#define LED 9
#define SERIAL_BAUD 115200
void setup(void) {
Serial.begin(SERIAL_BAUD);
}
void loop(void) {
for (int i=9;i<13;i++){
handleOWIO(6,i);
Serial.println();
}
delay(1000);
Blink(LED,3);
}
void handleOWIO(byte pin, byte resolution) {
int owpin = pin;
// Device identifier
byte dsaddr[8];
char dscharaddr[16];
OneWire myds(owpin);
getfirstdsadd(myds,dsaddr);
Serial.print(F("dsaddress:"));
int j;
for (j=0;j<8;j++) {
if (dsaddr[j] < 16) {
Serial.print('0');
}
Serial.print(dsaddr[j], HEX);
}
sprintf(dscharaddr,"%02x%02x%02x%02x%02x%02x%02x%02x",dsaddr[0],dsaddr[1],dsaddr[2],dsaddr[3],dsaddr[4],dsaddr[5],dsaddr[6],dsaddr[7]);
Serial.println(',');
// Data
Serial.println(getdstemp(myds, dsaddr, resolution));
} // run OW sequence
void getfirstdsadd(OneWire myds, byte firstadd[]){
byte i;
byte present = 0;
byte addr[8];
float celsius, fahrenheit;
int length = 8;
//Serial.print("Looking for 1-Wire devices...\n\r");
while(myds.search(addr)) {
//Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
for( i = 0; i < 8; i++) {
firstadd[i]=addr[i];
//Serial.print("0x");
if (addr[i] < 16) {
//Serial.print('0');
}
//Serial.print(addr[i], HEX);
if (i < 7) {
//Serial.print(", ");
}
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
//Serial.print("CRC is not valid!\n");
return;
}
// the first ROM byte indicates which chip
//Serial.print("\n\raddress:");
//Serial.print(addr[0]);
return;
}
}
float getdstemp(OneWire myds, byte addr[8], byte resolution) {
byte present = 0;
int i;
byte data[12];
byte type_s;
float celsius;
float fahrenheit;
switch (addr[0]) {
case 0x10:
//Serial.println(F(" Chip = DS18S20")); // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println(F(" Chip = DS18B20"));
type_s = 0;
break;
case 0x22:
//Serial.println(F(" Chip = DS1822"));
type_s = 0;
break;
default:
Serial.println(F("Device is not a DS18x20 family device."));
}
// Get byte for desired resolution
byte resbyte = 0x1F;
if (resolution == 12){
resbyte = 0x7F;
}
else if (resolution == 11) {
resbyte = 0x5F;
}
else if (resolution == 10) {
resbyte = 0x3F;
}
// Set configuration
myds.reset();
myds.select(addr);
myds.write(0x4E); // Write scratchpad
myds.write(0); // TL
myds.write(0); // TH
myds.write(resbyte); // Configuration Register
myds.write(0x48); // Copy Scratchpad
myds.reset();
myds.select(addr);
long starttime = millis();
myds.write(0x44,1); // start conversion, with parasite power on at the end
while (!myds.read()) {
// do nothing
}
Serial.print("Conversion took: ");
Serial.print(millis() - starttime);
Serial.println(" ms");
//delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = myds.reset();
myds.select(addr);
myds.write(0xBE); // Read Scratchpad
//Serial.print(" Data = ");
//Serial.print(present,HEX);
Serial.println("Raw Scratchpad Data: ");
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = myds.read();
Serial.print(data[i], HEX);
Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
Serial.println();
// convert the data to actual temperature
unsigned int raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// count remain gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw << 3; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
// default is 12 bit resolution, 750 ms conversion time
}
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
Serial.print("Temp (C): ");
//Serial.println(celsius);
return celsius;
}
void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}
Code Block 2:
#include <OneWire.h>
#define LED 9
#define SERIAL_BAUD 115200
OneWire myds(6);
byte readstage;
byte resolution;
unsigned long starttime;
unsigned long elapsedtime;
byte dsaddr[8];
void setup(void) {
Serial.begin(SERIAL_BAUD);
readstage = 0;
resolution = 12;
}
void loop(void) {
if (readstage == 0){
getfirstdsadd(myds,dsaddr);
dssetresolution(myds,dsaddr,resolution);
starttime = millis();
dsconvertcommand(myds,dsaddr);
readstage++;
}
else {
if (myds.read()) {
Serial.println(dsreadtemp(myds,dsaddr, resolution));
Serial.print("Elapsed time (ms): ");
elapsedtime = millis() - starttime;
Serial.println(elapsedtime);
readstage=0;
if (resolution == 12){
resolution = 9;
}
else {
resolution ++;
}
}
}
Blink(LED,5);
}
void getfirstdsadd(OneWire myds, byte firstadd[]){
byte i;
byte present = 0;
byte addr[8];
float celsius, fahrenheit;
int length = 8;
//Serial.print("Looking for 1-Wire devices...\n\r");
while(myds.search(addr)) {
//Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
for( i = 0; i < 8; i++) {
firstadd[i]=addr[i];
//Serial.print("0x");
if (addr[i] < 16) {
// Serial.print('0');
}
// Serial.print(addr[i], HEX);
if (i < 7) {
//Serial.print(", ");
}
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.print("CRC is not valid!\n");
return;
}
// the first ROM byte indicates which chip
//Serial.print("\n\raddress:");
//Serial.print(addr[0]);
return;
}
}
void dssetresolution(OneWire myds, byte addr[8], byte resolution) {
// Get byte for desired resolution
byte resbyte = 0x1F;
if (resolution == 12){
resbyte = 0x7F;
}
else if (resolution == 11) {
resbyte = 0x5F;
}
else if (resolution == 10) {
resbyte = 0x3F;
}
// Set configuration
myds.reset();
myds.select(addr);
myds.write(0x4E); // Write scratchpad
myds.write(0); // TL
myds.write(0); // TH
myds.write(resbyte); // Configuration Register
myds.write(0x48); // Copy Scratchpad
}
void dsconvertcommand(OneWire myds, byte addr[8]){
myds.reset();
myds.select(addr);
myds.write(0x44,1); // start conversion, with parasite power on at the end
}
float dsreadtemp(OneWire myds, byte addr[8], byte resolution) {
byte present = 0;
int i;
byte data[12];
byte type_s;
float celsius;
float fahrenheit;
switch (addr[0]) {
case 0x10:
//Serial.println(F(" Chip = DS18S20")); // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println(F(" Chip = DS18B20"));
type_s = 0;
break;
case 0x22:
//Serial.println(F(" Chip = DS1822"));
type_s = 0;
break;
default:
Serial.println(F("Device is not a DS18x20 family device."));
}
present = myds.reset();
myds.select(addr);
myds.write(0xBE); // Read Scratchpad
//Serial.print(" Data = ");
//Serial.print(present,HEX);
// Serial.println("Raw Scratchpad Data: ");
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = myds.read();
// Serial.print(data[i], HEX);
// Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
// Serial.println();
// convert the data to actual temperature
unsigned int raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// count remain gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw << 3; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
// default is 12 bit resolution, 750 ms conversion time
}
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
Serial.print("Temp (C): ");
//Serial.println(celsius);
return celsius;
}
void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}