Initial commit of pio.

This was extracted from github.com/maruel/dlibox/go/pio @ f51102b.
This code was written by Marc-Antoine Ruel.
pull/1/head
Marc-Antoine Ruel 10 years ago
commit eb384b9d0c

@ -0,0 +1,6 @@
# This is the list of pio authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google Inc.

@ -0,0 +1,29 @@
# This is the official list of people who can contribute
# (and typically have contributed) code to the pio repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# https://cla.developers.google.com/
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
#
# An entry with multiple email addresses specifies that the
# first address should be used in the submit logs and
# that the other addresses should be recognized as the
# same person when interacting with Gerrit.
# Please keep the list sorted.
Marc-Antoine Ruel <maruel@chromium.org> <maruel@gmail.com>

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,139 @@
# pio - Peripherals I/O in Go
* [doc/users/](doc/users/) for ready-to-use tools.
* [doc/apps/](doc/apps/) to use `pio` as a library. The complete API
documentation, including examples, is at
[![GoDoc](https://godoc.org/github.com/google/pio?status.svg)](https://godoc.org/github.com/google/pio).
* [doc/drivers/](doc/drivers/) to expand the list of supported hardware.
## Users
pio includes [many ready-to-use tools](cmd/)! See [doc/users/](doc/users/) for
more info on configuring the host and using the included tools.
```bash
go get github.com/google/pio/cmd/...
pio-info
headers-list
```
## Application developpers
For [application developpers](doc/apps/), `pio` provides OS-independent bus
interfacing. The following gets the current temperature, barometric pressure and
relative humidity using a bme280:
```go
package main
import (
"fmt"
"log"
"github.com/google/pio/devices"
"github.com/google/pio/devices/bme280"
"github.com/google/pio/host"
)
func main() {
// Load all the drivers:
if _, err := host.Init(); err != nil {
log.Fatal(err)
}
// Open a handle to the first available I²C bus. It could be a via FT232H
// over USB or an I²C bus exposed on the host's headers, it doesn't matter.
bus, err := i2c.New(-1)
if err != nil {
log.Fatal(err)
}
defer bus.Close()
// Open a handle to a bme280 connected on the I²C bus using default settings:
dev, err := bme280.NewI2C(bus, nil)
if err != nil {
log.Fatal(err)
}
defer dev.Close()
// Read temperature from the sensor:
var env devices.Environment
if err = dev.Sense(&env); err != nil {
log.Fatal(err)
}
fmt.Printf("%8s %10s %9s\n", env.Temperature, env.Pressure, env.Humidity)
}
```
See more examples at [doc/apps/SAMPLES.md](doc/apps/SAMPLES.md)!
## Contributions
`pio` provides an extensible driver registry and common bus interfaces which are
explained in more details at [doc/drivers/](doc/drivers/). `pio` is designed to
work well with drivers living in external repositories so you are not _required_
to fork to load drivers for your platform.
We gladly accept contributions from device driver developpers via GitHub pull
requests, as long as the author has signed the Google Contributor License.
Please see [doc/drivers/CONTRIBUTING.md](doc/drivers/CONTRIBUTING.md) for more
details.
## Philosophy
1. Optimize for simplicity, correctness and usability in that order.
* e.g. everything, interfaces and structs, uses strict typing, there's no
`interface{}` in sight.
2. OS agnostic. Clear separation of interfaces in [conn/](conn/),
enablers in [host/](host) and device drivers in [devices/](devices/).
* e.g. no devfs or sysfs path in sight.
* e.g. conditional compilation enables only the relevant drivers to be loaded
on each platform.
3. ... yet doesn't get in the way of platform specific code.
* e.g. A user can use statically typed global variables
[rpi.P1_3](https://godoc.org/github.com/google/pio/host/rpi#pkg-variables),
[bcm283x.GPIO2](https://godoc.org/github.com/google/pio/host/bcm283x#Pin)
or
[bcm283x.I2C1_SDA](https://godoc.org/github.com/google/pio/host/bcm283x#pkg-variables)
to refer to the exact same pin when I²C bus #1 is enabled on a Raspberry
Pi.
3. The user can chose to optimize for performance instead of usability.
* e.g.
[apa102.Dev](https://godoc.org/github.com/google/pio/devices/apa102#Dev)
exposes both high level
[draw.Image](https://golang.org/pkg/image/draw/#Image) to draw an image and
low level [io.Writer](https://golang.org/pkg/io/#Writer) to write raw RGB
24 bits pixels. The user chooses.
4. Use a divide and conquer approach. Each component has exactly one
responsibility.
* e.g. instead of having a driver per "platform", there's a driver per
"component": one for the CPU, one for the board headers, one for each
buses and sensors, etc.
5. Extensible via a [driver
registry](https://godoc.org/github.com/google/pio#Register).
* e.g. a user can inject a custom driver to expose more pins, headers, etc.
An USB device (like an FT232H) can expose headers _in addition_ to the
headers found on the host.
6. The drivers must use the fastest possible implementation.
* e.g. both
[allwinner](https://godoc.org/github.com/google/pio/host/allwinner)
and
[bcm283x](https://godoc.org/github.com/google/pio/host/bcm283x)
leverage sysfs gpio to expose interrupt driven edge detection, yet use
memory mapped GPIO registers to single-cycle reads and writes.
## Authors
The main author is [Marc-Antoine Ruel](https://github.com/maruel). The full list
is in [AUTHORS](AUTHORS) and [CONTRIBUTORS](CONTRIBUTORS).
## Disclaimer
This is not an official Google product (experimental or otherwise), it
is just code that happens to be owned by Google.

@ -0,0 +1,306 @@
// Copyright 2016 Google Inc. 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 apa102
import (
"errors"
"image"
"image/color"
"github.com/google/pio/conn/spi"
"github.com/google/pio/devices"
"github.com/maruel/temperature"
)
// maxOut is the maximum intensity of each channel on a APA102 LED.
const maxOut = 0x1EE1
// ramp converts input from [0, 0xFF] as intensity to lightness on a scale of
// [0, maxOut] or other desired range [0, max].
//
// It tries to use the same curve independent of the scale used. max can be
// changed to change the color temperature or to limit power dissipation.
//
// It's the reverse of lightness; https://en.wikipedia.org/wiki/Lightness
func ramp(l uint8, max uint16) uint16 {
if l == 0 {
// Make sure black is black.
return 0
}
// linearCutOff defines the linear section of the curve. Inputs between
// [0, linearCutOff] are mapped linearly to the output. It is 1% of maximum
// output.
linearCutOff := uint32((max + 50) / 100)
l32 := uint32(l)
if l32 < linearCutOff {
return uint16(l32)
}
// Maps [linearCutOff, 255] to use [linearCutOff*max/255, max] using a x^3
// ramp.
// Realign input to [0, 255-linearCutOff]. It now maps to
// [0, max-linearCutOff*max/255].
//const inRange = 255
l32 -= linearCutOff
inRange := 255 - linearCutOff
outRange := uint32(max) - linearCutOff
offset := inRange >> 1
y := (l32*l32*l32 + offset) / inRange
return uint16((y*outRange+(offset*offset))/inRange/inRange + linearCutOff)
}
// lut is a lookup table that initializes itself on the fly.
type lut struct {
intensity uint8 // Set an intensity between 0 (off) and 255 (full brightness).
temperature uint16 // In Kelvin.
r [256]uint16
g [256]uint16
b [256]uint16
}
func (l *lut) init(i uint8, t uint16) {
if i != l.intensity || t != l.temperature {
l.intensity = i
l.temperature = t
tr, tg, tb := temperature.ToRGB(l.temperature)
maxR := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tr) + 127*127) / 65025)
maxG := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tg) + 127*127) / 65025)
maxB := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tb) + 127*127) / 65025)
for i := range l.r {
l.r[i] = ramp(uint8(i), maxR)
}
if maxG == maxR {
copy(l.g[:], l.r[:])
} else {
for i := range l.g {
l.g[i] = ramp(uint8(i), maxG)
}
}
if maxB == maxR {
copy(l.b[:], l.r[:])
} else if maxB == maxG {
copy(l.b[:], l.g[:])
} else {
for i := range l.b {
l.b[i] = ramp(uint8(i), maxB)
}
}
}
}
// raster serializes converts a buffer of RGB bytes to the APA102 SPI format.
//
// It is expected to be given the part where pixels are, not the header nor
// footer.
//
// dst is in APA102 SPI 32 bits word format. src is in RGB 24 bits word format.
// maxR, maxG and maxB are the maximum light intensity to use per channel.
func (l *lut) raster(dst []byte, src []byte) {
// Whichever is the shortest.
length := len(src) / 3
if o := len(dst) / 4; o < length {
length = o
}
for i := 0; i < length; i++ {
// Converts a color into the 4 bytes needed to control an APA-102 LED.
//
// The response as seen by the human eye is very non-linear. The APA-102
// provides an overall brightness PWM but it is relatively slower and
// results in human visible flicker. On the other hand the minimal color
// (1/255) is still too intense at full brightness, so for very dark color,
// it is worth using the overall brightness PWM. The goal is to use
// brightness!=31 as little as possible.
//
// Global brightness frequency is 580Hz and color frequency at 19.2kHz.
// https://cpldcpu.wordpress.com/2014/08/27/apa102/
// Both are multiplicative, so brightness@50% and color@50% means an
// effective 25% duty cycle but it is not properly distributed, which is
// the main problem.
//
// It is unclear to me if brightness is exactly in 1/31 increment as I don't
// have an oscilloscope to confirm. Same for color in 1/255 increment.
// TODO(maruel): I have one now!
//
// Each channel duty cycle ramps from 100% to 1/(31*255) == 1/7905.
//
// Computes brighness, blue, green, red.
j := 3 * i
r := l.r[src[j]]
g := l.g[src[j+1]]
b := l.b[src[j+2]]
m := r | g | b
j += i
if m <= 1023 {
if m <= 255 {
dst[j], dst[j+1], dst[j+2], dst[j+3] = byte(0xE0+1), byte(b), byte(g), byte(r)
} else if m <= 511 {
dst[j], dst[j+1], dst[j+2], dst[j+3] = byte(0xE0+2), byte(b>>1), byte(g>>1), byte(r>>1)
} else {
dst[j], dst[j+1], dst[j+2], dst[j+3] = byte(0xE0+4), byte((b+2)>>2), byte((g+2)>>2), byte((r+2)>>2)
}
} else {
// In this case we need to use a ramp of 255-1 even for lower colors.
dst[j], dst[j+1], dst[j+2], dst[j+3] = byte(0xE0+31), byte((b+15)/31), byte((g+15)/31), byte((r+15)/31)
}
}
}
// rasterImg is the generic version of raster.
func (l *lut) rasterImg(dst []byte, r image.Rectangle, src image.Image, srcR image.Rectangle) {
// Render directly into the buffer for maximum performance and to keep
// untouched sections intact.
deltaX4 := 4 * (r.Min.X - srcR.Min.X)
if img, ok := src.(*image.NRGBA); ok {
// Fast path for image.NRGBA.
pix := img.Pix[srcR.Min.Y*img.Stride:]
for sX := srcR.Min.X; sX < srcR.Max.X; sX++ {
sX4 := 4 * sX
r := l.r[pix[sX4]]
g := l.g[pix[sX4+1]]
b := l.b[pix[sX4+2]]
m := r | g | b
rX := sX4 + deltaX4
if m <= 1023 {
if m <= 255 {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+1), byte(b), byte(g), byte(r)
} else if m <= 511 {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+2), byte(b>>1), byte(g>>1), byte(r>>1)
} else {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+4), byte((b+2)>>2), byte((g+2)>>2), byte((r+2)>>2)
}
} else {
// In this case we need to use a ramp of 255-1 even for lower colors.
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+31), byte((b+15)/31), byte((g+15)/31), byte((r+15)/31)
}
}
} else {
// Generic version.
for sX := srcR.Min.X; sX < srcR.Max.X; sX++ {
r16, g16, b16, _ := src.At(sX, srcR.Min.Y).RGBA()
r := l.r[byte(r16>>8)]
g := l.g[byte(g16>>8)]
b := l.b[byte(b16>>8)]
m := r | g | b
rX := sX*4 + deltaX4
if m <= 1023 {
if m <= 255 {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+1), byte(b), byte(g), byte(r)
} else if m <= 511 {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+2), byte(b>>1), byte(g>>1), byte(r>>1)
} else {
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+4), byte((b+2)>>2), byte((g+2)>>2), byte((r+2)>>2)
}
} else {
// In this case we need to use a ramp of 255-1 even for lower colors.
dst[rX], dst[rX+1], dst[rX+2], dst[rX+3] = byte(0xE0+31), byte((b+15)/31), byte((g+15)/31), byte((r+15)/31)
}
}
}
}
// 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 bus.
// 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.
numLights int
buf []byte
}
// 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.numLights, 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.buf[4:4+4*d.numLights], r, src, srcR)
_, _ = d.s.Write(d.buf)
}
// 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 {
return 0, errLength
}
d.l.init(d.Intensity, d.Temperature)
d.l.raster(d.buf[4:4+4*d.numLights], pixels)
_, err := d.s.Write(d.buf)
return len(pixels), err
}
// New returns a strip that communicates over SPI to APA102 LEDs.
//
// The SPI bus 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(s spi.Conn, numLights int, intensity uint8, temperature uint16) (*Dev, error) {
if err := s.Configure(spi.Mode3, 8); 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*(numLights+1)+numLights/2/8+1)
tail := buf[4+4*numLights:]
for i := range tail {
tail[i] = 0xFF
}
return &Dev{
Intensity: intensity,
Temperature: temperature,
s: s,
numLights: numLights,
buf: buf,
}, nil
}
//
var errLength = errors.New("invalid RGB stream length")
var _ devices.Display = &Dev{}

