一、PCA9685介绍
PCA9685是一种16通道PWM(脉宽调制)控制器芯片,由NXP Semiconductors公司生产。它具有高速I2C总线接口,可以通过I2C总线与微控制器或其他设备进行通信。PCA9685广泛应用于各种需要精确控制多路PWM信号的应用,如LED灯控制、舵机控制、电机控制等。
PCA9685芯片具有以下主要特点:
- 16个独立的PWM输出通道,每个通道可以独立设置频率和占空比。
- 可以设置PWM输出频率的范围从24Hz到1526Hz。
- 可以通过I2C接口进行配置和控制,使得系统设计更加简单。
- 支持输出使能和禁用功能,方便对PWM信号进行控制。
- 高精度的PWM输出,可以实现精确的电平控制。
- 内部集成了晶振,无需外部元件。
PCA9685芯片的工作原理是通过调整PWM的占空比来控制输出信号的电平。用户可以设置PWM输出频率和占空比,通过改变占空比来改变输出信号的电平。此外,PCA9685还具有外部触发功能,可以通过外部触发引脚来触发特定的操作或事件。
总之,PCA9685是一款功能丰富且易于使用的PWM控制器芯片,适用于各种需要精确控制多路PWM信号的应用。它的高精度、高可靠性和简单的接口使其成为许多电子设备和机器人应用的理想选择。
二、PCA9685原理图
三、源码
iic.h
#ifndef _MYIIC_H
#define _MYIIC_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOG->MODER&=~(3<<(13*2));GPIOG->MODER|=0<<13*2;} //PH5输入模式
#define SDA_OUT() {GPIOG->MODER&=~(3<<(13*2));GPIOG->MODER|=1<<13*2;} //PH5输出模式
//IO操作
#define IIC_SCL(n) (n?HAL_GPIO_WritePin(GPIOG,GPIO_PIN_14,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOG,GPIO_PIN_14,GPIO_PIN_RESET)) //SCL
#define IIC_SDA(n) (n?HAL_GPIO_WritePin(GPIOG,GPIO_PIN_13,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOG,GPIO_PIN_13,GPIO_PIN_RESET)) //SDA
#define READ_SDA HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_13) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif
iic.c
#include "myiic.h"
#include "delay.h"
//IIC初始化
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOG_CLK_ENABLE(); //使能GPIOH时钟
//PH4,5初始化设置
GPIO_Initure.Pin=GPIO_PIN_14|GPIO_PIN_13;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //快速
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
IIC_SDA(1);
IIC_SCL(1);
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA(1);
IIC_SCL(1);
delay_us(4);
IIC_SDA(0);//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL(0);//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL(0);
IIC_SDA(0);//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL(1);
IIC_SDA(1);//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA(1);delay_us(1);
IIC_SCL(1);delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL(0);//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(0);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(1);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL(0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA((txd&0x80)>>7);
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL(0);
delay_us(2);
IIC_SCL(1);
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
pca9685.h
#ifndef __NCA9555_H
#define __NCA9555_H
#include "sys.h"
#include "myiic.h"
#define PCA9685_MODE1 0x00
#define PCA9685_PRESCALE 0xFE
#define LED0_ON_L 0x06
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
void pca9685_init(u8 slaveAddr,float hz,uint8_t angle);
void pca9685_set_servo_angle(u8 slaveAddr,uint8_t num,uint8_t angle);
#endif
pca9685.c
#include "pca9685.h"
#include "delay.h"
#include "math.h"
void pca9685_write(u8 slaveAddr,u8 adrr,u8 data)//向PCA写数据,adrrd地址,data数据
{
IIC_Start();
IIC_Send_Byte(slaveAddr);
IIC_Wait_Ack();
IIC_Send_Byte(adrr);
IIC_Wait_Ack();
IIC_Send_Byte(data);
IIC_Wait_Ack();
IIC_Stop();
}
u8 pca9685_read(u8 slaveAddr,u8 adrr)//从PCA读数据
{
u8 data;
IIC_Start();
IIC_Send_Byte(slaveAddr);
IIC_Wait_Ack();
IIC_Send_Byte(adrr);
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(slaveAddr|0x01);
IIC_Wait_Ack();
data=IIC_Read_Byte(0);
IIC_Stop();
return data;
}
void pca9685_set_freq(u8 slaveAddr,float freq)//设置PWM频率
{
uint8_t prescale,oldmode,newmode;
double prescaleval;
freq *= 0.83f; //实际使用过程中存在偏差需要×矫正系数=0.83
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale =floor(prescaleval + 0.5f); //floor向下取整函数
oldmode = pca9685_read(slaveAddr,PCA9685_MODE1);
newmode = (oldmode&0x7F) | 0x10; // sleep睡眠
pca9685_write(slaveAddr,PCA9685_MODE1, newmode); // go to sleep(需要进入随眠状态才能设置频率)
pca9685_write(slaveAddr,PCA9685_PRESCALE, prescale); // 设置预分频系数
pca9685_write(slaveAddr,PCA9685_MODE1, oldmode);
HAL_Delay(2);
pca9685_write(slaveAddr,PCA9685_MODE1, oldmode | 0xA1);
}
/*num:舵机PWM输出引脚0~15,on:PWM上升计数值0~4096,off:PWM下降计数值0~4096*/
void pca9685_set_duty(u8 slaveAddr,uint8_t num, uint32_t on, uint32_t off)
{
pca9685_write(slaveAddr,LED0_ON_L+4*num,on);
pca9685_write(slaveAddr,LED0_ON_H+4*num,on>>8);
pca9685_write(slaveAddr,LED0_OFF_L+4*num,off);
pca9685_write(slaveAddr,LED0_OFF_H+4*num,off>>8);
}
/*函数作用:初始化舵机驱动板参数:1.PWM频率2.初始化舵机角度*/
void pca9685_init(u8 slaveAddr,float hz,uint8_t angle)
{
uint32_t off=0;
pca9685_write(slaveAddr,PCA9685_MODE1,0x0);
pca9685_set_freq(slaveAddr,hz);//设置PWM频率
off=(uint32_t)(145+angle*2.4);
pca9685_set_duty(slaveAddr,0,0,off);
pca9685_set_duty(slaveAddr,1,0,off);
pca9685_set_duty(slaveAddr,2,0,off);
pca9685_set_duty(slaveAddr,3,0,off);
pca9685_set_duty(slaveAddr,4,0,off);
pca9685_set_duty(slaveAddr,5,0,off);
pca9685_set_duty(slaveAddr,6,0,off);
pca9685_set_duty(slaveAddr,7,0,off);
pca9685_set_duty(slaveAddr,8,0,off);
pca9685_set_duty(slaveAddr,9,0,off);
pca9685_set_duty(slaveAddr,10,0,off);
pca9685_set_duty(slaveAddr,11,0,off);
pca9685_set_duty(slaveAddr,12,0,off);
pca9685_set_duty(slaveAddr,13,0,off);
pca9685_set_duty(slaveAddr,14,0,off);
pca9685_set_duty(slaveAddr,15,0,off);
}
/*函数作用:控制舵机转动;参数:1.输出端口,可选0~15; 2.结束角度,可选0~180;*/
void pca9685_set_servo_angle(u8 slaveAddr,uint8_t num,uint8_t angle)
{
uint32_t off=0;
off=(uint32_t)(158+angle*2.2);
pca9685_set_duty(slaveAddr,num,0,off);
}