tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/pca9685/pca9685.go (about)

     1  package pca9685
     2  
     3  import (
     4  	"encoding/binary"
     5  	"time"
     6  
     7  	"tinygo.org/x/drivers"
     8  )
     9  
    10  const (
    11  	// Internal oscillator frequency.
    12  	oscclock = 25000000
    13  	// Max value PWM value (on or off) can take.
    14  	maxtop       = 1<<12 - 1
    15  	milliseconds = 1_000_000 // units in nanoseconds
    16  )
    17  
    18  // Dev is a handle to the PCA9685 device given an address
    19  // (usually 0x47) and an i2c bus.
    20  type Dev struct {
    21  	addr uint8
    22  	bus  drivers.I2C
    23  	buf  [4]byte
    24  }
    25  
    26  type PWMConfig struct {
    27  	Period uint64
    28  }
    29  
    30  // New creates a new instance of a PCA9685 device. It performs
    31  // no IO on the i2c bus.
    32  func New(bus drivers.I2C, addr uint8) Dev {
    33  	return Dev{
    34  		bus:  bus,
    35  		addr: addr,
    36  	}
    37  }
    38  
    39  // Configure enables autoincrement, sets all PWM signals to logic low (Ground)
    40  // and finally sets the Period.
    41  func (d Dev) Configure(cfg PWMConfig) error {
    42  	err := d.SetAI(true)
    43  	if err != nil {
    44  		return err
    45  	}
    46  	d.SetAll(0)
    47  	d.SetDrive(true)
    48  	return d.SetPeriod(cfg.Period)
    49  }
    50  
    51  // SetPeriod updates the period of this PWM integrated circuit in nanoseconds.
    52  // To set a particular frequency, use the following formula:
    53  //
    54  //	period = 1e9 / frequency
    55  //
    56  // In the equation above frequency is in Hertz.
    57  //
    58  // If you use a period of 0, a period that works well for LEDs will be picked.
    59  //
    60  // PCA9685 accepts frequencies inbetween [40..1000 Hz],
    61  // or expressed as a period [1..25ms].
    62  func (d Dev) SetPeriod(period uint64) error {
    63  	const div = maxtop + 1
    64  	if period == 0 {
    65  		period = 1 * milliseconds
    66  	}
    67  	if period > 25*milliseconds || period < 1*milliseconds {
    68  		return ErrBadPeriod
    69  	}
    70  	// Correct for overshoot in provided frequency: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/issues/11
    71  	// Note: 0.96 was empirically determined to be closer. Should follow up to understand what is happening here.
    72  	freq := 96 * 1e9 / (100 * period)
    73  	prescale := byte(oscclock/(div*freq) - 1)
    74  	err := d.Sleep(true) // Enable sleep to write to PRESCALE register
    75  	if err != nil {
    76  		return err
    77  	}
    78  	d.buf[0] = prescale
    79  	err = d.writeReg(PRESCALE, d.buf[:1])
    80  	if err != nil {
    81  		return err
    82  	}
    83  	return d.Sleep(false)
    84  }
    85  
    86  // Top returns max value PWM can take.
    87  func (d Dev) Top() uint32 {
    88  	return maxtop
    89  }
    90  
    91  // Set sets the `on` value of a PWM channel in the range [0..15].
    92  // Max value `on` can take is 4095.
    93  // Example:
    94  //
    95  //	d.Set(1, d.Top()/4)
    96  //
    97  // sets the dutycycle of second (LED1) channel to 25%.
    98  func (d Dev) Set(channel uint8, on uint32) {
    99  	if on > maxtop {
   100  		panic("pca9685: value must be in range 0..4095")
   101  	}
   102  	d.SetPhased(channel, on, 0)
   103  }
   104  
   105  // SetAll sets all PWM signals to a ON value. Equivalent of calling
   106  //
   107  //	Dev.Set(pca9685.ALLLED, value)
   108  func (d Dev) SetAll(on uint32) {
   109  	d.Set(ALLLED, on)
   110  }
   111  
   112  // IsConnected returns error if read fails or if
   113  // driver suspects device is not connected.
   114  func (d Dev) IsConnected() error {
   115  	// Set data to the NOT of default MODE1 contents.
   116  	// If read is succesful then data will be modified
   117  	const notdefaultMODE1 = ^defaultMODE1Value
   118  	d.buf[0] = notdefaultMODE1
   119  
   120  	err := d.readReg(MODE1, d.buf[:1])
   121  	if err != nil {
   122  		return err
   123  	} else if d.buf[0] == notdefaultMODE1 {
   124  		return ErrInvalidMode1
   125  	}
   126  	return nil
   127  }
   128  
   129  // SetAI enables or disables autoincrement feature on device. Useful for
   130  // writing to many consecutive registers in one shot.
   131  func (d Dev) SetAI(ai bool) error {
   132  	err := d.readReg(MODE1, d.buf[:1])
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if ai {
   137  		d.buf[0] |= AI
   138  	} else {
   139  		d.buf[0] &^= AI
   140  	}
   141  	err = d.writeReg(MODE1, d.buf[:1])
   142  	return err
   143  }
   144  
   145  // SetDrive configures PWM output connection in MODE2 register.
   146  //
   147  //	false: The 16 LEDn outputs are configured with an open-drain structure.
   148  //	true: The 16 LEDn outputs are configured with a totem pole structure.
   149  func (d Dev) SetDrive(outdrv bool) error {
   150  	err := d.readReg(MODE2, d.buf[:1])
   151  	if err != nil {
   152  		return err
   153  	}
   154  	if outdrv {
   155  		d.buf[0] |= OUTDRV
   156  	} else {
   157  		d.buf[0] &^= OUTDRV
   158  	}
   159  	return d.writeReg(MODE2, d.buf[:1])
   160  }
   161  
   162  // Sleep sets/unsets SLEEP bit in MODE1.
   163  //
   164  //	if sleepEnabled
   165  //	  Stops PWM. Allows writing to PRE_SCALE register.
   166  //	else
   167  //	  wakes PCA9685. Resumes PWM.
   168  func (d Dev) Sleep(sleepEnabled bool) error {
   169  	err := d.readReg(MODE1, d.buf[:1])
   170  	if err != nil {
   171  		return err
   172  	}
   173  	if sleepEnabled {
   174  		d.buf[0] |= SLEEP
   175  		return d.writeReg(MODE1, d.buf[:1])
   176  	}
   177  	d.buf[0] &^= SLEEP
   178  	err = d.writeReg(MODE1, d.buf[:1])
   179  	// It takes 500μs max for the oscillator to be up and running once SLEEP bit
   180  	// has been set to logic 0. Timings on LEDn outputs are not guaranteed if PWM
   181  	// control registers are accessed within the 500μs window.
   182  	// There is no start-up delay required when using the EXTCLK pin as the PWM clock.
   183  	time.Sleep(1000 * time.Microsecond) // Requested by datasheet.
   184  	return err
   185  }
   186  
   187  // SetInverting inverts ALL PCA9685 PWMs. The channel argument merely implements PWM interface.
   188  //
   189  // Without inverting, a 25% duty cycle would mean the output is high for 25% of
   190  // the time and low for the rest. Inverting flips the output as if a NOT gate
   191  // was placed at the output, meaning that the output would be 25% low and 75%
   192  // high with a duty cycle of 25%.
   193  func (d Dev) SetInverting(_ uint8, inverting bool) error {
   194  	err := d.readReg(MODE2, d.buf[:1])
   195  	if err != nil {
   196  		return err
   197  	}
   198  	if inverting {
   199  		d.buf[0] |= INVRT
   200  	} else {
   201  		d.buf[0] &^= INVRT
   202  	}
   203  	return d.writeReg(MODE2, d.buf[:1])
   204  }
   205  
   206  // SetPhased sets PWM on and off mark.
   207  // The ON time, which is programmable, will be the time the LED output
   208  // will be asserted and the OFF time, which is also programmable, will be
   209  // the time when the LED output will be negated.
   210  // In this way, the phase shift becomes completely programmable.
   211  // The resolution for the phase shift is 1⁄4096 of the target frequency.
   212  func (d Dev) SetPhased(channel uint8, on, off uint32) {
   213  	binary.LittleEndian.PutUint16(d.buf[:2], uint16(on)&maxtop)
   214  	binary.LittleEndian.PutUint16(d.buf[2:4], uint16(off)&maxtop)
   215  	onLReg, _, _, _ := LED(channel)
   216  	d.writeReg(onLReg, d.buf[:4])
   217  }