gobot.io/x/gobot/v2@v2.1.0/drivers/i2c/pcf8591_driver.go (about)

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