ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
pca9685.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file pca9685.h
6 * @brief NXP PCA9685 16-channel 12-bit PWM / servo driver codec (PROTOCORE_ENABLE_PCA9685).
7 *
8 * The PCA9685 generates sixteen independent 12-bit PWM outputs from a 25 MHz oscillator. The
9 * output frequency is set by a PRESCALE register value; each channel is four registers (a 12-bit
10 * ON count and a 12-bit OFF count) at `0x06 + 4 * channel`. Driving a hobby servo is a matter of
11 * turning a pulse width (in microseconds) into an OFF count at the configured frequency.
12 *
13 * This codec is pure and host-tested: ::protocore_pca9685_prescale computes the prescale for a frequency,
14 * ::protocore_pca9685_us_to_count converts a servo pulse width to a 12-bit count, ::protocore_pca9685_channel_reg
15 * gives a channel's register base, and ::protocore_pca9685_set_pwm_bytes emits the 5-byte channel write.
16 * On an ESP32 the binding replays those writes over I2C (Wire); only that touches hardware.
17 *
18 * A cheap solder-and-bench-test breakout for driving up to 16 servos or LEDs: wire it up, sweep
19 * a servo.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_PCA9685_H
26#define PROTOCORE_PCA9685_H
27
28#include "protocore_config.h" // the entry point: protocore_types.h for the widths
29
30#if PROTOCORE_ENABLE_PCA9685
31
33
34// PROTOCORE_I2C_DEVICE_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
35// it into its arena. A caller takes them once and passes the pointer to every call. How they
36// are carved is this module's and is never named here.
37
38#define PCA9685_CHANNELS 16 ///< PWM output channels
39
40#define PCA9685_COUNT_MAX 4095 ///< a PWM count is 12-bit (0..4095)
41
42#define PCA9685_FULL_ON 0x1000 ///< pass as `on` for a channel fully on (bit 12 flag)
43
44#define PCA9685_FULL_OFF 0x1000 ///< pass as `off` for a channel fully off (bit 12 flag)
45
46#define PCA9685_REG_MODE1 0x00
47
48#define PCA9685_REG_MODE2 0x01
49
50#define PCA9685_REG_LED0_ON_L 0x06 ///< channel 0 base; channel n is this + 4*n
51
52#define PCA9685_REG_PRESCALE 0xFE
53
54/** @brief What prescale takes: freq_hz. */
55typedef struct
56{
57 uint32_t freq_hz;
58} Pca9685PrescaleArgs;
59
60/** @brief What channel_reg takes: channel. */
61typedef struct
62{
63 uint8_t channel;
64} Pca9685ChannelRegArgs;
65
66/** @brief What us_to_count takes: microseconds, freq_hz. */
67typedef struct
68{
69 uint32_t microseconds;
70 uint32_t freq_hz;
71} Pca9685UsToCountArgs;
72
73/** @brief What set_pwm_bytes takes: buf, cap, channel, on, off. */
74typedef struct
75{
76 uint8_t *buf;
77 size_t cap;
78 uint8_t channel;
79 uint16_t on;
80 uint16_t off;
81} Pca9685SetPwmBytesArgs;
82
83/** @brief What begin takes: addr, freq_hz. */
84typedef struct
85{
86 uint8_t addr;
87 uint32_t freq_hz;
88} Pca9685BeginArgs;
89
90/** @brief What set_pwm takes: channel, on, off. */
91typedef struct
92{
93 uint8_t channel;
94 uint16_t on;
95 uint16_t off;
96} Pca9685SetPwmArgs;
97
98/** @brief What set_servo_us takes: channel, microseconds. */
99typedef struct
100{
101 uint8_t channel;
102 uint32_t microseconds;
103} Pca9685SetServoUsArgs;
104
105/**
106 * @brief NXP PCA9685 16-channel 12-bit PWM / servo driver codec (PROTOCORE_ENABLE_PCA9685).
107 *
108 * A caller sets the members a call takes, invokes it through ::Pca9685 with the bytes it runs
109 * out of, and reads the outcome off the same handle.
110 *
111 * Pca9685.prescale_args.freq_hz = ...;
112 * Pca9685.prescale(work);
113 * // Pca9685.value is what the call reports
114 *
115 * @var Pca9685Ns::prescale_args what prescale takes: freq_hz
116 * @var Pca9685Ns::channel_reg_args what channel_reg takes: channel
117 * @var Pca9685Ns::us_to_count_args what us_to_count takes: microseconds, freq_hz
118 * @var Pca9685Ns::set_pwm_bytes_args what set_pwm_bytes takes: buf, cap, channel, on, off
119 * @var Pca9685Ns::begin_args what begin takes: addr, freq_hz
120 * @var Pca9685Ns::set_pwm_args what set_pwm takes: channel, on, off
121 * @var Pca9685Ns::set_servo_us_args what set_servo_us takes: channel, microseconds
122 * @var Pca9685Ns::ok a call's true/false outcome
123 * @var Pca9685Ns::value the value a call reports
124 * @var Pca9685Ns::count what a call reports
125 * @var Pca9685Ns::n 5, or 0 if cap < 5 or channel is out of range
126 * @var Pca9685Ns::prescale compute the PRESCALE register value for a PWM output frequency (25 ...
127 * @var Pca9685Ns::channel_reg the register base (LED_ON_L) for channel (0..15); 0 for an ...
128 * @var Pca9685Ns::us_to_count convert a servo pulse width (microseconds) at freq_hz to a 12-bit ...
129 * @var Pca9685Ns::set_pwm_bytes emit the 5-byte channel PWM write: `[LED_ON_L(channel), ON_L, ON_H, ...
130 * @var Pca9685Ns::begin reset the PCA9685 at addr and set the PWM frequency freq_hz. true ...
131 * @var Pca9685Ns::set_pwm set channel's raw 12-bit ON / OFF counts. false on I2C error / bad ...
132 * @var Pca9685Ns::set_servo_us drive a servo on channel to a microseconds pulse (uses the ...
133 *
134 * @c work is PROTOCORE_I2C_DEVICE_BORROW bytes the CALLER took, at an address it knows. It is not held past the call,
135 * so nothing here aliases it. How those bytes are carved is this module's and is never named here.
136 */
137typedef struct
138{
139 Pca9685PrescaleArgs prescale_args;
140 Pca9685ChannelRegArgs channel_reg_args;
141 Pca9685UsToCountArgs us_to_count_args;
142 Pca9685SetPwmBytesArgs set_pwm_bytes_args;
143 Pca9685BeginArgs begin_args;
144 Pca9685SetPwmArgs set_pwm_args;
145 Pca9685SetServoUsArgs set_servo_us_args;
146 proto_bool ok;
147 uint8_t value;
148 uint16_t count;
149 size_t n;
150} Pca9685Vars;
151
152/** @brief The operands and the outcome. */
153extern Pca9685Vars Pca9685V;
154
155/** @brief The entries. */
156typedef struct
157{
158 void (*const prescale)(uint8_t *work);
159 void (*const channel_reg)(uint8_t *work);
160 void (*const us_to_count)(uint8_t *work);
161 void (*const set_pwm_bytes)(uint8_t *work);
162 void (*const begin)(uint8_t *work);
163 void (*const set_pwm)(uint8_t *work);
164 void (*const set_servo_us)(uint8_t *work);
165} Pca9685Ns;
166
167// What the table binds, defined once in the .c and taking one parameter each: everything
168// else an entry needs is an operand in Pca9685V or a region of the borrow at a fixed offset.
169void protocore_pca9685_prescale(uint8_t *work);
170void protocore_pca9685_channel_reg(uint8_t *work);
171void protocore_pca9685_us_to_count(uint8_t *work);
172void protocore_pca9685_set_pwm_bytes(uint8_t *work);
173void protocore_pca9685_begin(uint8_t *work);
174void protocore_pca9685_set_pwm(uint8_t *work);
175void protocore_pca9685_set_servo_us(uint8_t *work);
176
177// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
178// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
179// `Pca9685.prescale(work)` resolves to a named function and becomes a DIRECT call. An extern table
180// leaves the call indirect and the symbol live at every level, -O2 -flto included.
181static const Pca9685Ns Pca9685 __attribute__((unused)) = {
182 .prescale = protocore_pca9685_prescale,
183 .channel_reg = protocore_pca9685_channel_reg,
184 .us_to_count = protocore_pca9685_us_to_count,
185 .set_pwm_bytes = protocore_pca9685_set_pwm_bytes,
186 .begin = protocore_pca9685_begin,
187 .set_pwm = protocore_pca9685_set_pwm,
188 .set_servo_us = protocore_pca9685_set_servo_us,
189};
190
191/**
192 * @brief The PROTOCORE_I2C_DEVICE_BORROW bytes this module's state lives in.
193 *
194 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
195 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
196 * walks, so the state lasts the life of the program.
197 *
198 * @return the span.
199 */
200uint8_t *protocore_pca9685_span(void);
201
203
204#endif // PROTOCORE_ENABLE_PCA9685
205
206#endif // PROTOCORE_PCA9685_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97