The below was tested on a NixOS 22.11 but should also work on other
Linux distributions. If you happen to have nix you can execute
nix-shell
in the path where the shell.nix is located to get an
environment with the arduino-cli. Otherwise install arduino-cli
by some other means.
arduino-cli --help
arduino-cli board --help
arduino-cli compile --help
arduino-cli board listall
arduino-cli board details --no-color --fqbn pico:rp2040:rpipico
Derived from the official μlisp installation instructions.
Use Earle Philhower’s Raspberry Pi Pico/RP2040 Arduino core.
set -ex
mkdir -p ~/Arduino/hardware/pico
git clone https://github.com/earlephilhower/arduino-pico.git ~/Arduino/hardware/pico/rp2040
cd ~/Arduino/hardware/pico/rp2040
git submodule update --init
cd pico-sdk
git submodule update --init
cd ../tools
python3 ./get.py
arduino-cli core update-index
arduino-cli board list
arduino-cli core list
arduino-cli core install pico:rp2040
Use ARM version of μlisp.
mkdir -p ./ulisp-arm
cd ./ulisp-arm/
curl -O https://raw.githubusercontent.com/technoblogy/ulisp-arm/master/ulisp-arm.ino
arduino-cli compile --no-color \
--output-dir ./ulisp-arm \
--board-options flash=2097152_1048576 \
--fqbn pico:rp2040:rpipico \
ulisp-arm
arduino-cli upload \
--input-dir ./ulisp-arm/ \
--port /dev/ttyACM0 \
--fqbn pico:rp2040:rpipico
(serial-term "/dev/ttyACM0" 9600)
(switch-to-buffer "README.org")
(split-window-below)
(switch-to-buffer-other-window "/dev/ttyACM0")
(defun send-to-pico ()
(interactive)
(let ((element (org-element-at-point)))
(cond
((not (equal 'src-block (car element)))
(error "element at point is not a src-block"))
((not (equal "lisp" (plist-get (cadr element) ':language)))
(error "src-block language is not lisp"))
(t
(process-send-string "/dev/ttyACM0" (plist-get (cadr element) ':value))))))
Now you can place the cursor on one of the lisp code blocks below and
execute M-x send-to-pico
to execute them on the Pico.
(+ 1 1)
(? dotimes)
(defun blink (&optional x)
"Let the LED blink forever."
(pinmode :led-builtin :output)
(digitalwrite :led-builtin x)
(delay 1000)
(blink (not x)))
(defun pulse ()
"Let the LED pulsate forever."
(let (down)
(loop
(dotimes (x 256)
(delay 5)
(analogwrite :led-builtin (if down (- 255 x) x)))
(setq down (not down)))))
Now you can make the led blink or pulse by typing (blink)
or
(pulse)
at the μlisp command prompt in the serial console.
To end the execution of the function press the escape char (~
).
(save-image)
Unplug and plug the Pico.
(load-image)
(blink)
Taken from here.
(defun prime (n)
"Return t iff the given number n is a prime."
(let ((d 2))
(loop
(when (> (* d d) n) (return t))
(when (zerop (mod n d)) (return nil))
(incf d))))
(defun blink-primes ()
"Blink an increasing series of prime numbers on the LED. For a prime
number n the LED blinks n times (0.5 seconds period). After each
prime there is an 1.5 seconds pause "
(pinmode :led-builtin :output)
(dotimes (x 32767)
(when (and (> x 1) (prime x))
(print x)
(princ " = ")
(dotimes (f (* x 2))
(if (evenp f)
(progn
(digitalwrite :led-builtin t)
(princ "|"))
(digitalwrite :led-builtin nil))
(delay 250))
(delay 1500))))
(blink-primes)
Useful references:
- uLisp - Language reference
- uLisp - Using interrupts (not implemented for the Pico)
- Raspberry Pi Documentation - Raspberry Pi Pico and Pico W
- Raspberry Pi Pico - Pinout
;; TODO change pins
;; Aliases for the used pins
(defvar pin-controller 25 )
(defvar pin-input-1 6 )
(defvar pin-input-2 7 )
(defvar pin-input-4 8 )
;; Should toggle when new input becomes ready
(defvar pin-input-ready 9 )
;; Used for remembering the last state of input-ready
(defvar prev-input-ready -1)
(defun throttle (v1 &optional t1 v2)
"Change the throttle to v1. Optional change to v2 after t1 milliseconds. Valid values for v1 and v2 are 0-255."
(analogwrite pin-controller v1)
(if (and t1 v2)
(progn
(delay t1)
(analogwrite pin-controller v2))))
(defun binaryread (pin)
"Like digitalread but return 1 (high) or 0 (low)."
(if (digitalread pin) 1 0))
(defun last-checkpoint ()
"Return number of the last passed checkpoint, or nil if no new checkpoint was past since the last call."
(let ((ready (binaryread pin-input-ready)))
(if (= ready prev-input-ready)
nil
(progn
(setq prev-input-ready ready)
(+ (* 1 (binaryread pin-input-1))
(* 2 (binaryread pin-input-2))
(* 4 (binaryread pin-input-4)))))))
(defun last-checkpoint-mock ()
"Mock of last-checkpoint for testing."
(if (not (boundp 'lcm-round)) (defvar lcm-round 0))
(setq lcm-round (+ 1 lcm-round))
(if (= 0 (mod lcm-round 2000))
(let ((n (/ lcm-round 2000)))
(if (= 7 n) (setq lcm-round 0))
n)
nil))
(defun enter-loop (&optional rounds)
"Enter the control loop. Loop forever (default) or exit after rounds."
(dolist (io (list pin-input-1 pin-input-2 pin-input-4 pin-input-ready))
(pinmode io :input))
(throttle 64)
(let ((i 1))
(loop
(case (last-checkpoint)
(1 (throttle 255 400 32))
(2 (throttle 255 400 32))
(3 (throttle 255 400 32))
(4 (throttle 255 400 32))
(5 (throttle 255 400 32))
(6 (throttle 255 400 32))
(7 (throttle 255 400 32)
(if rounds
(setq i (+ 1 i)))))
(if (and rounds (> i rounds))
(progn
(throttle 0)
(return))))))
(enter-loop 2)