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