Driving RainbowBits with Cytron’s sk1632!

Note: This tutorial is for those who are already experienced in C, MPLAB X and some knowledge in PWM.

I’m sure you have seen a lot of those RGB lights everywhere and they do sure look amazing and brilliant. Here, you will be amazed that you can experiment with these LEDs at your own home. We will be experimenting on a couple of those Cytron’s RainbowBits on the breadboard and show you how these little smart WS2812 RGB LEDs on these RainbowBits work.

If you want to play with these awesome RainbowBits first, you can skip to the Let’s Color the LEDs section. However, it is encouraged for you to read up a bit on how the WS2812 LEDs and how we can drive them using DMA and PWM.

Driving the WS2812

The first thing you will notice is, the LEDs are not the common ones which you can just directly connect the supply and run immediately. You must feed the data into the LED to display the color that you want. Of course, a microcontroller is needed to supply the data into the LED, or it will not work as what you have expected. You need to send out 24bits (3 bytes) of color data into the LED, the first byte is the Green, Blue and finally Red.

Each bit of the data is represented by the following diagram:

0 code: If you are sending a bit ‘0’, the ‘0 code’ pulse is sent out and being registered into the LED.

1 code: Similarly, if you are sending a bit ‘1’, the ‘1 code’ pulse is registered into the LED.

RET code: The reset code ‘RET code’ is an empty signal which occurs equal or more than the Treset. This reset code is meant for registering, or saving the colors in the LEDs once the system has done sending the pulses.

The pulses must adhere to the timing specifications (T0H, T0L, T1H, T1L) or else the LED does not accept the bit sent out by the microcontroller. The timing table is as follows:

T0H

0.35 us

T0L

0.80 us

T1H

0.70 us

T1L

0.60 us

Treset

above 50 us

TxH + TxL

1.25 us

By looking at the timing, it looks pretty tough and tight for smaller 8-bit microcontrollers like Microchip’s Atmega328P in Arduino and PIC16/18F. Many 8-bit microcontrollers are not capable of delivering pulses that are very short if the program is written in C. Hence the driver program for these microcontrollers must be written in assembly language so that the timings are perfect. To make matters worse, not all 8-bit microcontrollers are blazing fast! Some microcontrollers like Microchip’s Atmega328P, when run at the maximum speed of 16 MIPS could drive these LEDs without a hitch, but the program must be written in assembly. Here’s a small part of the code from Adafruit’s Neopixel library:

That’s one tough and impressive work! But sadly, we can’t use the same assembler code to other brands of microcontrollers without heavy modifications. No worries though, this tutorial can show you another way to drive these LEDs.

If you look back at the sequence chart earlier, you may notice that each bit looks like a Pulse Width Modulation pulse. Then, to send the data to the LED, we can program the microcontroller to send these PWM pulses for 24 times:

1.) Extract bit from the 24-bit color data, starting from the Most Significant Bit (MSB).

2.) Convert bit 1 or 0 to the respective PWM duty cycle.

3.) Place this value into the PWM duty cycle register.

4.) Wait for timer to go up.

5.) Go back to (1) and extract get the next bit. Repeat for 24 times!

That looks easy. You are now relieved from counting cycles, or writing tight assembly code. One issue is, the microcontroller still have to wait for the timer to go up! It will be fine for situations where you don’t need to keep updating the LEDs very often but if you love to show dancing colors while doing something else (like calculating the changes of colors), this isn’t too practical.

All is not lost though because in this tutorial, we are using a 32-bit PIC32 microcontroller in our SK1632 which contains a DMA (direct memory access) module inside. Read on to see how the DMA works.

PIC32’s DMA module, an introduction

DMA stands for Direct Memory Access. With this feature, data is directly sent to or received from the peripherals such as GPIO, PWM, ADC, DAC without the involvement of the CPU. Therefore the CPU is not being occupied when the data is being copied to or from the peripherals to the memory.

Here is an example of sending multiple data from the memory to the peripheral with or without using DMA:

To read more about the Direct Memory Access here are some useful articles:

So after knowing about how DMA works, let’s apply this to the PIC32 microcontroller!

PIC32’s DMA module

