Run lisp on Arduino using OpenBSD

I have been fascinated by the ulisp project for some time.

It seems attractive to be able to run a high level language on target using a REPL on a small embedded device.

So I thought I would try it out on an old Arduino board using OpenBSD as host.

These notes assumes OpenBSD 6.4 on the host.

Install arduino on OpenBSD

It turned out the running arduino on OpenBSD is really straight forward.

The process is explained in quite detail in Arduino Development using the OpenBSD CLI.

To install the environment just install package arduino.

# pkg_add arduino

See an intro to the package in:

less /usr/local/share/doc/pkg-readmes/arduino
less /usr/local/share/doc/pkg-readmes/avrdude

Deploy ulisp

Next I created a sample project

arduinoproject ulisp-uno
cd ulisp-uno

I first tried to build and deploy the helloworld.ino source from the link above copied into ulisp-uno.ino.

make

After connecting the board with a USB printer cable I could deploy the hex file.

doas make upload

doas (sudo alternative) is needed for USB access. This makes the arduino blink a LED in a nice and soothing pace.

Next I tried to build ulisp.

wget https://raw.githubusercontent.com/technoblogy/ulisp/master/ulisp.ino
mv ulisp.ino ulisp-uno.ino

Here I got some compile errors that needed to be fixed.

I also added some libraries to the Makefile.

LIBRARIES=SPI EEPROM

See https://github.com/peterljung/ulisp for details.

So after another make/upload ulisp was running on the Arduino.

To connect to the device i used cu serial terminal emulator.

cu -s 9600 -l /dev/cuaU0

See Using ulisp for some guidance on how to use the on target repl environment. Here is a sample session.

uLisp 2.6
314> (+ 451 42)
493

314> (defun b () (pinmode 13 t) (loop (digitalwrite 13 t) (delay 500) (digitalwrite 13 nil) (delay 500)))
b

279> (b)
Error: Escape!

The second example is the corresponding blinking hello world in lisp! You escape a running command with ~. You escape the cu serial terminal with ~. ENTER.

See ulisp Language Reference for a complete reference of available commands.

When you are satisfied with your program you can save the image (save-image) (i.e. current state) which you can optionally autorun on hardware reset. See Saving and loading images for more info.

Pretty awesome! But now I wonder if this is something to be used for real deployments?

You can certainly have some serious fun with this ...

Install ulisp on 8266 NodeMCU v0.9 on macOS and Arduino IDE

Install CH340 driver on macOS.

Install python3.

Install Arduino IDE.

Install support for NodeMCU board support in Arduino IDE.

Select the following to test the board:

But the LED_BUILTIN is not set correctly for some reason. Internal LED is connected to GPIO01 which can also can referenced by 1, D7 (external port name) or GPIO01. Check pinout here.

// const int LED = 1;
// const int LED = D10;
const int LED = GPIO01;

pinMode(LED, OUTPUT);
...

Download ulisp and open .ino file in Arduino IDE.

Start serial monitor on 9600 baud.

uLisp 3.0
3071> (+ 1 1)
2

Done!

Try the examples.

Connect to local wifi.

(wifi-connect "SSID" "password")

Download time based on current IP.

(defun println (x s) (princ x s) (princ #\return s) (princ #\newline s))

(defun time ()
  (with-client (s "worldtimeapi.org" 80)
    (println "GET /api/ip HTTP/1.0" s)
    (println "Host: worldtimeapi.org" s)
    (println "Connection: close" s) 
    (println "" s)
    (loop (unless (zerop (available s)) (return)))
    (loop
     (when (zerop (available s)) (return))
     (princ (read-line s))
     (terpri))))

Request page.

(time)
HTTP/1.1 200 OK
Connection: close
Server: Cowboy
Date: Sat, 22 Feb 2020 16:57:43 GMT
Content-Length: 346
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: 
Access-Control-Allow-Credentials: true
Via: 1.1 vegur
{
	"week_number":8,
	"utc_offset":"+01:00",
	"utc_datetime":"2020-02-22T16:57:44.231964+00:00",
	"unixtime":1582390664,
	"timezone":"Europe/Stockholm",
	"raw_offset":3600,
	"dst_until":null,
	"dst_offset":0,
	"dst_from":null,
	"dst":false,
	"day_of_year":53,
	"day_of_week":6,
	"datetime":"2020-02-22T17:57:44.231964+01:00",
	"client_ip":"212.85.95.32",
	"abbreviation":"CET"
}
nil

Use a RTC clock.

; Control DS3231 over i2c (addr: 0x68)
; Datasheet: https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
; NodeMCU 0.9 ESP-12 module
; D1 = GPIO5 = SCL
; D2 = GPIO4 = SDA
; See: http://www.ulisp.com/show?1EK4
:      http://www.ulisp.com/show?1DO6

; set time of DS3231
(defun set-time (hr min)
  (with-i2c (str #x68) 		;; start slave on addr 0x68
    (write-byte 0 str)    	;; slave address 00
    (write-byte 0 str)		;; set secs = 00
    (write-byte min str) 	;; set min
    (write-byte hr str)))   ;; set hr

; return time as (hr min)
(defun get-time ()
  (let (hr min)
    (with-i2c (str #x68)
      (write-byte 1 str) 			;; slave address 01
      (restart-i2c str 2)   		;; restart to read 2 bytes after write
      (setq min (read-byte str)) 	;; read min
      (setq hr (read-byte str)) 	;; read hr
      (list hr min))))

; set date as e.g. (set-date #x01 #x02 #x03 #x20) representing mon, 2nd march, 2020 
(defun set-date (wday day mon year)
  (with-i2c (str #x68) 		;; start slave on addr 0x68
    (write-byte 3 str)    	;; slave address 03
    (write-byte wday str)	;; set weekday 01-07
    (write-byte  day str)	;; set day 01-31
    (write-byte  mon str)	;; set month 01-12
    (write-byte year str))) ;; set year 00-99

; return time as (hr min)
(defun get-date ()
  (let (wday day mon year)
    (with-i2c (str #x68)
      (write-byte 3 str) 			;; slave address 03
      (restart-i2c str 4)   		;; restart to read 4 bytes after write
      (setq wday (read-byte str)) 	;; read weekday
      (setq  day (read-byte str)) 	;; read day
      (setq  mon (read-byte str)) 	;; read month
      (setq year (read-byte str)) 	;; read weekday
      (list wday day mon year))))

References