@ -0,0 +1,627 @@
// Copyright 2016 Google Inc. 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 apa102
import (
"bytes"
"fmt"
"image"
"image/color"
"io/ioutil"
"log"
"testing"
"github.com/google/pio/conn/spi"
"github.com/google/pio/conn/spi/spitest"
)
func TestRamp(t *testing.T) {
// Tests a few known values.
data := []struct {
input uint8
expected uint16
}{
{0x00, 0x0000},
{0x01, 0x0001},
{0x02, 0x0002},
{0x03, 0x0003},
{0x04, 0x0004},
{0x05, 0x0005},
{0x06, 0x0006},
{0x07, 0x0007},
{0x08, 0x0008},
{0x09, 0x0009},
{0x0A, 0x000A},
{0x0B, 0x000B},
{0x0C, 0x000C},
{0x0D, 0x000D},
{0x0E, 0x000E},
{0x0F, 0x000F},
{0x10, 0x0010},
{0x11, 0x0011},
{0x12, 0x0012},
{0x13, 0x0013},
{0x14, 0x0014},
{0x15, 0x0015},
{0x16, 0x0016},
{0x17, 0x0017},
{0x18, 0x0018},
{0x19, 0x0019},
{0x1A, 0x001A},
{0x1B, 0x001B},
{0x1C, 0x001C},
{0x1D, 0x001D},
{0x1E, 0x001E},
{0x1F, 0x001F},
{0x20, 0x0020},
{0x21, 0x0021},
{0x22, 0x0022},
{0x23, 0x0023},
{0x24, 0x0024},
{0x25, 0x0025},
{0x26, 0x0026},
{0x27, 0x0027},
{0x28, 0x0028},
{0x29, 0x0029},
{0x2A, 0x002A},
{0x2B, 0x002B},
{0x2C, 0x002C},
{0x2D, 0x002D},
{0x2E, 0x002E},
{0x2F, 0x002F},
{0x30, 0x0030},
{0x31, 0x0031},
{0x32, 0x0032},
{0x33, 0x0033},
{0x34, 0x0034},
{0x35, 0x0035},
{0x36, 0x0036},
{0x37, 0x0037},
{0x38, 0x0038},
{0x39, 0x0039},
{0x3A, 0x003A},
{0x3B, 0x003B},
{0x3C, 0x003C},
{0x3D, 0x003D},
{0x3E, 0x003E},
{0x3F, 0x003F},
{0x40, 0x0040},
{0x41, 0x0041},
{0x42, 0x0042},
{0x43, 0x0043},
{0x44, 0x0044},
{0x45, 0x0045},
{0x46, 0x0046},
{0x47, 0x0047},
{0x48, 0x0048},
{0x49, 0x0049},
{0x4A, 0x004A},
{0x4B, 0x004B},
{0x4C, 0x004C},
{0x4D, 0x004D},
{0x4E, 0x004E},
{0x4F, 0x004F},
{0x50, 0x004F},
{0x51, 0x004F},
{0x52, 0x004F},
{0x53, 0x004F},
{0x54, 0x004F},
{0x55, 0x004F},
{0x56, 0x004F},
{0x57, 0x0050},
{0x58, 0x0050},
{0x59, 0x0050},
{0x5A, 0x0051},
{0x5B, 0x0051},
{0x5C, 0x0052},
{0x5D, 0x0053},
{0x5E, 0x0054},
{0x5F, 0x0055},
{0x60, 0x0056},
{0x61, 0x0057},
{0x62, 0x0059},
{0x63, 0x005A},
{0x64, 0x005C},
{0x65, 0x005E},
{0x66, 0x0060},
{0x67, 0x0063},
{0x68, 0x0065},
{0x69, 0x0068},
{0x6A, 0x006B},
{0x6B, 0x006E},
{0x6C, 0x0072},
{0x6D, 0x0075},
{0x6E, 0x0079},
{0x6F, 0x007E},
{0x70, 0x0082},
{0x71, 0x0087},
{0x72, 0x008C},
{0x73, 0x0092},
{0x74, 0x0098},
{0x75, 0x009E},
{0x76, 0x00A4},
{0x77, 0x00AB},
{0x78, 0x00B2},
{0x79, 0x00B9},
{0x7A, 0x00C1},
{0x7B, 0x00C9},
{0x7C, 0x00D2},
{0x7D, 0x00DA},
{0x7E, 0x00E4},
{0x7F, 0x00ED},
{0x80, 0x00F8},
{0x81, 0x0102},
{0x82, 0x010D},
{0x83, 0x0119},
{0x84, 0x0124},
{0x85, 0x0131},
{0x86, 0x013E},
{0x87, 0x014B},
{0x88, 0x0159},
{0x89, 0x0167},
{0x8A, 0x0176},
{0x8B, 0x0185},
{0x8C, 0x0195},
{0x8D, 0x01A5},
{0x8E, 0x01B6},
{0x8F, 0x01C7},
{0x90, 0x01D9},
{0x91, 0x01EC},
{0x92, 0x01FF},
{0x93, 0x0212},
{0x94, 0x0226},
{0x95, 0x023B},
{0x96, 0x0251},
{0x97, 0x0267},
{0x98, 0x027D},
{0x99, 0x0294},
{0x9A, 0x02AC},
{0x9B, 0x02C5},
{0x9C, 0x02DE},
{0x9D, 0x02F8},
{0x9E, 0x0312},
{0x9F, 0x032E},
{0xA0, 0x034A},
{0xA1, 0x0366},
{0xA2, 0x0384},
{0xA3, 0x03A2},
{0xA4, 0x03C0},
{0xA5, 0x03E0},
{0xA6, 0x0400},
{0xA7, 0x0421},
{0xA8, 0x0443},
{0xA9, 0x0465},
{0xAA, 0x0489},
{0xAB, 0x04AC},
{0xAC, 0x04D1},
{0xAD, 0x04F7},
{0xAE, 0x051D},
{0xAF, 0x0545},
{0xB0, 0x056D},
{0xB1, 0x0596},
{0xB2, 0x05C0},
{0xB3, 0x05EA},
{0xB4, 0x0616},
{0xB5, 0x0642},
{0xB6, 0x066F},
{0xB7, 0x069D},
{0xB8, 0x06CC},
{0xB9, 0x06FC},
{0xBA, 0x072D},
{0xBB, 0x075F},
{0xBC, 0x0792},
{0xBD, 0x07C6},
{0xBE, 0x07FA},
{0xBF, 0x0830},
{0xC0, 0x0866},
{0xC1, 0x089E},
{0xC2, 0x08D6},
{0xC3, 0x090F},
{0xC4, 0x094A},
{0xC5, 0x0985},
{0xC6, 0x09C2},
{0xC7, 0x09FF},
{0xC8, 0x0A3E},
{0xC9, 0x0A7D},
{0xCA, 0x0ABE},
{0xCB, 0x0B00},
{0xCC, 0x0B42},
{0xCD, 0x0B86},
{0xCE, 0x0BCB},
{0xCF, 0x0C11},
{0xD0, 0x0C58},
{0xD1, 0x0CA1},
{0xD2, 0x0CEA},
{0xD3, 0x0D34},
{0xD4, 0x0D80},
{0xD5, 0x0DCD},
{0xD6, 0x0E1B},
{0xD7, 0x0E6A},
{0xD8, 0x0EBA},
{0xD9, 0x0F0B},
{0xDA, 0x0F5E},
{0xDB, 0x0FB2},
{0xDC, 0x1007},
{0xDD, 0x105D},
{0xDE, 0x10B4},
{0xDF, 0x110D},
{0xE0, 0x1167},
{0xE1, 0x11C2},
{0xE2, 0x121F},
{0xE3, 0x127C},
{0xE4, 0x12DB},
{0xE5, 0x133C},
{0xE6, 0x139D},
{0xE7, 0x1400},
{0xE8, 0x1464},
{0xE9, 0x14CA},
{0xEA, 0x1530},
{0xEB, 0x1599},
{0xEC, 0x1602},
{0xED, 0x166D},
{0xEE, 0x16D9},
{0xEF, 0x1747},
{0xF0, 0x17B6},
{0xF1, 0x1826},
{0xF2, 0x1898},
{0xF3, 0x190B},
{0xF4, 0x197F},
{0xF5, 0x19F5},
{0xF6, 0x1A6D},
{0xF7, 0x1AE5},
{0xF8, 0x1B60},
{0xF9, 0x1BDB},
{0xFA, 0x1C58},
{0xFB, 0x1CD7},
{0xFC, 0x1D57},
{0xFD, 0x1DD9},
{0xFE, 0x1E5C},
{0xFF, 0x1EE1},
}
if false {
for i := 0; i <= 255; i++ {
fmt.Printf("{0x%02X, 0x%04X},\n", i, ramp(uint8(i), maxOut))
}
}
for i, line := range data {
if i != int(line.input) || line.expected != ramp(line.input, maxOut) {
t.Fail()
}
}
if 0x00 != ramp(0x00, 0xFF) {
t.Fail()
}
if 0x21 != ramp(0x7F, 0xFF) {
t.Fail()
}
if 0xFF != ramp(0xFF, 0xFF) {
t.Fail()
}
}
func TestRampMonotonic(t *testing.T) {
// Ensures the ramp is 100% monotonically increasing and without bumps.
lastValue := uint16(0)
lastDelta := uint16(0)
for in := uint32(0); in <= 255; in++ {
out := ramp(uint8(in), maxOut)
if out < lastValue {
t.Fatalf("f(%d) = %d; f(%d) = %d", in-1, lastValue, in, out)
}
if out > maxOut {
t.Fatalf("f(%d) = %d", in, out)
}
if out-lastValue+1 < lastDelta {
t.Errorf("f(%d)=%d f(%d)=%d f(%d)=%d Deltas: '%d+1 < %d' but should be '>='",
in-2, ramp(uint8(in-2), maxOut), in-1, ramp(uint8(in-1), maxOut), in, ramp(uint8(in), maxOut), out-lastValue, lastDelta)
}
lastDelta = out - lastValue
lastValue = out
}
}
func TestDevEmpty(t *testing.T) {
buf := bytes.Buffer{}
d, _ := New(spitest.NewRecordRaw(&buf), 0, 255, 6500)
if n, err := d.Write([]byte{}); n != 0 || err != nil {
t.Fatalf("%d %v", n, err)
}
if expected := []byte{0x0, 0x0, 0x0, 0x0, 0xFF}; !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("%#v != %#v", expected, buf.Bytes())
}
}
func TestDevLen(t *testing.T) {
buf := bytes.Buffer{}
d, _ := New(spitest.NewRecordRaw(&buf), 1, 255, 6500)
if n, err := d.Write([]byte{0}); n != 0 || err != errLength {
t.Fatalf("%d %v", n, err)
}
if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("%#v != %#v", expected, buf.Bytes())
}
}
func TestDev(t *testing.T) {
buf := bytes.Buffer{}
colors := []color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x00, 0x10, 0x00},
{0x00, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00},
}
d, _ := New(spitest.NewRecordRaw(&buf), len(colors), 255, 6500)
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil {
t.Fatalf("%d %v", n, err)
}
expected := []byte{
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFB, 0xFB, 0xFB,
0xFF, 0xC4, 0xC4, 0xC4,
0xE1, 0xF8, 0xF8, 0xF8,
0xE1, 0x00, 0x00, 0xF8,
0xE1, 0x00, 0xF8, 0x00,
0xE1, 0xF8, 0x00, 0x00,
0xE1, 0x10, 0x00, 0x00,
0xE1, 0x01, 0x00, 0x00,
0xE1, 0x00, 0x00, 0x00,
0xFF,
}
if !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("%#v != %#v", expected, buf.Bytes())
}
}
func TestDevColo(t *testing.T) {
if (&Dev{}).ColorModel() != color.NRGBAModel {
t.Fail()
}
}
func TestDevIntensity(t *testing.T) {
buf := bytes.Buffer{}
colors := []color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x00, 0x10, 0x00},
{0x00, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00},
}
d, _ := New(spitest.NewRecordRaw(&buf), len(colors), 127, 6500)
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil {
t.Fatalf("%d %v", n, err)
}
expected := []byte{
0x00, 0x00, 0x00, 0x00,
0xFF, 0x7F, 0x7F, 0x7F,
0xFF, 0x7D, 0x7D, 0x7D,
0xFF, 0x67, 0x67, 0x67,
0xE2, 0x9B, 0x9B, 0x9B,
0xE2, 0x00, 0x00, 0x9B,
0xE2, 0x00, 0x9B, 0x00,
0xE2, 0x9B, 0x00, 0x00,
0xE1, 0x10, 0x00, 0x00,
0xE1, 0x01, 0x00, 0x00,
0xE1, 0x00, 0x00, 0x00,
0xFF,
}
if !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("%#v != %#v", expected, buf.Bytes())
}
}
func TestDevLong(t *testing.T) {
buf := bytes.Buffer{}
colors := make([]color.NRGBA, 256)
d, _ := New(spitest.NewRecordRaw(&buf), len(colors), 255, 6500)
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil {
t.Fatalf("%d %v", n, err)
}
expected := make([]byte, 4*(256+1)+17)
for i := 0; i < 256; i++ {
expected[4+4*i] = 0xE1
}
trailer := expected[4*257:]
for i := range trailer {
trailer[i] = 0xFF
}
if !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("%#v != %#v", expected, buf.Bytes())
}
}
// Expected output for 3 test cases. Each test case use a completely different
// code path so make sure each code path results in the exact same output.
var expectedi250t5000 = []byte{
0x00, 0x00, 0x00, 0x00, 0xE1, 0x08, 0x04, 0x00, 0xE1, 0x14, 0x10, 0xC, 0xE1,
0x20, 0x1C, 0x18, 0xE1, 0x2C, 0x28, 0x24, 0xE1, 0x38, 0x34, 0x30, 0xE1, 0x3F,
0x40, 0x3C, 0xE1, 0x43, 0x46, 0x48, 0xE1, 0x54, 0x4C, 0x4E, 0xE1, 0x7B, 0x63,
0x56, 0xE1, 0xC1, 0x96, 0x73, 0xE2, 0x97, 0x78, 0x5A, 0xE2, 0xE7, 0xBF, 0x94,
0xE4, 0xAA, 0x93, 0x77, 0xE4, 0xF1, 0xD8, 0xB8, 0xFF, 0x2B, 0x27, 0x23, 0xFF,
0x39, 0x36, 0x32, 0xFF, 0xFF,
}
func TestDevTemperatureWarm(t *testing.T) {
buf := bytes.Buffer{}
pixels := make([]byte, 16*3)
for i := range pixels {
// Test all intensity code paths.
pixels[i] = uint8(i << 2)
}
d, _ := New(spitest.NewRecordRaw(&buf), len(pixels)/3, 250, 5000)
if n, err := d.Write(pixels); n != len(pixels) || err != nil {
t.Fatalf("%d %v", n, err)
}
if !bytes.Equal(expectedi250t5000, buf.Bytes()) {
t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes())
}
}
func TestDrawNRGBA(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 16, 1))
for i := 0; i < 16; i++ {
// Test all intensity code paths. Confirm that alpha is ignored.
img.Pix[4*i] = uint8((3 * i) << 2)
img.Pix[4*i+1] = uint8((3*i + 1) << 2)
img.Pix[4*i+2] = uint8((3*i + 2) << 2)
img.Pix[4*i+3] = 0
}
buf := bytes.Buffer{}
d, _ := New(spitest.NewRecordRaw(&buf), 16, 250, 5000)
d.Draw(d.Bounds(), img, image.Point{})
if !bytes.Equal(expectedi250t5000, buf.Bytes()) {
t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes())
}
}
func TestDrawRGBA(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 16, 1))
for i := 0; i < 16; i++ {
// Test all intensity code paths. Alpha is not ignored in this case.
img.Pix[4*i] = uint8((3 * i) << 2)
img.Pix[4*i+1] = uint8((3*i + 1) << 2)
img.Pix[4*i+2] = uint8((3*i + 2) << 2)
img.Pix[4*i+3] = 0xFF
}
buf := bytes.Buffer{}
d, _ := New(spitest.NewRecordRaw(&buf), 16, 250, 5000)
d.Draw(d.Bounds(), img, image.Point{})
if !bytes.Equal(expectedi250t5000, buf.Bytes()) {
t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes())
}
}
//
func Example() {
bus, err := spi.New(-1, -1)
if err != nil {
log.Fatalf("failed to open SPI: %v", err)
}
defer bus.Close()
// Opens a strip of 150 lights are 50% intensity with color temperature at
// 5000 Kelvin.
dev, err := New(bus, 150, 127, 5000)
if err != nil {
log.Fatalf("failed to open apa102: %v", err)
}
img := image.NewNRGBA(image.Rect(0, 0, dev.Bounds().Dy(), 1))
for x := 0; x < img.Rect.Max.X; x++ {
img.SetNRGBA(x, 0, color.NRGBA{uint8(x), uint8(255 - x), 0, 255})
}
dev.Draw(dev.Bounds(), img, image.Point{})
}
//
func BenchmarkWriteWhite(b *testing.B) {
pixels := make([]byte, 150*3)
for i := range pixels {
pixels[i] = 0xFF
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), len(pixels)/3, 255, 6500)
_, _ = d.Write(pixels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels)
}
}
func BenchmarkWriteDim(b *testing.B) {
pixels := make([]byte, 150*3)
for i := range pixels {
pixels[i] = 1
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), len(pixels)/3, 255, 6500)
_, _ = d.Write(pixels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels)
}
}
func BenchmarkWriteBlack(b *testing.B) {
pixels := make([]byte, 150*3)
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), len(pixels)/3, 255, 6500)
_, _ = d.Write(pixels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels)
}
}
func BenchmarkWriteColorful(b *testing.B) {
pixels := make([]byte, 150*3)
for i := range pixels {
pixels[i] = uint8(i) + uint8(i>>8)
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), len(pixels)/3, 250, 5000)
_, _ = d.Write(pixels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = d.Write(pixels)
}
}
func BenchmarkDrawNRGBAColorful(b *testing.B) {
// Takes the fast path.
img := image.NewNRGBA(image.Rect(0, 0, 150, 1))
for i := range img.Pix {
img.Pix[i] = uint8(i) + uint8(i>>8)
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), img.Bounds().Max.X, 250, 5000)
r := d.Bounds()
p := image.Point{}
d.Draw(r, img, p)
b.ResetTimer()
for i := 0; i < b.N; i++ {
d.Draw(r, img, p)
}
}
func BenchmarkDrawRGBAColorful(b *testing.B) {
// Takes the slow path.
img := image.NewRGBA(image.Rect(0, 0, 256, 1))
for i := range img.Pix {
img.Pix[i] = uint8(i) + uint8(i>>8)
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), img.Bounds().Max.X, 250, 5000)
r := d.Bounds()
p := image.Point{}
d.Draw(r, img, p)
b.ResetTimer()
for i := 0; i < b.N; i++ {
d.Draw(r, img, p)
}
}
func BenchmarkWriteColorfulVariation(b *testing.B) {
// Continuously vary the lookup tables.
pixels := make([]byte, 256*3)
for i := range pixels {
pixels[i] = uint8(i) + uint8(i>>8)
}
d, _ := New(spitest.NewRecordRaw(ioutil.Discard), len(pixels)/3, 250, 5000)
_, _ = d.Write(pixels)
b.ResetTimer()
for i := 0; i < b.N; i++ {
d.Intensity = uint8(i)
d.Temperature = uint16((3000 + i) & 0x1FFF)
_, _ = d.Write(pixels)
}
}

