PCA9685 I2C 转16路PWM

 

目录

 

1 基本介绍

1.1 控制精度

2 硬件参数

2.1 封装及引脚排列

2.2 器件地址

2.3 寄存器地址

2.3.1 MODE1寄存器

2.3.2 MODE2寄存器

2.4 PWM通道寄存器及其占空比设置

2.5 PWM周期(频率)寄存器及其周期(频率)设置

2.6 推荐硬件设计

3.软件设计 

1 基本介绍

该IC主要参数特征如下:

  • I2C接口控制即可控制16路PWM支持高达16路舵机或PWM输出,每路12位分辨率(4096级)
  • 内置25MHz晶振,可不连接外部晶振,也可以连接外部晶振,最大50MHz
  • 支持2.3V-5.5V电压,最大耐压值5.5V
  • 具有上电复位,以及软件复位等功能

1.1 控制精度

假设舵机为50HZ的频率,脉宽为0.5ms~2.5ms,12位分辨率(4096级),相关计算如下:


PWM周期:PCA9685 I2C 转16路PWM

时间分辨率:PCA9685 I2C 转16路PWM

最大脉宽时间:PCA9685 I2C 转16路PWM

最大脉宽时间可分成的份数:PCA9685 I2C 转16路PWM

0-180度的舵机,角度分辨率:PCA9685 I2C 转16路PWM


2 硬件参数

2.1 封装及引脚排列

PCA9685有两种封装:TSSOP28, HVQFN28,其相应的引脚排列如图1所示:

PCA9685 I2C 转16路PWM
图1 PCA9685引脚排列图

2.2 器件地址

PCA9685的器件地址是由引脚A0,A1,A2,A3,A4,A5共同决定,并且该引脚不可悬空,由于有6个引脚共同决定器件地址,因此,可以有64个器件地址,由于该IC上电便保留LED All Call address (E0h,1110 000)以及Software Reset address(06h,0000 0110),实际仅有62个可用器件地址,因此,理论上,1个I2C接口可控制PCA9685 I2C 转16路PWM路PWM,其引脚控制器件地址的示意图如图2所示:

PCA9685 I2C 转16路PWM
图2 引脚控制寄存器地址示意图

默认情况下,若将A0-A5全部接地,则其器件地址为:0x40。

2.3 寄存器地址

默认情况下,上电复位后,寄存器地址的默认值均为0,寄存器地址机器用途见图3所示:

PCA9685 I2C 转16路PWM
图3.1 PCA9685寄存器地址1
PCA9685 I2C 转16路PWM
图3.2 PCA9685寄存器地址2

由图3可知,主要寄存器地址为MODE1,MODE2地址,每一个PWM通道占用4个寄存器地址,用于设置脉宽(占空比),PRE_SCALE寄存器用于设置周期(频率),接下来将介绍MODE1,MODE2寄存器,PMM通道寄存器,PRESCALE寄存器 。

2.3.1 MODE1寄存器

首先介绍MODE1寄存器,见图4:

PCA9685 I2C 转16路PWM
图4 MODE1寄存器

 在使用该寄存器的时候要注意:

  • 如果未停止所有PWM输出就将其进入到睡眠模式,那么,所有输出通道在下一轮都将输出高电平。
  • 睡眠后重新启动PWM的操作为:

PCA9685 I2C 转16路PWM

  •  注意,在设置PWM频率(写PRESCALE寄存器)的时候,要先设置为Sleep模式,请参考后面源码部分。

2.3.2 MODE2寄存器

该寄存器的各位功能如图5所示:

PCA9685 I2C 转16路PWM
图5 MODE2寄存器

2.4 PWM通道寄存器及其占空比设置

PWM通道寄存器如下:

PCA9685 I2C 转16路PWM
图6 PWM通道寄存器

由图6可知,对于每一个通道,有4个寄存器,每个寄存器图解如图7所示:

PCA9685 I2C 转16路PWM
图7 通道寄存器图解

 在设置PWM占空比的时候,首先,配置舵机的示例如图8所示(ON < OFF的情况):

PCA9685 I2C 转16路PWM
图8 配置舵机PWM占空比

当特殊情况下,PWM周期大于定时器一次计数时,如图9所示(ON>OFF的情况):

  

PCA9685 I2C 转16路PWM
图9 ON>OFF的情况

 

2.5 PWM周期(频率)寄存器及其周期(频率)设置

接下来介绍配置PWM频率的寄存器:

一般情况下,在用内置晶振,为25MHZ,通过配置PRE_SCALE寄存器进行配置,配置的PRE_SCALE寄存器的值与PWM频率的关系如图10所示:

PCA9685 I2C 转16路PWM
图10 寄存器值与PWM频率的关系

 

