diff --git a/README.md b/README.md index b4da79a..5b79e52 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,9 @@ A progressive series that introduces every peripheral one at a time. | [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 | +| [step9](tutorial/basics/step9/) | Serial monitor — prints button, encoder, and joystick events over USB serial | +| [step10](tutorial/basics/step10/) | USB MIDI — badge sends notes C4 / E4 / G4 to any DAW | +| [step11](tutorial/basics/step11/) | USB HID mouse — joystick moves the cursor, A/B click | --- diff --git a/TUTORIAL.md b/TUTORIAL.md index 4880088..13ee469 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -409,7 +409,57 @@ Each button plays a different note while held. --- -### step9 — USB MIDI +### step9 — Serial monitor + +**Goal:** use `fmt.Println` and `fmt.Printf` to send human-readable events from the badge to your computer over USB serial. + +The `-monitor` flag tells TinyGo to open the serial port immediately after flashing, so you see the output without extra steps. + +```go +// button press (falling-edge detection) +a := btnA.Get() +b := btnB.Get() +c := btnC.Get() // rotary encoder push button +if !a && prevA { + println("button A pressed") +} +if !b && prevB { + println("button B pressed") +} +if !c && prevC { + println("encoder button pressed") +} + +// rotary encoder — print on every position change +pos := enc.Position() +if pos != prevPos { + println("encoder:", pos) + prevPos = pos +} + +// joystick — print while outside the dead zone +rawX := int(ax.Get()) - 32767 +rawY := int(ay.Get()) - 32767 +if rawX > deadzone || rawX < -deadzone || rawY > deadzone || rawY < -deadzone { + println("joystick:", "x=", rawX, "y=", rawY) +} +``` + +```sh +tinygo flash -target nicenano -monitor ./basics/step9 +``` + +Move the joystick, turn the encoder knob, or press A, B, or the encoder button. Each event prints to your terminal in real time. + +**Key concepts** +- `println` is a TinyGo built-in that writes directly to the USB serial port with no imports needed — prefer it over `fmt.Print*` in embedded code. +- `-monitor` keeps the serial connection open after flashing — equivalent to running `tinygo monitor` right after. +- Falling-edge detection (`!a && prevA`) prints once per press instead of flooding the terminal while the button is held. +- A dead zone (`const deadzone = 5000`) suppresses joystick noise around the center resting position. + +--- + +### step10 — USB MIDI **Goal:** make the badge appear as a MIDI instrument over USB. @@ -426,7 +476,7 @@ midi.Midi.NoteOff(0, midichannel, notes[oldNote], 50) ``` ```sh -tinygo flash -target nicenano ./basics/step9 +tinygo flash -target nicenano ./basics/step10 ``` Open any online MIDI player (e.g. [muted.io/piano](https://muted.io/piano/)) or connect to a DAW. The three buttons play C4, E4, and G4 (a C major triad). @@ -439,7 +489,7 @@ Open any online MIDI player (e.g. [muted.io/piano](https://muted.io/piano/)) or --- -### step10 — USB HID mouse +### step11 — USB HID mouse **Goal:** use the joystick as a mouse pointer and buttons as mouse clicks. @@ -457,7 +507,7 @@ mouseDevice.Move(dx, dy) ``` ```sh -tinygo flash -target nicenano ./basics/step10 +tinygo flash -target nicenano ./basics/step11 ``` Connect the badge to a computer. The joystick moves the mouse cursor; button A is left click, button B is right click. diff --git a/tutorial/basics/step10/main.go b/tutorial/basics/step10/main.go index 50ddeef..64d873b 100644 --- a/tutorial/basics/step10/main.go +++ b/tutorial/basics/step10/main.go @@ -2,49 +2,46 @@ package main import ( "machine" - "machine/usb/hid/mouse" + "machine/usb/adc/midi" "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 + // 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}) - mouseDevice := mouse.Port() + // C major triad: C4, E4, G4 + notes := []midi.Note{midi.C4, midi.E4, midi.G4} + midichannel := uint8(1) + note := -1 + oldNote := -1 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) - + note = -1 if !btnA.Get() { - mouseDevice.Click(mouse.Left) + note = 0 // C4 } if !btnB.Get() { - mouseDevice.Click(mouse.Right) + 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) diff --git a/tutorial/basics/step11/main.go b/tutorial/basics/step11/main.go new file mode 100644 index 0000000..50ddeef --- /dev/null +++ b/tutorial/basics/step11/main.go @@ -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) + } +} diff --git a/tutorial/basics/step6/main.go b/tutorial/basics/step6/main.go index 5bfef78..67eb045 100644 --- a/tutorial/basics/step6/main.go +++ b/tutorial/basics/step6/main.go @@ -46,8 +46,9 @@ func main() { 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) + // -1 to avoid overflow + dotX := int16(uint32(ax.Get()-1) * 240 / 65535) + dotY := int16(uint32(-ay.Get()-1) * 135 / 65535) // erase previous dot tinydraw.FilledCircle(&display, prevX, prevY, 5, black) diff --git a/tutorial/basics/step9/main.go b/tutorial/basics/step9/main.go index 64d873b..5696df9 100644 --- a/tutorial/basics/step9/main.go +++ b/tutorial/basics/step9/main.go @@ -2,48 +2,64 @@ package main import ( "machine" - "machine/usb/adc/midi" "time" + + "tinygo.org/x/drivers/encoders" ) -func main() { +const deadzone = 5000 - // buttons: active LOW (internal pull-up) +func main() { btnA := machine.P1_06 btnB := machine.P1_04 - btnRot := machine.P0_22 // rotary encoder push button + btnC := 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}) + btnC.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) - // C major triad: C4, E4, G4 - notes := []midi.Note{midi.C4, midi.E4, midi.G4} - midichannel := uint8(1) + enc := encoders.NewQuadratureViaInterrupt(machine.P1_00, machine.P0_24) + enc.Configure(encoders.QuadratureConfig{Precision: 4}) + + machine.InitADC() + ax := machine.ADC{Pin: machine.P0_02} + ay := machine.ADC{Pin: machine.P0_29} + ax.Configure(machine.ADCConfig{}) + ay.Configure(machine.ADCConfig{}) + + prevA := true + prevB := true + prevC := true + prevPos := enc.Position() - note := -1 - oldNote := -1 for { - note = -1 - if !btnA.Get() { - note = 0 // C4 + a := btnA.Get() + b := btnB.Get() + c := btnC.Get() + if !a && prevA { + println("button A pressed") } - if !btnB.Get() { - note = 1 // E4 + if !b && prevB { + println("button B pressed") } - if !btnRot.Get() { - note = 2 // G4 + if !c && prevC { + println("encoder button pressed") + } + prevA = a + prevB = b + prevC = c + + pos := enc.Position() + if pos != prevPos { + println("encoder:", pos) + prevPos = pos } - 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 + rawX := int(ax.Get()) - 32767 + rawY := int(ay.Get()) - 32767 + if rawX > deadzone || rawX < -deadzone || rawY > deadzone || rawY < -deadzone { + println("joystick:", "x=", rawX, "y=", rawY) } - time.Sleep(100 * time.Millisecond) + time.Sleep(50 * time.Millisecond) } } diff --git a/tutorial/go.mod b/tutorial/go.mod index f030f82..fd00589 100644 --- a/tutorial/go.mod +++ b/tutorial/go.mod @@ -1,3 +1,23 @@ module code.madriguera.me/GoEducation/nicebadge/tutorial go 1.22.1 + +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af // indirect + github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 // indirect + github.com/tinygo-org/cbgo v0.0.4 // indirect + github.com/tinygo-org/pio v0.3.0 // indirect + golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect + golang.org/x/sys v0.11.0 // indirect + tinygo.org/x/bluetooth v0.14.0 // indirect + tinygo.org/x/drivers v0.34.0 // indirect + tinygo.org/x/tinydraw v0.4.0 // indirect + tinygo.org/x/tinyfont v0.6.0 // indirect +) + +replace tinygo.org/x/drivers => /home/conejo/go/src/tinygo.org/x/drivers diff --git a/tutorial/go.sum b/tutorial/go.sum index e69de29..85219da 100644 --- a/tutorial/go.sum +++ b/tutorial/go.sum @@ -0,0 +1,43 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b h1:du3zG5fd8snsFN6RBoLA7fpaYV9ZQIsyH9snlk2Zvik= +github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af h1:ZfFq94aH/BCSWWKd9RPUgdHOdgGKCnfl2VdvU9UksTA= +github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af/go.mod h1:MUaGO5m6X7xrkHrPDmnaxCEcuCCFN/0ZFh9oie+exbU= +github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 h1:Y9fBuiR/urFY/m76+SAZTxk2xAOS2n85f+H1CugajeA= +github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU= +github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk= +github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI= +github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tinygo.org/x/bluetooth v0.14.0 h1:rrUaT+Fu6O0phGm4Y5UZULL8F7UahOq/JwGAPjJm+V4= +tinygo.org/x/bluetooth v0.14.0/go.mod h1:YnyJRVX09i+wkFeHpXut0b+qHq+T2WwKBRRiF/scANA= +tinygo.org/x/drivers v0.34.0 h1:lw8ePJeUSn9oICKBvQXHC9TIE+J00OfXfkGTrpXM9Iw= +tinygo.org/x/drivers v0.34.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= +tinygo.org/x/tinydraw v0.4.0 h1:U9V0mHz8/jPShKjlh199vCfq1ARFyUOD1b+FfqIwV8c= +tinygo.org/x/tinydraw v0.4.0/go.mod h1:WCV/EMljTv8w04iAxjv+fRD6/4ffx0afATYeJlN90Yo= +tinygo.org/x/tinyfont v0.6.0 h1:GibXDSFz6xrWnEDkDRo6vsbOyRw0MVj/eza3zNHMSHs= +tinygo.org/x/tinyfont v0.6.0/go.mod h1:onflMSkpWl7r7j4MIqhPEVV39pn7yL4N3MOePl3G+G8=