esp32-servo/lib/pac9685/pca9685.c

162 lines
5.6 KiB
C
Raw Normal View History

2025-08-21 14:44:24 +08:00
#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);
}