Простой способ создания меню для устройств с LCD дисплеем, пример на C для PSoC контроллера

Тема создания меню неоднократно обсуждалась, тем не менее мне захотелось создать более простой, компактный и универсальный вариант реализации, чем то, что нашёл в сети.

В итоге получилось довольно красивое решение, которым я буду рад поделиться с другими.

Меню может содержать до 127 пунктов, разделённых на группы. Выбор числа групп, связей между ними и числа пунктов в разных группах может иметь произвольную конфигурацию и легко задаётся в массиве констант.
В описаном примере меню состоит из 4х групп:


Видео работы на YouTube

Каждый пункт в списке включает название в ковычках и следующий за ним индекс.
Нулевой индекс означает что по нажатии на этот пункт меню ничего не произойдёт.
Индекс, к примеру 2 в пункте Sensor (пункт 0) означает переход на DMX Addr (пункт 2) по нажатии на среднюю кнопку.
Установленный бит 7 (Last|XX) означает что этот пункт последний в своей группе.

Помимо навигации в примере реализована функция быстрого перебора значений от 0 до 255 и от 0 до 512 (после удержания кнопки вверх/вниз нажатой в течении секунды).

Сканирование клавиатуры и обновление значений счётчиков быстрого перебора работают в прерывании по слип-таймеру с частотой 64Гц.
Основная пользовательская програма может располагаться в пунктах цикла «SWITCH-CASE», значение переменной Menu определяет какую функцию устройства задействовать…

#include <m8c.h>        // part specific constants and macros
#include "PSoCAPI.h"    // PSoC API definitions for all User Modules
/*declare interrupt handlers*/
#pragma interrupt_handler SleepTimer_ISR

#define Last 	128

typedef struct Mesel
{
    const unsigned char String[9];
    const unsigned char Addr;  
}Mesel;

const Mesel TestStruct[]=
{
    {" Sensor ",0|2},
    {"Lighting",Last|6},
    {"DMX Addr",0|12},
    {"On-time ",0},
    {"Brghting",0},
    {"<<----  ",Last|1},
    {"Fade Set",0},
    {"Relay AC",0},
    {"MaxBrigh",0},
    {"MinBrigh",0},
    {"DMX Addr",0},
    {"<<----  ",Last|1},
    {" From-1 ",0},
    {"  To-1  ",0},
    {" From-2 ",0},
    {"  To-2  ",0},
    {" From-3 ",0},
    {"  To-3  ",0},
    {"<<----  ",Last|2},
}; 

/*declare global variables*/
unsigned char Keystate,PushTim,charcount;
unsigned int incount;
void SleepTimer_ISR(void)
{
    unsigned char x;

    Keystate&=7;
    ROW_1_On();
    ROW_3_Off();
    ROW_2_Off();//Delay
    if((PRT1DR&200)&128)  Keystate|=64;//up
    ROW_1_Off();
    ROW_2_On();
    ROW_3_Off();//Delay
    if((PRT1DR&200)&8) Keystate|=16;//left
    if((PRT1DR&200)&128) Keystate|=128;//center
    if((PRT1DR&200)&64) Keystate|=8;//right
    ROW_2_Off();
    ROW_3_On();
    ROW_1_Off();//Delay
    if((PRT1DR&200)&128) Keystate|=32;//down
    if(Keystate&~7)//any key pushed
    {
        if(PushTim)PushTim--; 
        else 
        {
            PushTim=5;
            Keystate&=~1;//set bit for fast scrolling
        }
    }
    else
    {	
        Keystate&=~3;//Reset bit 0 if nothing pushed
        PushTim=64;//one sec push button for fast scrolling
    } 
}

void main(void)
{
    unsigned char temp,temp1,temp2;
    unsigned char keys=Keystate&249;
    unsigned char Menu;
    unsigned char MenuSel;
    M8C_EnableGInt;    			// enable the global interrupt 
    M8C_EnableIntMask(INT_MSK0, INT_MSK0_SLEEP|INT_MSK0_GPIO);	//enable external interrupts
    LCD_Start();
    ROW_1_Start(); 
    ROW_2_Start();
    ROW_3_Start(); 
    PRT1DR=0;

    LCD_Position(0,0);
    LCD_PrCString(" 4 Group");
    LCD_Position(1,0);
    LCD_PrCString("  Menu  ");

    MenuSel=0;
    
    while(1)//main loop
    {	
        switch (Menu)
        {
            case(0):

            case(1):

            case(2):
            
            case(3):
                
            default: ;
        }
        
        temp=Keystate;		//scan keymatrix
        temp1=MenuSel;//Store state of current Menu Select of selected deph
        if(temp==64)//Up button up
        {
            LCD_Position(0,0);
            if((MenuSel>9)&&(MenuSel!=11)&&(MenuSel!=18))// 0..512 value scroll 
            {
                if (incount<512)
                {
                    incount++;
                    LCD_PrCString("Ch: ");
                    LCD_PrHexInt(incount);
                }
            }
            if((MenuSel==3)||(MenuSel==4)||(MenuSel==8)||(MenuSel==9))// 0..255 value scroll 
            {
                if (charcount<255)
                {
                    charcount++;//	MenuSel=To-1;
                    LCD_PrCString("Level:");
                    LCD_PrHexByte(charcount);
                }
            }
            Keystate|=1;//1;
        }
        if(temp==32)//Down button
        {
            LCD_Position(0,0);
            if((MenuSel>9)&&(MenuSel!=11)&&(MenuSel!=18))// 0..512 value scroll 
            {
                if (incount)
                {
                    incount--;
                    LCD_PrCString("Ch: ");
                    LCD_PrHexInt(incount);
                }
            }
            if((MenuSel==3)||(MenuSel==4)||(MenuSel==8)||(MenuSel==9))// 0..255 value scroll 
            {
                if (charcount)
                {
                    charcount--;
                    LCD_PrCString("Level:");
                    LCD_PrHexByte(charcount);
                }
            }
            Keystate|=1;//1;
        }
        if(temp==16)//Left button
        {
            if(MenuSel&&(!((TestStruct[MenuSel-1].Addr)&Last))) MenuSel--;
            else
            while(!((TestStruct[MenuSel].Addr)&Last))	
            {
                MenuSel++;
            }
            incount=0;
            charcount=0;
            LCD_Position(0,0);
            LCD_PrCString("        ");
        }
        if(temp==8)//Right button
        {
            if(!((TestStruct[MenuSel].Addr)&Last)) MenuSel++;
            else 
            do
            {
                MenuSel--;
            }while(MenuSel&&(!((TestStruct[MenuSel-1].Addr)&Last)));
            incount=0;
            charcount=0;
            LCD_Position(0,0);
            LCD_PrCString("        ");
        }
        if(temp==128)//center(Enter) button
        {
            temp2=(TestStruct[MenuSel].Addr)&~Last;
            if (temp2) MenuSel=temp2;
            else Menu=MenuSel;
        }	
        if(temp1!=MenuSel)
        {
            LCD_Position(1,0);
            LCD_PrCString(&TestStruct[MenuSel].String[0]);
            Keystate|=2;//1;
        }	
    }
}

Комментарии (49)

RSS свернуть / развернуть
Я уже выкладывал это на хабре, но страничка так и осталась без внимания в песочнице, а могла бы кому-то оказаться полезной…
0
Структура данных и общая идея вполне понятны, но читать такой код желания мало (хотя он и не так уж плох).
Но вот модифицировать такие менюшки — весьма мучительно и чревато багами. Не зря обычно применяются возможно менее эффективные по расходу памяти, но более удобные в работе решения.
0
А. Хе-хе. Вот и я не на ту кнопочку «ответить» нажал. Это был комментарий к топику.
0
Модифицируется только список строк-констант в самом начале, куда уж проще?
0
Втыкаем новый пункт в начале меню — и опа, нужно пересчитывать все линки и править все сравнения MenuSel в обработке вызова пунктов. Во первых — это изысканный мазохизм, во вторых — ошибки практически гарантированы.
Уж если авторы MPEG-4 ухитрились ошибиться на день в переводе 66 лет в количество дней…
0
Жуть.
В нулевой пункт вернуться нельзя. Даже в примере возврат из двух разных подменю происходит к одному и тому же пункту Lighting, вопреки диаграмме переходов.
Проверять пункты для изменения значений через:
if((MenuSel==3)||(MenuSel==4)||(MenuSel==8)||(MenuSel==9))
это катастрофа. Малого того что само меню записано неудобно (строки надо считать), то тут вообще придется по всей проге отлавливать изменения после каждого добавления нового пункта в меню.
Моё мнение: подумать хорошо и переписать заново, если этот код планируется использовать в дальнейшем. На «универсальный вариант» и «красивое решение» ну никак не тянет.
+2
  • avatar
  • ACE
  • 23 июля 2014, 01:46
Почему-же нельзя?.. Все согласно диаграме, и по видео работы это видно…
А проверять значения… можно конечно составить таблицу констант, проверять в сециальной функции-но когда меню небольшое это из пушки по воробьям, так явно понятнее и эффективнее по памяти…
0
Вопрос только, что даст экономия 10-20 байт памяти при том, что самые дешевые и распространенные ATMEGA8 имеют килобайт памяти, а более новые и местами более дешевые STM8/STM32 — от двух кило.
Но любые модификации такого меню… Да я лучше с нуля перепишу!
0
Да я уже ответил ниже, это нюанс который обходится пустым первым пунктом меню…
А на счет экономии-попробуйте такое меню организовать в виде традиционного массива стрингов, объем необходимой памяти увеличится во много раз…
0
Да я уже ответил ниже, это нюанс который обходится пустым первым пунктом меню…
Про это я ничего не писал. Я писал что, чем модифицировать такое меню — я лучше сотру весь код его обработки и напишу заново. Ошибок меньше будет, и быстрее.
в виде традиционного массива стрингов
Что есть «традиционный массив стригов» и за счет чего оно увеличится?
0
Вам не нравится только код обработки меню а не сам принцип?..
Тогда экономия здесь не при чем, я имел ввиду классическое меню в виде многомерного массива стрингов-пунктов…
В обработке достаточно компактная програма позволяет делать быстрый и медленный перебор значений, при чем они могут быть как в диапазоне 0… 255 так 0..512, а конструкция SWITCH позволяет гулять между различными режимами работы, просто и эффективно в плане ресурсов…
0
Мне не нравится код — нечитаемый и немодифицируемый.
Сама структура… Идея вполне любопытная, но требует доработок — как минимум избавиться от необходимости пересчитывать индексы при вставке нового пункта.
я имел ввиду классическое меню в виде многомерного массива стрингов-пунктов…
Пример?
просто и эффективно в плане ресурсов…
Но крайне неэффективно в плане разработки.
0
Но крайне неэффективно в плане разработки.
Поясню этот пункт. Это не программа. Это тарелка макарон.
0
Предлагаете создавать и модифицировать таблицу констант чтобы не считать пункты один раз при изменении проекта?.. Предложите что-то конкретное а не просто не нравится…
0
Я ничего не предлагаю. Я указываю на недостатки, требующие исправления. Исправить их можно множеством методов.
Так, в одном проекте я генерил константы с индексами элементов массив при помощи препроцессора. Аналогичный подход позволил бы, для начала, сравнивать MenuSel не с магическими числами, а с константантами, причем править эти константы при изменении меню не потребовалось бы.
0
В структуре меню у вас два раза "{"<< — ",Last|1}," — это возврат к первому пункту Lighting. А к нулевому пункту (Sensor) вернуться нельзя, ибо запись "{"<< — ",Last|0}," будет означать отсутствие действия.
0
Да сорри в нулевой действительно нельзя, я уже и забыл про это-но это не важно ибо возврат в любом случае происходит в корневое меню, а там уже стрелками влево-вправо можно гулять…
0
Если необходимо вернуться непременно в первый пункт меню-можно просто напросто нулевым пунктом оставить пустую строку, и тогда начальным станет первый пункт…
0
Лучше просто задействовать в качестве маркера «no transition» число 127. Тогда нельзя будет перейти на пункт №127 — что эквивалентно сокращению максимального числа пунктов со 128 до 127.
0
Опять ты опередил. Негодяй! :)
+1
согласен
0
Проще назначить «тупиковым кодом» 127. Вы как каз пишите, что поддерживается «до 127 пунктов» меню, 0-126, значение 127 свободно.
0
В качестве конструктивного предложения, хотя бы использовать дефайны:
// last item of submenu
#define MI_LAST 128
// no sub menu code
#define MI_NOP 127

#define MI_START_MAIN 0
#define MI_START_SENSOR 2
#define MI_START_LIGHTING 6
#define MI_START_DMX_ADDR 12

#define MI_SENSOR 0
    {" Sensor ", MI_START_SENSOR},
#define MI_LIGHTING 1
    {"Lighting", MI_START_LIGHTING | MI_LAST},

#define MI_DMX_ADDR 2
    {"DMX Addr", MI_START_DMX_ADDR},
#define MI_ONTIME 3
    {"On-time ", MI_NOP},
#define MI_BRGHTING 4
    {"Brghting", MI_NOP},
#define MI_STOP_SENSOR 5
    {"<<----  ", MI_START_MAIN | MI_LAST},

#define MI_FADE_SET 6

и т.д.

и в самом коде:
if ((MenuSel==MI_ONTIME) || 
    (MenuSel==MI_BRGHTING) ||
    (MenuSel==MI_SMTH1) ||
    (MenuSel==MI_SMTH2)) ...

Так код хоть чуточку легче будет править. При добавлении пунктов хотя бы не придется менять коды переходов и номера изменяемых пунктов по всей портянке. Хотя и придется перенумеровывать пункты, но они хотя бы по порядку идут.
0
  • avatar
  • ACE
  • 23 июля 2014, 03:01
Но если подумать, я бы сделал так.
Достаточно учесть тот факт, что переход к подменю всегда идёт на первый пункт подменю, а возврат всегда к тому пункту, из которого в подменю попали. Тогда нумеровать надо не пункты, а подменю.
#define MI_LAST 128
// no sub menu code
#define MI_NOP 127

#define MI_SUBMENU_MAIN 0
#define MI_SUBMENU_SENSOR 1
#define MI_SUBMENU_LIGHTING 2
#define MI_SUBMENU_DMX_ADDR 3

    {" Sensor ", MI_SUBMENU_SENSOR},
    {"Lighting", MI_SUBMENU_LIGHTING | MI_LAST},

    {"DMX Addr", MI_SUBMENU_DMX_ADDR},
    {"On-time ", MI_NOP},
    {"Brghting", MI_NOP},
    {"<<----  ", MI_SUBMENU_MAIN | MI_LAST},


Далее все просто. Номер первого пункта требуемого подменю находится проходом по массиву. Если переходим к подменю, номер которого больше номера текущего подменю, т.е. глубже по дереву, переходим к его первому пункту. Если наоборот, возвращаемся в родительское меню, то нужно в нём найти пункт, из которого есть переход в текущее подменю. Вуаля. Еще и пунктов меню теперь может быть 256, а подменю 127.
+1
А если сократить число допустимых подменю до, скажем, 31, можно выкроить ещё 2 бита, которые будут указывать нам, что это счетчик. Или, даже лучше, сократив число подменю до 63, зарезервировать значения переходов 64-126 на выполняемые действия. Например, переход на 126 будет означать что этот пункт — счетчик 0..255, код 125 — счетчик 0..512, остальные — пользовательские действия. Тем самым полностью избавиться от необходимости нумеровать пункты подменю.
0
Помимо прочих замечаний упомянутых выше, я бы изменил, как минимум, три вещи:
— полностью разделить железо-специфичный и общий код. сейчас во многих местах каша, что сильно усложняет чтение/понимание/перенос кода.
— создал бы константы для кнопок (и коментарии возле кейсов станут не нужны)
— перешел бы на событийную модель обработки.
Последний пункт чуть подробнее: имеет смысл убрать «main loop» и вынести обработчик событий в отдельную функцию, которая на вход получает массив меню, текущее состояние и событие, обрабатывает событие, сохраняет новое состояние, а на выходе возвращает действие, которое нужно сделать (вызвать какую-то функцию, например). Такая схема работы позволит выбросить код обработчика из главного цикла и оставить в нем только вызовы нужных функций. Второе преимущество — такая схема обработки универсальна, она годится и для однопоточного кода типа «main loop» и для многопоточной обработки (в этом случае по прерыванию генерится событие и кладется в очередь, а в отдельном потоке обработчик его обрабатывает, перерисовывает меню и, если надо, генерит события на вызов каких-либо функций либо делает это сам).
+2
Делал что-то подобное, но задачи минимизировать затраты ОЗУ не стояло. Выложу в качестве идеи.

Каждый пункт меню содержит название, указатель на функцию «обработчик» и два параметра для функции «обработчика». Эти параметры каждая функция может обрабатывать по своему (как в SendMessage() Win32).

//---------------------------------------------------------------------------------------------------------------------
typedef BOOL (*MenuHandler)(void * param, DWORD size);

//---------------------------------------------------------------------------------------------------------------------
typedef struct {
	const char Name[33];
	MenuHandler Handler;
	void * Param;
	DWORD ParamSize;
} MenuItem;


Вот пример описания меню с подменю

подменю

//---------------------------------------------------------------------------------------------------------------------
const MenuItem StatusMenu[] = {
		{"Баланс GSM", ShowGSM_Balance, NULL, 0},
		{"Сигнал GSM", ShowGSM_SignalLevel, NULL, 0},
		{"Процессор", ShowSPU_IDLE, NULL, 0},
		{"Диск", ShowFlashCardFree, NULL, 0}
};


основное меню

//---------------------------------------------------------------------------------------------------------------------
const MenuItem MainMenu[] = {
		{"Настройки", ShowMenu, (void  *) ConfigureMenu, sizeof(ConfigureMenu) / sizeof(ConfigureMenu[0])},
		{"Статус", ShowMenu, (void  *) StatusMenu, sizeof(StatusMenu) / sizeof(StatusMenu[0])}
};


Вот вся логика обработки меню

//---------------------------------------------------------------------------------------------------------------------
BOOL ShowMenu(void * Menu, DWORD MenuSize) {
	MenuItem * menu  = (MenuItem *) Menu;
	BYTE i = 0;

	for(;;) {
		CP_DisplayClear();
		CP_DisplayPutString(menu[i].Name);

		BYTE Key = 0;
		for(;;) {
			Key = CP_GetKey();
			if(Key) break;
			vTaskDelay(CHECK_KEY_INTERVAL);
		}

		switch(Key & 0x0F) {
			case KEY_UP:
					i = i > 0 ? i - 1 : MenuSize - 1;
					break;
			case KEY_DOWN:
					i = i < MenuSize - 1 ? i + 1 : 0;
					break;
			case KEY_ENTER:
					if(menu[i].Handler && menu[i].Handler(menu[i].Param, menu[i].ParamSize) == FALSE) return FALSE;
					break;
			case KEY_CANCEL:
					return (Key & 0xF0) ? FALSE : TRUE;
		}
	}
}


Для старта всего этого просто вызываем и передаем как параметры указатель на верхнее меню и кол-во пунктов этого меню.

//---------------------------------------------------------------------------------------------------------------------
void ShowMainMenu(void) {
	ShowMenu((void *) MainMenu, sizeof(MainMenu) / sizeof(MainMenu[0]));
}

Обратите внимание, что при переходе на подменю происходит рекурсивный вызов ShowMenu()
+2
На всякий случай поясню:

ShowMenu() принимает на входе указатель на меню и кол-во пунктов в меню. Она реализует навигацию и при нажатии KEY_ENTER вызывает соответствующий обработчик.

Для создания подменю, я а качестве обработчика указываю эту самую функцию ShowMenu() с соответствующими параметрами

{"Статус", ShowMenu, (void  *) StatusMenu, sizeof(StatusMenu) / sizeof(StatusMenu[0])}


Происходит рекурсивный вызов для показа подменю и т. д.
+2
Уважаемыйe_mc2 ваш код организации меню очень нетривиален (для меня).
Вот уже полчаса пытаюсь «въехать»в такой способ рекурсии. Только начинает проклевываться семечко понимания, ан нет, уже потерял Нить Ариадны. Спасибо за Ваш труд.
0
Если что не понятно — спрашивай.

Там есть один момент, который усложняет код. При коротком нажатии на KEY_CANCEL мы возвращаемся в меню верхнего уровня. При длинном — полностью выходим из рекурсии, выходим сразу из меню, из void ShowMainMenu(void).

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

//---------------------------------------------------------------------------------------------------------------------
BOOL ShowMenu(void * Menu, DWORD MenuSize) {
        MenuItem * menu  = (MenuItem *) Menu;
        BYTE i = 0;
        for(;;) {
                CP_DisplayClear();
                CP_DisplayPutString(menu[i].Name);
                BYTE Key = 0;
                for(;;) {
                        Key = CP_GetKey();
                        if(Key) break;
                        vTaskDelay(CHECK_KEY_INTERVAL);
                }
                switch(Key & 0x0F) {
                        case KEY_UP:
                                        i = i > 0 ? i - 1 : MenuSize - 1;
                                        break;
                        case KEY_DOWN:
                                        i = i < MenuSize - 1 ? i + 1 : 0;
                                        break;
                        case KEY_ENTER:
                                        menu[i].Handler(menu[i].Param, menu[i].ParamSize);
                                        break;
                        case KEY_CANCEL:
                                        return TRUE;
                }
        }
}
+2
В этой версии ты потерял проверку на валидность хэндлера.
Алсо, я так и не нашел, где в предыдущем варианте кода
1) Обрабатывается возвращаемое TRUE/FALSE
2) Как ветка с KEY_ENTER может вернуть TRUE.

P.S. А, кажется понял. Но для других (и для контроля, правильно ли понял я) можешь разъяснить. Алсо, вот это решение действительно красивое и лаконичное.
+1
В этой версии ты потерял проверку на валидность хэндлера.
Я не терял, я ее специально убрал.
и убрать кое-какие проверки, 
 Обрабатывается возвращаемое TRUE/FALSE

if(menu[i].Handler && menu[i].Handler(menu[i].Param, menu[i].ParamSize) == FALSE) return FALSE;


Если хенделер вернул FALSE то мы выходим из функции вернув тоже FALSE, вызвавшая нас рекурсивно функция тоже моментально завершится и вернет FALSE…
А если хендлер вернул TRUE — ничего не произойдет, мы просто останемся в текущем меню с текущей выбранной позицией.

Как ветка с KEY_ENTER может вернуть TRUE.

См. выше, никак. Либо выход из функции с FALSE, либо продолжаем работу дальше.
А вот ветка KEY_CANCEL возвращает либо TRUE — просто вернуться на уровень выше, либо FALSE — выйти из рекурсии совсем.
+1
Я не терял, я ее специально убрал.
А если там NULL окажется? Нет, ИМХО нужно вернуть
if(menu[i].Handler) menu[i].Handler(menu[i].Param, menu[i].ParamSize);
+2
Совершенно верно, указатель проверять нужно.

Но, в данном случае, я просто убрал все (почти все) лишнее дабы было проще понять логику. Ведь это просто пример реализации идеи с рекурсией и хранением указателей на хендлеры в структуре меню.

Это не пример правильного кода. В чистом виде код привязан к моей реализации, там торчат куски из FreeRTOS (vTaskDelay), другие специфические для моей реализации штуки, типа (Key & 0x0F)
+1
Уважаемые e_mc2 и Vga , спасибо за разъяснения. Сейчас сложился «Кубик Рубика» в голове — то бишь, понял логику работы сей рекурсии. Сейчас в кейловском симуляторе опробую трассировку ваших исходников.
… Ага уже на NULL ругается. Ну это мелочи. Я его(Keil) «победю» ;-).
Хорошего дня Вам.
+1
Если кто еще не видел — MicroMenu
0
Вы бы хоть пояснили чем лучше тот подход а не просто кидались сылками… У меня все разжевано с картинками и видео, главные достоинства простота и гибкость в архитектуре пунктов без использования памяти на пустые пункты (знакоместа)…
По сылке я вижу исходник, но без описания и принцип не так очевиден…
0
без использования памяти на пустые пункты (знакоместа)…
ORLY? Насколько я вижу, у тебя выделяется память под строку фиксированной длины. А значит, в пункте «OK» 6 байт окажутся пусты. Меню по ссылке хранит только необходимые симводы (хотя, как по мне, оно хранит слишком много указателей на каждый пункт меню — целых 6, а это 12 байт).

Описание там прямо в коде, в виде doc-комментариев. И сам код простой и понятный.
Но, на мой взгляд, так много недостатков.
Во первых, достаточно неудобное и не наглядное задание структуры меню.
Во вторых, крайне ограниченная отрисовка — оно отображает только текущий выбранный пункт. На многострочных дисплеях это не лучший выбор.
В третьих — для столь скромных возможностей оно хранит слишком много данных.
+1
Но, на мой взгляд, так много недостатков.
*там много
Описание там прямо в коде, в виде doc-комментариев.
И еще есть пример использования.
слишком много указателей на каждый пункт меню — целых 6, а это 12 байт
Пять. Текст хранится не по указателю, как мне в начале показалось.
0
А, и еще — не понятно, нафига вообще нужно NULL_MENU. Можно было заменить просто указателем на NULL.
0
NULL_MENU там нужно поскольку созданием структуры занимается препроцессор (в виде #define MAKE_MENU). А там extern Menu_Item, т.е. указатель должен быть определен где-то. Я как-то делал меню на основе этого исходника, тоже не мог понять зачем пустое меню, воткнул туда нули и компилятор не пропустил такой финт.
0
Согласен, но тем не менее мой вариант менее громоздкий визуально и легко читаемый, а те со структурами и функциями для каждого пункта… как-то заумно и подозреваю что в итоге и памятезатратно… Но каждому свое)
0
Насчет «легко читаемый» ты себе очень сильно льстишь.
Что до затрат памяти — как я уже сказал, микроменю хранит очень много данных на каждый пункт — 12 байт на указатели плюс текст (длина текста переменная, а не выделяется с запасом, как у тебя).
Из приведенных мне больше всего нравится вариант e_mc2 — правда, у него тоже выделение текста с запасом. При такой длине поля под текст ИМХО разумнее уже использовать указатели на строки. И при желании сэкономить память некоторые поля можно уменьшить (в частности, ParamSize — для восьмибиток DWORD явный перебор).
как-то заумно и подозреваю что в итоге и памятезатратно…
Это явный признак того, что программирование тебе надо подтягивать, и подтягивать сильно.
+1
При такой длине поля под текст ИМХО разумнее уже использовать указатели на строки

Ты, безусловно, прав, если дело касается восьмибиток и экономии памяти.

Я сознательно пожертвовал ПЗУ ради наглядности описания меню. Иначе бы пришлось отдельно объявлять строковую константу, потом подставлять ее указатель в структуру. По затратам памяти экономичнее, но не так удобно и наглядно.

в частности, ParamSize — для восьмибиток DWORD явный перебор

Данная реализация писалась под ARM7TDMI. Там, зачастую (из-за выравнивания доступа к памяти) эффективнее работать с 32-битными переменным, даже если такая размерность и не требуется.
0
Иначе бы пришлось отдельно объявлять строковую константу, потом подставлять ее указатель в структуру.
Мне помнится, С позволяет инициализировать литералом и указатель на строку. Тогда строка кладется куда-нибудь в память, а в указатель запихивается указатель на нее.
Данная реализация писалась под ARM7TDMI.
Да, для 32-биток это разумное решение.
+1
Мне помнится, С позволяет инициализировать литералом и указатель на строку. Тогда строка кладется куда-нибудь в память, а в указатель запихивается указатель на нее.


Да, позволяет.

Но я (почему-то) всегда искренне верил, что подобная конструкция синтаксически недопустима для статической инициализации полей структуры (в массиве из структур).

Только что проверил — допустима. По крайней мере оно компилируется при замене

const char Name[33];

в поле структуры на

const char * Name;
0
С порой позволяет совершенно невероятные вещи… Взять хотя бы то, на чем основан protothreads)
0
Собтвенно, это и есть описание к несколько устаревшей версии предыдущего пункта)
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.