dm37

Пример Реализации Меню На Микроконтроллере

16 сообщений в этой теме

dm37    54

Сделал два варианта (попроще и посложнее). Может кому надо. Внутри архива исходники на C (IAR) + проект для Proteus.

Оборудование:

- ATmega32;

- LCD 1602/1604 (HD44780);

- клавиатура (4/8 кнопок);

- 2 регистра ввода (74HC245);

- 2 регистра вывода (74HC573);

- зуммер.

https://ru.files.fm/u/a2sfccuf#/list/

  • Одобряю 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Быстрый заказ печатных плат

Полный цикл производства PCB по низким ценам!

  • x
    мм
Заказать Получить купон на $5.00
COKPOWEHEU    248

Проект довольно накрученный, по крайней мере с ходу я не разобрался где задаются пункты меню и как оно организовано на низком уровне. Ну и такое дерево директорий выглядит великоватым для такой задачи.

Ну и хотелось бы посмотреть результат - сколько места занимает само меню (и сколько - каждый пункт) и с какой скоростью выполняется.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
dm37    54

Добавил небольшое описание

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
COKPOWEHEU    248

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

По существу: было бы неплохо все-таки выложить описание, но не где-то в глубине ссылки, а в первом посте (думаю, админы помогут переместить если что) и более по существу.

Ну вопрос объема не раскрыт.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
demiurg1978    19

Мой пример на основе MicroMenu.

проекта на этом меню.

//========================================================================
#ifndef MENU_H

#define MENU_H

#include "menu.h"

#include "main_def_func.h"
//========================================================================

//========================================================================
// Typedefs:
typedef void (*FuncPtr)(void);
//========================================================================

//========================================================================
typedef struct menu_item
{
void		 *Parent;
void		 *Child;
void		 *Next;
void		 *Prev;
FuncPtr	 MenuFunc;
FuncPtr	 EnterFunc;
char __flash *Text;
} menu_item;
//========================================================================

// Externs:
//========================================================================
extern menu_item __flash *CurrMenuItem; // Текущий пункт меню.
extern menu_item __flash *BeginCurrMenuLevel; // Начало массива текущего уровня меню.

extern __flash menu_item Null_Menu;

extern char Menu_Str_Buf []; // Буфер для вывода текста.

extern void (*MenuFuncPtr)(void);
//========================================================================

// Defines and Macros:
//========================================================================
#define NULL_ENTRY Null_Menu
#define NULL_FUNC (void*)0
#define NULL_TEXT 0x00
#define PAGE_MENU 3
//========================================================================

//========================================================================
#define MAKE_MENU(Name, Parent, Child, Next, Prev, MenuFunc, EnterFunc, Text) \
extern menu_item __flash Parent;													 \
extern menu_item __flash Child;													 \
extern menu_item __flash Next;														 \
extern menu_item __flash Prev;														 \
 menu_item __flash Name =													 \
{																					 \
 (menu_item*)	 &Parent,													 \
 (menu_item*)	 &Child,													 \
 (menu_item*)	 &Next,														 \
 (menu_item*)	 &Prev,														 \
					 MenuFunc,													 \
					 EnterFunc,													 \
				 {Text}														 \
}
//========================================================================

//========================================================================
#define PARENT	 *((menu_item __flash*) (CurrMenuItem->Parent))
#define CHILD	 *((menu_item __flash*) (CurrMenuItem->Child))
#define NEXT	 *((menu_item __flash*) (CurrMenuItem->Next))
#define PREV	 *((menu_item __flash*) (CurrMenuItem->Prev))
#define MENU_FUNC *((FuncPtr) (CurrMenuItem->MenuFunc))
#define ENTER_FUNC *((FuncPtr) (CurrMenuItem->EnterFunc))
//========================================================================

//========================================================================
#define SET_MENU_LEVEL(x) \
Set_Menu_Level(&x)

#define SET_MENU_ITEM(x) \
Set_Menu_Item(&x)

#define GO_MENU_FUNC(x) \
MenuFunc((FuncPtr*)&x)

#define EXTERN_MENU(Name) \
extern menu_item __flash Name;
//========================================================================

//========================================================================
enum
{
SET_LEVEL = 0,
SET_NEXT,
SET_PREV,
};
//========================================================================

// Prototypes:
//========================================================================
void Set_Menu_Level (menu_item __flash *NewMenu);
void Set_Menu_Item (menu_item __flash *NewMenu);
void MenuFunc(FuncPtr* Function);
//========================================================================

//========================================================================
EXTERN_MENU (L_OUT_MODE);
//========================================================================

//========================================================================
void Out_Menu_Items_Init (void);
void Out_Menu_Items (void);
//========================================================================

