Display memory usage reduction (#8)

Convert all string references from const char* to String/F() to store values in flash instead of ram.

Memory usage from `main`:

```
Sketch uses 27878 bytes (90%) of program storage space. Maximum is 30720 bytes.
Global variables use 1755 bytes (85%) of dynamic memory, leaving 293 bytes for local variables. Maximum is 2048 bytes.
```

Memory usage after these changes:
```
Sketch uses 28054 bytes (91%) of program storage space. Maximum is 30720 bytes.
Global variables use 1445 bytes (70%) of dynamic memory, leaving 603 bytes for local variables. Maximum is 2048 bytes.
```

This provides a dynamic memory savings of 310 bytes!

Reviewed-on: https://git.pinkduck.xyz/adam/libGravity/pulls/8
Co-authored-by: Adam Wonak <adam.wonak@gmail.com>
Co-committed-by: Adam Wonak <adam.wonak@gmail.com>
This commit is contained in:
2025-06-22 18:44:01 +00:00
committed by adam
parent 973c13b8ef
commit 6fa5674909

View File

@ -58,11 +58,11 @@ const PROGMEM uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") =
#define play_icon_width 14
#define play_icon_height 14
static const unsigned char play_icon[] = {
static const unsigned char play_icon[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00, 0xFC, 0x03,
0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00, 0x7C, 0x00, 0x3C, 0x00,
0x00, 0x00, 0x00, 0x00};
static const unsigned char pause_icon[] = {
static const unsigned char pause_icon[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
0x38, 0x0E, 0x00, 0x00};
@ -93,23 +93,23 @@ void drawRightAlignedText(const char* text, int y) {
gravity.display.drawStr(drawX, y, text);
}
void drawSelectHero() {
void drawMainSelection() {
gravity.display.setDrawColor(1);
const int tickSize = 3;
const int heroWidth = SCREEN_WIDTH / 2;
const int heroHeight = 49;
const int mainWidth = SCREEN_WIDTH / 2;
const int mainHeight = 49;
gravity.display.drawLine(0, 0, tickSize, 0);
gravity.display.drawLine(0, 0, 0, tickSize);
gravity.display.drawLine(heroWidth, 0, heroWidth - tickSize, 0);
gravity.display.drawLine(heroWidth, 0, heroWidth, tickSize);
gravity.display.drawLine(heroWidth, heroHeight, heroWidth, heroHeight - tickSize);
gravity.display.drawLine(heroWidth, heroHeight, heroWidth - tickSize, heroHeight);
gravity.display.drawLine(0, heroHeight, tickSize, heroHeight);
gravity.display.drawLine(0, heroHeight, 0, heroHeight - tickSize);
gravity.display.drawLine(mainWidth, 0, mainWidth - tickSize, 0);
gravity.display.drawLine(mainWidth, 0, mainWidth, tickSize);
gravity.display.drawLine(mainWidth, mainHeight, mainWidth, mainHeight - tickSize);
gravity.display.drawLine(mainWidth, mainHeight, mainWidth - tickSize, mainHeight);
gravity.display.drawLine(0, mainHeight, tickSize, mainHeight);
gravity.display.drawLine(0, mainHeight, 0, mainHeight - tickSize);
gravity.display.setDrawColor(2);
}
void drawMenuItems(const char* menu_items[], int menu_size) {
void drawMenuItems(String menu_items[], int menu_size) {
// Draw menu items
gravity.display.setFont(TEXT_FONT);
@ -128,7 +128,7 @@ void drawMenuItems(const char* menu_items[], int menu_size) {
if (app.editing_param) {
gravity.display.drawBox(boxX, boxY, boxWidth, boxHeight);
drawSelectHero();
drawMainSelection();
} else {
gravity.display.drawFrame(boxX, boxY, boxWidth, boxHeight);
}
@ -143,7 +143,7 @@ void drawMenuItems(const char* menu_items[], int menu_size) {
for (int i = 0; i < min(menu_size, VISIBLE_MENU_ITEMS); ++i) {
int idx = start_index + i;
drawRightAlignedText(menu_items[idx], MENU_ITEM_HEIGHT * (i + 1) - 1);
drawRightAlignedText(menu_items[idx].c_str(), MENU_ITEM_HEIGHT * (i + 1) - 1);
}
}
@ -173,54 +173,52 @@ void DisplayMainPage() {
gravity.display.setFont(TEXT_FONT);
// Display selected editable value
char mainText[8];
const char* subText;
String mainText;
String subText;
switch (app.selected_param) {
case PARAM_MAIN_TEMPO:
// Serial MIDI is too unstable to display bpm in real time.
if (app.selected_source == Clock::SOURCE_EXTERNAL_MIDI) {
sprintf(mainText, "%s", "EXT");
mainText = F("EXT");
} else {
sprintf(mainText, "%d", gravity.clock.Tempo());
mainText = String(gravity.clock.Tempo());
}
subText = "BPM";
subText = F("BPM");
break;
case PARAM_MAIN_SOURCE:
mainText = F("EXT");
switch (app.selected_source) {
case Clock::SOURCE_INTERNAL:
sprintf(mainText, "%s", "INT");
subText = "CLOCK";
mainText = F("INT");
subText = F("CLOCK");
break;
case Clock::SOURCE_EXTERNAL_PPQN_24:
sprintf(mainText, "%s", "EXT");
subText = "24 PPQN";
subText = F("24 PPQN");
break;
case Clock::SOURCE_EXTERNAL_PPQN_4:
sprintf(mainText, "%s", "EXT");
subText = "4 PPQN";
subText = F("4 PPQN");
break;
case Clock::SOURCE_EXTERNAL_MIDI:
sprintf(mainText, "%s", "EXT");
subText = "MIDI";
subText = F("MIDI");
break;
}
break;
case PARAM_MAIN_ENCODER_DIR:
sprintf(mainText, "%s", "DIR");
subText = app.selected_sub_param == 0 ? "DEFAULT" : "REVERSED";
mainText = F("DIR");
subText = app.selected_sub_param == 0 ? F("DEFAULT") : F("REVERSED");
break;
case PARAM_MAIN_RESET_STATE:
sprintf(mainText, "%s", "RST");
subText = app.selected_sub_param == 0 ? "RESET ALL" : "BACK";
mainText = F("RST");
subText = app.selected_sub_param == 0 ? F("RESET ALL") : F("BACK");
break;
}
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);
drawCenteredText(subText.c_str(), SUB_TEXT_Y, TEXT_FONT);
// Draw Main Page menu items
const char* menu_items[PARAM_MAIN_LAST] = {"TEMPO", "SOURCE", "ENCODER DIR", "RESET"};
String menu_items[PARAM_MAIN_LAST] = {F("TEMPO"), F("SOURCE"), F("ENCODER DIR"), F("RESET")};
drawMenuItems(menu_items, PARAM_MAIN_LAST);
}
@ -231,8 +229,8 @@ void DisplayChannelPage() {
gravity.display.setDrawColor(2);
// Display selected editable value
char mainText[5];
const char* subText;
String mainText;
String subText;
// When editing a param, just show the base value. When not editing show
// the value with cv mod.
@ -242,87 +240,82 @@ void DisplayChannelPage() {
case PARAM_CH_MOD: {
int mod_value = ch.getClockMod(withCvMod);
if (mod_value > 1) {
sprintf(mainText, "/%d", mod_value);
subText = "DIVIDE";
mainText = F("/");
mainText += String(mod_value);
subText = F("DIVIDE");
} else {
sprintf(mainText, "x%d", abs(mod_value));
subText = "MULTIPLY";
mainText = F("x");
mainText += String(abs(mod_value));
subText = F("MULTIPLY");
}
break;
}
case PARAM_CH_PROB:
sprintf(mainText, "%d%%", ch.getProbability(withCvMod));
subText = "HIT CHANCE";
mainText = String(ch.getProbability(withCvMod)) + F("%");
subText = F("HIT CHANCE");
break;
case PARAM_CH_DUTY:
sprintf(mainText, "%d%%", ch.getDutyCycle(withCvMod));
subText = "PULSE WIDTH";
mainText = String(ch.getDutyCycle(withCvMod)) + F("%");
subText = F("PULSE WIDTH");
break;
case PARAM_CH_OFFSET:
sprintf(mainText, "%d%%", ch.getOffset(withCvMod));
subText = "SHIFT HIT";
mainText = String(ch.getOffset(withCvMod)) + F("%");
subText = F("SHIFT HIT");
break;
case PARAM_CH_SWING:
ch.getSwing() == 50
? sprintf(mainText, "OFF")
: sprintf(mainText, "%d%%", ch.getSwing(withCvMod));
? mainText = F("OFF")
: mainText = String(ch.getSwing(withCvMod)) + F("%");
subText = "DOWN BEAT";
swingDivisionMark();
break;
case PARAM_CH_CV_SRC: {
switch (ch.getCvSource()) {
mainText = F("SRC");
case CV_NONE:
sprintf(mainText, "SRC");
subText = "NONE";
subText = F("NONE");
break;
case CV_1:
sprintf(mainText, "SRC");
subText = "CV 1";
subText = F("CV 1");
break;
case CV_2:
sprintf(mainText, "SRC");
subText = "CV 2";
subText = F("CV 2");
break;
}
break;
}
case PARAM_CH_CV_DEST: {
switch (ch.getCvDestination()) {
mainText = F("DEST");
case CV_DEST_NONE:
sprintf(mainText, "DEST");
subText = "NONE";
subText = F("NONE");
break;
case CV_DEST_MOD:
sprintf(mainText, "DEST");
subText = "CLOCK MOD";
subText = F("CLOCK MOD");
break;
case CV_DEST_PROB:
sprintf(mainText, "DEST");
subText = "PROBABILITY";
subText = F("PROBABILITY");
break;
case CV_DEST_DUTY:
sprintf(mainText, "DEST");
subText = "DUTY CYCLE";
subText = F("DUTY CYCLE");
break;
case CV_DEST_OFFSET:
sprintf(mainText, "DEST");
subText = "OFFSET";
subText = F("OFFSET");
break;
case CV_DEST_SWING:
sprintf(mainText, "DEST");
subText = "SWING";
subText = F("SWING");
break;
}
break;
}
}
drawCenteredText(mainText, MAIN_TEXT_Y, LARGE_FONT);
drawCenteredText(subText, SUB_TEXT_Y, TEXT_FONT);
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);
drawCenteredText(subText.c_str(), SUB_TEXT_Y, TEXT_FONT);
// Draw Channel Page menu items
const char* menu_items[PARAM_CH_LAST] = {
"MOD", "PROBABILITY", "DUTY", "OFFSET", "SWING", "CV SOURCE", "CV DEST"};
String menu_items[PARAM_CH_LAST] = {
F("MOD"), F("PROBABILITY"), F("DUTY"), F("OFFSET"), F("SWING"), F("CV SOURCE"), F("CV DEST")};
drawMenuItems(menu_items, PARAM_CH_LAST);
}
@ -349,7 +342,7 @@ void DisplaySelectedChannel() {
if (i == 0) {
gravity.display.setBitmapMode(1);
auto icon = gravity.clock.IsPaused() ? pause_icon : play_icon;
gravity.display.drawXBM(2, boxY, play_icon_width, play_icon_height, icon);
gravity.display.drawXBMP(2, boxY, play_icon_width, play_icon_height, icon);
} else {
gravity.display.setFont(TEXT_FONT);
gravity.display.setCursor((i * boxWidth) + textOffset, SCREEN_HEIGHT - 3);