@ -0,0 +1,13 @@
// Copyright 2016 Google Inc. 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 apa102 drives a strip of APA102 LEDs connected on a SPI bus.
//
// It handles color intensity and temperature correction and uses the full
// 8000:1 dynamic range as supported by the device.
//
// Datasheet
//
// https://cpldcpu.files.wordpress.com/2014/08/apa-102c-super-led-specifications-2014-en.pdf
package apa102

@ -0,0 +1,394 @@
// Copyright 2016 Google Inc. 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 bme280 controls a Bosch BME280 device over I²C.
//
// Datasheet
//
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
package bme280
import (
"errors"
"github.com/google/pio/conn"
"github.com/google/pio/conn/i2c"
"github.com/google/pio/conn/spi"
"github.com/google/pio/devices"
)
// Oversampling affects how much time is taken to measure each of temperature,
// pressure and humidity.
//
// Temperature must be measured for pressure and humidity to be measured. The
// duration is approximatively:
// duration_in_ms = 1 + 2*temp + 2*press+0.5 + 2*humidy+0.5
//
// Using high oversampling and low standby results in highest power
// consumption, but this is still below 1mA so we generally don't care.
type Oversampling uint8
// Possible oversampling values.
const (
No Oversampling = 0
O1x Oversampling = 1
O2x Oversampling = 2
O4x Oversampling = 3
O8x Oversampling = 4
O16x Oversampling = 5
)
// Standby is the time the BME280 waits idle between measurements. This reduces
// power consumption when the host won't read the values as fast as the
// measurements are done.
type Standby uint8
// Possible standby values, these determines the refresh rate.
const (
S500us Standby = 0
S10ms Standby = 6
S20ms Standby = 7
S62ms Standby = 1
S125ms Standby = 2
S250ms Standby = 3
S500ms Standby = 4
S1s Standby = 5
)
// Filter specifies the internal IIR filter to get steady measurements without
// using oversampling. This is mainly used to reduce power consumption.
type Filter uint8
// Possible filtering values.
const (
FOff Filter = 0
F2 Filter = 1
F4 Filter = 2
F8 Filter = 3
F16 Filter = 4
)
// Dev is an handle to a bme280.
type Dev struct {
d conn.Conn
isSPI bool
c calibration
}
// Sense returns measurements as °C, kPa and % of relative humidity.
func (d *Dev) Sense(env *devices.Environment) error {
// All registers must be read in a single pass, as noted at page 21, section
// 4.1.
// Pressure: 0xF7~0xF9
// Temperature: 0xFA~0xFC
// Humidity: 0xFD~0xFE
buf := [0xFF - 0xF7]byte{}
if err := d.readReg(0xF7, buf[:]); err != nil {
return err
}
// These values are 20 bits as per doc.
pRaw := int32(buf[0])<<12 | int32(buf[1])<<4 | int32(buf[2])>>4
tRaw := int32(buf[3])<<12 | int32(buf[4])<<4 | int32(buf[5])>>4
// This value is 16 bits as per doc.
hRaw := int32(buf[6])<<8 | int32(buf[7])
t, tFine := d.c.compensateTempInt(tRaw)
env.Temperature = devices.Celcius(t * 10)
p := d.c.compensatePressureInt64(pRaw, tFine)
env.Pressure = devices.KPascal((int32(p) + 127) / 256)
h := d.c.compensateHumidityInt(hRaw, tFine)
env.Humidity = devices.RelativeHumidity((int32(h)*100 + 511) / 1024)
return nil
}
// Stop stops the bme280 from acquiring measurements. It is recommended to call
// to reduce idle power usage.
func (d *Dev) Stop() error {
// Page 27 (for register) and 12~13 section 3.3.
return d.writeCommands([]byte{0xF4, byte(sleep)})
}
// Opts is optional options to pass to the constructor.
//
// Recommended (and default) values are O4x for oversampling, S20ms for standby
// and FOff for filter if planing to call frequently, else use S500ms to get a
// bit more than one reading per second.
//
// BUG(maruel): Remove the Standby flag and replace with a
// WaitForNextSample(time.Duration). Then use the closest value automatically.
type Opts struct {
Temperature Oversampling
Pressure Oversampling
Humidity Oversampling
Standby Standby
Filter Filter
}
// NewI2C returns an object that communicates over I²C to BME280 environmental
// sensor.
//
// It is recommended to call Stop() when done with the device so it stops
// sampling.
func NewI2C(i i2c.Conn, opts *Opts) (*Dev, error) {
d := &Dev{d: &i2c.Dev{Conn: i, Addr: 0x76}, isSPI: false}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
// NewSPI returns an object that communicates over SPI to BME280 environmental
// sensor.
//
// Recommended values are O4x for oversampling, S20ms for standby and FOff for
// filter if planing to call frequently, else use S500ms to get a bit more than
// one reading per second.
//
// It is recommended to call Stop() when done with the device so it stops
// sampling.
//
// When using SPI, the CS line must be used.
//
// BUG(maruel): This code was not tested yet, still waiting for a SPI enabled
// device in the mail.
func NewSPI(s spi.Conn, opts *Opts) (*Dev, error) {
// It works both in Mode0 and Mode3.
if err := s.Configure(spi.Mode3, 8); err != nil {
return nil, err
}
d := &Dev{d: s, isSPI: true}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
//
// mode is stored in config
type mode byte
const (
sleep mode = 0 // no operation, all registers accessible, lowest power, selected after startup
forced mode = 1 // perform one measurement, store results and return to sleep mode
normal mode = 3 // perpetual cycling of measurements and inactive periods
)
type status byte
const (
measuring status = 8 // set when conversion is running
imUpdate status = 1 // set when NVM data are being copied to image registers
)
var defaults = Opts{
Temperature: O4x,
Pressure: O4x,
Humidity: O4x,
Standby: S20ms,
Filter: FOff,
}
func (d *Dev) makeDev(opts *Opts) error {
if opts == nil {
opts = &defaults
}
config := []byte{
// ctrl_meas; put it to sleep otherwise the config update may be ignored.
0xF4, byte(opts.Temperature)<<5 | byte(opts.Pressure)<<2 | byte(sleep),
// ctrl_hum
0xF2, byte(opts.Humidity),
// config
0xF5, byte(opts.Standby)<<5 | byte(opts.Filter)<<2,
// ctrl_meas
0xF4, byte(opts.Temperature)<<5 | byte(opts.Pressure)<<2 | byte(normal),
}
// The device starts in 2ms as per datasheet. No need to wait for boot to be
// finished.
var chipID [1]byte
// Read register 0xD0 to read the chip id.
if err := d.readReg(0xD0, chipID[:]); err != nil {
return err
}
if chipID[0] != 0x60 {
return errors.New("unexpected chip id; is this a BME280?")
}
// Read calibration data t1~3, p1~9, 8bits padding, h1.
var tph [0xA2 - 0x88]byte
if err := d.readReg(0x88, tph[:]); err != nil {
return err
}
// Read calibration data h2~6
var h [0xE8 - 0xE1]byte
if err := d.readReg(0xE1, h[:]); err != nil {
return err
}
if err := d.writeCommands(config[:]); err != nil {
return err
}
d.c.t1 = uint16(tph[0]) | uint16(tph[1])<<8
d.c.t2 = int16(tph[2]) | int16(tph[3])<<8
d.c.t3 = int16(tph[4]) | int16(tph[5])<<8
d.c.p1 = uint16(tph[6]) | uint16(tph[7])<<8
d.c.p2 = int16(tph[8]) | int16(tph[9])<<8
d.c.p3 = int16(tph[10]) | int16(tph[11])<<8
d.c.p4 = int16(tph[12]) | int16(tph[13])<<8
d.c.p5 = int16(tph[14]) | int16(tph[15])<<8
d.c.p6 = int16(tph[16]) | int16(tph[17])<<8
d.c.p7 = int16(tph[18]) | int16(tph[19])<<8
d.c.p8 = int16(tph[20]) | int16(tph[21])<<8
d.c.p9 = int16(tph[22]) | int16(tph[23])<<8
d.c.h1 = uint8(tph[25])
d.c.h2 = int16(h[0]) | int16(h[1])<<8
d.c.h3 = uint8(h[2])
d.c.h4 = int16(h[3])<<4 | int16(h[4])&0xF
d.c.h5 = int16(h[4])>>4 | int16(h[5])<<4
d.c.h6 = int8(h[6])
return nil
}
func (d *Dev) readReg(reg uint8, b []byte) error {
// Page 32-33
if d.isSPI {
read := make([]byte, len(b)+1)
write := make([]byte, len(read))
write[0] = reg
if err := d.d.Tx(write, read); err != nil {
return err
}
copy(b, read[:1])
}
return d.d.Tx([]byte{reg}, b)
}
// writeCommands writes a command to the bme280.
//
// Warning: b may be modified!
func (d *Dev) writeCommands(b []byte) error {
if d.isSPI {
// Page 33; set RW bit 7 to 0.
for i := 0; i < len(b); i += 2 {
b[i] &^= 0x80
}
}
_, err := d.d.Write(b)
return err
}
// Register table:
// 0x00..0x87 --
// 0x88..0xA1 Calibration data
// 0xA2..0xCF --
// 0xD0 Chip id; reads as 0x60
// 0xD1..0xDF --
// 0xE0 Reset by writing 0xB6 to it
// 0xE1..0xF0 Calibration data
// 0xF1 --
// 0xF2 ctrl_hum; ctrl_meas must be writen to after for change to this register to take effect
// 0xF3 status
// 0xF4 ctrl_meas
// 0xF5 config
// 0xF6 --
// 0xF7 press_msb
// 0xF8 press_lsb
// 0xF9 press_xlsb
// 0xFA temp_msb
// 0xFB temp_lsb
// 0xFC temp_xlsb
// 0xFD hum_msb
// 0xFE hum_lsb
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
// Page 23
type calibration struct {
t1 uint16
t2, t3 int16
p1 uint16
p2, p3, p4, p5, p6, p7, p8, p9 int16
h2 int16 // Reordered for packing
h1, h3 uint8
h4, h5 int16
h6 int8
}
// Pages 23-24
// compensateTempInt returns temperature in °C, resolution is 0.01 °C.
// Output value of 5123 equals 51.23 C.
//
// raw has 20 bits of resolution.
func (c *calibration) compensateTempInt(raw int32) (int32, int32) {
x := ((raw>>3 - int32(c.t1)<<1) * int32(c.t2)) >> 11
y := ((((raw>>4 - int32(c.t1)) * (raw>>4 - int32(c.t1))) >> 12) * int32(c.t3)) >> 14
tFine := x + y
return (tFine*5 + 128) >> 8, tFine
}
// compensatePressureInt64 returns pressure in Pa in Q24.8 format (24 integer
// bits and 8 fractional bits). Output value of 24674867 represents
// 24674867/256 = 96386.2 Pa = 963.862 hPa.
//
// raw has 20 bits of resolution.
func (c *calibration) compensatePressureInt64(raw, tFine int32) uint32 {
x := int64(tFine) - 128000
y := x * x * int64(c.p6)
y += (x * int64(c.p5)) << 17
y += int64(c.p4) << 35
x = (x*x*int64(c.p3))>>8 + ((x * int64(c.p2)) << 12)
x = ((int64(1)<<47 + x) * int64(c.p1)) >> 33
if x == 0 {
return 0
}
p := ((((1048576 - int64(raw)) << 31) - y) * 3125) / x
x = (int64(c.p9) * (p >> 13) * (p >> 13)) >> 25
y = (int64(c.p8) * p) >> 19
return uint32(((p + x + y) >> 8) + (int64(c.p7) << 4))
}
// compensateHumidityInt returns humidity in %RH in Q22.10 format (22 integer
// and 10 fractional bits). Output value of 47445 represents 47445/1024 =
// 46.333%
//
// raw has 16 bits of resolution.
func (c *calibration) compensateHumidityInt(raw, tFine int32) uint32 {
x := tFine - 76800
/*
Yes, someone wrote the following in the datasheet unironically:
v_x1_u32r = (((((adc_H << 14) (((BME280_S32_t)dig_H4) << 20)
(((BME280_S32_t)dig_H5) * v_x1_u32r)) + ((BME280_S32_t)16384)) >> 15) *
(((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r *
((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) +
((BME280_S32_t)2097152)) * ((BME280_S32_t)dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
*/
// Here's a more "readable" version:
x1 := raw<<14 - int32(c.h4)<<20 - int32(c.h5)*x
x2 := (x1 + 16384) >> 15
x3 := (x * int32(c.h6)) >> 10
x4 := (x * int32(c.h3)) >> 11
x5 := (x3 * (x4 + 32768)) >> 10
x6 := ((x5+2097152)*int32(c.h2) + 8192) >> 14
x = x2 * x6
x = x - ((((x>>15)*(x>>15))>>7)*int32(c.h1))>>4
if x < 0 {
return 0
}
if x > 419430400 {
return 419430400 >> 12
}
return uint32(x >> 12)
}
var _ devices.Environmental = &Dev{}

@ -0,0 +1,250 @@
// Copyright 2016 Google Inc. 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 bme280
import (
"fmt"
"log"
"testing"
"github.com/google/pio/conn/i2c"
"github.com/google/pio/conn/i2c/i2ctest"
"github.com/google/pio/devices"
)
// Real data extracted from a device.
var calib = calibration{
t1: 28176,
t2: 26220,
t3: 350,
p1: 38237,
p2: -10824,
p3: 3024,
p4: 7799,
p5: -99,
p6: -7,
p7: 9900,
p8: -10230,
p9: 4285,
h2: 366, // Note they are inversed for bit packing.
h1: 75,
h3: 0,
h4: 309,
h5: 0,
h6: 30,
}
func TestRead(t *testing.T) {
// This data was generated with "bme280 -r"
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}, Read: []byte{0x60}},
// Calibration data.
{
Addr: 0x76,
Write: []byte{0x88},
Read: []byte{0x10, 0x6e, 0x6c, 0x66, 0x32, 0x0, 0x5d, 0x95, 0xb8, 0xd5, 0xd0, 0xb, 0x77, 0x1e, 0x9d, 0xff, 0xf9, 0xff, 0xac, 0x26, 0xa, 0xd8, 0xbd, 0x10, 0x0, 0x4b},
},
// Calibration data.
{Addr: 0x76, Write: []byte{0xe1}, Read: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration.
{Addr: 0x76, Write: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xe0, 0xf4, 0x6f}, Read: nil},
// Read.
{Addr: 0x76, Write: []byte{0xf7}, Read: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0, 0x7a, 0x76}},
},
}
dev, err := NewI2C(&bus, nil)
if err != nil {
t.Fatal(err)
}
env := devices.Environment{}
if err := dev.Sense(&env); err != nil {
t.Fatalf("Sense(): %v", err)
}
if env.Temperature != 23720 {
t.Fatalf("temp %d", env.Temperature)
}
if env.Pressure != 100943 {
t.Fatalf("pressure %d", env.Pressure)
}
if env.Humidity != 6531 {
t.Fatalf("humidity %d", env.Humidity)
}
}
func TestCalibrationFloat(t *testing.T) {
// Real data extracted from measurements from this device.
tRaw := int32(524112)
pRaw := int32(309104)
hRaw := int32(30987)
// Compare the values with the 3 algorithms.
temp, tFine := calib.compensateTempFloat(tRaw)
pres := calib.compensatePressureFloat(pRaw, tFine)
humi := calib.compensateHumidityFloat(hRaw, tFine)
if tFine != 117494 {
t.Fatalf("tFine %d", tFine)
}
if !floatEqual(temp, 22.948120) {
// 22.95°C
t.Fatalf("temp %f", temp)
}
if !floatEqual(pres, 100.046074) {
// 100.046kPa
t.Fatalf("pressure %f", pres)
}
if !floatEqual(humi, 63.167889) {
// 63.17%
t.Fatalf("humidity %f", humi)
}
}
func TestCalibrationInt(t *testing.T) {
// Real data extracted from measurements from this device.
tRaw := int32(524112)
pRaw := int32(309104)
hRaw := int32(30987)
temp, tFine := calib.compensateTempInt(tRaw)
pres64 := calib.compensatePressureInt64(pRaw, tFine)
pres32 := calib.compensatePressureInt32(pRaw, tFine)
humi := calib.compensateHumidityInt(hRaw, tFine)
if tFine != 117407 {
t.Fatalf("tFine %d", tFine)
}
if temp != 2293 {
// 2293/100 = 22.93°C
// Delta is <0.02°C which is pretty good.
t.Fatalf("temp %d", temp)
}
if pres64 != 25611063 {
// 25611063/256/1000 = 100.043214844
// Delta is 3Pa which is ok.
t.Fatalf("pressure64 %d", pres64)
}
if pres32 != 100045 {
// 100045/1000 = 100.045kPa
// Delta is 1Pa which is pretty good.
t.Fatalf("pressure32 %d", pres32)
}
if humi != 64686 {
// 64686/1024 = 63.17%
// Delta is <0.01% which is pretty good.
t.Fatalf("humidity %d", humi)
}
}
//
func Example() {
bus, err := i2c.New(-1)
if err != nil {
log.Fatalf("failed to open I²C: %v", err)
}
defer bus.Close()
dev, err := NewI2C(bus, nil)
if err != nil {
log.Fatalf("failed to initialize bme280: %v", err)
}
env := devices.Environment{}
if err := dev.Sense(&env); err != nil {
log.Fatal(err)
}
fmt.Printf("%8s %10s %9s\n", env.Temperature, env.Pressure, env.Humidity)
}
//
var epsilon float32 = 0.00000001
func floatEqual(a, b float32) bool {
return (a-b) < epsilon && (b-a) < epsilon
}
// Page 50
// compensatePressureInt32 returns pressure in Pa. Output value of "96386"
// equals 96386 Pa = 963.86 hPa
//
// "Compensating the pressure value with 32 bit integer has an accuracy of
// typically 1 Pa"
//
// raw has 20 bits of resolution.
//
// BUG(maruel): Output is incorrect.
func (c *calibration) compensatePressureInt32(raw, tFine int32) uint32 {
x := tFine>>1 - 64000
y := (((x >> 2) * (x >> 2)) >> 11) * int32(c.p6)
y += (x * int32(c.p5)) << 1
y = y>>2 + int32(c.p4)<<16
x = (((int32(c.p3) * (((x >> 2) * (x >> 2)) >> 13)) >> 3) + ((int32(c.p2) * x) >> 1)) >> 18
x = ((32768 + x) * int32(c.p1)) >> 15
if x == 0 {
return 0
}
p := ((uint32(int32(1048576)-raw) - uint32(y>>12)) * 3125)
if p < 0x80000000 {
p = (p << 1) / uint32(x)
} else {
p = (p / uint32(x)) * 2
}
x = (int32(c.p9) * int32(((p>>3)*(p>>3))>>13)) >> 12
y = (int32(p>>2) * int32(c.p8)) >> 13
return uint32(int32(p) + ((x + y + int32(c.p7)) >> 4))
}
var _ devices.Environmental = &Dev{}
// Page 49
// compensateTempFloat returns temperature in °C. Output value of "51.23"
// equals 51.23 °C.
//
// raw has 20 bits of resolution.
func (c *calibration) compensateTempFloat(raw int32) (float32, int32) {
x := (float64(raw)/16384. - float64(c.t1)/1024.) * float64(c.t2)
y := (float64(raw)/131072. - float64(c.t1)/8192.) * float64(c.t3)
tFine := int32(x + y)
return float32((x + y) / 5120.), tFine
}
// compensateHumidityFloat returns pressure in Pa. Output value of "96386.2"
// equals 96386.2 Pa = 963.862 hPa.
//
// raw has 20 bits of resolution.
func (c *calibration) compensatePressureFloat(raw, tFine int32) float32 {
x := float64(tFine)*0.5 - 64000.
y := x * x * float64(c.p6) / 32768.
y += x * float64(c.p5) * 2.
y = y*0.25 + float64(c.p4)*65536.
x = (float64(c.p3)*x*x/524288. + float64(c.p2)*x) / 524288.
x = (1. + x/32768.) * float64(c.p1)
if x <= 0 {
return 0
}
p := float64(1048576 - raw)
p = (p - y/4096.) * 6250. / x
x = float64(c.p9) * p * p / 2147483648.
y = p * float64(c.p8) / 32768.
return float32(p+(x+y+float64(c.p7))/16.) / 1000.
}
// compensateHumidityFloat returns humidity in %rH. Output value of "46.332"
// represents 46.332 %rH.
//
// raw has 16 bits of resolution.
func (c *calibration) compensateHumidityFloat(raw, tFine int32) float32 {
h := float64(tFine - 76800)
h = (float64(raw) - float64(c.h4)*64. + float64(c.h5)/16384.*h) * float64(c.h2) / 65536. * (1. + float64(c.h6)/67108864.*h*(1.+float64(c.h3)/67108864.*h))
h *= 1. - float64(c.h1)*h/524288.
if h > 100. {
return 100.
}
if h < 0. {
return 0.
}
return float32(h)
}

