Introduction: Smooth PWM LED Fading With the ATTiny85


This short Instructable details how to achieve smooth transitions in response to step changes in PWM value (see video above for comparison. Apologies for poor light levels, but it does get the point across). The code was intended to be used with back lit displays to give a more professional look and feel to changes in brightness levels. Not dissimilar to that of an iPhone, LG or Samsung etc. when the display blanks after a period of user inactivity.


What parts do I need?

System entry requirements;

You will also need the following parts (to set up the comparison and prototyping circuits);

  1. 2 off Arduino Uno R3 (second Arduino to prototype and debug more complex I2C Slave code),
  2. 2 off 20x4 LCD Displays with I2C interface fitted (See
  3. 2 off ATTiny85,
  4. 2 off PN2222 NPN BJTs,
  5. 2 off ZTX789A PNP BJTs,
  6. 4 off 1K resistors,
  7. 1 off push fit perf board for prototyping,
  8. 1 off 3mm LED (for prototyping fade),
  9. 1 off 320R resistor (for prototyping fade),
  10. Various interconnection wires.


What skills do I need?
To set the system up, use the source code (provided) and create the necessary circuitry you will need the following;

  1. A minimal grasp of electronics,
  2. Knowledge of Arduino and it's IDE,
  3. Some Patience.


Topics covered

  1. Brief overview of the circuit,
  2. Brief overview of the software,
  3. How to test your 'smoothed' PWM device and see it in action,
  4. Conclusion,
  5. References used.


Whilst developing an ESP8266-12E IoT device for my study I wanted to use a 20x4 LCD to display some ambient environment variables around my home (temperature, humidity, barometric pressure etc.). However in using the LCD it became obvious that just statically fixing the brightness of the LCD back-light wasn't the best design decision.

During times of low ambient light (in the evening) the LCD would create a lot of glare. To tackle this I attached an ambient light sensor to the IoT device which detected these changes and adjusted the light level accordingly.

However, this was a far from elegant solution given once the ambient light levels had dropped below a trigger point the display would immediately switch from one level of intensity to the next. This was further compounded by a regular flickering due to the ESP8266-12E servicing other high priority tasks and not PWM functionality.

Design decision

To improve the back-light transition response I decided to move this task to another processor. The only issue was how to communicate with this processor given I had already used all the available ESP8266-12E I/O.

Simple, I decided to use the I2C bus and make my own slave device using the ATTiny85.

What follows is the detailed design, implementation and testing.


Please see the following Instructables on configuring the Arduino IDE for ATTiny85s and programme them along with how to create an Arduino I2C Slave.

Programming the ATTiny85, ATTiny84 and ATMega328P : Arduino As ISP

Expand your Arduino's I/O with an I2C Slave device

Step 1: Circuit Overview

Detailed above is the circuit used to connect the ATTiny85 as a slave I2C device. Note the use of the 10K pull ups.

For your circuit replace RED LED with backlight of choice.

See Instructable Arduino I2C LCD Driver Library and Packman for further details on I2C interfacing 'I2C_LCD_With_Arduino.pdf'

I have also included a diagram depicting the pin out of the ATTiny85 from an Arduino IDE perspective.

Step 2: Software Overview


To successfully compile this source code you will need the following extra libraries;


ATTiny Core


Given below are two Arduino sketches.

  • Tiny85_I2C_Slave_PWM_1.ino

This code gives a linear PWM response to a change PWM value and was the prototype code framework.

  • Tiny85_I2C_Slave_PWM_2.ino

This code gives a non-linear (quartic) smooth PWM response to a given change in PWM value.

Code Overview

In each case, the I2C slave address is hardcoded into the ATTiny85 at compile time with the #define I2C_SLAVE_ADDR.

At start up the software configures up the I/O, I2C slave address and then loops waiting to receive a single byte from the I2C interface addressed to this device. Upon receipt of a byte, it is written straight to PWM1

In the case of the non-linear response the difference in current and new PWM values are computed and 'plugged' into a formula;


This is carried out every 15mS to incrementally change the PWM output until it reaches it's final value. Thus giving a gradual smooth response to a change in PWM value.

Arduino IDE Coding practicalities

Coding the ATTiny85 was tricky given there is no easy way to debug during development. For example when writing software for the Arduino Uno you will invariably use many printf statements. Printf on the ATTiny85 is not available (unless 'bit banged' resulting in the potential drop in performance). For anything other than simple code you could employ a 'bit wiggling' strategy (changing the state of a given I/O pin under a particular condition) but this too is limited.

The code in sketch 'Tiny85_I2C_Slave_PWM_1.ino' is quite simple so I debugged by trying the code live on the target (ie. programme the device, remove from programmer and insert into target circuit, observe functionality), however for 'Tiny85_I2C_Slave_PWM_2.ino' I modified the code such that the debugging could be carried out on a second Arduino Uno then ported across to the target ATTiny85 platform upon completion (as in the picture above). This latter method is known as cross platform development.

Step 3: Testing the Software

As mentioned above for the smooth transition PWM code to debug and test I used two Arduino Unos connected as I2C Master and Slave as in the Fritzing diagram above.

I created a few test sketches for the I2C Master;

  • ATTiny85I2CRegWriteTest1

Generates a random PWM value and sends to I2C slave device every 15 seconds

  • ATTiny85I2CRegWriteTest2, ATTiny85I2CRegWriteTest3, ATTiny85I2CRegWriteTest4

Generate differing fixed sequences of PWM values to I2C slave device approx. every 3 seconds

Which allowed me to fully exercise the Slave code to ensure it worked robustly.


For completeness I have also included the code I wrote to show the comparison between the linear and non-linear transitions below as in the video (Tiny85_I2C_Slave_PWM_Comparison1.ino along with the circuit diagram). The two displays have I2C to parallel interface device fitted.


Note : If you are having trouble communicating with devices on the I2C bus, try running the Arduino I2C Scanner sketch located here;

Step 4: Conclusion

Ok so I could have used an IC such as the MAXIM I2C DS1050Z-001+ Pulse Width Modulator, but I'm big fan of 'rolling my own' and besides I had a stock of ATTiny85 hanging around and it gave me an excuse to look at programming the ATTiny85 and writing code for an I2C slave. Also there would still be the requirement to regularly compute the light level values with an easing function, much better to distribute the processing load as I mention in the design decision.

Design Expansion

It should be noted, this design only uses one of the available PWMs, it wouldn't be too difficult to provision access to all unused digital I/O or even analogue I/P within the ATTiny85. Alternatively the spare I/O could be used to set the I2C slave address at power up.

Step 5: References Used