Skip to content

Commit

Permalink
PCA9685: 16-channel, 12-bit PWM Fm+ I²C-bus LED controller (#250)
Browse files Browse the repository at this point in the history
* added PCA9685

* Update Pca9685.Sample.cs

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Fixed problems

* private I2cDevice, Register comments, stackalloc byte, I2C address

* Fixed comment

* Change back

* Change back

* delete extra enters and commas
  • Loading branch information
HumJ0218 authored and krwq committed Feb 27, 2019
1 parent ae4a131 commit 73de3a0
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/devices/Pca9685/Mode1.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Values for Mode1 register
/// </summary>
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
}
}
16 changes: 16 additions & 0 deletions src/devices/Pca9685/Mode2.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Values for Mode2 register
/// </summary>
internal enum Mode2 : byte
{
INVRT = 0b00010000, // Bit 4
OCH = 0b00001000, // Bit 3
OUTDRV = 0b00000100 // Bit 2
}
}
188 changes: 188 additions & 0 deletions src/devices/Pca9685/Pca9685.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// PCA9685 PWM LED/servo controller
/// </summary>
public class Pca9685 : IDisposable
{
/// <summary>
/// I2C Device
/// </summary>
private I2cDevice _device;

/// <summary>
/// Get default clock rate. Set if you are using external clock.
/// </summary>
public double ClockRate { get; set; } = 25000000; // 25MHz

/// <summary>
/// Set PWM frequency or get effective value.
/// </summary>
public double PwmFrequency
{
get => _pwmFrequency;
set
{
Prescale = GetPrescale(value);
}
}

/// <summary>
/// Set PWM frequency using prescale value or get the value.
/// </summary>
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;

/// <summary>
/// Initialize PCA9685
/// </summary>
/// <param name="i2cDevice">The I2C device to be used</param>
public Pca9685(I2cDevice i2cDevice)
{
// Setup I2C interface for the device.
_device = i2cDevice;

SetPwm(0, 0);

Span<byte> 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
}

/// <summary>
/// Set a single PWM channel
/// </summary>
/// <param name="on">The turn-on time of specfied channel</param>
/// <param name="off">The turn-off time of specfied channel</param>
/// <param name="channel">target channel</param>
public void SetPwm(int on, int off, int channel)
{
on &= 0xFFF;
off &= 0xFFF;
channel &= 0xF;

Span<byte> 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);
}

/// <summary>
/// Set all PWM channels
/// </summary>
/// <param name="on">The turn-on time of all channels</param>
/// <param name="off">The turn-on time of all channels</param>
public void SetPwm(int on, int off)
{
on &= 0xFFF;
off &= 0xFFF;

Span<byte> 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;
}

/// <summary>
/// Get prescale of specified PWM frequency
/// </summary>
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;
}

/// <summary>
/// Get PWM frequency of specified prescale
/// </summary>
private double GetFreq(byte prescale)
{
return ClockRate / 4096 / (prescale + 1);
}

/// <summary>
/// Set PWM frequency by using prescale
/// </summary>
private void SetPwmFrequency(byte prescale)
{
var oldmode = _device.ReadByte();
var newmode = (sbyte)oldmode | (byte)SLEEP;

Span<byte> 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);
}
}
}
25 changes: 25 additions & 0 deletions src/devices/Pca9685/Pca9685.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<!--Disabling default items so samples source won't get build by the main library-->
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Include="Pca9685.cs" />
<Compile Include="Mode1.cs" />
<Compile Include="Mode2.cs" />
<Compile Include="Register.cs" />
</ItemGroup>

<ItemGroup>
<None Include="README.md" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Device.Gpio" Version="0.1.0-prerelease*" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions src/devices/Pca9685/README.md
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions src/devices/Pca9685/Register.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Mode register 1
/// </summary>
MODE1 = 0x00,

/// <summary>
/// Mode register 2
/// </summary>
MODE2 = 0x01,

/// <summary>
/// I2C-bus subaddress 1
/// </summary>
SUBADR1 = 0x02,

/// <summary>
/// I2C-bus subaddress 2
/// </summary>
SUBADR2 = 0x03,

/// <summary>
/// I2C-bus subaddress 3
/// </summary>
SUBADR3 = 0x04,

/// <summary>
/// LED All Call I2C-bus address
/// </summary>
ALLCALLADR = 0x05,

/// <summary>
/// LED0 output and brightness control byte 0
/// </summary>
LED0_ON_L = 0x06,

/// <summary>
/// LED0 output and brightness control byte 1
/// </summary>
LED0_ON_H = 0x07,

/// <summary>
/// LED0 output and brightness control byte 2
/// </summary>
LED0_OFF_L = 0x08,

/// <summary>
/// LED0 output and brightness control byte 3
/// </summary>
LED0_OFF_H = 0x09,

/// <summary>
/// load all the LEDn_ON registers, byte 0
/// </summary>
ALL_LED_ON_L = 0xFA,

/// <summary>
/// load all the LEDn_ON registers, byte 1
/// </summary>
ALL_LED_ON_H = 0xFB,

/// <summary>
/// load all the LEDn_OFF registers, byte 0
/// </summary>
ALL_LED_OFF_L = 0xFC,

/// <summary>
/// load all the LEDn_OFF registers, byte 1
/// </summary>
ALL_LED_OFF_H = 0xFD,

/// <summary>
/// prescaler for PWM output frequency
/// </summary>
PRESCALE = 0xFE
}
}
Loading

0 comments on commit 73de3a0

Please sign in to comment.