Skip to content

File neopixel.h

File List > dev > neopixel.h

Go to the documentation of this file

Source Code

#pragma once
#ifndef DSY_NEO_PIXEL_H
#define DSY_NEO_PIXEL_H

#define NEO_TRELLIS_ADDR_NEOPIXEL (0x2E) 

// RGB NeoPixel permutations; white and red offsets are always same
// Offset:         W          R          G          B
#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1))
#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1))
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0))

// RGBW NeoPixel permutations; all 4 offsets are distinct
// Offset:         W          R          G          B
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3))
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2))
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3))
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2))
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1))
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1))

#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3))
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2))
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3))
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2))
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1))
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1))

#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3))
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2))
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3))
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2))
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1))
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1))

#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0))
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0))
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0))
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0))
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0))
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0))

// If 400 KHz support is enabled, the third parameter to the constructor
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
// is sufficient to encode pixel color order, saving some space.

#define NEO_KHZ800 0x0000 // 800 KHz datastream
#define NEO_KHZ400 0x0100 // 400 KHz datastream

namespace daisy
{
class NeoPixelI2CTransport
{
  public:
    NeoPixelI2CTransport() {}
    ~NeoPixelI2CTransport() {}

    struct Config
    {
        I2CHandle::Config::Peripheral periph;
        I2CHandle::Config::Speed      speed;
        Pin                           scl;
        Pin                           sda;

        uint8_t address;

        Config()
        {
            address = NEO_TRELLIS_ADDR_NEOPIXEL;

            periph = I2CHandle::Config::Peripheral::I2C_1;
            speed  = I2CHandle::Config::Speed::I2C_400KHZ;

            scl = Pin(PORTB, 8);
            sda = Pin(PORTB, 9);
        }
    };

    inline void Init(Config config)
    {
        config_ = config;

        I2CHandle::Config i2c_config;
        i2c_config.mode   = I2CHandle::Config::Mode::I2C_MASTER;
        i2c_config.periph = config.periph;
        i2c_config.speed  = config.speed;

        i2c_config.pin_config.scl = config.scl;
        i2c_config.pin_config.sda = config.sda;

        error_ |= I2CHandle::Result::OK != i2c_.Init(i2c_config);
    }

    void Write(uint8_t *data, uint16_t size)
    {
        error_ |= I2CHandle::Result::OK
                  != i2c_.TransmitBlocking(config_.address, data, size, 10);
    }

    void Read(uint8_t *data, uint16_t size)
    {
        error_ |= I2CHandle::Result::OK
                  != i2c_.ReceiveBlocking(config_.address, data, size, 10);
    }

    void
    ReadLen(uint8_t reg_high, uint8_t reg_low, uint8_t *buff, uint16_t size)
    {
        uint8_t reg[2] = {reg_high, reg_low};
        Write(reg, 2);
        Read(buff, size);
    }

    void
    WriteLen(uint8_t reg_high, uint8_t reg_low, uint8_t *buff, uint16_t size)
    {
        // max write size of 126...
        if(size >= 126)
        {
            return;
        }

        uint8_t reg[128];

        reg[0] = reg_high;
        reg[1] = reg_low;

        for(int i = 0; i < size; i++)
        {
            reg[i + 2] = buff[i];
        }

        Write(reg, size + 2);
    }

    void Write8(uint8_t reg_high, uint8_t reg_low, uint8_t value)
    {
        uint8_t buffer[3];

        buffer[0] = reg_high;
        buffer[1] = reg_low;
        buffer[2] = value;

        Write(buffer, 3);
    }

    uint8_t Read8(uint8_t reg_high, uint8_t reg_low)
    {
        uint8_t buffer;
        ReadLen(reg_high, reg_low, &buffer, 1);
        return buffer;
    }

    bool GetError()
    {
        bool tmp = error_;
        error_   = false;
        return tmp;
    }

  private:
    I2CHandle i2c_;
    Config    config_;

    // true if error has occured since last check
    bool error_;
};

template <typename Transport>
class NeoPixel
{
  public:
    NeoPixel() {}
    ~NeoPixel() {}

    struct Config
    {
        typename Transport::Config transport_config;
        uint16_t                   type;
        uint16_t                   numLEDs;
        int8_t                     output_pin;

