diff --git a/src/devices/Pca9685/Mode1.cs b/src/devices/Pca9685/Mode1.cs new file mode 100644 index 0000000000..104a5345fb --- /dev/null +++ b/src/devices/Pca9685/Mode1.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Pca9685 +{ + /// + /// Values for Mode1 register + /// + internal enum Mode1 : byte + { + RESTART = 0b10000000, // Bit 7 + EXTCLK = 0b01000000, // Bit 6 + AI = 0b00100000, // Bit 5 + SLEEP = 0b00010000, // Bit 4 + SUB1 = 0b00001000, // Bit 3 + SUB2 = 0b00000100, // Bit 2 + SUB3 = 0b00000010, // Bit 1 + ALLCALL = 0x00000001 // Bit 0 + } +} diff --git a/src/devices/Pca9685/Mode2.cs b/src/devices/Pca9685/Mode2.cs new file mode 100644 index 0000000000..31f0232846 --- /dev/null +++ b/src/devices/Pca9685/Mode2.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Pca9685 +{ + /// + /// Values for Mode2 register + /// + internal enum Mode2 : byte + { + INVRT = 0b00010000, // Bit 4 + OCH = 0b00001000, // Bit 3 + OUTDRV = 0b00000100 // Bit 2 + } +} diff --git a/src/devices/Pca9685/Pca9685.cs b/src/devices/Pca9685/Pca9685.cs new file mode 100644 index 0000000000..41688d4aab --- /dev/null +++ b/src/devices/Pca9685/Pca9685.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Device.I2c; +using System.Threading; +using static Iot.Device.Pca9685.Register; +using static Iot.Device.Pca9685.Mode1; +using static Iot.Device.Pca9685.Mode2; + +namespace Iot.Device.Pca9685 +{ + /// + /// PCA9685 PWM LED/servo controller + /// + public class Pca9685 : IDisposable + { + /// + /// I2C Device + /// + private I2cDevice _device; + + /// + /// Get default clock rate. Set if you are using external clock. + /// + public double ClockRate { get; set; } = 25000000; // 25MHz + + /// + /// Set PWM frequency or get effective value. + /// + public double PwmFrequency + { + get => _pwmFrequency; + set + { + Prescale = GetPrescale(value); + } + } + + /// + /// Set PWM frequency using prescale value or get the value. + /// + public byte Prescale + { + get => _prescale; + set + { + var v = value < 3 ? (byte)3 : value; // min value is 3 + SetPwmFrequency(v); + _prescale = v; + _pwmFrequency = GetFreq(v); + } + } + + private double _pwmFrequency; + private byte _prescale; + + /// + /// Initialize PCA9685 + /// + /// The I2C device to be used + public Pca9685(I2cDevice i2cDevice) + { + // Setup I2C interface for the device. + _device = i2cDevice; + + SetPwm(0, 0); + + Span buffer = stackalloc byte[2] { (byte)MODE2, (byte)OUTDRV }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)MODE1, (byte)ALLCALL }; + _device.Write(buffer); + + Thread.Sleep(5); // wait for oscillator + + int mode1 = _device.ReadByte(); + mode1 &= ~(byte)SLEEP; // wake up (reset sleep) + + buffer = stackalloc byte[2] { (byte)MODE1, (byte)mode1 }; + _device.Write(buffer); + + Thread.Sleep(5); // wait for oscillator + } + + /// + /// Set a single PWM channel + /// + /// The turn-on time of specfied channel + /// The turn-off time of specfied channel + /// target channel + public void SetPwm(int on, int off, int channel) + { + on &= 0xFFF; + off &= 0xFFF; + channel &= 0xF; + + Span buffer = stackalloc byte[2] { (byte)((byte)LED0_ON_L + 4 * channel), (byte)on }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)((byte)LED0_ON_H + 4 * channel), (byte)(on >> 8) }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)((byte)LED0_OFF_L + 4 * channel), (byte)off }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)((byte)LED0_OFF_H + 4 * channel), (byte)(off >> 8) }; + _device.Write(buffer); + } + + /// + /// Set all PWM channels + /// + /// The turn-on time of all channels + /// The turn-on time of all channels + public void SetPwm(int on, int off) + { + on &= 0xFFF; + off &= 0xFFF; + + Span buffer = stackalloc byte[2] { (byte)ALL_LED_ON_L, (byte)on }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)ALL_LED_ON_H, (byte)(on >> 8) }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)ALL_LED_OFF_L, (byte)off }; + _device.Write(buffer); + + buffer = stackalloc byte[2] { (byte)ALL_LED_OFF_H, (byte)(off >> 8) }; + _device.Write(buffer); + } + + public void Dispose() + { + _device?.Dispose(); + _device = null; + } + + /// + /// Get prescale of specified PWM frequency + /// + private byte GetPrescale(double freq_hz) + { + var prescaleval = ClockRate / 4096 / freq_hz - 1; + //Debug.Print($"Setting PWM frequency to {freq_hz} Hz"); + //Debug.Print($"Estimated pre-scale: {prescaleval}"); + + var prescale = (byte)Math.Round(prescaleval); + //Debug.Print($"Final pre-scale: {prescale}"); + + return prescale; + } + + /// + /// Get PWM frequency of specified prescale + /// + private double GetFreq(byte prescale) + { + return ClockRate / 4096 / (prescale + 1); + } + + /// + /// Set PWM frequency by using prescale + /// + private void SetPwmFrequency(byte prescale) + { + var oldmode = _device.ReadByte(); + var newmode = (sbyte)oldmode | (byte)SLEEP; + + Span buffer = stackalloc byte[2] { (byte)MODE1, (byte)newmode }; + _device.Write(buffer); // go to sleep + + buffer = stackalloc byte[2] { (byte)PRESCALE, prescale }; + _device.Write(buffer); + + + buffer = stackalloc byte[2] { (byte)MODE1, oldmode }; + _device.Write(buffer); + + Thread.Sleep(5); + + buffer = stackalloc byte[2] { (byte)MODE1, (byte)(oldmode | (byte)RESTART) }; + _device.Write(buffer); + } + } +} diff --git a/src/devices/Pca9685/Pca9685.csproj b/src/devices/Pca9685/Pca9685.csproj new file mode 100644 index 0000000000..a8c2b25a1d --- /dev/null +++ b/src/devices/Pca9685/Pca9685.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.1 + + false + latest + + + + + + + + + + + + + + + + + + diff --git a/src/devices/Pca9685/README.md b/src/devices/Pca9685/README.md new file mode 100644 index 0000000000..f0dde28f4d --- /dev/null +++ b/src/devices/Pca9685/README.md @@ -0,0 +1,15 @@ +# Pca9685 + +## Summary + +The PCA9685 is an I²C-bus controlled 16-channel LED controller optimized for Red/Green/Blue/Amber (RGBA) color backlighting applications. + +You can also use this to control servos. + +## Data Sheets from NXP + +https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf + +## References + +https://www.nxp.com/products/analog/interfaces/ic-bus/ic-led-controllers/16-channel-12-bit-pwm-fm-plus-ic-bus-led-controller:PCA9685 diff --git a/src/devices/Pca9685/Register.cs b/src/devices/Pca9685/Register.cs new file mode 100644 index 0000000000..40d3ae0a44 --- /dev/null +++ b/src/devices/Pca9685/Register.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Iot.Device.Pca9685 +{ + internal enum Register : byte + { + /// + /// Mode register 1 + /// + MODE1 = 0x00, + + /// + /// Mode register 2 + /// + MODE2 = 0x01, + + /// + /// I2C-bus subaddress 1 + /// + SUBADR1 = 0x02, + + /// + /// I2C-bus subaddress 2 + /// + SUBADR2 = 0x03, + + /// + /// I2C-bus subaddress 3 + /// + SUBADR3 = 0x04, + + /// + /// LED All Call I2C-bus address + /// + ALLCALLADR = 0x05, + + /// + /// LED0 output and brightness control byte 0 + /// + LED0_ON_L = 0x06, + + /// + /// LED0 output and brightness control byte 1 + /// + LED0_ON_H = 0x07, + + /// + /// LED0 output and brightness control byte 2 + /// + LED0_OFF_L = 0x08, + + /// + /// LED0 output and brightness control byte 3 + /// + LED0_OFF_H = 0x09, + + /// + /// load all the LEDn_ON registers, byte 0 + /// + ALL_LED_ON_L = 0xFA, + + /// + /// load all the LEDn_ON registers, byte 1 + /// + ALL_LED_ON_H = 0xFB, + + /// + /// load all the LEDn_OFF registers, byte 0 + /// + ALL_LED_OFF_L = 0xFC, + + /// + /// load all the LEDn_OFF registers, byte 1 + /// + ALL_LED_OFF_H = 0xFD, + + /// + /// prescaler for PWM output frequency + /// + PRESCALE = 0xFE + } +} diff --git a/src/devices/Pca9685/samples/Pca9685.Sample.cs b/src/devices/Pca9685/samples/Pca9685.Sample.cs new file mode 100644 index 0000000000..a7938cca0d --- /dev/null +++ b/src/devices/Pca9685/samples/Pca9685.Sample.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Device.I2c; +using System.Device.I2c.Drivers; + +namespace Iot.Device.Pca9685.Samples +{ + /// + /// This sample is for Raspberry Pi Model 3B+ + /// + class Program + { + static void Main(string[] args) + { + var busId = 1; // /dev/i2c-1 + + var deviceAddress_fixed = 0x40; + var deviceAddress_selectable = 0b000000; // A5 A4 A3 A2 A1 A0 + var deviceAddress = deviceAddress_fixed | deviceAddress_selectable; + + var settings = new I2cConnectionSettings(busId, deviceAddress); + var device = new UnixI2cDevice(settings); + + using (var pca9685 = new Pca9685(device)) + { + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PCA9685 is ready on I2C bus {device.ConnectionSettings.BusId} with address {device.ConnectionSettings.DeviceAddress}"); + + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Command: F {freq_hz} set PWM frequency"); + Console.WriteLine(" P {prescale} set PRE_SCALE register"); + Console.WriteLine(" S {off} set off step with on step is 0 to all channels"); + Console.WriteLine(" S {on} {off} set on step and off step to all channels"); + Console.WriteLine(" S {on} {off} {channel} set on step and off step to specified channel"); + + Console.WriteLine(); + while (true) + { + try + { + Console.ResetColor(); + Console.Write("> "); + var command = Console.ReadLine().ToLower().Split(' '); + + switch (command[0][0]) + { + case 'f': // set PWM frequency + { + var freq = double.Parse(command[1]); + pca9685.PwmFrequency = freq; + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PWM Frequency has been set to about {pca9685.PwmFrequency}Hz with prescale is {pca9685.Prescale}"); + break; + } + case 'p': // set PRE_SCALE register + { + var prescale = (byte)int.Parse(command[1]); + pca9685.Prescale = prescale; + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PWM Frequency has been set to about {pca9685.PwmFrequency}Hz with prescale is {pca9685.Prescale}"); + break; + } + case 's': // set PWM steps + { + switch (command.Length) + { + case 2: // 1 parameter : set off step with on step is 0 to all channels + { + var off = int.Parse(command[1]); + pca9685.SetPwm(0, off); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PWM pulse width has been set to {off}/4096"); + break; + } + case 3: // 2 parametes : set on step and off step to all channels + { + var on = int.Parse(command[1]); + var off = int.Parse(command[2]); + pca9685.SetPwm(on, off); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PWM pulse pull up at step {on} and pull down at step {off} on all channels"); + break; + } + case 4: // 3 parametes : set on step and off step to specified channel + { + var on = int.Parse(command[1]); + var off = int.Parse(command[2]); + var channel = int.Parse(command[3]); + pca9685.SetPwm(on, off, channel); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"PWM pulse pull up at step {on} and pull down at step {off} on channel {channel}"); + break; + } + } + break; + } + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.GetBaseException().Message); + Console.ResetColor(); + } + } + } + } + } +} diff --git a/src/devices/Pca9685/samples/Pca9685.Sample.csproj b/src/devices/Pca9685/samples/Pca9685.Sample.csproj new file mode 100644 index 0000000000..242a818093 --- /dev/null +++ b/src/devices/Pca9685/samples/Pca9685.Sample.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp2.1 + latest + + + + + + + + + + + diff --git a/src/devices/README.md b/src/devices/README.md index 7e9ea17ecf..79e24042cf 100644 --- a/src/devices/README.md +++ b/src/devices/README.md @@ -24,6 +24,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag * [Mcp3008 -- Analog-to-Digital Converter](Mcp3008/README.md) * [nRF24L01 -- Single chip 2.4 GHz Transceiver](Nrf24l01/README.md) * [Pca95x4 -- I2C GPIO Expander](Pca95x4/README.md) +* [Pca9685 -- I2C PWM Driver](Pca9685/README.md) * [Pcx857x -- I2C GPIO Expander](Pcx857x/README.md) * [Servo -- Servomotor Controller](Servo/README.md) * [SHT3x -- Humidity and Temperature Sensor](Sht3x/README.md)