The 1-Wire driver in Linux Kernel
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