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

Dependents:   PCA9955_HelloWorld

PCA9955.cpp

Committer:
neilt6
Date:
2014-05-30
Revision:
13:275e5ea3dc5c
Parent:
12:2b8adb10c605

File content as of revision 13:275e5ea3dc5c:

/* PCA9955 Driver Library
 * Copyright (c) 2014 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, int hz) : m_I2C(sda, scl), m_ADDR((int)addr)
{
    //Set the I2C bus frequency
    m_I2C.frequency(hz);
}

bool PCA9955::open()
{
    //Probe for the PCA9952/55 using a Zero Length Transfer
    if (!m_I2C.write(m_ADDR, NULL, 0)) {
        //Return success
        return true;
    } else {
        //Return failure
        return false;
    }
}

void PCA9955::reset()
{
    //The General Call Reset command
    char data = 0x06;

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

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(m_ADDR, 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(m_ADDR, 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(m_ADDR, 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(m_ADDR, 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(m_ADDR, 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(m_ADDR, 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(m_ADDR, 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(m_ADDR, reg, value);
}

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

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

    //Convert the value to a char and write it
    groupDuty_u8((char)(duty * 255.0), altAddr);
}

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

void PCA9955::groupDuty_u8(char duty, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_GRPPWM, duty);
}

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

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

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

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

    //Write the new 8-bit register value
    groupBlinkPeriod_u8(value, altAddr);
}

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

void PCA9955::groupBlinkPeriod_u8(char period, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_GRPFREQ, period);
}

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

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

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

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

void PCA9955::outputDuty_u8(Output output, char duty, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_PWM0 + (char)output, duty);
}

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

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

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

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

void PCA9955::outputCurrent_u8(Output output, char iref, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, 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, int altAddr)
{
    //Write the new 8-bit register value (minus the top 4 bits)
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_OFFSET, clocks & 0x0F);
}

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

void PCA9955::subCall1Addr(char addr, int altAddr)
{
    //Write the new 8-bit address
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_SUBADR1, addr);
}

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

void PCA9955::subCall2Addr(char addr, int altAddr)
{
    //Write the new 8-bit address
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_SUBADR2, addr);
}

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

void PCA9955::subCall3Addr(char addr, int altAddr)
{
    //Write the new 8-bit address
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_SUBADR3, addr);
}

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

void PCA9955::allCallAddr(char addr, int altAddr)
{
    //Write the new 8-bit address
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_ALLCALLADR, addr);
}

void PCA9955::getOutputStates(OutputState* states, Output start, Output end)
{
    //Read the range of output states
    for (int i = 0; i < end - start + 1; i++) {
        states[i] = outputState((Output)(start + i));
    }
}

void PCA9955::setOutputStates(OutputState* states, Output start, Output end)
{
    //Set the range of output states
    for (int i = 0; i < end - start + 1; i++) {
        outputState((Output)(start + i), states[i]);
    }
}

void PCA9955::allOutputStates(OutputState state, int altAddr)
{
    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((altAddr == NULL) ? m_ADDR : altAddr, buff, 5);
}

void PCA9955::getOutputDuties(float* duties, Output start, Output end)
{
    char buff[end - start + 1];

    //Read the range of the duty cycles as unsigned chars first
    getOutputDuties_u8(buff, start, end);

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

void PCA9955::setOutputDuties(float* duties, Output start, Output end, int altAddr)
{
    char buff[end - start + 2];

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

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

    //Send the array
    writeMulti((altAddr == NULL) ? m_ADDR : altAddr, buff, end - start + 2);
}

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

    //Convert the value to a char and write it
    allOutputDuties_u8((char)(duty * 255.0), altAddr);
}

void PCA9955::getOutputDuties_u8(char* duties, Output start, Output end)
{
    //Read all of the duty cycles at once
    readMulti((REG_PWM0 + start) | REG_AUTO_INC, duties, end - start + 1);
}

void PCA9955::setOutputDuties_u8(char* duties, Output start, Output end, int altAddr)
{
    char buff[end - start + 2];

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

    //Send the array
    writeMulti((altAddr == NULL) ? m_ADDR : altAddr, buff, end - start + 2);
}

void PCA9955::allOutputDuties_u8(char duty, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_PWMALL, duty);
}

void PCA9955::getOutputCurrents(float* irefs, Output start, Output end)
{
    char buff[end - start + 1];

    //Read all of the current references as unsigned chars first
    getOutputCurrents_u8(buff, start, end);

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

void PCA9955::setOutputCurrents(float* irefs, Output start, Output end, int altAddr)
{
    char buff[end - start + 2];

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

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

    //Send the array
    writeMulti((altAddr == NULL) ? m_ADDR : altAddr, buff, end - start + 2);
}

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

    //Convert the value to a char and write it
    allOutputCurrents_u8((char)(iref * 255.0), altAddr);
}

void PCA9955::getOutputCurrents_u8(char* irefs, Output start, Output end)
{
    //Read all of the current references at once
    readMulti((REG_IREF0 + start) | REG_AUTO_INC, irefs, end - start + 1);
}

void PCA9955::setOutputCurrents_u8(char* irefs, Output start, Output end, int altAddr)
{
    char buff[end - start + 2];

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

    //Send the array
    writeMulti((altAddr == NULL) ? m_ADDR : altAddr, buff, end - start + 2);
}

void PCA9955::allOutputCurrents_u8(char iref, int altAddr)
{
    //Write the new 8-bit register value
    write((altAddr == NULL) ? m_ADDR : altAddr, REG_IREFALL, iref);
}

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(m_ADDR, 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;
}

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

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(int addr, 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(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(int addr, char* data, int length)
{
    //Write the data
    m_I2C.write(addr, data, length);
}