ws2812b sample firmware.
Dependencies: BurstSPI
Dependents: mbed_ws2812b mbed_ws2812b
Fork of PixelArray by
Revision 2:3c3c41774cdf, committed 2014-08-01
- Comitter:
- JacobBramley
- Date:
- Fri Aug 01 22:17:23 2014 +0000
- Parent:
- 1:adbbe3e9f2c5
- Child:
- 3:6f392fcb1d3b
- Commit message:
- v1.0
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BurstSPI.lib Fri Aug 01 22:17:23 2014 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/Sissors/code/BurstSPI/#b862ffb6c5e2
--- a/neopixel-spi.cpp Fri Jul 25 15:18:03 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -#include <stdint.h> -#include "mbed.h" -#include "neopixel-spi.h" - -namespace neopixel { - -Array::Array(PinName out) : spi_(out, NC, NC) { - // TODO: WS2811 has a different timing specification. - - // WS2812 bit timings: - // '0': ----________ mark: 0.40us, space: 0.85us - // '1': --------____ mark: 0.80us, space: 0.45us - // The period is 1.25us, giving a basic frequency of 800kHz. - // Using three SPI bits per NeoPixel bit, we need an API frequency of 2.4MHz. - // '0': 100 mark: 0.42us, space: 0.83us - // '1': 110 mark: 0.83us, space: 0.42us - // These timings are well within the +/-150ns tolerance specified by WS2812. - - spi_.frequency(2400000); - spi_.format(12); // Send four NeoPixel bits in each packet. -} - -static void SendFourBits(BurstSPI& spi, uint32_t bits) { - static int const pattern_width = 3; - uint32_t word = 04444; // 0b100100100100 - - for (int i = 0; i < 4; i++) { - word |= ((bits >> 3) & 1) << (pattern_width * i + 1); - } - - spi.fastWrite(word); -} - -void Array::Update(Pixel buffer[], size_t length) { - // Pixels are sent as follows: - // - The first transmitted pixel is the pixel closest to the transmitter. - // - The most significant bit is always sent first. - // - Green is sent first, then red, then blue. - // - // g7,g6,g5,g4,g3,g2,g1,g0,r7,r6,r5,r4,r3,r2,r1,r0,b7,b6,b5,b4,b3,b2,b1,b0 - // \_____________________________________________________________________/ - // | _________________... - // | / __________________... - // | / / ___________________... - // | / / / - // GRB,GRB,GRB,GRB,... - - for (size_t i = 0; i < length; i++) { - SendFourBits(spi_, (buffer[i].green >> 4) & 0xf); - SendFourBits(spi_, (buffer[i].green >> 0) & 0xf); - SendFourBits(spi_, (buffer[i].red >> 4) & 0xf); - SendFourBits(spi_, (buffer[i].red >> 0) & 0xf); - SendFourBits(spi_, (buffer[i].blue >> 4) & 0xf); - SendFourBits(spi_, (buffer[i].blue >> 0) & 0xf); - } - spi_.clearRX(); - - wait_us(50); -} - -} \ No newline at end of file
--- a/neopixel-spi.h Fri Jul 25 15:18:03 2014 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#ifndef NEOPIXEL_SPI -#define NEOPIXEL_SPI - -#include <stdint.h> -#include "mbed.h" -#include "BurstSPI.h" - -namespace neopixel { - -struct Pixel { - uint8_t red; - uint8_t green; - uint8_t blue; -}; - -// Drive a chain of NeoPixels. The 'count' parameter specifies the length of the chain. -class Array { - public: - Array(PinName out); - - void Update(Pixel buffer[], size_t length); - - private: - BurstSPI spi_; -}; - -} - -#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/neopixel.cpp Fri Aug 01 22:17:23 2014 +0000 @@ -0,0 +1,103 @@ +#include <stdint.h> +#include "mbed.h" +#include "neopixel.h" + +namespace neopixel +{ + +PixelArray::PixelArray(PinName out, ByteOrder byte_order) + : spi_(out, NC, NC), byte_order_(byte_order) +{ + // WS281x bit encodings: + // '0': ----________ + // '1': --------____ + // The period is 1.25us, giving a basic frequency of 800kHz. + // Getting the mark-space ratio right is trickier, though. There are a number + // of different timings, and the correct (documented) values depend on the + // controller chip. + // + // The _real_ timing restrictions are much simpler though, and someone has + // published a lovely analysis here: + // http://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ + // + // In summary: + // - The period should be at least 1.25us. + // - The '0' high time can be anywhere from 0.0625us to 0.5us. + // - The '1' high time should be longer than 0.625us. + // + // These constraints are easy to meet by splitting each bit into three and packing them into SPI packets. + // '0': 100 mark: 0.42us, space: 0.83us + // '1': 110 mark: 0.83us, space: 0.42us + + spi_.frequency(2400000); // 800kHz * 3 + spi_.format(12); // Send four NeoPixel bits in each packet. +} + +static void SendFourBits(BurstSPI& spi, uint32_t bits) +{ + // Encode '0' bits as 100 and '1' bits as 110. + // We have this bit pattern: 00000000abcd + // We want this bit pattern: 1a01b01c01d0 + uint32_t ac = (bits * 0x088) & // 0abcdabcd000 + 0x410; // 0a00000c0000 + + uint32_t bd = (bits * 0x022) & // 000abcdabcd0 + 0x082; // 0000b00000d0 + + static uint32_t const base = 04444; // 100100100100 + + spi.fastWrite(base | ac | bd); // 1a01b01c01d0 +} + +void PixelArray::send_pixel(Pixel& pixel) +{ + // Pixels are sent as follows: + // - The first transmitted pixel is the pixel closest to the transmitter. + // - The most significant bit is always sent first. + // + // g7,g6,g5,g4,g3,g2,g1,g0,r7,r6,r5,r4,r3,r2,r1,r0,b7,b6,b5,b4,b3,b2,b1,b0 + // \_____________________________________________________________________/ + // | _________________... + // | / __________________... + // | / / ___________________... + // | / / / + // GRB,GRB,GRB,GRB,... + + if (byte_order_ == BYTE_ORDER_RGB) { + SendFourBits(spi_, (pixel.red >> 4) & 0xf); + SendFourBits(spi_, (pixel.red >> 0) & 0xf); + SendFourBits(spi_, (pixel.green >> 4) & 0xf); + SendFourBits(spi_, (pixel.green >> 0) & 0xf); + SendFourBits(spi_, (pixel.blue >> 4) & 0xf); + SendFourBits(spi_, (pixel.blue >> 0) & 0xf); + } else { + SendFourBits(spi_, (pixel.green >> 4) & 0xf); + SendFourBits(spi_, (pixel.green >> 0) & 0xf); + SendFourBits(spi_, (pixel.red >> 4) & 0xf); + SendFourBits(spi_, (pixel.red >> 0) & 0xf); + SendFourBits(spi_, (pixel.blue >> 4) & 0xf); + SendFourBits(spi_, (pixel.blue >> 0) & 0xf); + } +} + +void PixelArray::update(Pixel buffer[], uint32_t length) +{ + for (size_t i = 0; i < length; i++) { + send_pixel(buffer[i]); + } + + wait_us(latch_time_us_); +} + +void PixelArray::update(PixelGenerator generator, uint32_t length, uintptr_t extra) +{ + for (size_t i = 0; i < length; i++) { + Pixel out; + generator(&out, i, extra); + send_pixel(out); + } + + wait_us(latch_time_us_); +} + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/neopixel.h Fri Aug 01 22:17:23 2014 +0000 @@ -0,0 +1,146 @@ +#ifndef NEOPIXEL_H +#define NEOPIXEL_H + +#include <stdint.h> +#include "mbed.h" +#include "BurstSPI.h" + +namespace neopixel +{ + +/** Represent the value of a single pixel. + * + * Each channel uses the full 8 bits: 0x00 is fully off and 0xff is fully on. + */ +struct Pixel { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +/** Control the byte order used by the connected pixels. + * + * The vast majority of NeoPixels use a GRB byte order, so this is the default. + * A few use a RGB byte order. + * + * In principle, the WS281x controllers could be connected with _any_ byte + * ordering, but only GRB and RGB are supported at the moment. + */ +enum ByteOrder { + BYTE_ORDER_GRB, + BYTE_ORDER_RGB, +}; + +typedef void (*PixelGenerator)(Pixel* out, uint32_t index, uintptr_t extra); + +/** Control an array or chain of NeoPixel-compatible RGB LEDs. + * + * "NeoPixel" is Adafruit's name for WS2812- and WS2811-based addressable RGB + * LEDs. This library should work with any WS2811- or WS2812-based devices, as + * long as they support the fast-mode (800kHz) interface. + * + * Most example code uses bit-banging to generate the timed signal precisely. + * This library uses an SPI peripheral instead. The main advantage of this is + * that the chip can service interrupts and the like without disrupting the + * signal (as long as the interrupts don't take _too_ long). The main + * disadvantage is that it requires the use of an SPI peripheral. + * + * @note SPI peripherals will tend to leave the output pin ('MOSI') floating + * after a packet is sent. This will confuse the connected pixels, which expect + * the line to be driven low when idle. One way to fix this is to add a 10k + * resistor between 'MOSI' and ground so that it drops to '0' when not driven. + * Another method is to enable the on-chip pull-down resistor on the output pin. + * However, the mbed API only exposes this function through the DigitalIn and + * DigitalInOut classes. If you want to use the on-chip pull-down, you'll have + * to temporarily connect a DigitalIn peripheral _before_ creating instantiating + * the PixelArray. + * + * @code + * // Sample generator: Cycle through each colour combination, increasing the + * // brightness each time. `extra` is used as an iteration counter. + * void generate(neopixel::Pixel * out, uint32_t index, uintptr_t extra) { + * uint32_t brightness = (index + extra) >> 3; + * out->red = ((index + extra) & 0x1) ? brightness : 0; + * out->green = ((index + extra) & 0x2) ? brightness : 0; + * out->blue = ((index + extra) & 0x4) ? brightness : 0; + * } + * + * int main() { + * // Create a temporary DigitalIn so we can configure the pull-down resistor. + * // (The mbed API doesn't provide any other way to do this.) + * // An alternative is to connect an external pull-down resistor. + * DigitalIn(p5, PullDown); + * + * // The pixel array control class. + * neopixel::PixelArray array(p5); + * + * uint32_t offset = 0; + * while (1) { + * array.update(generate, 100, offset++); + * wait_ms(250); + * } + * } + * @endcode + */ +class PixelArray +{ +public: + /** Initialize a PixelArray. + * + * @param out Output (SPI MOSI) pin. + * @param byte_order The order in which to transmit colour channels. + */ + PixelArray(PinName out, + ByteOrder byte_order = BYTE_ORDER_GRB); + + /** Update the pixel display from a buffer. + * + * This update method is good in the following situations: + * - You want to make incremental changes to a fixed frame pattern. + * - The frame is hard (or impossible) to generate procedurally. + * - The frame requires a lot of time to generate. + * + * @param buffer Pixel data to be written. + * @param length The number of pixels to write. + * + * buffer[0] is written to the pixel nearest the mbed. + * buffer[length-1] is written to the pixel furthest from the mbed. + */ + void update(Pixel buffer[], uint32_t length); + + /** Update a pixel chain using the callback to generate the value for each + * pixel. + * + * This update method is good in the following situations: + * - You have a lot of pixels to drive and don't have enough RAM to buffer + * them all. + * - You want to display a frame pattern that can be generated procedurally + * generated without intensive processing. + * + * @param generator A callback which is called to generate a value for each + * pixel on demand. This function must be fairly fast: if it takes more + * than about 8-9us, the interface will reset and the display will be + * corrupted. The exact time limits will vary between WS281x variants. As a + * rough guide, an LPC1768 at 96MHz can (conservatively) execute about 750 + * instructions in that time. + * + * @param length The number of pixels to write. + * + * @param extra An arbitrary value to pass into the generator function. For + * example, this is a good way to pass an animation time index to the + * generator function. + */ + void update(PixelGenerator generator, uint32_t length, uintptr_t extra); + +private: + BurstSPI spi_; + ByteOrder byte_order_; + + static int const latch_time_us_ = 50; + + void send_pixel(Pixel& pixel); +}; + +} + +#endif \ No newline at end of file