4 minute read

I’ve been playing around with a digital thermometer DS18B20 from Maxim Integrated, connecting it to one of the GPIO pins of abundant Raspberry Pies lying around in my lab. This is a random note that I’ve stashed many years ago, but thought it might help somebody…

When studying the peripherals of a linux machine, the source code for the driver kernel module is a good starting point.

  • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/w1

Digital Sampling

The w1 driver samples the pulses with w1_read_bit:

/**
 * w1_read_bit() - Generates a write-1 cycle and samples the level.
 * @dev:	the master device
 *
 * Only call if dev->bus_master->touch_bit is NULL
 */
static u8 w1_read_bit(struct w1_master *dev)
{
	int result;
	unsigned long flags = 0;

	/* sample timing is critical here */
	local_irq_save(flags);
	dev->bus_master->write_bit(dev->bus_master->data, 0);
	w1_delay(6);
	dev->bus_master->write_bit(dev->bus_master->data, 1);
	w_delay(9);

	result = dev->bus_master->read_bit(dev->bus_master->data);
	local_irq_restore(flags);

	w1_delay(55);

	return result & 0x1;
}

To read the scratchpad in DS18B20, it needs to figure out how the linux machine is connected to the 1-Wire device.

  • https://github.com/torvalds/linux/blob/master/drivers/w1/w1_io.c#L289
/**
 * w1_read_block() - Reads a series of bytes.
 * @dev:	the master device
 * @buf:	pointer to the buffer to fill
 * @len:	the number of bytes to read
 * Return:	the number of bytes read
 */
u8 w1_read_block(struct w1_master *dev, u8 *buf, int len)
{
	int i;
	u8 ret;

	if (dev->bus_master->read_block)
		ret = dev->bus_master->read_block(dev->bus_master->data, buf, len);
	else {
		for (i = 0; i < len; ++i)
			buf[i] = w1_read_8(dev);
		ret = len;
	}

	return ret;
}
EXPORT_SYMBOL_GPL(w1_read_block);

/**
 * Reads 8 bits.
 *
 * @param dev     the master device
 * @return        the byte read
 */
u8 w1_read_8(struct w1_master *dev)
{
	int i;
	u8 res = 0;

	if (dev->bus_master->read_byte)
		res = dev->bus_master->read_byte(dev->bus_master->data);
	else
		for (i = 0; i < 8; ++i)
			res |= (w1_touch_bit(dev,1) << i);

	return res;
}
EXPORT_SYMBOL_GPL(w1_read_8);

/**
 * Generates a write-0 or write-1 cycle and samples the level.
 */
static u8 w1_touch_bit(struct w1_master *dev, int bit)
{
	if (dev->bus_master->touch_bit)
		return dev->bus_master->touch_bit(dev->bus_master->data, bit);
	else if (bit)
		return w1_read_bit(dev);
	else {
		w1_write_bit(dev, 0);
		return 0;
	}
}

where w1_bus_master is

/**
 * Note: read_bit and write_bit are very low level functions and should only
 * be used with hardware that doesn't really support 1-wire operations,
 * like a parallel/serial port.
 * Either define read_bit and write_bit OR define, at minimum, touch_bit and
 * reset_bus.
 */
struct w1_bus_master
{
	/** the first parameter in all the functions below */
	void		*data;

	/**
	 * Sample the line level
	 * @return the level read (0 or 1)
	 */
	u8		(*read_bit)(void *);

	/** Sets the line level */
	void		(*write_bit)(void *, u8);

	/**
	 * touch_bit is the lowest-level function for devices that really
	 * support the 1-wire protocol.
	 * touch_bit(0) = write-0 cycle
	 * touch_bit(1) = write-1 / read cycle
	 * @return the bit read (0 or 1)
	 */
	u8		(*touch_bit)(void *, u8);

	/**
	 * Reads a bytes. Same as 8 touch_bit(1) calls.
	 * @return the byte read
	 */
	u8		(*read_byte)(void *);

