You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/tm1637/tm1637.go

211 lines
4.9 KiB
Go

// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package tm1637
import (
"errors"
"fmt"
"runtime"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/host/cpu"
)
// 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
)
// New returns an object that communicates over two pins to a TM1637.
func New(clk gpio.PinOut, data gpio.PinIO) (*Dev, error) {
// Spec calls to idle at high.
if err := clk.Out(gpio.High); err != nil {
return nil, err
}
if err := data.Out(gpio.High); err != nil {
return nil, err
}
d := &Dev{clk: clk, data: data}
return d, nil
}
// Dev represents an handle to a tm1637.
type Dev struct {
clk gpio.PinOut
data gpio.PinIO
}
func (d *Dev) String() string {
return fmt.Sprintf("TM1637{clk:%s, data:%s}", d.clk, d.data)
}
// 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()
if _, err := d.writeByte(byte(b)); err != nil {
return err
}
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()
if _, err := d.writeByte(0x40); err != nil {
return 0, err
}
d.stop()
d.start()
if _, err := d.writeByte(0xC0); err != nil {
return 0, err
}
for i := 0; i < 6; i++ {
if len(seg) <= i {
if _, err := d.writeByte(0); err != nil {
return i, err
}
} else {
if _, err := d.writeByte(seg[i]); err != nil {
return i, err
}
}
}
d.stop()
return len(seg), nil
}
// Halt turns the display off.
func (d *Dev) Halt() error {
b := [6]byte{}
_, err := d.Write(b[:])
return err
}
//
// 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)
d.sleepHalfCycle()
// TODO(maruel): Add.
//if err := d.data.In(gpio.PullUp, gpio.NoEdge); 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() {
spin(clockHalfCycle)
}
var spin = cpu.Nanospin
var _ conn.Resource = &Dev{}