-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathPca9685PwmController.cs
More file actions
302 lines (274 loc) · 11.6 KB
/
Pca9685PwmController.cs
File metadata and controls
302 lines (274 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// This file is a port of the very fine Tigra Astronomy driver for the
// Adafruit V2 motor shields from Microsoft .NET Micro Framework to
// nanoFramework and the AbstractIO project. See http://tigra-astronomy.com/, especially
// http://tigra-astronomy.com/stepper-motor-control-for-net-microframework and
// https://bitbucket.org/tigra-astronomy/ta.netmf.motorcontrol. Thank you
// very much, dear Tigra Astronomy team, for the fine work you have done and
// for using the MIT license, so this port was possible.
// Here is the original TA copyright notice:
// This file is part of the TA.NetMF.MotorControl project
//
// Copyright © 2014-2014 Tigra Astronomy, all rights reserved.
// This source code is licensed under the MIT License, see http://opensource.org/licenses/MIT
//
// File: Pca9685PwmController.cs Created: 2014-06-06@18:06
// Last modified: 2014-11-30@13:57 by Tim
using System;
using System.Collections;
using System.Threading;
using Windows.Devices.I2c;
namespace AbstractIO.AdafruitMotorShieldV2
{
internal class Pca9685PwmController : IPwmController
{
private const int MaxChannel = 15;
private const int PwmCounterCycle = 4096;
private I2cDevice _i2cDevice;
private float _outputModulationFrequencyHz;
private readonly ArrayList _allocatedChannels = new ArrayList();
/// <summary>
/// Initializes a new instance of the <see cref="Pca9685PwmController" /> class at the specified I2C address
/// and with the specified output modulation frequency.
/// </summary>
/// <param name="i2cAddress">The base I2C address for the device.</param>
/// <param name="outputModulationFrequencyHz">
/// The output modulation frequency of all 16 PWM channels, in Hertz (cycles per second).
/// If not specified, then the default value of 1.6 KHz is used. The theoretical range is
/// approximately 24 Hz to 1743 Hz, but extremes should be avoided if possible.
/// </param>
public Pca9685PwmController(
ushort i2cAddress = 0x60,
float outputModulationFrequencyHz = Pca9685Constants.DefaultOutputModulationFrequency)
{
_i2cDevice = I2cDevice.FromId("IC21", new I2cConnectionSettings(i2cAddress));
Reset();
SetOutputModulationFrequency(outputModulationFrequencyHz);
// At this point the device is fully configured but all PWM channels are turned off.
}
public PwmChannel GetPwmChannel(uint channel)
{
if (channel > MaxChannel)
{
throw new ArgumentOutOfRangeException("channel", "Maximum channel is 15");
}
else if (_allocatedChannels.Contains(channel))
{
throw new ArgumentException("This channel is already reserved.");
}
else
{
_allocatedChannels.Add(channel);
return new PwmChannel(this, channel, 0.0f);
}
}
public float OutputModulationFrequencyHz
{
get
{
return _outputModulationFrequencyHz;
}
}
public void ConfigureChannelDutyCycle(uint channel, float dutyCycle)
{
if (dutyCycle >= 1.0)
{
SetFullOn(channel);
}
else if (dutyCycle <= 0.0)
{
SetFullOff(channel);
}
else
{
uint onCount = 0;
var offCount = (uint)System.Math.Floor(PwmCounterCycle * dutyCycle);
if (offCount <= onCount)
{
offCount = onCount + 1; // The two counts may not be the same value
}
var registerOffset = (byte)(Pca9685Constants.Channel0OnLow + (4 * channel));
WriteConsecutiveRegisters(
registerOffset,
(byte)onCount,
(byte)(onCount >> 8),
(byte)offCount,
(byte)(offCount >> 8));
}
}
/// <summary>
/// Sets the channel to 0% duty cycle.
/// </summary>
/// <param name="channel">The channel number (0-based).</param>
private void SetFullOff(uint channel)
{
var registerOffset = (byte)(Pca9685Constants.Channel0OnLow + (4 * channel));
WriteConsecutiveRegisters(registerOffset, 0x00, 0x00, 0x00, 0x00); // Set LED_FULL_OFF bit
}
/// <summary>
/// Sets the channel to 100% duty cycle.
/// </summary>
/// <param name="channel">The channel number (0-based).</param>
private void SetFullOn(uint channel)
{
var registerOffset = (byte)(Pca9685Constants.Channel0OnLow + (4 * channel));
WriteConsecutiveRegisters(registerOffset, 0x00, 0x10, 0x00, 0x00); // Set LED_FULL_ON bit
}
private void SetBitInRegister(byte registerOffset, ushort bitNumber)
{
var bitSetMask = 0x01 << bitNumber;
var registerValue = (int)ReadRegister(registerOffset);
registerValue |= bitSetMask;
WriteRegister(registerOffset, (byte)registerValue);
}
private void ClearBitInRegister(byte registerOffset, ushort bitNumber)
{
var bitClearMask = 0xFF ^ (0x01 << bitNumber);
var registerValue = (int)ReadRegister(registerOffset);
registerValue &= bitClearMask;
WriteRegister(registerOffset, (byte)registerValue);
}
/// <summary>
/// Resets the PCA9685 PWM controller into a known starting state
/// and turns off all PWM channels. Blocks for at least 1 millisecond
/// to allow the internal oscillator to stabilize.
/// </summary>
public void Reset()
{
WriteRegister(Pca9685Constants.Mode1Register, 0x00);
Thread.Sleep(1);
SetAllChannelsOff();
// Set the RESATART bit, but only if necessary.
if (!BitIsClear(Pca9685Constants.Mode1Register, Pca9685Constants.RestartBit))
{
SetBitInRegister(Pca9685Constants.Mode1Register, Pca9685Constants.RestartBit);
}
}
/// <summary>
/// Sets all channels to 0% duty cycle.
/// </summary>
private void SetAllChannelsOff()
{
// Sets the LED_FULL_OFF bit in each and every channel, which sets the duty cycle to 0%.
WriteConsecutiveRegisters(Pca9685Constants.AllChannelsBaseRegister, 0x00, 0x00, 0x00, 0x10);
}
/// <summary>
/// Sets the LED_FULL_ON bit in each and every channel, which sets the duty cycle to 100%..
/// </summary>
private void SetAllChannelsOn()
{
WriteConsecutiveRegisters(Pca9685Constants.AllChannelsBaseRegister, 0x00, 0x10, 0x00, 0x00);
}
private void WriteRegister(byte registerOffset, byte data)
{
byte[] writeBuffer = { registerOffset, data };
lock (AbstractIO.GlobalLockObjects.I2cLockObject)
{
var operationResult = _i2cDevice.WritePartial(writeBuffer);
if(operationResult.Status != I2cTransferStatus.FullTransfer)
{
// TODO
// handle error
}
}
}
private void WriteConsecutiveRegisters(byte startRegisterOffset, params byte[] values)
{
if (BitIsClear(Pca9685Constants.Mode1Register, Pca9685Constants.AutoIncrementBit))
{
SetAutoIncrement(true);
}
var bufferSize = values.Length + 1;
var writeBuffer = new byte[bufferSize];
writeBuffer[0] = startRegisterOffset;
var bufferIndex = 0;
foreach (var value in values)
{
writeBuffer[++bufferIndex] = value;
}
lock (AbstractIO.GlobalLockObjects.I2cLockObject)
{
var operationResult = _i2cDevice.WritePartial(writeBuffer);
if (operationResult.Status != I2cTransferStatus.FullTransfer)
{
// TODO
// handle error
}
}
}
private bool BitIsSet(byte registerOffset, ushort bitNumber)
{
var registerValue = ReadRegister(registerOffset);
var bitTestMask = 0x01 << bitNumber;
return (registerValue & bitTestMask) != 0;
}
public void SetOutputModulationFrequency(float frequencyHz = Pca9685Constants.DefaultOutputModulationFrequency)
{
// See PCA9685 data sheet, pp.24 for details on calculating the prescale value.
var computedPrescale = System.Math.Round(Pca9685Constants.InternalOscillatorFrequencyHz / 4096.0f /
frequencyHz) - 1;
if (computedPrescale < 3.0 || computedPrescale > 255.0)
{
throw new ArgumentOutOfRangeException("frequencyHz", "range 24 Hz to 1743 Hz");
}
var prescale = (byte)computedPrescale;
SetPrescale(prescale);
_outputModulationFrequencyHz = frequencyHz;
}
/// <summary>
/// Enables or disables the automatic increment mode.
/// When enabled, sequential register reads are possible.
/// </summary>
/// <param name="enabled">if set to <c>true</c> [enabled].</param>
private void SetAutoIncrement(bool enabled)
{
if (enabled)
{
SetBitInRegister(Pca9685Constants.Mode1Register, Pca9685Constants.AutoIncrementBit);
}
else
{
ClearBitInRegister(Pca9685Constants.Mode1Register, Pca9685Constants.AutoIncrementBit);
}
}
/// <summary>
/// Sets the master prescale divider for all channels.
/// Thi will turn off all PWM channels. They must be reconfigured
/// after setting the prescale value.
/// </summary>
/// <param name="prescale">The prescale.</param>
private void SetPrescale(byte prescale)
{
// The prescaler can only be set while the device is in SLEEP mode.
// so we must put it to sleep, set the prescaler, then restart it.
byte sleep = (byte)(0x01 << Pca9685Constants.SleepBit); // Set the SLEEP bit
WriteRegister(Pca9685Constants.Mode1Register, sleep);
WriteRegister(Pca9685Constants.PrescaleRegister, prescale);
Reset();
}
private bool BitIsClear(byte registerOffset, ushort bitNumber)
{
return !BitIsSet(registerOffset, bitNumber);
}
private byte ReadRegister(byte registerOffset)
{
byte[] writeBuffer = { registerOffset };
var readBuffer = new byte[1];
lock (AbstractIO.GlobalLockObjects.I2cLockObject)
{
var operationResult = _i2cDevice.WritePartial(writeBuffer);
if (operationResult.Status != I2cTransferStatus.FullTransfer)
{
// TODO
// handle error
}
operationResult = _i2cDevice.ReadPartial(readBuffer);
if (operationResult.Status != I2cTransferStatus.FullTransfer)
{
// TODO
// handle error
}
}
return readBuffer[0];
}
}
}