pca9685 stm32 hal iic

本文详细介绍了PCA9685驱动的实现方法,包括初始化配置、设置PWM频率及占空比等关键操作,并通过具体实例展示了如何利用PCA9685控制伺服电机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pca9685:

资料:https://blog.csdn.net/c1063891514/article/details/84401045。

 

#ifndef pca9685
#define pca9685

#include "main.h"

typedef struct
{
I2C_HandleTypeDef i2cConfig;     //不需要配置
I2C_TypeDef *i2cHardwareAddress; //I2C1
uint32_t i2cSpeed;               //100000
uint8_t pca9685Address;          //pca9865的地址,从0x40~0x7f
} pca9685ConfigStruct;

void pca9685Init(pca9685ConfigStruct *pca9685ConfigStructAddress);
void pca9685ConfigPwmFreq(pca9685ConfigStruct *pca9685ConfigStructAddress, float freq);
void pca9685ConfigPwmDutyCycle(pca9685ConfigStruct *pca9685ConfigStructAddress, uint8_t number, float dutyCycle); //占空比从0~1

#endif

 

 

#include "pca9685.h"

static const uint8_t pca9685Mode1RegisterAddress = 0,
pca9685PrescaleRegisterAddress = 0xfe,
pca9685UpLRegisterAddress = 0x06,
pca9685UpMRegisterAddress = 0x07,
pca9685DownLRegisterAddress = 0x08,
pca9685DownMRegisterAddress = 0x09;


static void pca9685MspInit(pca9685ConfigStruct *pca9685ConfigStructAddress);
static void pca9685Reset(pca9685ConfigStruct *pca9685ConfigStructAddress);

void pca9685Init(pca9685ConfigStruct *pca9685ConfigStructAddress)
{
pca9685MspInit(pca9685ConfigStructAddress);
pca9685Reset(pca9685ConfigStructAddress);
}

static void pca9685MspInit(pca9685ConfigStruct *pca9685ConfigStructAddress)
{
__HAL_RCC_I2C2_CLK_ENABLE();
pca9685ConfigStructAddress->i2cConfig.Instance = pca9685ConfigStructAddress->i2cHardwareAddress;
pca9685ConfigStructAddress->i2cConfig.Init.ClockSpeed = pca9685ConfigStructAddress->i2cSpeed;
pca9685ConfigStructAddress->i2cConfig.Init.DutyCycle = I2C_DUTYCYCLE_2;
pca9685ConfigStructAddress->i2cConfig.Init.OwnAddress1 = 0;
pca9685ConfigStructAddress->i2cConfig.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
pca9685ConfigStructAddress->i2cConfig.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
pca9685ConfigStructAddress->i2cConfig.Init.OwnAddress2 = 0;
pca9685ConfigStructAddress->i2cConfig.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
pca9685ConfigStructAddress->i2cConfig.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&pca9685ConfigStructAddress->i2cConfig) != HAL_OK)
Error_Handler();

GPIO_InitTypeDef GPIO_InitStruct = {0};
switch ((uint32_t)pca9685ConfigStructAddress->i2cConfig.Instance)
    {
case (uint32_t)I2C1:
break;

case (uint32_t)I2C2:
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
break;

default:
break;
    }
}

static void pca9685Write(pca9685ConfigStruct *pca9685ConfigStructAddress, uint8_t address, uint8_t data)
{
//需要将地址手动左移一位
//发送从高位开始,开始信号后首字节为7bit addr + 1bit w/r w:0 r:1
HAL_I2C_Mem_Write(&pca9685ConfigStructAddress->i2cConfig, pca9685ConfigStructAddress->pca9685Address << 1, address, I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);
}

static uint8_t pca9685Read(pca9685ConfigStruct *pca9685ConfigStructAddress, uint8_t address)
{
uint8_t data;
//需要将地址手动左移一位
HAL_I2C_Mem_Read(&pca9685ConfigStructAddress->i2cConfig, pca9685ConfigStructAddress->pca9685Address << 1, address, I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);
//从机发数据ack是不应答的,只有接收才会应答
return data;
}

static void pca9685Reset(pca9685ConfigStruct *pca9685ConfigStructAddress)
{
pca9685Write(pca9685ConfigStructAddress, pca9685Mode1RegisterAddress, 0x00); //清空
pca9685Write(pca9685ConfigStructAddress, pca9685Mode1RegisterAddress, 0xa1); //复位 + 基本配置
}

void pca9685ConfigPwmFreq(pca9685ConfigStruct *pca9685ConfigStructAddress, float freq)
{
freq = 25000000 / 4096 / freq - 3; //-3为补偿

uint8_t oldMode = pca9685Read(pca9685ConfigStructAddress, pca9685Mode1RegisterAddress);
pca9685Write(pca9685ConfigStructAddress, pca9685Mode1RegisterAddress, (oldMode & 0x7F) | 0x10); //清除bit7防止复位 + 进入睡眠
pca9685Write(pca9685ConfigStructAddress, pca9685PrescaleRegisterAddress, (uint8_t)freq); //修改频率
pca9685Write(pca9685ConfigStructAddress, pca9685Mode1RegisterAddress, oldMode & 0xef); //解除睡眠
}

void pca9685ConfigPwmDutyCycle(pca9685ConfigStruct *pca9685ConfigStructAddress, uint8_t number, float dutyCycle)
{
uint16_t dutyCycleBackup = dutyCycle * 4096 + 0.5;
pca9685Write(pca9685ConfigStructAddress, pca9685UpLRegisterAddress + 4 * number, 0);
pca9685Write(pca9685ConfigStructAddress, pca9685UpMRegisterAddress + 4 * number, 0);
pca9685Write(pca9685ConfigStructAddress, pca9685DownLRegisterAddress + 4 * number, dutyCycleBackup);
pca9685Write(pca9685ConfigStructAddress, pca9685DownMRegisterAddress + 4 * number, dutyCycleBackup >> 8);
}

 

 

