-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathLRThreeDigits.cpp
380 lines (331 loc) · 9.75 KB
/
LRThreeDigits.cpp
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
//
// Lucky Resistor's Boldport 3x7 Driver
// ---------------------------------------------------------------------------
// (c)2018 by Lucky Resistor. See LICENSE for details.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "LRThreeDigits.h"
#include <Arduino.h>
#include <avr/interrupt.h>
namespace lr {
namespace ThreeDigits {
// Configuration
// ---------------------------------------------------------------------------
// Note:
// The following 16 bit masks are the combined bits for GPIO ports B+D
// on the ATmega328P chip.
//
// The masks and variables are all 0-based, with significant bits
// starting from bit 0.
//
// Data is shifted into correct bit position at the point
// of setting the digits to display, after which the lower byte
// will be written to port D and the upper byte to port B.
//
/// The mask for all significant bits used on ports.
///
const uint16_t cPrePortBitMask = 0b0000001111111111;
/// The number of digits of the display.
///
const uint8_t cDigitCount = 3;
/// The bits on the ports to drive the digit sinks.
///
const uint16_t cDigitPortBit[cDigitCount] = {
0b0000000000000100, // d3
0b0000000000000010, // d2
0b0000000000000001, // d1
};
/// The number of segments for each digit.
///
const uint8_t cSegmentCount = 7;
/// The bits on the ports to drive each segment.
///
/// ```
/// .-0-. .-a-.
/// 5 1 f b
/// :-6-: :-g-:
/// 4 2 e c
/// .-3-. .-d-.
/// ```
///
const uint16_t cSegmentPortBit[cSegmentCount] = {
0b0000001000000000, // a
0b0000000100000000, // b
0b0000000010000000, // c
0b0000000001000000, // d
0b0000000000100000, // e
0b0000000000010000, // f
0b0000000000001000, // g
};
/// Bit flip array to rotate a digit.
///
const uint8_t cSegmentRotateMap[cSegmentCount] = {
3, 4, 5, 0, 1, 2, 6
};
/// Structure to define the mask for one digit.
///
/// The mask is a combination of bits which are mapped to the indexes
/// of the segments as defined in `cSegmentPortBit`. Therefore this definitions
/// are independent from the bit masks above.
///
struct DigitMask {
char c; ///< The character to map.
uint8_t mask; ///< The mask for this character.
};
/// A list with known characters to display.
///
/// Extend this list as you like, but make sure it always
/// ends with a `\0` zero character entry.
///
const DigitMask cDigitMask[] = {
{'0', 0x3f},
{'1', 0x06},
{'2', 0x5b},
{'3', 0x4f},
{'4', 0x66},
{'5', 0x6d},
{'6', 0x7d},
{'7', 0x07},
{'8', 0x7f},
{'9', 0x6f},
{'a', 0x77},
{'b', 0x7c},
{'c', 0x39},
{'d', 0x5e},
{'e', 0x79},
{'f', 0x71},
{'_', 0x08},
{'*', 0x63}, // degree symbol
{'\'', 0x20},
{'"', 0x0a},
{' ', 0x00},
{'\0', 0x00}
};
// Variables
// ---------------------------------------------------------------------------
/// The orientation of the display.
///
Orientation gOrientation = Orientation::ConnectorOnTop;
/// The mask for all bits used on ports.
/// This is recalculated when changing gPortBitOffset.
///
uint16_t gPortBitMask = 0b0000111111111100;
/// The number of lower unused pins in PORTD.
///
uint8_t gPortBitOffset = 2;
/// The current lighten digit index.
///
uint8_t gCurrentDigitIndex = 0;
/// The pre-calculated register masks for all digits.
/// This includes being shifted into correct port bit position.
///
volatile uint16_t gDigitOutputMask[cDigitCount] = {
cDigitPortBit[0],
cDigitPortBit[1],
cDigitPortBit[2]
};
// Helper Functions
// ---------------------------------------------------------------------------
/// Get the port D mask part from the combined mask.
///
/// This `constexpr` will allow the compiler to resolve this function call
/// into the optimized code.
///
/// @param mask The combined mask.
/// @return The mask for port D.
///
constexpr uint8_t getPortDMask(const uint16_t mask)
{
return static_cast<uint8_t>(mask & 0x00ffu);
}
/// Get the port B mask part from the combined mask.
///
/// @param mask The combined mask.
/// @return The mask for port B.
///
constexpr uint8_t getPortBMask(const uint16_t mask)
{
return static_cast<uint8_t>(mask >> 8);
}
/// Update the ports to light up the next digit.
///
/// This function is called from the interrupt to light up the next digit
/// on the display.
///
void updatePorts()
{
uint8_t portD = PORTD;
uint8_t portB = PORTB;
portD &= ~getPortDMask(gPortBitMask);
portB &= ~getPortBMask(gPortBitMask);
portD |= getPortDMask(gDigitOutputMask[gCurrentDigitIndex]);
portB |= getPortBMask(gDigitOutputMask[gCurrentDigitIndex]);
PORTD = portD;
PORTB = portB;
++gCurrentDigitIndex;
if (gCurrentDigitIndex == cDigitCount) {
gCurrentDigitIndex = 0;
}
}
/// Search the mask for a character in the table.
///
/// @param c The character to search.
/// @return The mask for the character.
///
uint8_t getDigitMask(char c)
{
const DigitMask *current = cDigitMask;
while (current->c != c) {
++current;
if (current->c == '\0') {
break;
}
}
return current->mask;
}
/// Convert the digit mask into the actual port mask.
///
/// @param segmentMask The abstract mask for the digits to display.
/// @return The port mask for the pin bits to enable.
///
uint16_t getPortBitsFromSegmentMask(uint8_t segmentMask)
{
uint16_t portBits = 0;
for (uint8_t i = 0; i < cSegmentCount; ++i) {
if ((segmentMask & 0b1) != 0) {
uint8_t targetSegmentIndex;
if (gOrientation == Orientation::ConnectorOnTop) {
targetSegmentIndex = i;
} else {
targetSegmentIndex = cSegmentRotateMap[i];
}
portBits |= cSegmentPortBit[targetSegmentIndex];
}
segmentMask >>= 1;
}
return portBits;
}
/// Update the port bits.
///
/// @param portBits Array with the port bits to display.
///
void updatePortBits(uint16_t *portBits)
{
uint16_t digitOutputMask;
cli();
if (gOrientation == Orientation::ConnectorOnTop) {
for (uint8_t i = 0; i < cDigitCount; ++i) {
digitOutputMask = portBits[i];
digitOutputMask |= cDigitPortBit[i];
gDigitOutputMask[i] = digitOutputMask << gPortBitOffset;
}
} else {
for (uint8_t i = 0; i < cDigitCount; ++i) {
const uint8_t sourceIndex = cDigitCount-i-1;
digitOutputMask = portBits[sourceIndex];
digitOutputMask |= cDigitPortBit[i];
gDigitOutputMask[i] = digitOutputMask << gPortBitOffset;
}
}
sei();
}
// Interface Implementation
// ---------------------------------------------------------------------------
void initialize(
Frequency frequency,
Orientation orientation,
Pins pins)
{
// Store the orientation.
gOrientation = orientation;
// Resolve and save the pin offset.
if (pins == Pins::From2to11) {
gPortBitOffset = 2;
} else {
gPortBitOffset = 4;
}
// Pre-calculate the port bit mask.
gPortBitMask = cPrePortBitMask << gPortBitOffset;
// Initialize the ports. Set all used pins to output and into low state.
DDRD |= getPortDMask(gPortBitMask);
DDRB |= getPortBMask(gPortBitMask);
PORTD &= ~getPortDMask(gPortBitMask);
PORTB &= ~getPortBMask(gPortBitMask);
// Initialize timer2 for the display refresh.
ASSR = 0; // Synchronous internal clock.
if (frequency != Frequency::Insane) {
TCCR2A = 0; // Normal operation.
// Simple use the lower 3 bits for pre-scaling to set the
// multiplexer speed.
TCCR2B = static_cast<uint8_t>(frequency);
OCR2A = 0; // Ignore the compare
OCR2B = 0; // Ignore the compare
TIMSK2 = _BV(TOIE2); // Interrupt on overflow.
} else {
TCCR2A = _BV(WGM21); // CTC mode.
TCCR2B = _BV(CS20); // No multiplexing.
OCR2A = 0x80; // Only count to 0x80
TIMSK2 = _BV(OCIE2A); // Interrupt on overflow.
}
sei(); // Allow interrupts.
}
void setOrientation(Orientation orientation)
{
gOrientation = orientation;
}
void setDigits(const char *text)
{
// Check if we got a pointer to text.
if (text == nullptr) {
return;
}
// Calculate the new bit masks for the ports.
uint16_t portBits[cDigitCount];
for (uint8_t i = 0; i < cDigitCount; ++i) {
portBits[i] = 0;
}
for (uint8_t i = 0; i < cDigitCount; ++i) {
if (text[i] == '\0') {
break;
}
const uint8_t segmentMask = getDigitMask(text[i]);
portBits[i] = getPortBitsFromSegmentMask(segmentMask);
}
// Update the output masks for the display.
updatePortBits(portBits);
}
void setSegments(const uint8_t *segmentMasks)
{
uint16_t portBits[cDigitCount];
for (uint8_t i = 0; i < cDigitCount; ++i) {
const uint8_t segmentMask = segmentMasks[i];
portBits[i] = getPortBitsFromSegmentMask(segmentMask);
}
// Update the output masks for the display.
updatePortBits(portBits);
}
}
}
/// The interrupt handler for timer 2.
///
ISR(TIMER2_OVF_vect)
{
lr::ThreeDigits::updatePorts();
}
ISR(TIMER2_COMPA_vect)
{
lr::ThreeDigits::updatePorts();
}