KaiHa / emacs-ulisp-and-the-pico

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Emacs, μlisp and the Raspberry Pi Pico

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.

The Arduino CLI

Getting started

Looking at the built-in help

arduino-cli --help
arduino-cli board --help
arduino-cli compile --help

Exploring supported boards and board options

arduino-cli board listall
arduino-cli board details --no-color --fqbn pico:rp2040:rpipico

Preparation of the Raspberry Pi Pico

Derived from the official μlisp installation instructions.

Arduino core

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

Installing the core with arduino-cli

arduino-cli core update-index
arduino-cli board list
arduino-cli core list
arduino-cli core install pico:rp2040

μlisp

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

Using μlisp

(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)

Blinking primes

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)

Controlling a car on a Carrera racetrack

Useful references:

;; 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)

About


Languages

Language:C++ 100.0%Language:Nix 0.0%