Issues with PCA9685 module on Nucleo_F446ZE, MBed 6.0

Hi Guys,

I am struggling to get a PCA9685 I2C PWM module to work on a Nucleo_F446ZE board - my first venture into I2C in this world, and the PCA9685 is new to me. Using MBed Studio.

I did get the servo working using PWMOut, so I think servo and board are working okay. The PCA9685 module is untested but I have no reason to think it is faulty.

As this is a learning exercise for me I am doing it ‘on-the-chip’, ‘straight line’ and not invoking any library code classes, but I have cut and pasted from a few classes on this website to help me with learning - Big thanks to Paulo Sanna

I am fairly sure I have the hookup, addressing and I2C setup working as the I2C write()s are returning 0. I did try an invalid I2C address and it hung on the write(), so I think the issue is in what I am actually writing to the module, and not the basic setup.

In the config of the chip it seemed to me I only needed to be concerned about the MODE1 register (Ox00) and the PRE_SCALE register (0xFE).

The configuration seems to me reasonably straightforward and I think I understand most of it, except the sections on sleep mode and restart where I am not 100% comfortable. I am also not sure if the auto increment bit needs to be set for the multi-byte write() to function properly. I suspect not but I set it anyway.

When I run it nothing happens - no servo sweep at all.

Any tips in diagnosing the issues or help as to what could be wrong would be appreciated.

Thanks - Peter

Diagnostic output with notes

====================start================
==Reset==
Write (hex values): 0 0 Write returned: 0
==set PWM==
Write for Read: 0 Write() returned: 0
Read() returned: 0 Value= 0
Write (hex values): 0 10 Write returned: 0 <Set Sleep bit 4>
Sleep 5 ms
Write (hex values): fe 79 Write returned: 0 <Set PRE_SCALE to 121>
Write (hex values): 0 0 Write returned: 0
Sleep 5 ms
Write (hex values): 0 a1 Write returned: 0 <Set restart, AI, All Call>
==Loop== No 1
Write 5 (dec values): 6 0 0 205 0 Write() returned: 0 <Servo 0 on 0 Off 205>
Write 5 (dec values): 6 0 0 154 1 Write() returned: 0 <Servo 0 on 0 off 410>
==Loop== No 2
Write 5 (dec values): 6 0 0 205 0 Write() returned: 0
Write 5 (dec values): 6 0 0 154 1 Write() returned: 0
==Loop== No 3
Write 5 (dec values): 6 0 0 205 0 Write() returned: 0
Write 5 (dec values): 6 0 0 154 1 Write() returned: 0
==Loop== No 4
Write 5 (dec values): 6 0 0 205 0 Write() returned: 0
Write 5 (dec values): 6 0 0 154 1 Write() returned: 0

Code

// test PCA9685 module with servo 
#include "mbed.h"

#define PCA9685_MODE1 0x0
#define PCA9685_PRESCALE 0xFE
#define TIMING_BLOCK_START 0x06

int addr8bit = 0x40 << 1;     // default 8-bit I2C address, 0x80 
uint8_t retVal ; 
I2C i2c(I2C_SDA , I2C_SCL);         // create an I2C object using specified pins 

void write8(uint8_t address, uint8_t data)
{
    char cmd[2];
    cmd[0] = address;
    cmd[1] = data;
    printf ("Write (hex values): %x %x ", cmd[0], cmd[1]) ; 
    retVal = i2c.write(addr8bit, cmd, 2);
    printf (" Write returned: %d \n", retVal) ; 
}
 
char read8(char address)
{
    printf ("Write for Read: %x ", address) ; 
    retVal = i2c.write(addr8bit, &address, 1);
    printf ("  Write() returned: %d \n", retVal) ; 
    char rtn;
    retVal = i2c.read(addr8bit, &rtn, 1);
    printf ("Read() returned: %x Value= %x \n", retVal, rtn) ; 
    return rtn;
}

void reset(void)
{
    write8(PCA9685_MODE1, 0x0);
}

void setPrescale(uint8_t prescale) 
{
    uint8_t oldmode = read8(PCA9685_MODE1);
    uint8_t newmode = (oldmode & 0x7F) | 0x10; // sleep
    write8(PCA9685_MODE1, newmode); // go to sleep
    printf ("Sleep 5 ms \n") ; 
    ThisThread::sleep_for(chrono::milliseconds(5)) ; 
    write8(PCA9685_PRESCALE, prescale); // set the prescaler
    write8(PCA9685_MODE1, oldmode);
    printf ("Sleep 5 ms \n") ; 
    ThisThread::sleep_for(chrono::milliseconds(5)) ; 
    write8(PCA9685_MODE1, oldmode | 0xa1);
}

void setPWMFreq(float freq)
{
    float prescaleval = 25000000;
    prescaleval /= 4096;
    prescaleval /= freq;
    uint8_t prescale = floor(prescaleval  + 0.5) - 1;
    setPrescale(prescale);
}

// function to set servo number (0-15) on and off count values 
// Range 0-4095 count for 1 ms = 205 count for 2 ms = 410
void setServoOnOff(uint8_t num, uint16_t on, uint16_t off)
{
    char cmd[5] ;   // 

    cmd[0] = TIMING_BLOCK_START + (4 * num) ; // address of the low byte of servo 
    cmd[1] = on;                     // low byte of on value 
    cmd[2] = (on >> 8);              // high byte of on 
    cmd[3] = off;                    // low byte of off  
    cmd[4] = (off >> 8);             // high byte of off 
    // note: passing cmd with no index passes an array pointer - required 
    printf ("Write 5 (dec values): %d %d %d %d %d ", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4]) ; 
    retVal = i2c.write(addr8bit, cmd, 5);     // write the address and 4 bytes   
    printf ("Write() returned: %d \n", retVal) ; 
}

int main() 
{   
    printf("====================start================ \n") ; 
    i2c.frequency(400000) ; // set the bus frequency 
    printf("==Reset== \n") ; 
    reset() ; 
    printf("==set PWM== \n") ;
    setPWMFreq(50.0) ; 
   
    uint8_t loopCount = 1 ; 
    while (true) 
    {
        printf("==Loop== No  %d \n", loopCount ) ; 
        setServoOnOff(0, 0, 205) ; 
        ThisThread::sleep_for(chrono::milliseconds(300)) ;  // let servo move 
        
        setServoOnOff(0, 0, 410) ; 
        ThisThread::sleep_for(chrono::milliseconds(300)) ;  // let servo move 

        loopCount ++ ; 
        if (loopCount > 4) 
            return 0 ; 
    }
}