forked from adafruit/Adafruit-PWM-Servo-Driver-Library
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAdafruit_PWMServoDriver.cpp
executable file
·246 lines (222 loc) · 6.94 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
/*!
* @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"
#include <Wire.h>
/*!
* @brief Instantiates a new PCA9685 PWM driver chip with the I2C address on a
* TwoWire interface
* @param i2c A pointer to a 'Wire' compatible object that we'll use to
* communicate with
* @param addr The 7-bit I2C address to locate this chip, default is 0x40
*/
Adafruit_PWMServoDriver::Adafruit_PWMServoDriver(TwoWire *i2c, uint8_t addr) {
_i2c = i2c;
_i2caddr = addr;
}
/*!
* @brief Setups the I2C interface and hardware
* @param prescale
* Sets External Clock (Optional)
*
*/
void Adafruit_PWMServoDriver::begin(uint8_t prescale) {
_i2c->begin();
reset();
if (prescale) {
setExtClk(prescale);
} else {
// set a default frequency
setPWMFreq(1000);
}
}
/*!
* @brief Sends a reset command to the PCA9685 chip over I2C
*/
void Adafruit_PWMServoDriver::reset() {
write8(PCA9685_MODE1, 0x80);
delay(10);
}
/*!
* @brief Puts board into sleep mode
*/
void Adafruit_PWMServoDriver::sleep() {
uint8_t awake = read8(PCA9685_MODE1);
uint8_t sleep = awake | 0x10; // 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 & ~0x10; // 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 & 0x7F) | 0x10; // 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 |= 0x40));
write8(PCA9685_PRESCALE, prescale); // set the prescaler
delay(5);
write8(PCA9685_MODE1,
(newmode & ~(0x10)) | 0xa0); // clear the SLEEP bit to start
#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
freq *=
0.9; // Correct for overshoot in the frequency setting (see issue #11).
float prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Estimated pre-scale: ");
Serial.println(prescaleval);
#endif
uint8_t prescale = floor(prescaleval + 0.5);
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Final pre-scale: ");
Serial.println(prescale);
#endif
uint8_t oldmode = read8(PCA9685_MODE1);
uint8_t newmode = (oldmode & 0x7F) | 0x10; // sleep
write8(PCA9685_MODE1, newmode); // go to sleep
write8(PCA9685_PRESCALE, prescale); // set the prescaler
write8(PCA9685_MODE1, oldmode);
delay(5);
write8(PCA9685_MODE1,
oldmode |
0xa0); // This sets the MODE1 register to turn on auto increment.
#ifdef ENABLE_DEBUG_OUTPUT
Serial.print("Mode now 0x");
Serial.println(read8(PCA9685_MODE1), HEX);
#endif
}
/*!
* @brief Gets the PWM output of one of the PCA9685 pins
* @param num One of the PWM output pins, from 0 to 15
* @return requested PWM output value
*/
uint8_t Adafruit_PWMServoDriver::getPWM(uint8_t num) {
_i2c->requestFrom((uint8_t)_i2caddr, LED0_ON_L + 4 * num, (uint8_t)4);
return _i2c->read();
}
/*!
* @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
*/
void 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
_i2c->beginTransmission(_i2caddr);
_i2c->write(LED0_ON_L + 4 * num);
_i2c->write(on);
_i2c->write(on >> 8);
_i2c->write(off);
_i2c->write(off >> 8);
_i2c->endTransmission();
}
/*!
* @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);
}
}
}
uint8_t Adafruit_PWMServoDriver::read8(uint8_t addr) {
_i2c->beginTransmission(_i2caddr);
_i2c->write(addr);
_i2c->endTransmission();
_i2c->requestFrom((uint8_t)_i2caddr, (uint8_t)1);
return _i2c->read();
}
void Adafruit_PWMServoDriver::write8(uint8_t addr, uint8_t d) {
_i2c->beginTransmission(_i2caddr);
_i2c->write(addr);
_i2c->write(d);
_i2c->endTransmission();
}