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 This was part of a larger project I did with my MilkV board: Repairing an old network power switch.