added tutorial and examples
This commit is contained in:
parent
f4bdf4f5ad
commit
3cbc50cd3e
22 changed files with 1453 additions and 12 deletions
69
README.md
69
README.md
|
|
@ -32,10 +32,13 @@ The microcontroller is the **[nice!nano](https://nicekeyboards.com/nice-nano/)**
|
|||
|
||||
```
|
||||
nicebadge/
|
||||
├── hardware/ # KiCad PCB design files
|
||||
├── hardware/ # KiCad PCB design files
|
||||
│ └── PCB-kicad/
|
||||
│ └── production/ # Gerbers, BOM, pick-and-place files
|
||||
└── tutorials/ # Step-by-step examples (coming soon)
|
||||
│ └── production/ # Gerbers, BOM, pick-and-place files
|
||||
└── 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
|
||||
|
||||
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
|
||||
2. Drawing on the display — TinyGo + TinyDraw
|
||||
3. Reading buttons and the joystick
|
||||
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
|
||||
```sh
|
||||
cd tutorial
|
||||
tinygo flash -target nicenano ./basics/step0
|
||||
```
|
||||
|
||||
*(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