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 a remote analog to digital converters connected to a master device using an I2C bus.

Background: A few years ago I've built a weather station based on Raspberry Pi. It collects various data and displays them on a 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 had a few ATtiny85 on hand tat hand. One to another, a few hours later: a photoresistor based daylight meter sensor connected via the 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 ATtiny85 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 an 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
 * The second request sends 8 lower bytes
 * Measurement is executed when a request for the first batch of data is requested
void requestEvent()

  if (reg_position >= reg_size)
      reg_position = 0;

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

Example code to read from device might look like this:

<pre>`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 =; // receive a byte as character

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



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

19 thoughts to “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.

        1. There is a SLEEP_MODE_PWR_DOWN for both the ATmega328P and ATtiny85. SLEEP_MODE_PWR_SAVE is only valid for the ATmega328P. From the ATmega328P datasheet:

          10.6 Power-save Mode
          When the SM2…0 bits are written to 011, the SLEEP instruction makes the MCU enter Power-save mode. This mode is identical to Power-down, with one exception:
          If Timer/Counter2 is enabled, it will keep running during sleep. The device can wake up from either Timer Overflow or Output Compare event from Timer/Counter2 if the corresponding Timer/Counter2 interrupt enable bits are set in TIMSK2, and the Global Interrupt Enable bit in SREG is set.
          If Timer/Counter2 is not running, Power-down mode is recommended instead of Power-save mode.

          SLEEP_MODE_PWR_DOWN turns off more than SLEEP_MODE_PWR_SAVE.

          I didn’t event know SLEEP_MODE_PWR_SAVE existed. I’ve only ever used SLEEP_MODE_PWR_DOWN with an interrupt or watchdog to wake the ATtiny.

  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….

  4. This is just what I was looking for thanks, one question how do you set the slave device address? I would like to create 3 of these sensor modules for a project.

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

      You just have to flash every device with different address

  5. Lovely project and the attiny85 is a great chip, but a simple i2c ADC converter is cheaper. If you have to buy an attiny85, might as well get an arduino pro mini. small difference in price

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.