This page is for the TinyGo Keeb Tour that started on 2024/08/04. If you have any questions, please create an issue in this repository or contact twitter:sago35tk.
This page has Japanese and English versions.
For hardware assembly, please refer to the build guide:
QR code for this page:
The following dependencies are required. For TinyGo, please use the latest version available.
- Git
- https://git-scm.com/downloads
- Not required for Go / TinyGo, but necessary for this workshop
- Go
- https://go.dev/dl/
- Installation instructions: https://go.dev/doc/install
- https://go.dev/dl/
- TinyGo
- https://github.com/tinygo-org/tinygo/releases/latest
- Installation instructions: https://tinygo.org/getting-started/install/
- https://github.com/tinygo-org/tinygo/releases/latest
Note that Go and TinyGo versions must be compatible. TinyGo typically requires the latest or previous version of Go.
| TinyGo | Compatible Go |
|---|---|
| 0.41.1 | 1.26 - 1.25 |
| 0.40.1 | 1.25 - 1.24 |
You can verify your installation with these commands:
$ tinygo version
tinygo version 0.41.1 windows/amd64 (using go version go1.25.6 and LLVM version 20.1.1)
$ tinygo build -o out.uf2 --target waveshare-rp2040-zero --size short examples/serial
code data bss | flash ram
9720 108 5208 | 9828 5316
$ tinygo flash --target waveshare-rp2040-zero --size short examples/serial
code data bss | flash ram
9720 108 5208 | 9828 5316
$ tinygo monitor --target waveshare-rp2040-zero
Connected to COM12. Press Ctrl-C to exit.
hello world!
hello world!
hello world!
On macOS 15 Sequoia or later, specify the port when running tinygo flash.
This also applies to the other tinygo flash commands shown below.
Port names vary by environment; use tinygo ports to find yours if necessary.
$ tinygo flash -port=$(ls /dev/cu.usbmodem*) --target waveshare-rp2040-zero --size short examples/serial
You can use the Linux version of TinyGo on Ubuntu in WSL2. However, WSL2 cannot directly access USB devices connected to the Windows host. Even when using WSL2, it's generally better to install the Windows version of TinyGo in your Windows PATH. In this case, you'll also need to install the Windows version of Go.
If you insist on using TinyGo from WSL2, you can use usbipd as shown below.
However, it's inconvenient because you need to attach usbipd every time you run tinygo flash.
To use tinygo flash, tinygo monitor, or Vial on Linux, you need to configure udev rules.
Create /etc/udev/rules.d/99-zero-kb02-udev.rules with the following contents and restart.
# RP2040
# ref: https://docs.platformio.org/en/latest/core/installation/udev-rules.html
ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="[01]*", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
# Vial
# ref: https://get.vial.today/manual/linux-udev.html
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{serial}=="*vial:f64c2b3c*", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"
A copy of this file is available here:
This file was created from the following documents. For more details, please refer to:
- https://docs.platformio.org/en/latest/core/installation/udev-rules.html
- https://get.vial.today/manual/linux-udev.html
If you want to use the latest version under development, download Artifact > release-double-zipped built by GitHub Actions.
- Windows
- Linux
- macOS
For more information, see:
TinyGo places packages like the machine package in GOROOT, so until you configure it, gopls will show errors and you won't be able to jump to definitions like machine.LED. TinyGo has an unfamiliar package structure (even if you know Go well) and many build-tag branches, so it's better to set up LSP for TinyGo.
The official documentation is available here:
For VS Code, we recommend installing the TinyGo extension.
For Vim (+ vim-lsp), try github.com/sago35/tinygo.vim.
For information in Japanese, see:
- How to integrate TinyGo + 'VSCode or Vim (or other LSP-compatible editors)' with gopls
- Settings for TinyGo + Vim with gopls
If you use Bash, Zsh, or Clink, you can install command-line completion for TinyGo:
The TinyGo Keeb Tour uses a custom keyboard/macro pad called zero-kb02. The microcontroller is an RP2040 (Cortex M0+), and the board is Waveshare RP2040-Zero.
Main features:
- Waveshare RP2040-Zero
- 12 keys with RGB LEDs
- 2-axis analog joystick for mouse cursor movement
- Rotary encoder
- OLED display - 128x64 monochrome
- GROVE connector
- 2x6 pin socket on the back
Schematics, firmware, pinouts, etc. can be found at:
- https://github.com/sago35/keyboards
- Schematics: kicanvas
For soldering and assembly instructions, please refer to the build guide:
First, clone this repository somewhere. From now on, we'll execute commands from the root of this repository. If you want to modify the source code, please edit the local code.
$ git clone https://github.com/tinygo-keeb/workshop
$ cd workshop
# Launch VS Code or other editor
$ code .
The source code is in paths like ./00_basic or ./12_matrix_basic.
You can build and flash from the command line with TinyGo, but we'll learn how to flash manually too.
Boards with RP2040 can boot into the bootloader by pressing the BOOT/BOOTSEL button while resetting (pressing the reset button or connecting to USB).
When in bootloader mode, the PC recognizes it as an external drive, so you can flash it by copying the binary file (*.uf2) to the newly created external drive.
Try flashing the following:
If the LEDs on the key switches light up, the write was successful.
Note: This flashing method is also valid for uf2 files created outside of TinyGo. Putting the device into bootloader mode will help if for some reason the flash command does not work.
To create the 00_basic.uf2 yourself, execute the following command.
If no error messages are displayed and 00_basic.uf2 is created, it's successful.
$ tinygo build -o 00_basic.uf2 --target waveshare-rp2040-zero --size short ./00_basic/
code data bss | flash ram
20420 192 3240 | 20612 3432
You can also build and flash at once using the tinygo flash command. If no error messages are displayed, the flash has completed successfully. If it fails in a Linux environment, check the udev rules settings mentioned earlier.
$ tinygo flash --target waveshare-rp2040-zero --size short examples/serial
code data bss | flash ram
7836 108 3152 | 7944 3260
The examples/serial program displays hello world! to the serial output.
You can verify it's working with the following:
$ tinygo monitor
Connected to COM7. Press Ctrl-C to exit.
hello world!
hello world!
hello world!
If you can't connect properly, check the port and add the --port option. The waveshare-rp2040-zero uses the same USB VID/PID as other boards with the RP2040 microcontroller, so the Boards section might not display correctly, but don't worry about it.
$ tinygo ports
Port ID Boards
COM7 2E8A:0003 waveshare-rp2040-zero
$ tinygo monitor --port COM7
Connected to COM7. Press Ctrl-C to exit.
hello world!
hello world!
hello world!
There's also a way to run tinygo flash --monitor which combines tinygo flash and tinygo monitor.
However, depending on the environment, it may connect to the wrong port, so if it doesn't work well, run them separately as shown above.
$ tinygo flash --target waveshare-rp2040-zero --size short --monitor examples/serial
code data bss | flash ram
7836 108 3152 | 7944 3260
Connected to COM7. Press Ctrl-C to exit.
hello world!
hello world!
hello world!
Note: This issue was fixed by micchie and merged into TinyGo 0.38.
Add NO NAME to msd-volume-name in $TINYGOROOT/targets/rp2040.json.
You can find $TINYGOROOT with tinygo env.
The modified JSON file is as follows:
{
"inherits": ["cortex-m0plus"],
"build-tags": ["rp2040", "rp"],
"flash-1200-bps-reset": "true",
"flash-method": "msd",
"serial": "usb",
"msd-volume-name": ["RPI-RP2", "NO NAME"],
"msd-firmware-name": "firmware.uf2",
"binary-format": "uf2",
"uf2-family-id": "0xe48bff56",
"rp2040-boot-patch": true,
"extra-files": [
"src/device/rp/rp2040.s"
],
"linkerscript": "targets/rp2040.ld",
"openocd-interface": "picoprobe",
"openocd-transport": "swd",
"openocd-target": "rp2040"
}Open a terminal and run the following, then restart:
$ sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.DiskArbitration.diskarbitrationd.plist DADisableEjectNotification -bool YES && sudo pkill diskarbitrationd
To revert back, run the following and restart:
$ sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.DiskArbitration.diskarbitrationd.plist DADisableEjectNotification && sudo pkill diskarbitrationd
See: https://www.reddit.com/r/mac/comments/vsn1t6/how_to_disable_not_ejected_safely_notification_on/
This could be due to:
- There's a problem with the USB cable
- Check if it's recognized with
tinygo portsor as a drive (try booting into bootloader mode)
- Check if it's recognized with
- Writing to external drives is restricted
- Company computers may restrict writing for security reasons
- In this case, neither tinygo flash nor copying uf2 files will work
Run the following:
$ tinygo flash --target waveshare-rp2040-zero --size short ./01_blinky1/If the RGB LED on the RP2040 Zero lights up successfully, try changing the color or blink speed by modifying the source code.
You can set a color.Color at the black or white locations below:
// 01_blinky1/main.go
for {
time.Sleep(time.Millisecond * 500)
ws.PutColor(black)
time.Sleep(time.Millisecond * 500)
ws.PutColor(white)
}Here are some other color examples. You can set any color by specifying RGBA. You can (somewhat) reduce the brightness by making the 0xFF values smaller.
red = color.RGBA{R: 0xFF, G: 0x00, B: 0x00, A: 0x00}
green = color.RGBA{R: 0x00, G: 0xFF, B: 0x00, A: 0x00}
blue = color.RGBA{R: 0x00, G: 0x00, B: 0xFF, A: 0x00}
yellow = color.RGBA{R: 0xFF, G: 0xFF, B: 0x00, A: 0x00}
cyan = color.RGBA{R: 0x00, G: 0xFF, B: 0xFF, A: 0x00}
magenta = color.RGBA{R: 0xFF, G: 0x00, B: 0xFF, A: 0x00}
Let's light up the keys. The board has 12 SK2812MINI-E LEDs (WS2812B compatible). They are mounted in the following positions/order:
0 3 6 9
1 4 7 10
2 5 8 11
Run the following: If it works successfully, try changing the color, blink speed, or blink pattern by modifying the source code.
$ tinygo flash --target waveshare-rp2040-zero --size short ./02_blinky2/It's almost the same as before, but WriteRaw() is used instead of PutColor().
Here, colors[0][:i+1] is specified, but if [:1] is specified, only the first LED is set.
[:4] would change a total of 4 LEDs.
// ./02_blinky2/main.go
ws.WriteRaw(colors[0][:i+1])WriteRaw() allows you to specify colors as uint32.
The values are set as Green / Red / Blue, 8 bits each from the most significant bit.
For example:
// ./02_blinky2/main.go
colors := []uint32{
0xFFFFFFFF, // white
0xFF0000FF, // green
0x00FF00FF, // red
0x0000FFFF, // blue
}You can (somewhat) reduce the brightness by making the 0xFF values smaller.
Let's also try USB CDC, which is useful for printf debugging and other purposes. USB CDC stands for Universal Serial Bus Communications Device Class, and simply put, it enables serial communication between a computer and a microcontroller through the USB cable. Rather than explaining, it's easier to understand by trying it, so first run the following:
$ tinygo flash --target waveshare-rp2040-zero --size short examples/serial
$ tinygo monitorOn Windows, it will look like this:
$ tinygo flash --target waveshare-rp2040-zero --size short examples/serial
code data bss | flash ram
7836 108 3152 | 7944 3260
$ tinygo monitor
Connected to COM7. Press Ctrl-C to exit.
hello world!
hello world!
hello world!
(omitted)
The examples/serial source is located at ./03_usbcdc-serial.
It repeatedly displays hello world! and then waits for 1 second.
Try changing the wait time, display string, or using fmt.Printf() for writing.
$ tinygo flash --target waveshare-rp2040-zero --size short ./03_usbcdc-serial/Standard input can be handled with code like ./04_usbcdc-echo/.
After pressing Enter/Return, you need to press Ctrl-j for a line break.
$ tinygo flash --target waveshare-rp2040-zero --size short ./04_usbcdc-echo/// ./04_usbcdc-echo/main.go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Printf("you typed : %s\n", scanner.Text())
}
}You can use encoders/quadrature-interrupt from tinygo-org/drivers.
Here's the configuration adjusted for zero-kb02:
// ./05_rotary/main.go
enc := encoders.NewQuadratureViaInterrupt(
machine.GPIO3,
machine.GPIO4,
)
enc.Configure(encoders.QuadratureConfig{
Precision: 4,
})
You can write and check operation with the following commands. When you turn the rotary encoder, the value display will update. Try linking it with the LEDs as an exercise.
$ tinygo flash --target waveshare-rp2040-zero --size short ./05_rotary/
code data bss | flash ram
8276 108 3624 | 8384 3732
$ tinygo monitor
Connected to COM7. Press Ctrl-C to exit.
value: -1
value: -2
value: -1
value: 0
value: 1
value: 2
(omitted)
Note that the rotary encoder can also be used as a button when pressed. Getting the pressed state of the rotary encoder will be discussed in the next section.
When the rotary encoder is pressed, it connects to GND and goes Low. With pull-up enabled, it will be High when not pressed.
The basic code is as follows:
// ./13_rotary_button/main.go
if !btn.Get() {
println("pressed")
} else {
println("released")
}$ tinygo flash --target waveshare-rp2040-zero --size short ./13_rotary_button/
$ tinygo monitorWhen you press the rotary encoder, pressed will be output to the terminal running tinygo monitor.
The analog joystick can be recognized as a digital value when pressed, and as analog values for the X and Y axes. So it can be handled as follows:
$ tinygo flash --target waveshare-rp2040-zero --size short ./06_joystick/
code data bss | flash ram
56792 1536 3176 | 58328 4712
$ tinygo monitor
Connected to COM7. Press Ctrl-C to exit.
7440 8000 false
7130 7F90 true
(omitted)From the left, it shows X-axis value (voltage value), Y-axis value, and whether it's pressed. When not doing anything, values close to 0x8000 are displayed.
You can use ssd1306/i2c_128x64 from tinygo-org/drivers.
Here's the configuration adjusted for zero-kb02:
// ./07_oled/main.go
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
SDA: machine.GPIO12,
SCL: machine.GPIO13,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})You can write and check operation with the following command:
$ tinygo flash --target waveshare-rp2040-zero --size short ./07_oled/Note: As of 2024/08/04, OLED drawing sometimes stops (currently investigating)
$ tinygo flash --target waveshare-rp2040-zero --size short ./08_oled_tinydraw/$ tinygo flash --target waveshare-rp2040-zero --size short ./09_oled_tinyfont/On zero-kb02, the OLED is mounted upside down, so you need to rotate the screen somehow. Here, let's try rotating it via hardware.
As shown below, you can rotate with Rotation in the Config.
For SSD1306, only Rotation0 (no rotation) and Rotation180 (inverted) can be used.
// ./10_oled_rotated/main.go
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
Rotation: drivers.Rotation180,
})You can also rotate with SetRotation() outside of Configure() time.
display.SetRotation(drivers.Rotation180)$ tinygo flash --target waveshare-rp2040-zero --size short ./16_oled_inverted_hw/We have seen how to implement no rotation or inversion; however, in some cases you might want to rotate the display 90 degrees to use it in portrait mode. In this case, you need to rotate via software. Screen drawing basically corresponds to the following Displayer interface, so we define a Displayer that can rotate the screen.
// https://github.com/tinygo-org/drivers/blob/release/displayer.go
type Displayer interface {
// Size returns the current size of the display.
Size() (x, y int16)
// SetPixel modifies the internal buffer.
SetPixel(x, y int16, c color.RGBA)
// Display sends the buffer (if any) to the screen.
Display() error
}Here, we've defined the following. We embed Displayer in the struct and process the x and y values of Size and SetPixel.
// ./16_oled_inverted_hw/main.go
type RotatedDisplay struct {
drivers.Displayer
}
func (d *RotatedDisplay) Size() (x, y int16) {
return y, x
}
func (d *RotatedDisplay) SetPixel(x, y int16, c color.RGBA) {
_, sy := d.Displayer.Size()
d.Displayer.SetPixel(y, sy-x, c)
}$ tinygo flash --target waveshare-rp2040-zero --size short ./10_oled_rotated/You can update the screen without flickering by using display.ClearBuffer() and display.Display().
$ tinygo flash --target waveshare-rp2040-zero --size short ./11_oled_animation/Currently, either BDF or OTF/TTF fonts can be displayed.
For small displays with 1-bit color like zero-kb02, BDF fonts are more suitable.
You can use them as follows:
$ tinygo flash --target waveshare-rp2040-zero --size short ./17_oled_japanese_font/zero-kb02 uses a wiring method called a matrix for its key connections. As shown in the circuit below, 12 switches are connected using 7 pins in a COL:4 x ROW:3 configuration.
The reading process works as follows:
- Set only COL1 to High, and set COL2 through COL4 to Low
- Wait a moment
- Read ROW1 through ROW3 in that state
- If ROW1 is High, SW1 is pressed
- If ROW2 is High, SW5 is pressed
- If ROW3 is High, SW9 is pressed
Next, if only COL2 is set to High, SW2 / SW6 / SW10 can be read, and so on.
Matrix wiring is a widely used connection method in custom keyboards, so let's implement it. A simple implementation of the above would look like this:
// ./12_matrix_basic/main.go
colPins[0].High()
colPins[1].Low()
colPins[2].Low()
colPins[3].Low()
time.Sleep(1 * time.Millisecond)
if rowPins[0].Get() {
fmt.Printf("sw1 pressed\n")
}
if rowPins[1].Get() {
fmt.Printf("sw5 pressed\n")
}
if rowPins[2].Get() {
fmt.Printf("sw9 pressed\n")
}By implementing the same pattern for all columns, you can detect the press state of all keys.
$ tinygo flash --target waveshare-rp2040-zero --size short --monitor ./12_matrix_basic/By organizing the loops and making the number of keys variable, you can move closer to a keyboard firmware.
Note: For those who want to learn more about matrix wiring, please see: https://blog.ikejima.org/make/keyboard/2019/12/14/keyboard-circuit.html
Let's create a USB HID Keyboard using the rotary encoder's press state.
The following code allows you to link the press state with the A key.
In TinyGo, you can import machine/usb/hid/keyboard and call keyboard.Port(), so your device will act as a keyboard recognized by the computer.
// ./14_hid_keyboard/main.go
kb := keyboard.Port()
for {
if !btn.Get() {
kb.Down(keyboard.KeyA)
} else {
kb.Up(keyboard.KeyA)
}
}$ tinygo flash --target waveshare-rp2040-zero --size short ./14_hid_keyboard/Press the rotary encoder to verify it's working properly.
Now let's create a USB HID Mouse using the rotary encoder's press state. With the following code, pressing the button becomes a mouse left click.
// ./15_hid_mouse/main.go
m := mouse.Port()
for {
if !btn.Get() {
m.Press(mouse.Left)
} else {
m.Release(mouse.Left)
}
}$ tinygo flash --target waveshare-rp2040-zero --size short ./15_hid_mouse/Press the rotary encoder to verify it's working properly.
TinyGo supports USB MIDI, so you can make MIDI sound sources or MIDI instruments.
You can use the 12 keys and the rotary encoder press.
$ tinygo flash --target waveshare-rp2040-zero --size short ./18_midi/
After creation, you can test it on sites like:
In Windows environments, MIDI-OX is a good option:
Please also refer to the following examples:
Note: This example requires an external driven buzzer. Note: Connect the buzzer between EX01 and 3V3. With the board face up, connect the buzzer to the top-left pin and the third pin from the left in the top row.
There are various ways to drive a buzzer, but here we use PWM. When using PWM with TinyGo, note that some microcontroller-specific configuration is required.
For RP2040, when using the back panel pins (EX01 - EX04) on zero-kb02, you can use the following configuration. You need to know which pin to use and which PWMGroup it corresponds to. Below is a map of pins to their corresponding PWMGroup:
var pinToPWM = map[machine.Pin]tone.PWM{
machine.GPIO14: machine.PWM7, // for EX01
machine.GPIO15: machine.PWM7, // for EX02
machine.GPIO26: machine.PWM5, // for EX03
machine.GPIO27: machine.PWM5, // for EX04
}After this, you can use tinygo.org/x/drivers/tone to produce sound:
func main() {
bzrPin := machine.GPIO14
pwm := pinToPWM[bzrPin]
speaker, err := tone.New(pwm, bzrPin)
if err != nil {
println("failed to configure PWM")
return
}
song := []tone.Note{
tone.C5,
tone.D5,
tone.E5,
tone.F5,
tone.G5,
tone.A5,
tone.B5,
tone.C6,
tone.C6,
tone.B5,
tone.A5,
tone.G5,
tone.F5,
tone.E5,
tone.D5,
tone.C5,
}
for {
for _, val := range song {
speaker.SetNote(val)
time.Sleep(time.Second / 2)
}
}
}$ tinygo flash --target waveshare-rp2040-zero --size short ./22_buzzer/- External driven buzzer example
- Reference sources
Note: This example requires an I2C-connected SHT4x sensor (SHT40 / SHT41, etc.). Note: Connect it to the GROVE connector.
Here we use an I2C sensor connected to the GROVE connector. Since the GROVE connector shares the same pins as the OLED, you need to use I2C0.
The frequency is set to 2.8MHz here, but according to specifications, it should be lowered to around 400KHz.
Temperature and humidity can be obtained using ReadTemperatureHumidity():
// import "tinygo.org/x/drivers/sht4x" is required
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 2.8 * machine.MHz,
SDA: machine.GPIO12,
SCL: machine.GPIO13,
})
sensor := sht4x.New(machine.I2C0)
temp, humidity, _ := sensor.ReadTemperatureHumidity()
t := fmt.Sprintf("Temperature %.2f C", float32(temp)/1000)
h := fmt.Sprintf("Humidity %.2f %%", float32(humidity)/1000)- Where to get SHT4x
The necessary elements for a custom keyboard vary from person to person. However, some common requirements include:
- Layer functionality
- Ability to change settings without rebuilding
- Package-based methods for reading various switches
Creating these from scratch each time can be challenging and time consuming, so using some kind of library is common. Here, let's create a custom keyboard using the package sago35/tinygo-keyboard.
With sago35/tinygo-keyboard, you can easily implement the following features:
- Support for various key input methods (matrix, GPIO, rotary encoder, etc.)
- Possibility to write your own extensions
- Layer functionality
- Integration with mouse clicks and pointer movement
- Split keyboard support via TRRS cable
- Configuration changes through Vial via web browser
- Keymaps
- Layers
- Matrix tester (key switch press test)
- Macro functionality
The Vial integration is particularly important as it makes it easy to change settings according to individual preferences without the need to re-flash the firmware again. Vial is available at the following URL and can be accessed from Edge/Chrome browsers that support the WebHID API:
For detailed usage instructions, please refer to:
- Creating a Custom Keyboard with sago35/tinygo-keyboard
- Create Your Own Keyboard with sago35/tinygo-keyboard
Available here:
We are developing a 2D game engine for TinyGo called koebiten.
It is positioned as a TinyGo version of Ebitengine, a 2D game engine for Go.
It features support for multiple hardware platforms including zero-kb02, and a simple API.
You can start by just running the samples. UF2 files can be downloaded from:
An introductory guide is available on Zenn (in Japanese):
- Cannot flash the program
Check if the microcontroller is recognized with the tinygo ports command. If recognized correctly, waveshare-rp2040-zero will be displayed.
$ tinygo ports
Port ID Boards
COM7 2E8A:0003 waveshare-rp2040-zero
If not recognized, disconnect the microcontroller from the PC and reconnect it. Try putting it in bootloader mode: hold the BOOT button on the back, press the RST button and release both.
- https://x.com/ysaito8015/status/1827626098450166185
- https://x.com/ysaito8015/status/1827630059580231788
- https://x.com/sago35tk/status/1830208709471223966
- https://x.com/Ryu_07_29/status/1847921967070163377
- ./19_redkey/
- ./20_rotary_gopher
- MIDI
- Buzzer
- https://x.com/triring/status/1891448348818776323
- https://github.com/sago35/koebiten
- When taking photos or videos, setting to 30 frames/second prevents LCD flickering.
I wrote a technical book "Learning TinyGo Embedded Development from the Basics" (released on 2022/11/12) using TinyGo 0.26 + Wio Terminal. Please check it along with this page.





