diff --git a/dht/thermometer.go b/dht/thermometer.go index f9405aac6..c19cef11c 100644 --- a/dht/thermometer.go +++ b/dht/thermometer.go @@ -9,9 +9,12 @@ package dht // import "tinygo.org/x/drivers/dht" import ( - "machine" "runtime/interrupt" "time" + + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/internal/legacy" + "tinygo.org/x/drivers/internal/pin" ) // DummyDevice provides a basic interface for DHT devices. @@ -30,7 +33,8 @@ type DummyDevice interface { // Since taking measurements from the sensor is time consuming procedure and blocks interrupts, // user can avoid any hidden calls to the sensor. type device struct { - pin machine.Pin + set pin.OutputFn + get pin.InputFn measurements DeviceType initialized bool @@ -43,8 +47,8 @@ type device struct { // According to documentation pin should be always, but the t *device restores pin to the state before call. func (t *device) ReadMeasurements() error { // initial waiting - state := powerUp(t.pin) - defer t.pin.Set(state) + state := powerUp(t.set, t.get) + defer t.set(state) err := t.read() if err == nil { t.initialized = true @@ -93,14 +97,11 @@ func (t *device) HumidityFloat() (float32, error) { // Perform initialization of the communication protocol. // Device lowers the voltage on pin for startingLow=20ms and starts listening for response // Section 5.2 in [1] -func initiateCommunication(p machine.Pin) { - // Send low signal to the device - p.Configure(machine.PinConfig{Mode: machine.PinOutput}) - p.Low() +func initiateCommunication(set pin.OutputFn) { + set.Low() time.Sleep(startingLow) // Set pin to high and wait for reply - p.High() - p.Configure(machine.PinConfig{Mode: machine.PinInput}) + set.High() } // Measurements returns both measurements: temperature and humidity as they sent by the device. @@ -131,14 +132,14 @@ func (t *device) read() error { signals := signalsData[:] // Start communication protocol with sensor - initiateCommunication(t.pin) + initiateCommunication(t.set) // Wait for sensor's response and abort if sensor does not reply - err := waitForDataTransmission(t.pin) + err := waitForDataTransmission(t.get) if err != nil { return err } // count low and high cycles for sensor's reply - receiveSignals(t.pin, signals) + receiveSignals(t.get, signals) // process received signals and store the result in the buffer. Abort if data transmission was interrupted and not // all 40 bits were received @@ -158,13 +159,13 @@ func (t *device) read() error { // receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables // interrupts -func receiveSignals(pin machine.Pin, result []counter) { +func receiveSignals(get pin.InputFn, result []counter) { i := uint8(0) mask := interrupt.Disable() defer interrupt.Restore(mask) for ; i < 40; i++ { - result[i*2] = expectChange(pin, false) - result[i*2+1] = expectChange(pin, true) + result[i*2] = expectChange(get, false) + result[i*2+1] = expectChange(get, true) } } @@ -189,33 +190,59 @@ func (t *device) extractData(signals []counter, buf []uint8) error { // waitForDataTransmission waits for reply from the sensor. // If no reply received, returns NoSignalError. // For more details, see section 5.2 in [1] -func waitForDataTransmission(p machine.Pin) error { +func waitForDataTransmission(get pin.InputFn) error { // wait for thermometer to pull down - if expectChange(p, true) == timeout { + if expectChange(get, true) == timeout { return NoSignalError } //wait for thermometer to pull up - if expectChange(p, false) == timeout { + if expectChange(get, false) == timeout { return NoSignalError } // wait for thermometer to pull down and start sending the data - if expectChange(p, true) == timeout { + if expectChange(get, true) == timeout { return NoSignalError } return nil } -// Constructor function for a DummyDevice implementation. -// This device provides full control to the user. -// It does not do any hidden measurements calls and does not check -// for 2 seconds delay between measurements. -func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice { - pin.High() +func newDevice(pin drivers.Pin, deviceType DeviceType) *device { + pin.Set(true) + // Pins are configured to maintain backward compatibility, + // When writing new drivers we assume that pins are configured in user code + // so the device initialization could be simplified like this: + // return &device{ + // set: pin.Set, + // get: pin.Get, + // ... + // } + isOutput := true return &device{ - pin: pin, + set: func(level bool) { + if !isOutput { + legacy.ConfigurePinOut(pin) + isOutput = true + } + pin.Set(level) + }, + get: func() bool { + if isOutput { + legacy.ConfigurePinInput(pin) + isOutput = false + } + return pin.Get() + }, measurements: deviceType, initialized: false, temperature: 0, humidity: 0, } } + +// Constructor function for a DummyDevice implementation. +// This device provides full control to the user. +// It does not do any hidden measurements calls and does not check +// for 2 seconds delay between measurements. +func NewDummyDevice(pin drivers.Pin, deviceType DeviceType) DummyDevice { + return newDevice(pin, deviceType) +} diff --git a/dht/timesafethermometer.go b/dht/timesafethermometer.go index 87fab50c7..a1a406204 100644 --- a/dht/timesafethermometer.go +++ b/dht/timesafethermometer.go @@ -9,8 +9,9 @@ package dht // import "tinygo.org/x/drivers/dht" import ( - "machine" "time" + + "tinygo.org/x/drivers" ) // Device interface provides main functionality of the DHTXX sensors. @@ -124,14 +125,10 @@ func (m *managedDevice) Configure(policy UpdatePolicy) { // Constructor of the Device implementation. // This implementation updates data every 2 seconds during data access. -func New(pin machine.Pin, deviceType DeviceType) Device { - pin.High() +func New(pin drivers.Pin, deviceType DeviceType) Device { + pin.Set(true) return &managedDevice{ - t: device{ - pin: pin, - measurements: deviceType, - initialized: false, - }, + t: *newDevice(pin, deviceType), lastUpdate: time.Time{}, policy: UpdatePolicy{ UpdateTime: time.Second * 2, @@ -141,14 +138,10 @@ func New(pin machine.Pin, deviceType DeviceType) Device { } // Constructor of the Device implementation with given UpdatePolicy -func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device { - pin.High() +func NewWithPolicy(pin drivers.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device { + pin.Set(true) result := &managedDevice{ - t: device{ - pin: pin, - measurements: deviceType, - initialized: false, - }, + t: *newDevice(pin, deviceType), lastUpdate: time.Time{}, } result.Configure(updatePolicy) diff --git a/dht/util.go b/dht/util.go index d2b1cc70b..5f881e9e4 100644 --- a/dht/util.go +++ b/dht/util.go @@ -3,23 +3,24 @@ package dht // import "tinygo.org/x/drivers/dht" import ( - "machine" "time" + + "tinygo.org/x/drivers/internal/pin" ) // Check if the pin is disabled -func powerUp(p machine.Pin) bool { - state := p.Get() +func powerUp(set pin.OutputFn, get pin.InputFn) bool { + state := get() if !state { - p.High() + set.High() time.Sleep(startTimeout) } return state } -func expectChange(p machine.Pin, oldState bool) counter { +func expectChange(get pin.InputFn, oldState bool) counter { cnt := counter(0) - for ; p.Get() == oldState && cnt != timeout; cnt++ { + for ; get() == oldState && cnt != timeout; cnt++ { } return cnt } diff --git a/examples/onewire/main.go b/examples/onewire/main.go index 6322b84ac..1b78d9373 100644 --- a/examples/onewire/main.go +++ b/examples/onewire/main.go @@ -2,7 +2,9 @@ package main import ( "encoding/hex" + "machine" "time" + "tinygo.org/x/drivers/onewire" ) @@ -18,7 +20,7 @@ func main() { println() println("Device:", machine.Device) - romIDs, err := ow.Search(onewire.SEARCH) + romIDs, err := ow.Search(onewire.SEARCH_ROM) if err != nil { println(err) } diff --git a/examples/onewire_v2/main.go b/examples/onewire_v2/main.go new file mode 100644 index 000000000..47cbf4b17 --- /dev/null +++ b/examples/onewire_v2/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "encoding/hex" + "machine" + "time" + + onewire "tinygo.org/x/drivers/onewire_v2" +) + +type onewirePin struct { + p machine.Pin + isOutput bool +} + +func (owp *onewirePin) Set(level bool) { + if level && owp.isOutput { + owp.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + owp.isOutput = false + } + if !level && !owp.isOutput { + owp.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + owp.isOutput = true + } +} + +func (owp *onewirePin) Get() bool { + if owp.isOutput { + owp.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + owp.isOutput = false + } + return owp.p.Get() +} + +func main() { + pin := &onewirePin{p: machine.D2, isOutput: false} + ow := onewire.New(pin) + + for { + time.Sleep(3 * time.Second) + + println() + println("Device:", machine.Device) + + romIDs, err := ow.Search(onewire.SEARCH_ROM) + if err != nil { + println(err) + } + for _, romid := range romIDs { + println(hex.EncodeToString(romid)) + } + + if len(romIDs) == 1 { + // only 1 device on bus + r, err := ow.ReadAddress() + if err != nil { + println(err) + } + println(hex.EncodeToString(r)) + + } + } +} diff --git a/internal/legacy/pinconfig.go b/internal/legacy/pinconfig.go new file mode 100644 index 000000000..7aeab7b75 --- /dev/null +++ b/internal/legacy/pinconfig.go @@ -0,0 +1,62 @@ +package legacy + +import ( + "errors" + + "tinygo.org/x/drivers" +) + +// 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 drivers.OutputPin) { + 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 drivers.InputPin) { + 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 drivers.InputPin) { + 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 drivers.InputPin) { + 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 drivers.Pin) 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..b998453eb --- /dev/null +++ b/internal/legacy/pinconfig_go.go @@ -0,0 +1,15 @@ +//go:build !tinygo + +package legacy + +import "tinygo.org/x/drivers" + +// This file compiles for non-tinygo builds +// for use with "big" or "upstream" Go where +// there is no machine package. + +func configurePinOut(p drivers.OutputPin) {} +func configurePinInput(p drivers.InputPin) {} +func configurePinInputPulldown(p drivers.InputPin) {} +func configurePinInputPullup(p drivers.InputPin) {} +func pinIsNoPin(a drivers.Pin) 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..28b1b5fe5 --- /dev/null +++ b/internal/legacy/pinconfig_tinygo.go @@ -0,0 +1,37 @@ +//go:build baremetal + +package legacy + +import ( + "machine" + + "tinygo.org/x/drivers" +) + +func configurePinOut(po drivers.OutputPin) { + configurePin(po, machine.PinOutput) +} + +func configurePinInputPulldown(pi drivers.InputPin) { + configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput. +} + +func configurePinInput(pi drivers.InputPin) { + configurePin(pi, machine.PinInput) +} + +func configurePinInputPullup(pi drivers.InputPin) { + configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput. +} + +func pinIsNoPin(a drivers.Pin) 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..833bd2fd2 --- /dev/null +++ b/internal/pin/internalpin.go @@ -0,0 +1,17 @@ +package pin + +// OutputFn is a function that sets the underlying pin's level. +type OutputFn func(level bool) + +// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true). +func (setPin OutputFn) High() { + setPin(true) +} + +// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false). +func (setPin OutputFn) Low() { + setPin(false) +} + +// InputFn is a function that reads the underlying pin's level. +type InputFn func() (level bool) diff --git a/onewire/onewire.go b/onewire/onewire.go index ee1828387..8c9e409a6 100644 --- a/onewire/onewire.go +++ b/onewire/onewire.go @@ -5,8 +5,11 @@ package onewire // import "tinygo.org/x/drivers/onewire" import ( "errors" - "machine" "time" + + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/internal/legacy" + "tinygo.org/x/drivers/internal/pin" ) // OneWire ROM commands @@ -19,7 +22,8 @@ const ( // Device wraps a connection to an 1-Wire devices. type Device struct { - p machine.Pin + set pin.OutputFn + get pin.InputFn } // Config wraps a configuration to an 1-Wire devices. @@ -34,9 +38,34 @@ var ( // New creates a new GPIO 1-Wire connection. // The pin must be pulled up to the VCC via a resistor greater than 500 ohms (default 4.7k). -func New(p machine.Pin) Device { +func New(p drivers.Pin) Device { + legacy.ConfigurePinInputPullup(p) + isOutput := false + // Pins are configured to maintain backward compatibility, + // When writing new drivers we assume that pins are configured in user code + // so the device initialization could be simplified like this: + // return &device{ + // set: p.Set, + // get: p.Get, + // } return Device{ - p: p, + set: func(level bool) { + if level && isOutput { + legacy.ConfigurePinInputPullup(p) + isOutput = false + } + if !level && !isOutput { + legacy.ConfigurePinOut(p) + isOutput = true + } + }, + get: func() bool { + if isOutput { + legacy.ConfigurePinInputPullup(p) + isOutput = false + } + return p.Get() + }, } } @@ -45,11 +74,11 @@ func (d *Device) Configure(config Config) {} // Reset pull DQ line low, then up. func (d Device) Reset() error { - d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + d.set.Low() time.Sleep(480 * time.Microsecond) - d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + d.set.High() time.Sleep(70 * time.Microsecond) - precence := d.p.Get() + precence := d.get() time.Sleep(410 * time.Microsecond) if precence { return errNoPresence @@ -59,14 +88,14 @@ func (d Device) Reset() error { // WriteBit transmits a bit to 1-Wire bus. func (d Device) WriteBit(data uint8) { - d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + d.set.Low() if data&1 == 1 { // Send '1' time.Sleep(5 * time.Microsecond) - d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + d.set.High() time.Sleep(60 * time.Microsecond) } else { // Send '0' time.Sleep(60 * time.Microsecond) - d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + d.set.Low() time.Sleep(5 * time.Microsecond) } } @@ -81,11 +110,11 @@ func (d Device) Write(data uint8) { // ReadBit receives a bit from 1-Wire bus. func (d Device) ReadBit() (data uint8) { - d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + d.set.Low() time.Sleep(3 * time.Microsecond) - d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + d.set.High() time.Sleep(8 * time.Microsecond) - if d.p.Get() { + if d.get() { data = 1 } time.Sleep(60 * time.Microsecond) diff --git a/onewire_v2/onewire_v2.go b/onewire_v2/onewire_v2.go new file mode 100644 index 000000000..df946a7e3 --- /dev/null +++ b/onewire_v2/onewire_v2.go @@ -0,0 +1,225 @@ +// Package wire implements the Dallas Semiconductor Corp.'s 1-wire bus system. +// +// Wikipedia: https://en.wikipedia.org/wiki/1-Wire +package onewire_v2 // import "tinygo.org/x/drivers/onewire_v2" + +import ( + "errors" + "time" + + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/internal/pin" +) + +// OneWire ROM commands +const ( + READ_ROM uint8 = 0x33 + MATCH_ROM uint8 = 0x55 + SKIP_ROM uint8 = 0xCC + SEARCH_ROM uint8 = 0xF0 +) + +// Device wraps a connection to an 1-Wire devices. +type Device struct { + set pin.OutputFn + get pin.InputFn +} + +// Config wraps a configuration to an 1-Wire devices. +type Config struct{} + +// Errors list +var ( + errNoPresence = errors.New("Error: OneWire. No devices on the bus.") + errTooManyDevices = errors.New("Error: OneWire. Too many devices on the bus.") + errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.") +) + +// New creates a new GPIO 1-Wire connection. +// The pin must be pulled up to the VCC via a resistor greater than 500 ohms (default 4.7k). +func New(p drivers.Pin) Device { + return Device{ + set: p.Set, + get: p.Get, + } +} + +// Configure initializes the protocol. +func (d *Device) Configure(config Config) {} + +// Reset pull DQ line low, then up. +func (d Device) Reset() error { + d.set.Low() + time.Sleep(480 * time.Microsecond) + d.set.High() + time.Sleep(70 * time.Microsecond) + precence := d.get() + time.Sleep(410 * time.Microsecond) + if precence { + return errNoPresence + } + return nil +} + +// WriteBit transmits a bit to 1-Wire bus. +func (d Device) WriteBit(data uint8) { + d.set.Low() + if data&1 == 1 { // Send '1' + time.Sleep(5 * time.Microsecond) + d.set.High() + time.Sleep(60 * time.Microsecond) + } else { // Send '0' + time.Sleep(60 * time.Microsecond) + d.set.Low() + time.Sleep(5 * time.Microsecond) + } +} + +// Write transmits a byte as bit array to 1-Wire bus. (LSB first) +func (d Device) Write(data uint8) { + for i := 0; i < 8; i++ { + d.WriteBit(data) + data >>= 1 + } +} + +// ReadBit receives a bit from 1-Wire bus. +func (d Device) ReadBit() (data uint8) { + d.set.Low() + time.Sleep(3 * time.Microsecond) + d.set.High() + time.Sleep(8 * time.Microsecond) + if d.get() { + data = 1 + } + time.Sleep(60 * time.Microsecond) + return data +} + +// Read receives a byte from 1-Wire bus. (LSB first) +func (d Device) Read() (data uint8) { + for i := 0; i < 8; i++ { + data >>= 1 + data |= d.ReadBit() << 7 + } + return data +} + +// ReadAddress receives a 64-bit unique ROM ID from Device. (LSB first) +// Note: use this if there is only one slave device on the bus. +func (d Device) ReadAddress() ([]uint8, error) { + var romid = make([]uint8, 8) + if err := d.Reset(); err != nil { + return nil, err + } + d.Write(READ_ROM) + for i := 0; i < 8; i++ { + romid[i] = d.Read() + } + if d.Сrc8(romid) != 0 { + return nil, errReadAddress + } + return romid, nil +} + +// Select selects the address of the device for communication +func (d Device) Select(romid []uint8) error { + if err := d.Reset(); err != nil { + return err + } + if len(romid) == 0 { + d.Write(SKIP_ROM) + return nil + } + d.Write(MATCH_ROM) + for i := 0; i < 8; i++ { + d.Write(romid[i]) + } + return nil +} + +// Search searches for all devices on the bus. +// Note: max 32 slave devices per bus +func (d Device) Search(cmd uint8) ([][]uint8, error) { + var ( + bit, bit_c uint8 = 0, 0 + bitOffset uint8 = 0 + lastZero uint8 = 0 + lastFork uint8 = 0 + lastAddress = make([]uint8, 8) + romIDs = make([][]uint8, 32) // + romIndex uint8 = 0 + ) + + for i := range romIDs { + romIDs[i] = make([]uint8, 8) + } + + for ok := true; ok; ok = (lastFork != 0) { + if err := d.Reset(); err != nil { + return nil, err + } + + // send search command to bus + d.Write(cmd) + + lastZero = 0 + + for bitOffset = 0; bitOffset < 64; bitOffset++ { + bit = d.ReadBit() // read first address bit + bit_c = d.ReadBit() // read second (complementary) address bit + + if bit == 1 && bit_c == 1 { // no device + return nil, errNoPresence + } + + if bit == 0 && bit_c == 0 { // collision + if bitOffset == lastFork { + bit = 1 + } + if bitOffset < lastFork { + bit = (lastAddress[bitOffset>>3] >> (bitOffset & 0x07)) & 1 + } + if bit == 0 { + lastZero = bitOffset + } + } + + if bit == 0 { + lastAddress[bitOffset>>3] &= ^(1 << (bitOffset & 0x07)) + } else { + lastAddress[bitOffset>>3] |= (1 << (bitOffset & 0x07)) + } + d.WriteBit(bit) + } + if d.Сrc8(lastAddress) != 0 { + continue + } + + lastFork = lastZero + copy(romIDs[romIndex], lastAddress) + romIndex++ + if romIndex >= 32 { + return romIDs, errTooManyDevices + } + } + return romIDs[:romIndex:romIndex], nil +} + +// Crc8 compute a Dallas Semiconductor 8 bit CRC. +func (_ Device) Сrc8(buffer []uint8) (crc uint8) { + // Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 + // Tiny 2x16 entry CRC table created by Arjen Lentz + // See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table + crc8_table := [...]uint8{ + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, + 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, + 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, + 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74, + } + for i := 0; i < len(buffer); i++ { + crc = buffer[i] ^ crc // just re-using crc as intermediate + crc = crc8_table[crc&0x0f] ^ crc8_table[16+((crc>>4)&0x0f)] + } + return crc +} diff --git a/pin.go b/pin.go new file mode 100644 index 000000000..949d416c3 --- /dev/null +++ b/pin.go @@ -0,0 +1,17 @@ +package drivers + +// OutputPin represents a pin hardware abstraction layer for a pin that can output a digital signal. +type OutputPin interface { + Set(level bool) +} + +// InputPin represents a pin hardware abstraction layer for a pin that can read a digital input signal. +type InputPin interface { + Get() (level bool) +} + +// Pin represents a pin hardware abstraction layer that can both input and output digital signals. +type Pin interface { + OutputPin + InputPin +} diff --git a/uc8151/uc8151.go b/uc8151/uc8151.go index 38c48f9a0..b466980ae 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 pin.OutputFn + dc pin.OutputFn + rst pin.OutputFn + isBusy pin.InputFn 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 drivers.OutputPin, busyPin drivers.InputPin) 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)