uLisp on M5Stack (ESP32):
Stand-alone uLisp computer (with code!)
Last edited on December 6, 2021
Last Thursday, I started to use the m5stack faces keyboard I mentioned before and wrote a keyboard interpreter and REPL so that this makes another little handheld self-containd uLisp computer. Batteries are included so this makes it stand-alone and take-along. :)
I have made this as a present to my nephew who just turned eight last Saturday. Let's see how this can be used to actually teach a bit of Lisp. The first programming language needs to be Lisp, of course!
Implementation
Talking to the keyboard was actually embarrassingly easy as the M5Faces input panels like the keyboard have their own separate processor, a Atmel MEGA328, and could actually run uLisp themselves. The code of the keyboard is in the official m5stack GitHub account in the file KeyBoard.ino.The second reason is that they communicate with the ESP32 of the M5Core via I2C and uLisp comes with a WITH-I2C form as part of the I2C and SPI serial interface. So nothing to be done in C and everything can be implemented in uLisp code.
The read keyboard code was written straight forward and is simply:
(defvar *faces-kbd-addr* #x08) (defvar *faces-kbd-pin* 5) (defun init-kbd () (pinmode *faces-kbd-pin* :input ) (digitalwrite *faces-kbd-pin* :high)) (defun kbd-pressed-p () (null (digitalread *faces-kbd-pin*))) (defun raw-read-kbd () (with-i2c (str *FACES-KBD-ADDR* 1) (read-byte str))) (defun decode-char (code) (if (< code 128) (code-char code) code)) (defun read-kbd () (when (kbd-pressed-p) (let ((raw (raw-read-kbd))) (when raw ;; might be nil if i2c unsuccessful as the kbd got disconnected (decode-char raw))))) #| (loop (let ((key (read-kbd))) (when key (print key))) (check-three-finger-salute) (delay 10)) |#
And the code of the first very simple REPL is this:
(defun input->line (input &optional (prompt "")) (apply concatenate 'string prompt (reverse (mapcar string input)))) (defvar *prompt* "> ") (defun new-prompt () (push *prompt* *screen-lines*) (redraw-screen)) (defun update-prompt (input) (pop *screen-lines*) (push (input->line input *prompt*) *screen-lines*) (redraw-screen)) (defun kbd-repl (&optional (welcome t)) (when welcome (to-screen "This is ulisp-esp-m5stack!")) (new-prompt) (let (input) (loop (when (and (button-pressed-p *button-1*) (button-pressed-p *button-3*)) (to-screen 'exit-repl) (return)) (let ((key (read-kbd))) (when key (print key) (case key (#\backspace (if input (progn (princ 'rubout) (pop input) (update-prompt input)) (princ 'no-input))) (167 #| alt-c |# (princ 'abort) (setf input nil)) (#\Return (to-screen (eval (read-from-string (input->line input)))) (new-prompt) (setf input nil)) (t (if (and key (characterp key) (<= 32 (char-code key) 126)) (progn (princ 'add) (push key input) (update-prompt input)) (princ 'ignored))))) (delay 10))))) (defun start () (set-text-size 2) (setf *max-lines* 14) (set-text-wrap t) (init-kbd) (check-three-finger-salute) (clear-screen) (kbd-repl))
I have to admit it is a harsh environment to learn Lisp right now. One error and everything freezes. Some would claim that makes for fast learning like in the good old days on paper. haha But I noted that someone named Goheeca is working on Error handling in uLisp. Wonderful! I have to check that out. That is absolutely necessary for my IOT client and HTTP code to make it robust and reliable. [Update: I merged the error-handling code and now you get informed about errors instead of a freeze. See "uLisp on M5Stack (ESP32): new version published".]
See also "temperature sensors via one wire", "Curl/Wget for uLisp", time via NTP, lispstring without escaping and more space, flash support, muting of the speaker and backlight control and uLisp on M5Stack (ESP32).