Using an ATtiny85 to meassure volume in a fuel tank as an One Wire Slave:

As a part of my home automation system, I needed an indicator for the amount of fuel oil remaining in the tank. The house is my vacation house which I monitor and control via internet.

All sensors and relays are connected with an One Wire bus controlled by an Raspberry pi. My tank sensor also needed connection to the One Wire bus.

After several attempts I ended up with a solution using Attiny85 emulating a One Wire device

The device calculates the remaining oil in the tank, based on the distance to the oil surface and the size and form of the oil tank.

I had a few prints made in China and installed the device in a small RJ12 connection box.

Oil tank image

The emulated One Wire device.

The clever thing to do was to emulate an existing One Wire device, known to OWFS, but I am not that clever, and took the hard way of creating my own device with my own family code.

This require modifications to OWFS and a lot of testing and recompiling. Anyway; it works now! The family code of my device is ’E2’, and it has dual PIO and a 14 byte scratchpad holding the following parameters:

float radius;	// radius of rounding 
float length;	// length of tank
float height;	// height of tank
int offset;	// distance from sensor to surface of full tank
int range;	// distance from sensor to actural surface of oil (read only)
int level;	// distance from bottom of tank to surface of oil (read only)
int volume;	// volume of oil in tank in litres (read only)

All in cm's.

Range measuring:

The distance from the sensor to the surface of the oil, is measured using a SRF02 using I2C protocol.

It runs in the main loop every 2 seconds (could/should be much longer but nice for testing);

Whenever the distance is measured, the volume is calculated and all parameters made ready for transmission when requested by the One Wire host.

The I2Cmaster protocol was an issue. Available libraries are based on timer interrupts but this conflicts wit my OneWireSlave protocol.

The solution was to write a new I2C master library based on bit-banging.

One Wire Slave emulation:

This was the hard part. There are many articles on the net about the subject, but most of them did not work with my ATtiny85. This posting is very nice and it works, but for my purpose I needed to structure the protocol for more general use.

Now the OneWireSlave protocol is isolated in it’s own library as an object.

The OneWireSlave object has four methods/events:

uint8_t crc8(const uint8_t* data, uint8_t numBytes);
	// calculate crc8 of a buffer "data" with the length "numBytes"

void begin(void (*onCommand)(uint8_t), uint8_t* owId);
	// Initialize the OneWireSlave object and sets the callback
	// routine "onCommand".
	// When the OneWireSlave object detects a Command (set pio, 
	// get pio, set scratchpad, get scratchpad), "onCommand" is called.
	// Based on the command, the OneWire slave will respond by 
	// reading or writing a buffer of bytes.

void read(uint8_t* buffer, uint8_t numBytes, void (*complete)());
	// ask OneWireSlave object to receive ‘length’ bytes into ‘buffer’.
	// call function "complete" on fininsh. 

void write(uint8_t* buffer, uint8_t numBytes, void (*complete)());
	// ask OneWireSlave object to send 'numBytes' from 'buffer'
	// call function "complete" on fininsh. 		

void reset();	
	// ask OneWireSlave to reset connection (send break)

These five methods/events makes it easy to implement any OneWire device.

I2C Master protocol

The protocol is implemented in it's own library as an object.

The object has the following methodes:

void transmitTo(unsigned char address);	
	// initiate transmission to an I2C device
void requestFrom(unsigned char address);	
	// request bytes from I2C device
unsigned char rx(unsigned char ack);	
	// receive byte from I2C device - ack is 0 if last byte 
unsigned char tx(unsigned char b);	
	// send byte to I2C device
void stop();
	// stop sequence

The ATtiny85:

The ATtiny85 is a small, cheap 8-pin DIL chip.

In my application, two pins are used for VCC (pin 8) and GND (pin 4).

One pin is used for reset (pin 1).

This leaves 5 pins for the user.
I have used DB0 (pin 5) and DB1 (pin 6) for I2C (SDA and CLK),
DB2 (pin 7) is for OneWire bus and DB3 (pin 2) and DB4 (pin 3) is used as PIO A and PIO B.

The ATtiny85 is programmed with an Arduino Nano using Arduino IDE.