一、vscode
1.安装ESPIDF插件
2.下载espidf库
这里我就不过多赘述了,有很多文章将这个,有的人一次成功,有的人重装系统也成功不了,我很幸运属于一次成功那种,还使用了两种不同的方法,下载了两个版本。我这次用的是v4.4.6,芯片是esp32s3.
参考:如何在VSCode搭建ESP-IDF开发ESP32_vscode创建esp32-CSDN博客
二、16路PWM舵机驱动板(PCA 9685)
1.iic设备从机地址
提到iic就不得不先说他的重要概念,分为主机从机。现在我们要使用esp32驱动PCA 9685,那么PCA 9685就是从机必然有个从机地址。
如上图右上角与下图对应:我将与A0对应的焊盘焊上那么A0=1
那么我的从机地址是1000001=0x41
2.直接上代码吧
寄存器等介绍参考:STM32--PCA9685驱动(16路舵机驱动模块)_16路舵机控制板pca9685-CSDN博客
.c文件,该代码由Arduino驱动库改编而来
#include "PCA9685_IIC_PWMServoDriver.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"
#include <math.h>
#define PCA9685_SUBADR1 0x2
#define PCA9685_SUBADR2 0x3
#define PCA9685_SUBADR3 0x4
#define PCA9685_MODE1 0x0
#define PCA9685_PRESCALE 0xFE
#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9
#define ALLLED_ON_L 0xFA
#define ALLLED_ON_H 0xFB
#define ALLLED_OFF_L 0xFC
#define ALLLED_OFF_H 0xFD
#define PCA9685_IIC_ADDR 0x41//PCA9685的地址
#define PCA9685_IIC_SDA 17 //SDA引脚定义,根据实际连接修改
#define PCA9685_IIC_SCL 18 //SCL引脚定义,根据实际连接修改
#define PCA9685_IIC_port I2C_NUM_1
#define PCA9685_IIC_FREQ 100000 /* 标准模式(100 kbit/s) */ //PCA9685工作频率:40-1000HZ
// Set to true to print some debug messages, or false to disable them.
#define ENABLE_DEBUG_OUTPUT false
//ESP32 IIC INITIALIZATION FUNCTION
/*函数名宏*/
#define read8 PCA9685_IIC_PWMServoDriver_read8
#define write8 PCA9685_IIC_PWMServoDriver_write8
#define setPWM PCA9685_IIC_PWMServoDriver_setPWM
#define reset PCA9685_IIC_PWMServoDriver_reset
static const char *TAG = "PCA9685_IIC_PWMServoDriver";
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */
void PCA9685_IIC_PWMServoDriver_Init(void) {
i2c_port_t i2c_master_port = PCA9685_IIC_port ;//IIC1
static i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = PCA9685_IIC_SDA, // select GPIO specific to your project
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = PCA9685_IIC_SCL, // select GPIO specific to your project
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = PCA9685_IIC_FREQ, // select frequency specific to your project
// .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */
};
i2c_param_config(i2c_master_port, &conf);
ESP_ERROR_CHECK(i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE , I2C_MASTER_TX_BUF_DISABLE, 0));//
printf("IIC INITIALIZATION SUCCESSFUL\n");
}
void PCA9685_IIC_PWMServoDriver_write8(uint8_t addr, uint8_t d){
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();//创建IIC命令链表
i2c_master_start(i2c_cmd);//IIC开始信号发送
i2c_master_write_byte(i2c_cmd, (PCA9685_IIC_ADDR << 1) | I2C_MASTER_WRITE, true);//IIC写入地址和写入模式
i2c_master_write_byte(i2c_cmd, addr, true);//IIC写入寄存器地址
i2c_master_write_byte(i2c_cmd, d, true);//IIC写入数据
i2c_master_stop(i2c_cmd);//IIC停止信号发送
i2c_master_cmd_begin(PCA9685_IIC_port, i2c_cmd, 1000 / portTICK_RATE_MS);//IIC命令链表开始执行,等待1000ms超时时间
i2c_cmd_link_delete(i2c_cmd);//删除IIC命令链表
}
uint8_t PCA9685_IIC_PWMServoDriver_read8(uint8_t addr) {
uint8_t read_data;
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();//创建IIC命令链表
i2c_master_start(i2c_cmd);//IIC开始信号发送
i2c_master_write_byte(i2c_cmd, (PCA9685_IIC_ADDR << 1) | I2C_MASTER_READ, true);//IIC写入地址和写入模式
i2c_master_read_byte(i2c_cmd, &read_data, true);//IIC读取数据
i2c_master_stop(i2c_cmd);//IIC停止信号发送
i2c_master_cmd_begin(PCA9685_IIC_port, i2c_cmd, 1000 / portTICK_RATE_MS);//IIC命令链表开始执行,等待1000ms超时时间
i2c_cmd_link_delete(i2c_cmd);//删除IIC命令链表
return read_data;
}
// void PCA9685_IIC_PWMServoDriver_reset(void) {
// PCA9685_IIC_PWMServoDriver_write8(PCA9685_MODE1, 0x0);
// }
void PCA9685_IIC_PWMServoDriver_reset(void) {
write8(PCA9685_MODE1, 0x0);
}
void PCA9685_IIC_PWMServoDriver_setPWMFreq(float freq) {
//Serial.print("Attempting to set freq ");
//Serial.println(freq);
freq *= 0.9; // Correct for overshoot in the frequency setting (see issue #11).
float prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
if (ENABLE_DEBUG_OUTPUT) {
ESP_LOGI(TAG,"Estimated pre-scale: %f\n",prescaleval);
}
uint8_t prescale = floor(prescaleval + 0.5);
if (ENABLE_DEBUG_OUTPUT) {
ESP_LOGI(TAG,"Final pre-scale: %d\n",prescale);
}
uint8_t oldmode = read8(PCA9685_MODE1);
uint8_t newmode = (oldmode&0x7F) | 0x10; // sleep
write8(PCA9685_MODE1, newmode); // go to sleep
write8(PCA9685_PRESCALE, prescale); // set the prescaler
write8(PCA9685_MODE1, oldmode);
vTaskDelay(5/portTICK_PERIOD_MS); // wait for oscillator
write8(PCA9685_MODE1, oldmode | 0xa1); // This sets the MODE1 register to turn on auto increment.
// This is why the beginTransmission below was not working.
// Serial.print("Mode now 0x"); Serial.println(read8(PCA9685_MODE1), HEX);
}
void PCA9685_IIC_PWMServoDriver_setPWM(uint8_t num, uint16_t on, uint16_t off) {
//Serial.print("Setting PWM "); Serial.print(num); Serial.print(": "); Serial.print(on); Serial.print("->"); Serial.println(off);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();//创建IIC命令链表
i2c_master_start(i2c_cmd);//IIC开始信号发送
i2c_master_write_byte(i2c_cmd, (PCA9685_IIC_ADDR << 1) | I2C_MASTER_WRITE, true);//IIC写入地址和写入模式
i2c_master_write_byte(i2c_cmd, LED0_ON_L+4*num, true);//IIC写入寄存器地址
i2c_master_write_byte(i2c_cmd, on, true);//IIC写入数据
i2c_master_write_byte(i2c_cmd, on>>8, true);//IIC写入数据
i2c_master_write_byte(i2c_cmd, off, true);//IIC写入数据
i2c_master_write_byte(i2c_cmd, off>>8, true);//IIC写入数据
i2c_master_stop(i2c_cmd);//IIC停止信号发送
i2c_master_cmd_begin(PCA9685_IIC_port, i2c_cmd, 1000 / portTICK_RATE_MS);//IIC命令链表开始执行,等待1000ms超时时间
i2c_cmd_link_delete(i2c_cmd);//删除IIC命令链表
}
// Sets pin without having to deal with on/off tick placement and properly handles
// a zero value as completely off. Optional invert parameter supports inverting
// the pulse for sinking to ground. Val should be a value from 0 to 4095 inclusive.
//设置引脚,而不必处理开/关刻度位置和正确处理
// 0值表示完全关闭。可选的invert参数支持逆变
//下沉到地面的脉冲。Val应该是0到4095之间的值。
uint16_t min(uint16_t a, uint16_t b) {
return (a < b) ? a : b;
}
void PCA9685_IIC_PWMServoDriver_setPin(uint8_t num, uint16_t val, bool invert)
{
// Clamp value between 0 and 4095 inclusive.
val = min(val, 4095);
if (invert) {
if (val == 0) {
// Special value for signal fully on.
setPWM(num, 4096, 0);
}
else if (val == 4095) {
// Special value for signal fully off.
setPWM(num, 0, 4096);
}
else {
setPWM(num, 0, 4095-val);
}
}
else {
if (val == 4095) {
// Special value for signal fully on.
setPWM(num, 4096, 0);
}
else if (val == 0) {
// Special value for signal fully off.
setPWM(num, 0, 4096);
}
else {
setPWM(num, 0, val);
}
}
}
void PCA9685_IIC_PWMServoDriver_begin(void) {
PCA9685_IIC_PWMServoDriver_Init();
reset();
PCA9685_IIC_PWMServoDriver_setPWMFreq(50);
}
.h
/*
* @Author: lonesix 111976810+lonesix@users.noreply.github.com
* @Date: 2024-04-23 15:05:23
* @LastEditors: lonesix 111976810+lonesix@users.noreply.github.com
* @LastEditTime: 2024-04-23 17:17:29
* @FilePath: \voiceHelperDemoV2\main\servo\PCA9685_IIC_PWMServoDriver.h
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#ifndef _PCA9685_IIC_PWMSERVODRIVER_H_
#define _PCA9685_IIC_PWMSERVODRIVER_H_
// 在 .h 文件中声明:
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// void my_cpp_func(void);
void PCA9685_IIC_PWMServoDriver_begin(void);
void PCA9685_IIC_PWMServoDriver_setPin(uint8_t num, uint16_t val, bool invert);
void PCA9685_IIC_PWMServoDriver_setPWM(uint8_t num, uint16_t on, uint16_t off);
#ifdef __cplusplus
}
#endif
// // 在 .cpp 文件中进行定义:
// extern "C" void my_cpp_func(void) {
// // ...
// }
#endif /*_PCA9685_IIC_PWMSERVODRIVER_H_ */
3.测试例程
随便写的效果不是太好
/*
* @Author: lonesix 111976810+lonesix@users.noreply.github.com
* @Date: 2024-04-23 17:18:14
* @LastEditors: lonesix 111976810+lonesix@users.noreply.github.com
* @LastEditTime: 2024-04-24 15:19:37
* @FilePath: \voiceHelperDemoV2\main\servo\servo_task.c
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "servo_task.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#define SERVOMIN 4096*(0.5/20) // this is the 'minimum' pulse length count (out of 4096)
//这是“最小”脉冲长度计数(在4096)中
#define SERVOMAX 4096*(2.5/20) // this is the 'maximum' pulse length count (out of 4096)
//这是“最大”脉冲长度计数(在4096中)
void servo_task(void *pvParameters){
PCA9685_IIC_PWMServoDriver_begin();
while(1){
for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; ) {
PCA9685_IIC_PWMServoDriver_setPWM(0, 0, pulselen);
vTaskDelay(50 / portTICK_PERIOD_MS); // 延时1毫秒
pulselen=pulselen+4096/180; // 增加脉冲长度,以控制舵机的角度变化
}
for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; ) {
PCA9685_IIC_PWMServoDriver_setPWM(0, 0, pulselen);
vTaskDelay(50 / portTICK_PERIOD_MS); // 延时1毫秒
pulselen=pulselen-4096/180; // 减少脉冲长度,以控制舵机的角度变化
}
}
}
void Action_management(){
xTaskCreate(servo_task, "servo_task", 4096, NULL, 5, NULL); // 创建servo_task任务,优先级为5,堆栈大小为2048字节。
}
4.直接角度驱动
早期版本凑合用
该函数放置于二、2的.c文件中
void PCA9685setAngle(uint8_t num,uint8_t angle)
{
uint32_t off = 0;
if (angle<=90 && angle>=0)
{
/* code */
// printf("angle:%d\n !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",angle);
// off = (uint32_t)(102.4+angle*2.28);
off=(uint32_t)(4095*((angle/180.0)*2.0+0.5)/20.0);
setPWM(num,0,off);
}else if (angle>90 && angle<=180){
// off = (uint32_t)(512-(180-angle)*2.28);
off=(uint32_t)(4095*((angle/180.0)*2.0+0.5)/20.0);
setPWM(num,0,off);
}
// printf("off:%d\n !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",off);
}