// Copyright (C)2018-2023, Philip Munts dba Munts Technologies.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
namespace IO.Devices.PCA9685
{
///
/// Encapsulates the PCA9685 I2C PWM Controller.
///
public class Device
{
///
/// Minimum clock frequency.
///
public const int MIN_CLOCK = 1;
///
/// Maximum clock frequency.
///
public const int MAX_CLOCK = 50000000;
///
/// Select internal 25 MHz clock oscillator.
///
public const int INTERNAL_CLOCK = 0;
///
/// Minimum PCA9685 output channel number.
///
public const int MIN_CHANNEL = 0;
///
/// Maximum PCA9685 output channel number.
///
public const int MAX_CHANNEL = 15;
private enum Registers
{
MODE1 = 0,
MODE2 = 1,
SUBADR1 = 2,
SUBADR2 = 3,
SUBADR3 = 4,
ALLCALLADR = 5,
LED0_ON_L = 6,
LED0_ON_H = 7,
LED0_OFF_L = 8,
LED0_OFF_H = 9,
LED1_ON_L = 10,
LED1_ON_H = 11,
LED1_OFF_L = 12,
LED1_OFF_H = 13,
LED2_ON_L = 14,
LED2_ON_H = 15,
LED2_OFF_L = 16,
LED2_OFF_H = 17,
LED3_ON_L = 18,
LED3_ON_H = 19,
LED3_OFF_L = 20,
LED3_OFF_H = 21,
LED4_ON_L = 22,
LED4_ON_H = 23,
LED4_OFF_L = 24,
LED4_OFF_H = 25,
LED5_ON_L = 26,
LED5_ON_H = 27,
LED5_OFF_L = 28,
LED5_OFF_H = 29,
LED6_ON_L = 30,
LED6_ON_H = 31,
LED6_OFF_L = 32,
LED6_OFF_H = 33,
LED7_ON_L = 34,
LED7_ON_H = 35,
LED7_OFF_L = 36,
LED7_OFF_H = 37,
LED8_ON_L = 38,
LED8_ON_H = 39,
LED8_OFF_L = 40,
LED8_OFF_H = 41,
LED9_ON_L = 42,
LED9_ON_H = 43,
LED9_OFF_L = 44,
LED9_OFF_H = 45,
LED10_ON_L = 46,
LED10_ON_H = 47,
LED10_OFF_L = 48,
LED10_OFF_H = 49,
LED11_ON_L = 50,
LED11_ON_H = 51,
LED11_OFF_L = 52,
LED11_OFF_H = 53,
LED12_ON_L = 54,
LED12_ON_H = 55,
LED12_OFF_L = 56,
LED12_OFF_H = 57,
LED13_ON_L = 58,
LED13_ON_H = 59,
LED13_OFF_L = 60,
LED13_OFF_H = 61,
LED14_ON_L = 62,
LED14_ON_H = 63,
LED14_OFF_L = 64,
LED14_OFF_H = 65,
LED15_ON_L = 66,
LED15_ON_H = 67,
LED15_OFF_L = 68,
LED15_OFF_H = 69,
ALL_LED_ON_L = 250,
ALL_LED_ON_H = 251,
ALL_LED_OFF_L = 252,
ALL_LED_OFF_H = 253,
PRE_SCALE = 254,
TESTMODE = 255
}
private readonly IO.Interfaces.I2C.Device dev;
private readonly int freq;
private byte[] cmd;
private byte[] resp;
///
/// Constructor for a single PCA9685 device.
///
/// I2C bus controller object.
/// I2C slave address.
/// PWM pulse frequency. Default is 50 Hz.
/// PCA9685 clock source.
/// Use INTERNAL_CLOCKc> to select the internal 25 MHz clock generator.
///
public Device(IO.Interfaces.I2C.Bus bus, int addr, int freq = 50,
int clock = INTERNAL_CLOCK)
{
// Validate parameters
if (freq < 1)
throw new System.Exception("Invalid PWM pulse frequency");
if ((clock != INTERNAL_CLOCK) && ((clock < MIN_CLOCK) || (clock > MAX_CLOCK)))
throw new System.Exception("Invalid clock frequency");
dev = new IO.Interfaces.I2C.Device(bus, addr);
this.freq = freq;
cmd = new byte[5];
resp = new byte[4];
if (clock == INTERNAL_CLOCK)
{
// Use internal 25 MHz clock
WriteRegister((byte)Registers.MODE1, 0x30); // Set SLEEP
WriteRegister((byte)Registers.PRE_SCALE, (byte)(25000000 / 4096 / freq - 1));
WriteRegister((byte)Registers.MODE1, 0x20); // Clear SLEEP
}
else
{
// Use external clock
WriteRegister((byte)Registers.MODE1, 0x70); // Set SLEEP
WriteRegister((byte)Registers.PRE_SCALE, (byte)(clock / 4096 / freq - 1));
WriteRegister((byte)Registers.MODE1, 0x60); // Clear SLEEP
}
WriteRegister((byte)Registers.MODE2, 0x0C);
}
private void ReadRegister(byte addr, ref byte data)
{
cmd[0] = addr;
dev.Transaction(cmd, 1, resp, 1);
data = resp[0];
}
private void WriteRegister(byte addr, byte data)
{
cmd[0] = addr;
cmd[1] = data;
dev.Write(cmd, 2);
}
///
/// Read PCA9685 output channel data.
///
/// Output channel number.
/// Output channel data (4 bytes).
public void ReadChannel(byte channel, ref byte[] data)
{
if (channel > MAX_CHANNEL)
throw new System.Exception("Invalid channel number");
cmd[0] = System.Convert.ToByte((int)Registers.LED0_ON_L + channel * 4);
dev.Transaction(cmd, 1, resp, 4);
data[0] = resp[0];
data[1] = resp[1];
data[2] = resp[2];
data[3] = resp[3];
}
///
/// Write PCA9685 output channel data.
///
/// Output channel number.
/// Output channel data.
public void WriteChannel(byte channel, byte[] data)
{
if (channel > MAX_CHANNEL)
throw new System.Exception("Invalid channel number");
cmd[0] = System.Convert.ToByte((int)Registers.LED0_ON_L + channel * 4);
cmd[1] = data[0];
cmd[2] = data[1];
cmd[3] = data[2];
cmd[4] = data[3];
dev.Write(cmd, 5);
}
///
/// Read-only property returning the configured PWM pulse frequency.
///
public int Frequency
{
get
{
return this.freq;
}
}
}
}