@ -0,0 +1,129 @@
// Copyright 2016 Google Inc. 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 devices
import (
"fmt"
"image"
"image/color"
"io"
)
// Display represents a pixel output device. It is a write-only interface.
//
// What Display represents can be as varied as a 1 bit OLED display or a strip
// of LED lights.
type Display interface {
// Writer can be used when the native display pixel format is known. Each
// write must cover exactly the whole screen as a single packed stream of
// pixels.
io.Writer
// ColorModel returns the device native color model.
//
// It is generally color.NRGBA for a color display.
ColorModel() color.Model
// Bounds returns the size of the output device.
//
// Generally displays should have Min at {0, 0} but this is not guaranteed in
// multiple displays setup or when an instance of this interface represents a
// section of a larger logical display.
Bounds() image.Rectangle
// Draw updates the display with this image starting at 'sp' offset into the
// display into 'r'. The code will likely be faster if the image is in the
// display's native color format.
//
// To be compatible with draw.Drawer, this function doesn't return an error.
Draw(r image.Rectangle, src image.Image, sp image.Point)
}
// Milli is a fixed point value with 0.001 precision.
type Milli int32
// Float64 returns the value as float64 with 0.001 precision.
func (m Milli) Float64() float64 {
return float64(m) * .001
}
// String returns the value formatted as a string.
func (m Milli) String() string {
return fmt.Sprintf("%d.%03d", m/1000, m%1000)
}
// Celcius is a temperature at a precision of 0.001°C.
//
// Expected range is [-273150, >1000000]
//
// BUG(maruel): Add function to convert to Fahrenheit for my American friends.
type Celcius Milli
// Float64 returns the value as float64 with 0.001 precision.
func (c Celcius) Float64() float64 {
return Milli(c).Float64()
}
// String returns the temperature formatted as a string.
func (c Celcius) String() string {
return Milli(c).String() + "°C"
}
// ToF returns the temperature as Fahrenheit, a unit used in the United States.
func (c Celcius) ToF() Fahrenheit {
return Fahrenheit((c*9+2)/5 + 32000)
}
// Fahrenheit is a unit used in the United States.
type Fahrenheit Milli
// Float64 returns the value as float64 with 0.001 precision.
func (f Fahrenheit) Float64() float64 {
return Milli(f).Float64()
}
// String returns the temperature formatted as a string.
func (f Fahrenheit) String() string {
return Milli(f).String() + "°F"
}
// KPascal is pressure at precision of 1Pa.
//
// Expected range is [0, >1000000].
type KPascal Milli
// Float64 returns the value as float64 with 0.001 precision.
func (k KPascal) Float64() float64 {
return Milli(k).Float64()
}
// String returns the pressure formatted as a string.
func (k KPascal) String() string {
return Milli(k).String() + "KPa"
}
// RelativeHumidity is humidity level in %rH with 0.01%rH precision.
type RelativeHumidity int32
// Float64 returns the value in %.
func (r RelativeHumidity) Float64() float64 {
return float64(r) * .01
}
// String returns the humidity formatted as a string.
func (r RelativeHumidity) String() string {
return fmt.Sprintf("%d.%02d%%rH", r/100, r%100)
}
// Environment represents measurements from an environmental sensor.
type Environment struct {
Temperature Celcius
Pressure KPascal
Humidity RelativeHumidity
}
// Environmental represents an environmental sensor.
type Environmental interface {
// Sense returns the value read from the sensor. Unsupported metrics are not
// modified.
Sense(env *Environment) error
}

