The Neo Trinkey is a USB-key sized board from Adafruit containing an ATSAMD21 ARM M0+ chip running at 48MHz, and four NeoPixel WS2812 serial addressable RGB displays:
For more information see:
Installing uLisp on the Neo Trinkey
To install the ARM version of uLisp on the Neo Trinkey choose the Adafruit Gemma M0 board setting in the Adafruit SAMD Boards core. The performance is similar to the other M0 boards.
Having got uLisp running I thought it would be interesting to use the uLisp ARM assembler to write a routine to control the four NeoPixel displays.
Installing the assembler
The assembler itself is written in Lisp to make it easy to extend it or add new instructions. For example, you could write assembler macros in Lisp as shown below. For more information see: ARM Assembler Overview
Get the assembler here: ARM assembler in uLisp. To add it to uLisp: do Select All and Copy, Paste it into the field at the top of the Arduino IDE Serial Monitor window, and press Return .
Controlling the NeoPixels
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’:
Here is a table showing the number of clock cycles that each of these timings correspond to with the 48MHz clock on the Neo Trinkey. 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 four displays on the Neo Trinkey by calling the routine with four parameters, each specifying the colour of one display. For example, to set the displays to yellow, cyan, magenta, white:
(neopixel #x070700 #x070007 #x000707 #x070707)
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.
On the Neo Trinkey the chain of four NeoPixel displays are connected to PA05. 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)
Finally, here’s the whole assembler routine:
(defcode neopixel (a b c d) ($push '(lr r5 r4 r3 r2 r1 r0)) ($ldr 'r4 porta) ($mov 'r1 1) ($lsl 'r3 'r1 5) ($str 'r3 '(r4 dirset)) ; make pin an output ($mov 'r2 4) nextled ($pop '(r0)) ; get bytes ($mov 'r1 1) ($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) ($sub 'r2 1) ($bne nextled) ($pop '(r4 r5 pc)) porta ($word #x41004400))
The four parameters are passed to the assembler routine in r0, r1, r2, and r3. The routine accesses these by pushing them all to the stack, and then popping them off one at a time as they are needed. I also pushed r4 and r5 as I used these in the routine.
The bits in each parameter are tested one at a time, and then the appropriate code at the labels zero or one is executed to generate a pulse with the appropriate timing.
Interrupts are disabled around the most time critical part of the routine, which generates the ‘0’ pulse, to prevent them for affecting the pulse timings.
You can write programs in uLisp to call the neopixel routine and generate animated colour displays.
29th April 2021: Changed the name of the macro to delay4 to avoid a conflict with the built-in uLisp function delay.
5th May 2021: Added $cpsid and $cpsid instructions 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 when you animate the NeoPixels.