When you are making a LED project, you often need to control it using PWM. For RGB projects you need at least 3 PWM channels. If you wanted to control 10 individual RGBs, you need 30 PWM channels, unless you use external driver IC or digital LEDs like WS2812.
Most AVR microcontrollers don’t have that many hardware PWM channels. You can do software PWM emulation, but it costs precious CPU time (unless it has nothing other to do), so we’d like to stay with hardware solutions.
Microcontrollers from XMEGA family are called “Atmega on steroids”, because they are like normal AVRs, but with many useful features. IMO they are very underrated today. One of their features solves our problem.
Most of XMEGA microcontrollers have built-in 2–4 Timers/Counters type 2. It is normal 16-bit timer with four Compare Channels (PWM channels), but it has one special feature — it can be split into two 8-bit timers, with 4 compare channels each. This gives us 8 PWM channels per one timer. With 4 timers, that is 32 PWM channels! We are able to control 10 RGB strips.
Let’s write a simple codebase to control 8 PWM channels. I’ll use a real world example — I’ve done this configuration in my room. It has two RGB strips and two single color LEDs:
- Bed RGB — RGB strip over my bed
- Window RGB
- Desk white LED
- Bookshelf white LED
Development tools I use Atmel Studio 7, an Atmel ICE programmer with PDI interface. The code is written in modern C++, built using avr-g++ with
-std=c++11 flag. But of course it is possible to write it in plain C, the code would need only slight changes.
To initialize PWM, we need to set a few registers, but it is pretty simple:
Now, when you want to set PWM value for specified channel, you can do it this way:
And that’s all! Additionally you can use enum or
#define to name the channels. Here’s an example:
Please note that used timer 2 instance must be the connected to used IO port — for instance if you want to use Port D pins, you have to select TCD2.
Adding fade effect
Now we’re going to add a simple fade effect. It will let us go smoothly from one color to another. I have done it using C++ features.
Firstly, let’s create a pair of files: header
LedController.cpp. Here’s the first one:
PWMController does the same functionality described in previous section — hardware timer configuration, but in C++ like way. Class
FxController handles fade effect. It is one level of abstraction higher as it takes
PWMController instance as constructor parameter.
Now it’s time for LedController.cpp file:
To use fade effect, we need to call it regularly, for example every 10ms. We can use a timer which causes an interrupt. Let’s go straight to example:
Fade updates even during
delay_ms() thanks to being called inside interrupt.