From 28f7e8009b55f672738d6ef9a15523e59763b796 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Wed, 31 Oct 2018 05:46:57 -0400 Subject: [PATCH] ht16k33: add support for LED alphanumeric display (#298) Add support to rainbowhat which is a composite of multiple devices. --- experimental/devices/ht16k33/alphanum.go | 167 ++++++++++++++++ experimental/devices/ht16k33/doc.go | 16 ++ experimental/devices/ht16k33/example_test.go | 63 ++++++ experimental/devices/ht16k33/ht16k33.go | 113 +++++++++++ experimental/devices/rainbowhat/doc.go | 28 +++ .../devices/rainbowhat/example_test.go | 113 +++++++++++ experimental/devices/rainbowhat/rainbowhat.go | 184 ++++++++++++++++++ 7 files changed, 684 insertions(+) create mode 100644 experimental/devices/ht16k33/alphanum.go create mode 100644 experimental/devices/ht16k33/doc.go create mode 100644 experimental/devices/ht16k33/example_test.go create mode 100644 experimental/devices/ht16k33/ht16k33.go create mode 100644 experimental/devices/rainbowhat/doc.go create mode 100644 experimental/devices/rainbowhat/example_test.go create mode 100644 experimental/devices/rainbowhat/rainbowhat.go diff --git a/experimental/devices/ht16k33/alphanum.go b/experimental/devices/ht16k33/alphanum.go new file mode 100644 index 0000000..da9a268 --- /dev/null +++ b/experimental/devices/ht16k33/alphanum.go @@ -0,0 +1,167 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package ht16k33 + +import ( + "periph.io/x/periph/conn/i2c" +) + +var digitValues = map[rune]uint16{ + ' ': 0x0, + '!': 0x6, + '"': 0x220, + '#': 0x12ce, + '$': 0x12ed, + '%': 0xc24, + '&': 0x235d, + '\'': 0x400, + '(': 0x2400, + ')': 0x900, + '*': 0x3fc0, + '+': 0x12c0, + ',': 0x800, + '-': 0xc0, + '.': 0x4000, + '/': 0xc00, + '0': 0xc3f, + '1': 0x6, + '2': 0xdb, + '3': 0x8f, + '4': 0xe6, + '5': 0x2069, + '6': 0xfd, + '7': 0x7, + '8': 0xff, + '9': 0xef, + ':': 0x1200, + ';': 0xa00, + '<': 0x2400, + '=': 0xc8, + '>': 0x900, + '?': 0x1083, + '@': 0x2bb, + 'A': 0xf7, + 'B': 0x128f, + 'C': 0x39, + 'D': 0x120f, + 'E': 0xf9, + 'F': 0x71, + 'G': 0xbd, + 'H': 0xf6, + 'I': 0x1200, + 'J': 0x1e, + 'K': 0x2470, + 'L': 0x38, + 'M': 0x536, + 'N': 0x2136, + 'O': 0x3f, + 'P': 0xf3, + 'Q': 0x203f, + 'R': 0x20f3, + 'S': 0xed, + 'T': 0x1201, + 'U': 0x3e, + 'V': 0xc30, + 'W': 0x2836, + 'X': 0x2d00, + 'Y': 0x1500, + 'Z': 0xc09, + '[': 0x39, + '\\': 0x2100, + ']': 0xf, + '^': 0xc03, + '_': 0x8, + '`': 0x100, + 'a': 0x1058, + 'b': 0x2078, + 'c': 0xd8, + 'd': 0x88e, + 'e': 0x858, + 'f': 0x71, + 'g': 0x48e, + 'h': 0x1070, + 'i': 0x1000, + 'j': 0xe, + 'k': 0x3600, + 'l': 0x30, + 'm': 0x10d4, + 'n': 0x1050, + 'o': 0xdc, + 'p': 0x170, + 'q': 0x486, + 'r': 0x50, + 's': 0x2088, + 't': 0x78, + 'u': 0x1c, + 'v': 0x2004, + 'w': 0x2814, + 'x': 0x28c0, + 'y': 0x200c, + 'z': 0x848, + '{': 0x949, + '|': 0x1200, + '}': 0x2489, + '~': 0x520, +} + +// Display is a handler to control an alphanumeric display based on ht16k33. +type Display struct { + dev *Dev +} + +// NewAlphaNumericDisplay returns a Display object that communicates over I2C to ht16k33. +// +// To use on the default address, ht16k33.I2CAddr must be passed as argument. +func NewAlphaNumericDisplay(bus i2c.Bus, address uint16) (*Display, error) { + dev, err := NewI2C(bus, address) + if err != nil { + return nil, err + } + return &Display{dev: dev}, nil +} + +// SetDigit at position to provided value. +func (d *Display) SetDigit(pos int, digit rune, decimal bool) error { + val := digitValues[digit] + if decimal { + val |= digitValues['.'] + } + return d.dev.WriteColumn(pos, val) +} + +// WriteString print string of values to the display. +// +// Characters in the string should be any ASCII value 32 to 127 (printable ASCII). +func (d *Display) WriteString(s string) (int, error) { + if err := d.dev.Halt(); err != nil { + return 0, err + } + + pos := (4 - len(s)) + if pos < 0 { + pos = 0 + } + // Go through each character and print it on the display. + for _, ch := range s { + if ch == '.' { + // Print decimal points on the previous digit. + c := rune(s[pos-1]) + if err := d.SetDigit(pos-1, c, true); err != nil { + return pos, err + } + } else { + if err := d.SetDigit(pos, ch, false); err != nil { + return pos, err + } + pos++ + } + } + return pos, nil +} + +// Halt clear all the display. +func (d *Display) Halt() error { + return d.dev.Halt() +} diff --git a/experimental/devices/ht16k33/doc.go b/experimental/devices/ht16k33/doc.go new file mode 100644 index 0000000..88a11ca --- /dev/null +++ b/experimental/devices/ht16k33/doc.go @@ -0,0 +1,16 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package ht16k33 implements interfacing code to Holtek HT16K33 Alphanumeric 16x8 LED driver. +// +// More Details +// +// Datasheets +// +// http://www.holtek.com/documents/10179/116711/HT16K33v120.pdf +// +// Product Page +// +// http://www.holtek.com/productdetail/-/vg/HT16K33 +package ht16k33 diff --git a/experimental/devices/ht16k33/example_test.go b/experimental/devices/ht16k33/example_test.go new file mode 100644 index 0000000..ec5beee --- /dev/null +++ b/experimental/devices/ht16k33/example_test.go @@ -0,0 +1,63 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. +package ht16k33_test + +import ( + "fmt" + "log" + "time" + + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/experimental/devices/ht16k33" + "periph.io/x/periph/host" +) + +func Example() { + // Make sure periph is initialized. + if _, err := host.Init(); err != nil { + log.Fatal(err) + } + + bus, err := i2creg.Open("") + if err != nil { + log.Fatal(err) + } + defer bus.Close() + + display, err := ht16k33.NewAlphaNumericDisplay(bus, ht16k33.I2CAddr) + if err != nil { + log.Fatal(err) + } + defer display.Halt() + + if _, err := display.WriteString("ABCD"); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + + if _, err := display.WriteString("GO"); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + + if _, err := display.WriteString(fmt.Sprintf("%d", 1234)); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + + if _, err := display.WriteString(fmt.Sprintf("%d", 60)); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + + if _, err := display.WriteString(fmt.Sprintf("%5f", 23.99)); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) + + if _, err := display.WriteString(fmt.Sprintf("%5f", 1.45)); err != nil { + log.Fatal(err) + } + time.Sleep(1 * time.Second) +} diff --git a/experimental/devices/ht16k33/ht16k33.go b/experimental/devices/ht16k33/ht16k33.go new file mode 100644 index 0000000..48eedd3 --- /dev/null +++ b/experimental/devices/ht16k33/ht16k33.go @@ -0,0 +1,113 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package ht16k33 + +import ( + "errors" + + "periph.io/x/periph/conn/i2c" +) + +// I2CAddr i2c default address. +const I2CAddr uint16 = 0x70 + +const ( + cmdRAM = 0x00 + cmdKeys = 0x40 + displaySetup = 0x80 + displayOff = 0x00 + displayOn = 0x01 + systemSetup = 0x20 + oscillatorOff = 0x00 + oscillatorOn = 0x01 + cmdBrightness = 0xE0 +) + +// BlinkFrequency display frequency must be a value allowed by the HT16K33. +type BlinkFrequency byte + +// Blinking frequencies. +const ( + BlinkOff = 0x00 + Blink2Hz = 0x02 + Blink1Hz = 0x04 + BlinkHalfHz = 0x06 +) + +// Dev is a handler to ht16k33 controller +type Dev struct { + dev i2c.Dev +} + +// NewI2C returns a Dev object that communicates over I2C. +// +// To use on the default address, ht16k33.I2CAddr must be passed as argument. +func NewI2C(bus i2c.Bus, address uint16) (*Dev, error) { + dev := &Dev{dev: i2c.Dev{Bus: bus, Addr: address}} + + if err := dev.init(); err != nil { + return nil, err + } + + return dev, nil +} + +func (d *Dev) init() error { + // Turn on the oscillator. + if _, err := d.dev.Write([]byte{systemSetup | oscillatorOn}); err != nil { + return err + } + + // Turn on display + if _, err := d.dev.Write([]byte{displaySetup | displayOn}); err != nil { + return err + } + + // Set no blinking. + if err := d.SetBlink(BlinkOff); err != nil { + return err + } + + // Set display to full brightness. + if err := d.SetBrightness(15); err != nil { + return err + } + return nil +} + +// SetBlink Blink display at specified frequency. +func (d *Dev) SetBlink(freq BlinkFrequency) error { + if _, err := d.dev.Write([]byte{displaySetup | displayOn | byte(freq)}); err != nil { + return err + } + return nil +} + +// SetBrightness of entire display to specified value. +// +// Supports 16 levels, from 0 to 15. +func (d *Dev) SetBrightness(brightness int) error { + if brightness < 0 || brightness > 15 { + return errors.New("ht16k33: brightness must be between 0 and 15") + } + _, err := d.dev.Write([]byte{cmdBrightness | byte(brightness)}) + return err +} + +// WriteColumn set data in a given column. +func (d *Dev) WriteColumn(column int, data uint16) error { + _, err := d.dev.Write([]byte{byte(column * 2), byte(data & 0xFF), byte(data >> 8)}) + return err +} + +// Halt clear the contents of display buffer. +func (d *Dev) Halt() error { + for i := 0; i < 4; i++ { + if err := d.WriteColumn(i, 0); err != nil { + return err + } + } + return nil +} diff --git a/experimental/devices/rainbowhat/doc.go b/experimental/devices/rainbowhat/doc.go new file mode 100644 index 0000000..2789cd8 --- /dev/null +++ b/experimental/devices/rainbowhat/doc.go @@ -0,0 +1,28 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package rainbowhat implements interfacing code to Pimoroni's Rainbow hat. +// +// This driver provides easy access to the peripherals available on the Rainbow Hat for Android Things: +// +// BMP280 temperature and pressure sensor (I2C) +// +// HT16K33 segment display (I2C) +// +// Capacitive buttons (GPIO) +// +// LEDs (GPIO) +// +// APA102 RGB LEDs (SPI) +// +// Piezo Buzzer (PWM) +// +// Servo header (PWM) +// +// More details +// +// Product Page +// +// https://shop.pimoroni.com/products/rainbow-hat-for-android-things +package rainbowhat diff --git a/experimental/devices/rainbowhat/example_test.go b/experimental/devices/rainbowhat/example_test.go new file mode 100644 index 0000000..e79ee06 --- /dev/null +++ b/experimental/devices/rainbowhat/example_test.go @@ -0,0 +1,113 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package rainbowhat_test + +import ( + "fmt" + "image" + "image/color" + "log" + "os" + "os/signal" + "syscall" + "time" + + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/physic" + "periph.io/x/periph/devices/apa102" + "periph.io/x/periph/experimental/devices/rainbowhat" + "periph.io/x/periph/host" +) + +func Example() { + + // Make sure periph is initialized. + if _, err := host.Init(); err != nil { + log.Fatal(err) + } + + hat, err := rainbowhat.NewRainbowHat(&apa102.DefaultOpts) + if err != nil { + log.Fatal(err) + } + defer hat.Halt() + + handleButton := func(btn gpio.PinIn, led gpio.PinOut) { + ledState := false + if err := led.Out(gpio.Low); err != nil { + log.Fatal(err) + } + for { + btn.WaitForEdge(-1) + if btn.Read() == gpio.Low { + if ledState { + if err := led.Out(gpio.High); err != nil { + log.Fatal(err) + } + } else { + if err := led.Out(gpio.Low); err != nil { + log.Fatal(err) + } + } + ledState = !ledState + } + } + } + + go handleButton(hat.GetButtonA(), hat.GetLedR()) + go handleButton(hat.GetButtonB(), hat.GetLedG()) + go handleButton(hat.GetButtonC(), hat.GetLedB()) + + ledstrip := hat.GetLedStrip() + ledstrip.Intensity = 50 + + img := image.NewNRGBA(image.Rect(0, 0, ledstrip.Bounds().Dx(), 1)) + img.SetNRGBA(0, 0, color.NRGBA{148, 0, 211, 255}) + img.SetNRGBA(1, 0, color.NRGBA{75, 0, 130, 255}) + img.SetNRGBA(2, 0, color.NRGBA{0, 0, 255, 255}) + img.SetNRGBA(3, 0, color.NRGBA{0, 255, 0, 255}) + img.SetNRGBA(4, 0, color.NRGBA{255, 255, 0, 255}) + img.SetNRGBA(5, 0, color.NRGBA{255, 127, 0, 255}) + img.SetNRGBA(6, 0, color.NRGBA{255, 0, 0, 255}) + + if err := ledstrip.Draw(ledstrip.Bounds(), img, image.Point{}); err != nil { + log.Fatalf("failed to draw: %v", err) + } + + display := hat.GetDisplay() + sensor := hat.GetBmp280() + ticker := time.NewTicker(3 * time.Second) + go func() { + for { + var envi physic.Env + if err := sensor.Sense(&envi); err != nil { + log.Fatal(err) + } + + temp := fmt.Sprintf("%5s", envi.Temperature) + fmt.Printf("Pressure %8s \n", envi.Pressure) + fmt.Printf("Temperature %8s \n", envi.Temperature) + + if _, err := display.WriteString(temp); err != nil { + log.Fatal(err) + } + <-ticker.C + } + }() + + sigs := make(chan os.Signal, 1) + done := make(chan bool, 1) + + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + + go func() { + sig := <-sigs // Wait for signal + log.Println(sig) + done <- true + }() + + log.Println("Press ctrl+c to stop...") + <-done // Wait +} diff --git a/experimental/devices/rainbowhat/rainbowhat.go b/experimental/devices/rainbowhat/rainbowhat.go new file mode 100644 index 0000000..893ecf4 --- /dev/null +++ b/experimental/devices/rainbowhat/rainbowhat.go @@ -0,0 +1,184 @@ +// Copyright 2018 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package rainbowhat + +import ( + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/conn/spi/spireg" + "periph.io/x/periph/devices/apa102" + "periph.io/x/periph/devices/bmxx80" + "periph.io/x/periph/experimental/devices/ht16k33" + "periph.io/x/periph/host/rpi" +) + +// Dev represents a Rainbow HAT (https://shop.pimoroni.com/products/rainbow-hat-for-android-things) +type Dev struct { + ledstrip *apa102.Dev + bmp280 *bmxx80.Dev + display *ht16k33.Display + buttonA gpio.PinIn + buttonB gpio.PinIn + buttonC gpio.PinIn + ledR gpio.PinOut + ledG gpio.PinOut + ledB gpio.PinOut + buzzer gpio.PinOut + servo gpio.PinOut +} + +// NewRainbowHat returns a rainbowhat driver. +func NewRainbowHat(ao *apa102.Opts) (*Dev, error) { + i2cPort, err := i2creg.Open("/dev/i2c-1") + if err != nil { + return nil, err + } + + spiPort, err := spireg.Open("/dev/spidev0.0") + if err != nil { + return nil, err + } + + bmp280, err := bmxx80.NewI2C(i2cPort, 0x77, &bmxx80.DefaultOpts) + if err != nil { + return nil, err + } + + display, err := ht16k33.NewAlphaNumericDisplay(i2cPort, ht16k33.I2CAddr) + if err != nil { + return nil, err + } + + opts := *ao + opts.NumPixels = 7 + ledstrip, err := apa102.New(spiPort, &opts) + if err != nil { + return nil, err + } + + dev := &Dev{ + ledstrip: ledstrip, + bmp280: bmp280, + display: display, + buttonA: rpi.P1_40, // GPIO21 + buttonB: rpi.P1_38, // GPIO20 + buttonC: rpi.P1_36, // GPIO16 + ledR: rpi.P1_31, // GPIO06 + ledG: rpi.P1_35, // GPIO19 + ledB: rpi.P1_37, // GPIO26 + buzzer: rpi.P1_33, // PWM1 + servo: rpi.P1_32, // PWM0 + } + + if err := dev.buttonA.In(gpio.PullUp, gpio.BothEdges); err != nil { + return nil, err + } + + if err := dev.buttonB.In(gpio.PullUp, gpio.BothEdges); err != nil { + return nil, err + } + + if err := dev.buttonC.In(gpio.PullUp, gpio.BothEdges); err != nil { + return nil, err + } + + return dev, nil +} + +// GetLedStrip returns apa102.Dev seven addressable led strip. +func (d *Dev) GetLedStrip() *apa102.Dev { + return d.ledstrip +} + +// GetBmp280 returns bmxx80.Dev handler. +func (d *Dev) GetBmp280() *bmxx80.Dev { + return d.bmp280 +} + +// GetDisplay returns ht16k33.Display with four alphanumeric digits. +func (d *Dev) GetDisplay() *ht16k33.Display { + return d.display +} + +// GetButtonA returns gpio.PinIn corresponding to the A capacitive button. +func (d *Dev) GetButtonA() gpio.PinIn { + return d.buttonA +} + +// GetButtonB returns gpio.PinIn corresponding to the B capacitive button. +func (d *Dev) GetButtonB() gpio.PinIn { + return d.buttonB +} + +// GetButtonC returns gpio.PinIn corresponding to the C capacitive button. +func (d *Dev) GetButtonC() gpio.PinIn { + return d.buttonC +} + +// GetLedR returns gpio.PinOut corresponding to the red LED. +func (d *Dev) GetLedR() gpio.PinOut { + return d.ledR +} + +// GetLedG returns gpio.PinOut corresponding to the green LED. +func (d *Dev) GetLedG() gpio.PinOut { + return d.ledG +} + +// GetLedB returns gpio.PinOut corresponding to the blue LED. +func (d *Dev) GetLedB() gpio.PinOut { + return d.ledB +} + +// GetBuzzer returns gpio.PinOut corresponding to the buzzer pin. +func (d *Dev) GetBuzzer() gpio.PinOut { + return d.buzzer +} + +// GetServo returns gpio.PinOut corresponding to the servo pin. +func (d *Dev) GetServo() gpio.PinOut { + return d.servo +} + +// Halt all internal devices. +func (d *Dev) Halt() error { + if err := d.bmp280.Halt(); err != nil { + return err + } + + if err := d.ledstrip.Halt(); err != nil { + return err + } + + if err := d.display.Halt(); err != nil { + return err + } + + if err := d.ledR.Halt(); err != nil { + return err + } + + if err := d.ledG.Halt(); err != nil { + return err + } + + if err := d.ledB.Halt(); err != nil { + return err + } + + if err := d.buttonA.Halt(); err != nil { + return err + } + + if err := d.buttonB.Halt(); err != nil { + return err + } + + if err := d.buttonC.Halt(); err != nil { + return err + } + + return nil +}