一直在用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管脚。