Raspberry Pi I2C communications with CAT9532 16-bit Programmable LED Dimmer



I have a couple of servos on a pan/tilt bracket that I want to control from my Raspberry Pi. As I started looking at options, I read about the Adafruit 16-channel servo driver. This board is not available where I live, and I got curious about chips that generate PWM signals that could be configured via the I2C protocol. I found a cheap one (less than 2 USD) on element14 – the CAT9532 16-bit Programmable LED Dimmer chip from Catalyst Semiconductor.


This is a short post on communicating with the CAT9532 from a Raspberry Pi using Python and the smbus module.


I won’t go into I2C – this is a very popular protocol, and you can read this excellent introduction here.


smbus is the module used on Raspberry Pi to communicate with I2C devices. If you are used to sending I2C commands like START/STOP/RESTART etc., you will find smbus to be very frustrating (like I did). The commands are at a higher level, and a typical I2C sequence is “baked in”, which may or may not suite you. One particular known problem with smbus on Raspberry Pi is that it cannot send a “REPEATED START” I2C command – what this means is that you cannot communicate with popular accelerometer chips like Freescale MMA8452 which require a REPEATED START to read back data from its registers. I burned my fingers on this, trying it with another accelerometer chip, the Freescale MMA7660. You can read more about this issue here, for instance. The suggested workaround is to use software I2C (bit-banging).


But luckily, the CAT9532 does not use a REPEATED START, which means we can use Python + smbus to communicate with it.


Looking at the datasheet, the CAT9532 has 10 registers, 2 read-only and 9 read/write, which can be used to set the frequency, duty cycle, and on/off state of 16 LED pins. The base address of the chip is 0x60, but you can use 3 more pins (3 more bits) to modify it.


This is what the chip looks like in its SOIC-24 package. I used a DIP adapter PCB so I could use it on a breadboard.


 


My goals here are:

  1. Make LED 8 (green) blink at 3 Hz, at 50% duty cycle.
  2. Make LED 7 (blue) “pulse” – going from off to on and back to off slowly.

This is how I connected the hardware:

  1. A0, A1 and A2 pins (configurable part of device I2C address) are all connected to GND, as is pin VSS.
  2. Pull-up resistors of 4.7k for SDA, SCL and RESET pins of CAT9532 to +3.3V GPIO pin on Pi.
  3. Connect SDA/SCL from Pi to that of CAT9532.
  4. LED7 pin to 270 ohms to Blue LED(-), LED(+) to +5V on Pi.
  5. LED8 pin to 270 ohms to Green LED(-), LED(+) to +5V on Pi.

This is what the full setup looks like:


 


The full Python source is below. Run it from the Pi as:

sudo python rpi-cat9532.py


###############################################################################
# rpi-cat9532.py
#
# Author: electronut.in
#
# Description:
#
# Raspberry Pi communicating with I2C PWM chip CAT9532
# (16-bit Programmable LED Dimmer)
#
###############################################################################

import sys
import smbus
import time

# registers
INPUT0 = 0x00
INPUT1 = 0x01
PSC0 = 0x02
PWM0 = 0x03
PSC1 = 0x04
PWM1 = 0x05
LS0 = 0x06
LS1 = 0x07
LS2 = 0x08
LS3 = 0x09

# test based on CAT9532 data sheet example
# LED 0-3: ON
# LED 4-7: Dimming 30%, Blink 1: 152 Hz, 30% duty cycle
# LED 8-11: Blink at 2 Hz, 50% duty cycle (Blink 2)
# LED 12-15: OFF
def test1():
  bus = smbus.SMbus(1)
  addr = 0x60
  # auto increment (AI) flag is set to 0
  bus.write_byte_data(addr, 0x02, 0x00)
  bus.write_byte_data(addr, 0x03, 0x4d)
  bus.write_byte_data(addr, 0x04, 0x4b)
  bus.write_byte_data(addr, 0x05, 0x80)
  bus.write_byte_data(addr, 0x06, 0x55)
  bus.write_byte_data(addr, 0x07, 0xaa)
  bus.write_byte_data(addr, 0x08, 0xff)
  bus.write_byte_data(addr, 0x09, 0x00)
  return

# main() function
def main():
  # use sys.argv if needed
  print 'starting CAT9532 comms...'
  print 'press Ctrl-C to exit.'
  bus = smbus.SMBus(1)
  addr = 0x60

  # set Blink 0 frequency
  # T_BLINK0 = (PSC0 + 1)/152
  # 0 for always on
  bus.write_byte_data(addr, PSC0, 0)

  # set PWM0 to 50 %
  bus.write_byte_data(addr, PWM0, 128)

  # set LED 7 to Blink 0
  bus.write_byte_data(addr, LS1, 0b10000000)

  # set Blink 1 frequency
  # T_BLINK1 = (PSC1 + 1)/152
  # for f = 3 Hz, PSC0 = 152/3 - 1 = 50
  bus.write_byte_data(addr, PSC1, 50)

  # set PWM1 to 50 %                                                           
  bus.write_byte_data(addr, PWM1, 128)

  # set LED 8 to Blink 1
  bus.write_byte_data(addr, LS2, 0b00000011)

  while True:
    try:
      # generate duty cycle array for 0-255-0
      # to "pulse" the LED intensity
      vals = range(0, 256, 10)
      vals = vals + vals[::-1]
      for i in vals:
        bus.write_byte_data(addr, PWM0, i)
        time.sleep(0.025)
    except IOError, err:
      print err
    except KeyboardInterrupt:
      # turn off LEDs
      bus.write_byte_data(addr, LS1, 0x0)
      bus.write_byte_data(addr, LS2, 0x0)
      print 'bye...'
      exit()

# call main
if __name__ == '__main__':
  main()

Now that I understand how to set the PWM frequency and duty cycle via I2C, I’ll need to figure out how to drive servos with this output – probably use some MOSFETs. But that’s for another post.