added tutorial and examples
This commit is contained in:
parent
f4bdf4f5ad
commit
3cbc50cd3e
22 changed files with 1453 additions and 12 deletions
65
README.md
65
README.md
|
|
@ -35,7 +35,10 @@ nicebadge/
|
||||||
├── hardware/ # KiCad PCB design files
|
├── hardware/ # KiCad PCB design files
|
||||||
│ └── PCB-kicad/
|
│ └── PCB-kicad/
|
||||||
│ └── production/ # Gerbers, BOM, pick-and-place files
|
│ └── production/ # Gerbers, BOM, pick-and-place files
|
||||||
└── tutorials/ # Step-by-step examples (coming soon)
|
└── tutorial/ # TinyGo tutorials and examples
|
||||||
|
├── basics/ # Step-by-step basics (step0 → step10)
|
||||||
|
├── ble/ # Bluetooth Low Energy examples
|
||||||
|
└── examples/ # Standalone examples (sensors, HID, etc.)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -94,17 +97,59 @@ Production files (Gerbers, BOM, positions) ready for fabrication are in [hardwar
|
||||||
|
|
||||||
## Tutorials & examples
|
## Tutorials & examples
|
||||||
|
|
||||||
The tutorial series walks you through every peripheral of the badge, starting from blinking an LED and ending with a Bluetooth HID device:
|
All code lives under [`tutorial/`](tutorial/). Flash any step from inside that directory:
|
||||||
|
|
||||||
1. Hello, Badge! — blink the RGB LEDs
|
```sh
|
||||||
2. Drawing on the display — TinyGo + TinyDraw
|
cd tutorial
|
||||||
3. Reading buttons and the joystick
|
tinygo flash -target nicenano ./basics/step0
|
||||||
4. Playing sounds with the buzzer
|
```
|
||||||
5. I2C expansion via StemmQT
|
|
||||||
6. Going wireless — BLE advertisements and GATT services
|
|
||||||
7. USB HID — turn the badge into a keyboard
|
|
||||||
|
|
||||||
*(Tutorials are coming soon — contributions welcome!)*
|
Run `go mod tidy` once inside `tutorial/` to fetch all dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Basics
|
||||||
|
|
||||||
|
A progressive series that introduces every peripheral one at a time.
|
||||||
|
|
||||||
|
| Step | What it does |
|
||||||
|
|------|--------------|
|
||||||
|
| [step0](tutorial/basics/step0/) | Blink the built-in LED — verifies the flash toolchain works |
|
||||||
|
| [step1](tutorial/basics/step1/) | Built-in LED controlled by button A |
|
||||||
|
| [step2](tutorial/basics/step2/) | WS2812 RGB LEDs alternating red and green |
|
||||||
|
| [step3](tutorial/basics/step3/) | WS2812 LEDs change color with buttons A, B, and rotary |
|
||||||
|
| [step3b](tutorial/basics/step3b/) | Rainbow cycle on the LEDs, A/B scroll through the hue |
|
||||||
|
| [step4](tutorial/basics/step4/) | "Hello Gophers!" text on the display |
|
||||||
|
| [step5](tutorial/basics/step5/) | Display shows a circle per button; rings appear on press |
|
||||||
|
| [step6](tutorial/basics/step6/) | Analog joystick — a dot follows the stick on the display |
|
||||||
|
| [step7](tutorial/basics/step7/) | Rotary encoder — turning cycles LED colors, push resets |
|
||||||
|
| [step8](tutorial/basics/step8/) | Passive buzzer plays a note per button press |
|
||||||
|
| [step9](tutorial/basics/step9/) | USB MIDI — badge sends notes C4 / E4 / G4 to any DAW |
|
||||||
|
| [step10](tutorial/basics/step10/) | USB HID mouse — joystick moves the cursor, A/B click |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BLE
|
||||||
|
|
||||||
|
Bluetooth Low Energy examples using the nRF52840 on the nice!nano. Compatible apps: **nRF Connect**, **nRF Toolbox**, **Serial Bluetooth Terminal**, **LightBlue**.
|
||||||
|
|
||||||
|
| Step | What it does |
|
||||||
|
|------|--------------|
|
||||||
|
| [step1](tutorial/ble/step1/) | **Counter** — advertises as Nordic UART Service (NUS), sends an incrementing counter via BLE notifications every second; display shows connection status and current value; send `reset` from the app to restart the count |
|
||||||
|
| [step2](tutorial/ble/step2/) | **LED color control** — mobile writes 3 bytes (R, G, B) to a custom characteristic; both WS2812 LEDs and the display update immediately |
|
||||||
|
| [step3](tutorial/ble/step3/) | **Scanner** — lists nearby BLE devices with name and RSSI, color-coded by signal strength; button A clears the list |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Standalone, feature-complete programs.
|
||||||
|
|
||||||
|
| Example | What it does | Extra hardware |
|
||||||
|
|---------|--------------|----------------|
|
||||||
|
| [thermal-camera](tutorial/examples/thermal-camera/) | AMG88xx 8×8 IR sensor upscaled with bilinear interpolation and rendered with an iron-palette color map on the full display | AMG88xx sensor on I2C1 (SDA=P0_17, SCL=P0_20) |
|
||||||
|
| [rubber-duck](tutorial/examples/rubber-duck/) | USB HID keyboard automation (Ubuntu/GNOME target): opens a text editor, types a message, and launches a URL. Press button A to trigger | — |
|
||||||
|
| [co2-sensor](tutorial/examples/co2-sensor/) | SCD4x CO2/temperature/humidity sensor: display background turns green/yellow/red by CO2 level, LEDs mirror the color, buzzer warns above 1500 ppm | SCD4x sensor on I2C1 (SDA=P0_17, SCL=P0_20) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
169
examples/co2-sensor/main.go
Normal file
169
examples/co2-sensor/main.go
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// CO2 sensor example using SCD4x (I2C).
|
||||||
|
// Connect the sensor to I2C1: SDA=P0_17, SCL=P0_20 (3.3V power).
|
||||||
|
//
|
||||||
|
// The display background changes color based on CO2 level:
|
||||||
|
// green → < 800 ppm (good air quality)
|
||||||
|
// yellow → < 1500 ppm (ventilate soon)
|
||||||
|
// red → ≥ 1500 ppm (ventilate now!)
|
||||||
|
//
|
||||||
|
// The WS2812 LEDs mirror the same color, and the buzzer sounds a
|
||||||
|
// warning tone when CO2 exceeds the danger threshold.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/scd4x"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freesans"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display st7789.Device
|
||||||
|
leds ws2812.Device
|
||||||
|
bzrPin machine.Pin
|
||||||
|
co2dev *scd4x.Device
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
machine.I2C1.Configure(machine.I2CConfig{
|
||||||
|
SDA: machine.P0_17,
|
||||||
|
SCL: machine.P0_20,
|
||||||
|
Frequency: 400000,
|
||||||
|
})
|
||||||
|
|
||||||
|
display = st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, // TFT_RESET
|
||||||
|
machine.P1_13, // TFT_DC
|
||||||
|
machine.P0_10, // TFT_CS
|
||||||
|
machine.P0_09) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
leds = ws2812.New(neo)
|
||||||
|
|
||||||
|
bzrPin = machine.P0_31
|
||||||
|
bzrPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
co2dev = scd4x.New(machine.I2C1)
|
||||||
|
co2dev.Configure()
|
||||||
|
|
||||||
|
display.FillScreen(color.RGBA{0, 0, 0, 255})
|
||||||
|
|
||||||
|
if err := co2dev.StartPeriodicMeasurement(); err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
co2 int32
|
||||||
|
temp int32
|
||||||
|
hum int32
|
||||||
|
err error
|
||||||
|
bg color.RGBA
|
||||||
|
oldbg = color.RGBA{R: 0xff, G: 0xff, B: 0xff}
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// SCD4x needs up to 5s between measurements; retry a few times
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
co2, err = co2dev.ReadCO2()
|
||||||
|
temp, _ = co2dev.ReadTemperature()
|
||||||
|
hum, _ = co2dev.ReadHumidity()
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
if co2 != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case co2 < 800:
|
||||||
|
bg = color.RGBA{0, 220, 0, 255}
|
||||||
|
case co2 < 1500:
|
||||||
|
bg = color.RGBA{220, 220, 0, 255}
|
||||||
|
default:
|
||||||
|
bg = color.RGBA{220, 0, 0, 255}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bg != oldbg {
|
||||||
|
display.FillScreen(bg)
|
||||||
|
oldbg = bg
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirror CO2 status on the LEDs
|
||||||
|
leds.WriteColors([]color.RGBA{bg, bg})
|
||||||
|
|
||||||
|
// buzzer warning when CO2 is dangerously high
|
||||||
|
if co2 >= 1500 {
|
||||||
|
warn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// display 240x135, text layout:
|
||||||
|
// y=40 CO2 level (most prominent)
|
||||||
|
// y=75 Temperature
|
||||||
|
// y=110 Humidity
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 40, "CO2: "+strconv.Itoa(int(co2))+" ppm", black)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 75, "Temp: "+formatMilliC(temp), black)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, "Hum: "+formatMilliPct(hum), black)
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// erase text by redrawing in background color (avoids full FillScreen flicker)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 40, "CO2: "+strconv.Itoa(int(co2))+" ppm", bg)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 75, "Temp: "+formatMilliC(temp), bg)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, "Hum: "+formatMilliPct(hum), bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMilliC formats a millidegree Celsius value as "XX.X C".
|
||||||
|
func formatMilliC(mc int32) string {
|
||||||
|
if mc < 0 {
|
||||||
|
mc = -mc
|
||||||
|
return "-" + strconv.Itoa(int(mc/1000)) + "." + strconv.Itoa(int((mc%1000)/100)) + " C"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(mc/1000)) + "." + strconv.Itoa(int((mc%1000)/100)) + " C"
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMilliPct formats a millipercent value as "XX.X %".
|
||||||
|
func formatMilliPct(mp int32) string {
|
||||||
|
return strconv.Itoa(int(mp/1000)) + "." + strconv.Itoa(int((mp%1000)/100)) + " %"
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn sounds two short beeps on the buzzer.
|
||||||
|
func warn() {
|
||||||
|
for beep := 0; beep < 2; beep++ {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
bzrPin.High()
|
||||||
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
bzrPin.Low()
|
||||||
|
time.Sleep(500 * time.Microsecond)
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
83
examples/rubber-duck/main.go
Normal file
83
examples/rubber-duck/main.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Rubber Duck attack - USB HID keyboard automation.
|
||||||
|
// The badge presents itself as a USB keyboard and executes a sequence of
|
||||||
|
// keystrokes on the connected computer.
|
||||||
|
//
|
||||||
|
// TARGETS UBUNTU/GNOME. For other platforms adjust the key sequences.
|
||||||
|
// NOTE: keyboard scan codes are US layout; non-US layouts may produce
|
||||||
|
// different characters for symbols (/, -, :, etc.).
|
||||||
|
//
|
||||||
|
// Press button A to trigger the attack. The badge waits on startup
|
||||||
|
// so you have time to plug it in safely.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"machine/usb/hid/keyboard"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// wait for USB enumeration
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// button A arms the attack — only runs when pressed
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
kb := keyboard.Port()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !btnA.Get() {
|
||||||
|
runAttack(kb)
|
||||||
|
// only run once per press
|
||||||
|
for !btnA.Get() {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAttack(kb keyboard.Device) {
|
||||||
|
// open application launcher (Super key on Ubuntu/GNOME)
|
||||||
|
kb.Down(keyboard.KeyLeftGUI)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
kb.Up(keyboard.KeyLeftGUI)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// search for text editor
|
||||||
|
kb.Write([]byte("text"))
|
||||||
|
time.Sleep(1500 * time.Millisecond)
|
||||||
|
kb.Press(keyboard.KeyEnter)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// type the ominous message
|
||||||
|
kb.Write([]byte("Please wait while you are being hacked"))
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// open run dialog (Alt+F2 on GNOME) and launch xdg-open
|
||||||
|
// NOTE: symbols below assume US keyboard layout
|
||||||
|
kb.Down(keyboard.KeyLeftAlt)
|
||||||
|
kb.Down(keyboard.KeyF2)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
kb.Up(keyboard.KeyF2)
|
||||||
|
kb.Up(keyboard.KeyLeftAlt)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
kb.Write([]byte("xdg"))
|
||||||
|
kb.Press(keyboard.KeypadMinus)
|
||||||
|
kb.Write([]byte("open https>"))
|
||||||
|
kb.Press(keyboard.KeypadSlash)
|
||||||
|
kb.Press(keyboard.KeypadSlash)
|
||||||
|
kb.Write([]byte("www.youtube.com"))
|
||||||
|
kb.Press(keyboard.KeypadSlash)
|
||||||
|
kb.Write([]byte("watch_v)dQw4w9WgXcQ"))
|
||||||
|
kb.Press(keyboard.KeyEnter)
|
||||||
|
|
||||||
|
// turn the volume up
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
kb.Press(keyboard.KeyMediaVolumeInc)
|
||||||
|
}
|
||||||
|
}
|
||||||
6
examples/thermal-camera/colors.go
Normal file
6
examples/thermal-camera/colors.go
Normal file
File diff suppressed because one or more lines are too long
102
examples/thermal-camera/main.go
Normal file
102
examples/thermal-camera/main.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Thermal camera example using AMG88xx 8x8 IR sensor.
|
||||||
|
// Connect the sensor to I2C1: SDA=P0_17, SCL=P0_20 (3.3V power).
|
||||||
|
//
|
||||||
|
// The 8x8 sensor data is scaled to 24x24 with bilinear interpolation and
|
||||||
|
// rendered with an iron-palette color map on the 240x135 display.
|
||||||
|
// Each scaled pixel is drawn as a 10x5 px block → 240x120 total, centered.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
draw2 "golang.org/x/image/draw"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/amg88xx"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sensorSize = 8 // AMG88xx is 8x8
|
||||||
|
scaledSize = 24 // intermediate bilinear-scaled image
|
||||||
|
blockW = 10 // px per scaled pixel (horizontal): 24*10 = 240
|
||||||
|
blockH = 5 // px per scaled pixel (vertical): 24*5 = 120
|
||||||
|
yOffset = 7 // center 120px vertically in 135px display: (135-120)/2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display st7789.Device
|
||||||
|
data [sensorSize * sensorSize]int16
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
machine.I2C1.Configure(machine.I2CConfig{
|
||||||
|
SDA: machine.P0_17,
|
||||||
|
SCL: machine.P0_20,
|
||||||
|
Frequency: 400000,
|
||||||
|
})
|
||||||
|
|
||||||
|
display = st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, // TFT_RESET
|
||||||
|
machine.P1_13, // TFT_DC
|
||||||
|
machine.P0_10, // TFT_CS
|
||||||
|
machine.P0_09) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
|
||||||
|
camera := amg88xx.New(machine.I2C1)
|
||||||
|
camera.Configure(amg88xx.Config{})
|
||||||
|
|
||||||
|
src := image.NewRGBA(image.Rect(0, 0, sensorSize, sensorSize))
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, scaledSize, scaledSize))
|
||||||
|
|
||||||
|
for {
|
||||||
|
camera.ReadPixels(&data)
|
||||||
|
|
||||||
|
// map each sensor pixel to a palette color
|
||||||
|
for j := 0; j < sensorSize; j++ {
|
||||||
|
for i := 0; i < sensorSize; i++ {
|
||||||
|
v := data[63-(i+j*sensorSize)]
|
||||||
|
// clamp to 18°C–33°C range → index 0–432
|
||||||
|
if v < 18000 {
|
||||||
|
v = 0
|
||||||
|
} else {
|
||||||
|
v = (v - 18000) / 36
|
||||||
|
if v > 432 {
|
||||||
|
v = 432
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src.Set(i, j, colors[v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bilinear upscale 8x8 → 24x24
|
||||||
|
draw2.BiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw2.Over, nil)
|
||||||
|
|
||||||
|
// draw horizontally mirrored (acts as a selfie mirror)
|
||||||
|
for j := 0; j < scaledSize; j++ {
|
||||||
|
for i := 0; i < scaledSize; i++ {
|
||||||
|
display.FillRectangle(
|
||||||
|
int16((scaledSize-1-i)*blockW),
|
||||||
|
yOffset+int16(j)*blockH,
|
||||||
|
blockW, blockH,
|
||||||
|
dst.RGBAAt(i, j),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tutorial/basics/step0/main.go
Normal file
29
tutorial/basics/step0/main.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// get the built-in LED pin on the nice!nano board
|
||||||
|
led := machine.LED
|
||||||
|
|
||||||
|
// configure the LED pin for output
|
||||||
|
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
for {
|
||||||
|
// turn off the LED
|
||||||
|
led.Low()
|
||||||
|
|
||||||
|
// wait 500 ms
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// turn on the LED
|
||||||
|
led.High()
|
||||||
|
|
||||||
|
// wait 500 ms
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tutorial/basics/step1/main.go
Normal file
27
tutorial/basics/step1/main.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
led := machine.LED
|
||||||
|
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
// button A is on pin P1_06, active LOW (internal pull-up)
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
for {
|
||||||
|
// button reads LOW when pressed
|
||||||
|
if !btnA.Get() {
|
||||||
|
led.High()
|
||||||
|
} else {
|
||||||
|
led.Low()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
tutorial/basics/step10/main.go
Normal file
52
tutorial/basics/step10/main.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"machine/usb/hid/mouse"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DEADZONE = 5000
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// joystick analog axes
|
||||||
|
machine.InitADC()
|
||||||
|
ax := machine.ADC{Pin: machine.P0_02} // X axis
|
||||||
|
ay := machine.ADC{Pin: machine.P0_29} // Y axis
|
||||||
|
ax.Configure(machine.ADCConfig{})
|
||||||
|
ay.Configure(machine.ADCConfig{})
|
||||||
|
|
||||||
|
// A = left click, B = right click
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnB := machine.P1_04
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
mouseDevice := mouse.Port()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// ADC center is ~32767; compute offset from center
|
||||||
|
rawX := int(ax.Get()) - 32767
|
||||||
|
rawY := int(ay.Get()) - 32767
|
||||||
|
|
||||||
|
var dx, dy int
|
||||||
|
if rawX > DEADZONE || rawX < -DEADZONE {
|
||||||
|
dx = rawX / 2048
|
||||||
|
}
|
||||||
|
if rawY > DEADZONE || rawY < -DEADZONE {
|
||||||
|
dy = rawY / 2048
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseDevice.Move(dx, dy)
|
||||||
|
|
||||||
|
if !btnA.Get() {
|
||||||
|
mouseDevice.Click(mouse.Left)
|
||||||
|
}
|
||||||
|
if !btnB.Get() {
|
||||||
|
mouseDevice.Click(mouse.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
tutorial/basics/step2/main.go
Normal file
42
tutorial/basics/step2/main.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NumberOfLEDs = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
red = color.RGBA{255, 0, 0, 255}
|
||||||
|
green = color.RGBA{0, 255, 0, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// WS2812 LEDs are on pin P1_11
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
leds := ws2812.New(neo)
|
||||||
|
ledColors := make([]color.RGBA, NumberOfLEDs)
|
||||||
|
|
||||||
|
rg := false
|
||||||
|
for {
|
||||||
|
for i := 0; i < NumberOfLEDs; i++ {
|
||||||
|
if rg {
|
||||||
|
ledColors[i] = red
|
||||||
|
} else {
|
||||||
|
ledColors[i] = green
|
||||||
|
}
|
||||||
|
rg = !rg
|
||||||
|
}
|
||||||
|
leds.WriteColors(ledColors)
|
||||||
|
rg = !rg
|
||||||
|
time.Sleep(time.Millisecond * 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
69
tutorial/basics/step3/main.go
Normal file
69
tutorial/basics/step3/main.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Red = iota
|
||||||
|
Green
|
||||||
|
Blue
|
||||||
|
Yellow
|
||||||
|
Cyan
|
||||||
|
Purple
|
||||||
|
White
|
||||||
|
Off
|
||||||
|
)
|
||||||
|
|
||||||
|
var colors = [...]color.RGBA{
|
||||||
|
{255, 0, 0, 255}, // Red
|
||||||
|
{0, 255, 0, 255}, // Green
|
||||||
|
{0, 0, 255, 255}, // Blue
|
||||||
|
{255, 255, 0, 255}, // Yellow
|
||||||
|
{0, 255, 255, 255}, // Cyan
|
||||||
|
{255, 0, 255, 255}, // Purple
|
||||||
|
{255, 255, 255, 255}, // White
|
||||||
|
{0, 0, 0, 255}, // Off
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// WS2812 LEDs are on pin P1_11
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
leds := ws2812.New(neo)
|
||||||
|
ledColors := make([]color.RGBA, 2)
|
||||||
|
|
||||||
|
// buttons: active LOW (internal pull-up)
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnB := machine.P1_04
|
||||||
|
btnRot := machine.P0_22 // rotary encoder push button
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnRot.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
c := Off
|
||||||
|
for {
|
||||||
|
if !btnA.Get() {
|
||||||
|
c = Red
|
||||||
|
}
|
||||||
|
if !btnB.Get() {
|
||||||
|
c = Blue
|
||||||
|
}
|
||||||
|
if !btnRot.Get() {
|
||||||
|
c = Green
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range ledColors {
|
||||||
|
ledColors[i] = colors[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
leds.WriteColors(ledColors)
|
||||||
|
time.Sleep(30 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
tutorial/basics/step3b/main.go
Normal file
52
tutorial/basics/step3b/main.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// WS2812 LEDs are on pin P1_11
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
leds := ws2812.New(neo)
|
||||||
|
ledColors := make([]color.RGBA, 2)
|
||||||
|
|
||||||
|
// A cycles forward, B cycles backward through the rainbow
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnB := machine.P1_04
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
var k uint8
|
||||||
|
for {
|
||||||
|
if !btnA.Get() {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
if !btnB.Get() {
|
||||||
|
k--
|
||||||
|
}
|
||||||
|
|
||||||
|
ledColors[0] = getRainbowRGB(k)
|
||||||
|
ledColors[1] = getRainbowRGB(k + 10)
|
||||||
|
leds.WriteColors(ledColors)
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRainbowRGB(i uint8) color.RGBA {
|
||||||
|
if i < 85 {
|
||||||
|
return color.RGBA{i * 3, 255 - i*3, 0, 255}
|
||||||
|
} else if i < 170 {
|
||||||
|
i -= 85
|
||||||
|
return color.RGBA{255 - i*3, 0, i * 3, 255}
|
||||||
|
}
|
||||||
|
i -= 170
|
||||||
|
return color.RGBA{0, i * 3, 255 - i*3, 255}
|
||||||
|
}
|
||||||
40
tutorial/basics/step4/main.go
Normal file
40
tutorial/basics/step4/main.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freesans"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
display := st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, // TFT_RESET
|
||||||
|
machine.P1_13, // TFT_DC
|
||||||
|
machine.P0_10, // TFT_CS
|
||||||
|
machine.P0_09) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
|
||||||
|
// clear the screen to black
|
||||||
|
display.FillScreen(color.RGBA{0, 0, 0, 255})
|
||||||
|
|
||||||
|
// the display is 240x135 pixels in landscape orientation
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 50, "Hello", color.RGBA{R: 255, G: 255, B: 0, A: 255})
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 90, "Gophers!", color.RGBA{R: 255, G: 0, B: 255, A: 255})
|
||||||
|
}
|
||||||
72
tutorial/basics/step5/main.go
Normal file
72
tutorial/basics/step5/main.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinydraw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
display := st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, // TFT_RESET
|
||||||
|
machine.P1_13, // TFT_DC
|
||||||
|
machine.P0_10, // TFT_CS
|
||||||
|
machine.P0_09) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
|
||||||
|
// buttons: active LOW (internal pull-up)
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnB := machine.P1_04
|
||||||
|
btnRot := machine.P0_22 // rotary encoder push button
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnRot.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
white := color.RGBA{255, 255, 255, 255}
|
||||||
|
circle := color.RGBA{0, 100, 250, 255}
|
||||||
|
ring := color.RGBA{200, 0, 0, 255}
|
||||||
|
|
||||||
|
display.FillScreen(white)
|
||||||
|
|
||||||
|
// draw a circle for each button on the 240x135 display
|
||||||
|
tinydraw.FilledCircle(&display, 60, 67, 14, circle) // B (left)
|
||||||
|
tinydraw.FilledCircle(&display, 120, 67, 14, circle) // rotary button (center)
|
||||||
|
tinydraw.FilledCircle(&display, 180, 67, 14, circle) // A (right)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !btnB.Get() {
|
||||||
|
tinydraw.Circle(&display, 60, 67, 16, ring)
|
||||||
|
} else {
|
||||||
|
tinydraw.Circle(&display, 60, 67, 16, white)
|
||||||
|
}
|
||||||
|
if !btnRot.Get() {
|
||||||
|
tinydraw.Circle(&display, 120, 67, 16, ring)
|
||||||
|
} else {
|
||||||
|
tinydraw.Circle(&display, 120, 67, 16, white)
|
||||||
|
}
|
||||||
|
if !btnA.Get() {
|
||||||
|
tinydraw.Circle(&display, 180, 67, 16, ring)
|
||||||
|
} else {
|
||||||
|
tinydraw.Circle(&display, 180, 67, 16, white)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
63
tutorial/basics/step6/main.go
Normal file
63
tutorial/basics/step6/main.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinydraw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
display := st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, // TFT_RESET
|
||||||
|
machine.P1_13, // TFT_DC
|
||||||
|
machine.P0_10, // TFT_CS
|
||||||
|
machine.P0_09) // TFT_LITE
|
||||||
|
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
|
||||||
|
// joystick analog axes
|
||||||
|
machine.InitADC()
|
||||||
|
ax := machine.ADC{Pin: machine.P0_02} // X axis
|
||||||
|
ay := machine.ADC{Pin: machine.P0_29} // Y axis
|
||||||
|
ax.Configure(machine.ADCConfig{})
|
||||||
|
ay.Configure(machine.ADCConfig{})
|
||||||
|
|
||||||
|
black := color.RGBA{0, 0, 0, 255}
|
||||||
|
dot := color.RGBA{0, 255, 100, 255}
|
||||||
|
|
||||||
|
display.FillScreen(black)
|
||||||
|
|
||||||
|
var prevX, prevY int16
|
||||||
|
for {
|
||||||
|
// ADC returns 0-65535; map to display dimensions (240x135)
|
||||||
|
dotX := int16(uint32(ax.Get()) * 240 / 65535)
|
||||||
|
dotY := int16(uint32(ay.Get()) * 135 / 65535)
|
||||||
|
|
||||||
|
// erase previous dot
|
||||||
|
tinydraw.FilledCircle(&display, prevX, prevY, 5, black)
|
||||||
|
|
||||||
|
// draw new dot
|
||||||
|
tinydraw.FilledCircle(&display, dotX, dotY, 5, dot)
|
||||||
|
|
||||||
|
prevX = dotX
|
||||||
|
prevY = dotY
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
54
tutorial/basics/step7/main.go
Normal file
54
tutorial/basics/step7/main.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/encoders"
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// WS2812 LEDs are on pin P1_11
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
leds := ws2812.New(neo)
|
||||||
|
ledColors := make([]color.RGBA, 2)
|
||||||
|
|
||||||
|
// rotary encoder: A=P1_00, B=P0_24; push button=P0_22
|
||||||
|
enc := encoders.NewQuadratureViaInterrupt(machine.P1_00, machine.P0_24)
|
||||||
|
enc.Configure(encoders.QuadratureConfig{Precision: 4})
|
||||||
|
|
||||||
|
btnRot := machine.P0_22
|
||||||
|
btnRot.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
var k uint8
|
||||||
|
for {
|
||||||
|
// pressing the rotary button resets position to zero
|
||||||
|
if !btnRot.Get() {
|
||||||
|
enc.SetPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encoder position cycles through the rainbow
|
||||||
|
k = uint8(enc.Position())
|
||||||
|
|
||||||
|
ledColors[0] = getRainbowRGB(k)
|
||||||
|
ledColors[1] = getRainbowRGB(k + 85)
|
||||||
|
leds.WriteColors(ledColors)
|
||||||
|
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRainbowRGB(i uint8) color.RGBA {
|
||||||
|
if i < 85 {
|
||||||
|
return color.RGBA{i * 3, 255 - i*3, 0, 255}
|
||||||
|
} else if i < 170 {
|
||||||
|
i -= 85
|
||||||
|
return color.RGBA{255 - i*3, 0, i * 3, 255}
|
||||||
|
}
|
||||||
|
i -= 170
|
||||||
|
return color.RGBA{0, i * 3, 255 - i*3, 255}
|
||||||
|
}
|
||||||
46
tutorial/basics/step8/main.go
Normal file
46
tutorial/basics/step8/main.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bzrPin machine.Pin
|
||||||
|
var btnA, btnB, btnRot machine.Pin
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// buttons: active LOW (internal pull-up)
|
||||||
|
btnA = machine.P1_06
|
||||||
|
btnB = machine.P1_04
|
||||||
|
btnRot = machine.P0_22 // rotary encoder push button
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnRot.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
// passive buzzer on P0_31
|
||||||
|
bzrPin = machine.P0_31
|
||||||
|
bzrPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !btnA.Get() {
|
||||||
|
tone(1046) // C6
|
||||||
|
}
|
||||||
|
if !btnB.Get() {
|
||||||
|
tone(739) // F#5
|
||||||
|
}
|
||||||
|
if !btnRot.Get() {
|
||||||
|
tone(523) // C5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tone(freq int) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
bzrPin.High()
|
||||||
|
time.Sleep(time.Duration(freq) * time.Microsecond)
|
||||||
|
|
||||||
|
bzrPin.Low()
|
||||||
|
time.Sleep(time.Duration(freq) * time.Microsecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
tutorial/basics/step9/main.go
Normal file
49
tutorial/basics/step9/main.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"machine/usb/adc/midi"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// buttons: active LOW (internal pull-up)
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnB := machine.P1_04
|
||||||
|
btnRot := machine.P0_22 // rotary encoder push button
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
btnRot.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
// C major triad: C4, E4, G4
|
||||||
|
notes := []midi.Note{midi.C4, midi.E4, midi.G4}
|
||||||
|
midichannel := uint8(1)
|
||||||
|
|
||||||
|
note := -1
|
||||||
|
oldNote := -1
|
||||||
|
for {
|
||||||
|
note = -1
|
||||||
|
if !btnA.Get() {
|
||||||
|
note = 0 // C4
|
||||||
|
}
|
||||||
|
if !btnB.Get() {
|
||||||
|
note = 1 // E4
|
||||||
|
}
|
||||||
|
if !btnRot.Get() {
|
||||||
|
note = 2 // G4
|
||||||
|
}
|
||||||
|
|
||||||
|
if note != oldNote {
|
||||||
|
if oldNote != -1 {
|
||||||
|
midi.Midi.NoteOff(0, midichannel, notes[oldNote], 50)
|
||||||
|
}
|
||||||
|
if note != -1 {
|
||||||
|
midi.Midi.NoteOn(0, midichannel, notes[note], 50)
|
||||||
|
}
|
||||||
|
oldNote = note
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
145
tutorial/ble/step1/main.go
Normal file
145
tutorial/ble/step1/main.go
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// BLE counter example using the Nordic UART Service (NUS).
|
||||||
|
// Compatible apps: nRF Toolbox, Serial Bluetooth Terminal (Android/iOS).
|
||||||
|
// - Subscribe to TX notifications to receive the counter value every second.
|
||||||
|
// - Send "reset" to the RX characteristic to reset the counter to zero.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/bluetooth"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freesans"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nordic UART Service UUIDs (6E400001-B5A3-F393-E0A9-E50E24DCCA9E)
|
||||||
|
var (
|
||||||
|
adapter = bluetooth.DefaultAdapter
|
||||||
|
|
||||||
|
serviceUUID = bluetooth.NewUUID([16]byte{
|
||||||
|
0x6E, 0x40, 0x00, 0x01, 0xB5, 0xA3, 0xF3, 0x93,
|
||||||
|
0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
|
||||||
|
})
|
||||||
|
rxUUID = bluetooth.NewUUID([16]byte{
|
||||||
|
0x6E, 0x40, 0x00, 0x02, 0xB5, 0xA3, 0xF3, 0x93,
|
||||||
|
0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
|
||||||
|
})
|
||||||
|
txUUID = bluetooth.NewUUID([16]byte{
|
||||||
|
0x6E, 0x40, 0x00, 0x03, 0xB5, 0xA3, 0xF3, 0x93,
|
||||||
|
0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display st7789.Device
|
||||||
|
connected bool
|
||||||
|
counter int
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
cyan = color.RGBA{0, 200, 255, 255}
|
||||||
|
yellow = color.RGBA{255, 220, 0, 255}
|
||||||
|
gray = color.RGBA{150, 150, 150, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDisplay()
|
||||||
|
drawStatus("Advertising...")
|
||||||
|
drawCounter(0)
|
||||||
|
|
||||||
|
must("enable BLE", adapter.Enable())
|
||||||
|
|
||||||
|
var txChar bluetooth.Characteristic
|
||||||
|
|
||||||
|
must("add service", adapter.AddService(&bluetooth.Service{
|
||||||
|
UUID: serviceUUID,
|
||||||
|
Characteristics: []bluetooth.CharacteristicConfig{
|
||||||
|
{
|
||||||
|
UUID: rxUUID,
|
||||||
|
Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
|
||||||
|
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
||||||
|
cmd := string(value)
|
||||||
|
if cmd == "reset" || cmd == "reset\n" {
|
||||||
|
counter = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Handle: &txChar,
|
||||||
|
UUID: txUUID,
|
||||||
|
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
adv := adapter.DefaultAdvertisement()
|
||||||
|
must("configure adv", adv.Configure(bluetooth.AdvertisementOptions{
|
||||||
|
LocalName: "NiceBadge",
|
||||||
|
ServiceUUIDs: []bluetooth.UUID{serviceUUID},
|
||||||
|
}))
|
||||||
|
must("start adv", adv.Start())
|
||||||
|
|
||||||
|
for {
|
||||||
|
counter++
|
||||||
|
drawCounter(counter)
|
||||||
|
|
||||||
|
// Write sends a BLE notification to subscribed centrals.
|
||||||
|
// If no device is subscribed, Write returns an error.
|
||||||
|
_, err := txChar.Write([]byte(strconv.Itoa(counter) + "\n"))
|
||||||
|
|
||||||
|
wasConnected := connected
|
||||||
|
connected = err == nil
|
||||||
|
if wasConnected != connected {
|
||||||
|
if connected {
|
||||||
|
drawStatus("Connected ")
|
||||||
|
} else {
|
||||||
|
drawStatus("Advertising...")
|
||||||
|
must("restart adv", adv.Start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDisplay() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
display = st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, machine.P1_13, machine.P0_10, machine.P0_09)
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
display.FillScreen(black)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawStatus(status string) {
|
||||||
|
display.FillRectangle(0, 0, 240, 32, black)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 22, "BLE: "+status, cyan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawCounter(n int) {
|
||||||
|
display.FillRectangle(0, 38, 240, 97, black)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 68, "Counter", gray)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, strconv.Itoa(n), yellow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to " + action + ": " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
143
tutorial/ble/step2/main.go
Normal file
143
tutorial/ble/step2/main.go
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// BLE LED color control example.
|
||||||
|
// A mobile app writes 3 bytes (R, G, B) to the color characteristic
|
||||||
|
// and both WS2812 LEDs light up with that color.
|
||||||
|
//
|
||||||
|
// Service UUID: BADA5501-B5A3-F393-E0A9-E50E24DCCA9E
|
||||||
|
// Color char: BADA5502-B5A3-F393-E0A9-E50E24DCCA9E
|
||||||
|
//
|
||||||
|
// Compatible apps: nRF Connect, LightBlue (iOS/Android).
|
||||||
|
// To set color: write 3 hex bytes to the color characteristic, e.g. FF0080 = red:255 green:0 blue:128.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/bluetooth"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/drivers/ws2812"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freesans"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter = bluetooth.DefaultAdapter
|
||||||
|
|
||||||
|
ledServiceUUID = bluetooth.NewUUID([16]byte{
|
||||||
|
0xBA, 0xDA, 0x55, 0x01, 0xB5, 0xA3, 0xF3, 0x93,
|
||||||
|
0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
|
||||||
|
})
|
||||||
|
colorCharUUID = bluetooth.NewUUID([16]byte{
|
||||||
|
0xBA, 0xDA, 0x55, 0x02, 0xB5, 0xA3, 0xF3, 0x93,
|
||||||
|
0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display st7789.Device
|
||||||
|
leds ws2812.Device
|
||||||
|
connected bool
|
||||||
|
ledColor color.RGBA
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
cyan = color.RGBA{0, 200, 255, 255}
|
||||||
|
white = color.RGBA{255, 255, 255, 255}
|
||||||
|
gray = color.RGBA{150, 150, 150, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDisplay()
|
||||||
|
initLEDs()
|
||||||
|
drawStatus("Advertising...")
|
||||||
|
drawColor(color.RGBA{0, 0, 0, 255})
|
||||||
|
|
||||||
|
must("enable BLE", adapter.Enable())
|
||||||
|
|
||||||
|
must("add service", adapter.AddService(&bluetooth.Service{
|
||||||
|
UUID: ledServiceUUID,
|
||||||
|
Characteristics: []bluetooth.CharacteristicConfig{
|
||||||
|
{
|
||||||
|
UUID: colorCharUUID,
|
||||||
|
Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
|
||||||
|
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
|
||||||
|
if len(value) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// value[0]=R, value[1]=G, value[2]=B
|
||||||
|
ledColor = color.RGBA{value[0], value[1], value[2], 255}
|
||||||
|
setLEDs(ledColor)
|
||||||
|
drawColor(ledColor)
|
||||||
|
|
||||||
|
if !connected {
|
||||||
|
connected = true
|
||||||
|
drawStatus("Connected ")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
adv := adapter.DefaultAdvertisement()
|
||||||
|
must("configure adv", adv.Configure(bluetooth.AdvertisementOptions{
|
||||||
|
LocalName: "NiceBadge",
|
||||||
|
ServiceUUIDs: []bluetooth.UUID{ledServiceUUID},
|
||||||
|
}))
|
||||||
|
must("start adv", adv.Start())
|
||||||
|
|
||||||
|
// wait forever; all logic is driven by the BLE WriteEvent callback
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDisplay() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
display = st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, machine.P1_13, machine.P0_10, machine.P0_09)
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
display.FillScreen(black)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLEDs() {
|
||||||
|
neo := machine.P1_11
|
||||||
|
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
leds = ws2812.New(neo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLEDs(c color.RGBA) {
|
||||||
|
leds.WriteColors([]color.RGBA{c, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawStatus(status string) {
|
||||||
|
display.FillRectangle(0, 0, 240, 32, black)
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 22, "BLE: "+status, cyan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawColor(c color.RGBA) {
|
||||||
|
// show a filled rectangle with the current color and its RGB values below
|
||||||
|
display.FillRectangle(0, 38, 240, 60, color.RGBA{c.R, c.G, c.B, 255})
|
||||||
|
display.FillRectangle(0, 100, 240, 35, black)
|
||||||
|
rgb := "R:" + strconv.Itoa(int(c.R)) +
|
||||||
|
" G:" + strconv.Itoa(int(c.G)) +
|
||||||
|
" B:" + strconv.Itoa(int(c.B))
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 122, rgb, white)
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to " + action + ": " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
150
tutorial/ble/step3/main.go
Normal file
150
tutorial/ble/step3/main.go
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// BLE scanner example.
|
||||||
|
// Scans for nearby BLE devices and shows their names, addresses and signal
|
||||||
|
// strength (RSSI) on the display. Press button A to clear the list.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/bluetooth"
|
||||||
|
"tinygo.org/x/drivers/st7789"
|
||||||
|
"tinygo.org/x/tinyfont"
|
||||||
|
"tinygo.org/x/tinyfont/freesans"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxDevices = 5
|
||||||
|
|
||||||
|
type bleDevice struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
rssi int16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapter = bluetooth.DefaultAdapter
|
||||||
|
display st7789.Device
|
||||||
|
devices [maxDevices]bleDevice
|
||||||
|
count int
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
black = color.RGBA{0, 0, 0, 255}
|
||||||
|
cyan = color.RGBA{0, 200, 255, 255}
|
||||||
|
white = color.RGBA{255, 255, 255, 255}
|
||||||
|
yellow = color.RGBA{255, 220, 0, 255}
|
||||||
|
green = color.RGBA{0, 220, 100, 255}
|
||||||
|
gray = color.RGBA{150, 150, 150, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initDisplay()
|
||||||
|
|
||||||
|
btnA := machine.P1_06
|
||||||
|
btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||||
|
|
||||||
|
must("enable BLE", adapter.Enable())
|
||||||
|
|
||||||
|
drawHeader()
|
||||||
|
|
||||||
|
// scan runs in a goroutine; found devices are stored and displayed in the main loop
|
||||||
|
go func() {
|
||||||
|
adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
|
||||||
|
name := result.LocalName()
|
||||||
|
if name == "" {
|
||||||
|
name = result.Address.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// update existing entry if already seen, otherwise add new
|
||||||
|
found := false
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if devices[i].addr == result.Address.String() {
|
||||||
|
devices[i].rssi = result.RSSI
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found && count < maxDevices {
|
||||||
|
devices[count] = bleDevice{
|
||||||
|
name: name,
|
||||||
|
addr: result.Address.String(),
|
||||||
|
rssi: result.RSSI,
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// button A clears the list
|
||||||
|
if !btnA.Get() {
|
||||||
|
count = 0
|
||||||
|
display.FillRectangle(0, 32, 240, 103, black)
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawDevices()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDisplay() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
SCK: machine.P1_01,
|
||||||
|
SDO: machine.P1_02,
|
||||||
|
Frequency: 8000000,
|
||||||
|
Mode: 0,
|
||||||
|
})
|
||||||
|
display = st7789.New(machine.SPI0,
|
||||||
|
machine.P1_15, machine.P1_13, machine.P0_10, machine.P0_09)
|
||||||
|
display.Configure(st7789.Config{
|
||||||
|
Rotation: st7789.ROTATION_90,
|
||||||
|
Width: 135,
|
||||||
|
Height: 240,
|
||||||
|
RowOffset: 40,
|
||||||
|
ColumnOffset: 53,
|
||||||
|
})
|
||||||
|
display.FillScreen(black)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawHeader() {
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 24, "BLE Scanner", cyan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawDevices() {
|
||||||
|
// clear device list area
|
||||||
|
display.FillRectangle(0, 32, 240, 103, black)
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 55, "No devices found...", gray)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
y := int16(52 + i*20)
|
||||||
|
name := devices[i].name
|
||||||
|
if len(name) > 16 {
|
||||||
|
name = name[:16]
|
||||||
|
}
|
||||||
|
rssi := " " + strconv.Itoa(int(devices[i].rssi)) + "dB"
|
||||||
|
|
||||||
|
// color by signal strength
|
||||||
|
c := white
|
||||||
|
if devices[i].rssi > -60 {
|
||||||
|
c = green
|
||||||
|
} else if devices[i].rssi > -80 {
|
||||||
|
c = yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 5, y, name+rssi, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(action string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to " + action + ": " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
tutorial/go.mod
Normal file
3
tutorial/go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module code.madriguera.me/GoEducation/nicebadge/tutorial
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
0
tutorial/go.sum
Normal file
0
tutorial/go.sum
Normal file
Loading…
Reference in a new issue