如果在舵机中,采用内置晶振,则osc_clock=25000000,update_rate=50

2.6 推荐硬件设计

首先,OE引脚须接低电平以确保IC使能,如果连接LED灯,则推荐以下几种连接方式,如图11所示:

PCA9685 I2C 转16路PWM
图11 连接LED时几种推荐的方式

3.软件设计 

接下来进行软件设计部分讲解,由于本次开发采用Micro:bit底层开发,采用的是TypeScript(JavaScript的超类),所以暂提供该语言,提供基本操作方法及其思路,日后再更新C,C++及其它平台(STM32,Linux树莓派,Arduino等)操作方法,源码如下,请结合DataSheet及以上教程理解:

/**
 * 使用此文件来定义自定义函数和图形块。
 * 想了解更详细的信息,请前往 https://makecode.microbit.org/blocks/custom
 */

/**
 * 自定义图形块
 */
//% weight=5 color=#0fbc11 icon="\uf113"
namespace Servo {
    const PCA9685_ADDRESS = 0x40
    const MODE1 = 0x00
    const MODE2 = 0x01
    const SUBADR1 = 0x02
    const SUBADR2 = 0x03
    const SUBADR3 = 0x04
    const PRESCALE = 0xFE
    const LED0_ON_L = 0x06
    const LED0_ON_H = 0x07
    const LED0_OFF_L = 0x08
    const LED0_OFF_H = 0x09
    const ALL_LED_ON_L = 0xFA
    const ALL_LED_ON_H = 0xFB
    const ALL_LED_OFF_L = 0xFC
    const ALL_LED_OFF_H = 0xFD

    const STP_CHA_L = 2047
    const STP_CHA_H = 4095

    const STP_CHB_L = 1
    const STP_CHB_H = 2047

    const STP_CHC_L = 1023
    const STP_CHC_H = 3071

    const STP_CHD_L = 3071
    const STP_CHD_H = 1023

    let initialized = false

    function i2cwrite(addr: number, reg: number, value: number) {
        let buf = pins.createBuffer(2)
        buf[0] = reg
        buf[1] = value
        pins.i2cWriteBuffer(addr, buf)
    }

    function i2cread(addr: number, reg: number) {
        pins.i2cWriteNumber(addr, reg, NumberFormat.UInt8BE);
        let val = pins.i2cReadNumber(addr, NumberFormat.UInt8BE);
        return val;
    }

    function initPCA9685(): void {
        i2cwrite(PCA9685_ADDRESS, MODE1, 0x00)
        setFreq(50);
        setPwm(0, 0, 4095);
        for (let idx = 1; idx < 16; idx++) {
            setPwm(idx, 0, 0);
        }
        initialized = true
    }

    function setFreq(freq: number): void {
        // Constrain the frequency
        let prescaleval = 25000000;
        prescaleval /= 4096;
        prescaleval /= freq;
        prescaleval -= 1;
        let prescale = prescaleval; //Math.Floor(prescaleval + 0.5);
        let oldmode = i2cread(PCA9685_ADDRESS, MODE1);
        let newmode = (oldmode & 0x7F) | 0x10; // sleep
        i2cwrite(PCA9685_ADDRESS, MODE1, newmode); // go to sleep
        i2cwrite(PCA9685_ADDRESS, PRESCALE, prescale); // set the prescaler
        i2cwrite(PCA9685_ADDRESS, MODE1, oldmode);
        control.waitMicros(5000);
        i2cwrite(PCA9685_ADDRESS, MODE1, oldmode | 0xa1);
    }

    function setPwm(channel: number, on: number, off: number): void {
        if (channel < 0 || channel > 15)
            return;

        let buf = pins.createBuffer(5);
        buf[0] = LED0_ON_L + 4 * channel;
        buf[1] = on & 0xff;
        buf[2] = (on >> 8) & 0xff;
        buf[3] = off & 0xff;
        buf[4] = (off >> 8) & 0xff;
        pins.i2cWriteBuffer(PCA9685_ADDRESS, buf);
    }

	/**
	 * Servo Execute
	 * @param degree [0-180] degree of servo; eg: 90, 0, 180
	*/
    //% blockId=setServo block="Servo channel|%channel|degree %degree"
    //% weight=85
    //% degree.min=0 degree.max=180
    export function Servo(channel: number,degree: number): void {
		if (!initialized) {
            initPCA9685();
        }
		// 50hz: 20,000 us
        let v_us = (degree * 1800 / 180 + 600); // 0.6 ~ 2.4
        let value = v_us * 4096 / 20000;
        setPwm(channel, 0, value);
    }
	
