forked from DeanIsMe/SevSeg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SevSeg.cpp
587 lines (515 loc) · 18.3 KB
/
SevSeg.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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
/* SevSeg Library
*
* Copyright 2020 Dean Reading
*
* This library allows an Arduino to easily display numbers and letters on a
* 7-segment display without a separate 7-segment display controller.
*
* See the included readme for instructions.
* https://github.com/DeanIsMe/SevSeg
*/
#include "SevSeg.h"
#define BLANK_IDX 36 // Must match with 'digitCodeMap'
#define DASH_IDX 37
#define PERIOD_IDX 38
#define ASTERISK_IDX 39
#define UNDERSCORE_IDX 40
static const int32_t powersOf10[] = {
1, // 10^0
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000
}; // 10^9
static const int32_t powersOf16[] = {
0x1, // 16^0
0x10,
0x100,
0x1000,
0x10000,
0x100000,
0x1000000,
0x10000000
}; // 16^7
// digitCodeMap indicate which segments must be illuminated to display
// each number.
static const uint8_t digitCodeMap[] = {
// GFEDCBA Segments 7-segment map:
0b00111111, // 0 "0" AAA
0b00000110, // 1 "1" F B
0b01011011, // 2 "2" F B
0b01001111, // 3 "3" GGG
0b01100110, // 4 "4" E C
0b01101101, // 5 "5" E C
0b01111101, // 6 "6" DDD
0b00000111, // 7 "7"
0b01111111, // 8 "8"
0b01101111, // 9 "9"
0b01110111, // 65 'A'
0b01111100, // 66 'b'
0b00111001, // 67 'C'
0b01011110, // 68 'd'
0b01111001, // 69 'E'
0b01110001, // 70 'F'
0b00111101, // 71 'G'
0b01110110, // 72 'H'
0b00110000, // 73 'I'
0b00001110, // 74 'J'
0b01110110, // 75 'K' Same as 'H'
0b00111000, // 76 'L'
0b00000000, // 77 'M' NO DISPLAY
0b01010100, // 78 'n'
0b00111111, // 79 'O'
0b01110011, // 80 'P'
0b01100111, // 81 'q'
0b01010000, // 82 'r'
0b01101101, // 83 'S'
0b01111000, // 84 't'
0b00111110, // 85 'U'
0b00111110, // 86 'V' Same as 'U'
0b00000000, // 87 'W' NO DISPLAY
0b01110110, // 88 'X' Same as 'H'
0b01101110, // 89 'y'
0b01011011, // 90 'Z' Same as '2'
0b00000000, // 32 ' ' BLANK
0b01000000, // 45 '-' DASH
0b10000000, // 46 '.' PERIOD
0b01100011, // 42 '*' DEGREE ..
0b00001000, // 95 '_' UNDERSCORE
};
// Constant pointers to constant data
const uint8_t * const numeralCodes = digitCodeMap;
const uint8_t * const alphaCodes = digitCodeMap + 10;
// SevSeg Constructor
/******************************************************************************/
SevSeg::SevSeg() {
// Initial value
ledOnTime = 2000; // Corresponds to a brightness of 100
waitOffTime = 0;
waitOffActive = false;
numDigits = 0;
prevUpdateIdx = 0;
prevUpdateTime = 0;
resOnSegments = 0;
updateWithDelays = 0;
}
// begin
/******************************************************************************/
// Saves the input pin numbers to the class and sets up the pins to be used.
// If you use current-limiting resistors on your segment pins instead of the
// digit pins, then set resOnSegments as true.
// Set updateWithDelays to true if you want to use the 'pre-2017' update method
// In that case, the processor is occupied with delay functions while refreshing
// leadingZerosIn indicates whether leading zeros should be displayed
// disableDecPoint is true when the decimal point segment is not connected, in
// which case there are only 7 segments.
void SevSeg::begin(uint8_t hardwareConfig, uint8_t numDigitsIn, const uint8_t digitPinsIn[],
const uint8_t segmentPinsIn[], bool resOnSegmentsIn,
bool updateWithDelaysIn, bool leadingZerosIn, bool disableDecPoint) {
resOnSegments = resOnSegmentsIn;
updateWithDelays = updateWithDelaysIn;
leadingZeros = leadingZerosIn;
numDigits = numDigitsIn;
numSegments = disableDecPoint ? 7 : 8; // Ternary 'if' statement
//Limit the max number of digits to prevent overflowing
if (numDigits > MAXNUMDIGITS) numDigits = MAXNUMDIGITS;
switch (hardwareConfig) {
case 0: // Common cathode
digitOnVal = LOW;
segmentOnVal = HIGH;
break;
case 1: // Common anode
digitOnVal = HIGH;
segmentOnVal = LOW;
break;
case 2: // With active-high, low-side switches (most commonly N-type FETs)
digitOnVal = HIGH;
segmentOnVal = HIGH;
break;
case 3: // With active low, high side switches (most commonly P-type FETs)
digitOnVal = LOW;
segmentOnVal = LOW;
break;
}
// define the Off-Values depending on the On-Values
if (digitOnVal == HIGH){
digitOffVal = LOW;
} else {
digitOffVal = HIGH;
}
// define the Off-Values depending on the On-Values
if (segmentOnVal == HIGH){
segmentOffVal = LOW;
} else {
segmentOffVal = HIGH;
}
// Save the input pin numbers to library variables
for (uint8_t segmentNum = 0 ; segmentNum < numSegments ; segmentNum++) {
segmentPins[segmentNum] = segmentPinsIn[segmentNum];
}
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
digitPins[digitNum] = digitPinsIn[digitNum];
}
// Set the pins as outputs, and turn them off
for (uint8_t digit = 0 ; digit < numDigits ; digit++) {
pinMode(digitPins[digit], OUTPUT);
digitalWrite(digitPins[digit], digitOffVal);
}
for (uint8_t segmentNum = 0 ; segmentNum < numSegments ; segmentNum++) {
pinMode(segmentPins[segmentNum], OUTPUT);
digitalWrite(segmentPins[segmentNum], segmentOffVal);
}
blank(); // Initialise the display
}
// refreshDisplay
/******************************************************************************/
// Turns on the segments specified in 'digitCodes[]'
// There are 4 versions of this function, with the choice depending on the
// location of the current-limiting resistors, and whether or not you wish to
// use 'update delays' (the standard method until 2017).
// For resistors on *digits* we will cycle through all 8 segments (7 + period),
// turning on the *digits* as appropriate for a given segment, before moving on
// to the next segment.
// For resistors on *segments* we will cycle through all __ # of digits,
// turning on the *segments* as appropriate for a given digit, before moving on
// to the next digit.
// If using update delays, refreshDisplay has a delay between each digit/segment
// as it cycles through. It exits with all LEDs off.
// If not using updateDelays, refreshDisplay exits with a single digit/segment
// on. It will move to the next digit/segment after being called again (if
// enough time has passed).
void SevSeg::refreshDisplay() {
if (!updateWithDelays) {
uint32_t us = micros();
// Exit if it's not time for the next display change
if (waitOffActive) {
if ((us - prevUpdateTime) < waitOffTime) return;
}
else {
if ((us - prevUpdateTime) < ledOnTime) return;
}
prevUpdateTime = us;
if (!resOnSegments) {
/**********************************************/
// RESISTORS ON DIGITS, UPDATE WITHOUT DELAYS
if (waitOffActive) {
waitOffActive = false;
}
else {
// Turn all lights off for the previous segment
segmentOff(prevUpdateIdx);
if (waitOffTime) {
// Wait a delay with all lights off
waitOffActive = true;
return;
}
}
prevUpdateIdx++;
if (prevUpdateIdx >= numSegments) prevUpdateIdx = 0;
// Illuminate the required digits for the new segment
segmentOn(prevUpdateIdx);
}
else {
/**********************************************/
// RESISTORS ON SEGMENTS, UPDATE WITHOUT DELAYS
if (waitOffActive) {
waitOffActive = false;
}
else {
// Turn all lights off for the previous digit
digitOff(prevUpdateIdx);
if (waitOffTime) {
// Wait a delay with all lights off
waitOffActive = true;
return;
}
}
prevUpdateIdx++;
if (prevUpdateIdx >= numDigits) prevUpdateIdx = 0;
// Illuminate the required segments for the new digit
digitOn(prevUpdateIdx);
}
}
else {
if (!resOnSegments) {
/**********************************************/
// RESISTORS ON DIGITS, UPDATE WITH DELAYS
for (uint8_t segmentNum = 0 ; segmentNum < numSegments ; segmentNum++) {
// Illuminate the required digits for this segment
segmentOn(segmentNum);
// Wait with lights on (to increase brightness)
delayMicroseconds(ledOnTime);
// Turn all lights off
segmentOff(segmentNum);
// Wait with all lights off if required
if (waitOffTime) delayMicroseconds(waitOffTime);
}
}
else {
/**********************************************/
// RESISTORS ON SEGMENTS, UPDATE WITH DELAYS
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
// Illuminate the required segments for this digit
digitOn(digitNum);
// Wait with lights on (to increase brightness)
delayMicroseconds(ledOnTime);
// Turn all lights off
digitOff(digitNum);
// Wait with all lights off if required
if (waitOffTime) delayMicroseconds(waitOffTime);
}
}
}
}
// segmentOn
/******************************************************************************/
// Turns a segment on, as well as all corresponding digit pins
// (according to digitCodes[])
void SevSeg::segmentOn(uint8_t segmentNum) {
digitalWrite(segmentPins[segmentNum], segmentOnVal);
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
if (digitCodes[digitNum] & (1 << segmentNum)) { // Check a single bit
digitalWrite(digitPins[digitNum], digitOnVal);
}
}
}
// segmentOff
/******************************************************************************/
// Turns a segment off, as well as all digit pins
void SevSeg::segmentOff(uint8_t segmentNum) {
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
digitalWrite(digitPins[digitNum], digitOffVal);
}
digitalWrite(segmentPins[segmentNum], segmentOffVal);
}
// digitOn
/******************************************************************************/
// Turns a digit on, as well as all corresponding segment pins
// (according to digitCodes[])
void SevSeg::digitOn(uint8_t digitNum) {
digitalWrite(digitPins[digitNum], digitOnVal);
for (uint8_t segmentNum = 0 ; segmentNum < numSegments ; segmentNum++) {
if (digitCodes[digitNum] & (1 << segmentNum)) { // Check a single bit
digitalWrite(segmentPins[segmentNum], segmentOnVal);
}
}
}
// digitOff
/******************************************************************************/
// Turns a digit off, as well as all segment pins
void SevSeg::digitOff(uint8_t digitNum) {
for (uint8_t segmentNum = 0 ; segmentNum < numSegments ; segmentNum++) {
digitalWrite(segmentPins[segmentNum], segmentOffVal);
}
digitalWrite(digitPins[digitNum], digitOffVal);
}
// setBrightness
/******************************************************************************/
// Sets ledOnTime according to the brightness given. Standard brightness range
// is 0 to 100. Flickering is more likely at brightness > 100, and < -100.
// A positive brightness introduces a delay while the LEDs are on, and a
// negative brightness introduces a delay while the LEDs are off.
void SevSeg::setBrightness(int16_t brightness) {
brightness = constrain(brightness, -200, 200);
if (brightness > 0) {
ledOnTime = map(brightness, 0, 100, 1, 2000);
waitOffTime = 0;
waitOffActive = false;
}
else {
ledOnTime = 0;
waitOffTime = map(brightness, 0, -100, 1, 2000);
}
}
// setNumber
/******************************************************************************/
// Receives an integer and passes it to 'setNewNum'.
void SevSeg::setNumber(int32_t numToShow, int8_t decPlaces, bool hex) { //int32_t
setNewNum(numToShow, decPlaces, hex);
}
// setNumberF
/******************************************************************************/
// Receives a float, prepares it, and passes it to 'setNewNum'.
void SevSeg::setNumberF(float numToShow, int8_t decPlaces, bool hex) { //float
int8_t decPlacesPos = constrain(decPlaces, 0, MAXNUMDIGITS);
if (hex) {
numToShow = numToShow * powersOf16[decPlacesPos];
}
else {
numToShow = numToShow * powersOf10[decPlacesPos];
}
// Modify the number so that it is rounded to an integer correctly
numToShow += (numToShow >= 0.f) ? 0.5f : -0.5f;
setNewNum((int32_t)numToShow, (int8_t)decPlaces, hex);
}
// setNewNum
/******************************************************************************/
// Changes the number that will be displayed.
void SevSeg::setNewNum(int32_t numToShow, int8_t decPlaces, bool hex) {
uint8_t digits[MAXNUMDIGITS];
findDigits(numToShow, decPlaces, hex, digits);
setDigitCodes(digits, decPlaces);
}
// setSegments
/******************************************************************************/
// Sets the 'digitCodes' that are required to display the desired segments.
// Using this function, one can display any arbitrary set of segments (like
// letters, symbols or animated cursors). See setDigitCodes() for common
// numeric examples.
//
// Bit-segment mapping: 0bHGFEDCBA
// Visual mapping:
// AAAA 0000
// F B 5 1
// F B 5 1
// GGGG 6666
// E C 4 2
// E C 4 2 (Segment H is often called
// DDDD H 3333 7 DP, for Decimal Point)
void SevSeg::setSegments(const uint8_t segs[]) {
for (uint8_t digit = 0; digit < numDigits; digit++) {
digitCodes[digit] = segs[digit];
}
}
// setSegmentsDigit
/******************************************************************************/
// Like setSegments above, but only manipulates the segments for one digit
// digitNum is 0-indexed.
void SevSeg::setSegmentsDigit(const uint8_t digitNum, const uint8_t segs) {
if (digitNum < numDigits) {
digitCodes[digitNum] = segs;
}
}
// getSegments
/******************************************************************************/
// Gets the 'digitCodes' of currently displayed segments.
// Using this function, one can get the current set of segments (placed
// elsewhere) and manipulate them to obtain effects, for example blink of
// only some digits.
// See setSegments() for bit-segment mapping
//
void SevSeg::getSegments(uint8_t segs[]) {
for (uint8_t digit = 0; digit < numDigits; digit++) {
segs[digit] = digitCodes[digit];
}
}
// setChars
/******************************************************************************/
// Displays the string on the display, as best as possible.
// Only alphanumeric characters plus '-' and ' ' are supported
void SevSeg::setChars(const char str[]) {
for (uint8_t digit = 0; digit < numDigits; digit++) {
digitCodes[digit] = 0;
}
uint8_t strIdx = 0; // Current position within str[]
for (uint8_t digitNum = 0; digitNum < numDigits; digitNum++) {
char ch = str[strIdx];
if (ch == '\0') break; // NULL string terminator
if (ch >= '0' && ch <= '9') { // Numerical
digitCodes[digitNum] = numeralCodes[ch - '0'];
}
else if (ch >= 'A' && ch <= 'Z') {
digitCodes[digitNum] = alphaCodes[ch - 'A'];
}
else if (ch >= 'a' && ch <= 'z') {
digitCodes[digitNum] = alphaCodes[ch - 'a'];
}
else if (ch == ' ') {
digitCodes[digitNum] = digitCodeMap[BLANK_IDX];
}
else if (ch == '.') {
digitCodes[digitNum] = digitCodeMap[PERIOD_IDX];
}
else if (ch == '*') {
digitCodes[digitNum] = digitCodeMap[ASTERISK_IDX];
}
else if (ch == '_') {
digitCodes[digitNum] = digitCodeMap[UNDERSCORE_IDX];
}
else {
// Every unknown character is shown as a dash
digitCodes[digitNum] = digitCodeMap[DASH_IDX];
}
strIdx++;
// Peek at next character. If it's a period, add it to this digit
if (str[strIdx] == '.') {
digitCodes[digitNum] |= digitCodeMap[PERIOD_IDX];
strIdx++;
}
}
}
// blank
/******************************************************************************/
void SevSeg::blank(void) {
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
digitCodes[digitNum] = digitCodeMap[BLANK_IDX];
}
segmentOff(0);
digitOff(0);
}
// findDigits
/******************************************************************************/
// Decides what each digit will display.
// Enforces the upper and lower limits on the number to be displayed.
// digits[] is an output
void SevSeg::findDigits(int32_t numToShow, int8_t decPlaces, bool hex, uint8_t digits[]) {
const int32_t * powersOfBase = hex ? powersOf16 : powersOf10;
const int32_t maxNum = powersOfBase[numDigits] - 1;
const int32_t minNum = -(powersOfBase[numDigits - 1] - 1);
// If the number is out of range, just display dashes
if (numToShow > maxNum || numToShow < minNum) {
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
digits[digitNum] = DASH_IDX;
}
}
else {
uint8_t digitNum = 0;
// Convert all number to positive values
if (numToShow < 0) {
digits[0] = DASH_IDX;
digitNum = 1; // Skip the first iteration
numToShow = -numToShow;
}
// Find all digits for base's representation, starting with the most
// significant digit
for ( ; digitNum < numDigits ; digitNum++) {
int32_t factor = powersOfBase[numDigits - 1 - digitNum];
digits[digitNum] = numToShow / factor;
numToShow -= digits[digitNum] * factor;
}
// Find unnnecessary leading zeros and set them to BLANK
if (decPlaces < 0) decPlaces = 0;
if (!leadingZeros) {
for (digitNum = 0 ; digitNum < (numDigits - 1 - decPlaces) ; digitNum++) {
if (digits[digitNum] == 0) {
digits[digitNum] = BLANK_IDX;
}
// Exit once the first non-zero number is encountered
else if (digits[digitNum] <= 9) {
break;
}
}
}
}
}
// setDigitCodes
/******************************************************************************/
// Sets the 'digitCodes' that are required to display the input numbers
void SevSeg::setDigitCodes(const uint8_t digits[], int8_t decPlaces) {
// Set the digitCode for each digit in the display
for (uint8_t digitNum = 0 ; digitNum < numDigits ; digitNum++) {
digitCodes[digitNum] = digitCodeMap[digits[digitNum]];
// Set the decimal point segment
if (decPlaces >= 0) {
if (digitNum == numDigits - 1 - decPlaces) {
digitCodes[digitNum] |= digitCodeMap[PERIOD_IDX];
}
}
}
}
/// END ///