/*
I am developing this class on an ESP32 with the Arduino IDE. An previous version of this class already exists on my PC and I am using it as a reference.
But I found out, that the used structure of the hardware settings is not suitable for my needs. So I decided to develop a total new structure for the definition of the display hardware.
I thinksometimes, it is published on my github. There is already a sketch with a very simple implementation of Charliplexing 6 LEDs.
This can be found on my github https://github.com/mikesch-1960/demo-Charlieplexing-ESP32-GPIO and also on wokwi https://wokwi.com/projects/411567201737096193.
In this sketch I implemented special wokwi code. If 'RunOnWOKWI' is defined, the sketch can run on wokwi with 7- and 2segment displays. This helps when testing some new features, but it is not time accurate.
For a final version, both defines 'RunOnWOKWI' and 'CPD_TESTS' and the corresponding code should be removed from the sketch.
*/
/*
The special thing about this version:
I developed a version with html and js for the display. In that version, I tested a lot of code and implemented new features. I used html because I can work much faster as I can with wokwi. Now I will go back to C++ and check all the things I did in the html version. For this I use the html version as a reference.
The displays comes from E-cigarette(Vapes). It can be consists of 7-segment and 2-segment digits, logos, symbols, unit signs and status inicators.
I suspect that the display connectors have internal resistors. However, I use the vape displays on ESPs without external resistors, since ESPs work with 3.3V, just like the vapes do.
Current problems:
- a negative number is ahown wrong on a group with an 2-segment digit and two 7-segment digits.
Improvments for the class CharlieplexDisplay:
✓ Faster GPIO usage.
✔ Change the LED mapping. Remove the signs. Use a twodimentional array to store the anode and cathode.
✓ Change the LedOn() function to use the new LED mapping.
✓ Remove the .litTime from the TLitList struct.
✓ Map the _digits and _leds to an array of structs, to make the code more readable.
✓ Handle the new digits array in verifySettings() and printDebug().
✓ Make sure that only one 2-segment digit exist in a group of 7-segment digits.
✓ Make sure that every digit has different firstLed and correct segmentCount.
✓ litNumber() does not work with the new digits structure.
✓ The 2-segment digits are not tested. I can try it by defining the left one as a 2-segment digit!
✓ Rename block to group
✓ Merge CharlieplexDisplay.js into CharlieplexDisplay.h
✓ Add Brighness property
✓ Use gamma correction for the brightness
- Try, if using a quadratic curve for the count of lit LEDs is usefull to get a more correct brightness for different numbers of LEDs
- Constrain the FPS to useful values
- Ability to show hexadecimal values.
- Add a segmentItem to be shown if the number, value or char is invalid for the selected digit group. F.e. 0b10110011 is looks similar to a question mark.
- Make the refresh() function non blocking, even if it is only using microseconds.
- Ability to Flash some lit LEDs
- Do I have to handle LEDs that are in the same position, but with different colors?
Improvments for other stuff:
✓ Change the dislay.svg acording to the new LEDs mapping.
✓ Remove Serial input from the ino file.
✓ If BTN_PIN is not defined, then every DisplayMode will be executed for a specific time in a loop.
✓ Extract the display config from the ino file into a separate header file.
✓ Possiblity to use other displays
✗ Read display config from a json file. Build a constructor for that.
✓ Add an ENUM CPDLED to the hardware config, so f.e. litLed(CPDLED.unit1) is possible.
Notes:
This project is still in development.
This project uses ArduinoIde 2 and ESP32 core V3
Faster GPIO usage:
- See my demo project:
https://github.com/mikesch-1960/demo-Charlieplexing-ESP32-GPIO
or on wokwi:
https://wokwi.com/projects/411567201737096193
- Doku zu esp32_hardware_gpio: https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/gpio.html
*/
// If defined, in setup() some testcode will be executed!
// #define CPD_TESTS
// If sketch should run on Wokwi
#define RunOnWOKWI
// Pinnumber for the push button that is used to circle through the displaymodes.
// If not defined, each DisplayMode is executed in a loop one after the other!
#define BTN_PIN GPIO_NUM_14
#include "display_7Pins_38Leds.h"
// #include "displays/display_6Pins_24Leds.h"
#include "CharlieplexDisplay.h"
CharlieplexDisplay display(display_pins, display_leds, display_digits, display_groups);
#ifdef RunOnWOKWI
#include <TM1637Display.h>
// #define CURR_GROUP_COUNT 2 // Equal to display.getGroupCount()
#define CURR_GROUP_COUNT sizeof(display_groups)/sizeof(display_groups[0])
// In wokwi there are 2 4-digit displays implemented. So at liest there are 8 digits
const int wokwiCLK_PINS[2] = {13, 26}; // Array for clock pins
const int wokwiDIO_PINS[2] = {12, 27}; // Array for data pins
TM1637Display wokwiGroup[2] = {
TM1637Display(wokwiCLK_PINS[0], wokwiDIO_PINS[0], 10), // First display
TM1637Display(wokwiCLK_PINS[1], wokwiDIO_PINS[1], 10) // Second display
};
uint8_t wokwiSegData[CURR_GROUP_COUNT][4] = { 0, 0, 0, 0 };
void wokwiRefreshDisplays() {
memset(wokwiSegData, 0, sizeof(wokwiSegData));
for (int i = 0; i < display.getLitLedCount(); i++) {
wokwiLitSegment(display.getLitLed(i));
}
for (int i = 0; i < CURR_GROUP_COUNT; i++) {
wokwiGroup[i].clear();
wokwiGroup[i].setSegments(wokwiSegData[i]);
}
}
void wokwiLitSegment(uint8_t ledIndex) {
if (ledIndex >= display.getLedCount()) {
Serial.printf("wokwiLitSegment(): Invalid LED index %d!\n", ledIndex);
return;
}
// Determine where to find the corresponding group and digit index of the led
uint8_t groupIndex = 255;
uint8_t digitIndex = 255;
CharlieplexDisplay::Group_t group;
CharlieplexDisplay::Digit_t digit;
// Find the corresponding digit index of the led index
for (uint8_t d = 0; d < display.getDigitCount(); d++) {
digit = display.getDigit(d);
if (ledIndex < digit.firstLEDIndex || ledIndex >= digit.firstLEDIndex + digit.segmentCount) {
continue; // The led does not belong to this digit
}
// digit found at index d
digitIndex = d;
// Find the group, the digit belongs to
for (uint8_t g = 0; g < display.getGroupCount(); g++) {
group = display.getGroup(g);
if (digitIndex < group.leftDigitIndex || digitIndex >= group.leftDigitIndex + group.digitCount) {
continue; // The digit does not belong to this group
}
groupIndex = g;
break;
}
// The led belongs to digit[digitIndex] in group[groupIndex]
break;
}
if (groupIndex >= display.getGroupCount() || digitIndex >= display.getDigitCount()) {
// LED do not belong to a digit field, so must be a symbol or indicator led.
// Such LEDs are not built into the wokwi circuit.
// Serial.printf("Symbol or indicator LED index %d is lit now!\n", ledIndex);
return;
}
// The hardware digits are indexed from 0 to 3 each. The defined digits are indexed from 0 to 2.
//Serial.printf("wokwiLitSegment(): ledIndex=%d results in groupIndex=%d, digitIndex=%d\n", ledIndex, groupIndex, digitIndex);
// We must adjust the ledIndex to the segment index
ledIndex = ledIndex - digit.firstLEDIndex;
// We must remove the groupIndex offset from the digitIndex
digitIndex -= groupIndex * group.digitCount;
if (digit.segmentCount == 2)
ledIndex++;
wokwiSegData[groupIndex][digitIndex] |= 1 << ledIndex;
} // wokwiLitSegment()
#endif // RunOnWOKWI
void ClearSerial() {
while (Serial.available()) {
Serial.read();
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool setupError = false; // If true, this prevents the loop() from executing invalid code
void setup() {
Serial.begin(115200);
delay(500);
#ifdef BTN_PIN
pinMode(BTN_PIN, INPUT_PULLUP);
#endif
#ifdef RunOnWOKWI
Serial.println("Configured for Running on Wokwi! ");
for (int i = 0; i < CURR_GROUP_COUNT; i++) {
wokwiGroup[i].setBrightness(7); //set the wokwi diplay to maximum brightness (brightness 0-7)
wokwiGroup[i].clear();
}
#endif // RunOnWOKWI
Serial.println();
Serial.println("------------------------------------------------------------------");
Serial.println("--- Starting LED Charlieplexing program! ---");
Serial.println();
if (! display.begin(75)) {
display.printDebugInfo(true);
setupError = true;
return;
}
// display.printDebugInfo();
#ifdef CPD_TESTS
// display.litGroupStr("Hello, World!", 0);
display.litGroupInt(-1, 0);
#ifndef RunOnWOKWI
display.refresh();
#else
wokwiRefreshDisplays();
#endif
uint8_t arg2 = 7;
int8_t arg3 = 0;
int res = display.getFirstDigit(arg2, arg3);
if (res >= 0) {
Serial.printf("Found digit %d for args: %d, %d\n", res, arg2, arg3);
} else {
Serial.printf("No digit found for args: %d, %d\n", arg2, arg3);
}
#endif
ClearSerial();
} // setup()
enum TDisplayModes {
MODE_ALL, // Light up all LEDs on the display
MODE_CIRCLELEDS, // Circle through all single LEDs
MODE_COUNTER, // Show a counter from 0 to 99 on the two 7-segment displays
MODE_ALLCHARS, // Circle through every possible char on the first 7-segment display
MODE_ONETOALL, // Starts with one LED and end with all LEDs
MODE_END
};
unsigned long nextLoopMS = 0;
int loopNumber = 0;
TDisplayModes curDisplayMode = TDisplayModes(MODE_ALL);
TDisplayModes prevDisplayMode = TDisplayModes(MODE_END);
#define getNextMode() static_cast<TDisplayModes>((curDisplayMode + 1) % TDisplayModes::MODE_END)
#ifdef RunOnWOKWI
bool litListChanged = false;
#endif
constexpr char LF = 10; // ASCII for Line Feed
constexpr char CR = 13; // ASCII for Carriage Return
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define CNTR_GRP 0 // Group index for the the counter mode values
void loop() {
if (setupError) {
delay(1000);
return;
}
#ifdef RunOnWOKWI && CPD_TESTS
delay(50);
return;
#endif
// Read serial input to set the display brightness. Note, that a lineending mode in the serial monitor must be selected!
if (Serial.available()) {
float br = Serial.parseFloat();
ClearSerial();
display.setBrightness(br);
}
// Check if it is time to refresh the display
if (millis() >= nextLoopMS || prevDisplayMode != curDisplayMode) {
#ifdef RunOnWOKWI
litListChanged = true;
#endif
if (prevDisplayMode != curDisplayMode) {
prevDisplayMode = curDisplayMode;
loopNumber = 0;
display.LitList.clear();
switch (curDisplayMode) {
case MODE_ALL:
Serial.println("Current mode = MODE_ALL"); break;
case MODE_CIRCLELEDS:
Serial.println("Current mode = MODE_CIRCLELEDS"); break;
case MODE_COUNTER:
Serial.println("Current mode = MODE_COUNTER");
loopNumber = display.getGroupIntMin(CNTR_GRP);
break;
case MODE_ALLCHARS:
Serial.println("Current mode = MODE_ALLCHARS"); break;
case MODE_ONETOALL:
Serial.println("Current mode = MODE_ONETOALL"); break;
}
}
else {
// Goto the next displaymode loop
loopNumber++;
}
uint32_t LOOP_DELAY;
switch (curDisplayMode) {
case MODE_ALL:
#ifndef BTN_PIN
// If no push button is used, go to the next displaymode
if (loopNumber > 0) {
curDisplayMode = getNextMode();
return;
}
#endif
// Add all LEDs to the lit list
if (loopNumber == 0) {
display.LitList.clear();
for (uint8_t i = 0; i < display.getLedCount(); i++) display.LitList.add(i);
}
LOOP_DELAY = 3000;
break;
case MODE_CIRCLELEDS:
if (loopNumber >= display.getLedCount()) {
#ifndef BTN_PIN
curDisplayMode = getNextMode();
return;
#endif
loopNumber = 0;
}
// Add the corresponding LED to the lit list
display.LitList.clear();
display.LitList.add(loopNumber);
LOOP_DELAY = 500;
break;
case MODE_COUNTER:
if (loopNumber > display.getGroupIntMax(CNTR_GRP)) {
#ifndef BTN_PIN
curDisplayMode = getNextMode();
return;
#endif
loopNumber = display.getGroupIntMin(CNTR_GRP);
}
display.LitList.clear();
display.litGroupInt(loopNumber, CNTR_GRP);
LOOP_DELAY = 1000;
break;
case MODE_ALLCHARS:
// Use the next character
if (loopNumber >= CharlieplexDisplay::NUM_SEGMAP_EBTRYS) {
#ifndef BTN_PIN
curDisplayMode = getNextMode();
return;
#endif
loopNumber = 0;
}
display.LitList.clear();
display.litDigitChar(CharlieplexDisplay::DigitChars[loopNumber], display.getFirstDigit(7, 0));
LOOP_DELAY = 1000;
break;
case MODE_ONETOALL:
if (loopNumber >= display.getLedCount()) {
#ifndef BTN_PIN
curDisplayMode = getNextMode();
return;
#endif
loopNumber = 0;
}
if (loopNumber == 0) { // init the new animation loop
display.LitList.clear();
}
display.LitList.add(loopNumber);
LOOP_DELAY = 300;
break;
}
prevDisplayMode = curDisplayMode;
nextLoopMS = millis() + LOOP_DELAY;
}
#ifndef RunOnWOKWI
display.refresh();
#else
if (litListChanged) wokwiRefreshDisplays();
litListChanged = false;
delay(10);
#endif
// If a push-button is added to a pin
#ifdef BTN_PIN
int btn = digitalRead(BTN_PIN);
if (btn == LOW) {
// Get the next display mode
curDisplayMode = getNextMode();
// While the button is pressed, show the current mode number in display
int m = (int)curDisplayMode;
display.LitList.clear();
// Every display should have at least two 7-segment digits
display.litGroupStr("d"+String(m), 0);
#ifdef RunOnWOKWI
litListChanged = true;
#endif
uint32_t downTime = millis();
while(digitalRead(BTN_PIN) == LOW || millis() - downTime < 200) {
#ifndef RunOnWOKWI
display.refresh();
#else
if (litListChanged) wokwiRefreshDisplays();
litListChanged = false;
delay(10);
#endif
};
Serial.println("Changing display mode...");
}
#endif
#ifdef RunOnWOKWI
delay(10);
#endif
} // loop()