PWM Expanders for Particle

Updated 14 April 2022

Welcome, have you had a good day? Fantastic, unless of course, you haven’t had a good day so far, in which case don’t worry, it’s about to get better! Today we’re going to be learning about adding PWM channels to our microcontroller by using PWM expanders. We’ll be using the Particle Photon board for the examples, however, a Particle Electron will also work, and the code and circuit diagram can be easily adapted to any Arduino compatible board as well, which is pretty neat. If you’re not sure what PWM is, but it sounds cool and you’d like to know more, check out our ‘Analog to Digital Conversion for Raspberry Pi’ tutorial. It uses the Raspberry Pi as an example, but the information on PWM is relevant to any platform.

If you’ve ever been working on a project with lots of outputs involving PWM, you’ll know that most microcontrollers don’t have a huge number of PWM channels. And if you want to dim a whole bunch of LEDs individually, or control a bunch of servos, for example, then it’s often far more economical to use a PWM expander rather than upgrade the entire microcontroller just for the sake of some PWM channels.

Before we get cracking, it’s important to bear in mind that we’ll be talking about using the I2C bus and hexadecimal numbers so if you’re not comfortable with these, consider checking out our ‘Hexadecimal, Binary and Decimal’ and ‘I2C Bus with Arduino tutorials. So, what is a PWM expander?

The Gear

To follow this tutorial you'll require the following:

Pimoroni SN3218 breakout boardWhat is a PWM Expander?

A PWM expander is a chip that accepts a serial input data to produce parallel PWM outputs. Or to put it another way, you can control them using only a couple of pins (usually I2C which only requires 2), and they’ll allow you to control multiple PWM outputs by writing to the chip’s internal registers.

There are lots of PWM expanders to choose from, and the code we’ll be using today isn’t going to work on all of them as it’s designed for the specific chip we’ll use today; the SN3218. Fortunately, most PWM expanders operate in a pretty similar way that’s quite straight forward. The general flow is:

  1. Enable the output channels
  2. Set control registers
  3.  Set the update register

The third step isn’t always true of every chip. Some chips will store the values that you write for each channel in a temporary register and will only output those values when a certain command is given. This allows you to set multiple channels, then output them all at once which can be useful for avoiding delays in changes. Other chips will simply pass the control commands directly to the channels.

The SN3218, for example, requires you to write to register 0x16 in order to output the values stored in the temporary register to the channels, but we’ll get to that further down.

Using the SN3218

The SN3218 chip is actually fairly easy to understand. It uses the standard I2C interface and the required registers and values that need to be written can be found in the datasheet. The datasheet also contains a description of what needs to be written to the SN3218 in order to use the PWM outputs correctly. Here are the pages from the datasheet which detail the registers and operating procedure with the I2C bus:

 

SN3218 datasheet-i2c-1

SN3218 datasheet-i2c-2

SN3218 datasheet-i2c-3

Now while the datasheet shows what needs to be done, it can often be confusing trying to read through the lines and find exactly what needs to be sent along the I2C bus in order to get the outputs to work correctly, so here is a bit of a TL;DR:

First of all, the default address of the chip is 0x54. It is pre-set in the silicon and cannot be changed. This means that you can’t operate multiple Sn3218 chips on the same I2C bus, whereas other PWM expanders allow you to do so.

Each function of the chip is controlled by a register, and the value of that register determines how it operates. The process for writing to these registers is to send two bytes of data in each transmission. The first byte will be the address of the register and the second byte is the value we want to write to the register. You can see this format described in the image below (taken from the datasheet):

SN3218 I2C transmission format

So if we wanted to set register 0x13 as 0x0F, then the basis of our code would look like this:

  • Begin transmission to address 0x54
  • Write 0x13 to the bus
  • Write 0x0F to the bus
  • End transmission

Anyway, we’re getting ahead of ourselves, we’ll get to the code later on. Let’s now take a look at the process of controlling the channel outputs:

  • Enable LED Control Registers 1-3 which are addressed as 0x13, 0x14, and 0x15.
  • Write to the corresponding PWM register for the channel you want to control. The channels are zero indexed and as such are numbered 0-17. The registers for these channels as addressed as 0x01 through to 0x12. You then write a value between 0-255 (0x00 – 0xFF) to set the output for that channel.
  • Update the PWM values to the output channels by writing any value to the register 0x16.

Ta da! Not as complex and confusing as the datasheet would have you believe, but fairly understandable once you break it up into its parts.

Whilst it’s good to understand the lower level functionality of the chip, why reinvent the wheel? The good team from Pimoroni have created an Arduino library for the SN3218 and with some slight tweaks, it works just as well for Particle. We’ll get to adding that library once we get to the code section further below. Now let’s get everything rolling, we’ll take a look at setting up the circuit.

The Circuit

Connecting your SN3218 board up is incredibly easy since it operates using I2C. Simply connect the SDA (data) pin to the SDA pin on the Photon (D0), and the SCL (clock) pin to the SCL pin on the Photon (D1). Because the Photon is a 3.3V board, we’ll connect the VCC pin up to the 3.3V output, and connect up the ground pins.

The SN3218 switches the negative side of the load which requires us to connect the anode of the LEDs up to 5V because the onboard current limiting resistor is calculated for 5V (you could use 3.3V, but the LEDs would be dimmer). Then connect the cathodes (negative leg) up to each individual channel you wish to control.

For the example code provided below, we’ll be using all 18 LEDs to create a Serial monitor controlled LED display. You could, of course, use fewer LEDs and modify the code to suit.

The Code

As we mentioned earlier, whilst gaining and understanding of the control required to use the SN3218 can be useful and helpful, there’s no need to rewrite a library that already exists, so we can simply modify the Pimoroni Arduino library for the SN3218 to work with the Particle boards.

The text files for the libraries are attached to this tutorial. Save them onto your computer, and then use the ‘ ’ tab in the Particle IDE to add files to your application. Copy the (.h) text file into the .h tab and the (.cpp) file into the .cpp tab.

Particle IDE screenshot for adding header files

Now to test it out we’ll be using Serial terminal commands to turn the LEDs on/off and print instructions for the user. Whilst the Arduino IDE includes a serial monitor, the Particle IDE (being online) does not. So if you’re not sure how to download and get started with a serial terminal application check out our ‘Serial Monitoring with Tera Term’ tutorial for more info.

Otherwise, flash the code onto your Photon (or Electron), and open up the Serial terminal and enjoy using the SN3218A. Pretty cool huh?

#include "sn3218.h"

const int ledCount = 18;

int checked;

//These messages are used through the code for feedback and instructions, feel free to modify them to your own project
String welcome = "Hello there young tinkerer";
String requestChannel = "What channel would you like to use? (0-17): ";
String requestBrightness = "Good, Good. Now choose a brightness value (0-255): ";
String errorMsg = "Oh dear, you a not strong in the ways of following instructions, please try again.";
String success = "Well down young padawan!";

void setup()
{
    sn3218.begin();
    sn3218.reset();
    sn3218.enable_leds(SN3218_CH_ALL);
    Serial.begin(9600);
    Serial.println(welcome);
    delay(1500);
    Serial.println(requestChannel);
    
    Serial.setTimeout(999999);
}

void loop()
{
    if(Serial.available())
    {
        unsigned char channel = Serial.parseInt();
        while(checked != 1)
        {
            if(Serial.available())
            {
                char test = Serial.read();
                if(test == '\r')
                {
                    checked = 1;
                }
            }
            Particle.process();
        }
        checked = 0;
        
        Serial.println(channel);
    
        if(channel >= ledCount | channel < 0)
        {
            Serial.println(errorMsg);
            Serial.println("");
            Serial.println(requestChannel);
            return;
        }
        else
        {
            Serial.print("Channel: ");
            Serial.println(channel);
            Serial.println(requestBrightness);
            while(!Serial.available())
            {
                Particle.process();
            }
            if(Serial.available())
            {
                unsigned char brightness = Serial.parseInt();
                while(checked != 1)
                {
                    if(Serial.available())
                    {
                        char test = Serial.read();
                        if(test == '\r')
                        {
                            checked = 1;
                        }
                    }
                    Particle.process();
                }
                checked = 0;
                
                if(brightness > 255)
                {
                    Serial.println(errorMsg);
                    Serial.println("");
                    Serial.println(requestChannel);
                    return;
                }
                else
                {
                    Serial.print("Brightness: ");
                    Serial.println(brightness);
                    setChannels(channel, brightness);  
                }
            }
        }
    }
}

void setChannels(unsigned char channel, unsigned char brightness)
{
   sn3218.set(channel, brightness); 
   sn3218.update();
   Serial.println(success);
   Serial.println("");
   Serial.println(requestChannel);
}

Hopefully, you’ve learnt about how valuable PWM expanders can be to your project, and how they work. They’re quite simple to use and are a great way of adding more PWM output pins to your project. Happy making!

Attachment - PWM_Expanders_with_Particle.zip

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.

Comments


Loading...
Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.