OK, can anyone tell me what the hell is going on here? It's really a pretty funky, roundabout code structure, but it's the most dynamic way I could find to create a dynamic menu construct from a bit mask and description array.
char prefs_mask[][15] PROGMEM = { "1Cmd Auto .5ms", "1Cmd Auto 1s", "1Cmd Auto 5s", "1Cmd Sticky" };
char FLAGS_mask[][15] PROGMEM = { "EDVF: All dead", "EDV1: 0%", "Overload", "Valid Dischrge", "N/A", "Valid Charge", "CurrTaper Term", "Temp Alarm", "Overcurrent", "Low Temp Fault", "Overvoltage", "N/A", "Charge Control", "0/NiMh 1/LiOn", "PSTAT", "% Rel/Abs Sel" };
char Status_mask[][15] PROGMEM = { "Error Bit 1", "Error Bit 2", "Error Bit 3", "Error Bit 4", "Fully Dischrgd", "Fully Charged", "Discharging", "Initialized", "RemainTimeAlrm", "RemainCapAlarm", "N/A", "TermDischgAlrm", "OverTempAlarm", "N/A", "TermChargeAlrm", "OverChargeAlrm" };
uint16_t prefs = 0xE;
unsigned int DisplayBitList(unsigned int bitField, prog_char* labelsList, byte labelCount) {
phi_prompt_struct dynaList = topMenu;
unsigned int x;
dynaList.high.i=labelCount-1; // Last item of the list is size of the list - 1.
dynaList.width = lcd_columns-1; // room for scroll bar and nothing more
dynaList.option |= phi_prompt_list_in_ram;
dynaList.option &= ~phi_prompt_arrow_dot;
char* dynaListBuf = (char*)malloc((dynaList.high.i + 1) * 16);
char** dynaListPtrs = (char**)malloc(dynaList.high.i + 1);
for (x=0; x<=dynaList.high.i; x++) {
if (bitField & (1 << x)) dynaListBuf[16 * x] = 126;
else dynaListBuf[16 * x] = 165;
strcpy_P(dynaListBuf+(16 * x)+1,(prog_char*)(labelsList + (x*15)));
*(dynaListPtrs + x) = &dynaListBuf[16 * x];
}
dynaList.ptr.list = dynaListPtrs;
dynaList.low.i = 0;
while (wait_on_escape(25)) ; // wait for buttons to be up, may have residual press from menu
// debug print & wait, shows free mem and addresses assigned to data by malloc()
lcd.clear();
i2c_msg_lcd(PSTR("Free: "));
lcd.print(freeMemory(), DEC); // is always the same value once i put free() in the right place and the right order
lcd.setCursor(0,1);
i2c_msg_lcd(PSTR("d.p.l: "));
lcd.print((uint16_t)dynaList.ptr.list, HEX); // unpredictably jumps around through addresses, sometimes crapping out on the 2nd re-use (assigning something like 0x1F39 address, subsequently recycling past the end of RAM and rebooting Arduino
lcd.setCursor(0,2);
sprintf(i2cBuffer, "dPtrs: %.3X%.3X%.3X", (uint16_t)dynaListPtrs[0], (uint16_t)dynaListPtrs[1], (uint16_t)dynaListPtrs[2]);
lcd.print(i2cBuffer);
lcd.setCursor(0,3);
i2c_msg_lcd(PSTR(" << Back | >> Go "));
while ((x = wait_on_escape(1000)) == 0) ;
if (x == 3) return bitField; // if we see it's going to crash on access, we can use back button to abort
lcd.clear();
while (wait_on_escape(25)) ; // wait for buttons to be up
while (1) {
switch (select_list(&dynaList)) {
case -3:
case -1:
free(dynaListPtrs); // free() in FILO order, seems to work better than FIFO
free(dynaListBuf);
return bitField;
break;
case 1: // select button: toggle selected bit in bitfield
bitField ^= (1 << dynaList.low.i); // toggle!
if (bitField & (1 << dynaList.low.i)) *(dynaListBuf + (16 * dynaList.low.i)) = 126;
else *(dynaListBuf + (16 * dynaList.low.i)) = 165;
break;
}
}
}
void OptionsMenu() {
prefs = DisplayBitList(prefs,(char*)&prefs_mask, sizeof(prefs_mask) / sizeof(*prefs_mask)); // generate, present, retrieve, and set the global value in one line - yay!
}
......
// part of a much larger function to parse and display various types of data based on the configured command "data type" - this is the bitfield type that I wrote the whole routine for.
case BATT_BITFIELD:
switch (singleCmdList.low.i) {
case Cmd_Flags: DisplayBitList(wordBuffer,(char*)&FLAGS_mask, sizeof(FLAGS_mask) / sizeof(*FLAGS_mask)); break;
case Cmd_BatteryStatus: DisplayBitList(wordBuffer,(char*)&Status_mask, sizeof(Status_mask) / sizeof(*Status_mask)); break;
default: lcdQuickPrintWord((unsigned int)wordBuffer,BIN); break;
}
break;
......
There's also a big curiosity in using malloc(): the first pointer (in the dynaListPtrs var) is almost always corrupt on the second call (after one malloc(), malloc(), free(), free() cycle), but only when it (seemingly randomly) decides to assign a different address to the allocation than it did the first time. Only the first iteration of pointer assignment, though. That is, the first menu entry on the LCD is garbage data, but all subsequent lines are valid (unless malloc() placed the start address too close to the end, which it often does after 3-4 runs). If it uses the same address block, though, it assigns and writes everything properly, and it always works right on a "fresh boot" with new malloc() calls.
So, just, um... WTF am I doing wrong here? The malloc() header is supposed to be at *(ptr-1), which of course I don't touch here. But it seems like it's really at *(ptr) itself, so when I write the first entry it clobbers the header info. Is that what seems to be going on here...?