Permalink
Browse files

o Add LED-strip class

o Add simple example.
  • Loading branch information...
hzeller committed May 28, 2016
1 parent b91e6cb commit b3500172b63cee946ab1af43657703c3413ad3d8
Showing with 220 additions and 15 deletions.
  1. +1 −0 .gitignore
  2. +15 −0 examples/Makefile
  3. +34 −0 examples/simple.cc
  4. BIN hardware/pi-adapter.pdf
  5. +48 −0 include/led-strip.h
  6. +9 −8 include/multi-spi.h
  7. +1 −1 lib/Makefile
  8. +9 −6 lib/dma-multi-spi.cc
  9. +103 −0 lib/led-strip.cc
View
@@ -1,3 +1,4 @@
*~
*.o
*.a
+simple
View
@@ -0,0 +1,15 @@
+LIBRARY=../lib/libspixels.a
+
+LDFLAGS=-L../lib -lspixels
+INCLUDE_FLAGS=-I../include
+
+CXXFLAGS=-Wall -O3 $(INCLUDE_FLAGS)
+
+simple : simple.cc $(LIBRARY)
+ $(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
+
+$(LIBRARY):
+ $(MAKE) -C ../lib
+
+clean:
+ rm -f simple
View
@@ -0,0 +1,34 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+ * Simple example how to use the spixels library
+ */
+
+#include "led-strip.h"
+
+#include <unistd.h>
+
+#define FRAME_RATE 200
+
+using namespace spixels;
+
+int main() {
+ MultiSPI *spi = CreateDMAMultiSPI();
+
+ // Connect LED strips with 144 LEDs to connector P1 and P2
+ LEDStrip *strip1 = CreateWS2801Strip(spi, MultiSPI::SPI_P1, 144);
+ LEDStrip *strip2 = CreateWS2801Strip(spi, MultiSPI::SPI_P2, 144);
+ // ... register more strips here.
+
+ spi->FinishRegistration(); // Done registering all the the strips.
+
+ for (int i = 0;;++i) {
+ strip1->SetPixel(i % strip1->count(), 255, 0, 0); // red pixel
+ strip2->SetPixel(i % strip2->count(), 0, 255, 0); // green pixel.
+
+ spi->SendBuffers(); // Send all pixels out at once.
+ usleep(1000000 / FRAME_RATE);
+ }
+
+ delete strip1;
+ delete strip2;
+ delete spi;
+}
View
Binary file not shown.
View
@@ -0,0 +1,48 @@
+// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+// Copyright (C) 2016 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+//
+// For an example, https://github.com/hzeller/spixels/examples
+
+#ifndef SPIXELS_LED_STRIP_H
+#define SPIXELS_LED_STRIP_H
+
+#include <stdint.h>
+
+#include "multi-spi.h"
+
+namespace spixels {
+
+// Simplest possible way for a LED strip.
+class LEDStrip {
+public:
+ virtual ~LEDStrip() {}
+ virtual void SetPixel(int pos, uint8_t red, uint8_t green, uint8_t blue) = 0;
+ // TODO: set global brightness ?
+
+ // Return number of attached LEDs.
+ inline int count() const { return count_; }
+
+protected:
+ LEDStrip(int count) : count_(count) {}
+
+ const int count_;
+};
+
+// Factories for various LED strips.
+LEDStrip *CreateWS2801Strip(MultiSPI *spi, int connector, int count);
+LEDStrip *CreateLPD6803Strip(MultiSPI *spi, int connector, int count);
+}
+
+#endif // SPIXELS_LED_STRIP_H
View
@@ -12,18 +12,23 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
-#ifndef RPI_MULTI_SPI_H
-#define RPI_MULTI_SPI_H
+#ifndef SPIXELS_MULTI_SPI_H
+#define SPIXELS_MULTI_SPI_H
#include <stdint.h>
#include <stddef.h>
+namespace spixels {
// MultiSPI outputs multiple SPI streams in parallel on different GPIOs.
// The clock is on a single GPIO-pin. This way, we can transmit 25-ish
// SPI streams in parallel on a Pi with 40 IO pins.
// Current implementation assumes that all streams have the same amount
// of data.
// Also, there is no chip-select at this point (not needed for the LED strips).
+//
+// This can be used of course of LED strips (see led-strip.h for the API), but
+// for all kinds of other SPI data you want to send to multiple devices in
+// a fire-and-forget way.
class MultiSPI {
public:
// Names of the pin-headers on the breakout board.
@@ -49,11 +54,6 @@ class MultiSPI {
SPI_P16 = 20,
};
- // Create MultiSPI that outputs clock on the "clock_gpio" pin. The
- // "serial_bytes_per_stream" define the length of the transmission in
- // each stream. This creates the necessary buffers for streams, all
- // initialized to zero.
- explicit MultiSPI(int clock_gpio = SPI_CLOCK);
virtual ~MultiSPI() {}
// Register a new data stream for the given GPIO. The SPI data is
@@ -85,5 +85,6 @@ class MultiSPI {
// - Limited speed (1-2Mhz). Good for WS2801 which can't go faste
// anyway. Worse for LDP6803 or APA102 that can go much faster.
MultiSPI *CreateDMAMultiSPI(int clock_gpio = MultiSPI::SPI_CLOCK);
+}
-#endif // RPI_MULTI_SPI_H
+#endif // SPIXELS_MULTI_SPI_H
View
@@ -1,4 +1,4 @@
-LIB_OBJECTS=ft-gpio.o dma-multi-spi.o rpi-dma.o mailbox.o
+LIB_OBJECTS=ft-gpio.o dma-multi-spi.o rpi-dma.o mailbox.o led-strip.o
CFLAGS=-Wall -O3 $(INCLUDES) $(DEFINES)
CXXFLAGS=$(CFLAGS)
INCLUDES=-I../include -I.
View
@@ -77,6 +77,7 @@ static void *mmap_bcm_register(off_t register_offset) {
return result;
}
+namespace spixels {
namespace {
class DMAMultiSPI : public MultiSPI {
public:
@@ -102,12 +103,7 @@ class DMAMultiSPI : public MultiSPI {
GPIOData *gpio_shadow_;
size_t gpio_copy_size_;
};
-}
-
-// Factory.
-MultiSPI *CreateDMAMultiSPI(int clock_gpio) {
- return new DMAMultiSPI(clock_gpio);
-}
+} // end anonymous namespace
struct DMAMultiSPI::GPIOData {
uint32_t set;
@@ -225,3 +221,10 @@ void DMAMultiSPI::SendBuffers() {
dma_channel_->cs &= ~DMA_CS_ACTIVE;
dma_channel_->cs |= DMA_CS_RESET;
}
+
+
+// Public interface
+MultiSPI *CreateDMAMultiSPI(int clock_gpio) {
+ return new DMAMultiSPI(clock_gpio);
+}
+} // namespace spixels
View
@@ -0,0 +1,103 @@
+// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "multi-spi.h"
+#include "led-strip.h"
+
+static const int kBitPlanes = 16;
+
+// Do CIE1931 luminance correction and scale to maximum expected output bits.
+static int luminance_cie1931_internal(uint8_t c) {
+ const float out_factor = ((1 << kBitPlanes) - 1);
+ const float v = 100.0 * c / 255.0;
+ return out_factor * ((v <= 8) ? v / 902.3 : pow((v + 16) / 116.0, 3));
+}
+
+static int *CreateCIE1931LookupTable() {
+ int *result = new int[256]; // TODO: more of these when brightness control
+ for (int i = 0; i < 256; ++i) {
+ result[i] = luminance_cie1931_internal(i);
+ }
+ return result;
+}
+
+static int luminance_cie1931(uint8_t output_bits, uint8_t color) {
+ static const int *const luminance_lookup = CreateCIE1931LookupTable();
+ return luminance_lookup[color] >> (kBitPlanes - output_bits);
+}
+
+namespace spixels {
+namespace {
+class WS2801LedStrip : public LEDStrip {
+public:
+ WS2801LedStrip(MultiSPI *spi, int gpio, int count)
+ : LEDStrip(count), spi_(spi), gpio_(gpio) {
+ spi_->RegisterDataGPIO(gpio, count * 3);
+ }
+ virtual void SetPixel(int pos, uint8_t red, uint8_t green, uint8_t blue) {
+ if (pos < 0 || pos >= count_) return;
+ spi_->SetBufferedByte(gpio_, 3 * pos + 0, luminance_cie1931(8, red));
+ spi_->SetBufferedByte(gpio_, 3 * pos + 1, luminance_cie1931(8, green));
+ spi_->SetBufferedByte(gpio_, 3 * pos + 2, luminance_cie1931(8, blue));
+ }
+
+private:
+ MultiSPI *const spi_;
+ const int gpio_;
+};
+
+class LPD6803LedStrip : public LEDStrip {
+public:
+ LPD6803LedStrip(MultiSPI *spi, int gpio, int count)
+ : LEDStrip(count), spi_(spi), gpio_(gpio) {
+ const size_t bytes_needed = 4 + 2 * count + 4;
+ spi_->RegisterDataGPIO(gpio, bytes_needed);
+ // Four zero bytes as start-bytes for lpd6803
+ spi_->SetBufferedByte(gpio_, 0, 0x00);
+ spi_->SetBufferedByte(gpio_, 1, 0x00);
+ spi_->SetBufferedByte(gpio_, 2, 0x00);
+ spi_->SetBufferedByte(gpio_, 3, 0x00);
+ for (int pos = 0; pos < count; ++pos)
+ SetPixel(pos, 0, 0, 0); // Initialize all top-bits.
+ }
+ virtual void SetPixel(int pos, uint8_t red, uint8_t green, uint8_t blue) {
+ if (pos < 0 || pos >= count_) return;
+ uint16_t data = 0;
+ data |= (1<<15); // start bit
+ data |= luminance_cie1931(5, red) << 10;
+ data |= luminance_cie1931(5, green) << 5;
+ data |= luminance_cie1931(5, blue) << 0;
+
+ spi_->SetBufferedByte(gpio_, 2 * pos + 4 + 0, data >> 8);
+ spi_->SetBufferedByte(gpio_, 2 * pos + 4 + 1, data & 0xFF);
+ }
+
+private:
+ MultiSPI *const spi_;
+ const int gpio_;
+};
+} // anonymous namespace
+
+// Public interface
+LEDStrip *CreateWS2801Strip(MultiSPI *spi, int connector, int count) {
+ return new WS2801LedStrip(spi, connector, count);
+}
+LEDStrip *CreateLPD6803Strip(MultiSPI *spi, int connector, int count) {
+ return new LPD6803LedStrip(spi, connector, count);
+}
+} // spixels namespace

0 comments on commit b350017

Please sign in to comment.