This repository contains the source code for a tinygo application firmware/main.go - a real time clock with multiple HD44780 LCD display configuration for rpi pico
The firmware is built by TinyGo and lives in its own directory because it imports machine, which only exists there. Everything at the top level is the host-side tooling and builds with go build . into a single mcu binary.
The time for the RTC is set via timestamp which is passed via --ldflags -X at compile time. No buttons needed!
This source code is used in the hackable clock kit / prototype
lcd-2024-03-29_18.16.39.mp4
mcu schematic draws the wiring for the firmware's default configuration — both HD44780 displays and the DS1307.
The wiring is a description rather than a drawing: boards are pin names, wires are pairs of pin references, and the schematic package works out the geometry. Retargeting it to different hardware is an edit to the data in schematic_cmd.go, not a re-tuning of coordinates.
mcu schematic text renderer, color
mcu schematic --plain no ANSI color
mcu schematic --netlist the connection table
mcu schematic --graph asciigraph renderer
mcu schematic --svg out.svg [--light]
The SVG is pico-lcd-rtc-schematic-v2.svg, with a light-background variant for print. The terminal renderer draws the same thing in box characters:
A wrapper for the development workflow (on linux!) and serial interfacer is built with go build .. The top level command starts a bidirectional serial interface
$ go run .
mcu serial interfacer
Usage:
mcu
Available Commands:
ef eval + flash
eval generate updated source code
flash mcu cli - tinygo flash command generator
mon serial monitor
send serial send
Flags:
-b, --baud int baud rate (default 9600)
-m, --ser string block device for serial interface (i.e. "/dev/ttyACM0")
if unspecified serial connection will not be attempted
The flash and ef subcommands rely on tinygo and are designed to either invoke tinygo directly or just generate the tinygo command string to run for flashing the application.
If a block device is specified by the -y, --dev flag, the program is compiled and flashed to the device. If no block device is specified, the command string with the default flag values converted to --ldflags -X is generated.
Example:
$ GOPROG=firmware/main.go go run . flash
tinygo flash -ldflags=" -X 'main.timeStamp=2024-03-22T11:56:20Z' -X 'main.offSet=9' -X 'main.rtcFuture=false' -X 'main.enableLED=false'" firmware/main.go
If a serial interface is specified to the -m, --ser flag, a serial connection will be attempted after flashing.
Help menu flags for flash, ef, and eval subcommands are created based on the .go source code file specified in the GOPROG environmental variable.
Example:
GOPROG=firmware/main.go go run . flash --help
Tinygo application command line interface
Set global-scope string variables at compile time for tinygo applications
via flags autogenerated for this help menu.
Usage:
mcu flash
Flags:
--timeStamp string main.timeStamp // Set timestamp $(date '+%Y-%m-%dT%H:%M:%SZ')
(default "2024-03-22T11:59:17Z")
--offSet string main.offSet // seconds to add to timeStamp
(default "9")
--rtcFuture string main.rtcFuture // set 'true' if RTC is set to the future
(default "false")
--enableLED string main.enableLED // set 'true' to enable onboard LED as a measure of rtc read time or program loop time indicator
(default "false")
-m, --ser string block device for serial interface (i.e. "/dev/ttyACM0")
if unspecified serial connection will not be attempted
-b, --baud int baud rate (default 9600)
-s, --slp duration seconds to wait before serial connection after flashing (default 3s)
-y, --dev string block device to flash (i.e. "/dev/sdx")
if unspecified, tinygo flash command is generated
-z, --target string tinygo flash target
Notice in the above that timestamp was evaluated by the shell.
Not all standard library packages will work with tinygo:
https://tinygo.org/docs/reference/lang-support/stdlib/
Some are importable but don't pass tests. Some work partially. Among these which are importable but fail tests is encoding/json.
It might be relatively trivial to pass json as a string via --ldflags -X and convert that to a struct of the configuration for an LCD, but with a broken library it is not possible.
Other library alternatives such as embed are not working either. So what option exists for configuration?
mcu eval can produce a modified source code, allowing globally-scoped non-string variables to be updated with new values
$ GOPROG=firmware/main.go go run . eval --help
modify initializations in source code for tinygo applications
Usage:
mcu eval
Flags:
--multidisplays string [...]LCDConfig{{DataPins: []m.Pin{m.GP22, m.GP21, m.GP20, m.GP19, m.GP18, m.GP17, m.GP16, m.GP15}, RS: m.GP26, EN: m.GP27, RW: m.NoPin, Contrast: m.GP28, Clvl: 2, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}, {DataPins: []m.Pin{m.GP5, m.GP6, m.GP7, m.GP8, m.GP9, m.GP10, m.GP11, m.GP12}, RS: m.GP4, EN: m.GP3, RW: m.NoPin, Contrast: m.GP2, Clvl: 3, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}}
(default "[...]LCDConfig{{DataPins: []m.Pin{m.GP22, m.GP21, m.GP20, m.GP19, m.GP18, m.GP17, m.GP16, m.GP15}, RS: m.GP26, EN: m.GP27, RW: m.NoPin, Contrast: m.GP28, Clvl: 2, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}, {DataPins: []m.Pin{m.GP5, m.GP6, m.GP7, m.GP8, m.GP9, m.GP10, m.GP11, m.GP12}, RS: m.GP4, EN: m.GP3, RW: m.NoPin, Contrast: m.GP2, Clvl: 3, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}}")
when the command is run, the updated source code is printed to stdout
A combination approach of updated source code + compile time variable assignment is included as mcu ef
First, flags generated for declaration initializations of non string globally scoped variables are assessed to an ephemeral modified version of the source code.
Next, the --ldflags -X for compile-time variable assignments are generated.
Then tinygo flash is invoked with those ldflags to compile the temporary copy of the source code with the modifications ; if the block device to flash is specified
Afterwards, a serial connection is attempted - if the serial interface is specified
$ GOPROG=firmware/main.go go run . ef --help
seamlessly flash modified source code
Usage:
mcu ef
Flags:
--multidisplays string [...]LCDConfig{{DataPins: []m.Pin{m.GP22, m.GP21, m.GP20, m.GP19, m.GP18, m.GP17, m.GP16, m.GP15}, RS: m.GP26, EN: m.GP27, RW: m.NoPin, Contrast: m.GP28, Clvl: 2, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}, {DataPins: []m.Pin{m.GP5, m.GP6, m.GP7, m.GP8, m.GP9, m.GP10, m.GP11, m.GP12}, RS: m.GP4, EN: m.GP3, RW: m.NoPin, Contrast: m.GP2, Clvl: 3, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}}
(default "[...]LCDConfig{{DataPins: []m.Pin{m.GP22, m.GP21, m.GP20, m.GP19, m.GP18, m.GP17, m.GP16, m.GP15}, RS: m.GP26, EN: m.GP27, RW: m.NoPin, Contrast: m.GP28, Clvl: 2, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}, {DataPins: []m.Pin{m.GP5, m.GP6, m.GP7, m.GP8, m.GP9, m.GP10, m.GP11, m.GP12}, RS: m.GP4, EN: m.GP3, RW: m.NoPin, Contrast: m.GP2, Clvl: 3, Rows: 2, Columns: 16, CursorBlink: false, CursorOnOff: false}}")
--timeStamp string main.timeStamp // Set timestamp $(date '+%Y-%m-%dT%H:%M:%SZ')
(default "2024-03-22T12:24:43Z")
--offSet string main.offSet // seconds to add to timeStamp
(default "9")
--rtcFuture string main.rtcFuture // set 'true' if RTC is set to the future
(default "false")
--enableLED string main.enableLED // set 'true' to enable onboard LED as a measure of rtc read time or program loop time indicator
(default "false")
-m, --ser string block device for serial interface (i.e. "/dev/ttyACM0")
if unspecified serial connection will not be attempted
-b, --baud int baud rate (default 9600)
-s, --slp duration seconds to wait before serial connection after flashing (default 3s)
-y, --dev string block device to flash (i.e. "/dev/sdx")
if unspecified, tinygo flash command is generated
-z, --target string tinygo flash target
Example:
$ GOPROG=firmware/main.go go run . ef -z pico -y /dev/sdd1 -m /dev/ttyACM?
modified source code would print here ; omitted for brevity
tinygo flash -target=pico -ldflags=" -X 'main.timeStamp=2024-03-22T13:05:14Z' -X 'main.offSet=9' -X 'main.rtcFuture=false' -X 'main.enableLED=false'" /tmp/2446216074.go
+ sudo echo 'sudo cache'
sudo cache
+ set +x
+ udisksctl mount -b /dev/sdd1
Mounted /dev/sdd1 at /run/media/user/RPI-RP2
+ set +x
ttyusb found: /dev/ttyACM0
"/dev/ttyACM0"
[2024-03-22 13:05:28]
[2024-03-22 13:05:29]
[2024-03-22 13:05:30]
[2024-03-22 13:05:31]
[2024-03-22 13:05:32]
[2024-03-22 13:05:33]
[2024-03-22 13:05:34]
[2024-03-22 13:05:35]
[2024-03-22 13:05:36]
The timestamp logging at the end is produced from the serial interface with the microcontroller
As should be apparent in examining the above text, the date command (provided in the comment of main.go for var timeStamp) is evaluated by the shell (via bitfield/script) and populates the --timeStamp flag as default value ; and is then used in it's evaluated format as:
tinygo flash -target=pico -ldflags=" -X 'main.timeStamp=2024-03-22T13:05:14Z'
the offSet (seconds) is added to the timestamp to compensate for the delay between compilation and setting the time on the RTC, more or less.
-X 'main.offSet=9'
The strategy for time-setting is to set the time on the RTC as closely as possible to the time on the host system, which is assumed to have the correct desired time. In order to make the time not get directly re-set to the timestamp on every boot cycle, the code as presented simply avoids setting the RTC to a time in the past.
In order to handle the situation where the RTC is stuck in the future, the --rtcFuture=true flag is used to explicitly set the time for the RTC to the timestamp time plus offset time.
The microcontroller should then be re-flashed without specifying --rtcFuture=true or by using the default value.
After compensating for the time offset of your hardware, it should be possible to set the RTC to the current time in just a few flashes.
Using this wrapper creates, essentially, a command-line interface or help menu for the application you want to run on an MCU. I/O Reconfiguration should not require editing the source code. It should be as easy to move a wire as to change some text supplied to a flag.
While this method requires intentional coding, in such a way that excludes those variables from appearing in a help menu that you don't want the user to set, and other considerations; it is not limited in it's application to the tinygo context and could be employed in go for creating executables with certain default values hard-coded.
Made with goda:
go run github.com/loov/goda@latest graph github.com/0magnet/tinygo-stuff/... | dot -Tsvg -o docs/tinygo-stuff-goda-graph.svg
Made with gocloc (excludes vendor/, node_modules/, .git/):
gocloc --not-match-d='(vendor|node_modules|\.git)' .
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 9 235 355 2296
Markdown 1 72 0 162
HTML 2 6 2 124
YAML 1 0 11 99
Makefile 1 12 9 53
Bourne Shell 1 8 16 30
JSON 2 0 0 28
-------------------------------------------------------------------------------
TOTAL 17 333 393 2792
-------------------------------------------------------------------------------

