This guide provides complete, working examples demonstrating various use cases for the PCA9685 driver.
Example 1: Basic Servo Control
This example shows how to control a standard servo motor using the PCA9685.
#include "pca9685.hpp"
#include "driver/i2c.h"
class Esp32I2cBus : public pca9685::I2cInterface<Esp32I2cBus> {
public:
bool EnsureInitialized() noexcept { return true; }
bool Write(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) noexcept {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_write(cmd, (uint8_t*)data, len, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret == ESP_OK;
}
bool Read(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) noexcept {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, data, len, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret == ESP_OK;
}
};
extern "C" void app_main() {
Esp32I2cBus i2c;
pca9685::PCA9685<Esp32I2cBus> pwm(&i2c, 0x40);
if (!pwm.Reset()) {
printf("Failed to initialize PCA9685!\n");
return;
}
pwm.SetPwmFreq(50.0f);
pwm.SetDuty(0, 0.075f);
pwm.SetDuty(0, 0.10f);
pwm.SetDuty(0, 0.05f);
printf("Servo control example complete.\n");
}
Explanation
- I2C Interface: Implement
I2cInterface for your platform (ESP32 example shown)
- Initialization: Call
Reset() to initialize the device
- Frequency: Set 50 Hz for standard servos (20ms period)
- Duty Cycle: Use
SetDuty() with values 0.05-0.10 for typical servo range
- 0.05 = 1.0ms pulse (0 degrees)
- 0.075 = 1.5ms pulse (center/90 degrees)
- 0.10 = 2.0ms pulse (180 degrees)
Expected Output
The servo should move to different positions as the duty cycle changes.
Example 3: Multiple Channels
This example shows controlling multiple channels simultaneously.
#include "pca9685.hpp"
extern "C" void app_main() {
Esp32I2cBus i2c;
pca9685::PCA9685<Esp32I2cBus> pwm(&i2c, 0x40);
pwm.Reset();
pwm.SetPwmFreq(1000.0f);
pwm.SetDuty(0, 0.25f);
pwm.SetDuty(1, 0.50f);
pwm.SetDuty(2, 0.75f);
pwm.SetDuty(3, 1.0f);
for (uint8_t ch = 4; ch < 16; ch++) {
pwm.SetDuty(ch, 0.5f);
}
}
Explanation
Each channel operates independently. You can set different duty cycles for each of the 16 channels.
Example 5: Bulk Channel Update
This example shows efficient bulk updates using SetAllPwm().
#include "pca9685.hpp"
extern "C" void app_main() {
Esp32I2cBus i2c;
pca9685::PCA9685<Esp32I2cBus> pwm(&i2c, 0x40);
pwm.Reset();
pwm.SetPwmFreq(1000.0f);
pwm.SetAllPwm(0, 2048);
pwm.SetAllPwm(0, 0);
pwm.SetAllPwm(0, 4095);
}
Explanation
SetAllPwm() is more efficient than calling SetPwm() 16 times. It writes to the ALL_LED registers, updating all channels in a single I2C transaction.
Running the Examples
ESP32 Applications
The ESP32 example project provides two applications, built and flashed via scripts (not raw idf.py):
| Application | Description |
| pca9685_comprehensive_test | Full driver test suite: init, frequency, PWM, duty cycle, all-channel control, prescale readback, sleep/wake, output config, error handling, stress tests (12 tests total). |
| pca9685_servo_demo | 16-channel hobby servo demo: 1000–2000 µs pulse, velocity-limited motion, synchronized animations (Wave, Breathe, Cascade, Mirror, etc.). |
Prerequisites: ESP-IDF (e.g. release/v5.5), target e.g. esp32s3. From the repo root:
cd examples/esp32
chmod +x scripts/*.sh
./scripts/setup_repo.sh # first time only
./scripts/build_app.sh list
Build:
./scripts/build_app.sh pca9685_comprehensive_test Debug
# or
./scripts/build_app.sh pca9685_servo_demo Debug
Flash and monitor:
./scripts/flash_app.sh flash_monitor pca9685_comprehensive_test Debug
# or
./scripts/flash_app.sh flash_monitor pca9685_servo_demo Debug
I2C pins: Default SDA=GPIO4, SCL=GPIO5. Override at configure time if needed:
./scripts/build_app.sh pca9685_comprehensive_test Debug \
-- -DPCA9685_EXAMPLE_I2C_SDA_GPIO=8 -DPCA9685_EXAMPLE_I2C_SCL_GPIO=9
See examples/esp32/README.md and examples/esp32/docs/ for hardware setup and app details.
Other Platforms
Adapt the I2C interface implementation for your platform (see Platform Integration) and compile with your platform's toolchain. The ESP32 implementation in examples/esp32/main/esp32_pca9685_bus.hpp uses ESP-IDF's driver/i2c_master.h (new I2C master API) as a reference.
Next Steps