diff --git a/.github/workflows/TestCompile.yml b/.github/workflows/TestCompile.yml index 5707314..b6bfe0e 100644 --- a/.github/workflows/TestCompile.yml +++ b/.github/workflows/TestCompile.yml @@ -25,16 +25,16 @@ jobs: - name: Checkout uses: actions/checkout@master - - name: Checkout custom library - uses: actions/checkout@master - with: - repository: ArminJo/NeoPatterns - ref: master - path: CustomNeoPatterns # must contain string "Custom" +# - name: Checkout custom library +# uses: actions/checkout@master +# with: +# repository: ArminJo/NeoPatterns +# ref: master +# path: CustomNeoPatterns # must contain string "Custom" - name: Compile all examples uses: ArminJo/arduino-test-compile@master with: # required-libraries: Adafruit NeoPixel,PlayRtttl,SoftI2CMaster - required-libraries: Adafruit NeoPixel,PlayRtttl + required-libraries: Adafruit NeoPixel,PlayRtttl,NeoPatterns # build-properties: -DSERIAL_RX_BUFFER_SIZE=8 diff --git a/OpenledRace/AVRUtils.cpp b/OpenledRace/AVRUtils.cpp index dff3d1a..187bde2 100644 --- a/OpenledRace/AVRUtils.cpp +++ b/OpenledRace/AVRUtils.cpp @@ -4,7 +4,7 @@ * Stack, Ram and Heap utilities. * Sleep utilities. * - * Copyright (C) 2016-2023 Armin Joachimsmeyer + * Copyright (C) 2016-2024 Armin Joachimsmeyer * Email: armin.joachimsmeyer@gmail.com * * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. @@ -30,215 +30,307 @@ #include #include #include // for __malloc_margin -/* - * The largest address just not allocated so far - * Under Unix, the "break value" was the end of the data - * segment as dynamically requested from the operating system. - * Since we don't have an operating system, just make sure - * that we don't collide with the stack. - */ -extern void *__brkval; // The largest address just not allocated so far /* - * Returns actual start of free heap + * Returns actual start of available / free heap * Usage for print: - Serial.print(F("HeapStart=0x")); - Serial.println((uintptr_t) getHeapStart(), HEX); + Serial.print(F("AvailableHeapStart=0x")); + Serial.println((uint16_t) getAvailableHeapStart(), HEX); */ -uint8_t* getHeapStart(void) { +uint8_t* getAvailableHeapStart(void) { if (__brkval == 0) { - __brkval = __malloc_heap_start; + // __brkval is 0 if no malloc() has happened before +// __brkval = __malloc_heap_start; + __brkval = &__heap_start; } return (uint8_t*) __brkval; } +void printAvailableHeapStart(Print *aSerial) { + aSerial->print(F("Heap start=")); + aSerial->println((uint16_t) getAvailableHeapStart()); +} /* * Initialize RAM between current stack and actual heap start (__brkval) with pattern 0x5A */ void initStackFreeMeasurement() { - uint8_t tDummyVariableOnStack; - uint8_t *tHeapPtr = getHeapStart(); + uint8_t *tHeapPtr = getAvailableHeapStart(); // This sets __brkval // Fill / paint stack do { *tHeapPtr++ = HEAP_STACK_UNTOUCHED_VALUE; - } while (tHeapPtr < &tDummyVariableOnStack); + } while (tHeapPtr < (uint8_t*) SP); } /* - * Returns the amount of stack/heap not used/touched since the last call to initStackFreeMeasurement() - * by check for first touched pattern on the stack/heap, starting the UPWARD search at heap start. - * Sets the variable aStackUsedBytesPointer points to with amount of used/touched bytes. - * - * This fails, if a HEAP_STACK_UNTOUCHED_VALUE was written in a former malloced block. + * @return The amount of used/touched bytes since the last call to initStackFreeMeasurement() + * -1 if stack was completely used + * Check for first touched pattern on the stack/heap, starting the UPWARD search at heap start. */ -uint16_t getStackUnusedAndUsedBytes(uint16_t *aStackUsedBytesPointer) { - uint8_t tDummyVariableOnStack; - uint8_t *tHeapPtr = getHeapStart(); - -// first search for first untouched value after current begin of heap, because malloc() and free() may be happened in between and overwrite low memory - while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr < &tDummyVariableOnStack) { - tHeapPtr++; +/* + * @param aStackUnusedSizePointer points to variable which is written with amount of stack/heap not used/touched. + * @return The amount of stack/heap touched since the last call to initStackFreeMeasurement() + * -1 if stack was completely used + * Do a downward search, because upward may be wrong, because malloc does not initialize the memory + * and the search fails with multiple mallocs and partial writing of allocated regions. + * Check for first two untouched pattern on the stack/heap, starting the DOWNWARD search at current stack pointer. + */ +//#include // for Serial +int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer) { + uint8_t *tAvailableHeapStart = getAvailableHeapStart(); // __brkval + uint8_t *tStackSearchPtr = (uint8_t*) SP; + + // Search for first two untouched values below current stackpointer. + // tStackSearchPtr > tAvailableHeapStart avoids overflow if stack was completely touched before. + while (!(*tStackSearchPtr == HEAP_STACK_UNTOUCHED_VALUE && *(tStackSearchPtr - 1) == HEAP_STACK_UNTOUCHED_VALUE) + && tStackSearchPtr > tAvailableHeapStart) { + tStackSearchPtr--; } -// then count untouched patterns +// Serial.println((uint16_t) tStackSearchPtr, HEX); +// Serial.println((uint16_t) tAvailableHeapStart, HEX); + /* + * tStackSearchPtr points now to highest untouched stack position + */ + int16_t tStackMaxUsedSize = RAMEND - (uint16_t) tStackSearchPtr; + + // Search for first touched value used stack. uint16_t tStackUnused = 0; - while (*tHeapPtr == HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr < &tDummyVariableOnStack) { - tHeapPtr++; + while (*tStackSearchPtr == HEAP_STACK_UNTOUCHED_VALUE && tStackSearchPtr > tAvailableHeapStart) { + tStackSearchPtr--; tStackUnused++; } - *aStackUsedBytesPointer = (RAMEND - (uint16_t) tHeapPtr) + 1; - - return tStackUnused; + *aStackUnusedSizePointer = tStackUnused; + if (tStackUnused == 0) { + return -1; + } + return tStackMaxUsedSize; } /* - * Returns the amount of stack/heap touched since the last call to initStackFreeMeasurement() - * by check for first non touched pattern on the stack/heap, starting the DOWNWARD search at current stack pointer. - * - * This returns too big value, if the end of former malloced and written memory was higher than current stackpointer, - * which nevertheless looks like a potential programming problem. + * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() + * Example: "Stack unused=0, used=16" if stack runs into data */ -uint16_t getStackUsedBytes() { - uint8_t tDummyVariableOnStack; - uint8_t *tHeapStart = getHeapStart(); - uint8_t *tSearchPtr = &tDummyVariableOnStack; - - // Search for first untouched value below current stackpointer - while (*tSearchPtr != HEAP_STACK_UNTOUCHED_VALUE && tSearchPtr > tHeapStart) { - tSearchPtr--; - } - - return (RAMEND + 1) - (uint16_t)tSearchPtr; +void printStackMaxUsedAndUnusedSizes(Print *aSerial) { + uint16_t tStackUnusedBytes; + aSerial->print(F("Stack used=")); + aSerial->print(RAMEND - SP); + aSerial->print(F(", max used=")); + aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); + aSerial->print(F(", unused=")); + aSerial->print(tStackUnusedBytes); + aSerial->print(F(" of current total ")); + aSerial->println((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); } /* - * Returns the amount of stack/heap not touched since the last call to initStackFreeMeasurement() - * by check for first touched pattern on the stack/heap, starting the search at heap start. + * Search upwards the first two HEAP_STACK_UNTOUCHED_VALUE values after current begin of heap */ -uint16_t getStackUnusedBytes() { - uint8_t tDummyVariableOnStack; - uint8_t *tHeapPtr = getHeapStart(); - -// first search for first match after current begin of heap, because malloc() and free() may be happened in between and overwrite low memory - while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr < &tDummyVariableOnStack) { +uint16_t getHeapMaxUsedSize() { + uint8_t *tHeapPtr = getAvailableHeapStart(); + while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && *(tHeapPtr + 1) != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { tHeapPtr++; } -// then count untouched patterns - uint16_t tStackUnused = 0; - while (*tHeapPtr == HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr < &tDummyVariableOnStack) { - tHeapPtr++; - tStackUnused++; + // tHeapPtr points now to lowest untouched stack position or to lowest current stack byte + return tHeapPtr - (uint8_t*) __malloc_heap_start; +} + +/* + * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() + * Print only if value changed. + * @return true, if values changed + */ +bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial) { + static int16_t tOldStackUsedBytes = 0; + + uint16_t tStackUnusedBytes; + int16_t tStackMaxUsedBytes = getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes); + if (tOldStackUsedBytes != tStackMaxUsedBytes) { + tOldStackUsedBytes = tStackMaxUsedBytes; + aSerial->print(F("Stack used=")); + aSerial->print(RAMEND - SP); + aSerial->print(F(", max used=")); + aSerial->print(tStackMaxUsedBytes); + aSerial->print(F(", unused=")); + aSerial->println(tStackUnusedBytes); + return true; } - return tStackUnused; + return false; } /* - * Get amount of free RAM = current stackpointer - heap end + * Get amount of free Stack = current stackpointer - heap end */ -uint16_t getCurrentFreeHeapOrStack(void) { - uint16_t tHeapStart = (uint16_t) getHeapStart(); - if (tHeapStart >= SP) { +uint16_t getCurrentAvailableStackSize(void) { + uint16_t tAvailableHeapStart = (uint16_t) getAvailableHeapStart(); // __brkval + if (tAvailableHeapStart >= SP) { return 0; } - return (SP - (uint16_t) getHeapStart()); + return (SP - tAvailableHeapStart); +} +void printCurrentAvailableStackSize(Print *aSerial) { + aSerial->print(F("Currently available Stack[bytes]=")); + aSerial->println(getCurrentAvailableStackSize()); } /* * Get amount of maximum available memory for malloc() * FreeRam - __malloc_margin (128 for ATmega328) */ -uint16_t getCurrentAvailableHeap(void) { - if (getCurrentFreeHeapOrStack() <= __malloc_margin) { +uint16_t getCurrentAvailableHeapSize(void) { + if (getCurrentAvailableStackSize() <= (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) { return 0; } - return getCurrentFreeHeapOrStack() - __malloc_margin; // (128) + // SP - __brkval - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) + return getCurrentAvailableStackSize() - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); // (128) } -void printCurrentAvailableHeap(Print *aSerial) { - aSerial->print(F("Currently max available Heap[bytes]=")); - aSerial->println(getCurrentAvailableHeap()); -} - -void printHeapStart(Print *aSerial) { - aSerial->print(F("Heap start=")); - aSerial->println((uint16_t) getHeapStart()); +void printCurrentAvailableHeapSize(Print *aSerial) { + aSerial->print(F("Currently available Heap[bytes]=")); + aSerial->println(getCurrentAvailableHeapSize()); } /* - * Prints the amount of stack used/touched since the last call to initStackFreeMeasurement() - * Example: "Stack used 20 of 7" up to "Stack used 20 of 20" if stack runs into data + * Simple and short implementation, does not work before initStackFreeMeasurement() or first malloc() + * The STACK required for this function is 4 bytes, so available numbers are 4 less than for caller. */ -void printStackUsedBytes(Print *aSerial) { - aSerial->print(F("Stack used ")); - aSerial->print(getStackUsedBytes()); - aSerial->print(F(" of ")); - aSerial->println((RAMEND + 1) - (uint16_t) getHeapStart()); +void printCurrentAvailableHeapSizeSimple(Print *aSerial) { + aSerial->print(F("available=")); + aSerial->println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); } -/* - * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() - * Example: "Stack unused=0, used=16" if stack runs into data - */ -void printStackUnusedAndUsedBytes(Print *aSerial) { - uint16_t tStackUsedBytes; - aSerial->print(F("Stack unused=")); - aSerial->print(getStackUnusedAndUsedBytes(&tStackUsedBytes)); - aSerial->print(F(", used=")); - aSerial->println(tStackUsedBytes); -} +// This define is in AVRUtils.h +// #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) -/* - * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() - * Print only if value changed. - */ -void printStackUnusedAndUsedBytesIfChanged(Print *aSerial) { - static uint16_t sStackUsedBytes = 0; - - uint16_t tOldStackUsedBytes = sStackUsedBytes; - uint16_t tStackUnusedBytes = getStackUnusedAndUsedBytes(&sStackUsedBytes); - if (tOldStackUsedBytes != sStackUsedBytes) { - aSerial->print(F("Stack unused=")); - aSerial->print(tStackUnusedBytes); - aSerial->print(F(", used=")); - aSerial->println(sStackUsedBytes); - } -} +void printBaseRAMData(Print *aSerial) { + aSerial->print(F("__malloc_heap_start=")); + aSerial->print((uint16_t) __malloc_heap_start); // = __bss_end, __heap_start in lst file + aSerial->print(F("|0x")); + aSerial->print((uint16_t) __malloc_heap_start, HEX); + + aSerial->print(F(", &__heap_start=")); + aSerial->print((uint16_t) &__heap_start); + aSerial->print(F("|0x")); + aSerial->print((uint16_t) &__heap_start, HEX); + + aSerial->print(F(", __brkval=")); + aSerial->print((uint16_t) __brkval); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() + aSerial->print(F("|0x")); + aSerial->print((uint16_t) __brkval, HEX); + aSerial->print(F(", __malloc_margin=")); + aSerial->print((uint16_t) __malloc_margin); // =128 + + aSerial->print(F(", SP=")); + aSerial->print((uint16_t) SP); + aSerial->print(F("|0x")); + aSerial->print((uint16_t) SP, HEX); + + /* + * The next 2 entries seems to be always 0 + */ + aSerial->print(F(", __malloc_heap_end=")); + aSerial->print((uint16_t) __malloc_heap_end); + + aSerial->print(F(", __flp=")); + aSerial->print((uint16_t) __flp); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() + aSerial->println(); +} /* - * RAM starts of variables initialized with values != 0, - * followed by variables initialized with 0 + * RAM starts with Data, i.e. variables initialized with values != 0, + * followed by BSS, i.e. uninitalized variables (which are initialized with 0) * and variables not initialized by using attribute "__attribute__((section(".noinit")))". * It ends with the heap and the stack. * - * Sample output if stack runs into data: - * Size of Data + BSS, Heap start, Stack end=2041 - * Stack used 20 of 7 - * Currently available Heap=0 + * The STACK required for this function is 8 bytes, so available numbers are 8 less than for caller. + * + * Sample output: + * Data+BSS=445. Heap: used=770, max used=1096, available=663. Stack: available=791, used=42, max used=319, unused=188 of current total 833 + * Formulas: + * Stack available + used = current total + * Heap available + __malloc_margin (128) = Stack available + * Data+BSS + Heap max used + Stack unused + Stack max used = RAMSIZE */ void printRAMInfo(Print *aSerial) { - uint16_t tHeapStart = (uint16_t) getHeapStart(); - aSerial->print(F("Size of Data + BSS, Heap start, Stack end=")); - aSerial->println(tHeapStart - RAMSTART); - printStackUsedBytes(aSerial); + aSerial->print(F("Data+BSS=")); + aSerial->print((uint16_t) &__heap_start - RAMSTART); + + aSerial->print(F(". Heap: used=")); + aSerial->print((uint16_t) getAvailableHeapStart() - (uint16_t) &__heap_start); + aSerial->print(F(", max used=")); + aSerial->print(getHeapMaxUsedSize()); + aSerial->print(F(", available=")); + uint16_t tStackAvailable = SP - (uint16_t) getAvailableHeapStart() + 1; + aSerial->print(tStackAvailable - (uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); - aSerial->print(F("Currently available Heap=")); - aSerial->println(getCurrentAvailableHeap()); + aSerial->print(F(". Stack: available=")); + aSerial->print(tStackAvailable); + aSerial->print(F(", used=")); + aSerial->print(RAMEND - SP); + uint16_t tStackUnusedBytes; + aSerial->print(F(", max used=")); + aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); + aSerial->print(F(", unused=")); + aSerial->print(tStackUnusedBytes); + aSerial->print(F(" of current total ")); + aSerial->print((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); // getAvailableHeapStart() + + aSerial->println(); +} + +void set__malloc_margin(uint8_t aNewMallocMargin) { + __malloc_margin = aNewMallocMargin; } -void printCurrentFreeHeap(Print *aSerial) { - aSerial->print(F("Current free Heap / Stack[bytes]=")); - aSerial->println(getCurrentFreeHeapOrStack()); +void reset__malloc_margin() { + __malloc_margin = 128; } bool isAddressInRAM(void *aAddressToCheck) { return (aAddressToCheck <= (void*) RAMEND); } -bool isAddressBelowHeap(void *aAddressToCheck) { - return (aAddressToCheck < getHeapStart()); +bool isAddressBelowAvailableHeapStart(void *aAddressToCheck) { + return (aAddressToCheck < getAvailableHeapStart()); } +/* + * Test available heap by callocing 128 bytes chunks, + * If no memory available, try with 64, 32 etc up to 2, 1 byte chunks + */ +void testCallocSizesAndPrint(Print *aSerial) { + uint8_t *tLastMallocPtr; + uint16_t tMallocSize = 128; + while (true) { + aSerial->print(F("SP=0x")); + aSerial->print(SP, HEX); + aSerial->print(F(" available=")); + aSerial->print(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); + uint8_t *tMallocPtr = (uint8_t*) calloc(tMallocSize, 1); + + aSerial->print(F(" -> calloc(")); + aSerial->print(tMallocSize); + aSerial->print(F(",1)")); + + if (tMallocPtr == NULL) { + aSerial->print(F("failed ->")); + tMallocSize = tMallocSize >> 1; + if (tMallocSize < 1) { + aSerial->println(); + break; + } + } else { + tLastMallocPtr = tMallocPtr; + aSerial->print(F("=0x")); + aSerial->print((uint16_t) tLastMallocPtr, HEX); + aSerial->print(F(" ->")); + + *tLastMallocPtr = HEAP_STACK_UNTOUCHED_VALUE; // For testing detection using 2 consecutive HEAP_STACK_UNTOUCHED_VALUE + *(tLastMallocPtr + tMallocSize - 1) = 0x11; + } + printCurrentAvailableHeapSizeSimple(aSerial); + } +} /******************************************** * SLEEP AND WATCHDOG STUFF ********************************************/ diff --git a/OpenledRace/AVRUtils.h b/OpenledRace/AVRUtils.h index f6f51f1..32a9bf5 100644 --- a/OpenledRace/AVRUtils.h +++ b/OpenledRace/AVRUtils.h @@ -1,7 +1,7 @@ /* * AVRUtils.h * - * Copyright (C) 2016-2020 Armin Joachimsmeyer + * Copyright (C) 2016-2024 Armin Joachimsmeyer * Email: armin.joachimsmeyer@gmail.com * * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. @@ -27,6 +27,19 @@ #include #include #include +#include "avr/boot.h" + +/* + * The largest address just not allocated so far + * Under Unix, the "break value" was the end of the data + * segment as dynamically requested from the operating system. + * Since we don't have an operating system, just make sure + * that we don't collide with the stack. + */ +extern void *__brkval; // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() +extern void *__flp; // +extern char __heap_start; // = __bss_end, the linker address of heap start +#define HEURISTIC_ADDITIONAL_MALLOC_MARGIN 14 // No malloc() possible if size is lower than (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) /* * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. @@ -54,26 +67,32 @@ extern volatile uint16_t sNumberOfSleeps; #include -uint8_t* getHeapStart(); -uint16_t getCurrentFreeHeapOrStack(void); -uint16_t getCurrentAvailableHeap(void); -void printHeapStart(Print *aSerial); -void printCurrentFreeHeap(Print *aSerial); -void printCurrentAvailableHeap(Print *aSerial); +uint8_t* getAvailableHeapStart(); +void printAvailableHeapStart(Print *aSerial); +uint16_t getCurrentAvailableStackSize(void); +void printCurrentAvailableStackSize(Print *aSerial); +uint16_t getCurrentAvailableHeapSize(void); +void printCurrentAvailableHeapSize(Print *aSerial); +void printCurrentAvailableHeapSizeSimple(Print *aSerial); +#define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) #define HEAP_STACK_UNTOUCHED_VALUE 0x5A void initStackFreeMeasurement(); -uint16_t getStackUnusedBytes(); -uint16_t getStackUsedBytes(); -uint16_t getStackUnusedAndUsedBytes(uint16_t *aStackUsedBytesPointer); -void printStackUsedBytes(Print *aSerial); -void printStackUnusedAndUsedBytes(Print *aSerial); -void printStackUnusedAndUsedBytesIfChanged(Print *aSerial); +int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer); +void printStackMaxUsedAndUnusedSizes(Print *aSerial); +bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial); + +void printBaseRAMData(Print *aSerial); void printRAMInfo(Print *aSerial); bool isAddressInRAM(void *aAddressToCheck); -bool isAddressBelowHeap(void *aAddressToCheck); +bool isAddressBelowAvailableHeapStart(void *aAddressToCheck); + +void set__malloc_margin(uint8_t aNewMallocMargin); +void reset__malloc_margin(); + +void testCallocSizesAndPrint(Print *aSerial); #endif // defined(__AVR__) #endif // _AVR_UTILS_H diff --git a/OpenledRace/LCDBigNumbers.hpp b/OpenledRace/LCDBigNumbers.hpp index b01c8e1..ed2389b 100644 --- a/OpenledRace/LCDBigNumbers.hpp +++ b/OpenledRace/LCDBigNumbers.hpp @@ -3,7 +3,7 @@ * * Arduino library to write big numbers on a 1602 or 2004 LCD. * - * Copyright (C) 2022-2023 Armin Joachimsmeyer + * Copyright (C) 2022-2024 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com * * This file is part of LCDBigNumbers https://github.com/ArminJo/LCDBigNumbers. @@ -34,6 +34,19 @@ #define ONE_COLUMN_HYPHEN_CHARACTER '_' // This input character is printed as a one column hyphen. Normal hyphen / minus are printed as a hyphen with the width of the number - 1. #define ONE_COLUMN_HYPHEN_STRING "_" // This input string is printed as a one column hyphen. Normal hyphen / minus are printed as a hyphen with the width of the number - 1. +#define VERSION_LCD_BIG_NUMBERS "1.2.3" +#define VERSION_LCD_BIG_NUMBERS_MAJOR 1 +#define VERSION_LCD_BIG_NUMBERS_MINOR 2 +#define VERSION_LCD_BIG_NUMBERS_PATCH 3 +// The change log is at the README.md + +/* + * Macro to convert 3 version parts into an integer + * To be used in preprocessor comparisons, such as #if VERSION_LCD_BIG_NUMBERS_HEX >= VERSION_HEX_VALUE(3, 0, 0) + */ +#define VERSION_HEX_VALUE(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) +#define VERSION_LCD_BIG_NUMBERS_HEX VERSION_HEX_VALUE(VERSION_LCD_BIG_NUMBERS_MAJOR, VERSION_LCD_BIG_NUMBERS_MINOR, VERSION_LCD_BIG_NUMBERS_PATCH) + //#define USE_PARALLEL_2004_LCD // Is default //#define USE_PARALLEL_1602_LCD //#define USE_SERIAL_2004_LCD @@ -274,12 +287,13 @@ const uint8_t bigNumbers3x4_2[4][33] PROGMEM = { // 4-li class LCDBigNumbers: public Print { public: + virtual ~LCDBigNumbers(){} #if defined(USE_PARALLEL_LCD) LiquidCrystal *LCD; #else LiquidCrystal_I2C *LCD; #endif - uint8_t NumberWidth; + uint8_t NumberWidth; // Width of the rendered number not including the optional blank gap uint8_t NumberHeight; uint8_t FontVariant; const uint8_t (*bigNumbersCustomPatterns)[8]; @@ -287,7 +301,7 @@ class LCDBigNumbers: public Print { const uint8_t *bigNumbersFont; bool forceGapBetweenNumbers; // The default depends on the font used uint8_t upperLeftColumnIndex; // Start of the next character - uint8_t maximumColumnIndex; // Maximum of columns to be written. Used to not clear the gap after a number which ends at the last column. ( 44 bytes program space) + uint8_t maximumColumns; // Auto detected maximum of columns which can be written (e.g. 16 or 20). To avoid clearing the gap after a number which ends at the last column. (44 bytes program space) uint8_t upperLeftRowIndex; // Start of the next character /* @@ -304,9 +318,10 @@ class LCDBigNumbers: public Print { /* * Creates custom character used for generating big numbers + * This also sets cursor to 0.0 by call to _createChar() */ void begin() { - maximumColumnIndex = 0; + maximumColumns = 0; // create maximum 8 custom characters for (uint_fast8_t i = 0; i < NumberOfCustomPatterns; i++) { _createChar(i, bigNumbersCustomPatterns[i]); @@ -399,20 +414,25 @@ class LCDBigNumbers: public Print { init(aBigNumberFontIdentifier); } - //createChar with PROGMEM input + /* + * Like LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) + * but with PROGMEM input + * This also sets cursor to 0.0 + */ void _createChar(uint8_t location, const uint8_t *charmap) { location &= 0x7; // we only have 8 locations 0-7 LCD->command(LCD_SETCGRAMADDR | (location << 3)); for (int i = 0; i < 8; i++) { LCD->write(pgm_read_byte(charmap++)); } + LCD->command(LCD_SETDDRAMADDR); // set cursor to 0.0, this avoids overwriting CGRAM by next write() command. } /** * Draws a big digit of size aNumberWidth x aNumberHeight at cursor position * Special characters always have the width of 1! * After each number one column gap is inserted. The gap is cleared, if not at the (last + 1) column! - * @param aNumber - byte 0x00 to 0x09 or ASCII number or one of ' ', '|', '-', '_', '.' and ':' special characters to display + * @param aNumber - byte 0x00 to 0x09 or ASCII number or one of these special characters: ' ', '|', '-', '_', '.', ':' * @return The number of columns written (1 to 4 currently) */ size_t writeBigNumber(uint8_t aNumberOrSpecialCharacter) { @@ -434,12 +454,15 @@ class LCDBigNumbers: public Print { // print a one column space aNumberOrSpecialCharacter = ' '; } else { + /* + * Here we have numbers only + */ if (aNumberOrSpecialCharacter > 9) { - // if not byte 0x00 to 0x09, convert number character to ASCII - aNumberOrSpecialCharacter -= '0'; // convert ASCII value to number + // if not byte 0x00 to 0x09, convert ASCII character to number + aNumberOrSpecialCharacter -= '0'; } if (aNumberOrSpecialCharacter > 9) { - // If we have a non number character now, we convert it to a space with the width of the number + // If we do not have a number 0 to 9 now, we convert it to a space with the width of a number aNumberOrSpecialCharacter = ' '; } tCharacterWidth = NumberWidth; @@ -456,8 +479,10 @@ class LCDBigNumbers: public Print { Serial.println(upperLeftColumnIndex); #endif const uint8_t *tArrayPtr = bigNumbersFont + tFontArrayOffset; + // Render character row by row for (uint_fast8_t tRow = 0; tRow < NumberHeight; tRow++) { LCD->setCursor(upperLeftColumnIndex, upperLeftRowIndex + tRow); + // Render all columns in a row for (uint_fast8_t i = 0; i < tCharacterWidth; i++) { uint8_t tCharacterIndex; if (aNumberOrSpecialCharacter == ' ') { @@ -466,7 +491,7 @@ class LCDBigNumbers: public Print { tCharacterIndex = pgm_read_byte(tArrayPtr); } LCD->write(tCharacterIndex); - tArrayPtr++; // next number column + tArrayPtr++; // Prepare for next column, we do not use i #if defined(LOCAL_DEBUG) Serial.print(F(" 0x")); Serial.print(tCharacterIndex, HEX); @@ -479,26 +504,31 @@ class LCDBigNumbers: public Print { Serial.print('|'); #endif } - upperLeftColumnIndex += tCharacterWidth; + upperLeftColumnIndex += tCharacterWidth; // now we are at the index for the next number or the optional blank gap - if (maximumColumnIndex < upperLeftColumnIndex) { + /* + * Auto detect maximum column count, which can be written. + * We assume that all characters, which were written, fit on the display. + * Thus the maximum of the upperLeftColumnIndex after write is the index of the first column after the display and maximum possible column count. + */ + if (maximumColumns < upperLeftColumnIndex) { // find maximum column at runtime - maximumColumnIndex = upperLeftColumnIndex; + maximumColumns = upperLeftColumnIndex; } /* - * Implement the gap after the character + * Implement the gap after the number character */ if (forceGapBetweenNumbers && (NumberWidth == 1 || tCharacterWidth > 1 || aNumberOrSpecialCharacter == '-')) { - if (maximumColumnIndex != upperLeftColumnIndex) { + if (maximumColumns >= upperLeftColumnIndex) { // We are not at the last column, so clear the gap after the number for (uint_fast8_t tRow = 0; tRow < NumberHeight; tRow++) { - LCD->setCursor(upperLeftColumnIndex + 1, upperLeftRowIndex + tRow); + LCD->setCursor(upperLeftColumnIndex, upperLeftRowIndex + tRow); LCD->write(' '); // Blank } tCharacterWidth++; } - upperLeftColumnIndex++; // This provides one column gap between big numbers, but not between special characters. + upperLeftColumnIndex++; // This provides one column gap between numbers, but not between special characters. } #if defined(LOCAL_DEBUG) diff --git a/OpenledRace/LiquidCrystal_I2C.hpp b/OpenledRace/LiquidCrystal_I2C.hpp index 2659ef4..7ac4cea 100644 --- a/OpenledRace/LiquidCrystal_I2C.hpp +++ b/OpenledRace/LiquidCrystal_I2C.hpp @@ -1,4 +1,15 @@ +// LiquidCrystal_I2C.hpp // Based on the work by DFRobot +/* + * Extensions made by AJ 2023 + * Removed Arduino 0.x support + * Added SoftI2CMaste support, which drastically reduces program size. + * Added OLED stuff + * Added createChar() with PROGMEM input + * Added fast timing + */ +#ifndef _LIQUID_CRYSTAL_I2C_HPP +#define _LIQUID_CRYSTAL_I2C_HPP #include "Arduino.h" @@ -23,6 +34,18 @@ inline size_t LiquidCrystal_I2C::write(uint8_t value) { #include "SoftWire.h" #endif +#if defined(__AVR__) +/* + * The datasheet says: a command need > 37us to settle. Enable pulse must be > 450ns. + * Use no delay for enable pulse after each command, + * because the overhead of this library seems to be using the 37 us and 450 ns. + * At least it works perfectly for all my LCD's connected to Uno, Nano etc. + * and it saves a lot of time in realtime applications using LCD as display, + * like https://github.com/ArminJo/Arduino-DTSU666H_PowerMeter + */ +#define USE_FAST_TIMING +#endif + // When the display powers up, it is configured as follows: // // 1. Display clear @@ -131,7 +154,11 @@ void LiquidCrystal_I2C::begin(uint8_t cols __attribute__((unused)), uint8_t line /********** high level commands, for the user! */ void LiquidCrystal_I2C::clear() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero +#if defined(USE_FAST_TIMING) delayMicroseconds(1500); // this command takes a long time! // AJ 20.9.23 1200 is too short for my 2004 LCD's, 1400 is OK +#else + delayMicroseconds(2000); // this command takes a long time! +#endif if (_oled) setCursor(0, 0); } @@ -274,10 +301,13 @@ void LiquidCrystal_I2C::expanderWrite(uint8_t _data) { void LiquidCrystal_I2C::pulseEnable(uint8_t _data) { expanderWrite(_data | En); // En high -// delayMicroseconds(1); // enable pulse must be >450ns // AJ 20.9.23 not required for my LCD's - +#if !defined(USE_FAST_TIMING) + delayMicroseconds(1); // enable pulse must be > 450ns // AJ 20.9.23 not required for my LCD's +#endif expanderWrite(_data & ~En); // En low -// delayMicroseconds(50); // commands need > 37us to settle // AJ 20.9.23 not required for my LCD's +#if !defined(USE_FAST_TIMING) + delayMicroseconds(50); // commands need > 37us to settle // AJ 20.9.23 not required for my LCD's +#endif } // Alias functions @@ -342,3 +372,4 @@ void LiquidCrystal_I2C::setContrast(uint8_t new_val) { } #pragma GCC diagnostic pop +#endif // _LIQUID_CRYSTAL_I2C_HPP diff --git a/OpenledRace/OpenLedRace.ino b/OpenledRace/OpenLedRace.ino index c390860..f91beb3 100644 --- a/OpenledRace/OpenLedRace.ino +++ b/OpenledRace/OpenLedRace.ino @@ -1055,7 +1055,8 @@ void setup() { #endif Serial.begin(115200); -#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217) +#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/ \ + || defined(SERIALUSB_PID) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_attiny3217) delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor! #endif @@ -1212,7 +1213,7 @@ void loop() { #if defined(INFO) && defined(__AVR__) if (!sOnlyPlotterOutput) { - printStackUnusedAndUsedBytesIfChanged(&Serial); + printStackMaxUsedAndUnusedSizesIfChanged(&Serial); } #endif diff --git a/OpenledRace/SoftI2CMaster.h b/OpenledRace/SoftI2CMaster.h index 5f638cf..bf30f9a 100644 --- a/OpenledRace/SoftI2CMaster.h +++ b/OpenledRace/SoftI2CMaster.h @@ -401,7 +401,11 @@ bool i2c_init(void) TWSR = (1<