360 lines
11 KiB
Go
360 lines
11 KiB
Go
package i2c
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// PCF8591 supports addresses from 0x48 to 0x4F
|
|
// The default address applies when all address pins connected to ground.
|
|
const pcf8591DefaultAddress = 0x48
|
|
|
|
const (
|
|
pcf8591Debug = false
|
|
)
|
|
|
|
type (
|
|
pcf8591Mode uint8
|
|
PCF8591Channel uint8
|
|
)
|
|
|
|
const (
|
|
pcf8591_CHAN0 PCF8591Channel = 0x00
|
|
pcf8591_CHAN1 PCF8591Channel = 0x01
|
|
pcf8591_CHAN2 PCF8591Channel = 0x02
|
|
pcf8591_CHAN3 PCF8591Channel = 0x03
|
|
)
|
|
|
|
const pcf8591_AION = 0x04 // auto increment, only relevant for ADC
|
|
|
|
const (
|
|
pcf8591_ALLSINGLE pcf8591Mode = 0x00
|
|
pcf8591_THREEDIFF pcf8591Mode = 0x10
|
|
pcf8591_MIXED pcf8591Mode = 0x20
|
|
pcf8591_TWODIFF pcf8591Mode = 0x30
|
|
pcf8591_ANAON pcf8591Mode = 0x40
|
|
)
|
|
|
|
const pcf8591_ADMASK = 0x33 // channels and mode
|
|
|
|
type pcf8591ModeChan struct {
|
|
mode pcf8591Mode
|
|
channel PCF8591Channel
|
|
}
|
|
|
|
// modeMap is to define the relation between a given description and the mode and channel
|
|
// beside the long form there are some short forms available without risk of confusion
|
|
//
|
|
// pure single mode
|
|
// "s.0"..."s.3": read single value of input n => channel n
|
|
// pure differential mode
|
|
// "d.0-1": differential value between input 0 and 1 => channel 0
|
|
// "d.2-3": differential value between input 2 and 3 => channel 1
|
|
// mixed mode
|
|
// "m.0": single value of input 0 => channel 0
|
|
// "m.1": single value of input 1 => channel 1
|
|
// "m.2-3": differential value between input 2 and 3 => channel 2
|
|
// three differential inputs, related to input 3
|
|
// "t.0-3": differential value between input 0 and 3 => channel 0
|
|
// "t.1-3": differential value between input 1 and 3 => channel 1
|
|
// "t.2-3": differential value between input 1 and 3 => channel 2
|
|
var pcf8591ModeMap = map[string]pcf8591ModeChan{
|
|
"s.0": {pcf8591_ALLSINGLE, pcf8591_CHAN0},
|
|
"0": {pcf8591_ALLSINGLE, pcf8591_CHAN0},
|
|
"s.1": {pcf8591_ALLSINGLE, pcf8591_CHAN1},
|
|
"1": {pcf8591_ALLSINGLE, pcf8591_CHAN1},
|
|
"s.2": {pcf8591_ALLSINGLE, pcf8591_CHAN2},
|
|
"2": {pcf8591_ALLSINGLE, pcf8591_CHAN2},
|
|
"s.3": {pcf8591_ALLSINGLE, pcf8591_CHAN3},
|
|
"3": {pcf8591_ALLSINGLE, pcf8591_CHAN3},
|
|
"d.0-1": {pcf8591_TWODIFF, pcf8591_CHAN0},
|
|
"0-1": {pcf8591_TWODIFF, pcf8591_CHAN0},
|
|
"d.2-3": {pcf8591_TWODIFF, pcf8591_CHAN1},
|
|
"m.0": {pcf8591_MIXED, pcf8591_CHAN0},
|
|
"m.1": {pcf8591_MIXED, pcf8591_CHAN1},
|
|
"m.2-3": {pcf8591_MIXED, pcf8591_CHAN2},
|
|
"t.0-3": {pcf8591_THREEDIFF, pcf8591_CHAN0},
|
|
"0-3": {pcf8591_THREEDIFF, pcf8591_CHAN0},
|
|
"t.1-3": {pcf8591_THREEDIFF, pcf8591_CHAN1},
|
|
"1-3": {pcf8591_THREEDIFF, pcf8591_CHAN1},
|
|
"t.2-3": {pcf8591_THREEDIFF, pcf8591_CHAN2},
|
|
}
|
|
|
|
// PCF8591Driver is a Gobot Driver for the PCF8591 8-bit 4xA/D & 1xD/A converter with i2c (100 kHz) and 3 address pins.
|
|
// The analog inputs can be used as differential inputs in different ways.
|
|
//
|
|
// All values are linear scaled to 3.3V by default. This can be changed, see example "tinkerboard_pcf8591.go".
|
|
//
|
|
// Address specification:
|
|
// 1 0 0 1 A2 A1 A0|rd
|
|
// Lowest bit (rd) is mapped to switch between write(0)/read(1), it is not part of the "real" address.
|
|
//
|
|
// Example: A1,A2=1, others are 0
|
|
// Address mask => 1001110|1 => real 7-bit address mask 0100 1110 = 0x4E
|
|
//
|
|
// For example, here is the Adafruit board that uses this chip:
|
|
// https://www.adafruit.com/product/4648
|
|
//
|
|
// This driver was tested with Tinkerboard and the YL-40 driver.
|
|
type PCF8591Driver struct {
|
|
*Driver
|
|
lastCtrlByte byte
|
|
lastAnaOut byte
|
|
additionalReadWrite uint8
|
|
additionalRead uint8
|
|
forceRefresh bool
|
|
LastRead [][]byte // for debugging purposes
|
|
}
|
|
|
|
// NewPCF8591Driver creates a new driver with specified i2c interface
|
|
// Params:
|
|
//
|
|
// c Connector - the Adaptor to use with this Driver
|
|
//
|
|
// Optional params:
|
|
//
|
|
// i2c.WithBus(int): bus to use with this driver
|
|
// i2c.WithAddress(int): address to use with this driver
|
|
// i2c.WithPCF8591With400kbitStabilization(uint8, uint8): stabilize read in 400 kbit mode
|
|
func NewPCF8591Driver(c Connector, options ...func(Config)) *PCF8591Driver {
|
|
p := &PCF8591Driver{
|
|
Driver: NewDriver(c, "PCF8591", pcf8591DefaultAddress),
|
|
}
|
|
p.afterStart = p.initialize
|
|
p.beforeHalt = p.shutdown
|
|
|
|
for _, option := range options {
|
|
option(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// WithPCF8591With400kbitStabilisation option sets the PCF8591 additionalReadWrite and additionalRead value
|
|
func WithPCF8591With400kbitStabilization(additionalReadWrite, additionalRead int) func(Config) {
|
|
return func(c Config) {
|
|
p, ok := c.(*PCF8591Driver)
|
|
if ok {
|
|
if additionalReadWrite < 0 {
|
|
additionalReadWrite = 1 // works in most cases
|
|
}
|
|
if additionalRead < 0 {
|
|
additionalRead = 2 // works in most cases
|
|
}
|
|
p.additionalReadWrite = uint8(additionalReadWrite) //nolint:gosec // checked before
|
|
p.additionalRead = uint8(additionalRead) //nolint:gosec // checked before
|
|
if pcf8591Debug {
|
|
log.Printf("400 kbit stabilization for PCF8591Driver set rw: %d, r: %d", p.additionalReadWrite, p.additionalRead)
|
|
}
|
|
} else if pcf8591Debug {
|
|
log.Printf("trying to set 400 kbit stabilization for non-PCF8591Driver %v", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPCF8591ForceWrite option modifies the PCF8591Driver forceRefresh option
|
|
// Setting to true (1) will force refresh operation to register, although there is no change.
|
|
// Normally this is not needed, so default is off (0).
|
|
// When there is something flaky, there is a small chance to stabilize by setting this flag to true.
|
|
// However, setting this flag to true slows down each IO operation up to 100%.
|
|
func WithPCF8591ForceRefresh(val uint8) func(Config) {
|
|
return func(c Config) {
|
|
d, ok := c.(*PCF8591Driver)
|
|
if ok {
|
|
d.forceRefresh = val > 0
|
|
} else if pcf8591Debug {
|
|
log.Printf("Trying to set forceRefresh for non-PCF8591Driver %v", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AnalogRead returns value from analog reading of given input description
|
|
//
|
|
// Vlsb = (Vref-Vagnd)/256, value = (Van+ - Van-)/Vlsb, Van-=Vagnd for single mode
|
|
//
|
|
// The first read contains the last converted value (usually the last read).
|
|
// After the channel was switched this means the value of the previous read channel.
|
|
// After power on, the first byte read will be 80h, because the read is one cycle behind.
|
|
//
|
|
// Important note for 440 kbit mode:
|
|
// With a bus speed of 100 kBit/sec, the ADC conversion has ~80 us + ACK (time to transfer the previous value).
|
|
// This time is the limit for A-D conversion (datasheet 90 us).
|
|
// An i2c bus extender (LTC4311) don't fix it (it seems rather the opposite).
|
|
//
|
|
// This leads to following behavior:
|
|
// - the control byte is not written correctly
|
|
// - the transition process takes an additional cycle, very often
|
|
// - some circuits takes one cycle longer transition time in addition
|
|
// - reading more than one byte by Read([]byte), e.g. to calculate an average, is not sufficient,
|
|
// because some missing integration steps in each conversion (each byte value is a little bit lower than expected)
|
|
//
|
|
// So, for default, we drop the first three bytes to get the right value.
|
|
func (p *PCF8591Driver) AnalogRead(description string) (int, error) {
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
mc, err := PCF8591ParseModeChan(description)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// reset channel and mode
|
|
ctrlByte := p.lastCtrlByte & ^uint8(pcf8591_ADMASK)
|
|
// set to current channel and mode, AI must be off, because we need reading twice
|
|
ctrlByte = ctrlByte | uint8(mc.mode) | uint8(mc.channel) & ^uint8(pcf8591_AION)
|
|
|
|
var uval byte
|
|
p.LastRead = make([][]byte, p.additionalReadWrite+1)
|
|
// repeated write and read cycle to stabilize value in 400 kbit mode
|
|
for writeReadCycle := uint8(1); writeReadCycle <= p.additionalReadWrite+1; writeReadCycle++ {
|
|
if err = p.writeCtrlByte(ctrlByte, p.forceRefresh || writeReadCycle > 1); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// initiate read but skip some bytes
|
|
if err := p.readBuf(writeReadCycle, 1+p.additionalRead); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// additional relax time
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// real used read
|
|
if uval, err = p.connection.ReadByte(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if pcf8591Debug {
|
|
p.LastRead[writeReadCycle-1] = append(p.LastRead[writeReadCycle-1], uval)
|
|
}
|
|
}
|
|
|
|
// prepare return value
|
|
value := int(uval)
|
|
if mc.pcf8591IsDiff() {
|
|
if uval > 127 {
|
|
// first bit is set, means negative
|
|
value = int(uval) - 256
|
|
}
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// AnalogWrite writes the given value to the analog output (DAC)
|
|
// Vlsb = (Vref-Vagnd)/256, Vaout = Vagnd+Vlsb*value
|
|
// implements the aio.AnalogWriter interface, pin is unused here
|
|
func (p *PCF8591Driver) AnalogWrite(pin string, value int) error {
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
byteVal := byte(value)
|
|
|
|
if p.lastAnaOut == byteVal {
|
|
if pcf8591Debug {
|
|
log.Printf("write skipped because value unchanged: 0x%X\n", byteVal)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
ctrlByte := p.lastCtrlByte | byte(pcf8591_ANAON)
|
|
if err := p.connection.WriteByteData(ctrlByte, byteVal); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.lastCtrlByte = ctrlByte
|
|
p.lastAnaOut = byteVal
|
|
return nil
|
|
}
|
|
|
|
// AnalogOutputState enables or disables the analog output
|
|
// Please note that in case of using the internal oscillator
|
|
// and the auto increment mode the output should not switched off.
|
|
// Otherwise conversion errors could occur.
|
|
func (p *PCF8591Driver) AnalogOutputState(state bool) error {
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
return p.analogOutputState(state)
|
|
}
|
|
|
|
// PCF8591ParseModeChan is used to get a working combination between mode (single, mixed, 2 differential,
|
|
// 3 differential) and the related channel to read from, parsed from the given description string.
|
|
func PCF8591ParseModeChan(description string) (*pcf8591ModeChan, error) {
|
|
mc, ok := pcf8591ModeMap[description]
|
|
if !ok {
|
|
descriptions := []string{}
|
|
for k := range pcf8591ModeMap {
|
|
descriptions = append(descriptions, k)
|
|
}
|
|
ds := strings.Join(descriptions, ", ")
|
|
return nil, fmt.Errorf("Unknown description '%s' for read analog value, accepted values: %s", description, ds)
|
|
}
|
|
|
|
return &mc, nil
|
|
}
|
|
|
|
func (p *PCF8591Driver) writeCtrlByte(ctrlByte uint8, forceRefresh bool) error {
|
|
if p.lastCtrlByte != ctrlByte || forceRefresh {
|
|
if err := p.connection.WriteByte(ctrlByte); err != nil {
|
|
return err
|
|
}
|
|
p.lastCtrlByte = ctrlByte
|
|
} else if pcf8591Debug {
|
|
log.Printf("write skipped because control byte unchanged: 0x%X\n", ctrlByte)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *PCF8591Driver) readBuf(nr uint8, cntBytes uint8) error {
|
|
buf := make([]byte, cntBytes)
|
|
cntRead, err := p.connection.Read(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cntRead != len(buf) {
|
|
return fmt.Errorf("Not enough bytes (%d of %d) read", cntRead, len(buf))
|
|
}
|
|
if pcf8591Debug {
|
|
p.LastRead[nr-1] = buf
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mc pcf8591ModeChan) pcf8591IsDiff() bool {
|
|
switch mc.mode {
|
|
case pcf8591_TWODIFF:
|
|
return true
|
|
case pcf8591_THREEDIFF:
|
|
return true
|
|
case pcf8591_MIXED:
|
|
return mc.channel == pcf8591_CHAN2
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (p *PCF8591Driver) initialize() error {
|
|
return p.analogOutputState(false)
|
|
}
|
|
|
|
func (p *PCF8591Driver) shutdown() error {
|
|
return p.analogOutputState(false)
|
|
}
|
|
|
|
func (p *PCF8591Driver) analogOutputState(state bool) error {
|
|
var ctrlByte uint8
|
|
if state {
|
|
ctrlByte = p.lastCtrlByte | byte(pcf8591_ANAON)
|
|
} else {
|
|
ctrlByte = p.lastCtrlByte & ^uint8(pcf8591_ANAON)
|
|
}
|
|
|
|
if err := p.writeCtrlByte(ctrlByte, p.forceRefresh); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|