У меня проблема с навигацией по довольно сложным данным во флэш-памяти. Поскольку не многие функции могут читать непосредственно из флэш-памяти, у меня есть несколько вспомогательных функций для копирования структур и строк из флэш-памяти в ОЗУ. Либо они работают не так, как я ожидаю, либо способ, которым я указываю на вещи во флэш-памяти, из других вещей во флэш-памяти не работает.
У меня есть заголовочный файл, который полностью определяет иерархическую структуру меню во флэш-памяти. Он имеет ряд элементов с указателями на другие элементы.
/**************************
* UI management functions-
* header file
**************************/
#ifndef _UI.H
#define _UI.H
/***********************************
* This first part contains things for
* defining the structure of the menu.
* These should be pretty fixed from
* app to app.
************************************/
typedef enum { UI_STATE_HOME, UI_STATE_MENU, UI_STATE_DIALOG } te_UIState;
typedef enum { UI_ENTRY_SUBMENU, UI_ENTRY_NUMERIC_INT, UI_ENTRY_NUMERIC_FLOAT, UI_ENTRY_BOOL, UI_ENTRY_DISCRETE, UI_ENTRY_TIME } te_UIEntryType;
/* Typedefs for parameters */
typedef struct
{
short hour;
short minute;
} ts_SimpleTime;
/* Typedefs for the different types of entry
*
*/
typedef struct {
char* entryName; // pointer to one of our string entries, eg MNU_Light
te_UIEntryType entryType;
} tsEntry;
typedef struct {
char* entryName;
te_UIEntryType entryType;// = UI_ENTRY_NUMERIC_INT;
int(*finalIntHandler)(int selectedValue); //The function that should be called when a number has been selected
int(*initialIntValue) (); //The function that should be called to obtain the starting value for the selector
} ts_EntryInt;
typedef struct {
char* entryName;
te_UIEntryType entryType;// = UI_ENTRY_NUMERIC_FLOAT;
int(*finalIntHandler)(float selectedValue);
float(*initialSingleValue) ();
} ts_EntrySingle;
typedef struct {
char* entryName;
te_UIEntryType entryType;// = UI_ENTRY_BOOL;
int(*finalIntHandler)(bool selectedValue);
bool(*initialBoolValue) ();
} ts_EntryBool;
typedef struct {
char* entryName;
te_UIEntryType entryType;// = UI_ENTRY_DISCRETE;
int(*handler)();
char* (*optionalEntryNamePtrFunction)(); //If this points to a function, it'll be called to determine what text to display as the entry name.
// This is for things like enable/disable where the text changes depending on the present state.
} ts_EntryDiscrete;
typedef struct {
char* entryName;
te_UIEntryType entryType;
int(*finalIntHandler)(ts_SimpleTime selectedValue);
ts_SimpleTime(*initialSingleValue) ();
} ts_EntryTime;
typedef union tuEntry tuEntry; //Forward declaration of tuEntry so we can use it in the submenu entry.
typedef struct {
char* entryName;
te_UIEntryType entryType;// = UI_ENTRY_SUBMENU;
int numItems;
tuEntry* entries;
tuEntry* parent;
} ts_EntrySubmenu;
union tuEntry
{
char* entryName;
tsEntry entry;
ts_EntryInt entryInt;
ts_EntrySingle entrySingle;
ts_EntryBool entryBool;
ts_EntryDiscrete entryDiscrete;
ts_EntryTime entryTime;
ts_EntrySubmenu entrySubmenu;
};
typedef struct ts_UIStateStorage
{
te_UIState uiState;
ts_EntrySubmenu *presentMenu;
int presentIndex;
void(*onUpPress)();
void(*onDownPress)();
void(*onOkClick)();
void(*onOkHold)();
void(*renderer)();
int idleIterations;
};
/* General UI functions
* Want buttons etc to change internal state,
* but we want screen rendering to be triggered
* from outside when we know we have time and
* won't interfere with something else.
* Also means things update continuously rather than
* when the user does something. Like a clock.
*/
void ui_getStringFromFlash(char* destPtr, const char* sourcePtr);
void ui_getTuEntryFromFlash(tuEntry* destPtr, const tuEntry* sourcePtr);
void ui_Init();
void ui_UpPressed();
void ui_DownPressed();
void ui_OkClicked();
void ui_OkHeld();
void ui_Render();
/********************************************************
* This is where we define the menus and functions
* to call in the menus. So this bit changes on an
* app by app basis.
********************************************************/
/* All the strings we're going to use in our menu system
* Make sure MAX_STRING_INDEX is big enough to contain
* the longest.
*
*/
#define MAX_STRING_INDEX 20
const char MNU_Light[] PROGMEM = "Light";
const char MNU_LightOn[] PROGMEM = "Light on";
const char MNU_LightOff[] PROGMEM = "Light off";
const char MNU_On[] PROGMEM = "On";
const char MNU_Off[] PROGMEM = "Off";
const char MNU_LightEffect[] PROGMEM = "Effect";
const char MNU_Fire[] PROGMEM = "Fire";
const char MNU_Back[] PROGMEM = "Back";
const char MNU_SetSunrise[] PROGMEM = "Set sunrise";
const char MNU_EnableSunrise[] PROGMEM = "Enable sunrise";
const char MNU_DisableSunrise[] PROGMEM = "Disable sunrise";
const char MNU_SetHour[] PROGMEM = "Set hour";
const char MNU_SetMinute[] PROGMEM = "Set minute";
const char MNU_SetTime[] PROGMEM = "Set Time";
const char MNU_MainMenu[] PROGMEM = "Main menu";
/* Our menu entries
*/
const ts_EntryDiscrete mnu_Light_OnOff PROGMEM = { MNU_LightOn,UI_ENTRY_DISCRETE,NULL,NULL };
//Light effect submenu
//todo: first NULL needs to be the address of a function to switch this on.
const tuEntry mnu_sub_Light_Effect[] PROGMEM = {
{.entryDiscrete = {MNU_Fire,UI_ENTRY_DISCRETE,NULL,NULL}}
};
const tuEntry mnu_sub_Light[] PROGMEM = {
{.entryDiscrete = {MNU_LightOn,UI_ENTRY_DISCRETE,NULL,NULL}},
{.entrySubmenu = {MNU_LightEffect,UI_ENTRY_SUBMENU,1,mnu_sub_Light_Effect,mnu_sub_Light}}
};
//Our array of top menu entries
const tuEntry mnu_Top_Level[] PROGMEM = {
{.entrySubmenu = {MNU_Light,UI_ENTRY_SUBMENU,2,mnu_sub_Light,mnu_Top_Level}},
{.entryTime = {MNU_SetSunrise,UI_ENTRY_TIME,NULL, NULL}},
{.entryTime = {MNU_SetTime,UI_ENTRY_TIME,NULL, NULL}}
};
//Our root entry- a single menu element that's a submenu with no parent.
const tuEntry mnu_Root PROGMEM =
{ .entrySubmenu = {MNU_MainMenu, UI_ENTRY_SUBMENU,3,mnu_Top_Level, NULL}
};
#endif
Чтобы проверить это, у меня есть простая программа;
#include <Arduino.h>
#include "ui.h"
void test_getStringFromFlash(char* destPtr, const char* sourcePtr)
{
strncpy_P(destPtr, sourcePtr, MAX_STRING_INDEX);
destPtr[MAX_STRING_INDEX] = 0; //Make sure it's null terminated even if truncated.
}
void test_getTuEntryFromFlash(tuEntry* destPtr, const tuEntry* sourcePtr)
{
int srcPtrInt = (int)sourcePtr;
int dstPtrInt = (int)destPtr;
int size = sizeof(tuEntry);
memcpy_P(destPtr, sourcePtr, sizeof(tuEntry));
te_UIEntryType type = destPtr->entry.entryType;
char name[MAX_STRING_INDEX];
test_getStringFromFlash(name, destPtr->entry.entryName);
Serial.print("EntryFromFlash: source 0x");
Serial.print(srcPtrInt, HEX);
Serial.print(";type ");
Serial.print(type);
Serial.print("; name ");
Serial.println(name);
}
void setup()
{
Serial.begin(115200);
Serial.println(F("Startup"));
char test[MAX_STRING_INDEX];
Serial.println(F("Getting root entry.. "));
tuEntry testEntry;
test_getTuEntryFromFlash(&testEntry, &mnu_Root);
test_getStringFromFlash(test, testEntry.entry.entryName);
Serial.print(F("Entry name is: "));
Serial.println(test);
Serial.print(F("Item count: "));
Serial.println(testEntry.entrySubmenu.numItems);
tuEntry testEntry2;
for (int i = 0; i < 3; i++)
{
Serial.print(F("Getting subentry "));
Serial.println(i);
test_getTuEntryFromFlash(&testEntry2, &testEntry.entrySubmenu.entries[i]);
}
Serial.println(F("Test done"));
}
void loop()
{
}
Эта программа при запуске выдает следующее
Startup
Getting root entry..
EntryFromFlash: source 0x117;type 0; name Main menu
Entry name is: ?????????????????????????????????????!??????
Item count: -18248
Getting subentry 0
EntryFromFlash: source 0xFFFFB8B8;type -32640; name ght on
Getting subentry 1
EntryFromFlash: source 0xFFFFB8C2;type -32640; name ght on
Getting subentry 2
EntryFromFlash: source 0xFFFFB8CC;type -32640; name ght on
Test done
Знаки вопроса и восклицательные знаки заменяют двоичный мусор.
Во-первых, неясно, почему он может получить имя корневого элемента «Главное меню» при переходе по указателю на локальную копию, а не когда я смотрю на локальную копию напрямую.
Во-вторых, вывод показывает, что, хотя я получаю корневое меню, когда я пытаюсь просмотреть массив элементов, содержащихся в этом элементе, указатели выглядят совершенно иначе, чем указатель корневого элемента, и я получаю мусор обратно что по чистой случайности в элементе name есть значения, указывающие на часть моей таблицы строк.
Итак, возможно ли то, что я делаю, или я пытаюсь заставить компилятор слишком углубиться в указатели вложенности? Я знаю, что во флэш-памяти можно иметь массив указателей на строки во флеш-памяти, поэтому основная идея не глупа, но, возможно, это особый случай.