Light up your NeoPixels using the uLisp ARM assembler


#1

There’s now an updated version of the page:

ARM NeoPixel driver using assembler

The newer version supports a larger range of boards, including the Adafruit Pixel trinkey:

It also includes instructions for calibrating it to run on virtually any ARM board or at any clock rate.

Lighting up a string of NeoPixels is as simple as evaluating:

(neopixel pin colours)

where colours is a list of the colour values for each NeoPixel. There is an example showing how to use it as the basis for animated effects written in uLisp.


#2

I just tried this on the Adafruit 2040 feather and it works really nicely. Some notes include that the LED is really bright if you go all the way to 255, so I added a brightness parameter (should be a floating point number less than 1) to make it more pleasant. Also you can go below 1 for the speed to get slower fades. (wave 1 0.1 0.07) looks nice to me for example.

(defun wave (len speed &optional (brite 1))
  (let ((strip nil)
        (scroll 0)
        (step (truncate (/ 768 len))))
    (dotimes (n len) (push 0 strip))
    (loop
     (let ((i scroll))
       (mapl #'(lambda (c)
               (setf (car c) (+ (ash (col (+ i 256) brite) 16) (ash (col i brite) 8) (col (+ i 512) brite)))
               (incf i step))
           strip))
     (neopixel *pin-bit* strip)
     (incf scroll speed))))
	
(defun col (x brite) 
  (let ((y (mod x 768)))
    (round (* brite (max (if (< y 256) y (- 511 y)) 0)))))

#3

Great suggestions!

I’ve written a few more examples, and have adopted the following conventions:

  • The first parameter is always len, the number of NeoPixels on the strip.
  • All the other parameters should be optional, with defaults that give a good display.
  • The brightness parameter, if supplied, should go from 0 to 1 as you suggested.
  • The speed parameter, if supplied, should also go from 0 to 1 where 0.2 will be a recommended speed.
  • Finally, the examples should use unwind-protect to turn off the LEDs when you escape from the function, to save you having to do that manually.

Here are the examples:

Wave

This is an updated version of the original example:

(defun wave (len &optional (speed 0.2) (bri 0.25))
  (let ((strip nil)
        (scroll 0)
        (step (/ len)))
    (dotimes (n len) (push 0 strip))
    (unwind-protect
        (loop
         (let ((i scroll))
           (mapl #'(lambda (c) (setf (car c) (wheel i bri)) (incf i step))
                 strip))
         (neopixel *pin-bit* strip)
         (incf scroll (/ speed len)))
      (neopixel *pin-bit* (mapcar #'(lambda (c) 0) strip)))))

It uses wheel, which is a colour wheel that returns a colour value for values of i from 0 to 1, wrapping round to the start, and at brightness bri, also 0 to 1. It uses a subfunction tri that gives a triangle function:

(defun wheel (i &optional bri)
  (let ((x (* i 768)))
    (+ (ash (tri (+ x 256) bri) 16) (ash (tri x bri) 8) (tri (+ x 512) bri))))

(defun tri (x bri) 
  (let ((y (mod x 768)))
    (truncate (* bri (max (if (< y 256) y (- 511 y)) 0)))))

Chase

This provides a pattern of lights that travels along the string.

(defun chase (leds &optional (speed 0.2) (col #x404040))
  (let ((strip nil))
    (dotimes (n leds) (if (zerop (mod n 3)) (push col strip) (push 0 strip)))
    (unwind-protect
      (loop
       (neopixel *pin-bit* strip)
       (delay (truncate 20 speed))
       (setq strip (append (cdr strip) (list (first strip)))))
    (neopixel *pin-bit* (mapcar #'(lambda (c) 0) strip)))))

It takes an optional colour value for all the lights, default 25% white.

It works as follows: First it sets up a list with every third element set to the colour col. Then the strip is repeatedly shifted to the left by removing the first element and appending it to the end.

Twinkle

This simulates flashing coloured fairy lights. Each NeoPixel flashes with a regular period, and with a consistent colour, but the periods of all the lights are slightly different, so the sequence of flashes is continuously varying and unpredictable.

(defun twinkle (len &optional (bri 0.25))
  (let ((strip nil)
        (scroll 0)
        (step (/ len))
        (offset 120)
        (n 0))
    (dotimes (n len) (push 0 strip))
    (unwind-protect
        (loop
          (dotimes (i len)
            (let* ((j (mod (+ 7 (* i 13)) len))
                   (k (mod n (+ offset j))))
              (setf (nth i strip) (if (zerop k) (wheel (* j step) bri) 0))))
          (neopixel *pin-bit* strip)
          (delay 5)
          (incf n))
      (neopixel *pin-bit* (mapcar #'(lambda (c) 0) strip)))))

It needs the functions wheel and tri defined above to give the colours of the lights.

It works as follows: Each NeoPixel is assigned an integer such that NeoPixel i is given the integer j defined as:

(mod (+ 7 (* i 13)) len)

The value n counts up from zero. When n is divisible by (+ offset j) for any NeoPixel, that NeoPixel is turned on with the colour j; otherwise it is turned off.

Look forward to any comments! I’ll also update the original ARM NeoPixel page with these examples in due course.


#4

By the way, if you’re thinking of buying a string of NeoPixels to experiment with these routines I can recommend the Adafruit NeoPixel LED Dots Strand:

It has 20 NeoPixels spaced 4" apart, and the total current consumption will be within the current capability of a standard computer USB-A 5V output so you won’t need an external power supply.


#5

For those of us on the western side of the Atlantic Pond, while the strands are longer (16.4 ft or 5m), I can recommend these (https://www.walmart.com/ip/onn-Fairy-16-4-RGBIC-LED-Indoor-Decoration-Dream-Multi-Color-Lights-with-IR-Remote/741672275) from Wal-Mart.

They used to be carried in the US stores but were discontinued late last year (2024). Still, these fairy lights (RGB LEDs) can be ordered from the Walmart.com website. I easily run the whole strand via a RPi Pico off USB or a 5v microUSB power supply. There’s 50 RGB LEDs on a single strand with each LED encapsulated by a “glob of snot” (that’s what it looks like to me. :).

Three-wire connection from the supplied controller and they are compatible with WS2812/NeoPixel LEDs.