//========================================================================
bool proc_menu_keys (void);
//========================================================================

//========================================================================
void out_name_level (void);
u08 count_chars (char __flash *data);
void make_page_menu (void);
void inc_pos_y_curs (void);
void dec_pos_y_curs (void);
void set_pos_curs (void);
//========================================================================

#endif

//========================================================================
#include "menu.h"
//========================================================================

//========================================================================
static u08 quant_items;
static u08 pos_y_curs;
//========================================================================

//==============================================================================================================================================
menu_item __flash *CurrMenuItem; // Текущий пункт меню.

menu_item __flash *BeginCurrMenuLevel; // Начало массива текущего уровня меню.

menu_item __flash *temp_menu;

menu_item __flash Null_Menu = {(void*)0, (void*)0, (void*)0, (void*)0, NULL_FUNC, NULL_FUNC, {NULL_TEXT}};

void (*MenuFuncPtr)(void);
//==============================================================================================================================================

//========================================================================
void Set_Menu_Level (menu_item __flash *NewMenu)
{
if ((void*)NewMenu == (void*)&NULL_ENTRY)
 return;

CurrMenuItem = NewMenu;

Out_Menu_Items_Init (); // Так как новый уровень, инициализация переменных.
Out_Menu_Items (); // Вывод названия уровня меню и пунктов меню, курсора.

GO_MENU_FUNC (MENU_FUNC);
}
//========================================================================

//========================================================================
void Set_Menu_Item (menu_item __flash *NewMenu)
{
if ((void*)NewMenu == (void*)&NULL_ENTRY)
 return;

CurrMenuItem = NewMenu;

Out_Menu_Items (); // Вывод названия уровня меню и пунктов меню, курсора.

GO_MENU_FUNC (ENTER_FUNC);
}
//========================================================================

//========================================================================
void MenuFunc (FuncPtr* Function)
{
if ((void*) Function == (void*) NULL_FUNC)
 return;

((FuncPtr) Function)();
}
//========================================================================

//========================================================================
bool proc_menu_keys (void)
{
switch (GetKeyCode ())
{
 case KEY_ESC_COD:
	 SET_MENU_LEVEL (PARENT);
	 return true;

 case KEY_ENTER_COD:
	 SET_MENU_LEVEL (CHILD);
	 return true;

 case KEY_NEXT_COD:
	 inc_pos_y_curs ();
	 SET_MENU_ITEM (NEXT);
	 return true;

 case KEY_PREV_COD:
	 dec_pos_y_curs ();
	 SET_MENU_ITEM (PREV);
	 return true;

 default:
	 return false;
}
}
//========================================================================

/*
Уровни, пункты, текст - все выводится автоматом.
Так как все переходы по меню расписаны в структуре, то отпадает надобность в запоминании перемещений по меню.
*/

//========================================================================
void Out_Menu_Items_Init (void)
{
quant_items = 1;
pos_y_curs = 1;

// Получение адреса начала массива уровня меню.
BeginCurrMenuLevel = CurrMenuItem;
temp_menu = (menu_item __flash *)(CurrMenuItem->Prev);

while (1)
{
 if ((void*)temp_menu == (void*)&NULL_ENTRY)
 {
	 break;
 }
 else
 {
	 BeginCurrMenuLevel = temp_menu;
	 temp_menu = (menu_item __flash *)(temp_menu->Prev);
 }
}

// Получение количества пунктов меню.
temp_menu = (menu_item __flash *)(BeginCurrMenuLevel->Next);

while (1)
{
 if ((void*)temp_menu == (void*)&NULL_ENTRY)
 {
	 break;
 }

 temp_menu = (menu_item __flash *)(temp_menu->Next);
 quant_items++;
}

// Позиция курсора.
if (quant_items > 1)
{
 temp_menu = BeginCurrMenuLevel;

 while (1)
 {
	 if ((void*)temp_menu == (void*)&NULL_ENTRY)
	 return;

	 if (temp_menu == CurrMenuItem)
	 return;
	 else
	 pos_y_curs++;

	 temp_menu = (menu_item __flash *)(temp_menu->Next);
 }
}
}

void Out_Menu_Items (void)
{
clr_dsp_buf ();

out_name_level (); // Вывод названия уровня меню.

make_page_menu (); // Вывод пунктов меню.

set_pos_curs (); // Установка позиции и вывод курсора.
}
//========================================================================

