C example for Unicorn hat HD


#1

Hello!

I picked up a couple of unicorn hat he’s and love them, I have a C++/OpenGL app that I’d like to send the output to the hat, but I only see python example code. Is there some C code coming like there is for the original unicorn hat?

Many thanks!


#2

We don’t normally include C examples for any of our libraries, since it’s a can of worms that we don’t have the time or manpower to support. That said, it should be pretty trivial to drive Unicorn HAT HD from C.

The only relevant parts of the Python library are setting up the SPI: https://github.com/pimoroni/unicorn-hat-hd/blob/master/library/unicornhathd/init.py#L19-L21
And sending the buffer: https://github.com/pimoroni/unicorn-hat-hd/blob/master/library/unicornhathd/init.py#L127

The buffer is just a block of 768 (RGB * 16 * 16) bytes blasted straight out to Unicorn HAT HD, with a Start Of Frame byte preceding them: 0x72

Your buffer, in C, might as well just be a flat array of 768 bytes, or even 769 with the SOF byte already dropped in place. Something like (code typed from memory and not tested!):

uint8_t buffer[769];
buffer[0] = 0x72;

void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
    uint16_t offset = (x * 16) + y;
    buffer[offset + 0] = r;
    buffer[offset + 1] = g;
    buffer[offset + 2]  = b;
}

And you could use the BCM2835 SPI library: http://www.airspayce.com/mikem/bcm2835/group__spi.html

Or WiringPi’s SPI library: http://wiringpi.com/reference/spi-library/


#3

Thanks for the pointer, it was almost absurdly easy to put together a very quick test using the WiringPi SPI library and was up and running in no time at all!

One thing to be careful of is the wiringPi method clears the buffer out that you send it which includes the SOF byte 0x72, so make sure you completely fill out the buffer with information every time you send…

Here is a copy of there code for anyone wanting to do something similar:

/*
 --- nebulus UnicornHat HD test ---
 
 to compile cut and paste the following in a terminal,
 changing the filename as necessary:
 
 gcc test-uhhd.c -o test-uhhd -l wiringPi
 
 */

#include <stdio.h>
#include <strings.h>
#include <wiringPiSPI.h>

#define SPI_CHANNEL 0
#define SPI_SPEED   9000000

#define PACKET_SIZE (1 + ( 16 * 16 * 3 ))

int main( int argc, char *argv[] )
{
  if( wiringPiSPISetup( SPI_CHANNEL, SPI_SPEED ) == -1 )
  {
    printf("Could not initialise SPI\n");
    return( 1 );
  }
  
  // buffer for pixel data and pixel we address on each loop
  unsigned char buffer[PACKET_SIZE];
  int pixel = 0;
  
  while( 1 )
  {
    // calculate the address in the buffer the pixel we are going to paint
    int index = 1 + (pixel*3);
    
    // setup the buffer
    buffer[0] = 0x72;   // SOF byte
    
    // colour one pixel purple -> yellow depending upon pixel index
    buffer[index] = 255;          // Red
    buffer[index+1] = pixel;      // Green
    buffer[index+2] = 255-pixel;  // Blue

    // send off the buffer
    wiringPiSPIDataRW( SPI_CHANNEL, buffer, PACKET_SIZE ) ;
    
    // sleep a bit
    usleep( (int)(1000000/120) );
    
    // move to the next pixel
    pixel++;
    if( pixel >= 256 ) pixel = 0;
  }
}

#4

After working through the code that folks have posted here (which is awesome) and reaching back to my undergrad years (which is farther back than I care to think about) to recall enough C to try and work on this I came up with the following. Nebulus’ point about the wiringPi method clearing out the buffer is important. I don’t know if folks will find this helpful but here it is.

//
// A simulated (random) bar graph displayed on the Unicorn Hat HD from
// pimoroni (https://shop.pimoroni.com/products/unicorn-hat-hd)
//

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <wiringPiSPI.h>

#define SPI_CHANNEL 0
#define SPI_SPEED   9000000

#define PACKET_SIZE (1 + ( 16 * 16 * 3 ))

unsigned char buffer[PACKET_SIZE];

// With the exception of red, green and blue the color names are
// best guesses I could come up with. I was shooting for a reasonable
// gradient where each color was discernable from the next.
int colors[16][3] = {
    {255,153,255},  // light-pink
    {255,102,255},  // pink
    {255,0,255},    // fuschia
    {153,0,255},    // violet
    {102,0,255},    // indigo
    {0,0,255},      // blue
    {0,102,255},    // cyan
    {0,153,255},    // baby-blue
    {0,255,255},    // teal
    {0,255,102},    // seafoam
    {0,255,0},      // green
    {153,255,0},    // pale-green
    {255,255,0},    // yellow
    {255,153,0},    // orange
    {255,102,0},    // red-orange
    {255,0,0}       // red
};