The PIC32’s DMA module is fairly complicated and there are a lot of options to play around. Here, we are coupling the PWM module to the DMA module so that the bits can be sent more efficently without the CPU’s involvement.

Do not worry if these look very complicated. You will be only briefed on the most important parts of the code fragment.

DCH0SSIZ (DMA channel 0 source size) stores the size of the source memory. Here, it is 25 for example. The destination size, of course, is 1 byte, so we set this DCH0DSIZ (DMA channel 0 destination size) to 1. Meanwhile, we set this DCH0CSIZ (DMA channel 0 cell size) to 1 because we transfer 1 byte each to the destination.

To sum this up: “25 bytes source, each transfer from the source is one byte and the destination register is one byte size. Each transfer is triggered by the Timer2’s interrupt. For the DMA to complete its operation, 25 transfers are done.”

Finally, we need to start the transfer! In this tutorial, the transfers are only one-off, which means the DMA channel has to be re-enabled again each time it finishes its operation:

To adhere to the strict WS2812’s timing, the PR2 (period register 2) for the Timer2 must be set in accordance to the clock speed of the microcontroller:

OC1CONSET = 0x8000;
T2CONCLR = 0x8000;
PR2 = 25;

How do you get the “PR2 = 25”? Simple:

System Clock: 40MHz

Peripheral Clock: 40MHz/2 = 20MHz (divide by two)

Prescaler for Timer2: 1 (divide by one)

One Timer2 tick: 1/20MHz = 0.05uS

One time period for WS2812: 1.25uS (TxH + TxL in the table)

PR2 = 1.25uS/0.05uS = 25

After the value of PR2 is known, we can proceed to calculate the bit 0 and 1 lengths:

‘Bit 0’ high (T0H) = 0.35 us.

Value to be placed into OCxRS register = 0.35us / Timer2_tick

= 0.35us / 0.05us = 7.

‘Bit 1’ high (T1H) = 0.70 us.

Value to be placed into OCxRS register = 0.70us / Timer2_tick

= 0.70us / 0.05us = 14.

If we want to send 0b10100011 to the WS2812 for example to the PWM, the duty cycles are 14, 7, 14, 7, 7, 7, 14 and 14.

In the function ws2812_setColorStrip, after extracting the bits out from the 24-bit data, converting them into duty cycles and placing them into memory, we command the DMA to immediately streaming the data out.

“DCH0ECONbits.CHEN = 1” means channel 0 is enabled.

“DCH0ECONbits.CFORCE = 1” simply means force the DMA module to perform the operation. The timers are then enabled for the PWM to work.

It is required to shut off the Timer2, clear out the counter register (TMR2) and the duty cycle register (OC1RS) to avoid the Timer2 module to continue running and possibly disturb the next DMA transmission.

Let’s color the LEDs!

What do you need before you start experimenting with the tutorial:

Required components:

1.) MPLAB X IDE 3.26 or higher.

2.) MPLAB Harmony 1.07.01 or higher.

3.) MPLAB xc32 1.42 or higher.

4.) Cytron SK1632 with PIC32MX250F128B

5.) 2 or more Cytron RainbowBit.

6.) Breadboard and some wires.

7.) 0.1uF capacitor.

8.) AC adapter (12V) or smartphone charger to supply power to the SK1632.

Assemble the SK1632, RainbowBit and a capacitor according to the following picture:

Since the SK1632 cannot be powered by PICKit3 alone, you have to use an AC Adapter or a smartphone charger.

It’s straightforward! In main.c, for example, write 2 in the first parameter if you want to ‘paint’ only 2 LEDs, and last parameter is an address to the array:

ws2812_setColorStrip(2, ws2812array);

The array is earlier declared:

uint32_t ws2812array[16] = {0x000000, 0x000000};

If you want the 1st LED to be all Green, while the 2nd LED is to be all Blue, we put:

ws2812array[0] = 0xff0000;
ws2812array[1] = 0x00ff00;

Simple! Then call the function “ws2812_setColorStrip(2, ws2812array)”, compile and upload the program into the sk1632 and you will see two lit LEDs in green and red.

(The LEDs are extremely bright – please do not stare at them too long!)