[TUT] Android Things – APA102 7 LED Array, SPI on the Rainbow Hat

This blog post will explain how you can use the SPI protocol to shine an LED rainbow from your AndroidThings application. We’ll discuss the Raspberry Pi Rainbow hat and its actuators, in this case the seven rgb pixels which we’ll use to make a lovely kids style rainbow colored pattern.

Hardware Prerequisite:

We have the Rainbow Hat connected to the top of our raspberry pi.

Software Prerequisite:

An AndroidThings project already set up in Android Studio, if you aren’t sure how to get your project to this point you can view the ‘first Android Things app’ blog for instructions.

Let’s get started.

First we create an instance of PeripheralManagerService. This is the AndroidThings SDK class that allows us to open connections to different board pins using the different available protocols.

PeripheralManagerService service = new PeripheralManagerService();

Using this instance, we want to open a SPI connection to our strip of LEDs. The open method takes one parameter; the SPI address of the peripheral.

public class MainActivity extends Activity {

   private static final String APA102_RGB_7_LED_SLAVE = "SPI3.1";

   private SpiDevice bus;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       PeripheralManagerService service = new PeripheralManagerService();
       try {
           bus = service.openSpiDevice(APA102_RGB_7_LED_SLAVE);
       } catch (IOException e) {
           throw new IllegalStateException(APA102_RGB_7_LED_SLAVE + " connection cannot be opened.", e);
       }
}

Since we opened the connection to our pin in the onCreate method, we should use the symmetrically matching pair from the Android Lifecycle to close the connection.

@Override
protected void onDestroy() {
   try {
       bus.close();
   } catch (IOException e) {
       Log.e("TUT", APA102_RGB_7_LED_SLAVE + "connection cannot be closed, you may experience errors on next launch.", e);
   }
   super.onDestroy();
}

With SPI peripherals there are three attributes that make up the “mode” of communication. Setting the mode is important for correct data transfer. They are the idle level, leading edge and trailing edge. The combination of these choices make up the mode.

I actually couldn’t find documentation of the mode or these attributes described in simple terms for this peripheral, therefore to keep things simple I found through trial and error we will be using MODE2 for communication.

You can also set the frequency of communication, the endianness i.e if bytes should be read forwards or backwards and the number of bits per word. For all three of these the default values work for our device so we’ll just use those.

//  onCreate
       try {
           bus.setMode(SpiDevice.MODE2);
//            bus.setFrequency(1_000_000); // 1Mhz
//            bus.setBitsPerWord(8);
//            bus.setBitJustification(false); // MSB first
       } catch (IOException e) {
           throw new IllegalStateException(APA102_RGB_7_LED_SLAVE + " cannot be configured.", e);
       }

Our LED strip takes RGB values for each of its 7 leds. I have created another class here called Color and set up an array of 7 colors that will make up our rainbow. Let’s use this class to separate color creation and SPI communication. Now we’re ready to sing a rainbow in the MainActivity.

final class Color {
   private static final Color RED = new Color(50, 0, 0);
   private static final Color YELLOW = new Color(50, 50, 0);
   private static final Color PINK = new Color(50, 10, 12);
   private static final Color GREEN = new Color(0, 50, 0);
   private static final Color PURPLE = new Color(50, 0, 50);
   private static final Color ORANGE = new Color(50, 22, 0);
   private static final Color BLUE = new Color(0, 0, 50);

   static final Color[] RAINBOW = {
           Color.RED, Color.YELLOW, Color.PINK,
           Color.GREEN,
           Color.PURPLE, Color.ORANGE, Color.BLUE
   };

   int r;
   int g;
   int b;

   private Color(int r, int g, int b) {
       this.r = r;
       this.g = g;
       this.b = b;
   }
}

Now we have our SPI peripheral configured and ready to show our rainbow. Let’s write some data to it to turn on the LEDs. We’ll do this in onStart to keep a separation from the onCreate configuration code.

First step, we’ll be using the write method of the SPI bus api. This allows us to send a byte array of any length we need for our data. Here is the code.

try {
   bus.write(data, data.length); // Data is a byte array
} catch (IOException e) {
   throw new IllegalStateException(APA102_RGB_7_LED_SLAVE + " cannot be written to.", e);
}

Note: All the specific information about the LED strip that we are about to discuss, if you are looking for it as a reference, is in the datasheet here.

The documentation explains we will need a 37 byte update transaction. So let’s create that as our data array. It looks like this.

private static final int TRANSACTION_SIZE = 37;

// in onStart
byte[] data = new byte[TRANSACTION_SIZE];

Next is a start frame of 32 bits. Each byte is 8 bits, therefore 32 divided by 8 is 4, we need 4 bytes of zero bits. Let’s do that in a simple loop with this code.

private static final int ZERO_BITS = 0b0;

// in onStart
for (int i = 0; i <= 3; i++) {
   data[i] = ZERO_BITS;
}

The docs then say to send the data for each of our 7 leds in the strip. Each LED requires 32 bits, which is 4 bytes, first byte is the start frame plus the brightness, then 1 byte each for blue, green, red in that order.

For the 7 LEDs let’s create a loop. Now, we are going to have a fixed brightness of 3 so let’s create that byte combining it with the start frame byte as a constant.

Each color segment of blue, green, red is going to take its color from our RAINBOW constant i showed earlier. Using this in the loop and assigning blue, green, red to each byte looks like this.

int p = 4;
for (int i = 0; i < 7; i++) {
   data[p++] = LED_BRIGHT_START_BYTE;
   Color color = Color.RAINBOW[i];
   data[p++] = (byte) color.b;
   data[p++] = (byte) color.g;
   data[p++] = (byte) color.r;
}

Finally the docs say it needs an end frame of at least 36 bits, 36 divided by 8 rounded up is 5 bytes, so we loop for the final 5 bytes setting them to all zeros with this code.

for (int i = 32; i < TRANSACTION_SIZE; i++) {
   data[i] = ZERO_BITS;
}

We’ve created our 37 byte update transaction, and remember we are writing it to the S P I bus API write method. This is all done in the onStart method, as a summary:

@Override
protected void onStart() {
   super.onStart();

   byte[] data = new byte[TRANSACTION_SIZE];
   for (int i = 0; i <= 3; i++) {
       data[i] = ZERO_BITS;
   }
   int p = 4;
   for (int i = 0; i < 7; i++) {
       data[p++] = LED_BRIGHT_START_BYTE;
       Color color = Color.RAINBOW[i];
       data[p++] = (byte) color.b;
       data[p++] = (byte) color.g;
       data[p++] = (byte) color.r;
   }
   for (int i = 32; i < TRANSACTION_SIZE; i++) {
       data[i] = ZERO_BITS;
   }

   try {
       bus.write(data, data.length);
   } catch (IOException e) {
       throw new IllegalStateException(APA102_RGB_7_LED_SLAVE + " cannot be written to.", e);
   }
}

That’s it!

The rainbow hat is now showing the APA102 LED Strip shining a beautiful rainbow. For a next step change, you could make it flash by editing the code to change the colors in a loop.

You now understand how the Android Things SPI output communication with peripherals works. You can interface with an APA102 LED strip controlling each LEDs brightness and colour individually but updating simultaneously. This knowledge also transfers to other SPI peripherals such as other types of LED strips, pressure sensors, potentiometers or graphical displays.

The source is on GitHub and all the code is available here.

If you are more of an audible learning, or prefer video content. You can check out my Android Things introductory video course on Caster.io.

Leave a Reply

Your email address will not be published. Required fields are marked *