gobot.io/x/gobot@v1.16.0/drivers/i2c/pcf8591_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"gobot.io/x/gobot"
    11  )
    12  
    13  // PCF8591 supports addresses from 0x48 to 0x4F
    14  // The default address applies when all address pins connected to ground.
    15  const pcf8591DefaultAddress = 0x48
    16  
    17  const (
    18  	pcf8591Debug = false
    19  )
    20  
    21  type pcf8591Mode uint8
    22  type PCF8591Channel uint8
    23  
    24  const (
    25  	pcf8591_CHAN0 PCF8591Channel = 0x00
    26  	pcf8591_CHAN1 PCF8591Channel = 0x01
    27  	pcf8591_CHAN2 PCF8591Channel = 0x02
    28  	pcf8591_CHAN3 PCF8591Channel = 0x03
    29  )
    30  
    31  const pcf8591_AION = 0x04 // auto increment, only relevant for ADC
    32  
    33  const (
    34  	pcf8591_ALLSINGLE pcf8591Mode = 0x00
    35  	pcf8591_THREEDIFF pcf8591Mode = 0x10
    36  	pcf8591_MIXED     pcf8591Mode = 0x20
    37  	pcf8591_TWODIFF   pcf8591Mode = 0x30
    38  	pcf8591_ANAON     pcf8591Mode = 0x40
    39  )
    40  
    41  const pcf8591_ADMASK = 0x33 // channels and mode
    42  
    43  type pcf8591ModeChan struct {
    44  	mode    pcf8591Mode
    45  	channel PCF8591Channel
    46  }
    47  
    48  // modeMap is to define the relation between a given description and the mode and channel
    49  // beside the long form there are some short forms available without risk of confusion
    50  //
    51  // pure single mode
    52  // "s.0"..."s.3": read single value of input n => channel n
    53  // pure differential mode
    54  // "d.0-1": differential value between input 0 and 1 => channel 0
    55  // "d.2-3": differential value between input 2 and 3 => channel 1
    56  // mixed mode
    57  // "m.0": single value of input 0  => channel 0
    58  // "m.1": single value of input 1  => channel 1
    59  // "m.2-3": differential value between input 2 and 3 => channel 2
    60  // three differential inputs, related to input 3
    61  // "t.0-3": differential value between input 0 and 3 => channel 0
    62  // "t.1-3": differential value between input 1 and 3 => channel 1
    63  // "t.2-3": differential value between input 1 and 3 => channel 2
    64  var pcf8591ModeMap = map[string]pcf8591ModeChan{
    65  	"s.0":   {pcf8591_ALLSINGLE, pcf8591_CHAN0},
    66  	"0":     {pcf8591_ALLSINGLE, pcf8591_CHAN0},
    67  	"s.1":   {pcf8591_ALLSINGLE, pcf8591_CHAN1},
    68  	"1":     {pcf8591_ALLSINGLE, pcf8591_CHAN1},
    69  	"s.2":   {pcf8591_ALLSINGLE, pcf8591_CHAN2},
    70  	"2":     {pcf8591_ALLSINGLE, pcf8591_CHAN2},
    71  	"s.3":   {pcf8591_ALLSINGLE, pcf8591_CHAN3},
    72  	"3":     {pcf8591_ALLSINGLE, pcf8591_CHAN3},
    73  	"d.0-1": {pcf8591_TWODIFF, pcf8591_CHAN0},
    74  	"0-1":   {pcf8591_TWODIFF, pcf8591_CHAN0},
    75  	"d.2-3": {pcf8591_TWODIFF, pcf8591_CHAN1},
    76  	"m.0":   {pcf8591_MIXED, pcf8591_CHAN0},
    77  	"m.1":   {pcf8591_MIXED, pcf8591_CHAN1},
    78  	"m.2-3": {pcf8591_MIXED, pcf8591_CHAN2},
    79  	"t.0-3": {pcf8591_THREEDIFF, pcf8591_CHAN0},
    80  	"0-3":   {pcf8591_THREEDIFF, pcf8591_CHAN0},
    81  	"t.1-3": {pcf8591_THREEDIFF, pcf8591_CHAN1},
    82  	"1-3":   {pcf8591_THREEDIFF, pcf8591_CHAN1},
    83  	"t.2-3": {pcf8591_THREEDIFF, pcf8591_CHAN2},
    84  }
    85  
    86  // PCF8591Driver is a Gobot Driver for the PCF8591 8-bit 4xA/D & 1xD/A converter with i2c (100 kHz) and 3 address pins.
    87  // The analog inputs can be used as differential inputs in different ways.
    88  //
    89  // All values are linear scaled to 3.3V by default. This can be changed, see example "tinkerboard_pcf8591.go".
    90  //
    91  // Address specification:
    92  // 1 0 0 1 A2 A1 A0|rd
    93  // Lowest bit (rd) is mapped to switch between write(0)/read(1), it is not part of the "real" address.
    94  //
    95  // Example: A1,A2=1, others are 0
    96  // Address mask => 1001110|1 => real 7-bit address mask 0100 1110 = 0x4E
    97  //
    98  // For example, here is the Adafruit board that uses this chip:
    99  // https://www.adafruit.com/product/4648
   100  //
   101  // This driver was tested with Tinkerboard and the YL-40 driver.
   102  //
   103  type PCF8591Driver struct {
   104  	name       string
   105  	connector  Connector
   106  	connection Connection
   107  	Config
   108  	gobot.Commander
   109  	lastCtrlByte        byte
   110  	lastAnaOut          byte
   111  	additionalReadWrite uint8
   112  	additionalRead      uint8
   113  	forceRefresh        bool
   114  	LastRead            [][]byte    // for debugging purposes
   115  	mutex               *sync.Mutex // mutex needed to ensure write-read sequence of AnalogRead() is not interrupted
   116  }
   117  
   118  // NewPCF8591Driver creates a new driver with specified i2c interface
   119  // Params:
   120  //    conn Connector - the Adaptor to use with this Driver
   121  //
   122  // Optional params:
   123  //    i2c.WithBus(int): bus to use with this driver
   124  //    i2c.WithAddress(int): address to use with this driver
   125  //    i2c.WithPCF8591With400kbitStabilization(uint8, uint8): stabilize read in 400 kbit mode
   126  //
   127  func NewPCF8591Driver(a Connector, options ...func(Config)) *PCF8591Driver {
   128  	p := &PCF8591Driver{
   129  		name:      gobot.DefaultName("PCF8591"),
   130  		connector: a,
   131  		Config:    NewConfig(),
   132  		Commander: gobot.NewCommander(),
   133  		mutex:     &sync.Mutex{},
   134  	}
   135  
   136  	for _, option := range options {
   137  		option(p)
   138  	}
   139  
   140  	return p
   141  }
   142  
   143  // WithPCF8591With400kbitStabilisation option sets the PCF8591 additionalReadWrite and additionalRead value
   144  func WithPCF8591With400kbitStabilization(additionalReadWrite, additionalRead int) func(Config) {
   145  	return func(c Config) {
   146  		p, ok := c.(*PCF8591Driver)
   147  		if ok {
   148  			if additionalReadWrite < 0 {
   149  				additionalReadWrite = 1 // works in most cases
   150  			}
   151  			if additionalRead < 0 {
   152  				additionalRead = 2 // works in most cases
   153  			}
   154  			p.additionalReadWrite = uint8(additionalReadWrite)
   155  			p.additionalRead = uint8(additionalRead)
   156  			if pcf8591Debug {
   157  				log.Printf("400 kbit stabilization for PCF8591Driver set rw: %d, r: %d", p.additionalReadWrite, p.additionalRead)
   158  			}
   159  		} else if pcf8591Debug {
   160  			log.Printf("trying to set 400 kbit stabilization for non-PCF8591Driver %v", c)
   161  		}
   162  	}
   163  }
   164  
   165  // WithPCF8591ForceWrite option modifies the PCF8591Driver forceRefresh option
   166  // Setting to true (1) will force refresh operation to register, although there is no change.
   167  // Normally this is not needed, so default is off (0).
   168  // When there is something flaky, there is a small chance to stabilize by setting this flag to true.
   169  // However, setting this flag to true slows down each IO operation up to 100%.
   170  func WithPCF8591ForceRefresh(val uint8) func(Config) {
   171  	return func(c Config) {
   172  		d, ok := c.(*PCF8591Driver)
   173  		if ok {
   174  			d.forceRefresh = val > 0
   175  		} else if pcf8591Debug {
   176  			log.Printf("Trying to set forceRefresh for non-PCF8591Driver %v", c)
   177  		}
   178  	}
   179  }
   180  
   181  // Name returns the Name for the Driver
   182  func (p *PCF8591Driver) Name() string { return p.name }
   183  
   184  // SetName sets the Name for the Driver
   185  func (p *PCF8591Driver) SetName(n string) { p.name = n }
   186  
   187  // Connection returns the connection for the Driver
   188  func (p *PCF8591Driver) Connection() gobot.Connection { return p.connector.(gobot.Connection) }
   189  
   190  // Start initializes the PCF8591
   191  func (p *PCF8591Driver) Start() (err error) {
   192  	bus := p.GetBusOrDefault(p.connector.GetDefaultBus())
   193  	address := p.GetAddressOrDefault(pcf8591DefaultAddress)
   194  
   195  	p.connection, err = p.connector.GetConnection(address, bus)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	if err := p.AnalogOutputState(false); err != nil {
   201  		return err
   202  	}
   203  	return
   204  }
   205  
   206  // Halt stops the device
   207  func (p *PCF8591Driver) Halt() (err error) {
   208  	return p.AnalogOutputState(false)
   209  }
   210  
   211  // AnalogRead returns value from analog reading of given input description
   212  //
   213  // Vlsb = (Vref-Vagnd)/256, value = (Van+ - Van-)/Vlsb, Van-=Vagnd for single mode
   214  //
   215  // The first read contains the last converted value (usually the last read).
   216  // After the channel was switched this means the value of the previous read channel.
   217  // After power on, the first byte read will be 80h, because the read is one cycle behind.
   218  //
   219  // Important note for 440 kbit mode:
   220  // With a bus speed of 100 kBit/sec, the ADC conversion has ~80 us + ACK (time to transfer the previous value).
   221  // This time is the limit for A-D conversion (datasheet 90 us).
   222  // An i2c bus extender (LTC4311) don't fix it (it seems rather the opposite).
   223  //
   224  // This leads to following behavior:
   225  // * the control byte is not written correctly
   226  // * the transition process takes an additional cycle, very often
   227  // * some circuits takes one cycle longer transition time in addition
   228  // * reading more than one byte by Read([]byte), e.g. to calculate an average, is not sufficient,
   229  //   because some missing integration steps in each conversion (each byte value is a little bit lower than expected)
   230  //
   231  // So, for default, we drop the first three bytes to get the right value.
   232  func (p *PCF8591Driver) AnalogRead(description string) (value int, err error) {
   233  	p.mutex.Lock()
   234  	defer p.mutex.Unlock()
   235  
   236  	mc, err := PCF8591ParseModeChan(description)
   237  	if err != nil {
   238  		return 0, err
   239  	}
   240  
   241  	// reset channel and mode
   242  	ctrlByte := p.lastCtrlByte & ^uint8(pcf8591_ADMASK)
   243  	// set to current channel and mode, AI must be off, because we need reading twice
   244  	ctrlByte = ctrlByte | uint8(mc.mode) | uint8(mc.channel) & ^uint8(pcf8591_AION)
   245  
   246  	var uval byte
   247  	p.LastRead = make([][]byte, p.additionalReadWrite+1)
   248  	// repeated write and read cycle to stabilize value in 400 kbit mode
   249  	for writeReadCycle := uint8(1); writeReadCycle <= p.additionalReadWrite+1; writeReadCycle++ {
   250  		if err = p.writeCtrlByte(ctrlByte, p.forceRefresh || writeReadCycle > 1); err != nil {
   251  			return 0, err
   252  		}
   253  
   254  		// initiate read but skip some bytes
   255  		if err := p.readBuf(writeReadCycle, 1+p.additionalRead); err != nil {
   256  			return 0, err
   257  		}
   258  
   259  		// additional relax time
   260  		time.Sleep(1 * time.Millisecond)
   261  
   262  		// real used read
   263  		if uval, err = p.connection.ReadByte(); err != nil {
   264  			return 0, err
   265  		}
   266  
   267  		if pcf8591Debug {
   268  			p.LastRead[writeReadCycle-1] = append(p.LastRead[writeReadCycle-1], uval)
   269  		}
   270  	}
   271  
   272  	// prepare return value
   273  	value = int(uval)
   274  	if mc.pcf8591IsDiff() {
   275  		if uval > 127 {
   276  			// first bit is set, means negative
   277  			value = int(uval) - 256
   278  		}
   279  	}
   280  
   281  	return value, err
   282  }
   283  
   284  // AnalogWrite writes the given value to the analog output (DAC)
   285  // Vlsb = (Vref-Vagnd)/256, Vaout = Vagnd+Vlsb*value
   286  // implements the aio.AnalogWriter interface, pin is unused here
   287  func (p *PCF8591Driver) AnalogWrite(pin string, value int) (err error) {
   288  	p.mutex.Lock()
   289  	defer p.mutex.Unlock()
   290  
   291  	byteVal := byte(value)
   292  
   293  	if p.lastAnaOut == byteVal {
   294  		if pcf8591Debug {
   295  			log.Printf("write skipped because value unchanged: 0x%X\n", byteVal)
   296  		}
   297  		return nil
   298  	}
   299  
   300  	ctrlByte := p.lastCtrlByte | byte(pcf8591_ANAON)
   301  	err = p.connection.WriteByteData(ctrlByte, byteVal)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	p.lastCtrlByte = ctrlByte
   307  	p.lastAnaOut = byteVal
   308  	return nil
   309  }
   310  
   311  // AnalogOutputState enables or disables the analog output
   312  // Please note that in case of using the internal oscillator
   313  // and the auto increment mode the output should not switched off.
   314  // Otherwise conversion errors could occur.
   315  func (p *PCF8591Driver) AnalogOutputState(state bool) (err error) {
   316  	p.mutex.Lock()
   317  	defer p.mutex.Unlock()
   318  
   319  	var ctrlByte uint8
   320  	if state {
   321  		ctrlByte = p.lastCtrlByte | byte(pcf8591_ANAON)
   322  	} else {
   323  		ctrlByte = p.lastCtrlByte & ^uint8(pcf8591_ANAON)
   324  	}
   325  
   326  	if err = p.writeCtrlByte(ctrlByte, p.forceRefresh); err != nil {
   327  		return err
   328  	}
   329  	return nil
   330  }
   331  
   332  // PCF8591ParseModeChan is used to get a working combination between mode (single, mixed, 2 differential, 3 differential)
   333  // and the related channel to read from, parsed from the given description string.
   334  func PCF8591ParseModeChan(description string) (*pcf8591ModeChan, error) {
   335  	mc, ok := pcf8591ModeMap[description]
   336  	if !ok {
   337  		descriptions := []string{}
   338  		for k := range pcf8591ModeMap {
   339  			descriptions = append(descriptions, k)
   340  		}
   341  		ds := strings.Join(descriptions, ", ")
   342  		return nil, fmt.Errorf("Unknown description '%s' for read analog value, accepted values: %s", description, ds)
   343  	}
   344  
   345  	return &mc, nil
   346  }
   347  
   348  func (p *PCF8591Driver) writeCtrlByte(ctrlByte uint8, forceRefresh bool) error {
   349  	if p.lastCtrlByte != ctrlByte || forceRefresh {
   350  		if err := p.connection.WriteByte(ctrlByte); err != nil {
   351  			return err
   352  		}
   353  		p.lastCtrlByte = ctrlByte
   354  	} else {
   355  		if pcf8591Debug {
   356  			log.Printf("write skipped because control byte unchanged: 0x%X\n", ctrlByte)
   357  		}
   358  	}
   359  	return nil
   360  }
   361  
   362  func (p *PCF8591Driver) readBuf(nr uint8, cntBytes uint8) error {
   363  	buf := make([]byte, cntBytes)
   364  	cntRead, err := p.connection.Read(buf)
   365  	if err != nil {
   366  		return err
   367  	}
   368  	if cntRead != len(buf) {
   369  		return fmt.Errorf("Not enough bytes (%d of %d) read", cntRead, len(buf))
   370  	}
   371  	if pcf8591Debug {
   372  		p.LastRead[nr-1] = buf
   373  	}
   374  	return nil
   375  }
   376  
   377  func (mc pcf8591ModeChan) pcf8591IsDiff() bool {
   378  	switch mc.mode {
   379  	case pcf8591_TWODIFF:
   380  		return true
   381  	case pcf8591_THREEDIFF:
   382  		return true
   383  	case pcf8591_MIXED:
   384  		return mc.channel == pcf8591_CHAN2
   385  	default:
   386  		return false
   387  	}
   388  }