pca9685可以通过i2c通信产生16路频率相同的pwm波形,这16路pwm的脉冲宽度可以从0-100任意调整,而且一旦将数据写入寄存器后,单片机无需再关注,能极大减轻单片机的工作任务,常用于驱动由多路舵机组成的机械结构。下面通过51单片机和stm32的实例程序介绍pca9685的使用方法以及注意事项。
使用pca9685主要是两个步骤
设置pwm频率 设置pwm占空比,也就是pwm的两个最主要参数
设置频率要注意模块初次上电是工作在正常工作模式下,想要设置pwm的频率要先使模块进入休眠模式,将MODE1寄存器(地址为0x00)D4位置1,其他位可以全部置0,也就是往MODE1寄存器写入0x10
模块进入休眠模式后,频率的设置参考下图公式
osc为时钟频率,如果使用上图的模块的话,就是使用内部时钟,为25M,update_rate为你想要设置的频率,round为四舍五入,引用math.h就可以使用。比如一般舵机采用50Hz pwm,通过计算就知道,我们要向控制周期的寄存器(地址为0xfe)写入121。然后向MODE1寄存器写入0x00退出休眠模式。
接下来就可以进行第二步,设置pwm占空比了,设置占空比是通过两个10位的寄存器,但是由于i2c一次只能写入8位数据,所以12位被分为低8位和高2位,一个pwm周期由两个10位的寄存器分别控制信号的拉高和拉低,当芯片正常工作时,芯片内部的计数器会不断的自动进行加1计数,当计数值达到on时会把电平拉高,计数到off时会把电平拉低,一个pwm周期一共被分为2的11次方加2的11次方等于4096份,计数满后自动清0。
下面只介绍LED0通道占空比的设置,其他通道同理。比如想设置通道0为10%的占空比,就可以向on寄存器写入0,让一个pwm周期在刚开始的时候就是高电平,off寄存器写入410,也就是计数410后,将电平拉低,这样就实现了LED0通道50Hz 10%占空比的pwm信号
接下来就是代码部分
首先是51的,使用STC89C52RC单片机
底层I2C的头文件
#ifndef _I2C_H_
#define _I2C_H_
#include<reg52.h>
#include<intrins.h>
sbit SDA=P2^2;
sbit SCL=P2^3;
#define I2C_Delay {_nop_();_nop_();_nop_();_nop_();_nop_();}
void I2C_Start();
void I2C_Stop();
bit I2C_WriteByte(unsigned char dat);
unsigned char I2C_ReadByte();
void Send_Ack(bit ack);
bit I2C_ReceiveAck();
#endif
io口模拟I2C通信
#include "i2c.h"
void I2C_Start()
{
SCL=1;
SDA=1;
I2C_Delay;
SDA=0;
I2C_Delay;
SCL=0;
}
void I2C_Stop()
{
SDA=0;
I2C_Delay;
SCL=1;
I2C_Delay;
SDA=1;
I2C_Delay;
}
bit I2C_WriteByte(unsigned char dat)
{
bit ack;
unsigned char temp;
for(temp=0x80;temp!=0;temp>>=1)
{
if((dat&temp)==0)
{
SDA=0;
}
else
SDA=1;
I2C_Delay;
SCL=1;
I2C_Delay;
SCL=0;
}
ack=I2C_ReceiveAck();
return ack;
}
unsigned char I2C_ReadByte()
{
unsigned char dat=0;
unsigned char temp;
SDA=1;
for(temp=0x80;temp!=0;temp>>=1)
{
I2C_Delay;
SCL=1;
if(SDA==1)
{
dat|=temp;
}
else
{
dat&=~temp;
}
I2C_Delay;
SCL=0;
}
return dat;
}
void Send_Ack(bit ack)
{
SDA=ack;
I2C_Delay;
SCL=1;
I2C_Delay;
SCL=0;
}
bit I2C_ReceiveAck()
{
bit ack;
SDA=1;
I2C_Delay;
SCL=1;
ack=SDA;
I2C_Delay;
SCL=0;
return ack;
}
pca9685的头文件
#ifndef _PCA9685_H_
#define _PCA9685_H_
unsigned char pca9685_Read_Reg(unsigned char reg);
void pca9685_Write_Reg(unsigned char reg,unsigned char dat);
void pca9685_Init(unsigned char Hz);
void Set_Duty(unsigned char num,unsigned int off);
#endif
pca9685驱动函数
#include "pca9685.h"
#include "i2c.h"
#define MODE1 0x00
#define T=0xfe
void Delay1ms() //@12.000MHz
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
void Set_Duty(unsigned char num,unsigned int off);
void pca9685_Init(unsigned char Hz)
{
pca9685_Write_Reg(0x00,0x10);
pca9685_Write_Reg(0xfe,(char)((25000000/4096)/Hz)-1);
pca9685_Write_Reg(0x00,0x00);
Delay1ms();
Set_Duty(0,0);
Set_Duty(1,0);
Set_Duty(2,0);
Set_Duty(3,0);
Set_Duty(4,0);
Set_Duty(5,0);
Set_Duty(6,0);
Set_Duty(7,0);
Set_Duty(8,0);
Set_Duty(9,0);
Set_Duty(10,0);
Set_Duty(11,0);
Set_Duty(12,0);
Set_Duty(13,0);
Set_Duty(14,0);
Set_Duty(15,0);
}
unsigned char pca9685_Read_Reg(unsigned char reg)
{
unsigned char dat;
I2C_Start();
I2C_WriteByte(0x80);
I2C_WriteByte(reg);
I2C_WriteByte(0x81);
dat=I2C_ReadByte();
Send_Ack(1);
return dat;
}
void pca9685_Write_Reg(unsigned char reg,unsigned char dat)
{
I2C_Start();
I2C_WriteByte(0x80);
I2C_WriteByte(reg);
I2C_WriteByte(dat);
I2C_Stop();
}
void Set_Duty(unsigned char num,unsigned int off)//占空比乘上4096为off的值
{
pca9685_Write_Reg(num*4+6,0);
pca9685_Write_Reg(num*4+7,0);
pca9685_Write_Reg(num*4+8,off&0xff);
pca9685_Write_Reg(num*4+9,off>>=8);
}
main函数就比较简单了,这里也是让pca9685输出50Hz 10%占空比的pwm信号
#include "pca9685.h"
#include "i2c.h"
#include "reg52.h"
void main()
{
pca9685_Init(50);
Set_Duty(0,410);
while(1)
{
}
}
下面是proteus仿真的电路图
这里有个小插曲,这个软件对电脑性能还是有一定要求的,刚开始我用了示波器和I2C调试器,示波器只能显示I2C通信的波形,不能显示pca9685输出的pwm,后面把I2C调试器去掉后,就可以显示波形了。
51到此结束
接下来是stm32的,使用stm32f103c8t6单片机
官方固件库
由于stm32的硬件I2C容易卡死,所以这里仍然使用io口模拟I2C通信时序
I2C头文件
#ifndef _I2C_H_
#define _I2C_H_
void I2C_Start(void);
unsigned char I2C_Send(unsigned char dat);
char I2C_Receive_Ack(void);
char I2C_Read_Byte(unsigned char ack);
void I2C_Send_Ack(unsigned char ack);
void I2C_Stop(void);
void IIC_Init(void);
#endif
底层I2C通信
#include "i2c.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "delay.h"
void I2C_Send_Ack(unsigned char ack);
void IIC_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_SetBits(GPIOB,GPIO_Pin_0);//PB0为SDA
GPIO_SetBits(GPIOB,GPIO_Pin_1);//PB1为SCL
}
void I2C_Start(void)
{
GPIO_SetBits(GPIOB,GPIO_Pin_0);//PB0为SDA
GPIO_SetBits(GPIOB,GPIO_Pin_1);//PB1为SCL
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//SDA拉低
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
GPIO_SetBits(GPIOB,GPIO_Pin_0);
delay_nus(50);
}
unsigned char I2C_Send(unsigned char dat)
{
unsigned char ack=1;
for(unsigned char i=0x80;i!=0;i>>=1)
{
if(dat&i)
{
GPIO_SetBits(GPIOB,GPIO_Pin_0);//SDA拉高
delay_nus(50);
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
delay_nus(50);
}
else
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//SDA拉低
delay_nus(50);
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
GPIO_SetBits(GPIOB,GPIO_Pin_0);//SDA释放
delay_nus(50);
}
}
ack=I2C_Receive_Ack();
return ack;
}
char I2C_Receive_Ack(void)
{
unsigned char ack=1;
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
ack=GPIOB->IDR&1<<0;
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
delay_nus(50);
return ack;
}
char I2C_Read_Byte(unsigned char ack)
{
unsigned char Dat,i;
for(i=0;i<8;i++)
{
Dat<<=1;
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
Dat|=(GPIOB->IDR&1<<0);
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
delay_nus(50);
}
if(ack==1)
{
I2C_Send_Ack(1);
}
else if(ack==0)
{
I2C_Send_Ack(0);
}
return Dat;
}
void I2C_Send_Ack(unsigned char ack)
{
if(ack==0)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//SDA拉低
}
else if(ack==1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_0);
}
delay_nus(50);
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);//SCL拉低
delay_nus(50);
GPIO_SetBits(GPIOB,GPIO_Pin_0);//SDA释放
}
void I2C_Stop(void)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_0);//SDA拉低
GPIO_SetBits(GPIOB,GPIO_Pin_1);//SCL拉高
delay_nus(50);
GPIO_SetBits(GPIOB,GPIO_Pin_0);//SDA释放
}
pca9685的头文件
#ifndef _PCA9685_H_
#define _PCA9685_H_
void PCA9685_Init(unsigned char Hz);
unsigned char PCA9685_Read_Reg(unsigned char Reg);
void PCA9685_Write_Reg(unsigned char Reg,unsigned char Data);
void Set_PWM(unsigned char num,unsigned int Duty);
#endif
pca9685的驱动函数
#include "i2c.h"
#include "delay.h"
#include "math.h"
#define MODE1 0x00
#define T 0xfe
void PCA9685_Write_Reg(unsigned char Reg,unsigned char Data);
void PCA9685_Init(unsigned char Hz)
{
unsigned char prescale=0;
IIC_Init();
PCA9685_Write_Reg(MODE1,0x10);
prescale=round((25000000/4096)/Hz)-1;
PCA9685_Write_Reg(T,prescale);
PCA9685_Write_Reg(MODE1,0x00);
delay_nms(1);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
Set_PWM(0,0);
}
unsigned char PCA9685_Read_Reg(unsigned char Reg)
{
unsigned char Dat;
I2C_Start();
I2C_Send(0x80);
I2C_Send(Reg);
I2C_Start();
I2C_Send(0x81);
Dat=I2C_Read_Byte(1);
I2C_Stop();
return Dat;
}
void PCA9685_Write_Reg(unsigned char Reg,unsigned char Data)
{
I2C_Start();
I2C_Send(0x80);
I2C_Send(Reg);
I2C_Send(Data);
I2C_Stop();
}
void Set_PWM(unsigned char num,unsigned int off)//占空比乘上4096就是off的值
{
PCA9685_Write_Reg(num*4+6,0);
PCA9685_Write_Reg(num*4+7,0);
PCA9685_Write_Reg(num*4+8,off&0xff);
PCA9685_Write_Reg(num*4+9,off>>=8);
}
main函数,LDD0通道产生50Hz,1%占空比的pwm信号
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "i2c.h"
#include "delay.h"
#include "pca9685.h"
int main()
{
IIC_Init();
PCA9685_Init(50);
Set_PWM(0,41);
//PCA9685_Write_Reg(0x06,0);
//PCA9685_Write_Reg(0x07,0);
//PCA9685_Write_Reg(0x08,0x0b);
//PCA9685_Write_Reg(0x09,0x08);
while(1)
{
}
}
下面是stm32的proteus仿真电路,由于proteus不能仿真stm32f103c8,这里用stm32f103c6代替,同样也要注意如果电脑性能一般就不要同时用示波器和I2C调试器了
文章来源:https://www.toymoban.com/news/detail-404859.html
第一次写博客,如果有哪里不对的地方,欢迎大佬批评指正,以后也会不断更新关于其他芯片的教程。文章来源地址https://www.toymoban.com/news/detail-404859.html
到了这里,关于pca9685使用教程以及proteus仿真的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!