Adafruit Neo Trinkey

The Neo Trinkey is a USB-key sized board from Adafruit containing an ATSAMD21 ARM M0+ chip running at 48MHz, with 256KB Flash and 32 KB RAM, and four NeoPixel WS2812 serial addressable RGB displays [1]:

AdafruitNeoTrinkey.jpg

This article first appeared on the uLisp Forum.

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.

Using the uLisp assembler to control the NeoPixel displays.

You can 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':

NeoPixelBits.gif

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:

  Time Cycles
T0H 350 ns 16.8
T0L 800 ns 38.4
T1H 700 ns  33.6
T1L 600 ns 28.4

The colour for each NeoPixel display is specified by a stream of 24 bits:

NeoPixelCodes.gif

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)

AdafruitNeoTrinkey2.jpg

I've purposely kept the displays at a low brightness because at full brightness they can be a bit dazzling.

Delay macro

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.

Assembler routine

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 dirsetoutset, and outclr registers to set 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, using $cpsid and $cpsid instructions, to prevent them for affecting the pulse timings.

Creating animated displays

You can write programs in uLisp to call the neopixel routine and generate animated colour displays. For example, the following function animate takes a list of four colour definitions, and cycles them around the four displays repeatedly:

(defun animate ()
  (let ((lst '(#x000707 #x070707 #x070700 #x070007)))
    (loop
     (eval (cons 'neopixel lst))
     (delay 200)
     (setq lst (append (cdr lst) (list (car lst)))))))

Making the animation run automatically

You can use uLisp's autorun feature to make the animate program run automatically when power is applied to the Neo Trinkey.

  • Uncomment the compile option:
#define resetautorun
  • Upload uLisp (ARM Version 3.6b or later) to the Neo Trinkey board.
  • Copy and paste in the Lisp definitions for the ARM Assembler.
  • Copy and paste in the Lisp definitions for the neopixel routine and animate.
  • Check that animate works, and then type a '~' to escape.
  • Save the Lisp image with the command:
(save-image 'animate)
  • Unplug the Neo Trinkey, and then plug it in again.

The animate program should run automatically.


  1. ^ Adafruit Neo Trinkey - SAMD21 USB Key with 4 NeoPixels on Adafruit.