/
ShiftRegister.cs
243 lines (218 loc) · 9.03 KB
/
ShiftRegister.cs
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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.Gpio;
using System.Device.Spi;
namespace Iot.Device.Multiplexing
{
/// <summary>
/// Generic shift register implementation. Supports multiple register lengths.
/// Compatible with SN74HC595, MBI5027 and MBI5168, for example.
/// Supports SPI and GPIO control.
/// </summary>
public class ShiftRegister : IDisposable
{
// Datasheet: https://www.ti.com/lit/ds/symlink/sn74hc595.pdf
// Datasheet: http://archive.fairchip.com/pdf/MACROBLOCK/MBI5168.pdf
// Tutorial: https://www.youtube.com/watch?v=6fVbJbNPrEU
// Using with SPI:
// https://forum.arduino.cc/index.php?topic=571144.0
// http://www.cupidcontrols.com/2013/12/turn-on-the-spi-lights-spi-output-shift-registers-and-leds/
private readonly ShiftRegisterPinMapping _pinMapping;
private readonly int _serial;
private readonly int _clock;
private readonly int _latch;
private readonly int _bitLength;
private readonly bool _shouldDispose;
private GpioController? _controller;
private SpiDevice? _spiDevice;
/// <summary>
/// Initialize a new shift register connected through GPIO.
/// </summary>
/// <param name="pinMapping">The pin mapping to use by the binding.</param>
/// <param name="bitLength">Bit length of register, including chained registers.</param>
/// <param name="gpioController">The GPIO Controller used for interrupt handling.</param>
/// <param name="shouldDispose">True (the default) if the GPIO controller shall be disposed when disposing this instance.</param>
public ShiftRegister(ShiftRegisterPinMapping pinMapping, int bitLength, GpioController? gpioController = null, bool shouldDispose = true)
{
_shouldDispose = shouldDispose || gpioController is null;
_controller = gpioController ?? new GpioController();
_pinMapping = pinMapping;
_serial = _pinMapping.SerialDataInput;
_clock = _pinMapping.Clock;
_latch = _pinMapping.LatchEnable;
_bitLength = bitLength;
SetupPins();
}
/// <summary>
/// Initialize a new shift register device connected through SPI.
/// Uses 3 pins (SDI -> SDI, SCLK -> SCLK, CE0 -> LE)
/// </summary>
/// <param name="spiDevice">SpiDevice used for serial communication.</param>
/// <param name="bitLength">Bit length of register, including chained registers.</param>
public ShiftRegister(SpiDevice spiDevice, int bitLength)
{
_spiDevice = spiDevice ?? throw new ArgumentNullException(nameof(spiDevice));
_bitLength = bitLength;
}
/// <summary>
/// GPIO controller.
/// </summary>
protected GpioController? GpioController => _controller;
/// <summary>
/// SPI device.
/// </summary>
protected SpiDevice? SpiDevice => _spiDevice;
/// <summary>
/// Bit length across all connected registers.
/// </summary>
public int BitLength => _bitLength;
/// <summary>
/// Reports if shift register is connected with SPI.
/// </summary>
public bool UsesSpi => _spiDevice is object;
/// <summary>
/// Reports if shift register is connected with GPIO.
/// </summary>
public bool UsesGpio => _controller is object;
/// <summary>
/// Shifts zeros.
/// Will dim all connected LEDs, for example.
/// Assumes register bit length evenly divisible by 8.
/// Supports GPIO controller or SPI device.
/// </summary>
public void ShiftClear()
{
if (_bitLength % 8 > 0)
{
throw new ArgumentNullException(nameof(ShiftClear), "Only supported for registers with bit lengths evenly divisible by 8.");
}
for (int i = 0; i < _bitLength / 8; i++)
{
ShiftByte(0b_0000_0000);
}
}
/// <summary>
/// Writes PinValue value to storage register.
/// This will shift existing values to the next storage slot.
/// Does not latch.
/// Requires use of GPIO controller.
/// </summary>
public void ShiftBit(PinValue value)
{
if (_controller is null || _pinMapping.SerialDataInput < 0)
{
throw new ArgumentNullException(nameof(ShiftBit), "GpioController was not provided or {nameof(_pinMapping.SerialDataInput)} not mapped to pin");
}
// writes value to serial data pin
_controller.Write(_serial, value);
// data is written to the storage register on the rising edge of the storage register clock
_controller.Write(_clock, 1);
// values are reset to low
_controller.Write(_serial, 0);
_controller.Write(_clock, 0);
}
/// <summary>
/// Shifts a byte -- 8 bits -- to the storage register.
/// Assumes register bit length evenly divisible by 8.
/// Pushes / overwrites any existing values.
/// Latches by default.
/// </summary>
public void ShiftByte(byte value, bool latch = true)
{
if (_spiDevice is object)
{
_spiDevice.WriteByte(value);
return;
}
for (int i = 0; i < 8; i++)
{
// 0b_1000_0000 (same as integer 128) used as input to create mask
// determines value of i bit in byte value
// logical equivalent of value[i] (which isn't supported for byte type in C#)
// starts left-most and ends up right-most
int data = (0b_1000_0000 >> i) & value;
// writes value to storage register
ShiftBit(data);
}
if (latch)
{
Latch();
}
}
/// <summary>
/// Latches values in data register to output pi.
/// Requires use of GPIO controller.
/// </summary>
public void Latch()
{
if (_controller is null || _pinMapping.LatchEnable < 0)
{
throw new Exception($"{nameof(Latch)}: GpioController was not provided or {nameof(_pinMapping.LatchEnable)} not mapped to pin");
}
// latches value on rising edge of register clock (LE)
_controller.Write(_latch, 1);
// value reset to low in preparation for next use.
_controller.Write(_latch, 0);
}
/// <summary>
/// Switch output register to high or low-impedance state.
/// Enables or disables register outputs, but does not delete values.
/// Requires use of GPIO controller.
/// </summary>
public bool OutputEnable
{
set
{
if (_controller is null || _pinMapping.OutputEnable < 0)
{
throw new Exception($"{nameof(OutputEnable)}: {nameof(_pinMapping.OutputEnable)} not mapped to non-zero pin value");
}
_controller.Write(_pinMapping.OutputEnable, value ? 0 : 1);
}
}
/// <summary>
/// Cleanup.
/// Failing to dispose this class, especially when callbacks are active, may lead to undefined behavior.
/// </summary>
public void Dispose()
{
// this condition only applies to GPIO devices
if (_shouldDispose)
{
_controller?.Dispose();
_controller = null;
}
// SPI devices are always disposed
_spiDevice?.Dispose();
_spiDevice = null;
}
private void SetupPins()
{
// these three pins are required
if (_serial >= 0 &&
_latch >= 0 &&
_clock >= 0)
{
OpenPinAndWrite(_serial, 0);
OpenPinAndWrite(_latch, 0);
OpenPinAndWrite(_clock, 0);
}
else
{
throw new Exception($"{nameof(ShiftRegister)} -- {nameof(ShiftRegisterPinMapping)} values must be non-zero; Values: {nameof(ShiftRegisterPinMapping.SerialDataInput)}: {_serial}; {nameof(ShiftRegisterPinMapping.LatchEnable)}: {_latch}; {nameof(ShiftRegisterPinMapping.Clock)}: {_clock};.");
}
// this pin assignment is optional
// if not assigned, must be tied to ground
if (_pinMapping.OutputEnable > 0)
{
OpenPinAndWrite(_pinMapping.OutputEnable, 0);
}
}
private void OpenPinAndWrite(int pin, PinValue value)
{
_controller?.OpenPin(pin, PinMode.Output);
_controller?.Write(pin, value);
}
}
}