Read DS18B20 1wire temperature sensor from Linux

After much trying I finally got a solution to read a 1wire temperature probe on my MilkV Duo through it’s GPIO.
Let’s quickly go over what I’ve tried and what didn’t work:

  • There’s no kernel support for 1wire on the GPIO so stuff like owserver is no option here.
  • The python example in /usr/lib/python3.9/site-packages/pinpong/examples/milkv-Duo/ds18b20.py is for a different board. Changing the board didn’t help either since a bunch of functions are just missing in the pinpong library.

I tried a bit with bit-banging in python, it might work, but I found I don’t get an accurate delay() for my pulses, so I went back to just plain old C.

I’ve started off the sensor-demp for the DHT22. I don’t know what changed in the wiringx library but initializing it with

wiringXSetup("milkv_duo", NULL)

doesn’t work. The board is just called duo in the library. With that I got it going.

Here’s the dump of my complete working code. You can compile it using the DHT22 makefile, just adjust the TARGET in the first line to your liking.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>

#include <wiringx.h>

#define DS18B20_SKIP_ROM	0xCC
#define DS18B20_CONVERT_T	0x44
#define DS18B20_READ_SCRATCHPAD	0xBE
#define PIN 			15

int verbose = 0;

static uint8_t sizecvt(const int read) {
    /* digitalRead() and friends from wiringpi are defined as returning a value
    < 256. However, they are returned as int() types. This is a safety function */

    if (read > 255 || read < 0) {
        printf("Invalid data from wiringPi library\n");
        exit(EXIT_FAILURE);
    }
    return (uint8_t)read;
}

uint8_t crc8_update( uint8_t crc, uint8_t data ) {
	uint8_t i;

	crc = crc ^ data;

	for (i = 0; i < 8; ++i) {
		if (crc & 0x01) {
			crc = (crc >> 1) ^ 0x8c;
		} else {
			crc >>= 1;
		}
	}

	return crc;
}

/*  Check an array of bytes using the Maxim 8-bit CRC algorithm.
**  The last byte must be the expected CRC to make the final
**  result zero.
*/
uint8_t check_crc(uint8_t *cp, uint8_t length) {
	uint8_t crc = 0x00;

	while( length-- ) {
		crc = crc8_update(crc, *cp++);
	}

	return crc;
}

static void write_pin( uint8_t pin, uint8_t state ) {
	pinMode( pin, PINMODE_OUTPUT );
	digitalWrite( pin, state );
}

static void write_byte( uint8_t pin, uint8_t value ) {
	if( verbose ) printf( "Writing 0x%02x: ", value );
	for( uint8_t loop = 0; loop < 8; loop++ ) {
		write_pin( pin, LOW );
		if( ( value & ( 1 << loop ) ) != 0 ) {
			delayMicroseconds( 1 );
			pinMode( pin, PINMODE_INPUT );
			delayMicroseconds( 60 );
			if( verbose ) printf( "1" );
		} else {
			delayMicroseconds( 60 );
			pinMode( pin, PINMODE_INPUT );
			delayMicroseconds( 1 );
			if( verbose ) printf( "0" );
		}
		delayMicroseconds( 60 );
	}
	delayMicroseconds( 100 );
	if( verbose ) printf( "\n" );
}

static uint8_t read_pin( uint8_t pin ) {
	pinMode( pin, PINMODE_INPUT );
	return sizecvt( digitalRead( pin ) );
}

static uint8_t read_byte( uint8_t pin ) {
	uint8_t b = 0;
	for( uint8_t loop = 0; loop < 8; loop++ ) {
		write_pin( pin, LOW );
		delayMicroseconds( 1 );
		pinMode( pin, PINMODE_INPUT );
		delayMicroseconds( 2 );
		if( read_pin( pin ) == HIGH ) {
			b |= (1<<loop);
		}
		delayMicroseconds( 60 );
	}
	return b;
}

static uint8_t reset( uint8_t pin ) {
	// send reset pulse
	write_pin( pin, LOW );
	delayMicroseconds( 480 );
	pinMode( pin, PINMODE_INPUT );
	delayMicroseconds( 60 );

	uint8_t response = read_pin( pin );
	delayMicroseconds( 420 );

	if( response == LOW ) {
		return 1;
	}
	return 0;
}

static int read_ds18b20() {
	// Reset the chip
	if( reset( PIN ) == 1 ) {

		// send skip ROM command (0xCC)
		write_byte( PIN, DS18B20_SKIP_ROM );

		// send convert T command( 0x44 )
		write_byte( PIN, DS18B20_CONVERT_T );

		// wait conversion delay
		delayMicroseconds( 750000 );

		// reset again
		if( reset( PIN ) == 1 ) {

			// skip rom, then read scratchpad
			write_byte( PIN, DS18B20_SKIP_ROM );
			write_byte( PIN, DS18B20_READ_SCRATCHPAD );

			uint8_t data[9];
			if( verbose ) printf( "Reading scratchpad:\n" );
			for( uint8_t i = 0; i < 9; i++ ) {
				data[i] = read_byte( PIN );
				if( verbose ) printf( "0x%02x,", data[i] );
			}
			if( verbose ) printf( "\n" );

			// now check CRC:
			if( check_crc( data, 9 ) == 0 ) {
				return ( (data[1]<<8) | data[0] );
			} else {
				if( verbose ) printf( "CRC check failed\n" );
			}
		} else {
			if( verbose ) printf( "Failed to reset after Convert_T\n" );
		}
	} else {
		if( verbose ) printf( "Failed to reset\n" );
	}
	return -1;
}

int main( int argc, char *argv[] ) {
	if( wiringXSetup("duo", NULL) == -1) {
		wiringXGC();
		return -1;
	}

	if( wiringXValidGPIO(PIN) != 0) {
		printf("Invalid GPIO %d\n", PIN);
	}

	for (int i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-v") == 0) {
			verbose = 1;
			break;
		}
	}

	int ds18b20 = read_ds18b20();

	printf( "{\"success\":" );
	if( ds18b20 < 0 ) {
		printf( "false}" );
	} else {
		double temp = (double)ds18b20 * 0.0625;
		printf( "true,\"temperature\":%.2f}", temp );
	}
	printf("\n");
	return 0;
}

My code creates a json output which I’m using in my webapp. It’s easiest that way and you can properly handle the case where ever so often the CRC check fails.

I’ve also added mosquitto to my base image, so I can now publish the temperature on MQTT. For that I simply edited /mnt/system/auto.sh

#!/bin/sh

# Put the program you want to run automatically here

while [ 1 ]; do
	# Update temperature file every 30 seconds
	/mnt/system/ds18b20 > /tmp/ds18b20.json

	# publish to mqtt on success
	TMP="$(fgrep true /tmp/ds18b20.json | cut -d":" -f3 | cut -d'}' -f1)"
	if [ "$TMP" != "" ]; then
		/usr/bin/mosquitto_pub -h your.mosquitto.server -t 'homie/RackTemp/sensor/temperature' -m $TMP
	fi

	sleep 30
done

Hope this helps someone :wink:

2 Likes