From 457e1e5dc25f33c537eccc8bfc518009807b6067 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Mon, 29 Sep 2025 19:16:15 -0300 Subject: [PATCH] first commit: add HAL and uc8151 driver demo --- internal/legacy/pinconfig.go | 62 ++++++++++++++++++++++++++++ internal/legacy/pinconfig_go.go | 15 +++++++ internal/legacy/pinconfig_nopulls.go | 10 +++++ internal/legacy/pinconfig_pulls.go | 13 ++++++ internal/legacy/pinconfig_tinygo.go | 37 +++++++++++++++++ internal/pin/internalpin.go | 28 +++++++++++++ pin.go | 60 +++++++++++++++++++++++++++ uc8151/uc8151.go | 40 ++++++++++-------- 8 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 internal/legacy/pinconfig.go create mode 100644 internal/legacy/pinconfig_go.go create mode 100644 internal/legacy/pinconfig_nopulls.go create mode 100644 internal/legacy/pinconfig_pulls.go create mode 100644 internal/legacy/pinconfig_tinygo.go create mode 100644 internal/pin/internalpin.go create mode 100644 pin.go diff --git a/internal/legacy/pinconfig.go b/internal/legacy/pinconfig.go new file mode 100644 index 000000000..d9d805ffe --- /dev/null +++ b/internal/legacy/pinconfig.go @@ -0,0 +1,62 @@ +package legacy + +import ( + "errors" + + "tinygo.org/x/drivers/internal/pin" +) + +// The pingconfig group of files serve to abstract away +// pin configuration calls on the machine.Pin type. +// It was observed this way of developing drivers was +// non-portable and unusable on "big" Go projects so +// future projects should NOT configure pins in driver code. +// Users must configure pins before passing them as arguments +// to drivers. + +// ConfigurePinOut is a legacy function used to configure pins as outputs. +// +// Deprecated: Do not configure pins in drivers. +// This is a legacy feature and should only be used by drivers that +// previously configured pins in initialization to avoid breaking users. +func ConfigurePinOut(po pin.Output) { + configurePinOut(po) +} + +// ConfigurePinInput is a legacy function used to configure pins as inputs. +// +// Deprecated: Do not configure pins in drivers. +// This is a legacy feature and should only be used by drivers that +// previously configured pins in initialization to avoid breaking users. +func ConfigurePinInputPulldown(pi pin.Input) { + configurePinInputPulldown(pi) +} + +// ConfigurePinInput is a legacy function used to configure pins as inputs. +// +// Deprecated: Do not configure pins in drivers. +// This is a legacy feature and should only be used by drivers that +// previously configured pins in initialization to avoid breaking users. +func ConfigurePinInput(pi pin.Input) { + configurePinInput(pi) +} + +// ConfigurePinInput is a legacy function used to configure pins as inputs. +// +// Deprecated: Do not configure pins in drivers. +// This is a legacy feature and should only be used by drivers that +// previously configured pins in initialization to avoid breaking users. +func ConfigurePinInputPullup(pi pin.Input) { + configurePinInputPullup(pi) +} + +// PinIsNoPin returns true if the argument is a machine.Pin type and is the machine.NoPin predeclared type. +// +// Deprecated: Drivers do not require pin knowledge from now on. +func PinIsNoPin(pin any) bool { + return pinIsNoPin(pin) +} + +var ( + ErrConfigBeforeInstantiated = errors.New("device must be instantiated with New before calling Configure method") +) diff --git a/internal/legacy/pinconfig_go.go b/internal/legacy/pinconfig_go.go new file mode 100644 index 000000000..3a7096ba7 --- /dev/null +++ b/internal/legacy/pinconfig_go.go @@ -0,0 +1,15 @@ +//go:build !tinygo + +package legacy + +import "tinygo.org/x/drivers/internal/pin" + +// This file compiles for non-tinygo builds +// for use with "big" or "upstream" Go where +// there is no machine package. + +func configurePinOut(p pin.Output) {} +func configurePinInput(p pin.Input) {} +func configurePinInputPulldown(p pin.Input) {} +func configurePinInputPullup(p pin.Input) {} +func pinIsNoPin(a any) bool { return false } diff --git a/internal/legacy/pinconfig_nopulls.go b/internal/legacy/pinconfig_nopulls.go new file mode 100644 index 000000000..1fc61389d --- /dev/null +++ b/internal/legacy/pinconfig_nopulls.go @@ -0,0 +1,10 @@ +//go:build baremetal && fe310 + +package legacy + +import "machine" + +const ( + pulldown = machine.PinInput + pullup = machine.PinInput +) diff --git a/internal/legacy/pinconfig_pulls.go b/internal/legacy/pinconfig_pulls.go new file mode 100644 index 000000000..d7e88c658 --- /dev/null +++ b/internal/legacy/pinconfig_pulls.go @@ -0,0 +1,13 @@ +//go:build baremetal && !fe310 + +package legacy + +import "machine" + +// If you are getting a build error here you then we missed adding +// your CPU build tag to the list of CPUs that do not have pulldown/pullups. +// Add it above and in pinhal_nopulls! You should also add a smoketest for it :) +const ( + pulldown = machine.PinInputPulldown + pullup = machine.PinInputPullup +) diff --git a/internal/legacy/pinconfig_tinygo.go b/internal/legacy/pinconfig_tinygo.go new file mode 100644 index 000000000..d0068b2c8 --- /dev/null +++ b/internal/legacy/pinconfig_tinygo.go @@ -0,0 +1,37 @@ +//go:build baremetal + +package legacy + +import ( + "machine" + + "tinygo.org/x/drivers/internal/pin" +) + +func configurePinOut(po pin.Output) { + configurePin(po, machine.PinOutput) +} + +func configurePinInputPulldown(pi pin.Input) { + configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput. +} + +func configurePinInput(pi pin.Input) { + configurePin(pi, machine.PinInput) +} + +func configurePinInputPullup(pi pin.Input) { + configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput. +} + +func pinIsNoPin(a any) bool { + p, ok := a.(machine.Pin) + return ok && p == machine.NoPin +} + +func configurePin(p any, mode machine.PinMode) { + machinePin, ok := p.(machine.Pin) + if ok { + machinePin.Configure(machine.PinConfig{Mode: mode}) + } +} diff --git a/internal/pin/internalpin.go b/internal/pin/internalpin.go new file mode 100644 index 000000000..083d3a294 --- /dev/null +++ b/internal/pin/internalpin.go @@ -0,0 +1,28 @@ +package pin + +import "tinygo.org/x/drivers" + +// This file contains interface-style Pin HAL definition. +// It serves to eliminate machine.Pin from driver constructors +// so that drivers can be used in "big" Go projects where +// there is no machine package. + +// Here to aid relevant documentation links of [drivers.PinOutput] and [drivers.PinInput]. +var _ drivers.PinOutput + +// Output represents a pin hardware abstraction layer for a pin that can output a digital signal. +// +// This is an alternative to [drivers.PinOutput] abstraction which is a function type and has +// not been standardized as of yet as a standard HAL in the drivers package, +// [discussion ongoing here]. +// +// [discussion ongoing here]: https://github.com/orgs/tinygo-org/discussions/5043 +type Output interface { + Set(level bool) +} + +// Input represents a pin hardware abstraction layer. +// See [Output] for more information on why this type exists separate to drivers. +type Input interface { + Get() (level bool) +} diff --git a/pin.go b/pin.go new file mode 100644 index 000000000..0ef99ed28 --- /dev/null +++ b/pin.go @@ -0,0 +1,60 @@ +package drivers + +// TinyGo Pin HAL (exported) +// +// Pins are represented as function types instead of interfaces because they are: +// - Faster on MCUs +// - Conceptually simpler than an interface +// - Very likely easier to teach than interface: +// - Cleaner at call sites (e.g., isBusy := d.isBusy() vs d.isBusy.Get()). +// - Enable inline funcs at the usage site- less boilerplate than defining +// an interface type + method (see example below). +// - Less prone to “method creep”: the API surface stays small while +// advanced behavior can be composed in user code. +// Example: a pin that can act as both output and input without adding methods. +// +// var pinIsOutput bool +// var po PinOutput = func(b bool) { +// if !pinIsOutput { +// pin.Configure(outputMode) +// pinIsOutput = true +// } +// pin.Set(b) +// } +// var pi PinInput = func() bool { +// if pinIsOutput { +// pin.Configure(inputMode) +// pinIsOutput = false +// } +// return pin.Get() +// } +// +// See https://github.com/tinygo-org/drivers/pull/753 for detailed commentary. + +// PinOutput is hardware abstraction for a pin which outputs a +// digital signal (high or low level). +// +// // Code conversion demo: from machine.Pin to drivers.PinOutput +// led := machine.LED +// led.Configure(machine.PinConfig{Mode: machine.PinOutput}) +// var pin drivers.PinOutput = led.Set // Going from a machine.Pin to a drivers.PinOutput +type PinOutput func(level bool) + +// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true). +func (setPin PinOutput) High() { + setPin(true) +} + +// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false). +func (setPin PinOutput) Low() { + setPin(false) +} + +// PinInput is hardware abstraction for a pin which receives a +// digital signal and reads it (high or low level). +// +// // Code conversion demo: from machine.Pin to drivers.PinInput +// input := machine.LED +// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.PinInput +// var pin drivers.PinInput = input.Get // Going from a machine.Pin to a drivers.PinInput +type PinInput func() (level bool) diff --git a/uc8151/uc8151.go b/uc8151/uc8151.go index 38c48f9a0..7652cfda5 100644 --- a/uc8151/uc8151.go +++ b/uc8151/uc8151.go @@ -8,10 +8,11 @@ package uc8151 // import "tinygo.org/x/drivers/uc8151" import ( "errors" "image/color" - "machine" "time" "tinygo.org/x/drivers" + "tinygo.org/x/drivers/internal/legacy" + "tinygo.org/x/drivers/internal/pin" "tinygo.org/x/drivers/pixel" ) @@ -31,10 +32,10 @@ type Config struct { type Device struct { bus drivers.SPI - cs machine.Pin - dc machine.Pin - rst machine.Pin - busy machine.Pin + cs drivers.PinOutput + dc drivers.PinOutput + rst drivers.PinOutput + isBusy drivers.PinInput width int16 height int16 buffer []uint8 @@ -49,17 +50,22 @@ type Device struct { type Speed uint8 // New returns a new uc8151 driver. Pass in a fully configured SPI bus. -func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device { - csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) - dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) - rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) - busyPin.Configure(machine.PinConfig{Mode: machine.PinInput}) +// Pins passed in must be configured beforehand. +func New(bus drivers.SPI, csPin, dcPin, rstPin pin.Output, busyPin pin.Input) Device { + // For backwards compatibility. + // This driver used to configure pins, + // so leave in to not break users. + // May be removed in future so try not to depend on it! + legacy.ConfigurePinOut(csPin) + legacy.ConfigurePinOut(dcPin) + legacy.ConfigurePinOut(rstPin) + legacy.ConfigurePinInput(busyPin) return Device{ - bus: bus, - cs: csPin, - dc: dcPin, - rst: rstPin, - busy: busyPin, + bus: bus, + cs: csPin.Set, + dc: dcPin.Set, + rst: rstPin.Set, + isBusy: busyPin.Get, } } @@ -313,14 +319,14 @@ func (d *Device) ClearDisplay() { // WaitUntilIdle waits until the display is ready func (d *Device) WaitUntilIdle() { - for !d.busy.Get() { + for !d.isBusy() { time.Sleep(10 * time.Millisecond) } } // IsBusy returns the busy status of the display func (d *Device) IsBusy() bool { - return d.busy.Get() + return d.isBusy() } // ClearBuffer sets the buffer to 0xFF (white)