Precision PWM Frequency for Arduino / ATmega 328P

I’m working on an application where I need fine adjustment of PWM frequency. The existing PWM code that I found, such as the Arduino PWM Frequency Library, only allows integral frequencies to be selected. On pins supported by 16-bit timers, the Arduino PWM Frequency Library allows fine adjustment of duty cycle, but not frequency. After searching for a while, I found an interesting article in Udo Klein’s Blinkenlight blog: Flexible Sweep. In the article, Klein has an Arduino sketch which sweeps the LEDs of a Blinkenlight board from 0-999.9999 Hz in increments of .0001 Hz.

I hacked his sketch into PrecisionPWM, which outputs PWM to any arbitrary digital pin in increments of .0001 Hz. What’s nice is that since it doesn’t use the ATmega’s internal PWM generator, you can use it on any arbitrary digital pin, whether or not it supports hardware PWM.

Download from github: PrecisionPWM

WS2812B LED (NeoPixel) Control: Part 1 – Serial Control via 8-bit ATmega (Arduino)

INTRODUCTION

I’ve been laying the groundwork for doing some projects using remote controlled RGB LEDs. My first attempt was Lampduino, which used discrete RGB LEDs, and an ITEAD Colorduino as a controller. In that project, I ran into several pitfalls:

  • though inexpensive, assembly of the LED matrix was very labor intensive
  • the LED’s were rather dim, due to the limited drive capability of the Colorduino
  • the frame rate was slow, due to limited baud rate and RAM
  • while it was scalable, I didn’t like the idea of having to use a separate Colorduino for every 64 LEDs

A few years have passed, and WS2812B LEDs have dropped enough in price enough to get into the range that I feel is affordable. They can be found on eBay and AliExpress very cheaply. Also, they can be controlled without any specialized hardware – all that is needed is one GPIO pin. There are libraries available for many of the popular microcontrollers. Some examples are Arduino, ESP8266, Teensy 3.x, and Raspberry Pi.

HOST SOFTWARE

The other piece of the puzzle is control software. For Lampduino, I hacked uRaNGaTaNG’s mtXcontrol Processing sketch into rgbMtx, but I found Processing to be a very limiting platform, which was hard to debug. This time around, I found a couple of interesting free LED control programs, which are both quite powerful. The first one is Jinx! LED Matrix Control, which runs on Windows only, and the second is Glediator, which is a Java app. Both programs, while free, are not open source. However, they are both powerful enough to do some interesting things.

HARDWARE

I decided to start my experiments with an Arduino Pro Mini clone, because they have a tiny footprint, are cheap (clones are <$2 shipped from China on eBay), and I happened to have some laying around. Also, the Arduino Pro Mini’s ATmega 328P MCU runs at 5V, so no level shifting is required when interfacing to WS2812Bs. Glediator’s creator, Solderlab, has barebones, fast serial client Arduino sketch which can be downloaded at: WS2812-Glediator-Interface. It can run on any 8-bit ATmega-based Arduino such as the Mega, UNO, Deumilanove, etc. The code that outputs the data to the LEDs is written in assembly language, and is thus, very fast & compact. Also, rather than using Arduino’s Serial library, it contains its own very compact serial code. At the expense of a little bit of speed, I decided to generalize it a bit, and add my own packet protocol. My code is on github at: WS2812Remote. The main changes that I made in my version of the code are:

  • Since I didn’t understand the Glediator example’s serial code, I reverted to using Arduino’s built-in Serial library. I’ve tested it with baud rates up to 1000000 and an FTDI cable on a Windows PC, and it works fine
  • I added support for my own packet protocol. Glediator’s serial protocol is extremely simple. Each frame starts with 0x01, followed by the pixel data stream. My simple packet protocol adds an XOR check byte, as well as a few simple commands such as color fill and blanking of the LEDs.

I also wrote a C++ program, called pkt_test, which demonstrates usage of my packet protocol.

