ws2812b sample firmware.

Dependencies:   BurstSPI

Dependents:   mbed_ws2812b mbed_ws2812b

Fork of PixelArray by Jacob Bramley

Files at this revision

API Documentation at this revision

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

BurstSPI.lib Show annotated file Show diff for this revision Revisions of this file
neopixel-spi.cpp Show diff for this revision Revisions of this file
neopixel-spi.h Show diff for this revision Revisions of this file
neopixel.cpp Show annotated file Show diff for this revision Revisions of this file
neopixel.h Show annotated file Show diff for this revision Revisions of this file
--- /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