        Config()
        {
            type       = NEO_GRB + NEO_KHZ800;
            numLEDs    = 16;
            output_pin = 3;
        }
    };

    enum Result
    {
        OK = 0,
        ERR
    };

    typedef uint16_t neoPixelType;

    enum ModBaseAdd
    {
        SEESAW_STATUS_BASE  = 0x00,
        SEESAW_GPIO_BASE    = 0x01,
        SEESAW_SERCOM0_BASE = 0x02,

        SEESAW_TIMER_BASE     = 0x08,
        SEESAW_ADC_BASE       = 0x09,
        SEESAW_DAC_BASE       = 0x0A,
        SEESAW_INTERRUPT_BASE = 0x0B,
        SEESAW_DAP_BASE       = 0x0C,
        SEESAW_EEPROM_BASE    = 0x0D,
        SEESAW_NEOPIXEL_BASE  = 0x0E,
        SEESAW_TOUCH_BASE     = 0x0F,
        SEESAW_KEYPAD_BASE    = 0x10,
        SEESAW_ENCODER_BASE   = 0x11,
        SEESAW_SPECTRUM_BASE  = 0x12,
    };

    enum ModAddReg
    {
        SEESAW_NEOPIXEL_STATUS     = 0x00,
        SEESAW_NEOPIXEL_PIN        = 0x01,
        SEESAW_NEOPIXEL_SPEED      = 0x02,
        SEESAW_NEOPIXEL_BUF_LENGTH = 0x03,
        SEESAW_NEOPIXEL_BUF        = 0x04,
        SEESAW_NEOPIXEL_SHOW       = 0x05,
    };

    enum StatAddReg
    {
        SEESAW_STATUS_HW_ID   = 0x01,
        SEESAW_STATUS_VERSION = 0x02,
        SEESAW_STATUS_OPTIONS = 0x03,
        SEESAW_STATUS_TEMP    = 0x04,
        SEESAW_STATUS_SWRST   = 0x7F,
    };

    Result Init(Config config)
    {
        config_ = config;

        type    = config_.type;
        numLEDs = config_.numLEDs;
        pin     = config_.output_pin;
        pixels  = pixelsd;

        transport_.Init(config_.transport_config);

        SWReset();

        // 10 ms delay
        System::Delay(10);

        UpdateType(type);
        UpdateLength(numLEDs);
        SetPin(pin);

        return GetTransportError();
    }

    void Write(uint8_t reg_high, uint8_t reg_low, uint8_t *buff, uint8_t size)
    {
        transport_.WriteLen(reg_high, reg_low, buff, size);
    }

    void Write8(uint8_t reg_high, uint8_t reg_low, uint8_t value)
    {
        return transport_.Write8(reg_high, reg_low, value);
    }

    uint8_t Read8(uint8_t reg_high, uint8_t reg_low)
    {
        return transport_.Read8(reg_high, reg_low);
    }

    void ReadLen(uint8_t reg_high, uint8_t reg_low, uint8_t *buff, uint8_t len)
    {
        transport_.ReadLen(reg_high, reg_low, buff, len);
    }

    Result GetTransportError() { return transport_.GetError() ? ERR : OK; }

    void SWReset()
    {
        return Write8(SEESAW_STATUS_BASE, SEESAW_STATUS_SWRST, 0xFF);
    }

    void UpdateLength(uint16_t n)
    {
        // Allocate new data -- note: ALL PIXELS ARE CLEARED
        numBytes = n * ((wOffset == rOffset) ? 3 : 4);
        mymemset(pixels, 0, numBytes);
        numLEDs = n;

        uint8_t buf[] = {(uint8_t)(numBytes >> 8), (uint8_t)(numBytes & 0xFF)};
        Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF_LENGTH, buf, 2);
    }

    void UpdateType(neoPixelType t)
    {
        bool oldThreeBytesPerPixel = (wOffset == rOffset); // false if RGBW

        wOffset  = (t >> 6) & 0b11; // See notes in header file
        rOffset  = (t >> 4) & 0b11; // regarding R/G/B/W offsets
        gOffset  = (t >> 2) & 0b11;
        bOffset  = t & 0b11;
        is800KHz = (t < 256); // 400 KHz flag is 1<<8

        Write8(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_SPEED, is800KHz);

        // If bytes-per-pixel has changed (and pixel data was previously
        // allocated), re-allocate to new size.  Will clear any data.
        bool newThreeBytesPerPixel = (wOffset == rOffset);
        if(newThreeBytesPerPixel != oldThreeBytesPerPixel)
            UpdateLength(numLEDs);
    }

