pca9685使用教程以及proteus仿真

pca9685可以通过i2c通信产生16路频率相同的pwm波形,这16路pwm的脉冲宽度可以从0-100任意调整,而且一旦将数据写入寄存器后,单片机无需再关注,能极大减轻单片机的工作任务,常用于驱动由多路舵机组成的机械结构。下面通过51单片机和stm32的实例程序介绍pca9685的使用方法以及注意事项。40bc381984c342afbebf1b1fb0fbd2c0.jpg

 使用pca9685主要是两个步骤

设置pwm频率 设置pwm占空比,也就是pwm的两个最主要参数

设置频率要注意模块初次上电是工作在正常工作模式下,想要设置pwm的频率要先使模块进入休眠模式,将MODE1寄存器(地址为0x00)D4位置1,其他位可以全部置0,也就是往MODE1寄存器写入0x1009d50f227d104bed9f2dc6cd457a47d4.jpg

 模块进入休眠模式后,频率的设置参考下图公式ae7e8b8688564bd8853324f2f83dcd93.jpg

 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信号6ecd13f604b1400f8d0c8ae9cbbe0dc1.jpg

 接下来就是代码部分

首先是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仿真的电路图

9f75e1d862264f63834d6d9bbcbc8fe2.png

 这里有个小插曲,这个软件对电脑性能还是有一定要求的,刚开始我用了示波器和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调试器了

f5b0d9be71b14354a5e0bbc5eccff5e7.png

 第一次写博客,如果有哪里不对的地方,欢迎大佬批评指正,以后也会不断更新关于其他芯片的教程。

物联沃分享整理
物联沃-IOTWORD物联网 » pca9685使用教程以及proteus仿真

发表评论