@ -0,0 +1,60 @@
// Copyright 2016 Google Inc. 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 devices
import "testing"
func TestMilli(t *testing.T) {
o := Milli(10010)
if s := o.String(); s != "10.010" {
t.Fatalf("%#v", s)
}
if f := o.Float64(); f > 10.011 || f < 10.009 {
t.Fatalf("%f", f)
}
}
func TestCelcius(t *testing.T) {
o := Celcius(10010)
if s := o.String(); s != "10.010°C" {
t.Fatalf("%#v", s)
}
if f := o.Float64(); f > 10.011 || f < 10.009 {
t.Fatalf("%f", f)
}
if f := o.ToF(); f != 50018 {
t.Fatalf("%d", f)
}
}
func TestFahrenheit(t *testing.T) {
o := Fahrenheit(10010)
if s := o.String(); s != "10.010°F" {
t.Fatalf("%#v", s)
}
if f := o.Float64(); f > 10.011 || f < 10.009 {
t.Fatalf("%f", f)
}
}
func TestKPascal(t *testing.T) {
o := KPascal(10010)
if s := o.String(); s != "10.010KPa" {
t.Fatalf("%#v", s)
}
if f := o.Float64(); f > 10.011 || f < 10.009 {
t.Fatalf("%f", f)
}
}
func TestRelativeHumidity(t *testing.T) {
o := RelativeHumidity(5010)
if s := o.String(); s != "50.10%rH" {
t.Fatalf("%#v", s)
}
if f := o.Float64(); f > 50.11 || f < 50.09 {
t.Fatalf("%f", f)
}
}

@ -0,0 +1,45 @@
// Copyright 2016 Google Inc. 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 devicestest
import (
"errors"
"image"
"image/color"
"image/draw"
"github.com/google/pio/devices"
)
// Display is a fake devices.Display.
type Display struct {
Img *image.NRGBA
}
// Write implements devices.Display.
func (d *Display) Write(pixels []byte) (int, error) {
if len(pixels)%3 != 0 {
return 0, errors.New("invalid RGB stream length")
}
copy(d.Img.Pix, pixels)
return len(pixels), nil
}
// ColorModel implements image.Image.
func (d *Display) ColorModel() color.Model {
return d.Img.ColorModel()
}
// Bounds implements image.Image.
func (d *Display) Bounds() image.Rectangle {
return d.Img.Bounds()
}
// Draw implements draw.Image.
func (d *Display) Draw(r image.Rectangle, src image.Image, sp image.Point) {
draw.Draw(d.Img, r, src, sp, draw.Src)
}
var _ devices.Display = &Display{}

@ -0,0 +1,7 @@
// Copyright 2016 Google Inc. 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 devicestest contains non-hardware devices implementations for
// testing or emulation purpose.
package devicestest

@ -0,0 +1,11 @@
// Copyright 2016 Google Inc. 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 devices contains interfaces for classes of devices.
//
// Subpackages contain the concrete implementations. Devices accept bus
// interface, constructors return concrete type.
//
// Subpackage devicestest contains fake implementations for testing.
package devices

@ -0,0 +1,48 @@
// Copyright 2016 Google Inc. 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 lirc implements InfraRed receiver support through native linux app
// lirc.
//
// Configuration
//
// lircd MUST be configured via TWO files: /etc/lirc/hardware.conf and
// /etc/lirc/lircd.conf.
//
// See http://www.lirc.org/ for more details about daemon configuration.
//
// /etc/lirc/hardware.conf
//
// This file contains the interaction between the lircd process and the kernel
// driver, if any. This is the link between the physical signal and decoding
// pulses.
//
// /etc/lirc/lircd.conf
//
// This file contains all the known IR codes for the remotes you plan to use
// and convert into key codes. This means you need to "train" lircd with the
// remotes you plan to use.
//
// Keys are listed at
// http://www.lirc.org/api-docs/html/input__map_8inc_source.html
//
// Debugging
//
// Here's a quick recipe to train a remote:
//
// # Detect your remote
// irrecord -a -d /var/run/lirc/lircd ~/lircd.conf
// # Grep for key names you found to find the remote in the remotes library
// grep -R '<hex value>' /usr/share/lirc/remotes/
// # Listen and send command to the server
// nc -U /var/run/lirc/lircd
// # List all valid key names
// irrecord -l
// grep -hoER '(BTN|KEY)_\w+' /usr/share/lirc/remotes | sort | uniq | less
//
// Raspbian
//
// Please see documentation of package pio/host/rpi for details on how to set
// it up.
package lirc