    inline bool CanShow(void) { return (System::GetUs() - endTime) >= 300L; }

    void Show(void)
    {
        // Data latch = 300+ microsecond pause in the output stream.  Rather than
        // put a delay at the end of the function, the ending time is noted and
        // the function will simply hold off (if needed) on issuing the
        // subsequent round of data until the latch time has elapsed.  This
        // allows the mainline code to start generating the next frame of data
        // rather than stalling for the latch.
        while(!CanShow())
            ;

        Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_SHOW, NULL, 0);

        endTime = System::GetUs(); // Save EOD time for latch on next call
    }

    // Set the output pin number
    void SetPin(uint8_t p)
    {
        Write8(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_PIN, p);
        pin = p;
    }

    // Set pixel color from separate R,G,B components:
    void SetPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b)
    {
        if(n < numLEDs)
        {
            if(brightness)
            { // See notes in setBrightness()
                r = (r * brightness) >> 8;
                g = (g * brightness) >> 8;
                b = (b * brightness) >> 8;
            }
            uint8_t *p;
            if(wOffset == rOffset)
            {                       // Is an RGB-type strip
                p = &pixels[n * 3]; // 3 bytes per pixel
            }
            else
            {                                // Is a WRGB-type strip
                p          = &pixels[n * 4]; // 4 bytes per pixel
                p[wOffset] = 0; // But only R,G,B passed -- set W to 0
            }
            p[rOffset] = r; // R,G,B always stored
            p[gOffset] = g;
            p[bOffset] = b;

            uint8_t  len    = (wOffset == rOffset ? 3 : 4);
            uint16_t offset = n * len;

            uint8_t writeBuf[6];
            writeBuf[0] = (offset >> 8);
            writeBuf[1] = offset;
            mymemcpy(&writeBuf[2], p, len);

            Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF, writeBuf, len + 2);
        }
    }

    void SetPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
    {
        if(n < numLEDs)
        {
            if(brightness)
            { // See notes in setBrightness()
                r = (r * brightness) >> 8;
                g = (g * brightness) >> 8;
                b = (b * brightness) >> 8;
                w = (w * brightness) >> 8;
            }
            uint8_t *p;
            if(wOffset == rOffset)
            {                       // Is an RGB-type strip
                p = &pixels[n * 3]; // 3 bytes per pixel (ignore W)
            }
            else
            {                                // Is a WRGB-type strip
                p          = &pixels[n * 4]; // 4 bytes per pixel
                p[wOffset] = w;              // Store W
            }
            p[rOffset] = r; // Store R,G,B
            p[gOffset] = g;
            p[bOffset] = b;

            uint8_t  len    = (wOffset == rOffset ? 3 : 4);
            uint16_t offset = n * len;

            uint8_t writeBuf[6];
            writeBuf[0] = (offset >> 8);
            writeBuf[1] = offset;
            mymemcpy(&writeBuf[2], p, len);

            Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF, writeBuf, len + 2);
        }
    }

    // Set pixel color from 'packed' 32-bit RGB color:
    void SetPixelColor(uint16_t n, uint32_t c)
    {
        if(n < numLEDs)
        {
            uint8_t *p, r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8),
                        b = (uint8_t)c;
            if(brightness)
            { // See notes in setBrightness()
                r = (r * brightness) >> 8;
                g = (g * brightness) >> 8;
                b = (b * brightness) >> 8;
            }
            if(wOffset == rOffset)
            {
                p = &pixels[n * 3];
            }
            else
            {
                p          = &pixels[n * 4];
                uint8_t w  = (uint8_t)(c >> 24);
                p[wOffset] = brightness ? ((w * brightness) >> 8) : w;
            }
            p[rOffset] = r;
            p[gOffset] = g;
            p[bOffset] = b;

            uint8_t  len    = (wOffset == rOffset ? 3 : 4);
            uint16_t offset = n * len;

            uint8_t writeBuf[6];
            writeBuf[0] = (offset >> 8);
            writeBuf[1] = offset;
            mymemcpy(&writeBuf[2], p, len);

            Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF, writeBuf, len + 2);
        }
    }

    // Convert separate R,G,B into packed 32-bit RGB color.
    // Packed format is always RGB, regardless of LED strand color order.
    uint32_t Color(uint8_t r, uint8_t g, uint8_t b)
    {
        return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
    }

    // Convert separate R,G,B,W into packed 32-bit WRGB color.
    // Packed format is always WRGB, regardless of LED strand color order.
    uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
    {
        return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8)
               | b;
    }

    // Query color from previously-set pixel (returns packed 32-bit RGB value)
    uint32_t GetPixelColor(uint16_t n) const
    {
        if(n >= numLEDs)
            return 0; // Out of bounds, return no color.

        uint8_t *p;

        if(wOffset == rOffset)
        { // Is RGB-type device
            p = &pixels[n * 3];
            if(brightness)
            {
                // Stored color was decimated by setBrightness().  Returned value
                // attempts to scale back to an approximation of the original 24-bit
                // value used when setting the pixel color, but there will always be
                // some error -- those bits are simply gone.  Issue is most
                // pronounced at low brightness levels.
                return (((uint32_t)(p[rOffset] << 8) / brightness) << 16)
                       | (((uint32_t)(p[gOffset] << 8) / brightness) << 8)
                       | ((uint32_t)(p[bOffset] << 8) / brightness);
            }
            else
            {
                // No brightness adjustment has been made -- return 'raw' color
                return ((uint32_t)p[rOffset] << 16)
                       | ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset];
            }
        }
        else
        { // Is RGBW-type device
            p = &pixels[n * 4];
            if(brightness)
            { // Return scaled color
                return (((uint32_t)(p[wOffset] << 8) / brightness) << 24)
                       | (((uint32_t)(p[rOffset] << 8) / brightness) << 16)
                       | (((uint32_t)(p[gOffset] << 8) / brightness) << 8)
                       | ((uint32_t)(p[bOffset] << 8) / brightness);
            }
            else
            { // Return raw color
                return ((uint32_t)p[wOffset] << 24)
                       | ((uint32_t)p[rOffset] << 16)
                       | ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset];
            }
        }
    }

    // Returns pointer to pixels[] array.  Pixel data is stored in device-
    // native format and is not translated here.  Application will need to be
    // aware of specific pixel data format and handle colors appropriately.
    uint8_t *GetPixels(void) const { return pixels; }

    uint16_t NumPixels(void) const { return numLEDs; }

    void Clear()
    {
        // Clear local pixel buffer
        mymemset(pixels, 0, numBytes);

        // Now clear the pixels on the seesaw
        uint8_t writeBuf[32];
        mymemset(writeBuf, 0, 32);
        for(uint8_t offset = 0; offset < numBytes; offset += 32 - 4)
        {
            writeBuf[0] = (offset >> 8);
            writeBuf[1] = offset;
            Write(SEESAW_NEOPIXEL_BASE, SEESAW_NEOPIXEL_BUF, writeBuf, 32);
        }
    }

    void SetBrightness(uint8_t b) { brightness = b; }

  private:
    void mymemcpy(uint8_t *dest, uint8_t *src, uint8_t len)
    {
        for(uint8_t i = 0; i < len; i++)
        {
            dest[i] = src[i];
        }
    }

    void mymemset(uint8_t *addr, uint8_t val, uint8_t len)
    {
        for(uint8_t i = 0; i < len; i++)
        {
            addr[i] = val;
        }
    }

    Config    config_;
    Transport transport_;

  protected:
    bool is800KHz,    // ...true if 800 KHz pixels
        begun;        // true if begin() previously called
    uint16_t numLEDs, // Number of RGB LEDs in strip
        numBytes;     // Size of 'pixels' buffer below (3 or 4 bytes/pixel)
    int8_t pin;

    uint8_t pixelsd[256]; // hopefully we won't need more than this...

    uint8_t brightness,
        *pixels,      // Holds LED color values (3 or 4 bytes each)
        rOffset,      // Index of red byte within each 3- or 4-byte pixel
        gOffset,      // Index of green byte
        bOffset,      // Index of blue byte
        wOffset;      // Index of white byte (same as rOffset if no white)
    uint32_t endTime; // Latch timing reference

    uint16_t type;

}; // namespace daisy

using NeoPixelI2C = NeoPixel<NeoPixelI2CTransport>;
} // namespace daisy
#endif