Samstag, 31. Dezember 2016

Lightpainting

Inspiriert von einen bereits etwas älteren Kickstarter-Projekt (https://www.kickstarter.com/projects/bitbangerlabs/pixelstick-light-painting-evolved?lang=de), habe ich in den Weihnachtsferien ein cooles kleines Projekt realisiert.


Wegen der kurzen Vorbereitungszeit verwendete ich Bauteile, die ich schon zuhause hatte. Im wesentlichen ein Arduino Uno, ein Ethernetshield mit SD-Kartenslot und eine Neopixel-Strip (RGBW).  Dank dem Neopixel-Painter Programm (https://learn.adafruit.com/neopixel-painter/overview) von Adafruit, konnte ich einen ziemlichen Jumpstart hinlegen. Wie sich herausstellte, ist das Programm aber nur für RGB-Ledstrips geeignet. Die Ansteuerung der Leds (WS2811 Decoder) ist in Assembler realisiert. Nach etwas Recherche konnte ich die Assembler-Routine so modifizieren, dass zusätzlich zu den 3 Farbwerten jeweils ein Farbwert für die weisse Led an den Ledstrip geschickt wird.

Auf instructables.com habe ich eine wirklich gute Erklärung der WS2811 Protokolls und der Implementierung in Assembler gefunden: http://www.instructables.com/id/Bitbanging-step-by-step-Arduino-control-of-WS2811-/?ALLSTEPS#step5


Hier nun die modifizierte Assemblerroutine, in die C-Funktion show() eingepackt. Diese C-Funktion kann nun direkt als Ersatz für die bestehende Neopixel-Painter Funktion eingesetzt werden.

/*------------------------------------------------------------------------
  Acrobotic - 01/10/2013
  Author: x1sc0
  Platforms: Arduino Uno R3
  File: bitbang_105.ino

  Description:
  This code sample accompanies the "How To Control 'Smart' RGB LEDs:
  WS2811, WS2812, and WS2812B (Bitbanging Tutorial)" Instructable
  ()
 
  The code illustrates how to continuously send a single 105 value to the
  WS2811 (driver)/WS2812/WS2812B RGB LEDs over a self-clocked 800KHz, NZR
  signal.  The implementation was done using assembly so that the timing
  of the signal was extremely accurate.

  Usage:
  Connect power (5V), ground (GND), and the Arduino Uno pin defined
  by DIGITAL_PIN to the WS281X ports VCC, GND, and DIN ports, respectively.

  Upload the program to an Arduino Uno, and the Green LED of the first
  WS281X will light up to full brightness.

  ------------------------------------------------------------------------
  For a full-blown Arduino library, check out:
  https://github.com/acrobotic/Ai_Library_WS281X/
  ------------------------------------------------------------------------
  Please consider buying products from Acrobotic to help fund future
  Open-Source projects like this! We’ll always put our best effort in every
  project, and release all our design files and code for you to use.
  http://acrobotic.com/
  ------------------------------------------------------------------------

  License:
  Beerware License; if you find the code useful, and we happen to cross
  paths, you're encouraged to buy us a beer. The code is distributed hoping
  that you in fact find it useful, but  without warranty of any kind.
------------------------------------------------------------------------*/


// NEW SHOW FUNCTION ---------------------------------------------------------
void show()
{
  noInterrupts();

  volatile uint8_t 
   *p    = sdBuf,                  // Copy the start address of our data array
    val  = *p++,                   // Get the current byte value & point to next byte
    high = PORT |  _BV(PORT_PIN),  // Bitmask for sending HIGH to pin
    low  = PORT & ~_BV(PORT_PIN),  // Bitmask for sending LOW to pin
    tmp  = low,                    // Swap variable to adjust duty cycle
    nbits= NUM_BITS,               // Bit counter for inner loop
    white= 3;

  volatile uint16_t nbytes =  N_LEDS*3;  // 4 Bytes per Led

  asm volatile(
    // Instruction        CLK     Description                 Phase
   "nextbit:\n\t"         // -    label                       (T =  0)
    "sbi  %0, %1\n\t"     // 2    signal HIGH                 (T =  2)
    "sbrc %4, 7\n\t"      // 1-2  if MSB set                  (T =  ?)         
    "mov  %6, %3\n\t"     // 0-1   tmp'll set signal high     (T =  4)
    "dec  %5\n\t"         // 1    decrease bitcount           (T =  5)
    "nop\n\t"             // 1    nop (idle 1 clock cycle)    (T =  6)
    "st   %a2, %6\n\t"    // 2    set PORT to tmp             (T =  8)
    "mov  %6, %7\n\t"     // 1    reset tmp to low (default)  (T =  9)
    "breq nextbyte\n\t"   // 1-2  if bitcount ==0 -> nextbyte (T =  ?)               
    "rol  %4\n\t"         // 1    shift MSB leftwards         (T = 11)
    "rjmp .+0\n\t"        // 2    nop nop                     (T = 13)
    "cbi   %0, %1\n\t"    // 2    signal LOW                  (T = 15)
    "rjmp .+0\n\t"        // 2    nop nop                     (T = 17)
    "nop\n\t"             // 1    nop                         (T = 18)
    "rjmp nextbit\n\t"    // 2    bitcount !=0 -> nextbit     (T = 20)

   "nextbyte:\n\t"        // -    label                       -
    "dec %10\n\t"         // 1    decrease colorcount         (T = 11)
    "breq whiteled\n\t"   // 1-2  if byte for whiteled 
  
    "nop\n\t"             // 1    nop                         (T = 13)
    "cbi  %0, %1\n\t"     // 2    signal LOW                  (T = 15)
    "ld   %4, %a8+\n\t"   // 2    val = *p++                  (T = 17)
    "ldi  %5, 8\n\t"      // 1    reset bitcount              (T = 18)

    "dec %9\n\t"          // 1    decrease bytecount          (T = 19)
    "brne nextbit\n\t"    // 2    if bytecount !=0 -> nextbit (T = 20)
    "rjmp done\n\t"       //      we are done, leave assembler code

   "whiteled:\n\t"        // -    label                       -
    "cbi  %0, %1\n\t"     // 2    signal LOW                  (T = 15)
    "clr  %4\n\t"         // 1    val= 0  (white led off)     (T = 16)
    "ldi  %5, 8\n\t"      // 1    reset bitcount              (T = 17)
    "ldi  %10, 4\n\t"     // 1    reset colorcount            (T = 18)
    "nop\n\t"             // 1    nop                         (T = 19)
    "rjmp nextbit\n\t"    // 2    if bytecount !=0 -> nextbit (T = 20)

   "done:\n\t"        // -    label                       -

    ::
    // Input operands         Operand Id (w/ constraint)
    "I" (_SFR_IO_ADDR(PORT)), // %0
    "I" (PORT_PIN),           // %1
    "e" (&PORT),              // %a2
    "r" (high),               // %3
    "r" (val),                // %4
    "r" (nbits),              // %5
    "r" (tmp),                // %6
    "r" (low),                // %7
    "e" (p),                  // %a8
    "w" (nbytes),             // %9
    "r" (white)               // %10
  );

  interrupts();
  delayMicroseconds(50);



Und so sieht der improvisierte Aufbau aus: