Вложенные структуры и указатели в flash - PullRequest
0 голосов
/ 05 марта 2019

У меня проблема с навигацией по довольно сложным данным во флэш-памяти. Поскольку не многие функции могут читать непосредственно из флэш-памяти, у меня есть несколько вспомогательных функций для копирования структур и строк из флэш-памяти в ОЗУ. Либо они работают не так, как я ожидаю, либо способ, которым я указываю на вещи во флэш-памяти, из других вещей во флэш-памяти не работает.

У меня есть заголовочный файл, который полностью определяет иерархическую структуру меню во флэш-памяти. Он имеет ряд элементов с указателями на другие элементы.

/**************************
 * 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 есть значения, указывающие на часть моей таблицы строк.

Итак, возможно ли то, что я делаю, или я пытаюсь заставить компилятор слишком углубиться в указатели вложенности? Я знаю, что во флэш-памяти можно иметь массив указателей на строки во флеш-памяти, поэтому основная идея не глупа, но, возможно, это особый случай.

...