PCA9685 Driver
API reference
Loading...
Searching...
No Matches
pca9685.ipp
Go to the documentation of this file.
1
6#ifndef PCA9685_IMPL
7#define PCA9685_IMPL
8
9// Include standard library headers BEFORE the namespace opens
10#include <algorithm>
11#include <array>
12#include <cmath>
13#include <string.h> // NOLINT(modernize-deprecated-headers) - Use C string.h for ESP-IDF compatibility (cstring has issues with ESP-IDF toolchain)
14
15// When included from header, use relative path; when compiled directly, use standard include
16#ifdef PCA9685_HEADER_INCLUDED
17#include "../inc/pca9685.hpp"
18#else
19#include "../inc/pca9685.hpp"
20#endif
21
22template <typename I2cType>
23pca9685::PCA9685<I2cType>::PCA9685(I2cType* bus, uint8_t address) : i2c_(bus), addr_(address) {
24 // No initialization here - use EnsureInitialized() or Reset() when ready
25}
26
27template <typename I2cType>
29 if (initialized_) {
30 return true; // Already initialized
31 }
32 return Reset();
33}
34
35template <typename I2cType>
37 // Ensure I2C bus is initialized and ready (mirrors PCAL95555 pattern)
38 if (!i2c_ || !i2c_->EnsureInitialized()) {
39 setError(Error::I2cWrite);
40 initialized_ = false;
41 return false;
42 }
43
44 // MODE1 = AI only: oscillator on (SLEEP = 0), register auto-increment on (datasheet 7.3.1,
45 // Table 5) so that the four-byte LEDn / ALL_LED frames reach all four registers.
46 if (!writeMode1(0x00)) {
47 initialized_ = false;
48 return false;
49 }
50 waitOscillatorStartup(); // SLEEP may have been 1 (it is at power-up): 500 us start-up
51 initialized_ = true;
52 last_error_ = Error::None;
53 return true;
54}
55
56template <typename I2cType>
57bool pca9685::PCA9685<I2cType>::SetPwmFreq(float freq_hz) noexcept {
58 if (!EnsureInitialized()) {
59 setError(Error::NotInitialized);
60 return false;
61 }
62 if (freq_hz < 24.0F || freq_hz > 1526.0F) {
63 setError(Error::OutOfRange);
64 return false;
65 }
66 uint8_t prescale = calcPrescale(freq_hz);
67 uint8_t old_mode = 0;
68 if (!readReg(static_cast<uint8_t>(Register::MODE1), old_mode)) {
69 return false;
70 }
71 // PRE_SCALE accepts writes only while MODE1.SLEEP = 1 (Table 4, note 1)
72 if (!writeMode1(static_cast<uint8_t>(old_mode | MODE1_SLEEP_))) {
73 return false;
74 }
75 if (!writeReg(static_cast<uint8_t>(Register::PRE_SCALE), prescale)) {
76 return false;
77 }
78 if ((old_mode & MODE1_SLEEP_) == 0) {
79 // Was running: wake, give the oscillator 500 us, then write RESTART so that the channels
80 // that were running resume (datasheet 7.3.1.1 steps 2 and 3). A 1 written to RESTART when it
81 // reads 0 has no effect (Table 5), so it is written unconditionally.
82 if (!restartAfterSleep(old_mode, true)) {
83 return false;
84 }
85 }
86 last_error_ = Error::None;
87 return true;
88}
89
90template <typename I2cType>
91bool pca9685::PCA9685<I2cType>::SetPwm(uint8_t channel, uint16_t on_time,
92 uint16_t off_time) noexcept {
93 if (!EnsureInitialized()) {
94 setError(Error::NotInitialized);
95 return false;
96 }
97 if (channel >= MAX_CHANNELS_ || on_time > MAX_PWM_ || off_time > MAX_PWM_) {
98 setError(Error::OutOfRange);
99 return false;
100 }
101 uint8_t reg = static_cast<uint8_t>(Register::LED0_ON_L) + (4 * channel);
102 const auto data = pwmFrame(on_time, off_time);
103 if (!writeRegBlock(reg, data.data(), 4)) {
104 return false;
105 }
106 last_error_ = Error::None;
107 return true;
108}
109
110template <typename I2cType>
111// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - Types are different enough (uint8_t vs
112// float)
113bool pca9685::PCA9685<I2cType>::SetDuty(uint8_t channel, float duty) noexcept {
114 duty = ::std::max(duty, 0.0F);
115 duty = ::std::min(duty, 1.0F);
116 if (duty >= 1.0F) {
117 // 100 %: the full-ON bit, LEDn_ON_H[4] (datasheet 7.3.3, Table 7, p. 24). ON = 0, OFF = 4095
118 // would leave the output low for one tick of every period.
119 return SetChannelFullOn(channel);
120 }
121 // Rounds to 0 for duty < 1/8190: SetPwm() turns ON = OFF = 0 into full OFF.
122 auto off_time = static_cast<uint16_t>(lroundf((duty * MAX_PWM_)));
123 return SetPwm(channel, 0, off_time);
124}
125
126template <typename I2cType>
127bool pca9685::PCA9685<I2cType>::SetAllPwm(uint16_t on_time, uint16_t off_time) noexcept {
128 if (!EnsureInitialized()) {
129 setError(Error::NotInitialized);
130 return false;
131 }
132 if (on_time > MAX_PWM_ || off_time > MAX_PWM_) {
133 setError(Error::OutOfRange);
134 return false;
135 }
136 const auto data = pwmFrame(on_time, off_time);
137 if (!writeRegBlock(static_cast<uint8_t>(Register::ALL_LED_ON_L), data.data(), 4)) {
138 return false;
139 }
140 last_error_ = Error::None;
141 return true;
142}
143
144template <typename I2cType>
145bool pca9685::PCA9685<I2cType>::GetPrescale(uint8_t& prescale) noexcept {
146 if (!EnsureInitialized()) {
147 setError(Error::NotInitialized);
148 return false;
149 }
150 if (!readReg(static_cast<uint8_t>(Register::PRE_SCALE), prescale)) {
151 return false;
152 }
153 last_error_ = Error::None;
154 return true;
155}
156
157// ---- Power Management ----
158
159template <typename I2cType>
161 if (!EnsureInitialized()) {
162 setError(Error::NotInitialized);
163 return false;
164 }
165 uint8_t mode1 = 0;
166 if (!readReg(static_cast<uint8_t>(Register::MODE1), mode1)) {
167 return false;
168 }
169 if (!writeMode1(static_cast<uint8_t>(mode1 | MODE1_SLEEP_))) {
170 return false;
171 }
172 last_error_ = Error::None;
173 return true;
174}
175
176template <typename I2cType>
178 if (!EnsureInitialized()) {
179 setError(Error::NotInitialized);
180 return false;
181 }
182 uint8_t mode1 = 0;
183 if (!readReg(static_cast<uint8_t>(Register::MODE1), mode1)) {
184 return false;
185 }
186 const bool asleep = (mode1 & MODE1_SLEEP_) != 0;
187 const bool restart = (mode1 & MODE1_RESTART_) != 0;
188 if (asleep || restart) {
189 // Datasheet 7.3.1.1: clear SLEEP, allow 500 us for the oscillator, then write 1 to RESTART
190 // if it read 1, so that the channels that were running resume.
191 if (!restartAfterSleep(mode1, restart)) {
192 return false;
193 }
194 }
195 last_error_ = Error::None;
196 return true;
197}
198
199// ---- Output Configuration ----
200
201template <typename I2cType>
203 if (!EnsureInitialized()) {
204 setError(Error::NotInitialized);
205 return false;
206 }
207 return modifyReg(static_cast<uint8_t>(Register::MODE2), 0x10,
208 invert ? static_cast<uint8_t>(0x10U) : static_cast<uint8_t>(0x00U));
209}
210
211template <typename I2cType>
213 if (!EnsureInitialized()) {
214 setError(Error::NotInitialized);
215 return false;
216 }
217 return modifyReg(static_cast<uint8_t>(Register::MODE2), 0x04,
218 totem_pole ? static_cast<uint8_t>(0x04U) : static_cast<uint8_t>(0x00U));
219}
220
221template <typename I2cType>
223 if (!i2c_) {
224 setError(Error::InvalidParam);
225 return false;
226 }
227 // OE is active LOW (datasheet 7.4): ACTIVE = outputs enabled = pin low; the port maps the level.
228 i2c_->GpioSet(CtrlPin::OE, enable ? GpioSignal::ACTIVE : GpioSignal::INACTIVE);
229 return true;
230}
231
232// ---- Channel On/Off ----
233
234template <typename I2cType>
235bool pca9685::PCA9685<I2cType>::SetChannelFullOn(uint8_t channel) noexcept {
236 if (!EnsureInitialized()) {
237 setError(Error::NotInitialized);
238 return false;
239 }
240 if (channel >= MAX_CHANNELS_) {
241 setError(Error::OutOfRange);
242 return false;
243 }
244 uint8_t reg = static_cast<uint8_t>(Register::LED0_ON_L) + (4 * channel);
245 // Set LEDn_ON_H bit 4 (full-on), clear LEDn_OFF_H bit 4 (full-off)
246 ::std::array<uint8_t, 4> data = {0x00, 0x10, 0x00, 0x00};
247 if (!writeRegBlock(reg, data.data(), 4)) {
248 return false;
249 }
250 last_error_ = Error::None;
251 return true;
252}
253
254template <typename I2cType>
255bool pca9685::PCA9685<I2cType>::SetChannelFullOff(uint8_t channel) noexcept {
256 if (!EnsureInitialized()) {
257 setError(Error::NotInitialized);
258 return false;
259 }
260 if (channel >= MAX_CHANNELS_) {
261 setError(Error::OutOfRange);
262 return false;
263 }
264 uint8_t reg = static_cast<uint8_t>(Register::LED0_ON_L) + (4 * channel);
265 // Clear LEDn_ON_H bit 4, set LEDn_OFF_H bit 4 (full-off)
266 ::std::array<uint8_t, 4> data = {0x00, 0x00, 0x00, 0x10};
267 if (!writeRegBlock(reg, data.data(), 4)) {
268 return false;
269 }
270 last_error_ = Error::None;
271 return true;
272}
273
274// ---- Low-level register access with retries ----
275
276template <typename I2cType>
277bool pca9685::PCA9685<I2cType>::writeReg(uint8_t reg, uint8_t value) noexcept {
278 if (!i2c_) {
279 return false;
280 }
281 for (int attempt = 0; attempt <= retries_; ++attempt) {
282 if (i2c_->Write(addr_, reg, &value, 1)) {
283 return true;
284 }
285 if (attempt < retries_ && retry_delay_) {
286 retry_delay_();
287 }
288 }
289 setError(Error::I2cWrite);
290 return false;
291}
292
293template <typename I2cType>
294bool pca9685::PCA9685<I2cType>::readReg(uint8_t reg, uint8_t& value) noexcept {
295 if (!i2c_) {
296 return false;
297 }
298 for (int attempt = 0; attempt <= retries_; ++attempt) {
299 if (i2c_->Read(addr_, reg, &value, 1)) {
300 return true;
301 }
302 if (attempt < retries_ && retry_delay_) {
303 retry_delay_();
304 }
305 }
306 setError(Error::I2cRead);
307 return false;
308}
309
310template <typename I2cType>
311bool pca9685::PCA9685<I2cType>::writeRegBlock(uint8_t reg, const uint8_t* data,
312 size_t len) noexcept {
313 if (!i2c_) {
314 return false;
315 }
316 for (int attempt = 0; attempt <= retries_; ++attempt) {
317 if (i2c_->Write(addr_, reg, data, len)) {
318 return true;
319 }
320 if (attempt < retries_ && retry_delay_) {
321 retry_delay_();
322 }
323 }
324 setError(Error::I2cWrite);
325 return false;
326}
327
328template <typename I2cType>
329bool pca9685::PCA9685<I2cType>::readRegBlock(uint8_t reg, uint8_t* data, size_t len) noexcept {
330 if (!i2c_) {
331 return false;
332 }
333 for (int attempt = 0; attempt <= retries_; ++attempt) {
334 if (i2c_->Read(addr_, reg, data, len)) {
335 return true;
336 }
337 if (attempt < retries_ && retry_delay_) {
338 retry_delay_();
339 }
340 }
341 setError(Error::I2cRead);
342 return false;
343}
344
345template <typename I2cType>
346bool pca9685::PCA9685<I2cType>::writeMode1(uint8_t value) noexcept {
347 // Keep AI set on every MODE1 write, and never write 1 to RESTART by accident: a 1 there restarts
348 // the PWM channels (datasheet 7.3.1, Table 5 and 7.3.1.1).
349 const auto mode1 = static_cast<uint8_t>((value & ~MODE1_RESTART_) | MODE1_AI_);
350 return writeReg(static_cast<uint8_t>(Register::MODE1), mode1);
351}
352
353template <typename I2cType>
354bool pca9685::PCA9685<I2cType>::restartAfterSleep(uint8_t mode1, bool restart) noexcept {
355 const auto awake = static_cast<uint8_t>(mode1 & ~MODE1_SLEEP_);
356 if (!writeMode1(awake)) {
357 return false;
358 }
359 waitOscillatorStartup(); // SLEEP must be 0 for 500 us before RESTART is written (7.3.1.1)
360 if (!restart) {
361 return true;
362 }
363 return writeReg(static_cast<uint8_t>(Register::MODE1),
364 static_cast<uint8_t>((awake & ~MODE1_RESTART_) | MODE1_AI_ | MODE1_RESTART_));
365}
366
367template <typename I2cType>
369 if constexpr (requires(I2cType& bus, uint32_t us) { bus.DelayUs(us); }) {
370 i2c_->DelayUs(OSC_STARTUP_US_);
371 } else {
372 // The port has no DelayUs(): time the wait with bus traffic instead. A one-byte register read
373 // is at least 39 SCL periods (START, 4 x 9 bits, repeated START, STOP), 39 us at the chip's
374 // fastest clock of 1 MHz (Table 15), so 13 reads of MODE1 take at least 507 us. MODE1 is not a
375 // PWM control register, so reading it inside the start-up window is allowed (Table 5, note 2).
376 uint8_t scratch = 0;
377 for (uint32_t i = 0; i < OSC_STARTUP_FALLBACK_READS_; ++i) {
378 (void)i2c_->Read(addr_, static_cast<uint8_t>(Register::MODE1), &scratch, 1);
379 }
380 }
381}
382
383template <typename I2cType>
384::std::array<uint8_t, 4> pca9685::PCA9685<I2cType>::pwmFrame(uint16_t on_time,
385 uint16_t off_time) noexcept {
386 if (on_time == off_time) {
387 // LEDn_ON and LEDn_OFF must never hold the same count (datasheet 7.3.4, p. 25). Equal ticks
388 // mean zero duty, so write the full-OFF bit, LEDn_OFF_H[4], instead.
389 return {0x00, 0x00, 0x00, 0x10};
390 }
391 return {static_cast<uint8_t>(on_time & 0xFF), static_cast<uint8_t>((on_time >> 8) & 0x0F),
392 static_cast<uint8_t>(off_time & 0xFF), static_cast<uint8_t>((off_time >> 8) & 0x0F)};
393}
394
395template <typename I2cType>
396bool pca9685::PCA9685<I2cType>::modifyReg(uint8_t reg, uint8_t mask, uint8_t value) noexcept {
397 uint8_t current = 0;
398 if (!readReg(reg, current)) {
399 return false;
400 }
401 uint8_t new_val = (current & static_cast<uint8_t>(~mask)) | (value & mask);
402 if (!writeReg(reg, new_val)) {
403 return false;
404 }
405 last_error_ = Error::None;
406 return true;
407}
408
409template <typename I2cType>
410uint8_t pca9685::PCA9685<I2cType>::calcPrescale(float freq_hz) const noexcept {
411 float prescale_val = (static_cast<float>(OSC_FREQ_) / (4096.0F * freq_hz)) - 1.0F;
412 prescale_val = ::std::max(prescale_val, 3.0F);
413 prescale_val = ::std::min(prescale_val, 255.0F);
414 return static_cast<uint8_t>(lroundf(prescale_val));
415}
416
417// Note: Namespace is closed in pca9685.hpp, not here
418
419#endif // PCA9685_IMPL
bool SetPwmFreq(float freq_hz) noexcept
Set the PWM frequency for all channels.
Definition pca9685.ipp:57
bool SetPwm(uint8_t channel, uint16_t on_time, uint16_t off_time) noexcept
Set the PWM on/off time for a channel.
Definition pca9685.ipp:91
bool SetAllPwm(uint16_t on_time, uint16_t off_time) noexcept
Set all channels to the same PWM value.
Definition pca9685.ipp:127
uint8_t calcPrescale(float freq_hz) const noexcept
Compute prescale value for given frequency.
Definition pca9685.ipp:410
bool Wake() noexcept
Wake the PCA9685 from sleep mode.
Definition pca9685.ipp:177
bool SetOutputInvert(bool invert) noexcept
Set output polarity inversion.
Definition pca9685.ipp:202
static ::std::array< uint8_t, 4 > pwmFrame(uint16_t on_time, uint16_t off_time) noexcept
Build the four LEDn / ALL_LED bytes for ON and OFF ticks; equal ticks give full OFF.
Definition pca9685.ipp:384
bool EnsureInitialized() noexcept
Ensure the driver and I2C bus are initialized (lazy initialization).
Definition pca9685.ipp:28
bool Reset() noexcept
Reset the device to its power-on default state.
Definition pca9685.ipp:36
PCA9685(I2cType *bus, uint8_t address)
Construct a new PCA9685 driver instance.
Definition pca9685.ipp:23
void waitOscillatorStartup() noexcept
Wait OSC_STARTUP_US_ through I2cType::DelayUs(), or with MODE1 reads if the port has none.
Definition pca9685.ipp:368
bool readReg(uint8_t reg, uint8_t &value) noexcept
Read one byte from a register.
Definition pca9685.ipp:294
bool SetDuty(uint8_t channel, float duty) noexcept
Set the duty cycle for a channel (0.0-1.0).
Definition pca9685.ipp:113
bool SetOutputEnable(bool enable) noexcept
Enable or blank all 16 outputs with the OE pin.
Definition pca9685.ipp:222
bool readRegBlock(uint8_t reg, uint8_t *data, size_t len) noexcept
Read a block of bytes from a register.
Definition pca9685.ipp:329
bool SetChannelFullOn(uint8_t channel) noexcept
Set a channel to fully ON (100% duty, no PWM).
Definition pca9685.ipp:235
bool modifyReg(uint8_t reg, uint8_t mask, uint8_t value) noexcept
Read-modify-write a single register.
Definition pca9685.ipp:396
bool Sleep() noexcept
Put the PCA9685 into low-power sleep mode.
Definition pca9685.ipp:160
bool SetChannelFullOff(uint8_t channel) noexcept
Set a channel to fully OFF (0% duty, no PWM).
Definition pca9685.ipp:255
bool writeMode1(uint8_t value) noexcept
Write MODE1 with AI forced to 1 and RESTART forced to 0.
Definition pca9685.ipp:346
bool restartAfterSleep(uint8_t mode1, bool restart) noexcept
Clear SLEEP, wait for the oscillator, then optionally write RESTART (datasheet 7.3....
Definition pca9685.ipp:354
bool GetPrescale(uint8_t &prescale) noexcept
Get the current prescale value (frequency divider).
Definition pca9685.ipp:145
bool writeReg(uint8_t reg, uint8_t value) noexcept
Write one byte to a register.
Definition pca9685.ipp:277
bool writeRegBlock(uint8_t reg, const uint8_t *data, size_t len) noexcept
Write a block of bytes to a register.
Definition pca9685.ipp:311
bool SetOutputDriverMode(bool totem_pole) noexcept
Set the output driver mode.
Definition pca9685.ipp:212
@ ACTIVE
Pin function is asserted.
@ INACTIVE
Pin function is deasserted.
@ OE
Output Enable (active-low on the physical pin)