13th October 2020
This article describes a simple 12-hour clock, written in uLisp, using two I2C dot-matrix displays to show the time:
Dot matrix clock written in uLisp
The clock can run on any of the microcontroller boards supported by uLisp apart from the Arduino Uno, which doesn't have quite enough memory. In the prototype I used a BBC Micro:bit.
This application was first published on the uLisp forum.
You can display the time on two 8x8 dot matrix LED displays, using characters based on a 3x8 matrix, and there's even space left for a colon between the hours and minutes. I used Keyestudio I2C displays, which incorporate a HT16K33 driver chip  to handle the display multiplexing and I2C interface allowing you to control them with just two I/O lines . Adafruit make a similar display . The displays include pullup resistors on SCL and SDA, so you don't need to provide these.
Three solder links allow you to choose one of eight I2C addresses for each display, allowing you to drive up to eight displays simultaneously. You can choose an address from 0x70 (112) to 0x77 (119), and the default I2C address with no links is #x70 or 112. So, for example, you could extend this project to display the time in London and New York on two pairs of displays.
Here's the circuit for using a BBC Micro:bit:
I built the circuit on a prototyping board, with the BBC Micro:bit mounted in a Micro:bit breadboard breakout board .
First we define the character definitions for the 3x8 digits, 0 to 9:
(defvar *digits* '((#x7F #x41 #x7F) (#x00 #x20 #x7F) (#x4F #x49 #x79) (#x49 #x49 #x7F) (#x78 #x08 #x7F) (#x79 #x49 #x4F) (#x7F #x49 #x4F) (#x40 #x40 #x7F) (#x7F #x49 #x7F) (#x79 #x49 #x7F)))
The variables adr1 and adr2 are used to define the addresses of the two displays:
(defvar adr2 112) (defvar adr1 113)
The setup routine configures the HT16K33 display driver, and sets the brightness which can be from 0 to 15:
(defun setup (addr bri) (with-i2c (s addr) (write-byte #x21 s) (restart-i2c s) (write-byte #x81 s) (restart-i2c s) (write-byte (+ #xe0 bri) s)))
The routine put writes a series of bit patterns to successive columns of a display:
(defun put (addr byt) (with-i2c (s addr) (write-byte 0 s) (dolist (item byt) (write-byte (logior (ash item -1) (ash item 7)) s) (write-byte 0 s))))
so, for example, this displays a diagonal line on one of the displays:
(setup adr1 8) (put adr1 '(1 2 4 8 16 32 64 128)))
(logior (ash col -1) (ash col 7))
compensates for the fact that for some reason the rows of these displays are numbered 7, 0, 1, 2, 3, 4, 5, 6 from bottom to top.
Finally showtime calls put to display the time, in hours and minutes, on the two displays:
(defun showtime (hours mins) (put adr1 (append (if (zerop (truncate hours 10)) '(0 0) (cdadr *digits*)) '(0) (nth (mod hours 10) *digits*) '(0 #x12))) (put adr2 (append '(0) (nth (truncate mins 10) *digits*) '(0) (nth (mod mins 10) *digits*))))
For example, to display 12:34 evaluate:
(showtime 12 34)
The number #x12 is the code to display a colon.
Here's the main clock routine:
(defun clock (h m s) (setup adr1 8) (setup adr2 8) (loop (for-millis (1000) (showtime h m) (incf s) (when (= s 60) (incf m) (setq s 0) (when (= m 60) (incf h) (setq m 0) (when (= h 13) (setq h 1))))))
To run the clock call clock with the current time, in hours, minutes, and seconds. For example:
(clock 12 34 00)
The nice thing about using dot matrix displays is that you can be creative, and customise the display digits to suit your preferences. For example, here are some alternative character definitions for rounded digits:
(defvar *digits* '((#x3E #x41 #x3E) (#x00 #x20 #x7F) (#x27 #x49 #x31) (#x2A #x49 #x36) (#x18 #x28 #x7F) (#x7A #x49 #x4E) (#x3E #x49 #x26) (#x47 #x48 #x70) (#x36 #x49 #x36) (#x30 #x48 #x3F)))
Here's the whole Dot-matrix clock program in a single file: Dot-matrix clock program.