-
Notifications
You must be signed in to change notification settings - Fork 313
/
Adafruit_PWMServoDriver.cpp
374 lines (332 loc) · 10.7 KB
/
Adafruit_PWMServoDriver.cpp
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/*!
* @file Adafruit_PWMServoDriver.cpp
*
* @mainpage Adafruit 16-channel PWM & Servo driver
*
* @section intro_sec Introduction
*
* This is a library for the 16-channel PWM & Servo driver.
*
* Designed specifically to work with the Adafruit PWM & Servo driver.
*
* Pick one up today in the adafruit shop!
* ------> https://www.adafruit.com/product/815
*
* These displays use I2C to communicate, 2 pins are required to interface.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit andopen-source hardware by purchasing products
* from Adafruit!
*
* @section author Author
*
* Limor Fried/Ladyada (Adafruit Industries).
*
* @section license License
*
* BSD license, all text above must be included in any redistribution
*/
#include "Adafruit_PWMServoDriver.h"
//#define ENABLE_DEBUG_OUTPUT
/*!
* @brief Instantiates a new PCA9685 PWM driver chip with the I2C address on a
* TwoWire interface
*/
Adafruit_PWMServoDriver::Adafruit_PWMServoDriver()
: _i2caddr(PCA9685_I2C_ADDRESS), _i2c(&Wire) {}
/*!
* @brief Instantiates a new PCA9685 PWM driver chip with the I2C address on a
* TwoWire interface
* @param addr The 7-bit I2C address to locate this chip, default is 0x40
*/
Adafruit_PWMServoDriver::Adafruit_PWMServoDriver(const uint8_t addr)
: _i2caddr(addr), _i2c(&Wire) {}
/*!
* @brief Instantiates a new PCA9685 PWM driver chip with the I2C address on a
* TwoWire interface
* @param addr The 7-bit I2C address to locate this chip, default is 0x40
* @param i2c A reference to a 'TwoWire' object that we'll use to communicate
* with
*/
Adafruit_PWMServoDriver::Adafruit_PWMServoDriver(const uint8_t addr,
TwoWire &i2c)
: _i2caddr(addr), _i2c(&i2c) {}
/*!
* @brief Setups the I2C interface and hardware
* @param prescale
* Sets External Clock (Optional)
* @return true if successful, otherwise false
*/
bool Adafruit_PWMServoDriver::begin(uint8_t prescale) {
if (i2c_dev)
delete i2c_dev;
i2c_dev = new Adafruit_I2CDevice(_i2caddr, _i2c);
if (!i2c_dev->begin())
return false;
reset();
// set the default internal frequency
setOscillatorFrequency(FREQUENCY_OSCILLATOR);
if (prescale) {
setExtClk(prescale);
} else {
// set a default frequency
setPWMFreq(1000);
}
return true;
}
/*!
* @brief Sends a reset command to the PCA9685 chip over I2C
*/
void Adafruit_PWMServoDriver::reset() {
write8(PCA9685_MODE1, MODE1_RESTART);
delay(10);
}
/*!
* @brief Puts board into sleep mode
*/
void Adafruit_PWMServoDriver::sleep() {
uint8_t awake = read8(PCA9685_MODE1);
uint8_t sleep = awake | MODE1_SLEEP; // set sleep bit high
write8(PCA9685_MODE1, sleep);
delay(5); // wait until cycle ends for sleep to be active
}
/*!
* @brief Wakes board from sleep
*/
void Adafruit_PWMServoDriver::wakeup() {
uint8_t sleep = read8(PCA9685_MODE1);
uint8_t wakeup = sleep & ~MODE1_SLEEP; // set sleep bit low
write8(PCA9685_MODE1, wakeup);
}
/*!
* @brief Sets EXTCLK pin to use the external clock
* @param prescale
* Configures the prescale value to be used by the external clock
*/
void Adafruit_PWMServoDriver::setExtClk(uint8_t prescale) {
uint8_t oldmode = read8(PCA9685_MODE1);
uint8_t newmode = (oldmode & ~MODE1_RESTART) | MODE1_SLEEP; // sleep
write8(PCA9685_MODE1, newmode); // go to sleep, turn off internal oscillator
// This sets both the SLEEP and EXTCLK bits of the MODE1 register to switch to
// use the external clock.
write8(PCA9685_MODE1, (newmode |= MODE1_EXTCLK));
write8(PCA9685_PRESCALE, prescale); // set the prescaler
delay(5);
// clear the SLEEP bit to start
write8(PCA9685_MODE1, (newmode & ~MODE1_SLEEP) | MODE1_RESTART | MODE1_AI);
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Mode now 0x");
Serial.println(read8(PCA9685_MODE1), HEX);
#endif
}
/*!
* @brief Sets the PWM frequency for the entire chip, up to ~1.6 KHz
* @param freq Floating point frequency that we will attempt to match
*/
void Adafruit_PWMServoDriver::setPWMFreq(float freq) {
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Attempting to set freq ");
Serial.println(freq);
#endif
// Range output modulation frequency is dependant on oscillator
if (freq < 1)
freq = 1;
if (freq > 3500)
freq = 3500; // Datasheet limit is 3052=50MHz/(4*4096)
float prescaleval = ((_oscillator_freq / (freq * 4096.0)) + 0.5) - 1;
if (prescaleval < PCA9685_PRESCALE_MIN)
prescaleval = PCA9685_PRESCALE_MIN;
if (prescaleval > PCA9685_PRESCALE_MAX)
prescaleval = PCA9685_PRESCALE_MAX;
uint8_t prescale = (uint8_t)prescaleval;
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Final pre-scale: ");
Serial.println(prescale);
#endif
uint8_t oldmode = read8(PCA9685_MODE1);
uint8_t newmode = (oldmode & ~MODE1_RESTART) | MODE1_SLEEP; // sleep
write8(PCA9685_MODE1, newmode); // go to sleep
write8(PCA9685_PRESCALE, prescale); // set the prescaler
write8(PCA9685_MODE1, oldmode);
delay(5);
// This sets the MODE1 register to turn on auto increment.
write8(PCA9685_MODE1, oldmode | MODE1_RESTART | MODE1_AI);
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Mode now 0x");
Serial.println(read8(PCA9685_MODE1), HEX);
#endif
}
/*!
* @brief Sets the output mode of the PCA9685 to either
* open drain or push pull / totempole.
* Warning: LEDs with integrated zener diodes should
* only be driven in open drain mode.
* @param totempole Totempole if true, open drain if false.
*/
void Adafruit_PWMServoDriver::setOutputMode(bool totempole) {
uint8_t oldmode = read8(PCA9685_MODE2);
uint8_t newmode;
if (totempole) {
newmode = oldmode | MODE2_OUTDRV;
} else {
newmode = oldmode & ~MODE2_OUTDRV;
}
write8(PCA9685_MODE2, newmode);
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Setting output mode: ");
Serial.print(totempole ? "totempole" : "open drain");
Serial.print(" by setting MODE2 to ");
Serial.println(newmode);
#endif
}
/*!
* @brief Reads set Prescale from PCA9685
* @return prescale value
*/
uint8_t Adafruit_PWMServoDriver::readPrescale(void) {
return read8(PCA9685_PRESCALE);
}
/*!
* @brief Gets the PWM output of one of the PCA9685 pins
* @param num One of the PWM output pins, from 0 to 15
* @param off If true, returns PWM OFF value, otherwise PWM ON
* @return requested PWM output value
*/
uint16_t Adafruit_PWMServoDriver::getPWM(uint8_t num, bool off) {
uint8_t buffer[2] = {uint8_t(PCA9685_LED0_ON_L + 4 * num), 0};
if (off)
buffer[0] += 2;
i2c_dev->write_then_read(buffer, 1, buffer, 2);
return uint16_t(buffer[0]) | (uint16_t(buffer[1]) << 8);
}
/*!
* @brief Sets the PWM output of one of the PCA9685 pins
* @param num One of the PWM output pins, from 0 to 15
* @param on At what point in the 4096-part cycle to turn the PWM output ON
* @param off At what point in the 4096-part cycle to turn the PWM output OFF
* @return 0 if successful, otherwise 1
*/
uint8_t Adafruit_PWMServoDriver::setPWM(uint8_t num, uint16_t on,
uint16_t off) {
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Setting PWM ");
Serial.print(num);
Serial.print(": ");
Serial.print(on);
Serial.print("->");
Serial.println(off);
#endif
uint8_t buffer[5];
buffer[0] = PCA9685_LED0_ON_L + 4 * num;
buffer[1] = on;
buffer[2] = on >> 8;
buffer[3] = off;
buffer[4] = off >> 8;
if (i2c_dev->write(buffer, 5)) {
return 0;
} else {
return 1;
}
}
/*!
* @brief Helper to set pin PWM output. Sets pin without having to deal with
* on/off tick placement and properly handles a zero value as completely off and
* 4095 as completely on. Optional invert parameter supports inverting the
* pulse for sinking to ground.
* @param num One of the PWM output pins, from 0 to 15
* @param val The number of ticks out of 4096 to be active, should be a value
* from 0 to 4095 inclusive.
* @param invert If true, inverts the output, defaults to 'false'
*/
void Adafruit_PWMServoDriver::setPin(uint8_t num, uint16_t val, bool invert) {
// Clamp value between 0 and 4095 inclusive.
val = min(val, (uint16_t)4095);
if (invert) {
if (val == 0) {
// Special value for signal fully on.
setPWM(num, 4096, 0);
} else if (val == 4095) {
// Special value for signal fully off.
setPWM(num, 0, 4096);
} else {
setPWM(num, 0, 4095 - val);
}
} else {
if (val == 4095) {
// Special value for signal fully on.
setPWM(num, 4096, 0);
} else if (val == 0) {
// Special value for signal fully off.
setPWM(num, 0, 4096);
} else {
setPWM(num, 0, val);
}
}
}
/*!
* @brief Sets the PWM output of one of the PCA9685 pins based on the input
* microseconds, output is not precise
* @param num One of the PWM output pins, from 0 to 15
* @param Microseconds The number of Microseconds to turn the PWM output ON
*/
void Adafruit_PWMServoDriver::writeMicroseconds(uint8_t num,
uint16_t Microseconds) {
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Setting PWM Via Microseconds on output");
Serial.print(num);
Serial.print(": ");
Serial.print(Microseconds);
Serial.println("->");
#endif
double pulse = Microseconds;
double pulselength;
pulselength = 1000000; // 1,000,000 us per second
// Read prescale
uint16_t prescale = readPrescale();
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print(prescale);
Serial.println(" PCA9685 chip prescale");
#endif
// Calculate the pulse for PWM based on Equation 1 from the datasheet section
// 7.3.5
prescale += 1;
pulselength *= prescale;
pulselength /= _oscillator_freq;
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print(pulselength);
Serial.println(" us per bit");
#endif
pulse /= pulselength;
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print(pulse);
Serial.println(" pulse for PWM");
#endif
setPWM(num, 0, pulse);
}
/*!
* @brief Getter for the internally tracked oscillator used for freq
* calculations
* @returns The frequency the PCA9685 thinks it is running at (it cannot
* introspect)
*/
uint32_t Adafruit_PWMServoDriver::getOscillatorFrequency(void) {
return _oscillator_freq;
}
/*!
* @brief Setter for the internally tracked oscillator used for freq
* calculations
* @param freq The frequency the PCA9685 should use for frequency calculations
*/
void Adafruit_PWMServoDriver::setOscillatorFrequency(uint32_t freq) {
_oscillator_freq = freq;
}
/******************* Low level I2C interface */
uint8_t Adafruit_PWMServoDriver::read8(uint8_t addr) {
uint8_t buffer[1] = {addr};
i2c_dev->write_then_read(buffer, 1, buffer, 1);
return buffer[0];
}
void Adafruit_PWMServoDriver::write8(uint8_t addr, uint8_t d) {
uint8_t buffer[2] = {addr, d};
i2c_dev->write(buffer, 2);
}