@ -0,0 +1,275 @@
// Copyright 2016 Google Inc. 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 lirc
import (
"bufio"
"fmt"
"io/ioutil"
"log"
"net"
"strconv"
"strings"
"sync"
"github.com/google/pio"
"github.com/google/pio/conn/gpio"
"github.com/google/pio/conn/ir"
)
// Conn is an open port to lirc.
type Conn struct {
w net.Conn
c chan ir.Message
lock 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")
if err != nil {
return nil, err
}
c := &Conn{w: w, c: make(chan ir.Message), list: map[string][]string{}}
// Inconditionally retrieve the list of all known keys at start.
if _, err := w.Write([]byte("LIST\n")); err != nil {
w.Close()
return nil, err
}
go c.loop(bufio.NewReader(w))
return c, nil
}
// Close closes the socket to lirc. It is not a requirement to close before
// process termination.
func (c *Conn) Close() error {
return c.w.Close()
}
// Emit implements ir.IR.
func (c *Conn) Emit(remote string, key ir.Key) error {
// http://www.lirc.org/html/lircd.html#lbAH
_, err := fmt.Fprintf(c.w, "SEND_ONCE %s %s", remote, key)
return err
}
// Channel implements ir.IR.
func (c *Conn) Channel() <-chan ir.Message {
return c.c
}
// Codes returns all the known codes.
//
// Empty if the list was not retrieved yet.
func (c *Conn) Codes() map[string][]string {
c.lock.Lock()
defer c.lock.Unlock()
return c.list
}
//
func (c *Conn) loop(r *bufio.Reader) {
defer func() {
close(c.c)
c.c = nil
}()
for {
line, err := read(r)
if line == "BEGIN" {
err = c.readData(r)
} else if len(line) != 0 {
// Format is: <code> <repeat count> <button name> <remote control name>
// http://www.lirc.org/html/lircd.html#lbAG
if parts := strings.SplitN(line, " ", 5); len(parts) != 4 {
log.Printf("ir: corrupted line: %v", line)
} else {
if i, err2 := strconv.Atoi(parts[1]); err2 != nil {
log.Printf("ir: corrupted line: %v", line)
} else if len(parts[2]) != 0 && len(parts[3]) != 0 {
c.c <- ir.Message{Key: ir.Key(parts[2]), RemoteType: parts[3], Repeat: i != 0}
}
}
}
if err != nil {
break
}
}
}
func (c *Conn) readData(r *bufio.Reader) error {
// Format is:
// BEGIN
// <original command>
// SUCCESS
// DATA
// <number of entries 1 based>
// <entries>
// ...
// END
cmd, err := read(r)
if err != nil {
return err
}
switch cmd {
case "SIGHUP":
_, err = c.w.Write([]byte("LIST\n"))
default:
// In case of any error, ignore the rest.
line, err := read(r)
if err != nil {
return err
}
if line != "SUCCESS" {
log.Printf("ir: unexpected line: %v, expected SUCCESS", line)
return nil
}
if line, err = read(r); err != nil {
return err
}
if line != "DATA" {
log.Printf("ir: unexpected line: %v, expected DATA", line)
return nil
}
if line, err = read(r); err != nil {
return err
}
nbLines, err := strconv.Atoi(line)
if err != nil {
return err
}
list := make([]string, nbLines)
for i := 0; i < nbLines; i++ {
if list[i], err = read(r); err != nil {
return err
}
}
switch {
case cmd == "LIST":
// Request the codes for each remote.
c.pendingList = map[string][]string{}
for _, l := range list {
if _, ok := c.pendingList[l]; ok {
log.Printf("ir: unexpected %s", cmd)
} else {
c.pendingList[l] = []string{}
if _, err = fmt.Fprintf(c.w, "LIST %s\n", l); err != nil {
return err
}
}
}
case strings.HasPrefix(line, "LIST "):
if c.pendingList == nil {
log.Printf("ir: unexpected %s", cmd)
} else {
remote := cmd[5:]
c.pendingList[remote] = list
all := true
for _, v := range c.pendingList {
if len(v) == 0 {
all = false
break
}
}
if all {
c.lock.Lock()
c.list = c.pendingList
c.pendingList = nil
c.lock.Unlock()
}
}
default:
}
}
line, err := read(r)
if err != nil {
return err
}
if line != "END" {
log.Printf("ir: unexpected line: %v, expected END", line)
}
return nil
}
func read(r *bufio.Reader) (string, error) {
raw, err := r.ReadBytes('\n')
if err != nil {
return "", err
}
if len(raw) != 0 {
raw = raw[:len(raw)-1]
}
return string(raw), nil
}
// driver implements pio.Driver.
type driver struct {
}
func (d *driver) String() string {
return "lirc"
}
func (d *driver) Type() pio.Type {
// Return the lowest priority, which is Functional.
return pio.Functional
}
func (d *driver) Init() (bool, error) {
in, out := getPins()
if in == -1 && out == -1 {
return false, nil
}
if in != -1 {
if pin := gpio.ByNumber(in); pin != nil {
gpio.MapFunction("IR_IN", pin)
} else {
gpio.MapFunction("IR_IN", gpio.INVALID)
}
} else {
gpio.MapFunction("IR_IN", gpio.INVALID)
}
if out != -1 {
if pin := gpio.ByNumber(out); pin != nil {
gpio.MapFunction("IR_OUT", pin)
} else {
gpio.MapFunction("IR_OUT", gpio.INVALID)
}
} else {
gpio.MapFunction("IR_OUT", gpio.INVALID)
}
return true, nil
}
// getPins queries the kernel module to determine which GPIO pins are taken by
// the driver.
//
// The return values can be converted to bcm238x.Pin. Return (-1, -1) on
// failure.
func getPins() (int, int) {
// This is configured in /boot/config.txt as:
// dtoverlay=gpio_in_pin=23,gpio_out_pin=22
bytes, err := ioutil.ReadFile("/sys/module/lirc_rpi/parameters/gpio_in_pin")
if err != nil || len(bytes) == 0 {
return -1, -1
}
in, err := strconv.Atoi(strings.TrimRight(string(bytes), "\n"))
if err != nil {
return -1, -1
}
bytes, err = ioutil.ReadFile("/sys/module/lirc_rpi/parameters/gpio_out_pin")
if err != nil || len(bytes) == 0 {
return -1, -1
}
out, err := strconv.Atoi(strings.TrimRight(string(bytes), "\n"))
if err != nil {
return -1, -1
}
return in, out
}
var _ ir.Conn = &Conn{}

@ -0,0 +1,145 @@
// Copyright 2016 Google Inc. 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 image1bit implements black and white (1 bit per pixel) 2D graphics
// in the memory format of the ssd1306 controller.
//
// It is compatible with package image/draw.
package image1bit
import (
"errors"
"image"
"image/color"
"image/draw"
)
// Bit implements a 1 bit color.
type Bit bool
// RGBA returns either all white or all black and transparent.
func (b Bit) RGBA() (uint32, uint32, uint32, uint32) {
if b {
return 65535, 65535, 65535, 65535
}
return 0, 0, 0, 0
}
func (b Bit) String() string {
if b {
return "On"
}
return "Off"
}
// Possible bitness.
const (
On = Bit(true)
Off = Bit(false)
)
// Image is a 1 bit (black and white) image.
//
// The packing used is unusual, each byte is 8 vertical pixels, with each byte
// stride being an horizontal band of 8 pixels high.
//
// It is designed specifically to work with SSD1306 OLED display controler.
type Image struct {
W int
H int
Buf []byte // Can be passed directly to ssd1306.(*Dev).Write()
}
// New returns an initialized Image instance.
func New(r image.Rectangle) (*Image, error) {
h := r.Dy()
w := r.Dx()
if h&7 != 0 {
return nil, errors.New("height must be multiple of 8")
}
return &Image{w, h, make([]byte, w*h/8)}, nil
}
// SetAll sets all pixels to On.
func (i *Image) SetAll() {
for j := range i.Buf {
i.Buf[j] = 0xFF
}
}
// Clear sets all pixels to Off.
func (i *Image) Clear() {
for j := range i.Buf {
i.Buf[j] = 0
}
}
// Inverse changes all On pixels to Off and Off pixels to On.
func (i *Image) Inverse() {
for j := range i.Buf {
i.Buf[j] ^= 0xFF
}
}
// ColorModel implements image.Image.
func (i *Image) ColorModel() color.Model {
return color.ModelFunc(convert)
}
// Bounds implements image.Image.
func (i *Image) Bounds() image.Rectangle {
return image.Rectangle{Max: image.Point{X: i.W, Y: i.H}}
}
// At implements image.Image.
func (i *Image) At(x, y int) color.Color {
return i.AtBit(x, y)
}
// AtBit is the optimized version of At().
func (i *Image) AtBit(x, y int) Bit {
offset := x + y/8*i.W
mask := byte(1 << byte(y&7))
return Bit(i.Buf[offset]&mask != 0)
}
// Set implements draw.Image
func (i *Image) Set(x, y int, c color.Color) {
i.SetBit(x, y, convertBit(c))
}
// SetBit is the optimized version of Set().
func (i *Image) SetBit(x, y int, b Bit) {
if x >= 0 && x < i.W {
if y >= 0 && y < i.H {
offset := x + y/8*i.W
mask := byte(1 << byte(y&7))
if b {
i.Buf[offset] |= mask
} else {
i.Buf[offset] &^= mask
}
}
}
}
//
var _ draw.Image = &Image{}
// Anything not transparent and not pure black is white.
func convert(c color.Color) color.Color {
return convertBit(c)
}
// Anything not transparent and not pure black is white.
func convertBit(c color.Color) Bit {
switch t := c.(type) {
case Bit:
return t
default:
r, g, b, _ := c.RGBA()
return Bit((r | g | b) >= 0x8000)
}
}

@ -0,0 +1,72 @@
// Copyright 2016 Google Inc. 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 image1bit
import (
"bytes"
"image"
"image/color"
"testing"
)
func TestBit(t *testing.T) {
if r, g, b, a := On.RGBA(); r != 65535 || g != r || b != r || a != r {
t.Fail()
}
if r, g, b, a := Off.RGBA(); r != 0 || g != r || b != r || a != r {
t.Fail()
}
if On.String() != "On" || Off.String() != "Off" {
t.Fail()
}
if On != convertBit(On) {
t.Fail()
}
}
func TestImageNew(t *testing.T) {
if img, err := New(image.Rect(0, 0, 8, 7)); img != nil || err == nil {
t.Fail()
}
if img, err := New(image.Rect(0, 0, 1, 8)); img == nil || err != nil {
t.Fail()
}
}
func TestImagePixels(t *testing.T) {
img, _ := New(image.Rect(0, 0, 1, 8))
if !bytes.Equal(img.Buf, []byte{0x00}) {
t.Fatal("starts black")
}
img.SetAll()
if !bytes.Equal(img.Buf, []byte{0xFF}) {
t.Fatal("SetAll sets white")
}
img.Clear()
if !bytes.Equal(img.Buf, []byte{0x00}) {
t.Fatal("Clear sets black")
}
img.Set(0, 2, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
img.Set(1, 2, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
img.Inverse()
if !bytes.Equal(img.Buf, []byte{0xFB}) {
t.Fatalf("inverse %# v", img.Buf)
}
if img.At(0, 2).(Bit) != Off {
t.Fail()
}
if r := img.Bounds(); r.Min.X != 0 || r.Min.Y != 0 || r.Max.X != 1 || r.Max.Y != 8 {
t.Fail()
}
}
func TestColorModel(t *testing.T) {
img, _ := New(image.Rect(0, 0, 1, 8))
if v := img.ColorModel().Convert(color.NRGBA{0x80, 0x80, 0x80, 0xFF}).(Bit); v != On {
t.Fatalf("%s", v)
}
if v := img.ColorModel().Convert(color.NRGBA{0x7F, 0x7F, 0x7F, 0xFF}).(Bit); v != Off {
t.Fatalf("%s", v)
}
}

@ -0,0 +1,294 @@
// Copyright 2016 Google Inc. 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 ssd1306 controls a 128x64 monochrome OLED display via a ssd1306
// controler.
//
// The SSD1306 is a write-only device. It can be driven on either I²C or SPI.
// Changing between protocol is likely done through resistor soldering, for
// boards that support both.
//
// Datasheet
//
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
package ssd1306
// Some have SPI enabled;
// https://hallard.me/adafruit-oled-display-driver-for-pi/
// https://learn.adafruit.com/ssd1306-oled-displays-with-raspberry-pi-and-beaglebone-black?view=all
import (
"errors"
"image"
"image/color"
"io"
"log"
"github.com/google/pio/conn/i2c"
"github.com/google/pio/conn/spi"
"github.com/google/pio/devices"
"github.com/google/pio/devices/ssd1306/image1bit"
)
// FrameRate determines scrolling speed.
type FrameRate byte
// Possible frame rates.
const (
FrameRate2 FrameRate = 7
FrameRate3 FrameRate = 4
FrameRate4 FrameRate = 5
FrameRate5 FrameRate = 0
FrameRate25 FrameRate = 6
FrameRate64 FrameRate = 1
FrameRate128 FrameRate = 2
FrameRate256 FrameRate = 3
)
// Orientation is used for scrolling.
type Orientation byte
// Possible orientations for scrolling.
const (
Left Orientation = 0x27
Right Orientation = 0x26
UpRight Orientation = 0x29
UpLeft Orientation = 0x2A
)
// Dev is an open handle to the display controler.
type Dev struct {
w io.Writer
W int
H int
}
// NewSPI returns a Dev object that communicates over SPI to SSD1306 display
// controler.
//
// If rotated, turns the display by 180°
//
// It's up to the caller to use the RES (reset) pin if desired. Simpler
// connection is to connect RES and DC to ground, CS to 3.3v, SDA to MOSI, SCK
// to SCLK.
//
// As per datasheet, maximum clock speed is 1/100ns = 10MHz.
func NewSPI(s spi.Conn, w, h int, rotated bool) (*Dev, error) {
if err := s.Configure(spi.Mode3, 8); err != nil {
return nil, err
}
return newDev(s, w, h, rotated)
}
// NewI2C returns a Dev object that communicates over I²C to SSD1306 display
// controler.
//
// If rotated, turns the display by 180°
//
// As per datasheet, maximum clock speed is 1/2.5µs = 400KHz. It's worth
// bumping up from default bus speed of 100KHz if possible.
func NewI2C(i i2c.Conn, w, h int, rotated bool) (*Dev, error) {
return newDev(&i2c.Dev{Conn: i, Addr: 0x3C}, w, h, rotated)
}
// newDev is the common initialization code that is independent of the bus
// being used.
func newDev(dev io.Writer, w, h int, rotated bool) (*Dev, error) {
if w&7 != 0 || h&7 != 0 {
return nil, errors.New("height and width must be multiple of 8")
}
if w < 8 || w > 128 {
return nil, errors.New("invalid height")
}
if h < 8 || h > 64 {
return nil, errors.New("invalid width")
}
d := &Dev{w: dev, W: w, H: h}
contrast := byte(0x7F) // (default value)
// 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)
}
// Initialize the device by fully reseting all values.
// https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
// Page 64 has the full recommended flow.
// Page 28 lists all the commands.
// BUG(maruel): This flow may not recover a controller in a complete
// corrupted state. Figure out a more resilient startup init code.
init := []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, contrast, // Set contrast control
0xA4, // Set display to use GDDRAM content
0xA6, // Set normal display (0xA7 for reversed bitness i.e. bit set is black) (?)
0xD5, 0x40, // Set osc frequency and divide ratio; power on reset value is 0x3F.
0x8D, 0x14, // Enable charge pump regulator; page 62
// Not sure
0xD9, 0xF1, // Set pre-charge period.
//0xDB, 0x40, // Set Vcomh deselect level; page 32
0x20, 0x00, // Set memory addressing mode to horizontal (can be page, horizontal or vertical)
0x2E, // Deactivate scroll
0x00 | 0x00, // Set column offset (lower nibble)
0x10 | 0x00, // Set column offset (higher nibble)
0xA8, byte(d.H - 1), // Set multiplex ratio (number of lines to display)
0xAF, // Display on
}
if _, err := d.w.Write(init); err != nil {
return nil, err
}
return d, nil
}
// ColorModel implements devices.Display. It is a one bit color model.
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.W, Y: d.H}}
}
func colorToBit(c color.Color) byte {
r, g, b, a := c.RGBA()
if (r|g|b) >= 0x8000 && a >= 0x4000 {
return 1
}
return 0
}
// Draw implements devices.Display.
//
// BUG(maruel): It discards any failure. Change devices.Display interface?
// BUG(maruel): Support r.Min.Y and r.Max.Y not divisible by 8.
// BUG(maruel): Support sp.Y not divisible by 8.
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
}
// Take 8 lines at a time.
deltaX := r.Min.X - srcR.Min.X
deltaY := r.Min.Y - srcR.Min.Y
var pixels []byte
if img, ok := src.(*image1bit.Image); ok {
if srcR.Min.X == 0 && srcR.Dx() == d.W && srcR.Min.Y == 0 && srcR.Dy() == d.H {
// Fast path.
pixels = img.Buf
}
}
if pixels == nil {
pixels = make([]byte, d.W*d.H/8)
for sY := srcR.Min.Y; sY < srcR.Max.Y; sY += 8 {
rY := ((sY + deltaY) / 8) * d.W
for sX := srcR.Min.X; sX < srcR.Max.X; sX++ {
rX := sX + deltaX
c0 := colorToBit(src.At(sX, sY))
c1 := colorToBit(src.At(sX, sY+1)) << 1
c2 := colorToBit(src.At(sX, sY+2)) << 2
c3 := colorToBit(src.At(sX, sY+3)) << 3
c4 := colorToBit(src.At(sX, sY+4)) << 4
c5 := colorToBit(src.At(sX, sY+5)) << 5
c6 := colorToBit(src.At(sX, sY+6)) << 6
c7 := colorToBit(src.At(sX, sY+7)) << 7
pixels[rX+rY] = c0 | c1 | c2 | c3 | c4 | c5 | c6 | c7
}
}
}
if _, err := d.Write(pixels); err != nil {
log.Printf("ssd1306: Draw failed: %v", err)
}
}
// Write writes a buffer of pixels to the display.
//
// The format is unsual as each byte represent 8 vertical pixels at a time. So
// the memory is effectively horizontal bands of 8 pixels high.
func (d *Dev) Write(pixels []byte) (int, error) {
if len(pixels) != d.H*d.W/8 {
return 0, errors.New("invalid pixel stream")
}
// Run as 2 big transactions to reduce downtime on the bus. Doing with one
// transaction doesn't work (?)
hdr := []byte{
0x21, 0x00, byte(d.W - 1), // Set column address (Width)
0x22, 0x00, byte(d.H/8 - 1), // Set page address (Pages)
}
if _, err := d.w.Write(hdr); err != nil {
return 0, err
}
if _, err := d.w.Write(append([]byte{0x40}, pixels...)); err != nil {
return 0, err
}
return len(pixels), nil
}
// Scroll scrolls the entire.
func (d *Dev) Scroll(o Orientation, rate FrameRate) error {
// TODO(maruel): Allow to specify page.
// TODO(maruel): Allow to specify offset.
if o == Left || o == Right {
// page 28
// STOP, <op>, dummy, <start page>, <rate>, <end page>, <dummy>, <dummy>, <ENABLE>
_, err := d.w.Write([]byte{0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x00, 0xFF, 0x2F})
return err
}
// page 29
// STOP, <op>, dummy, <start page>, <rate>, <end page>, <offset>, <ENABLE>
// page 30: 0xA3 permits to set rows for scroll area.
_, err := d.w.Write([]byte{0x2E, byte(o), 0x00, 0x00, byte(rate), 0x07, 0x01, 0x2F})
return err
}
// StopScroll stops any scrolling previously set.
//
// It will only take effect after redrawing the ram.
//
// BUG(maruel): Doesn't work.
func (d *Dev) StopScroll() error {
_, err := d.w.Write([]byte{0x2E})
return err
}
// SetContrast changes the screen contrast.
//
// BUG(maruel): Doesn't work.
func (d *Dev) SetContrast(level byte) error {
_, err := d.w.Write([]byte{0x81, level})
return err
}
// Enable or disable the display.
//
// BUG(maruel): Doesn't work.
func (d *Dev) Enable(on bool) error {
b := byte(0xAE)
if on {
b = 0xAF
}
_, err := d.w.Write([]byte{b})
return err
}
var _ devices.Display = &Dev{}