// It took me a little while to work out how to properly address each
// pixel. Once I had the the (x,y) to array index formula worked out
// I used the following python script to generate a table to confirm.
//
// print('| {0:>2s} | {1:>2s} | {2:>3s} | {3:>3s} | {4:>3s} |'.format(
//     'x', 'y', 'r', 'g', 'b'))
// print('=============================')
// for x in range(16):
//     for y in range(16):
//         r = 1 + ((y * 16) + x) * 3
//         g = r + 1
//         b = r + 2
//         print('| {0:2d} | {1:2d} | {2:3d} | {3:3d} | {4:3d} |'.format(
//
// | Coords  | Color Indices   |
// |  x |  y |   r |   g |   b |
// =============================
// |  0 |  0 |   1 |   2 |   3 |
// |  0 |  1 |  49 |  50 |  51 |
// |  0 |  2 |  97 |  98 |  99 |
// |  0 |  3 | 145 | 146 | 147 |
// |  0 |  4 | 193 | 194 | 195 |
// |  0 |  5 | 241 | 242 | 243 |
//            .
//            .
//            .
// | 15 | 10 | 526 | 527 | 528 |
// | 15 | 11 | 574 | 575 | 576 |
// | 15 | 12 | 622 | 623 | 624 |
// | 15 | 13 | 670 | 671 | 672 |
// | 15 | 14 | 718 | 719 | 720 |
// | 15 | 15 | 766 | 767 | 768 |
//
// All this the bottom of the matrix along the GPIO edge of the RPi.
// If you swap x and y in the offset equation it will rotate the display
// 90 degrees widdershins (counter-clockwise) so the bottm of the graph
// is now along the Micro SD card edge.
//
// print('| {0:7s} | {1:15s} |'.format('Coords', 'Color Indices'))
// print('| {0:>2s} | {1:>2s} | {2:>3s} | {3:>3s} | {4:>3s} |'.format(
//     'x', 'y', 'r', 'g', 'b'))
// print('=============================')
// for x in range(16):
//     for y in range(16):
//         r = 1 + ((x * 16) + y) * 3
//         g = r + 1
//         b = r + 2
//         print('| {0:2d} | {1:2d} | {2:3d} | {3:3d} | {4:3d} |'.format(
//             x, y, r, g, b))
// | Coords  | Color Indices   |
// |  x |  y |   r |   g |   b |
// =============================
// |  0 |  0 |   1 |   2 |   3 |
// |  0 |  1 |   4 |   5 |   6 |
// |  0 |  2 |   7 |   8 |   9 |
//              .
//              .
//              .
// | 15 | 13 | 760 | 761 | 762 |
// | 15 | 14 | 763 | 764 | 765 |
// | 15 | 15 | 766 | 767 | 768 |

void set_pixel(int x, int y, int r, int g, int b) {
    // Sets specified pixel with an RGB value.
    // Find the starting position in the array.
    int offset = 1 + ((y * 16) + x) * 3;
    buffer[offset + 0] = r;         // led red value
    buffer[offset + 1] = g;         // led green value
    buffer[offset + 2] = b;        // led blue value
}

int main( int argc, char *argv[] )
{
    srand(time(0));
    if( wiringPiSPISetup( SPI_CHANNEL, SPI_SPEED ) == -1 )
    {
        printf("Could not initialise SPI\n");
        return( 1 );
    }
    // setup the buffer and set the Start of Frame byte.
    buffer[0] = 0x72; // SOF byte

    // Set buffer to zero so we start out with a blank matrix.
    for (size_t i = 1; i < 770; i++) {
        buffer[i] = 0;
    }

    // What follows just iterates over each column of LEDs (x) and
    // then picks a row height (y) for that column. Each x,y value is
    // then set to the appropriate color.
    while (1) {
        for (size_t x = 0; x < 16; x++) {
            buffer[0] = 0x72; // SOF byte
            int y_height = rand() / (RAND_MAX / 15) + 1;
            for (size_t y = 0; y < y_height; y++) {
                set_pixel(x, y, colors[x][0], colors[x][1], colors[x][2]);
            }
        }
        // Write the buffer to the Unicorn Hat HD via SPI.
        wiringPiSPIDataRW( SPI_CHANNEL, buffer, PACKET_SIZE );
        // Based on experimentation without sleeping updates to the
        // Unicorn Hat HD seem to happen too often for it to display
        // anything.
        usleep( (int)(1000) );
    }
}

#5

Now, if I can wrap my head around the pivumeter code I’ll see if I can’t come up with a device section for the Unicorn Hat HD. Since I know next to nothing about ALSA, spectrum analyzers, linear algebra and Fast Fourier Transforms this should be an interesting and educational endeavor.