#include "main.h"
#include "stdbool.h"

#include "pca9685.h"

bool msFlag; //每过1毫秒就置true

static void systeamInit();
static void timerServe();

static void servo();

void loop()
{
systeamInit();
HAL_Delay(1000);

while (true)
    {
timerServe();
    }
}

static void timerServe()
{
if (!msFlag)
return;
msFlag = false;

servo();
}

static void servo()
{
static pca9685ConfigStruct pca9685Addr0x40 = {
        .i2cHardwareAddress = I2C2,
        .i2cSpeed = 100000,
        .pca9685Address = 0x40};

static uint8_t switchStep;
static uint16_t timer1;
switch (switchStep)
    {
case 0:
pca9685Init(&pca9685Addr0x40);
pca9685ConfigPwmFreq(&pca9685Addr0x40, 50);
switchStep++;
break;

case 1:
pca9685ConfigPwmDutyCycle(&pca9685Addr0x40, 0, 0.025); //角度0
switchStep++;
break;

case 2:
if (timer1++ >= 1000)
timer1 = 0, switchStep++;
break;

case 3:
pca9685ConfigPwmDutyCycle(&pca9685Addr0x40, 0, 0.075); //角度90
switchStep++;
break;

case 4:
if (timer1++ >= 1000)
timer1 = 0, switchStep++;
break;

case 5:
pca9685ConfigPwmDutyCycle(&pca9685Addr0x40, 0, 0.125); //角度180
switchStep++;
break;

case 6:
if (timer1++ >= 1000)
timer1 = 0, switchStep++;
break;

default:
switchStep = 1;
break;
    }
}

void SysTick_Handler()
{
HAL_IncTick();
msFlag = true;
}

static void SystemClock_Config()
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
Error_Handler();

RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
Error_Handler();
}

static void MX_GPIO_Init()
{
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}

static void systeamInit()
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
}

 

### STM32 HAL PCA9685 控制舵机教程 为了实现通过 STM32HAL 来控制 PCA9685 驱动的舵机,可以按照以下方法编写代码。以下是基于 STM32F103C8T6 和 IIC1 通信接口的一个简单示例。 #### 初始化硬件配置 STM32 使用 HAL 初始化 I2C 接口时,需指定 PA6(SCL) 和 PA7(SDA),并设置为开漏模式以支持 I2C 协议[^1]。PCA9685 默认地址为 `0x40`,可以通过寄存器操作调整 PWM 输出频率和占空比。 ```c #include "stm32f1xx_hal.h" #define PCA9685_ADDRESS 0x40 // PCA9685 设备地址 #define MODE1 0x00 // 寄存器地址:MODE1 #define PRESCALE 0xFE // 寄存器地址:预分频器 #define LED0_ON_L 0x06 // 起始PWM通道寄存器 #define ALL_LED_ON_L 0xFA // 所有LED开启低字节寄存器 #define ALL_LED_OFF_H 0xFB // 所有LED关闭高字节寄存器 // 定义I2C句柄结构体 I2C_HandleTypeDef hi2c1; void pca9685_init(void); uint8_t write_byte(uint8_t reg, uint8_t value); void set_pwm_freq(float freq); /** * @brief 设置PCA9685初始状态 */ void pca9685_init() { uint8_t mode1_reg = 0; // 停止振荡器工作 write_byte(MODE1, mode1_reg | 0x10); // 计算并写入预分频值 set_pwm_freq(50.0); // 启动振荡器 write_byte(MODE1, mode1_reg & ~0x10); } /** * @brief 写单个寄存器数据到PCA9685 * * @param reg 寄存器地址 * @param value 数据值 * @return 返回成功与否的状态码 */ uint8_t write_byte(uint8_t reg, uint8_t value) { uint8_t data[2]; data[0] = reg; // 寄存器地址 data[1] = value; // 待写入的数据 HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDRESS << 1, data, 2, HAL_MAX_DELAY); return HAL_OK; } /** * @brief 设置PCA9685的PWM频率 * * @param freq 目标频率(Hz) */ void set_pwm_freq(float freq) { float prescaleval = 25000000.0; // 振荡器频率25MHz prescaleval /= 4096.0; // 12位分辨率 prescaleval /= freq; // 目标频率 prescaleval -= 1.0; uint8_t prescale = (uint8_t)(prescaleval + 0.5); write_byte(PRESCALE, prescale); } ``` 上述代码实现了 PCA9685 的基本初始化功能,包括停止和启动振荡器以及设定 PWM 输出频率。舵机通常需要 50Hz 的信号作为输入[^2]。 #### 控制特定通道的 PWM 输出 要驱动某个通道上的舵机,可通过设置该通道对应的 ON/OFF 时间来完成: ```c void set_servo_angle(uint8_t channel, int angle) { const int min_pulse_width = 150; // 对应角度0° const int max_pulse_width = 600; // 对应角度180° int pulse_width = map(angle, 0, 180, min_pulse_width, max_pulse_width); int on_time = 0; int off_time = pulse_width * 4096 / 20000; uint8_t buf[4]; buf[0] = LED0_ON_L + 4 * channel; buf[1] = on_time & 0xFF; buf[2] = (on_time >> 8) & 0xFF; buf[3] = off_time & 0xFF; buf[4] = (off_time >> 8) & 0xFF; HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDRESS << 1, buf, 5, HAL_MAX_DELAY); } int map(int x, int in_min, int in_max, int out_min, int out_max){ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } ``` 此函数用于将目标角度映射至脉冲宽度范围,并将其转换成适合 PCA9685 的时间单位[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值