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. :/
pull/1/head
Marc-Antoine Ruel 8 years ago
parent 89531ab87e
commit 5b5083e8fb

@ -15,6 +15,121 @@ import (
"periph.io/x/periph/devices" "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. // maxOut is the maximum intensity of each channel on a APA102 LED.
const maxOut = 0x1EE1 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 _ conn.Resource = &Dev{}
var _ devices.Display = &Dev{} var _ devices.Display = &Dev{}
var _ fmt.Stringer = &Dev{} var _ fmt.Stringer = &Dev{}

@ -124,6 +124,99 @@ const (
F16 Filter = 4 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. // Dev is a handle to an initialized BMxx80 device.
// //
// The actual device type was auto detected. // The actual device type was auto detected.
@ -247,99 +340,6 @@ func (d *Dev) Halt() error {
return nil 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 { func (d *Dev) makeDev(opts *Opts) error {

@ -31,6 +31,26 @@ import (
"periph.io/x/periph/devices" "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 // New returns an object that communicates over 1-wire to the DS18B20 sensor
// with the specified 64-bit address. // 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 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 // Dev is a handle to a Dallas Semi / Maxim DS18B20 temperature sensor on a
// 1-wire bus. // 1-wire bus.
type Dev struct { type Dev struct {

@ -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{}

@ -14,9 +14,12 @@ package ds248x
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync"
"time" "time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/i2c" "periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/onewire"
) )
// PupOhm controls the strength of the passive pull-up resistor // 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 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. func (d *Dev) String() string {
var defaults = Opts{ if d.isDS2483 {
PassivePullup: false, return fmt.Sprintf("DS2483{%s}", d.i2c)
ResetLow: 560 * time.Microsecond, }
PresenceDetect: 68 * time.Microsecond, return fmt.Sprintf("DS2482-100{%s}", d.i2c)
Write0Low: 64 * time.Microsecond, }
Write0Recovery: 5250 * time.Nanosecond,
PullupRes: R1000Ω, // 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 { func (d *Dev) makeDev(opts *Opts) error {
@ -154,6 +315,36 @@ func (d *Dev) makeDev(opts *Opts) error {
return nil 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 ( const (
cmdReset = 0xf0 // reset ds248x cmdReset = 0xf0 // reset ds248x
cmdSetReadPtr = 0xe1 // set the read pointer cmdSetReadPtr = 0xe1 // set the read pointer

@ -139,22 +139,6 @@ type FFCMode struct {
ExplicitCommandToOpen bool // Default: false 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. // New returns a driver for the FLIR Lepton CCI protocol.
func New(i i2c.Bus) (*Dev, error) { func New(i i2c.Bus) (*Dev, error) {
d := &Dev{ 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 { func (d *Dev) String() string {
return d.c.String() return d.c.String()
} }

@ -64,23 +64,6 @@ type Frame struct {
Metadata Metadata // Metadata that is sent along the pixels. 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. // New returns an initialized connection to the FLIR Lepton.
// //
// The CS line is manually managed by using mode spi.NoCS when calling // 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 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 { func (d *Dev) String() string {
return fmt.Sprintf("Lepton(%s/%s/%s)", d.Dev, d.s, d.cs) return fmt.Sprintf("Lepton(%s/%s/%s)", d.Dev, d.s, d.cs)
} }

@ -18,16 +18,6 @@ import (
"periph.io/x/periph/conn/ir" "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. // New returns a IR receiver / emitter handle.
func New() (*Conn, error) { func New() (*Conn, error) {
w, err := net.Dial("unix", "/var/run/lirc/lircd") w, err := net.Dial("unix", "/var/run/lirc/lircd")
@ -44,6 +34,16 @@ func New() (*Conn, error) {
return c, nil 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 { func (c *Conn) String() string {
return "lirc" return "lirc"
} }

@ -77,33 +77,6 @@ const (
UpLeft Orientation = 0x2A 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 // NewSPI returns a Dev object that communicates over SPI to a SSD1306 display
// controller. // 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) return newDev(&i2c.Dev{Bus: i, Addr: 0x3C}, w, h, rotated, false, nil)
} }
// newDev is the common initialization code that is independent of the // Dev is an open handle to the display controller.
// communication protocol (I²C or SPI) being used. type Dev struct {
func newDev(c conn.Conn, w, h int, rotated, usingSPI bool, dc gpio.PinOut) (*Dev, error) { // Communication
if w < 8 || w > 128 || w&7 != 0 { c conn.Conn
return nil, fmt.Errorf("ssd1306: invalid width %d", w) dc gpio.PinOut
} spi bool
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 { // Display size controlled by the SSD1306.
// Set COM output scan direction; C0 means normal; C8 means reversed rect image.Rectangle
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. // Mutable
// Page 64 has the full recommended flow. // See page 25 for the GDDRAM pages structure.
// Page 28 lists all the commands. // Narrow screen will waste the end of each page.
return []byte{ // Short screen will ignore the lower pages.
0xAE, // Display off // There is 8 pages, each covering an horizontal band of 8 pixels high (1
0xD3, 0x00, // Set display offset; 0 // byte) for 128 bytes.
0x40, // Start display start line; 0 // 8*128 = 1024 bytes total for 128x64 display.
columnAddr, // Set segment remap; RESET is column 127. buffer []byte
comScan, // // next is lazy initialized on first Draw(). Write() skips this buffer.
0xDA, 0x12, // Set COM pins hardware configuration; see page 40 next *image1bit.VerticalLSB
0x81, 0xFF, // Set max contrast startPage, endPage int
0xA4, // Set display to use GDDRAM content startCol, endCol int
0xA6, // Set normal display (0xA7 for inverted 0=lit, 1=dark) scrolled bool
0xD5, freq, // Set osc frequency and divide ratio; power on reset value is 0x80. halted bool
0x8D, 0x14, // Enable charge pump regulator; page 62 err error
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) String() string { 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) { func (d *Dev) calculateSubset(next []byte) (int, int, int, int, bool) {
w := d.rect.Dx() w := d.rect.Dx()
h := d.rect.Dy() h := d.rect.Dy()

@ -63,6 +63,19 @@ const (
Brightness14 Brightness = 0x8F // 14/16 PWM 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. // Dev represents an handle to a tm1637.
type Dev struct { type Dev struct {
clk gpio.PinOut clk gpio.PinOut
@ -136,19 +149,6 @@ func (d *Dev) Halt() error {
return err 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. // Page 10 states the max clock frequency is 500KHz but page 3 states 250KHz.

@ -23,6 +23,39 @@ import (
// SkipAddr can be used to skip the address from being sent. // SkipAddr can be used to skip the address from being sent.
const SkipAddr uint16 = 0xFFFF 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. // I2C represents an I²C master implemented as bit-banging on 2 GPIO pins.
type I2C struct { type I2C struct {
mu sync.Mutex mu sync.Mutex
@ -106,39 +139,6 @@ func (i *I2C) SDA() gpio.PinIO {
return i.sda 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 // "When CLK is a high level and DIO changes from high to low level, data input

@ -23,6 +23,41 @@ import (
"periph.io/x/periph/host/cpu" "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. // SPI represents a SPI master implemented as bit-banging on 3 or 4 GPIO pins.
type SPI struct { type SPI struct {
sck gpio.PinOut // Clock sck gpio.PinOut // Clock
@ -152,41 +187,6 @@ func (s *SPI) CS() gpio.PinOut {
return s.csn 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. // sleep does a busy loop to act as fast as possible.

@ -52,6 +52,35 @@ func (i TouchStatus) String() string {
return touchStatusName[touchStatusIndex[i]:touchStatusIndex[i+1]] 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. // Dev is a handle to a cap1188.
type Dev struct { type Dev struct {
c mmr.Dev8 c mmr.Dev8
@ -215,35 +244,6 @@ func (d *Dev) ClearInterrupt() error {
return nil 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) { func makeDev(c conn.Conn, isSPI bool, opts *Opts) (*Dev, error) {

@ -46,7 +46,8 @@ const (
KeyA_RN_WN_BITS_RAB_WN_KeyB_RN_WN_EXTRA = 0x07 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 type AuthStatus byte
// Card authentication status enum. // Card authentication status enum.
@ -56,8 +57,8 @@ const (
AuthFailure AuthFailure
) )
// BlocksAccess defines the access structure for first 3 blocks of the sector and the access bits for the // BlocksAccess defines the access structure for first 3 blocks of the sector
// sector trail. // and the access bits for the sector trail.
type BlocksAccess struct { type BlocksAccess struct {
B0, B1, B2 BlockAccess B0, B1, B2 BlockAccess
B3 SectorTrailerAccess 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) 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. // CalculateBlockAccess calculates the block access.
var DefaultKey = [...]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 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. // ParseBlockAccess parses the given byte array into the block access structure.
type Dev struct { func ParseBlockAccess(ad []byte) *BlocksAccess {
resetPin gpio.PinOut return &BlocksAccess{
irqPin gpio.PinIn B0: BlockAccess(((ad[1] & 0x10) >> 2) | ((ad[2] & 0x01) << 1) | ((ad[2] & 0x10) >> 5)),
operationTimeout time.Duration B1: BlockAccess(((ad[1] & 0x20) >> 3) | (ad[2] & 0x02) | ((ad[2] & 0x20) >> 5)),
spiDev spi.Conn 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. // 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 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. // 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. // timeout the duration to wait for IRQ strobe.
func (r *Dev) SetOperationtimeout(timeout time.Duration) { 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[:]) 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. // sector - the sector number to read the data from.
func (r *Dev) ReadSectorTrail(sector int) ([]byte, error) { 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)) 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 // MFRC522 SPI Dev private/helper functions
func (ba *BlocksAccess) getBits(bitNum uint) byte { func (ba *BlocksAccess) getBits(bitNum uint) byte {

@ -41,6 +41,32 @@ func NRZ(b byte) uint32 {
return out 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. // Dev is a handle to the LED strip.
type Dev struct { type Dev struct {
p gpiostream.PinOut p gpiostream.PinOut
@ -152,32 +178,6 @@ func (d *Dev) Write(pixels []byte) (int, error) {
return len(pixels), nil 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 // raster converts a RGB/RGBW input stream into a MSB binary output stream as it

Loading…
Cancel
Save