ARM NeoPixel driver using assembler
NeoPixels use a non-standard protocol consisting of a serial stream of pulses, and the width of each pulse determines whether it is a '0' or a '1'. However the pulses are very short; a zero is defined as having a maximum width of 500ns, which is just 8 cycles on a 16 MHz CPU, or 12 cycles on a 24 MHz CPU. Most NeoPixel libraries therefore use assembler routines tailored to each processor, at least for the low-level pulse generation. This is therefore an excellent application for the built-in assember in uLisp.
This page gives a NeoPixel routine for a single NeoPixel display on boards based on the ATSAMD21, such as the Adafruit QT-Py or Adafruit Neo Trinkey.
NeoPixel is the name given by Adafruit to the WS2812, a chainable RGB LED invented in 2013 by the Shenzhen-based company Worldsemi. You can power them with 5V or 3.3V, and chain them together, tying the data-out pin of one NeoPixel to the data-in of the next one in the chain. There's an excellent overview of NeoPixels on the Adafruit site .
You can get single NeoPixels in a variety of shapes and sizes: Through-hole: 8mm , and 5mm ; SMD: 5050 , 3535 , 2427 , and 1515 . You can also get them already mounted in almost any configuration, including strips, discs, and matrixes.
The NeoPixel protocol
The datasheet  specifies these timings as the ideal timings for the zero and one bits, and the gaps after each bit:
Here is a table showing the number of clock cycles that each of these timings correspond to with the standard 48MHz clock on the ATSAMD21. All the timings have a tolerance of ±150 ns:
The colour for each NeoPixel display is specified by a stream of 24 bits:
The following assembler routine lets you control the NeoPixel display by calling the routine with a parameter specifying the colour of the display. For example, to set the display to yellow:
I’ve purposely kept the displays at a low brightness because at full brightness they can be a bit dazzling.
The key section of code in all these routines is a delay4 macro written in Lisp. This generates a list of the instruction words which will be inserted in the assembler at the appropriate point to get a delay of n clock cycles:
(defun delay4 (n) (list ($mov 'r5 n) ($sub 'r5 1) ($bne (- *pc* 2))))
This uses r5 as a counter to execute the loop a number of times, specified by the constant n specified in the parameter, and the total execution time is n * 4 cycles.
The following routine assumes that the NeoPixel display is driven by a pin on port A. The base address of Port A is #x41004400, and offsets from this give access to the dirset, outset, and outclr registers to define a pin as an output, set a pin high, and set a pin low respectively. These are defined by the following defvar statements:
(defvar dirset #x08) (defvar outset #x18) (defvar outclr #x14)
The actual pin number is defined as follows; for example, on the Adafruit QT-Py the NeoPixel is connected to PA18:
(defvar pin 18)
On the Adafruit Neo Trinkey the NeoPixels are connected to PA5, so change this to:
(defvar pin 5)
Finally, here’s the whole assembler routine:
(defcode neopixel (a) ($push '(lr r5 r4)) ($ldr 'r4 porta) ($mov 'r1 1) ($lsl 'r3 'r1 pin) ; NeoPixel pin ($str 'r3 '(r4 dirset)) ; make pin an output ($lsl 'r1 23) nextbit ($tst 'r0 'r1) ; test if bit is 1 ($bne one) zero ($cpsid 3) ($str 'r3 '(r4 outset)) (delay4 4) ($str 'r3 '(r4 outclr)) (delay4 10) ($cpsie 3) ($b next) one ($str 'r3 '(r4 outset)) (delay4 8) ($str 'r3 '(r4 outclr)) (delay4 7) next ($lsr 'r1 1) ($bne nextbit) ($pop '(r4 r5 pc)) porta ($word #x41004400))
The colour parameter is passed to the assembler routine in r0. The bits in this parameter are tested one at a time, starting with bit 23, and then the appropriate code at the labels zero or one is executed to generate a pulse with the appropriate timing.
$cpsid and $cpsid instructions are used to disable interrupts around the most time-critical part of the routine, which generates the ‘0’ pulse, to prevent them for affecting the pulse timings.
- ^ NeoPixel Überguide on Adafruit.
- ^ NeoPixel Diffused 8mm Through-Hole LED on Adafruit.
- ^ NeoPixel Diffused 5mm Through-Hole LED on Adafruit.
- ^ NeoPixel RGB 5050 LED on Adafruit.
- ^ Mini 3535 RGB LEDs on Adafruit.
- ^ NeoPixel Nano 2427 RGB LEDs on Adafruit.
- ^ NeoPixel Addressible 1515 LEDs on Adafruit.
- ^ WS2812 datasheet on Adafruit.