PCA9685与 NXP1768单片机iic通信,扩展PWM端口。已调试成功。

一直在用NXP的单片机做各种开发, 最近遇到了这么个问题,就是单片机的PWM端口不够用,公司硬件工程师推荐使用NXP的PCA9685芯片扩展,PCA9685 是一款 I2C 总线接口的 16 位 LED 控制器。一个芯片可以扩展出16路PWM,我们用了两个,成功扩展出32路PWM端口,再也不用使用GPIO去模拟了。由于硬件设计板子,投版,制版流程太慢,所以我在淘宝上买了个PCA9685的开发板,很便宜,十几块钱。

切入正题,怎么使用PCA9685扩展PWM端口呢?此芯片是通过IIC与其他MCU通信的,程序参考了http://bbs.elecfans.com/forum.php?mod=viewthread&tid=1104135&extra=page=1&orderby=dateline有关51单片机驱动程序修改的有兴趣可以看看,下面是修改后的程序。

接线方法:VCC -3.3V  给芯片供电,GND-接地 SCL-接单片机SCL(时钟)

//PCA9685 扩展芯片相关
#define PCA9685_I2C_ID        1
#define PCA9685_adrr 0x80//  1+A5+A4+A3+A2+A1+A0+w/r   最后一位0代表写地址,1代表读地址
                                            //片选地址,将焊接点置1可改变地址,
                                            // 需要注意的是芯片的IIC地址在不做任何焊接的情况下是0X80,有些淘宝卖家认为是0X40。
#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

 

/*****************************************************************************
** Function name:         PCA9685_write
** Descriptions:        向PCA9685写地址或者数据
** parameters:        id:i2c number
**                            address: Chip or register address
                               data:Data to be written
** Returned value:        none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void PCA9685_write(KC_UINT8 id, KC_UINT8 address,KC_UINT8 data)
{
    i2c_start(id);
    i2c_write_byte(id,PCA9685_adrr);        //PCA9685的片选地址
                        
    i2c_write_byte(id,address);  //写地址控制字节
    
    i2c_write_byte(id,data);          //写数据
    
    i2c_stop(id);
}

/*****************************************************************************
** Function name:         PCA9685_read
** Descriptions:        Read data from the address value in PCA9685
** parameters:        id:i2c number
**                                    address: Chip or register address               
** Returned value:        The data that was read
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
KC_UINT8 PCA9685_read(KC_UINT8 id, KC_UINT8 address)
{
    KC_UINT8 data;
    i2c_start(id);
    i2c_write_byte(id,PCA9685_adrr); //PCA9685的片选地址
    
    i2c_write_byte(id,address);
    
    i2c_start(id);
    i2c_write_byte(id,PCA9685_adrr|0x01);        //地址的第八位控制数据流方向,就是写或读

    data=i2c_read_byte(id);
    i2c_stop(id);
    return data;
}
/*****************************************************************************
** Function name:         PCA9685_reset
** Descriptions:         PCA9685_reset
** parameters:         none
** Returned value:         none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void PCA9685_reset(void)
{
    SCL_OUT(PCA9685_I2C_ID);
    SCLL(PCA9685_I2C_ID);
    i2c_delay(I2C_DELAY_TIME);
    i2c_stop(PCA9685_I2C_ID);
    i2c_delay(I2C_DELAY_TIME);
    PCA9685_write(PCA9685_I2C_ID,PCA9685_MODE1,0x0);
}

/*****************************************************************************
** Function name:         PCA9685_begin
** Descriptions:         PCA9685_begin
** parameters:         none
** Returned value:         none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void PCA9685_begin(void)
{
    PCA9685_reset();
}

/*****************************************************************************
** Function name:         setPWMFreq
** Descriptions:        Set PWM frequency
** parameters:        freq:PWM frequency
** Returned value:        none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void setPWMFreq(float freq)
{
    KC_UINT8 prescale,oldmode,newmode;
    float prescaleval;
    freq *= 0.92;  // Correct for overshoot in the frequency setting
    prescaleval = 25000000;
    prescaleval /= 4096;
    prescaleval /= freq;
    prescaleval -= 1;
    prescale = floor(prescaleval + 0.5);

    oldmode = PCA9685_read(PCA9685_I2C_ID,PCA9685_MODE1);
    newmode = (oldmode&0x7F) | 0x10; // sleep
    PCA9685_write(PCA9685_I2C_ID,PCA9685_MODE1, newmode); // go to sleep
    PCA9685_write(PCA9685_I2C_ID,PCA9685_PRESCALE, prescale); // set the prescaler
    PCA9685_write(PCA9685_I2C_ID,PCA9685_MODE1, oldmode);
    i2c_delay(I2C_DELAY_TIME);
    PCA9685_write(PCA9685_I2C_ID,PCA9685_MODE1, oldmode | 0xa1);
}

/*****************************************************************************
** Function name:         setPWM
** Descriptions:        Set PWM frequency
** parameters:        num:PWM输出引脚0~15,
                    on:PWM上升计数值0~4096,
                    off:PWM下降计数值0~4096
                    一个PWM周期分成4096份,由0开始+1计数,计到on时跳变为高电平,继续计数到off时
                    跳变为低电平,直到计满4096重新开始。所以当on不等于0时可作延时,当on等于0时,
                    off/4096的值就是PWM的占空比。                                         
** Returned value:        none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void setPWM(KC_UINT8 num, KC_UINT16 on, KC_UINT16 off)
{
    PCA9685_write(PCA9685_I2C_ID,LED0_ON_L+4*num,on);
    PCA9685_write(PCA9685_I2C_ID,LED0_ON_H+4*num,on>>8);
    PCA9685_write(PCA9685_I2C_ID,LED0_OFF_L+4*num,off);
    PCA9685_write(PCA9685_I2C_ID,LED0_OFF_H+4*num,off>>8);
}
/*****************************************************************************
** Function name:         PCA9685_init
** Descriptions:        PCA9685 chip init
** parameters:        freq:PWM frequency
** Returned value:        none
** Author:             joelv
** Date:                06/20/2020
*****************************************************************************/
void PCA9685_init(KC_UINT8 freq)
{
        PCA9685_begin();
        setPWMFreq(freq);
}

PCA9685_write();PCA9685_read();i2c_start();i2c_stop();i2c_wait_ack();等函数都是使用公司内部iic驱动程序,不便公布,请读者谅解。但这些都是基础的iic驱动程序,读者只需要根据IIC时序图自己编写或者查找其他单片机的IIC驱动(51,STM32等等的很普遍),另一方面,我所使用的单片机也不常见,一般人也用不到。有兴趣的开发者可使用适用的单片机驱动替换即可使用。

在使用时,仅需要调用初始化函数,PWM频率我当时设置为140HZ,然后调用SetPWM()函数即可控制各个PWM管脚。


版权声明:本文为joelvqzx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。