@ -0,0 +1,192 @@
// Copyright 2016 Google Inc. 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 ssd1306
import (
"image"
"image/color"
"log"
"testing"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
"github.com/google/pio/conn/i2c"
"github.com/google/pio/conn/i2c/i2ctest"
"github.com/google/pio/devices/ssd1306/image1bit"
)
func TestDrawGray(t *testing.T) {
// ssd1306 is a write-only device. This is simply a playback test to exercise
// the code a little.
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Startup initialization.
{Addr: 0x3c, Write: initline},
// Preparation to draw.
{Addr: 0x3c, Write: []byte{0x21, 0x0, 0x7f, 0x22, 0x0, 0x7}},
// Actual draw buffer.
{Addr: 0x3c, Write: grayCheckboardWrite},
},
}
// To record, use the following instead:
//bus := i2ctest.Record{}
dev, err := NewI2C(&bus, 128, 64, false)
if err != nil {
t.Fatal(err)
}
dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{0, 0})
if err := bus.Close(); err != nil {
t.Fatal(err)
}
//pretty.Printf("%# v\n", bus)
}
func TestDraw1D(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
{Addr: 0x3c, Write: initline},
{Addr: 0x3c, Write: []byte{0x21, 0x0, 0x7f, 0x22, 0x0, 0x7}},
{Addr: 0x3c, Write: grayCheckboardWrite},
},
}
dev, err := NewI2C(&bus, 128, 64, false)
if err != nil {
t.Fatal(err)
}
bounds := dev.Bounds()
gray := makeGrayCheckboard(bounds)
img, err := image1bit.New(bounds)
if err != nil {
t.Fatal(err)
}
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
img.Set(x, y, gray.At(x, y))
}
}
dev.Draw(dev.Bounds(), img, image.Point{0, 0})
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
//
func Example() {
bus, err := i2c.New(-1)
if err != nil {
log.Fatalf("failed to open I²C: %v", err)
}
defer bus.Close()
dev, err := NewI2C(bus, 128, 64, false)
if err != nil {
log.Fatalf("failed to initialize ssd1306: %v", err)
}
// Draw on it.
f := basicfont.Face7x13
img, err := image1bit.New(dev.Bounds())
if err != nil {
log.Fatal(err)
}
drawer := font.Drawer{
Dst: img,
Src: &image.Uniform{image1bit.On},
Face: f,
Dot: fixed.P(0, img.Bounds().Dy()-1-f.Descent),
}
drawer.DrawString("Hello from pio!")
dev.Draw(dev.Bounds(), img, image.Point{})
}
//
func makeGrayCheckboard(r image.Rectangle) image.Image {
img := image.NewGray(r)
c := color.Gray{255}
for y := r.Min.Y; y < r.Max.Y; y++ {
for x := r.Min.X; x < r.Max.X; x++ {
if (y+x)&1 != 0 {
img.SetGray(x, y, c)
}
}
}
return img
}
var initline = []byte{
0xae, 0xd3, 0x00, 0x40, 0xa1, 0xc8, 0xda, 0x12, 0x81, 0x7f, 0xa4, 0xa6, 0xd5,
0x40, 0x8d, 0x14, 0xd9, 0xf1, 0x20, 0x00, 0x2e, 0x00, 0x10, 0xa8, 0x3f, 0xaf,
}
var grayCheckboardWrite = []byte{
0x40,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55,
}

@ -0,0 +1,188 @@
// Copyright 2016 Google Inc. 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 tm1637 controls a TM1637 device over GPIO pins.
//
// Datasheet
//
// http://olimex.cl/website_MCI/static/documents/Datasheet_TM1637.pdf
package tm1637
import (
"errors"
"runtime"
"time"
"github.com/google/pio/conn/gpio"
"github.com/google/pio/host"
)
// Clock converts time to a slice of bytes as segments.
func Clock(hour, minute int, showDots bool) []byte {
seg := make([]byte, 4)
seg[0] = byte(digitToSegment[hour/10])
seg[1] = byte(digitToSegment[hour%10])
seg[2] = byte(digitToSegment[minute/10])
seg[3] = byte(digitToSegment[minute%10])
if showDots {
seg[1] |= 0x80
}
return seg[:]
}
// Digits converts hex numbers to a slice of bytes as segments.
//
// Numbers outside the range [0, 15] are displayed as blank. Use -1 to mark it
// as blank.
func Digits(n ...int) []byte {
seg := make([]byte, len(n))
for i := range n {
if n[i] >= 0 && n[i] < 16 {
seg[i] = byte(digitToSegment[n[i]])
}
}
return seg
}
// Brightness defines the screen brightness as controlled by the internal PWM.
type Brightness uint8
// Valid brightness values.
const (
Off Brightness = 0x80 // Completely off.
Brightness1 Brightness = 0x88 // 1/16 PWM
Brightness2 Brightness = 0x89 // 2/16 PWM
Brightness4 Brightness = 0x8A // 4/16 PWM
Brightness10 Brightness = 0x8B // 10/16 PWM
Brightness11 Brightness = 0x8C // 11/16 PWM
Brightness12 Brightness = 0x8D // 12/16 PWM
Brightness13 Brightness = 0x8E // 13/16 PWM
Brightness14 Brightness = 0x8F // 14/16 PWM
)
// Dev represents an handle to a tm1637.
type Dev struct {
clk gpio.PinOut
data gpio.PinIO
}
// SetBrightness changes the brightness and/or turns the display on and off.
func (d *Dev) SetBrightness(b Brightness) error {
// This helps reduce jitter a little.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
d.start()
d.writeByte(byte(b))
d.stop()
return nil
}
// Write writes raw segments, while implementing io.Writer.
//
// P can be a dot or ':' following a digit. Otherwise it is likely
// disconnected. Each byte is encoded as PGFEDCBA.
//
// -A-
// F B
// -G-
// E C
// -D- P
func (d *Dev) Write(seg []byte) (int, error) {
if len(seg) > 6 {
return 0, errors.New("tm1637: up to 6 segment groups are supported")
}
// This helps reduce jitter a little.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Use auto-incrementing address. It is possible to write to a single
// segment but there isn't much point.
d.start()
d.writeByte(0x40)
d.stop()
d.start()
d.writeByte(0xC0)
for i := 0; i < 6; i++ {
if len(seg) <= i {
d.writeByte(0)
} else {
d.writeByte(seg[i])
}
}
d.stop()
return len(seg), nil
}
// 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.
//
// Writing the complete display is 8 bytes, totalizing 9*8+2 = 74 cycles.
// At 250KHz, this is 296µs.
const clockHalfCycle = time.Second / 250000 / 2
// Hex digits from 0 to F.
var digitToSegment = []byte{
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71,
}
func (d *Dev) start() {
d.data.Out(gpio.Low)
d.sleepHalfCycle()
d.clk.Out(gpio.Low)
}
func (d *Dev) stop() {
d.sleepHalfCycle()
d.clk.Out(gpio.High)
d.sleepHalfCycle()
d.data.Out(gpio.High)
d.sleepHalfCycle()
}
// writeByte starts with d.data low and d.clk high and ends with d.data low and
// d.clk high.
func (d *Dev) writeByte(b byte) (bool, error) {
for i := 0; i < 8; i++ {
// LSB (!)
d.data.Out(b&(1<<byte(i)) != 0)
d.sleepHalfCycle()
d.clk.Out(gpio.High)
d.sleepHalfCycle()
d.clk.Out(gpio.Low)
}
// 9th clock is ACK.
d.data.Out(gpio.Low)
time.Sleep(clockHalfCycle)
// TODO(maruel): Add.
//if err := d.data.In(gpio.Up, gpio.None); err != nil {
// return false, err
//}
d.clk.Out(gpio.High)
d.sleepHalfCycle()
//ack := d.data.Read() == gpio.Low
//d.sleepHalfCycle()
//if err := d.data.Out(); err != nil {
// return false, err
//}
d.clk.Out(gpio.Low)
return true, nil
}
// sleep does a busy loop to act as fast as possible.
func (d *Dev) sleepHalfCycle() {
host.Nanospin(clockHalfCycle)
}