Hookup of the WS2812B LED strand to the Arduino is quite simple. You can use any 8-bit Arduino. First, you must select a data pin to drive the strand. I arbitrarily decided to use pin PD2. For speed and compactness, instead of using Arduino functions to access the data pin, the code refers to the ATmega port and pin numbers, rather than Arduino’s rather arbitrary digital pin numbers. On the Arduino Pro Mini, digital pin 2 = PD2, as can be seen from the following pin mapping diagram:

FIRMWARE CONFIGURATION

So WS2812Remote.h is configured as follows:

#define DATA_PORT PORTD
#define DATA_DDR DDRD
#define DATA_PIN 2

Next, PIXEL_CNT needs to be set to the number of LEDs in your strand. I tested with an Adafruit NeoPixel ring containing 16 LEDs:

#define PIXEL_CNT   16

Connect your the data input pin of the first LED of your WS2812B strand to your selected data pin. Adafruit recommends a 300-500 ohm inline resistor to protect from voltage spikes. The NeoPixel ring I used already has a resistor onboard, so I didn’t need it. I connected the +5V and GND pins directly to the corresponding pins on the Arduino. To protect against current inrush when powering it up, Adafruit also recommends connecting a 100uf capacitor between the +5V and GND pins. However, it’s not necessary if you’re just going to power it from USB, which is what I did, since I was only powering 16 LEDs. For large strands, you will need an external power supply to supply sufficient current, as each LED can draw up to 60mA at full brightness. If using an external power supply, make sure to always apply power to the WS2812B strand before the data pin!

HOST SOFTWARE CONFIGURATION

I used the same FTDI cable that I used for programming the Arduino as a virtual com port for sending data to it. When configuring Jinx! or Glediator, select Glediator protocol. For speed, the sketch just receives raw pixel data, and dumps it out to the LED strand, so the data format is in native GRB order.

When configuring Jinx! or Glediator, select Glediator as the device type (Jinx!) or output mode (Glediator). Make sure that the baud rate of the corresponding com port matches BAUD_RATE as defined in your sketch. I tested 115200 and 1000000 bps with my FTDI cable, and both worked fine with both programs. It failed at 1250000 bps.

If you want to play around with my packet protocol, the pkt_test code is self explanatory. I tested it with Visual Studio 2015 in Windows 8.1, and g++ in Debian linux 8.2.0. Prior to compilation, set COMM_PORT to correspond to your Arduino’s serial port. Also, confirm that BAUD_RATE in ../WS2812Remote.h matches the value that was used when loading the Arduino sketch. To compile and run pkt_test in linux, use:

g++ pkt_test.cpp serialib.cpp -o pkt_test
sudo ./pkt_test

WIRELESS CONTROL

My first inclination for wireless control was to use Bluetooth, due to its simplicity. The Bluetooth SPP (Serial Port Profile) makes it easy to construct a wireless virtual serial interface between a host computer and the Arduino. This allows you to use exactly the same host software configuration that you would for a direct serial connection to the host. I had an Elechouse EHB Serial Bluetooth Module in my parts bin (very similar to the ubiquitous HC-05), so I decided to try it out.

Before using the EHB module can be used, it must be configured with a series of simple AT commands. I hooked it up to my Windows PC with my FTDI cable, and used PuTTY as a serial terminal to configure it. Connecting the EHB module to the Arduino is quite straightforward:

Arduino -> EHB

5v -> VCC
GND -> GND
RXD -> TXD
TXD -> RXD

Pairing the EHB to a host computer creates a virtual serial port for the host software to access. Unfortunately, I was using Windows 8.1 as my host computer, and its handling of Bluetooth SPP clients is rather flaky. Every time I powered down the LED controller, I had to unpair/pair the Bluetooth in order to get the virtual serial port to work properly. While it worked flawlessly when the virtual serial port was functional, ultimately, I abandoned Bluetooth due to the flakiness of Windows’ Bluetooth SPP support. Perhaps Linux can handle it better.

Next: WS2812B LED (NeoPixel) Control: Part 2 – WiFi Control via ARTnet on ESP8266