	/**
	 * Servo Execute
	 * @param pulse [500-2500] pulse of servo; eg: 1500, 500, 2500
	*/
    //% blockId=setServoPulse block="Servo channel|%channel|pulse %pulse"
    //% weight=85
    //% pulse.min=500 pulse.max=2500
    export function ServoPulse(channel: number,pulse: number): void {
		if (!initialized) {
            initPCA9685();
        }
		// 50hz: 20,000 us
        let value = pulse * 4096 / 20000;
        setPwm(channel, 0, value);
    }
} 

 以上便是Micro:bit驱动PCA9685的源代码,注意源代码中的时间为us,而教程中的时间为ms。


2018年10月15日更新,树莓派平台Python程序:

要运行该程序,首先选装python,安装好Python后,还需要安装树莓派平台的smbus库:

sudo apt-get install python-smbus

树莓派平台采用Python驱动PCA9685的Python代码如下所示:

#!/usr/bin/python

import time
import math
import smbus

# ============================================================================
# Raspi PCA9685 16-Channel PWM Servo Driver
# ============================================================================

class PCA9685:

  # Registers/etc.
  __SUBADR1            = 0x02
  __SUBADR2            = 0x03
  __SUBADR3            = 0x04
  __MODE1              = 0x00
  __PRESCALE           = 0xFE
  __LED0_ON_L          = 0x06
  __LED0_ON_H          = 0x07
  __LED0_OFF_L         = 0x08
  __LED0_OFF_H         = 0x09
  __ALLLED_ON_L        = 0xFA
  __ALLLED_ON_H        = 0xFB
  __ALLLED_OFF_L       = 0xFC
  __ALLLED_OFF_H       = 0xFD

  def __init__(self, address=0x40, debug=False):
    self.bus = smbus.SMBus(1)
    self.address = address
    self.debug = debug
    if (self.debug):
      print("Reseting PCA9685")
    self.write(self.__MODE1, 0x00)
	
  def write(self, reg, value):
    "Writes an 8-bit value to the specified register/address"
    self.bus.write_byte_data(self.address, reg, value)
    if (self.debug):
      print("I2C: Write 0x%02X to register 0x%02X" % (value, reg))
	  
  def read(self, reg):
    "Read an unsigned byte from the I2C device"
    result = self.bus.read_byte_data(self.address, reg)
    if (self.debug):
      print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg))
    return result
	
  def setPWMFreq(self, freq):
    "Sets the PWM frequency"
    prescaleval = 25000000.0    # 25MHz
    prescaleval /= 4096.0       # 12-bit
    prescaleval /= float(freq)
    prescaleval -= 1.0
    if (self.debug):
      print("Setting PWM frequency to %d Hz" % freq)
      print("Estimated pre-scale: %d" % prescaleval)
    prescale = math.floor(prescaleval + 0.5)
    if (self.debug):
      print("Final pre-scale: %d" % prescale)

    oldmode = self.read(self.__MODE1);
    newmode = (oldmode & 0x7F) | 0x10        # sleep
    self.write(self.__MODE1, newmode)        # go to sleep
    self.write(self.__PRESCALE, int(math.floor(prescale)))
    self.write(self.__MODE1, oldmode)
    time.sleep(0.005)
    self.write(self.__MODE1, oldmode | 0x80)

  def setPWM(self, channel, on, off):
    "Sets a single PWM channel"
    self.write(self.__LED0_ON_L+4*channel, on & 0xFF)
    self.write(self.__LED0_ON_H+4*channel, on >> 8)
    self.write(self.__LED0_OFF_L+4*channel, off & 0xFF)
    self.write(self.__LED0_OFF_H+4*channel, off >> 8)
    if (self.debug):
      print("channel: %d  LED_ON: %d LED_OFF: %d" % (channel,on,off))
	  
  def setServoPulse(self, channel, pulse):
    "Sets the Servo Pulse,The PWM frequency must be 50HZ"
    pulse = pulse*4096/20000        #PWM frequency is 50HZ,the period is 20000us
    self.setPWM(channel, 0, pulse)

if __name__=='__main__':
 
  pwm = PCA9685(0x40, debug=True)
  pwm.setPWMFreq(50)
  while True:
   # setServoPulse(2,2500)
    for i in range(500,2500,10):  
      pwm.setServoPulse(0,i)   
      time.sleep(0.02)     
    
    for i in range(2500,500,-10):
      pwm.setServoPulse(0,i) 
      time.sleep(0.02)  

保存文件命名为: pca9685.py,命令行进入该文件所在的路径,运行该Python脚本:

sudo python pca9685.py

执行该命令后,便可控制舵机从PCA9685 I2C 转16路PWM转到PCA9685 I2C 转16路PWM,在从PCA9685 I2C 转16路PWM转到PCA9685 I2C 转16路PWM