From 5b5083e8fbd9e74e4c7640b4ad9321c01cac8b61 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Mon, 26 Mar 2018 11:11:56 -0400 Subject: [PATCH] Move code around to make drivers follow a common pattern The idea is to enable navigating across the code more easily by using similar layout amongs the drivers. Similar to gofmt, it's not about making a stylistic choice, but having a single style. Use the following layout for drivers: - exported support types - Opts struct - New func - Dev struct and methods - Private support code No functional change. A lot of code moved around, so it will likely break any pending PR or fork. :/ --- devices/apa102/apa102.go | 230 ++++++++++++------------ devices/bmxx80/bmxx80.go | 186 +++++++++---------- devices/ds18b20/ds18b20.go | 42 +++-- devices/ds248x/dev.go | 202 --------------------- devices/ds248x/ds248x.go | 207 ++++++++++++++++++++- devices/lepton/cci/cci.go | 30 ++-- devices/lepton/lepton.go | 34 ++-- devices/lirc/lirc.go | 20 +-- devices/ssd1306/ssd1306.go | 190 ++++++++++---------- devices/tm1637/tm1637.go | 26 +-- experimental/devices/bitbang/i2c.go | 66 +++---- experimental/devices/bitbang/spi.go | 70 ++++---- experimental/devices/cap1188/cap1188.go | 58 +++--- experimental/devices/mfrc522/mfrc522.go | 80 +++++---- experimental/devices/nrzled/nrzled.go | 52 +++--- 15 files changed, 740 insertions(+), 753 deletions(-) delete mode 100644 devices/ds248x/dev.go diff --git a/devices/apa102/apa102.go b/devices/apa102/apa102.go index 0f9aa35..f1167f2 100644 --- a/devices/apa102/apa102.go +++ b/devices/apa102/apa102.go @@ -15,6 +15,121 @@ import ( "periph.io/x/periph/devices" ) +// ToRGB converts a slice of color.NRGBA to a byte stream of RGB pixels. +// +// Ignores alpha. +func ToRGB(p []color.NRGBA) []byte { + b := make([]byte, 0, len(p)*3) + for _, c := range p { + b = append(b, c.R, c.G, c.B) + } + return b +} + +// New returns a strip that communicates over SPI to APA102 LEDs. +// +// The SPI port speed should be high, at least in the Mhz range, as +// there's 32 bits sent per LED, creating a staggered effect. See +// https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ +// +// Temperature is in °Kelvin and a reasonable default value is 6500°K. +// +// As per APA102-C spec, the chip's max refresh rate is 400hz. +// https://en.wikipedia.org/wiki/Flicker_fusion_threshold is a recommended +// reading. +func New(p spi.Port, numPixels int, intensity uint8, temperature uint16) (*Dev, error) { + c, err := p.Connect(20000000, spi.Mode3, 8) + if err != nil { + return nil, err + } + // End frames are needed to be able to push enough SPI clock signals due to + // internal half-delay of data signal from each individual LED. See + // https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ + buf := make([]byte, 4*(numPixels+1)+numPixels/2/8+1) + tail := buf[4+4*numPixels:] + for i := range tail { + tail[i] = 0xFF + } + return &Dev{ + Intensity: intensity, + Temperature: temperature, + s: c, + numPixels: numPixels, + rawBuf: buf, + pixels: buf[4 : 4+4*numPixels], + }, nil +} + +// Dev represents a strip of APA-102 LEDs as a strip connected over a SPI port. +// It accepts a stream of raw RGB pixels and converts it to the full dynamic +// range as supported by APA102 protocol (nearly 8000:1 contrast ratio). +// +// Includes intensity and temperature correction. +type Dev struct { + Intensity uint8 // Set an intensity between 0 (off) and 255 (full brightness). + Temperature uint16 // In Kelvin. + s spi.Conn // + l lut // Updated at each .Write() call. + numPixels int // + rawBuf []byte // Raw buffer sent over SPI. Cached to reduce heap fragmentation. + pixels []byte // Double buffer of pixels, to enable partial painting via Draw(). Effectively points inside rawBuf. +} + +func (d *Dev) String() string { + return fmt.Sprintf("APA102{I:%d, T:%dK, %dLEDs, %s}", d.Intensity, d.Temperature, d.numPixels, d.s) +} + +// ColorModel implements devices.Display. There's no surprise, it is +// color.NRGBAModel. +func (d *Dev) ColorModel() color.Model { + return color.NRGBAModel +} + +// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. +func (d *Dev) Bounds() image.Rectangle { + return image.Rectangle{Max: image.Point{X: d.numPixels, Y: 1}} +} + +// Draw implements devices.Display. +// +// Using something else than image.NRGBA is 10x slower. When using image.NRGBA, +// the alpha channel is ignored. +func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { + r = r.Intersect(d.Bounds()) + srcR := src.Bounds() + srcR.Min = srcR.Min.Add(sp) + if dX := r.Dx(); dX < srcR.Dx() { + srcR.Max.X = srcR.Min.X + dX + } + if dY := r.Dy(); dY < srcR.Dy() { + srcR.Max.Y = srcR.Min.Y + dY + } + d.l.init(d.Intensity, d.Temperature) + d.l.rasterImg(d.pixels, r, src, srcR) + _ = d.s.Tx(d.rawBuf, nil) +} + +// Write accepts a stream of raw RGB pixels and sends it as APA102 encoded +// stream. +func (d *Dev) Write(pixels []byte) (int, error) { + if len(pixels)%3 != 0 || len(pixels) > len(d.pixels) { + return 0, errors.New("apa102: invalid RGB stream length") + } + d.l.init(d.Intensity, d.Temperature) + // Do not touch header and footer. + d.l.raster(d.pixels, pixels) + err := d.s.Tx(d.rawBuf, nil) + return len(pixels), err +} + +// Halt turns off all the lights. +func (d *Dev) Halt() error { + _, err := d.Write(make([]byte, d.numPixels*3)) + return err +} + +// + // maxOut is the maximum intensity of each channel on a APA102 LED. const maxOut = 0x1EE1 @@ -200,121 +315,6 @@ func (l *lut) rasterImg(dst []byte, r image.Rectangle, src image.Image, srcR ima } } -// ToRGB converts a slice of color.NRGBA to a byte stream of RGB pixels. -// -// Ignores alpha. -func ToRGB(p []color.NRGBA) []byte { - b := make([]byte, 0, len(p)*3) - for _, c := range p { - b = append(b, c.R, c.G, c.B) - } - return b -} - -// Dev represents a strip of APA-102 LEDs as a strip connected over a SPI port. -// It accepts a stream of raw RGB pixels and converts it to the full dynamic -// range as supported by APA102 protocol (nearly 8000:1 contrast ratio). -// -// Includes intensity and temperature correction. -type Dev struct { - Intensity uint8 // Set an intensity between 0 (off) and 255 (full brightness). - Temperature uint16 // In Kelvin. - s spi.Conn // - l lut // Updated at each .Write() call. - numPixels int // - rawBuf []byte // Raw buffer sent over SPI. Cached to reduce heap fragmentation. - pixels []byte // Double buffer of pixels, to enable partial painting via Draw(). Effectively points inside rawBuf. -} - -func (d *Dev) String() string { - return fmt.Sprintf("APA102{I:%d, T:%dK, %dLEDs, %s}", d.Intensity, d.Temperature, d.numPixels, d.s) -} - -// ColorModel implements devices.Display. There's no surprise, it is -// color.NRGBAModel. -func (d *Dev) ColorModel() color.Model { - return color.NRGBAModel -} - -// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. -func (d *Dev) Bounds() image.Rectangle { - return image.Rectangle{Max: image.Point{X: d.numPixels, Y: 1}} -} - -// Draw implements devices.Display. -// -// Using something else than image.NRGBA is 10x slower. When using image.NRGBA, -// the alpha channel is ignored. -func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { - r = r.Intersect(d.Bounds()) - srcR := src.Bounds() - srcR.Min = srcR.Min.Add(sp) - if dX := r.Dx(); dX < srcR.Dx() { - srcR.Max.X = srcR.Min.X + dX - } - if dY := r.Dy(); dY < srcR.Dy() { - srcR.Max.Y = srcR.Min.Y + dY - } - d.l.init(d.Intensity, d.Temperature) - d.l.rasterImg(d.pixels, r, src, srcR) - _ = d.s.Tx(d.rawBuf, nil) -} - -// Write accepts a stream of raw RGB pixels and sends it as APA102 encoded -// stream. -func (d *Dev) Write(pixels []byte) (int, error) { - if len(pixels)%3 != 0 || len(pixels) > len(d.pixels) { - return 0, errors.New("apa102: invalid RGB stream length") - } - d.l.init(d.Intensity, d.Temperature) - // Do not touch header and footer. - d.l.raster(d.pixels, pixels) - err := d.s.Tx(d.rawBuf, nil) - return len(pixels), err -} - -// Halt turns off all the lights. -func (d *Dev) Halt() error { - _, err := d.Write(make([]byte, d.numPixels*3)) - return err -} - -// New returns a strip that communicates over SPI to APA102 LEDs. -// -// The SPI port speed should be high, at least in the Mhz range, as -// there's 32 bits sent per LED, creating a staggered effect. See -// https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ -// -// Temperature is in °Kelvin and a reasonable default value is 6500°K. -// -// As per APA102-C spec, the chip's max refresh rate is 400hz. -// https://en.wikipedia.org/wiki/Flicker_fusion_threshold is a recommended -// reading. -func New(p spi.Port, numPixels int, intensity uint8, temperature uint16) (*Dev, error) { - c, err := p.Connect(20000000, spi.Mode3, 8) - if err != nil { - return nil, err - } - // End frames are needed to be able to push enough SPI clock signals due to - // internal half-delay of data signal from each individual LED. See - // https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ - buf := make([]byte, 4*(numPixels+1)+numPixels/2/8+1) - tail := buf[4+4*numPixels:] - for i := range tail { - tail[i] = 0xFF - } - return &Dev{ - Intensity: intensity, - Temperature: temperature, - s: c, - numPixels: numPixels, - rawBuf: buf, - pixels: buf[4 : 4+4*numPixels], - }, nil -} - -// - var _ conn.Resource = &Dev{} var _ devices.Display = &Dev{} var _ fmt.Stringer = &Dev{} diff --git a/devices/bmxx80/bmxx80.go b/devices/bmxx80/bmxx80.go index 1221a8f..5c9201b 100644 --- a/devices/bmxx80/bmxx80.go +++ b/devices/bmxx80/bmxx80.go @@ -124,6 +124,99 @@ const ( F16 Filter = 4 ) +// Opts is optional options to pass to the constructor. +// +// Recommended (and default) values are O4x for oversampling. +// +// Recommended sensing settings as per the datasheet: +// +// → Weather monitoring: manual sampling once per minute, all sensors O1x. +// Power consumption: 0.16µA, filter NoFilter. RMS noise: 3.3Pa / 30cm, 0.07%RH. +// +// → Humidity sensing: manual sampling once per second, pressure Off, humidity +// and temperature O1X, filter NoFilter. Power consumption: 2.9µA, 0.07%RH. +// +// → Indoor navigation: continuous sampling at 40ms with filter F16, pressure +// O16x, temperature O2x, humidity O1x, filter F16. Power consumption 633µA. +// RMS noise: 0.2Pa / 1.7cm. +// +// → Gaming: continuous sampling at 40ms with filter F16, pressure O4x, +// temperature O1x, humidity Off, filter F16. Power consumption 581µA. RMS +// noise: 0.3Pa / 2.5cm. +// +// See the datasheet for more details about the trade offs. +type Opts struct { + // Temperature can only be oversampled on BME280/BMP280. + // + // Temperature must be measured for pressure and humidity to be measured. + Temperature Oversampling + // Pressure can be oversampled up to 8x on BMP180 and 16x on BME280/BMP280. + Pressure Oversampling + // Humidity sensing is only supported on BME280. The value is ignored on other + // devices. + Humidity Oversampling + // Filter is only used while using SenseContinuous() and is only supported on + // BMx280. + Filter Filter +} + +func (o *Opts) delayTypical280() time.Duration { + // Page 51. + µs := 1000 + if o.Temperature != Off { + µs += 2000 * o.Temperature.asValue() + } + if o.Pressure != Off { + µs += 2000*o.Pressure.asValue() + 500 + } + if o.Humidity != Off { + µs += 2000*o.Humidity.asValue() + 500 + } + return time.Microsecond * time.Duration(µs) +} + +// NewI2C returns an object that communicates over I²C to BMP180/BME280/BMP280 +// environmental sensor. +// +// The address must be 0x76 or 0x77. BMP180 uses 0x77. BME280/BMP280 default to +// 0x76 and can optionally use 0x77. The value used depends on HW +// configuration of the sensor's SDO pin. +// +// It is recommended to call Halt() when done with the device so it stops +// sampling. +func NewI2C(b i2c.Bus, addr uint16, opts *Opts) (*Dev, error) { + switch addr { + case 0x76, 0x77: + default: + return nil, errors.New("bmxx80: given address not supported by device") + } + d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false} + if err := d.makeDev(opts); err != nil { + return nil, err + } + return d, nil +} + +// NewSPI returns an object that communicates over SPI to either a BME280 or +// BMP280 environmental sensor. +// +// It is recommended to call Halt() when done with the device so it stops +// sampling. +// +// When using SPI, the CS line must be used. +func NewSPI(p spi.Port, opts *Opts) (*Dev, error) { + // It works both in Mode0 and Mode3. + c, err := p.Connect(10000000, spi.Mode3, 8) + if err != nil { + return nil, fmt.Errorf("bmxx80: %v", err) + } + d := &Dev{d: c, isSPI: true} + if err := d.makeDev(opts); err != nil { + return nil, err + } + return d, nil +} + // Dev is a handle to an initialized BMxx80 device. // // The actual device type was auto detected. @@ -247,99 +340,6 @@ func (d *Dev) Halt() error { return nil } -// Opts is optional options to pass to the constructor. -// -// Recommended (and default) values are O4x for oversampling. -// -// Recommended sensing settings as per the datasheet: -// -// → Weather monitoring: manual sampling once per minute, all sensors O1x. -// Power consumption: 0.16µA, filter NoFilter. RMS noise: 3.3Pa / 30cm, 0.07%RH. -// -// → Humidity sensing: manual sampling once per second, pressure Off, humidity -// and temperature O1X, filter NoFilter. Power consumption: 2.9µA, 0.07%RH. -// -// → Indoor navigation: continuous sampling at 40ms with filter F16, pressure -// O16x, temperature O2x, humidity O1x, filter F16. Power consumption 633µA. -// RMS noise: 0.2Pa / 1.7cm. -// -// → Gaming: continuous sampling at 40ms with filter F16, pressure O4x, -// temperature O1x, humidity Off, filter F16. Power consumption 581µA. RMS -// noise: 0.3Pa / 2.5cm. -// -// See the datasheet for more details about the trade offs. -type Opts struct { - // Temperature can only be oversampled on BME280/BMP280. - // - // Temperature must be measured for pressure and humidity to be measured. - Temperature Oversampling - // Pressure can be oversampled up to 8x on BMP180 and 16x on BME280/BMP280. - Pressure Oversampling - // Humidity sensing is only supported on BME280. The value is ignored on other - // devices. - Humidity Oversampling - // Filter is only used while using SenseContinuous() and is only supported on - // BMx280. - Filter Filter -} - -func (o *Opts) delayTypical280() time.Duration { - // Page 51. - µs := 1000 - if o.Temperature != Off { - µs += 2000 * o.Temperature.asValue() - } - if o.Pressure != Off { - µs += 2000*o.Pressure.asValue() + 500 - } - if o.Humidity != Off { - µs += 2000*o.Humidity.asValue() + 500 - } - return time.Microsecond * time.Duration(µs) -} - -// NewI2C returns an object that communicates over I²C to BMP180/BME280/BMP280 -// environmental sensor. -// -// The address must be 0x76 or 0x77. BMP180 uses 0x77. BME280/BMP280 default to -// 0x76 and can optionally use 0x77. The value used depends on HW -// configuration of the sensor's SDO pin. -// -// It is recommended to call Halt() when done with the device so it stops -// sampling. -func NewI2C(b i2c.Bus, addr uint16, opts *Opts) (*Dev, error) { - switch addr { - case 0x76, 0x77: - default: - return nil, errors.New("bmxx80: given address not supported by device") - } - d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false} - if err := d.makeDev(opts); err != nil { - return nil, err - } - return d, nil -} - -// NewSPI returns an object that communicates over SPI to either a BME280 or -// BMP280 environmental sensor. -// -// It is recommended to call Halt() when done with the device so it stops -// sampling. -// -// When using SPI, the CS line must be used. -func NewSPI(p spi.Port, opts *Opts) (*Dev, error) { - // It works both in Mode0 and Mode3. - c, err := p.Connect(10000000, spi.Mode3, 8) - if err != nil { - return nil, fmt.Errorf("bmxx80: %v", err) - } - d := &Dev{d: c, isSPI: true} - if err := d.makeDev(opts); err != nil { - return nil, err - } - return d, nil -} - // func (d *Dev) makeDev(opts *Opts) error { diff --git a/devices/ds18b20/ds18b20.go b/devices/ds18b20/ds18b20.go index fcf62f6..97434ce 100644 --- a/devices/ds18b20/ds18b20.go +++ b/devices/ds18b20/ds18b20.go @@ -31,6 +31,26 @@ import ( "periph.io/x/periph/devices" ) +// ConvertAll performs a conversion on all DS18B20 devices on the bus. +// +// During the conversion it places the bus in strong pull-up mode to power +// parasitic devices and returns when the conversions have completed. This time +// period is determined by the maximum resolution of all devices on the bus and +// must be provided. +// +// ConvertAll uses time.Sleep to wait for the conversion to finish, which takes +// from 94ms to 752ms. +func ConvertAll(o onewire.Bus, maxResolutionBits int) error { + if maxResolutionBits < 9 || maxResolutionBits > 12 { + return errors.New("ds18b20: invalid maxResolutionBits") + } + if err := o.Tx([]byte{0xcc, 0x44}, nil, onewire.StrongPullup); err != nil { + return err + } + conversionSleep(maxResolutionBits) + return nil +} + // New returns an object that communicates over 1-wire to the DS18B20 sensor // with the specified 64-bit address. // @@ -72,28 +92,6 @@ func New(o onewire.Bus, addr onewire.Address, resolutionBits int) (*Dev, error) return d, nil } -// ConvertAll performs a conversion on all DS18B20 devices on the bus. -// -// During the conversion it places the bus in strong pull-up mode to power -// parasitic devices and returns when the conversions have completed. This time -// period is determined by the maximum resolution of all devices on the bus and -// must be provided. -// -// ConvertAll uses time.Sleep to wait for the conversion to finish, which takes -// from 94ms to 752ms. -func ConvertAll(o onewire.Bus, maxResolutionBits int) error { - if maxResolutionBits < 9 || maxResolutionBits > 12 { - return errors.New("ds18b20: invalid maxResolutionBits") - } - if err := o.Tx([]byte{0xcc, 0x44}, nil, onewire.StrongPullup); err != nil { - return err - } - conversionSleep(maxResolutionBits) - return nil -} - -//===== Dev - // Dev is a handle to a Dallas Semi / Maxim DS18B20 temperature sensor on a // 1-wire bus. type Dev struct { diff --git a/devices/ds248x/dev.go b/devices/ds248x/dev.go deleted file mode 100644 index eddfa3e..0000000 --- a/devices/ds248x/dev.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2016 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package ds248x - -import ( - "fmt" - "sync" - "time" - - "periph.io/x/periph/conn" - "periph.io/x/periph/conn/onewire" -) - -// Dev is a handle to a ds248x device and it implements the onewire.Bus -// interface. -// -// Dev implements a persistent error model: if a fatal error is encountered it -// places itself into an error state and immediately returns the last error on -// all subsequent calls. A fresh Dev, which reinitializes the hardware, must be -// created to proceed. -// -// A persistent error is only set when there is a problem with the ds248x -// device itself (or the I²C bus used to access it). Errors on the 1-wire bus -// do not cause persistent errors and implement the onewire.BusError interface -// to indicate this fact. -type Dev struct { - sync.Mutex // lock for the bus while a transaction is in progress - i2c conn.Conn // i2c device handle for the ds248x - isDS2483 bool // true: ds2483, false: ds2482-100 - confReg byte // value written to configuration register - tReset time.Duration // time to perform a 1-wire reset - tSlot time.Duration // time to perform a 1-bit 1-wire read/write - err error // persistent error, device will no longer operate -} - -func (d *Dev) String() string { - if d.isDS2483 { - return fmt.Sprintf("DS2483{%s}", d.i2c) - } - return fmt.Sprintf("DS2482-100{%s}", d.i2c) -} - -// Halt implements conn.Resource. -func (d *Dev) Halt() error { - return nil -} - -// Tx performs a bus transaction, sending and receiving bytes, and ending by -// pulling the bus high either weakly or strongly depending on the value of -// power. -// -// A strong pull-up is typically required to power temperature conversion or -// EEPROM writes. -func (d *Dev) Tx(w, r []byte, power onewire.Pullup) error { - d.Lock() - defer d.Unlock() - - // Issue 1-wire bus reset. - if present, err := d.reset(); err != nil { - return err - } else if !present { - return busError("ds248x: no device present") - } - - // Send bytes onto 1-wire bus. - for i, b := range w { - if power == onewire.StrongPullup && i == len(w)-1 && len(r) == 0 { - // This is the last byte, need to activate strong pull-up. - d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil) - } - d.i2cTx([]byte{cmd1WWrite, b}, nil) - d.waitIdle(7 * d.tSlot) - } - - // Read bytes from one-wire bus. - for i := range r { - if power == onewire.StrongPullup && i == len(r)-1 { - // This is the last byte, need to activate strong-pull-up - d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil) - } - d.i2cTx([]byte{cmd1WRead}, r[i:i+1]) - d.waitIdle(7 * d.tSlot) - d.i2cTx([]byte{cmdSetReadPtr, regRDR}, r[i:i+1]) - } - - return d.err -} - -// Search performs a "search" cycle on the 1-wire bus and returns the addresses -// of all devices on the bus if alarmOnly is false and of all devices in alarm -// state if alarmOnly is true. -// -// If an error occurs during the search the already-discovered devices are -// returned with the error. -func (d *Dev) Search(alarmOnly bool) ([]onewire.Address, error) { - return onewire.Search(d, alarmOnly) -} - -// SearchTriplet performs a single bit search triplet command on the bus, waits -// for it to complete and returs the outcome. -// -// SearchTriplet should not be used directly, use Search instead. -func (d *Dev) SearchTriplet(direction byte) (onewire.TripletResult, error) { - // Send one-wire triplet command. - var dir byte - if direction != 0 { - dir = 0x80 - } - d.i2cTx([]byte{cmd1WTriplet, dir}, nil) - // Wait and read status register, concoct result from there. - status := d.waitIdle(0 * d.tSlot) // in theory 3*tSlot but it's actually overlapped - tr := onewire.TripletResult{ - GotZero: status&0x20 == 0, - GotOne: status&0x40 == 0, - Taken: status >> 7, - } - return tr, d.err -} - -// - -// reset issues a reset signal on the 1-wire bus and returns true if any device -// responded with a presence pulse. -func (d *Dev) reset() (bool, error) { - // Issue reset. - d.i2cTx([]byte{cmd1WReset}, nil) - - // Wait for reset to complete. - status := d.waitIdle(d.tReset) - if d.err != nil { - return false, d.err - } - // Detect bus short and turn into 1-wire error - if (status & 4) != 0 { - return false, shortedBusError("onewire/ds248x: bus has a short") - } - return (status & 2) != 0, nil -} - -// i2cTx is a helper function to call i2c.Tx and handle the error by persisting -// it. -func (d *Dev) i2cTx(w, r []byte) { - if d.err != nil { - return - } - d.err = d.i2c.Tx(w, r) -} - -// waitIdle waits for the one wire bus to be idle. -// -// It initially sleeps for the delay and then polls the status register and -// sleeps for a tenth of the delay each time the status register indicates that -// the bus is still busy. The last read status byte is returned. -// -// An overall timeout of 3ms is applied to the whole procedure. waitIdle uses -// the persistent error model and returns 0 if there is an error. -func (d *Dev) waitIdle(delay time.Duration) byte { - if d.err != nil { - return 0 - } - // Overall timeout. - tOut := time.Now().Add(3 * time.Millisecond) - sleep(delay) - for { - // Read status register. - var status [1]byte - d.i2cTx(nil, status[:]) - // If bus idle complete, return status. This also returns if d.err!=nil - // because in that case status[0]==0. - if (status[0] & 1) == 0 { - return status[0] - } - // If we're timing out return error. This is an error with the ds248x, not with - // devices on the 1-wire bus, hence it is persistent. - if time.Now().After(tOut) { - d.err = fmt.Errorf("ds248x: timeout waiting for bus cycle to finish") - return 0 - } - // Try not to hog the kernel thread. - sleep(delay / 10) - } -} - -// shortedBusError implements error and onewire.ShortedBusError. -type shortedBusError string - -func (e shortedBusError) Error() string { return string(e) } -func (e shortedBusError) IsShorted() bool { return true } -func (e shortedBusError) BusError() bool { return true } - -// busError implements error and onewire.BusError. -type busError string - -func (e busError) Error() string { return string(e) } -func (e busError) BusError() bool { return true } - -var sleep = time.Sleep - -var _ conn.Resource = &Dev{} -var _ fmt.Stringer = &Dev{} diff --git a/devices/ds248x/ds248x.go b/devices/ds248x/ds248x.go index de3398b..e7fab5b 100644 --- a/devices/ds248x/ds248x.go +++ b/devices/ds248x/ds248x.go @@ -14,9 +14,12 @@ package ds248x import ( "errors" "fmt" + "sync" "time" + "periph.io/x/periph/conn" "periph.io/x/periph/conn/i2c" + "periph.io/x/periph/conn/onewire" ) // PupOhm controls the strength of the passive pull-up resistor @@ -67,16 +70,174 @@ func New(i i2c.Bus, opts *Opts) (*Dev, error) { return d, nil } +// Dev is a handle to a ds248x device and it implements the onewire.Bus +// interface. // +// Dev implements a persistent error model: if a fatal error is encountered it +// places itself into an error state and immediately returns the last error on +// all subsequent calls. A fresh Dev, which reinitializes the hardware, must be +// created to proceed. +// +// A persistent error is only set when there is a problem with the ds248x +// device itself (or the I²C bus used to access it). Errors on the 1-wire bus +// do not cause persistent errors and implement the onewire.BusError interface +// to indicate this fact. +type Dev struct { + sync.Mutex // lock for the bus while a transaction is in progress + i2c conn.Conn // i2c device handle for the ds248x + isDS2483 bool // true: ds2483, false: ds2482-100 + confReg byte // value written to configuration register + tReset time.Duration // time to perform a 1-wire reset + tSlot time.Duration // time to perform a 1-bit 1-wire read/write + err error // persistent error, device will no longer operate +} -// defaults holds default values for optional parameters. -var defaults = Opts{ - PassivePullup: false, - ResetLow: 560 * time.Microsecond, - PresenceDetect: 68 * time.Microsecond, - Write0Low: 64 * time.Microsecond, - Write0Recovery: 5250 * time.Nanosecond, - PullupRes: R1000Ω, +func (d *Dev) String() string { + if d.isDS2483 { + return fmt.Sprintf("DS2483{%s}", d.i2c) + } + return fmt.Sprintf("DS2482-100{%s}", d.i2c) +} + +// Halt implements conn.Resource. +func (d *Dev) Halt() error { + return nil +} + +// Tx performs a bus transaction, sending and receiving bytes, and ending by +// pulling the bus high either weakly or strongly depending on the value of +// power. +// +// A strong pull-up is typically required to power temperature conversion or +// EEPROM writes. +func (d *Dev) Tx(w, r []byte, power onewire.Pullup) error { + d.Lock() + defer d.Unlock() + + // Issue 1-wire bus reset. + if present, err := d.reset(); err != nil { + return err + } else if !present { + return busError("ds248x: no device present") + } + + // Send bytes onto 1-wire bus. + for i, b := range w { + if power == onewire.StrongPullup && i == len(w)-1 && len(r) == 0 { + // This is the last byte, need to activate strong pull-up. + d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil) + } + d.i2cTx([]byte{cmd1WWrite, b}, nil) + d.waitIdle(7 * d.tSlot) + } + + // Read bytes from one-wire bus. + for i := range r { + if power == onewire.StrongPullup && i == len(r)-1 { + // This is the last byte, need to activate strong-pull-up + d.i2cTx([]byte{cmdWriteConfig, d.confReg&0xbf | 0x4}, nil) + } + d.i2cTx([]byte{cmd1WRead}, r[i:i+1]) + d.waitIdle(7 * d.tSlot) + d.i2cTx([]byte{cmdSetReadPtr, regRDR}, r[i:i+1]) + } + + return d.err +} + +// Search performs a "search" cycle on the 1-wire bus and returns the addresses +// of all devices on the bus if alarmOnly is false and of all devices in alarm +// state if alarmOnly is true. +// +// If an error occurs during the search the already-discovered devices are +// returned with the error. +func (d *Dev) Search(alarmOnly bool) ([]onewire.Address, error) { + return onewire.Search(d, alarmOnly) +} + +// SearchTriplet performs a single bit search triplet command on the bus, waits +// for it to complete and returs the outcome. +// +// SearchTriplet should not be used directly, use Search instead. +func (d *Dev) SearchTriplet(direction byte) (onewire.TripletResult, error) { + // Send one-wire triplet command. + var dir byte + if direction != 0 { + dir = 0x80 + } + d.i2cTx([]byte{cmd1WTriplet, dir}, nil) + // Wait and read status register, concoct result from there. + status := d.waitIdle(0 * d.tSlot) // in theory 3*tSlot but it's actually overlapped + tr := onewire.TripletResult{ + GotZero: status&0x20 == 0, + GotOne: status&0x40 == 0, + Taken: status >> 7, + } + return tr, d.err +} + +// + +// reset issues a reset signal on the 1-wire bus and returns true if any device +// responded with a presence pulse. +func (d *Dev) reset() (bool, error) { + // Issue reset. + d.i2cTx([]byte{cmd1WReset}, nil) + + // Wait for reset to complete. + status := d.waitIdle(d.tReset) + if d.err != nil { + return false, d.err + } + // Detect bus short and turn into 1-wire error + if (status & 4) != 0 { + return false, shortedBusError("onewire/ds248x: bus has a short") + } + return (status & 2) != 0, nil +} + +// i2cTx is a helper function to call i2c.Tx and handle the error by persisting +// it. +func (d *Dev) i2cTx(w, r []byte) { + if d.err != nil { + return + } + d.err = d.i2c.Tx(w, r) +} + +// waitIdle waits for the one wire bus to be idle. +// +// It initially sleeps for the delay and then polls the status register and +// sleeps for a tenth of the delay each time the status register indicates that +// the bus is still busy. The last read status byte is returned. +// +// An overall timeout of 3ms is applied to the whole procedure. waitIdle uses +// the persistent error model and returns 0 if there is an error. +func (d *Dev) waitIdle(delay time.Duration) byte { + if d.err != nil { + return 0 + } + // Overall timeout. + tOut := time.Now().Add(3 * time.Millisecond) + sleep(delay) + for { + // Read status register. + var status [1]byte + d.i2cTx(nil, status[:]) + // If bus idle complete, return status. This also returns if d.err!=nil + // because in that case status[0]==0. + if (status[0] & 1) == 0 { + return status[0] + } + // If we're timing out return error. This is an error with the ds248x, not with + // devices on the 1-wire bus, hence it is persistent. + if time.Now().After(tOut) { + d.err = fmt.Errorf("ds248x: timeout waiting for bus cycle to finish") + return 0 + } + // Try not to hog the kernel thread. + sleep(delay / 10) + } } func (d *Dev) makeDev(opts *Opts) error { @@ -154,6 +315,36 @@ func (d *Dev) makeDev(opts *Opts) error { return nil } +// + +// shortedBusError implements error and onewire.ShortedBusError. +type shortedBusError string + +func (e shortedBusError) Error() string { return string(e) } +func (e shortedBusError) IsShorted() bool { return true } +func (e shortedBusError) BusError() bool { return true } + +// busError implements error and onewire.BusError. +type busError string + +func (e busError) Error() string { return string(e) } +func (e busError) BusError() bool { return true } + +var sleep = time.Sleep + +var _ conn.Resource = &Dev{} +var _ fmt.Stringer = &Dev{} + +// defaults holds default values for optional parameters. +var defaults = Opts{ + PassivePullup: false, + ResetLow: 560 * time.Microsecond, + PresenceDetect: 68 * time.Microsecond, + Write0Low: 64 * time.Microsecond, + Write0Recovery: 5250 * time.Nanosecond, + PullupRes: R1000Ω, +} + const ( cmdReset = 0xf0 // reset ds248x cmdSetReadPtr = 0xe1 // set the read pointer diff --git a/devices/lepton/cci/cci.go b/devices/lepton/cci/cci.go index 39f31e0..338e622 100644 --- a/devices/lepton/cci/cci.go +++ b/devices/lepton/cci/cci.go @@ -139,22 +139,6 @@ type FFCMode struct { ExplicitCommandToOpen bool // Default: false } -// - -// Dev is the Lepton specific Command and Control Interface (CCI). -// -// -// Dev can safely accessed concurrently via multiple goroutines. -// -// This interface is accessed via I²C and provides access to view and modify -// the internal state. -// -// Maximum I²C speed is 1Mhz. -type Dev struct { - c cciConn - serial uint64 -} - // New returns a driver for the FLIR Lepton CCI protocol. func New(i i2c.Bus) (*Dev, error) { d := &Dev{ @@ -173,6 +157,20 @@ func New(i i2c.Bus) (*Dev, error) { } } +// Dev is the Lepton specific Command and Control Interface (CCI). +// +// +// Dev can safely accessed concurrently via multiple goroutines. +// +// This interface is accessed via I²C and provides access to view and modify +// the internal state. +// +// Maximum I²C speed is 1Mhz. +type Dev struct { + c cciConn + serial uint64 +} + func (d *Dev) String() string { return d.c.String() } diff --git a/devices/lepton/lepton.go b/devices/lepton/lepton.go index d22adb1..13505e7 100644 --- a/devices/lepton/lepton.go +++ b/devices/lepton/lepton.go @@ -64,23 +64,6 @@ type Frame struct { Metadata Metadata // Metadata that is sent along the pixels. } -// Dev controls a FLIR Lepton. -// -// It assumes a specific breakout board. Sadly the breakout board doesn't -// expose the PWR_DWN_L and RESET_L lines so it is impossible to shut down the -// Lepton. -type Dev struct { - *cci.Dev - s spi.Conn - cs gpio.PinOut - prevImg *image.Gray16 - frameA, frameB []byte - frameWidth int // in bytes - frameLines int - maxTxSize int - delay time.Duration -} - // New returns an initialized connection to the FLIR Lepton. // // The CS line is manually managed by using mode spi.NoCS when calling @@ -150,6 +133,23 @@ func New(p spi.Port, i i2c.Bus, cs gpio.PinOut) (*Dev, error) { return d, nil } +// Dev controls a FLIR Lepton. +// +// It assumes a specific breakout board. Sadly the breakout board doesn't +// expose the PWR_DWN_L and RESET_L lines so it is impossible to shut down the +// Lepton. +type Dev struct { + *cci.Dev + s spi.Conn + cs gpio.PinOut + prevImg *image.Gray16 + frameA, frameB []byte + frameWidth int // in bytes + frameLines int + maxTxSize int + delay time.Duration +} + func (d *Dev) String() string { return fmt.Sprintf("Lepton(%s/%s/%s)", d.Dev, d.s, d.cs) } diff --git a/devices/lirc/lirc.go b/devices/lirc/lirc.go index e4343b8..30a1bf8 100644 --- a/devices/lirc/lirc.go +++ b/devices/lirc/lirc.go @@ -18,16 +18,6 @@ import ( "periph.io/x/periph/conn/ir" ) -// Conn is an open port to lirc. -type Conn struct { - w net.Conn - c chan ir.Message - - mu sync.Mutex - list map[string][]string // list of remotes and associated keys - pendingList map[string][]string // list of remotes and associated keys being created. -} - // New returns a IR receiver / emitter handle. func New() (*Conn, error) { w, err := net.Dial("unix", "/var/run/lirc/lircd") @@ -44,6 +34,16 @@ func New() (*Conn, error) { return c, nil } +// Conn is an open port to lirc. +type Conn struct { + w net.Conn + c chan ir.Message + + mu sync.Mutex + list map[string][]string // list of remotes and associated keys + pendingList map[string][]string // list of remotes and associated keys being created. +} + func (c *Conn) String() string { return "lirc" } diff --git a/devices/ssd1306/ssd1306.go b/devices/ssd1306/ssd1306.go index eb55dbc..46d8b49 100644 --- a/devices/ssd1306/ssd1306.go +++ b/devices/ssd1306/ssd1306.go @@ -77,33 +77,6 @@ const ( UpLeft Orientation = 0x2A ) -// Dev is an open handle to the display controller. -type Dev struct { - // Communication - c conn.Conn - dc gpio.PinOut - spi bool - - // Display size controlled by the SSD1306. - rect image.Rectangle - - // Mutable - // See page 25 for the GDDRAM pages structure. - // Narrow screen will waste the end of each page. - // Short screen will ignore the lower pages. - // There is 8 pages, each covering an horizontal band of 8 pixels high (1 - // byte) for 128 bytes. - // 8*128 = 1024 bytes total for 128x64 display. - buffer []byte - // next is lazy initialized on first Draw(). Write() skips this buffer. - next *image1bit.VerticalLSB - startPage, endPage int - startCol, endCol int - scrolled bool - halted bool - err error -} - // NewSPI returns a Dev object that communicates over SPI to a SSD1306 display // controller. // @@ -149,76 +122,31 @@ func NewI2C(i i2c.Bus, w, h int, rotated bool) (*Dev, error) { return newDev(&i2c.Dev{Bus: i, Addr: 0x3C}, w, h, rotated, false, nil) } -// newDev is the common initialization code that is independent of the -// communication protocol (I²C or SPI) being used. -func newDev(c conn.Conn, w, h int, rotated, usingSPI bool, dc gpio.PinOut) (*Dev, error) { - if w < 8 || w > 128 || w&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid width %d", w) - } - if h < 8 || h > 64 || h&7 != 0 { - return nil, fmt.Errorf("ssd1306: invalid height %d", h) - } - - nbPages := h / 8 - pageSize := w - d := &Dev{ - c: c, - spi: usingSPI, - dc: dc, - rect: image.Rect(0, 0, int(w), int(h)), - buffer: make([]byte, nbPages*pageSize), - startPage: 0, - endPage: nbPages, - startCol: 0, - endCol: w, - // Signal that the screen must be redrawn on first draw(). - scrolled: true, - } - if err := d.sendCommand(getInitCmd(w, h, rotated)); err != nil { - return nil, err - } - return d, nil -} +// Dev is an open handle to the display controller. +type Dev struct { + // Communication + c conn.Conn + dc gpio.PinOut + spi bool -func getInitCmd(w, h int, rotated bool) []byte { - // Set COM output scan direction; C0 means normal; C8 means reversed - comScan := byte(0xC8) - // See page 40. - columnAddr := byte(0xA1) - if rotated { - // Change order both horizontally and vertically. - comScan = 0xC0 - columnAddr = byte(0xA0) - } - // Set the max frequency. The problem with I²C is that it creates visible - // tear down. On SPI at high speed this is not visible. Page 23 pictures how - // to avoid tear down. For now default to max frequency. - freq := byte(0xF0) + // Display size controlled by the SSD1306. + rect image.Rectangle - // Initialize the device by fully resetting all values. - // Page 64 has the full recommended flow. - // Page 28 lists all the commands. - return []byte{ - 0xAE, // Display off - 0xD3, 0x00, // Set display offset; 0 - 0x40, // Start display start line; 0 - columnAddr, // Set segment remap; RESET is column 127. - comScan, // - 0xDA, 0x12, // Set COM pins hardware configuration; see page 40 - 0x81, 0xFF, // Set max contrast - 0xA4, // Set display to use GDDRAM content - 0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark) - 0xD5, freq, // Set osc frequency and divide ratio; power on reset value is 0x80. - 0x8D, 0x14, // Enable charge pump regulator; page 62 - 0xD9, 0xF1, // Set pre-charge period; from adafruit driver - 0xDB, 0x40, // Set Vcomh deselect level; page 32 - 0x2E, // Deactivate scroll - 0xA8, byte(h - 1), // Set multiplex ratio (number of lines to display) - 0x20, 0x00, // Set memory addressing mode to horizontal - 0x21, 0, uint8(w - 1), // Set column address (Width) - 0x22, 0, uint8(h/8 - 1), // Set page address (Pages) - 0xAF, // Display on - } + // Mutable + // See page 25 for the GDDRAM pages structure. + // Narrow screen will waste the end of each page. + // Short screen will ignore the lower pages. + // There is 8 pages, each covering an horizontal band of 8 pixels high (1 + // byte) for 128 bytes. + // 8*128 = 1024 bytes total for 128x64 display. + buffer []byte + // next is lazy initialized on first Draw(). Write() skips this buffer. + next *image1bit.VerticalLSB + startPage, endPage int + startCol, endCol int + scrolled bool + halted bool + err error } func (d *Dev) String() string { @@ -356,6 +284,78 @@ func (d *Dev) Invert(blackOnWhite bool) error { // +// newDev is the common initialization code that is independent of the +// communication protocol (I²C or SPI) being used. +func newDev(c conn.Conn, w, h int, rotated, usingSPI bool, dc gpio.PinOut) (*Dev, error) { + if w < 8 || w > 128 || w&7 != 0 { + return nil, fmt.Errorf("ssd1306: invalid width %d", w) + } + if h < 8 || h > 64 || h&7 != 0 { + return nil, fmt.Errorf("ssd1306: invalid height %d", h) + } + + nbPages := h / 8 + pageSize := w + d := &Dev{ + c: c, + spi: usingSPI, + dc: dc, + rect: image.Rect(0, 0, int(w), int(h)), + buffer: make([]byte, nbPages*pageSize), + startPage: 0, + endPage: nbPages, + startCol: 0, + endCol: w, + // Signal that the screen must be redrawn on first draw(). + scrolled: true, + } + if err := d.sendCommand(getInitCmd(w, h, rotated)); err != nil { + return nil, err + } + return d, nil +} + +func getInitCmd(w, h int, rotated bool) []byte { + // Set COM output scan direction; C0 means normal; C8 means reversed + comScan := byte(0xC8) + // See page 40. + columnAddr := byte(0xA1) + if rotated { + // Change order both horizontally and vertically. + comScan = 0xC0 + columnAddr = byte(0xA0) + } + // Set the max frequency. The problem with I²C is that it creates visible + // tear down. On SPI at high speed this is not visible. Page 23 pictures how + // to avoid tear down. For now default to max frequency. + freq := byte(0xF0) + + // Initialize the device by fully resetting all values. + // Page 64 has the full recommended flow. + // Page 28 lists all the commands. + return []byte{ + 0xAE, // Display off + 0xD3, 0x00, // Set display offset; 0 + 0x40, // Start display start line; 0 + columnAddr, // Set segment remap; RESET is column 127. + comScan, // + 0xDA, 0x12, // Set COM pins hardware configuration; see page 40 + 0x81, 0xFF, // Set max contrast + 0xA4, // Set display to use GDDRAM content + 0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark) + 0xD5, freq, // Set osc frequency and divide ratio; power on reset value is 0x80. + 0x8D, 0x14, // Enable charge pump regulator; page 62 + 0xD9, 0xF1, // Set pre-charge period; from adafruit driver + 0xDB, 0x40, // Set Vcomh deselect level; page 32 + 0x2E, // Deactivate scroll + 0xA8, byte(h - 1), // Set multiplex ratio (number of lines to display) + 0x20, 0x00, // Set memory addressing mode to horizontal + 0x21, 0, uint8(w - 1), // Set column address (Width) + 0x22, 0, uint8(h/8 - 1), // Set page address (Pages) + 0xAF, // Display on + } +} + func (d *Dev) calculateSubset(next []byte) (int, int, int, int, bool) { w := d.rect.Dx() h := d.rect.Dy() diff --git a/devices/tm1637/tm1637.go b/devices/tm1637/tm1637.go index 6905416..91e6952 100644 --- a/devices/tm1637/tm1637.go +++ b/devices/tm1637/tm1637.go @@ -63,6 +63,19 @@ const ( Brightness14 Brightness = 0x8F // 14/16 PWM ) +// New returns an object that communicates over two pins to a TM1637. +func New(clk gpio.PinOut, data gpio.PinIO) (*Dev, error) { + // Spec calls to idle at high. + if err := clk.Out(gpio.High); err != nil { + return nil, err + } + if err := data.Out(gpio.High); err != nil { + return nil, err + } + d := &Dev{clk: clk, data: data} + return d, nil +} + // Dev represents an handle to a tm1637. type Dev struct { clk gpio.PinOut @@ -136,19 +149,6 @@ func (d *Dev) Halt() error { return err } -// New returns an object that communicates over two pins to a TM1637. -func New(clk gpio.PinOut, data gpio.PinIO) (*Dev, error) { - // Spec calls to idle at high. - if err := clk.Out(gpio.High); err != nil { - return nil, err - } - if err := data.Out(gpio.High); err != nil { - return nil, err - } - d := &Dev{clk: clk, data: data} - return d, nil -} - // // Page 10 states the max clock frequency is 500KHz but page 3 states 250KHz. diff --git a/experimental/devices/bitbang/i2c.go b/experimental/devices/bitbang/i2c.go index d0384ff..0a2a8ea 100644 --- a/experimental/devices/bitbang/i2c.go +++ b/experimental/devices/bitbang/i2c.go @@ -23,6 +23,39 @@ import ( // SkipAddr can be used to skip the address from being sent. const SkipAddr uint16 = 0xFFFF +// New returns an object that communicates I²C over two pins. +// +// BUG(maruel): It is close to working but not yet, the signal is incorrect +// during ACK. +// +// It has two special features: +// - Special address SkipAddr can be used to skip the address from being +// communicated +// - An arbitrary speed can be used +func New(clk gpio.PinIO, data gpio.PinIO, speedHz int) (*I2C, error) { + // Spec calls to idle at high. Page 8, section 3.1.1. + // Set SCL as pull-up. + if err := clk.In(gpio.PullUp, gpio.NoEdge); err != nil { + return nil, err + } + if err := clk.Out(gpio.High); err != nil { + return nil, err + } + // Set SDA as pull-up. + if err := data.In(gpio.PullUp, gpio.NoEdge); err != nil { + return nil, err + } + if err := data.Out(gpio.High); err != nil { + return nil, err + } + i := &I2C{ + scl: clk, + sda: data, + halfCycle: time.Second / time.Duration(speedHz) / time.Duration(2), + } + return i, nil +} + // I2C represents an I²C master implemented as bit-banging on 2 GPIO pins. type I2C struct { mu sync.Mutex @@ -106,39 +139,6 @@ func (i *I2C) SDA() gpio.PinIO { return i.sda } -// New returns an object that communicates I²C over two pins. -// -// BUG(maruel): It is close to working but not yet, the signal is incorrect -// during ACK. -// -// It has two special features: -// - Special address SkipAddr can be used to skip the address from being -// communicated -// - An arbitrary speed can be used -func New(clk gpio.PinIO, data gpio.PinIO, speedHz int) (*I2C, error) { - // Spec calls to idle at high. Page 8, section 3.1.1. - // Set SCL as pull-up. - if err := clk.In(gpio.PullUp, gpio.NoEdge); err != nil { - return nil, err - } - if err := clk.Out(gpio.High); err != nil { - return nil, err - } - // Set SDA as pull-up. - if err := data.In(gpio.PullUp, gpio.NoEdge); err != nil { - return nil, err - } - if err := data.Out(gpio.High); err != nil { - return nil, err - } - i := &I2C{ - scl: clk, - sda: data, - halfCycle: time.Second / time.Duration(speedHz) / time.Duration(2), - } - return i, nil -} - // // "When CLK is a high level and DIO changes from high to low level, data input diff --git a/experimental/devices/bitbang/spi.go b/experimental/devices/bitbang/spi.go index 5b3ccc0..35d88b4 100644 --- a/experimental/devices/bitbang/spi.go +++ b/experimental/devices/bitbang/spi.go @@ -23,6 +23,41 @@ import ( "periph.io/x/periph/host/cpu" ) +// NewSPI returns an object that communicates SPI over 3 or 4 pins. +// +// BUG(maruel): Completely untested. +// +// cs can be nil. +func NewSPI(clk, mosi gpio.PinOut, miso gpio.PinIn, cs gpio.PinOut, speedHz int64) (*SPI, error) { + if err := clk.Out(gpio.High); err != nil { + return nil, err + } + if err := mosi.Out(gpio.High); err != nil { + return nil, err + } + if miso != nil { + if err := miso.In(gpio.PullUp, gpio.NoEdge); err != nil { + return nil, err + } + } + if cs != nil { + // Low means active. + if err := cs.Out(gpio.High); err != nil { + return nil, err + } + } + s := &SPI{ + sck: clk, + sdi: miso, + sdo: mosi, + csn: cs, + mode: spi.Mode3, + bits: 8, + halfCycle: time.Second / time.Duration(speedHz) / time.Duration(2), + } + return s, nil +} + // SPI represents a SPI master implemented as bit-banging on 3 or 4 GPIO pins. type SPI struct { sck gpio.PinOut // Clock @@ -152,41 +187,6 @@ func (s *SPI) CS() gpio.PinOut { return s.csn } -// NewSPI returns an object that communicates SPI over 3 or 4 pins. -// -// BUG(maruel): Completely untested. -// -// cs can be nil. -func NewSPI(clk, mosi gpio.PinOut, miso gpio.PinIn, cs gpio.PinOut, speedHz int64) (*SPI, error) { - if err := clk.Out(gpio.High); err != nil { - return nil, err - } - if err := mosi.Out(gpio.High); err != nil { - return nil, err - } - if miso != nil { - if err := miso.In(gpio.PullUp, gpio.NoEdge); err != nil { - return nil, err - } - } - if cs != nil { - // Low means active. - if err := cs.Out(gpio.High); err != nil { - return nil, err - } - } - s := &SPI{ - sck: clk, - sdi: miso, - sdo: mosi, - csn: cs, - mode: spi.Mode3, - bits: 8, - halfCycle: time.Second / time.Duration(speedHz) / time.Duration(2), - } - return s, nil -} - // // sleep does a busy loop to act as fast as possible. diff --git a/experimental/devices/cap1188/cap1188.go b/experimental/devices/cap1188/cap1188.go index 3df5d94..f043d38 100644 --- a/experimental/devices/cap1188/cap1188.go +++ b/experimental/devices/cap1188/cap1188.go @@ -52,6 +52,35 @@ func (i TouchStatus) String() string { return touchStatusName[touchStatusIndex[i]:touchStatusIndex[i+1]] } +// NewI2C returns a new device that communicates over I²C to cap1188. +// +// Use default options if nil is used. +func NewI2C(b i2c.Bus, opts *Opts) (*Dev, error) { + if opts == nil { + opts = DefaultOpts() + } + addr, err := opts.i2cAddr() + if err != nil { + return nil, wrapf("%v", err) + } + d, err := makeDev(&i2c.Dev{Bus: b, Addr: addr}, false, opts) + if err != nil { + return nil, err + } + log.Printf("cap1188: Connected via I²C address: %#x", addr) + return d, nil +} + +/* +// NewSPI returns an object that communicates over SPI to cap1188 environmental +// sensor. +// +// TODO(mattetti): Expose once implemented and tested. +func NewSPI(p spi.Port, opts *Opts) (*Dev, error) { + return nil, fmt.Errorf("cap1188: not implemented") +} +*/ + // Dev is a handle to a cap1188. type Dev struct { c mmr.Dev8 @@ -215,35 +244,6 @@ func (d *Dev) ClearInterrupt() error { return nil } -// NewI2C returns a new device that communicates over I²C to cap1188. -// -// Use default options if nil is used. -func NewI2C(b i2c.Bus, opts *Opts) (*Dev, error) { - if opts == nil { - opts = DefaultOpts() - } - addr, err := opts.i2cAddr() - if err != nil { - return nil, wrapf("%v", err) - } - d, err := makeDev(&i2c.Dev{Bus: b, Addr: addr}, false, opts) - if err != nil { - return nil, err - } - log.Printf("cap1188: Connected via I²C address: %#x", addr) - return d, nil -} - -/* -// NewSPI returns an object that communicates over SPI to cap1188 environmental -// sensor. -// -// TODO(mattetti): Expose once implemented and tested. -func NewSPI(p spi.Port, opts *Opts) (*Dev, error) { - return nil, fmt.Errorf("cap1188: not implemented") -} -*/ - // func makeDev(c conn.Conn, isSPI bool, opts *Opts) (*Dev, error) { diff --git a/experimental/devices/mfrc522/mfrc522.go b/experimental/devices/mfrc522/mfrc522.go index e56baa5..a5ff1c2 100644 --- a/experimental/devices/mfrc522/mfrc522.go +++ b/experimental/devices/mfrc522/mfrc522.go @@ -46,7 +46,8 @@ const ( KeyA_RN_WN_BITS_RAB_WN_KeyB_RN_WN_EXTRA = 0x07 ) -// AuthStatus indicates the authentication response, could be one of AuthOk, AuthReadFailure or AuthFailure +// AuthStatus indicates the authentication response, could be one of AuthOk, +// AuthReadFailure or AuthFailure type AuthStatus byte // Card authentication status enum. @@ -56,8 +57,8 @@ const ( AuthFailure ) -// BlocksAccess defines the access structure for first 3 blocks of the sector and the access bits for the -// sector trail. +// BlocksAccess defines the access structure for first 3 blocks of the sector +// and the access bits for the sector trail. type BlocksAccess struct { B0, B1, B2 BlockAccess B3 SectorTrailerAccess @@ -67,18 +68,28 @@ func (ba *BlocksAccess) String() string { return fmt.Sprintf("B0: %d, B1: %d, B2: %d, B3: %d", ba.B0, ba.B1, ba.B2, ba.B3) } -// DefaultKey provides the default bytes for card authentication for method B. -var DefaultKey = [...]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} +// CalculateBlockAccess calculates the block access. +func CalculateBlockAccess(ba *BlocksAccess) []byte { + res := make([]byte, 4) + res[0] = ((^ba.getBits(2) & 0x0F) << 4) | (^ba.getBits(1) & 0x0F) + res[1] = ((ba.getBits(1) & 0x0F) << 4) | (^ba.getBits(3) & 0x0F) + res[2] = ((ba.getBits(3) & 0x0F) << 4) | (ba.getBits(2) & 0x0F) + res[3] = res[0] ^ res[1] ^ res[2] + return res +} -// Dev is an handle to an MFRC522 RFID reader. -type Dev struct { - resetPin gpio.PinOut - irqPin gpio.PinIn - operationTimeout time.Duration - spiDev spi.Conn +// ParseBlockAccess parses the given byte array into the block access structure. +func ParseBlockAccess(ad []byte) *BlocksAccess { + return &BlocksAccess{ + B0: BlockAccess(((ad[1] & 0x10) >> 2) | ((ad[2] & 0x01) << 1) | ((ad[2] & 0x10) >> 5)), + B1: BlockAccess(((ad[1] & 0x20) >> 3) | (ad[2] & 0x02) | ((ad[2] & 0x20) >> 5)), + B2: BlockAccess(((ad[1] & 0x40) >> 4) | ((ad[2] & 0x04) >> 1) | ((ad[2] & 0x40) >> 6)), + B3: SectorTrailerAccess(((ad[1] & 0x80) >> 5) | ((ad[2] & 0x08) >> 2) | ((ad[2] & 0x80) >> 7)), + } } -// MFRC522 SPI Dev public API +// DefaultKey provides the default bytes for card authentication for method B. +var DefaultKey = [...]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // NewSPI creates and initializes the RFID card reader attached to SPI. // @@ -114,8 +125,23 @@ func NewSPI(spiPort spi.Port, resetPin gpio.PinOut, irqPin gpio.PinIn) (*Dev, er return dev, nil } +// Dev is an handle to an MFRC522 RFID reader. +type Dev struct { + resetPin gpio.PinOut + irqPin gpio.PinIn + operationTimeout time.Duration + spiDev spi.Conn +} + +func (r *Dev) String() string { + return fmt.Sprintf("Mifare MFRC522 [bus: %v, reset pin: %s, irq pin: %s]", + r.spiDev, r.resetPin.Name(), r.irqPin.Name()) +} + // SetOperationtimeout updates the device timeout for card operations. -// Effectively that sets the maximum time the RFID device will wait for IRQ from the proximity card detection. +// +// Effectively that sets the maximum time the RFID device will wait for IRQ +// from the proximity card detection. // // timeout the duration to wait for IRQ strobe. func (r *Dev) SetOperationtimeout(timeout time.Duration) { @@ -458,7 +484,8 @@ func (r *Dev) WriteBlock(auth byte, sector int, block int, data [16]byte, key [6 return r.write(calcBlockAddress(sector, block%3), data[:]) } -// ReadSectorTrail reads the sector trail (the last sector that contains the sector access bits) +// ReadSectorTrail reads the sector trail (the last sector that contains the +// sector access bits) // // sector - the sector number to read the data from. func (r *Dev) ReadSectorTrail(sector int) ([]byte, error) { @@ -563,31 +590,6 @@ func (r *Dev) ReadAuth(auth byte, sector int, key [6]byte) (data []byte, err err return r.read(calcBlockAddress(sector, 3)) } -// CalculateBlockAccess calculates the block access. -func CalculateBlockAccess(ba *BlocksAccess) []byte { - res := make([]byte, 4) - res[0] = ((^ba.getBits(2) & 0x0F) << 4) | (^ba.getBits(1) & 0x0F) - res[1] = ((ba.getBits(1) & 0x0F) << 4) | (^ba.getBits(3) & 0x0F) - res[2] = ((ba.getBits(3) & 0x0F) << 4) | (ba.getBits(2) & 0x0F) - res[3] = res[0] ^ res[1] ^ res[2] - return res -} - -// ParseBlockAccess parses the given byte array into the block access structure. -func ParseBlockAccess(ad []byte) *BlocksAccess { - return &BlocksAccess{ - B0: BlockAccess(((ad[1] & 0x10) >> 2) | ((ad[2] & 0x01) << 1) | ((ad[2] & 0x10) >> 5)), - B1: BlockAccess(((ad[1] & 0x20) >> 3) | (ad[2] & 0x02) | ((ad[2] & 0x20) >> 5)), - B2: BlockAccess(((ad[1] & 0x40) >> 4) | ((ad[2] & 0x04) >> 1) | ((ad[2] & 0x40) >> 6)), - B3: SectorTrailerAccess(((ad[1] & 0x80) >> 5) | ((ad[2] & 0x08) >> 2) | ((ad[2] & 0x80) >> 7)), - } -} - -func (r *Dev) String() string { - return fmt.Sprintf("Mifare MFRC522 [bus: %v, reset pin: %s, irq pin: %s]", - r.spiDev, r.resetPin.Name(), r.irqPin.Name()) -} - // MFRC522 SPI Dev private/helper functions func (ba *BlocksAccess) getBits(bitNum uint) byte { diff --git a/experimental/devices/nrzled/nrzled.go b/experimental/devices/nrzled/nrzled.go index cda24dc..4943076 100644 --- a/experimental/devices/nrzled/nrzled.go +++ b/experimental/devices/nrzled/nrzled.go @@ -41,6 +41,32 @@ func NRZ(b byte) uint32 { return out } +// New opens a handle to a compatible LED strip. +// +// The speed (hz) should either be 800000 for fast ICs and 400000 for the slow +// ones. +// +// channels should be either 1 (White only), 3 (RGB) or 4 (RGBW). For RGB and +// RGBW, the encoding is respectively GRB and GRBW. +func New(p gpiostream.PinOut, numPixels, hz int, channels int) (*Dev, error) { + if hz <= 0 || hz > 1000000000 { + return nil, errors.New("nrzled: specify valid speed in hz") + } + if channels != 3 && channels != 4 { + return nil, errors.New("nrzled: specify valid number of channels (3 or 4)") + } + return &Dev{ + p: p, + numPixels: numPixels, + channels: channels, + b: gpiostream.BitStreamMSB{ + Res: time.Second / time.Duration(hz), + // Each bit is encoded on 3 bits. + Bits: make(gpiostream.BitsMSB, numPixels*3*channels), + }, + }, nil +} + // Dev is a handle to the LED strip. type Dev struct { p gpiostream.PinOut @@ -152,32 +178,6 @@ func (d *Dev) Write(pixels []byte) (int, error) { return len(pixels), nil } -// New opens a handle to a compatible LED strip. -// -// The speed (hz) should either be 800000 for fast ICs and 400000 for the slow -// ones. -// -// channels should be either 1 (White only), 3 (RGB) or 4 (RGBW). For RGB and -// RGBW, the encoding is respectively GRB and GRBW. -func New(p gpiostream.PinOut, numPixels, hz int, channels int) (*Dev, error) { - if hz <= 0 || hz > 1000000000 { - return nil, errors.New("nrzled: specify valid speed in hz") - } - if channels != 3 && channels != 4 { - return nil, errors.New("nrzled: specify valid number of channels (3 or 4)") - } - return &Dev{ - p: p, - numPixels: numPixels, - channels: channels, - b: gpiostream.BitStreamMSB{ - Res: time.Second / time.Duration(hz), - // Each bit is encoded on 3 bits. - Bits: make(gpiostream.BitsMSB, numPixels*3*channels), - }, - }, nil -} - // // raster converts a RGB/RGBW input stream into a MSB binary output stream as it