A feature complete driver for the PCA9952/55 LED driver from NXP.

Dependents:   PCA9955_HelloWorld

PCA9955.cpp

Committer:
neilt6
Date:
2013-11-08
Revision:
4:6ca7ab31c5fb
Parent:
1:016f916c5579
Child:
5:7ad949955db8

File content as of revision 4:6ca7ab31c5fb:

/* PCA9955 Driver Library
 * Copyright (c) 2013 Neil Thiessen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PCA9955.h"

PCA9955::PCA9955(PinName sda, PinName scl, Address addr) : m_I2C(sda, scl), m_ADDR((int)addr)
{
    //Set the I2C bus frequency to 400kHz
    m_I2C.frequency(400000);
}

PCA9955::PCA9955(PinName sda, PinName scl, int addr) : m_I2C(sda, scl), m_ADDR(addr)
{
    //Set the I2C bus frequency to 400kHz
    m_I2C.frequency(400000);
}

bool PCA9955::open()
{
    //Probe for the PCA9952/55 using a Zero Length Transfer
    if (!m_I2C.write(m_ADDR, NULL, 0)) {
        //NOTE: We don't issue a SWRST here since it might reset other I2C devices as well

        //Read the current 8-bit register value
        char value = read(REG_MODE1);

        //Configure Auto-Increment for 0x00 to 0x41
        value &= ~(1 << 5); //AI0 bit
        value &= ~(1 << 6); //AI1 bit

        //Write the value back out
        write(REG_MODE1, value);

        //Return success
        return true;
    } else {
        //Return failure
        return false;
    }
}

void PCA9955::reset()
{
    //The SWRST magic data byte
    char data = 0x06;

    //Issue the SWRST call to the General Call address
    m_I2C.write(0x00, &data, 1);

    //Wait for 10ms to allow the device to reset
    wait_ms(10);
}

bool PCA9955::allCallEnabled()
{
    //Read the 8-bit register value
    char value = read(REG_MODE1);

    //Return the status of the ALLCALL bit
    if (value & (1 << 0))
        return true;
    else
        return false;
}

void PCA9955::allCallEnabled(bool enabled)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE1);

    //Set or clear the ALLCALL bit
    if (enabled)
        value |= (1 << 0);
    else
        value &= ~(1 << 0);

    //Write the value back out
    write(REG_MODE1, value);
}

bool PCA9955::subCall3Enabled()
{
    //Read the 8-bit register value
    char value = read(REG_MODE1);

    //Return the status of the SUB3 bit
    if (value & (1 << 1))
        return true;
    else
        return false;
}

void PCA9955::subCall3Enabled(bool enabled)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE1);

    //Set or clear the SUB3 bit
    if (enabled)
        value |= (1 << 1);
    else
        value &= ~(1 << 1);

    //Write the value back out
    write(REG_MODE1, value);
}

bool PCA9955::subCall2Enabled()
{
    //Read the 8-bit register value
    char value = read(REG_MODE1);

    //Return the status of the SUB2 bit
    if (value & (1 << 2))
        return true;
    else
        return false;
}

void PCA9955::subCall2Enabled(bool enabled)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE1);

    //Set or clear the SUB2 bit
    if (enabled)
        value |= (1 << 2);
    else
        value &= ~(1 << 2);

    //Write the value back out
    write(REG_MODE1, value);
}

bool PCA9955::subCall1Enabled()
{
    //Read the 8-bit register value
    char value = read(REG_MODE1);

    //Return the status of the SUB1 bit
    if (value & (1 << 3))
        return true;
    else
        return false;
}

void PCA9955::subCall1Enabled(bool enabled)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE1);

    //Set or clear the SUB1 bit
    if (enabled)
        value |= (1 << 3);
    else
        value &= ~(1 << 3);

    //Write the value back out
    write(REG_MODE1, value);
}

PCA9955::PowerMode PCA9955::powerMode()
{
    //Read the 8-bit register value
    char value = read(REG_MODE1);

    //Return the status of the SLEEP bit
    if (value & (1 << 4))
        return POWER_SHUTDOWN;
    else
        return POWER_NORMAL;
}

void PCA9955::powerMode(PowerMode mode)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE1);

    //Set or clear the SLEEP bit
    if (mode == POWER_SHUTDOWN)
        value |= (1 << 4);
    else
        value &= ~(1 << 4);

    //Write the value back out
    write(REG_MODE1, value);
}

PCA9955::OutputChangeMode PCA9955::outputChangeMode()
{
    //Read the 8-bit register value
    char value = read(REG_MODE2);

    //Return the status of the OCH bit
    if (value & (1 << 3))
        return OUTPUT_CHANGE_ON_ACK;
    else
        return OUTPUT_CHANGE_ON_STOP;
}

void PCA9955::outputChangeMode(OutputChangeMode mode)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE2);

    //Set or clear the OCH bit
    if (mode == OUTPUT_CHANGE_ON_ACK)
        value |= (1 << 3);
    else
        value &= ~(1 << 3);

    //Write the value back out
    write(REG_MODE2, value);
}

PCA9955::GroupMode PCA9955::groupMode()
{
    //Read the 8-bit register value
    char value = read(REG_MODE2);

    //Return the status of the DMBLNK bit
    if (value & (1 << 5))
        return GROUP_BLINKING;
    else
        return GROUP_DIMMING;
}

void PCA9955::groupMode(GroupMode mode)
{
    //Read the current 8-bit register value
    char value = read(REG_MODE2);

    //Set or clear the DMBLNK bit
    if (mode == GROUP_BLINKING)
        value |= (1 << 5);
    else
        value &= ~(1 << 5);

    //Write the value back out
    write(REG_MODE2, value);
}

bool PCA9955::overTemp()
{
    //Read the current 8-bit register value
    char value = read(REG_MODE2);

    //Return the status of the OVERTEMP bit
    if (value & (1 << 7))
        return true;
    else
        return false;
}

PCA9955::OutputState PCA9955::outputState(Output output)
{
    char value;
    char reg;

    //Determine which register to read
    if (output < 4) {
        reg = REG_LEDOUT0;
    } else if (output < 8) {
        output = (Output)(output - 4);
        reg = REG_LEDOUT1;
    } else if (output < 12) {
        output = (Output)(output - 8);
        reg = REG_LEDOUT2;
    } else {
        output = (Output)(output - 12);
        reg = REG_LEDOUT3;
    }

    //Read the 8-bit register value
    value = read(reg);

    //Shift and mask the other output states
    value = (value >> (output * 2)) & 0x03;

    //Return the selected output's state
    if (value == 0)
        return OUTPUT_OFF;
    else if (value == 1)
        return OUTPUT_ON;
    else if (value == 2)
        return OUTPUT_PWM;
    else
        return OUTPUT_PWM_GRPPWM;
}

void PCA9955::outputState(Output output, OutputState state)
{
    char value;
    char reg;

    //Determine which register to read
    if (output < 4) {
        reg = REG_LEDOUT0;
    } else if (output < 8) {
        output = (Output)(output - 4);
        reg = REG_LEDOUT1;
    } else if (output < 12) {
        output = (Output)(output - 8);
        reg = REG_LEDOUT2;
    } else {
        output = (Output)(output - 12);
        reg = REG_LEDOUT3;
    }

    //Read the current 8-bit register value
    value = read(reg);

    //Mask off the old output state (also turns the output off)
    value &= ~(0x03 << (output * 2));

    //Add the new output state
    if (state == OUTPUT_ON)
        value |= (1 << (output * 2));
    else if (state == OUTPUT_PWM)
        value |= (2 << (output * 2));
    else if (state == OUTPUT_PWM_GRPPWM)
        value |= (3 << (output * 2));

    //Write the value back out
    write(reg, value);
}

float PCA9955::groupDuty()
{
    //Return the value as a float
    return groupDuty_char() / 255.0f;
}

void PCA9955::groupDuty(float duty)
{
    //Range check the value
    if (duty < 0.0f)
        duty = 0.0f;
    if (duty > 1.0f)
        duty = 1.0f;

    //Convert the value to a char and write it
    groupDuty_char((char)(duty * 255.0f));
}

char PCA9955::groupDuty_char()
{
    //Return the 8-bit register value
    return read(REG_GRPPWM);
}

void PCA9955::groupDuty_char(char duty)
{
    //Write the new 8-bit register value
    write(REG_GRPPWM, duty);
}

float PCA9955::groupBlinkPeriod()
{
    //Read the 8-bit register value
    char value = groupBlinkPeriod_char();

    //Return the period in seconds
    if (value == 0x00)
        return 0.067f;
    else if (value == 0xFF)
        return 16.8f;
    else
        return (value + 1) / 15.26f;
}

void PCA9955::groupBlinkPeriod(float period)
{
    char value = 0;

    //Do a smart conversion
    if (period > 0.067f) {
        if (period < 16.8f)
            value = (char)((period * 15.26f) - 1);
        else
            value = 0xFF;
    }

    //Write the new 8-bit register value
    groupBlinkPeriod_char(value);
}

char PCA9955::groupBlinkPeriod_char()
{
    //Return the 8-bit register value
    return read(REG_GRPFREQ);
}

void PCA9955::groupBlinkPeriod_char(char period)
{
    //Write the new 8-bit register value
    write(REG_GRPFREQ, period);
}

float PCA9955::outputDuty(Output output)
{
    //Return the value as a float
    return outputDuty_char(output) / 255.0f;
}

void PCA9955::outputDuty(Output output, float duty)
{
    //Range check the value
    if (duty < 0.0f)
        duty = 0.0f;
    if (duty > 1.0f)
        duty = 1.0f;

    //Convert the value to a char and write it
    outputDuty_char(output, (char)(duty * 255.0f));
}

char PCA9955::outputDuty_char(Output output)
{
    //Return the 8-bit register value
    return read(REG_PWM0 + (char)output);
}

void PCA9955::outputDuty_char(Output output, char duty)
{
    //Write the new 8-bit register value
    write(REG_PWM0 + (char)output, duty);
}

float PCA9955::outputCurrent(Output output)
{
    //Return the value as a float
    return outputCurrent_char(output) / 255.0f;
}

void PCA9955::outputCurrent(Output output, float iref)
{
    //Range check the value
    if (iref < 0.0f)
        iref = 0.0f;
    if (iref > 1.0f)
        iref = 1.0f;

    //Convert the value to a char and write it
    outputCurrent_char(output, (char)(iref * 255.0f));
}

char PCA9955::outputCurrent_char(Output output)
{
    //Return the 8-bit register value
    return read(REG_IREF0 + (char)output);
}

void PCA9955::outputCurrent_char(Output output, char iref)
{
    //Write the new 8-bit register value
    write(REG_IREF0 + (char)output, iref);
}

char PCA9955::outputDelay()
{
    //Return the 8-bit register value (minus the top 4 bits)
    return read(REG_OFFSET) & 0x0F;
}

void PCA9955::outputDelay(char clocks)
{
    //Write the new 8-bit register value (minus the top 4 bits)
    write(REG_OFFSET, clocks & 0x0F);
}

char PCA9955::subCall1Addr()
{
    //Return the 8-bit address
    return read(REG_SUBADR1);
}

void PCA9955::subCall1Addr(char addr)
{
    //Write the new 8-bit address
    write(REG_SUBADR1, addr);
}

char PCA9955::subCall2Addr()
{
    //Return the 8-bit address
    return read(REG_SUBADR2);
}

void PCA9955::subCall2Addr(char addr)
{
    //Write the new 8-bit address
    write(REG_SUBADR2, addr);
}

char PCA9955::subCall3Addr()
{
    //Return the 8-bit address
    return read(REG_SUBADR3);
}

void PCA9955::subCall3Addr(char addr)
{
    //Write the new 8-bit address
    write(REG_SUBADR3, addr);
}

char PCA9955::allCallAddr()
{
    //Return the 8-bit address
    return read(REG_ALLCALLADR);
}

void PCA9955::allCallAddr(char addr)
{
    //Write the new 8-bit address
    write(REG_ALLCALLADR, addr);
}

void PCA9955::allOutputStates(OutputState state)
{
    char buff[5];

    //Assemble the sending array
    buff[0] = REG_LEDOUT0 | REG_AUTO_INC;
    if (state == OUTPUT_OFF) {
        memset(buff + 1, 0x00, 4);
    } else if (state == OUTPUT_ON) {
        memset(buff + 1, 0x55, 4);
    } else if (state == OUTPUT_PWM) {
        memset(buff + 1, 0xAA, 4);
    } else {
        memset(buff + 1, 0xFF, 4);
    }

    //Send the array
    writeMulti(buff, 5);
}

void PCA9955::getOutputDuties(float* duties)
{
    char buff[16];

    //Read all of the duty cycles as unsigned chars first
    getOutputDuties_char(buff);

    //Convert all of the duty cycles to percents
    for (int i = 0; i < 16; i++) {
        duties[i] = buff[i] / 255.0f;
    }
}

void PCA9955::allOutputDuties(float duty)
{
    //Range check the value
    if (duty < 0.0f)
        duty = 0.0f;
    if (duty > 1.0f)
        duty = 1.0f;

    //Convert the value to a char and write it
    allOutputDuties_char((char)(duty * 255.0f));
}

void PCA9955::allOutputDuties(float* duties)
{
    char buff[17];

    //Assemble the sending array
    buff[0] = REG_PWM0 | REG_AUTO_INC;
    for (int i = 1; i < 17; i++) {
        //Range check the value
        if (duties[i - 1] < 0.0f)
            duties[i - 1] = 0.0f;
        if (duties[i - 1] > 1.0f)
            duties[i - 1] = 1.0f;

        //Convert the value to a char and write it
        buff[i] = duties[i - 1] * 255.0f;
    }

    //Send the array
    writeMulti(buff, 17);
}

void PCA9955::getOutputDuties_char(char* duties)
{
    //Read all of the duty cycles at once
    readMulti(REG_PWM0 | REG_AUTO_INC, duties, 16);
}

void PCA9955::allOutputDuties_char(char duty)
{
    //Write the new 8-bit register value
    write(REG_PWMALL, duty);
}

void PCA9955::allOutputDuties_char(char* duties)
{
    char buff[17];

    //Assemble the sending array
    buff[0] = REG_PWM0 | REG_AUTO_INC;
    memcpy(buff + 1, duties, 16);

    //Send the array
    writeMulti(buff, 17);
}

void PCA9955::getOutputCurrents(float* irefs)
{
    char buff[16];

    //Read all of the current references as unsigned chars first
    getOutputCurrents_char(buff);

    //Convert all of the duty cycles to percents
    for (int i = 0; i < 16; i++) {
        irefs[i] = buff[i] / 255.0f;
    }
}

void PCA9955::allOutputCurrents(float iref)
{
    //Range check the value
    if (iref < 0.0f)
        iref = 0.0f;
    if (iref > 1.0f)
        iref = 1.0f;

    //Convert the value to a char and write it
    allOutputCurrents_char((char)(iref * 255.0f));
}

void PCA9955::allOutputCurrents(float* irefs)
{
    char buff[17];

    //Assemble the sending array
    buff[0] = REG_IREF0 | REG_AUTO_INC;
    for (int i = 1; i < 17; i++) {
        //Range check the value
        if (irefs[i - 1] < 0.0f)
            irefs[i - 1] = 0.0f;
        if (irefs[i - 1] > 1.0f)
            irefs[i - 1] = 1.0f;

        //Convert the value to a char and write it
        buff[i] = irefs[i - 1] * 255.0f;
    }

    //Send the array
    writeMulti(buff, 17);
}

void PCA9955::getOutputCurrents_char(char* irefs)
{
    //Read all of the current references at once
    readMulti(REG_IREF0 | REG_AUTO_INC, irefs, 16);
}

void PCA9955::allOutputCurrents_char(char iref)
{
    //Write the new 8-bit register value
    write(REG_IREFALL, iref);
}

void PCA9955::allOutputCurrents_char(char* irefs)
{
    char buff[17];

    //Assemble the sending array
    buff[0] = REG_IREF0 | REG_AUTO_INC;
    memcpy(buff + 1, irefs, 16);

    //Send the array
    writeMulti(buff, 17);
}

unsigned short PCA9955::faultTest()
{
    //Read the current 8-bit register value
    char value = read(REG_MODE2);

    //Set the FAULTTEST bit
    value |= (1 << 6);

    //Write the value back out
    write(REG_MODE2, value);

    //Wait for the fault test to complete
    while (read(REG_MODE2) & (1 << 6));

    //Read the lower 8 flags
    unsigned short flags = read(REG_EFLAG0);

    //Add the upper 8 flags
    flags |= read(REG_EFLAG1) << 8;

    //Return the combined flags
    return flags;
}

PCA9955& PCA9955::operator=(float value)
{
    //Set all of the output duties
    allOutputDuties(value);
    return *this;
}

char PCA9955::read(char reg)
{
    //Select the register
    m_I2C.write(m_ADDR, &reg, 1, true);

    //Read the 8-bit register
    m_I2C.read(m_ADDR, &reg, 1);

    //Return the byte
    return reg;
}

void PCA9955::write(char reg, char data)
{
    //Create a temporary buffer
    char buff[2];

    //Load the register address and 8-bit data
    buff[0] = reg;
    buff[1] = data;

    //Write the data
    m_I2C.write(m_ADDR, buff, 2);
}

void PCA9955::readMulti(char startReg, char* data, int length)
{
    //Select the starting register
    m_I2C.write(m_ADDR, &startReg, 1, true);

    //Read the specified number of bytes
    m_I2C.read(m_ADDR, data, length);
}

void PCA9955::writeMulti(char* data, int length)
{
    //Write the data
    m_I2C.write(m_ADDR, data, length);
}