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)