@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. 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 tm1637
import (
"log"
"testing"
"github.com/google/pio/conn/gpio"
"github.com/google/pio/conn/gpio/gpiotest"
"github.com/google/pio/host"
)
func TestNew(t *testing.T) {
var clk, data gpiotest.Pin
dev, err := New(&clk, &data)
if err != nil {
t.Fatalf("failed to initialize tm1637: %v", err)
}
if _, err := dev.Write(Clock(12, 00, true)); err != nil {
log.Fatalf("failed to write to tm1637: %v", err)
}
// TODO(maruel): Check the state of the pins. That's hard since it has to
// emulate the quasi-I²C protocol.
}
func Example() {
if _, err := host.Init(); err != nil {
log.Fatalf("failed to initialize pio: %v", err)
}
dev, err := New(gpio.ByNumber(6), gpio.ByNumber(12))
if err != nil {
log.Fatalf("failed to initialize tm1637: %v", err)
}
if err := dev.SetBrightness(Brightness10); err != nil {
log.Fatalf("failed to set brightness on tm1637: %v", err)
}
if _, err := dev.Write(Clock(12, 00, true)); err != nil {
log.Fatalf("failed to write to tm1637: %v", err)
}
}

@ -0,0 +1,5 @@
# experimental/devices
You are welcome to send PR (pull request) to add experimental device drivers
here. Please follow the instructions in
[CONTRIBUTING.md](../../doc/drivers/CONTRIBUTING.md).

@ -0,0 +1,8 @@
// Copyright 2016 Google Inc. 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 bitbang implements conn by banging on the bits (GPIO pins).
//
// This is not efficient but works around broken or missing drivers.
package bitbang

@ -0,0 +1,257 @@
// Copyright 2016 Google Inc. 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.
// Specification
//
// http://www.nxp.com/documents/user_manual/UM10204.pdf
package bitbang
import (
"errors"
"fmt"
"runtime"
"sync"
"time"
"github.com/google/pio/conn/gpio"
"github.com/google/pio/conn/i2c"
"github.com/google/pio/host"
)
// Use SkipAddr to skip the address from being sent.
const SkipAddr uint16 = 0xFFFF
// I2C represents an I²C master implemented as bit-banging on 2 GPIO pins.
type I2C struct {
lock sync.Mutex
scl gpio.PinIO // Clock line
sda gpio.PinIO // Data line
halfCycle time.Duration
}
func (i *I2C) String() string {
return fmt.Sprintf("bitbang/i2c(%s, %s)", i.scl, i.sda)
}
// Close implements i2c.ConnCloser.
func (i *I2C) Close() error {
return nil
}
// Tx implements i2c.Conn.
func (i *I2C) Tx(addr uint16, w, r []byte) error {
i.lock.Lock()
defer i.lock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
//syscall.Setpriority(which, who, prio)
i.start()
defer i.stop()
if addr != SkipAddr {
if addr > 0xFF {
// Page 15, section 3.1.11 10-bit addressing
// TOOD(maruel): Implement if desired; prefix 0b11110xx.
return errors.New("invalid address")
}
// Page 13, section 3.1.10 The slave address and R/W bit
addr <<= 1
if len(r) == 0 {
addr |= 1
}
ack, err := i.writeByte(byte(addr))
if err != nil {
return err
}
if !ack {
return errors.New("i2c: got NACK")
}
}
for _, b := range w {
ack, err := i.writeByte(b)
if err != nil {
return err
}
if !ack {
return errors.New("i2c: got NACK")
}
}
for x := range r {
var err error
r[x], err = i.readByte()
if err != nil {
return err
}
}
return nil
}
// Speed implements i2c.Conn.
func (i *I2C) Speed(hz int64) error {
i.lock.Lock()
defer i.lock.Unlock()
i.halfCycle = time.Second / time.Duration(hz) / time.Duration(2)
return nil
}
// SCL implements i2c.Pins.
func (i *I2C) SCL() gpio.PinIO {
return i.scl
}
// SDA implements i2c.Pins.
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.Up, gpio.None); 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.Up, gpio.None); 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
// starts."
//
// Ends with SDA and SCL low.
//
// Lasts 1/2 cycle.
func (i *I2C) start() {
// Page 9, section 3.1.4 START and STOP conditions
// In multi-master mode, it would have to sense SDA first and after the sleep.
i.sda.Out(gpio.Low)
i.sleepHalfCycle()
i.scl.Out(gpio.Low)
}
// "When CLK is a high level and DIO changes from low level to high level, data
// input ends."
//
// Lasts 3/2 cycle.
func (i *I2C) stop() {
// Page 9, section 3.1.4 START and STOP conditions
i.scl.Out(gpio.Low)
i.sleepHalfCycle()
i.scl.Out(gpio.High)
i.sleepHalfCycle()
i.sda.Out(gpio.High)
// TODO(maruel): This sleep could be skipped, assuming we wait for the next
// transfer if too quick to happen.
i.sleepHalfCycle()
}
// writeByte writes 8 bits then waits for ACK.
//
// Expects SDA and SCL low.
//
// Ends with SDA low and SCL high.
//
// Lasts 9 cycles.
func (i *I2C) writeByte(b byte) (bool, error) {
// Page 9, section 3.1.3 Data validity
// "The data on te SDA line must be stable during the high period of the
// clock."
// Page 10, section 3.1.5 Byte format
for x := 0; x < 8; x++ {
i.sda.Out(b&byte(1<<byte(7-x)) != 0)
i.sleepHalfCycle()
// Let the device read SDA.
// TODO(maruel): Support clock stretching, the device may keep the line low.
i.scl.Out(gpio.High)
i.sleepHalfCycle()
i.scl.Out(gpio.Low)
}
// Page 10, section 3.1.6 ACK and NACK
// 9th clock is ACK.
i.sleepHalfCycle()
// SCL was already set as pull-up. PullNoChange
if err := i.scl.In(gpio.Up, gpio.None); err != nil {
return false, err
}
// SDA was already set as pull-up.
if err := i.sda.In(gpio.Up, gpio.None); err != nil {
return false, err
}
// Implement clock stretching, the device may keep the line low.
for i.scl.Read() == gpio.Low {
i.sleepHalfCycle()
}
// ACK == Low.
ack := i.sda.Read() == gpio.Low
if err := i.scl.Out(gpio.Low); err != nil {
return false, err
}
if err := i.sda.Out(gpio.Low); err != nil {
return false, err
}
return ack, nil
}
// readByte reads 8 bits and an ACK.
//
// Expects SDA and SCL low.
//
// Ends with SDA low and SCL high.
//
// Lasts 9 cycles.
func (i *I2C) readByte() (byte, error) {
var b byte
if err := i.sda.In(gpio.Up, gpio.None); err != nil {
return b, err
}
for x := 0; x < 8; x++ {
i.sleepHalfCycle()
// TODO(maruel): Support clock stretching, the device may keep the line low.
i.scl.Out(gpio.High)
i.sleepHalfCycle()
if i.sda.Read() == gpio.High {
b |= byte(1) << byte(7-x)
}
i.scl.Out(gpio.Low)
}
if err := i.sda.Out(gpio.Low); err != nil {
return 0, err
}
i.sleepHalfCycle()
i.scl.Out(gpio.High)
i.sleepHalfCycle()
return b, nil
}
// sleep does a busy loop to act as fast as possible.
func (i *I2C) sleepHalfCycle() {
host.Nanospin(i.halfCycle)
}
var _ i2c.Conn = &I2C{}

@ -0,0 +1,169 @@
// Copyright 2016 Google Inc. 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.
// Specification
//
// Motorola never published a proper specification.
// http://electronics.stackexchange.com/questions/30096/spi-specifications
// http://www.nxp.com/files/microcontrollers/doc/data_sheet/M68HC11E.pdf page 120
// http://www.st.com/content/ccc/resource/technical/document/technical_note/58/17/ad/50/fa/c9/48/07/DM00054618.pdf/files/DM00054618.pdf/jcr:content/translations/en.DM00054618.pdf
package bitbang
import (
"errors"
"fmt"
"sync"
"time"
"github.com/google/pio/conn/gpio"
"github.com/google/pio/conn/spi"
"github.com/google/pio/host"
)
// SPI represents a SPI master implemented as bit-banging on 3 or 4 GPIO pins.
type SPI struct {
sck gpio.PinOut // Clock
sdi gpio.PinIn // MISO
sdo gpio.PinOut // MOSI
csn gpio.PinOut // CS
lock sync.Mutex
mode spi.Mode
bits int
halfCycle time.Duration
}
func (s *SPI) String() string {
return fmt.Sprintf("bitbang/spi(%s, %s, %s, %s)", s.sck, s.sdi, s.sdo, s.csn)
}
// Close implements spi.ConnCloser.
func (s *SPI) Close() error {
return nil
}
// Speed implements spi.Conn.
func (s *SPI) Speed(hz int64) error {
s.lock.Lock()
defer s.lock.Unlock()
s.halfCycle = time.Second / time.Duration(hz) / time.Duration(2)
return nil
}
// Configure implements spi.Conn.
func (s *SPI) Configure(mode spi.Mode, bits int) error {
s.lock.Lock()
defer s.lock.Unlock()
if mode != spi.Mode3 {
return errors.New("not implemented")
}
s.mode = mode
s.bits = bits
return nil
}
// Tx implements spi.Conn.
//
// BUG(maruel): Implement mode.
// BUG(maruel): Implement bits.
// BUG(maruel): Test if read works.
func (s *SPI) Tx(w, r []byte) error {
if len(r) != 0 && len(w) != len(r) {
return errors.New("write and read buffers must be the same length")
}
s.lock.Lock()
defer s.lock.Unlock()
if s.csn != nil {
s.csn.Out(gpio.Low)
s.sleepHalfCycle()
}
for i := uint(0); i < uint(len(w)*8); i++ {
s.sdo.Out(w[i/8]&(1<<(i%8)) != 0)
s.sleepHalfCycle()
s.sck.Out(gpio.Low)
s.sleepHalfCycle()
if len(r) != 0 {
if s.sdi.Read() == gpio.High {
r[i/8] |= 1 << (i % 8)
}
}
s.sck.Out(gpio.Low)
}
if s.csn != nil {
s.csn.Out(gpio.High)
}
return nil
}
// Write implements spi.Conn.
func (s *SPI) Write(d []byte) (int, error) {
if err := s.Tx(d, nil); err != nil {
return 0, err
}
return len(d), nil
}
// CLK implements spi.Pins.
func (s *SPI) CLK() gpio.PinOut {
return s.sck
}
// MOSI implements spi.Pins.
func (s *SPI) MOSI() gpio.PinOut {
return s.sdo
}
// MISO implements spi.Pins.
func (s *SPI) MISO() gpio.PinIn {
return s.sdi
}
// CS implements spi.Pins.
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.Up, gpio.None); 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.
func (s *SPI) sleepHalfCycle() {
host.Nanospin(s.halfCycle)
}
var _ spi.Conn = &SPI{}

@ -0,0 +1,75 @@
// Copyright 2016 Google Inc. 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 piblaster implements interfacing code is piblaster.
//
// See https://github.com/sarfata/pi-blaster for more details. This package
// relies on pi-blaster being installed and enabled.
//
// TODO(maruel): "dtoverlay=pwm" or "dtoverlay=pwm-2chan" works without having
// to install anything, albeit with less pins supported.
//
// Warning
//
// piblaster doesn't report what pins is controls so it is easy to misuse this
// library.
package piblaster
import (
"fmt"
"io"
"os"
"github.com/google/pio/conn/gpio"
)
// SetPWM enables and sets the PWM duty on a GPIO output pin via piblaster.
//
// duty must be [0, 1].
func SetPWM(p gpio.PinIO, duty float32) error {
if duty < 0 || duty > 1 {
return fmt.Errorf("duty %f is invalid for blaster", duty)
}
err := openPiblaster()
if err == nil {
_, err = io.WriteString(piblasterHandle, fmt.Sprintf("%d=%f\n", p.Number(), duty))
}
return err
}
// ReleasePWM releases a GPIO output and leave it floating.
//
// This function must be called on process exit for each activated pin
// otherwise the pin will stay in the state.
func ReleasePWM(p gpio.PinIO) error {
err := openPiblaster()
if err == nil {
_, err = io.WriteString(piblasterHandle, fmt.Sprintf("release %d\n", p.Number()))
}
return err
}
//
var piblasterHandle io.WriteCloser
func openPiblaster() error {
if piblasterHandle == nil {
f, err := os.OpenFile("/dev/pi-blaster", os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
piblasterHandle = f
}
return nil
}
func closePiblaster() error {
if piblasterHandle != nil {
w := piblasterHandle
piblasterHandle = nil
return w.Close()
}
return nil
}
Loading…
Cancel
Save