ATtiny85 Light Sensor – I2C slave device

I love AVR ATtinyx5 series microcontrollers. They are cheap, easy to use, they can be programmed just like Arduinos and comparing to their size they offer great features. For example, they can be used as remote analog to digital converters connected to master device using I2C bus.

Background: few year ago I've build a weather station based on Raspberry Pi. It collects various data and displays them on dedicated web page and Android app. Every few months I try to add a new sensor to it. Last time it was a daylight sensor. Raspberry Pi does not offer ADC inputs and I has few ATtiny85 on hand that time. One to another, few hours later: photoresistor based daylight meter sensor connected via I2C bus.

ATtiny85 as light sensor with I2C bus

Electric assembly is pretty simple: ATtiny85 directly connected to Raspberry Pi via I2C, photoresistor with 10kOhm pull down connected to ATtiny85 and signal LED.

attiny85 i2c slave light sensor with photoresistor

Code driving this rig is also pretty simple: watchdog timer wakes up ATtiny every few minutes, measures voltage, filters it and stores in memory. Every time read operation is requested, last filtered ADC value (10 bits as 2 bytes).

I2C support is provided by TinyWireS library that configures USI as I2C slave.

/**
 * This function is executed when there is a request to read sensor
 * To get data, 2 reads of 8 bits are required
 * First requests send 8 older bits of 16bit unsigned int
 * Second request send 8 lower bytes
 * Measurement is executed when request for first batch of data is requested
 */
void requestEvent()
{  
  TinyWireS.send(i2c_regs[reg_position]);

  reg_position++;
  if (reg_position >= reg_size)
  {
      reg_position = 0;
  }
}

/*
 * Setup I2C
 */
TinyWireS.begin(I2C_SLAVE_ADDRESS);
TinyWireS.onRequest(requestEvent); //Set I2C read event handler

Example code to read from device might look like this:

Wire.requestFrom(0x13, 2);    // request 2 bytes from slave device #0x13

int i =0;
unsigned int readout = 0;

while (Wire.available()) { // slave may send less than requested
byte c = Wire.read(); // receive a byte as character

if (i == 0) {
    readout = c;
} else {
    readout = readout << 8;
    readout = readout + c;
}

i++;
}

Serial.print(readout);

Full source code is available on GitHub and my Weather Station with almost a year of light level history is available here.

13 thoughts on “ATtiny85 Light Sensor – I2C slave device

  1. I love AVR Attiny too.

    Small, cheap, easy to start, enough powerful for many tasks, even for implement simple USB devices like HID or mass storage devices. I use Attiny in my cheap RC lap counter project – sadly not done yet. 🙂

  2. I’ve been using this project as an example to get started with using the ATtiny85 – your work has been a great help in getting me started and testing my toolchain for programming the ATtiny! At this point, I am at the stage of compiling, and I have hit the issue that SLEEP_MODE_PWR_SAVE isn’t defined. I am using Arduino 1.6.13 on a Raspberry Pi 3. On doing a bit of digging, I found that it appears that there are only 3 sleep modes defined for the ATTiny85 – namely IDLE, ADC Noise Reduction and Idle.
    Am I missing something, or have you also run into this?

      1. I suspect that is the case. I looked this up in the ATTiny85 datasheet, which only defines the 3 sleep modes I listed. I suspect the core you are using defines that macro to map to one of the 3 modes, which does help in order to make software more portable between different AVR chips.

  3. Out of interest: once I used one of the modes that compiled with my core, the sketch compiled and worked. I’m now modifying it a little add some more registers (to start with, that allows tuning the delay between samples by writing to a register on the I2C slave). I think I am getting the bugs ironed out (then I can put the sleep code back.) Let me know if you are interested in the modifications to the sketch.

      1. I will do, as soon as I have my code cleaned up a bit. At the moment I still want to look at the handling of outlier values read from the ADC. That seems to be an issue with LDRs. I am thinking now of taking a group of readings, discarding the min and max, and then taking the average of the rest.

        1. I seem to be making progress. I’ve now added a different smoothing algorithm: I have set up an array in which I store the raw values – between 1 and 16 can be stored at the moment. If only one is being stored, the raw value is returned. Two is not allowed. If 3 or more are stored, I discard the maximum and minimum and then return the average of the other values. That should hopefully provide a relatively simple way to discard outliers.
          A separate I2C device register then allows the original smoothing algorithm to be turned on or off.
          Another 12C register allows you to set the interval between samples – instead of 120s, I am setting a default of 30s at the moment.
          For example, at the moment I am sampling every 15s, and I am storing 16 raw values. That means, at each sample I then discard the lowest and highest values and compute the average of the rest. I have then turned smoothing off.

          The code needs some tidying now – assuming I don’t find any bugs….

Leave a Reply

Your email address will not be published.