-
Notifications
You must be signed in to change notification settings - Fork 585
/
LcdInterface.Gpio.cs
262 lines (226 loc) · 9.63 KB
/
LcdInterface.Gpio.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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// 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;
using System.Device.Gpio;
namespace Iot.Device.CharacterLcd
{
public abstract partial class LcdInterface : IDisposable
{
/// <summary>
/// Standard direct pin access to the HD44780 controller.
/// </summary>
private class Gpio : LcdInterface
{
/// <summary>
/// Register select pin. Low is for writing to the instruction
/// register and reading the address counter. High is for reading
/// and writing to the data register.
/// </summary>
private readonly int _rsPin;
/// <summary>
/// Read/write pin. Low for write, high for read.
/// </summary>
private readonly int _rwPin;
/// <summary>
/// Enable pin. Pulse high to process a read/write.
/// </summary>
private readonly int _enablePin;
private readonly int _backlight;
private readonly int[] _dataPins;
// We need to add PWM support to make this useful (to drive the VO pin).
// For now we'll just stash the value and use it to decide the initial
// backlight state.
private float _backlightBrightness;
private byte _lastByte;
private bool _useLastByte;
private GpioController _controller;
private bool _shouldDispose;
private PinValuePair[] _pinBuffer = new PinValuePair[8];
public Gpio(int registerSelectPin, int enablePin, int[] dataPins, int backlightPin = -1, float backlightBrightness = 1.0f, int readWritePin = -1, GpioController? controller = null, bool shouldDispose = true)
{
_rwPin = readWritePin;
_rsPin = registerSelectPin;
_enablePin = enablePin;
_dataPins = dataPins;
_backlight = backlightPin;
_backlightBrightness = backlightBrightness;
if (dataPins.Length == 8)
{
EightBitMode = true;
}
else if (dataPins.Length != 4)
{
throw new ArgumentException("The length of the array must be 4 or 8.", nameof(dataPins));
}
_shouldDispose = shouldDispose || controller is null;
_controller = controller ?? new GpioController(PinNumberingScheme.Logical);
Initialize();
}
public override bool EightBitMode { get; }
private void Initialize()
{
// Prep the pins
_controller.OpenPin(_rsPin, PinMode.Output);
if (_rwPin != -1)
{
_controller.OpenPin(_rwPin, PinMode.Output);
// Set to write. Once we enable reading have reading pull high and reset
// after reading to give maximum performance to write (i.e. assume that
// the pin is low when writing).
_controller.Write(_rwPin, PinValue.Low);
}
if (_backlight != -1)
{
_controller.OpenPin(_backlight, PinMode.Output);
if (_backlightBrightness > 0)
{
// Turn on the backlight
_controller.Write(_backlight, PinValue.High);
}
}
_controller.OpenPin(_enablePin, PinMode.Output);
for (int i = 0; i < _dataPins.Length; ++i)
{
_controller.OpenPin(_dataPins[i], PinMode.Output);
}
// The HD44780 self-initializes when power is turned on to the following settings:
//
// - 8 bit, 1 line, 5x7 font
// - Display, cursor, and blink off
// - Increment with no shift
//
// It is possible that the initialization will fail if the power is not provided
// within specific tolerances. As such, we'll always perform the software based
// initialization as described on pages 45/46 of the HD44780 data sheet. We give
// a little extra time to the required waits as described.
if (_dataPins.Length == 8)
{
// Init to 8 bit mode (this is the default, but other drivers
// may set the controller to 4 bit mode, so reset to be safe.)
DelayHelper.DelayMilliseconds(50, allowThreadYield: true);
WriteBits(0b0011_0000, 8);
DelayHelper.DelayMilliseconds(5, allowThreadYield: true);
WriteBits(0b0011_0000, 8);
DelayHelper.DelayMicroseconds(100, allowThreadYield: true);
WriteBits(0b0011_0000, 8);
}
else
{
// Init to 4 bit mode, setting _rspin to low as we're writing 4 bits directly.
// (Send writes the whole byte in two 4bit/nybble chunks)
_controller.Write(_rsPin, PinValue.Low);
DelayHelper.DelayMilliseconds(50, allowThreadYield: true);
WriteBits(0b0011, 4);
DelayHelper.DelayMilliseconds(5, allowThreadYield: true);
WriteBits(0b0011, 4);
DelayHelper.DelayMicroseconds(100, allowThreadYield: true);
WriteBits(0b0011, 4);
WriteBits(0b0010, 4);
}
// The busy flag can NOT be checked until this point.
}
public override bool BacklightOn
{
get => _backlight != -1 && _controller.Read(_backlight) == PinValue.High;
set
{
if (_backlight != -1)
{
_controller.Write(_backlight, value ? PinValue.High : PinValue.Low);
}
}
}
public override void SendCommand(byte command)
{
_controller.Write(_rsPin, PinValue.Low);
SendByte(command);
}
public override void SendCommands(ReadOnlySpan<byte> commands)
{
_controller.Write(_rsPin, PinValue.Low);
foreach (byte command in commands)
{
SendByte(command);
}
}
public override void SendData(byte value)
{
_controller.Write(_rsPin, PinValue.High);
SendByte(value);
}
public override void SendData(ReadOnlySpan<byte> values)
{
_controller.Write(_rsPin, PinValue.High);
foreach (byte value in values)
{
SendByte(value);
}
}
public override void SendData(ReadOnlySpan<char> values)
{
_controller.Write(_rsPin, PinValue.High);
foreach (byte value in values)
{
SendByte(value);
}
}
private void SendByte(byte value)
{
if (_dataPins.Length == 8)
{
WriteBits(value, 8);
}
else
{
WriteBits((byte)(value >> 4), 4);
WriteBits(value, 4);
}
// Most commands need a maximum of 37μs to complete.
WaitForNotBusy(37);
}
private void WriteBits(byte bits, int count)
{
int changedCount = 0;
for (int i = 0; i < count; i++)
{
int newBit = (bits >> i) & 1;
if (!_useLastByte)
{
_pinBuffer[changedCount++] = new PinValuePair(_dataPins[i], newBit);
}
else
{
// Each bit change takes ~23μs, so only change what we have to
// This is particularly impactful when using all 8 data lines.
int oldBit = (_lastByte >> i) & 1;
if (oldBit != newBit)
{
_pinBuffer[changedCount++] = new PinValuePair(_dataPins[i], newBit);
}
}
}
if (changedCount > 0)
{
_controller.Write(new ReadOnlySpan<PinValuePair>(_pinBuffer, 0, changedCount));
}
_useLastByte = true;
_lastByte = bits;
// Enable pin needs to be high for at least 450ns when running on 3V
// and 230ns on 5V. (PWeh on page 49/52 and Figure 25 on page 58)
_controller.Write(_enablePin, PinValue.High);
DelayHelper.DelayMicroseconds(1, allowThreadYield: false);
_controller.Write(_enablePin, PinValue.Low);
}
protected override void Dispose(bool disposing)
{
if (_shouldDispose)
{
_controller?.Dispose();
_controller = null!;
}
base.Dispose(disposing);
}
}
}
}