esp32-servo/lib/pac9685/pca9685.c
2025-08-21 14:44:24 +08:00

162 lines
5.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "pca9685.h"
#include "esp_log.h"
#include "esp_err.h"
#include <math.h>
static const char *TAG = "PCA9685";
static esp_err_t write8(i2c_port_t i2c_num, uint8_t addr, uint8_t reg, uint8_t val) {
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_byte(cmd, val, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "write8 failed: reg=0x%02X val=0x%02X, err=0x%X", reg, val, ret);
}
return ret;
}
static uint8_t read8(i2c_port_t i2c_num, uint8_t addr, uint8_t reg) {
uint8_t val = 0;
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);
// 重复START条件
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, true);
// 读取数据 (NACK最后一个字节)
i2c_master_read_byte(cmd, &val, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "read8 failed: reg=0x%02X, err=0x%X", reg, ret);
}
return val;
}
static esp_err_t set_pwm(i2c_port_t i2c_num, uint8_t addr, uint8_t channel, uint16_t on, uint16_t off) {
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, LED0_ON_L + 4 * channel, true);
i2c_master_write_byte(cmd, on & 0xFF, true);
i2c_master_write_byte(cmd, on >> 8, true);
i2c_master_write_byte(cmd, off & 0xFF, true);
i2c_master_write_byte(cmd, off >> 8, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "set_pwm failed: ch=%d, err=0x%X", channel, ret);
}
return ret;
}
void pca9685_init(i2c_port_t i2c_num, uint8_t addr) {
// 完整初始化序列
write8(i2c_num, addr, MODE1, MODE1_SLEEP | MODE1_ALLCALL);
vTaskDelay(50 / portTICK_PERIOD_MS); // 延长延迟
// 唤醒设备并启用自动增量
write8(i2c_num, addr, MODE1, MODE1_AI | MODE1_ALLCALL);
vTaskDelay(10 / portTICK_PERIOD_MS);
// 关闭所有LED输出
write8(i2c_num, addr, ALL_LED_OFF_H, 0x10); // 设置所有LED_OFF_H为0x10
ESP_LOGI(TAG, "PCA9685 at 0x%02X initialized", addr);
}
void pca9685_set_pwm_freq(i2c_port_t i2c_num, uint8_t addr, float freq) {
//待优化
// 计算预分频值 (25MHz时钟)
float prescaleval = 25000000.0;
prescaleval /= 4096.0; // 12位分辨率
prescaleval /= freq;
prescaleval -= 1.0;
uint8_t prescale = (uint8_t)(prescaleval + 0.5);
ESP_LOGI(TAG, "Setting PWM freq: %.1f Hz, prescale: %d", freq, prescale);
// 进入睡眠模式设置预分频
uint8_t oldmode = read8(i2c_num, addr, MODE1);
uint8_t sleepmode = (oldmode & ~MODE1_RESTART) | MODE1_SLEEP;
write8(i2c_num, addr, MODE1, sleepmode);
vTaskDelay(5 / portTICK_PERIOD_MS);
// 设置预分频寄存器
write8(i2c_num, addr, PRESCALE, prescale);
// 恢复模式并唤醒
write8(i2c_num, addr, MODE1, oldmode);
vTaskDelay(5 / portTICK_PERIOD_MS);
// 触发重启
write8(i2c_num, addr, MODE1, oldmode | MODE1_RESTART);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
void pca9685_set_angle(i2c_port_t i2c_num, uint8_t addr, uint8_t channel, float angle) {
// 角度限幅
angle = (angle < 0) ? 0 : (angle > 180) ? 180 : angle;
// 计算脉冲宽度 (0.5ms - 2.5ms)
float pulse_width_ms = 0.5f + (angle / 180.0f) * 2.0f;
// 计算PWM值 (20ms周期)
uint16_t pwm_value = (uint16_t)((pulse_width_ms / 20.0f) * 4096.0f);
// 设置PWM (ON=0, OFF=pwm_value)
esp_err_t ret = set_pwm(i2c_num, addr, channel, 0, pwm_value);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Set addr=0x%02X ch=%d: %d° -> PWM=%d",
addr, channel, (int)angle, pwm_value);
} else {
ESP_LOGE(TAG, "Failed to set angle on addr=0x%02X ch=%d", addr, channel);
}
}
// 一次性更新多个通道的示例(在 pca9685.c 新增)
void pca9685_set_angles(i2c_port_t i2c_num, uint8_t addr,
uint8_t start_ch, uint8_t count,
const float *angles) {
// 准备一大块 data每通道 4 字节 ON_L,ON_H,OFF_L,OFF_H
uint8_t data[4 * count];
for (int i = 0; i < count; i++) {
float a = angles[i];
a = a < 0 ? 0 : a > 180 ? 180 : a;
float pw = 0.5f + (a/180.0f)*2.0f; // ms
uint16_t pwm = (uint16_t)((pw/20.0f)*4096);
data[4*i + 0] = 0; // ON_L
data[4*i + 1] = 0; // ON_H
data[4*i + 2] = pwm & 0xFF;// OFF_L
data[4*i + 3] = pwm >> 8; // OFF_H
}
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
// 写入寄存器地址LED0_ON_L + 4*start_ch自动递增模式
i2c_master_write_byte(cmd, (addr<<1)|I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, LED0_ON_L + 4*start_ch, true);
// 一次性写出所有 data
i2c_master_write(cmd, data, sizeof(data), true);
i2c_master_stop(cmd);
i2c_master_cmd_begin(i2c_num, cmd, 1000/portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
}