	/**
	 * Writes a byte. Same as 8 touch_bit(x) calls.
	 */
	void		(*write_byte)(void *, u8);

	/**
	 * Same as a series of read_byte() calls
	 * @return the number of bytes read
	 */
	u8		(*read_block)(void *, u8 *, int);

	/** Same as a series of write_byte() calls */
	void		(*write_block)(void *, const u8 *, int);

	/**
	 * Combines two reads and a smart write for ROM searches
	 * @return bit0=Id bit1=comp_id bit2=dir_taken
	 */
	u8		(*triplet)(void *, u8);

	/**
	 * long write-0 with a read for the presence pulse detection
	 * @return -1=Error, 0=Device present, 1=No device present
	 */
	u8		(*reset_bus)(void *);

	/**
	 * Put out a strong pull-up pulse of the specified duration.
	 * @return -1=Error, 0=completed
	 */
	u8		(*set_pullup)(void *, int);

	/** Really nice hardware can handles the different types of ROM search
	 *  w1_master* is passed to the slave found callback.
	 */
	void		(*search)(void *, struct w1_master *,
		u8, w1_slave_found_callback);
};

struct w1_master
{
	struct list_head	w1_master_entry;
	struct module		*owner;
	unsigned char		name[W1_MAXNAMELEN];
	struct list_head	slist;
	int			max_slave_count, slave_count;
	unsigned long		attempts;
	int			slave_ttl;
	int			initialized;
	u32			id;
	int			search_count;

	atomic_t		refcnt;

	void			*priv;
	int			priv_size;

	/** 5V strong pullup enabled flag, 1 enabled, zero disabled. */
	int			enable_pullup;
	/** 5V strong pullup duration in milliseconds, zero disabled. */
	int			pullup_duration;

	struct task_struct	*thread;
	struct mutex		mutex;

	struct device_driver	*driver;
	struct device		dev;

	struct w1_bus_master	*bus_master;

	u32			seq;
};

In linux/drivers/w1/slaves/w1_therm.c, there’s the DS18B20 temperature conversion method:

static inline int w1_DS18B20_convert_temp(u8 rom[9])
{
	s16 t = le16_to_cpup((__le16 *)rom);
	return t*1000/16;
}

I2C Pins

I2C pins are already equipped with a 1.8 kohms fixed resistors, but it’s too small for DS18B20; not suitable

1-Wire Python libraries

PyDigiTemp

A great demonstration for translating the bit array to an actual temperature value to read. https://github.com/mcsakoff/pydigitemp/blob/master/digitemp/device/termometer.py

    @classmethod
    def _calc_temperature(cls, scratchpad):
        """
        Extract temperature value from scratchpad.
        :param scratchpad: Scratchpad 8-bytes as bytes.
        :return: float, temperature in Celcius
        """
        resolution = (iord(scratchpad, 4) >> 5) & 0x3
        temp_register = struct.unpack('<h', scratchpad[0:2])[0]
        if resolution == DS18B20.RES_12_BIT:
            temperature = float(temp_register) / 16.0
        elif resolution == DS18B20.RES_11_BIT:
            temperature = float(temp_register >> 1) / 8.0
        elif resolution == DS18B20.RES_10_BIT:
            temperature = float(temp_register >> 2) / 4.0
        elif resolution == DS18B20.RES_9_BIT:
            temperature = float(temp_register >> 3) / 2.0
        else:
            raise NotImplementedError()
        return temperature

pyownet

This is a library intented to communicate with owserver (OWFS 1-Wire File System server); however it holds a good module for accessing the BUS.

Found this thread: https://raspberrypi.stackexchange.com/questions/62292/usage-of-python-ow-one-wire-file-system-python-package-for-reading-1-wire-ds18b2 and, redirected here: https://pyownet.readthedocs.io/en/latest/protocol.html

  • The synopsis for DS18B20 comes in handy: http://owfs.org/index.php?page=ds18b20

Leave a comment