//========================================================================
// Вывод названия уровня меню.
void out_name_level (void)
{
temp_menu = (menu_item __flash *)(CurrMenuItem->Parent); // Считывание названия уровня меню из пункта меню в верхнем уровне.

if ((void*)temp_menu != (void*)&NULL_ENTRY)
{
 char __flash *data = temp_menu->Text;

 u08 i = count_chars (data); // Подсчет кол-ва символов в строке.

// Выравнивание текста посередине строки дисплея.

 u08 a = i;

 i = (20 - i); // Дисплей 20x4. Отнимаем от 20 число символов.

 i >>= 1; // Делим остаток на 2.

 if (a & (1<<0))
	 i += 2; // Если число нечетное.
 else
	 i++; // Если число четное.

 Print_Buf (1, i, temp_menu->Text);
}
}
//========================================================================

//========================================================================
// Подсчет кол-ва символов в строке.
u08 count_chars (char __flash *data)
{
u08 i = 0;

while (data [i])
{
 i++;
}
return i;
}
//========================================================================

//========================================================================
void make_page_menu (void)
{
signed char tmp_pos_y_curs;
u08 i; // Счетчик страниц.
u08 j; // Страница меню.

if (quant_items > 1) // Если пунктов меню больше 1, значит есть что выводить.
{
 temp_menu = BeginCurrMenuLevel;

 if (pos_y_curs > PAGE_MENU)
 {
	 tmp_pos_y_curs = pos_y_curs;

	 i = 0; // Счетчик страниц.

	 while (tmp_pos_y_curs > 0)
	 {
	 tmp_pos_y_curs -= PAGE_MENU;
	 i++;
	 }
	 tmp_pos_y_curs += PAGE_MENU;

	 j = PAGE_MENU; // Страница меню.

	 while (i-- > 1)
	 {
	 while (j--)
	 {
		 temp_menu = (menu_item __flash *)(temp_menu->Next); // Следующий пункт меню.
	 }
	 j = PAGE_MENU; // Страница меню.
	 }
 }

 u08 pos_y_text_item = 2; //
 j = PAGE_MENU; // Страница меню.

 while (j--)
 {
	 Print_Buf (pos_y_text_item, 2, temp_menu->Text); // вывод названия пункта меню.

	 temp_menu = (menu_item __flash *)(temp_menu->Next); // Следующий пункт меню.

	 if ((void*)temp_menu == (void*)&NULL_ENTRY) // Если элемент Next
	 return;								 // пустой, то выход.
	 else
	 pos_y_text_item++;
 }
}
}
//========================================================================

//========================================================================
void inc_pos_y_curs (void)
{
if (quant_items > 1)
{
 if (pos_y_curs < quant_items) pos_y_curs++;
}
}

void dec_pos_y_curs (void)
{
if (quant_items > 1)
{
 if (pos_y_curs > 1) pos_y_curs--;
}
}
//========================================================================

//========================================================================
void set_pos_curs (void)
{
if (quant_items > 1)
{
 signed char tmp = pos_y_curs;

 while (tmp > 0)
 {
	 tmp -= PAGE_MENU;
 }

 if (tmp <= 0) tmp += PAGE_MENU;

 PrintChar (tmp + 1, 1, ARROW_RIGHT);
}
}
//========================================================================

//=======================================================================================================================================================================================================================================================
//		 NAME	 PARENT	 CHILD	 NEXT	 PREV	 MENU_FUNC			 ENTER_FUNC TEXT
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MAKE_MENU (L_OUT_MODE, NULL_ENTRY, L1_I1,	 NULL_ENTRY, NULL_ENTRY, set_main_tui_service, NULL_FUNC, "ПАРАМЕТРЫ");
MAKE_MENU (L1_I1,	 L_OUT_MODE, NULL_ENTRY, L1_I2,	 NULL_ENTRY, set_menu_parameters, NULL_FUNC, "ПУНКТ_1");
MAKE_MENU (L1_I2,	 L_OUT_MODE, NULL_ENTRY, L1_I3,	 L1_I1,	 NULL_FUNC,		 NULL_FUNC, "ПУНКТ_2");
MAKE_MENU (L1_I3,	 L_OUT_MODE, NULL_ENTRY, L1_I4,	 L1_I2,	 NULL_FUNC,		 NULL_FUNC, "ПУНКТ_3");
MAKE_MENU (L1_I4,	 L_OUT_MODE, NULL_ENTRY, NULL_ENTRY, L1_I3,	 NULL_FUNC,		 NULL_FUNC, "ПУНКТ_4");
//=======================================================================================================================================================================================================================================================

Изменено пользователем demiurg1978

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
dm37    54

Ну достоинства и недостатки наверно лучше расскажут другие (хвалиться не буду).

Я просто привёл в нормальный вид свой проект с меню и решил им поделиться (на указателях построено большинство меню).

Также показал свой код работы с LCD индикатором на HD44780 от 0801 до 4002 (выбирается дефайнами без изменения кода), в том числе перекодировку символов для индикатора двумя способами (макрос и таблица), обработку клавиатуры и работу с входными и выходными дискретами.

Да, описание немного страдает.

Как мне кажется, меню создать не сложно (мне кажется просто, в описание это показал). Каждое меню отдельный файл, для каждой кнопки своя функция. На скорость работы вроде не жалуются.

Время выполнения кода в прерывании (таймер на 1 млсек) (опрос клавиатуры, вывод на экран, ввод/вывод дискретов) - ~40 мксек при кварце 8 МГц (при 16 МГц будет работать в 2 раза быстрее :-) )

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
demiurg1978    19

Весь вопрос в том, насколько просто создавать проекты и меню. На мой взгляд, MicroMenu это обеспечивает.

Увидеть бы видео хотя бы тестового проекта и сам проект по вашему способу.

Кстати, в IAR есть файл intrinsics.h, там много интересного.

__enable_interrupt (); - sei
__disable_interrupt (); - cli
__watchdog_reset (); - wdr

Изменено пользователем demiurg1978

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
dm37    54

Добавил видео по созданию нового пункта меню

https://ru.files.fm/u/a2sfccuf#/list/

Скачивать по ссылке, далее нажимая на кнопку "Скачать" или в таблице столбец "Download"

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
demiurg1978    19

Если честно, то я не составил никакого мнения. Главные критерии создания меню - легкость, минимум мест правки. Ваш проект состоит из туевой хучи файлов и непонятно какие файлы смотреть. Займитесь этим. Вычистите проект, избавьтесь от лишних файлов. Когда будет готово, напишите новое сообщение, чтобы было понятно, что у вас что-то готово.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
dm37    54

Чиститься ничего не будет, файлы проекта разбиты на функционалу. Дерево файлов проекта описано. Лёгкость построения меню я показал в видео.

Построение меню похоже на работу панелей операторов для связи с ПЛК: создаётся экран и на кнопки прописываются функции (например переход на другой экран).

Можно легко перейти с одного дисплея на другой (с 1602 на 2004) не меняя код. Если засунуть весть этот код в один файл неужели легче будет разобраться?

Если есть вопросы спрашивайте - отвечу.

Много пустых функций в меню можно убрать и поставить заглушку stub() (также было в описание). Код будет выглядеть уже так:

//==============================================================================
// Меню "Версия устройства"
//==============================================================================
#include "..\module\menu.h"
extern const uint8_t __flash MsgName[];
extern const uint8_t __flash MsgVersion[];
//==============================================================================
// Отображение меню
//==============================================================================
static void show(void)
{
 ShowTextF(6, MsgName);	  // отображаем имя проекта
 ShowTextF(16, MsgVersion);  // отображаем версию проекта
}
//==============================================================================
// Действие кнопки "Параметр"
//==============================================================================
static void param(void)
{
 MenuSetMenu(MENU_DEFAULT);  // при нажатии кнопки "Param" переходим на "Главный экран"
}
//==============================================================================
const TMenu __flash MenuVersion = {show, stub, stub, {stub, stub, param, stub}};

Изменено пользователем dm37

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
demiurg1978    19

Подход как к панели оператора одобряю, сам использую именно такой подход. По поводу меню, кнопок и так далее. Выше я приводил пример. В самом конце само меню. Обслуживание меню только на конечных автоматах. Иначе никак. От слова совсем. Кто-то скажет прототреды, но прототреды те же самые конечные автоматы, только в фантике.

Кнопки у вас намертво завязаны на функции. И это уже накладывает ограничения.

Лично я не хочу ковыряться во всех ваших файлах.

Как сделано у меня: модуль меню (интерпретатор структуры меню), аппаратный модуль дисплея, модуль вывода информации (модуль меню - всего лишь часть этого модуля).

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
dm37    54

что вы имеете ввиду

"Кнопки у вас намертво завязаны на функции. И это уже накладывает ограничения."?

я могу не вернуться туда откуда пришёл, если я захожу в один экран из разных мест? Да это так. Но можно и это обойти добавив адрес возврата.

у меня только скелет меню, и многоуровневое меню из проекта avr_menu2 только небольшая надстройка. Ввиду идентичности экранов меню общие функции вынесены в файл menu.c.

Отображение идёт на уровне экранов, что захотите, то и поместите на экран.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
COKPOWEHEU    248

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

Вопрос занимаемого объема остается открытым, вопрос достоинств и недостатков тоже.

На что вы рассчитывали, давая ссылку на свой вариант? Что новички (у тех кто поопытнее часто уже есть своя или сторонняя реализация) кинутся разбираться в ваших десятках файлов, смотреть видео и т.д. чтобы получить кота в мешке?

Если бы оформили меню как отдельный модуль с нормальным описанием - другое дело.

  • Одобряю 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас