Skocz do zawartości

Uniwersalna biblioteka obsługi menu wielopoziomowego - jak to ugryźć?


_LM_

Pomocna odpowiedź

3 minuty temu, _LM_ napisał:

To że dyskusja jest taka żywa świadczy o tym że taka biblioteka jest potrzebna. Być może już na tym etapie przydałoby się zbudować coś co można zaprezentować tak aby każdy mógł wyrazić swoją opinię i wskazać miejsca gdzie należy coś poprawić. 

To może jasno sprecyzujcie założenia: jaką funkcjonalność ta biblioteka ma zapewniać. Nie można programować w C++ (chociaż każdy program tworzony w "Arduino IDE" jest tak naprawdę napisany w  C++ i jako taki kompilowany) z nieznanego powodu. OOP i  C++ zapewnia dużo większą warstwę abstrakcji niż C i uważam, że projekt biblioteki powinien być napisany z użyciem OOP. Jeśli powodem wyboru C jest nieznajomość C++ to polecam te tutoriale:

https://www.youtube.com/watch?v=wN0x9eZLix4

https://www.youtube.com/watch?v=GQp1zzTwrIg

Po drugie projekt ma być także na małe AVR'y (np. Arduino Uno), które mają bardzo mało zasobów (ATMega328: 2KB RAM i 32 Flash). Z tego powodu nie powinna zawierać za dużej ilości specjalizowanego kodu,aa w zamian powinna być łatwo rozszerzalna o nowe opcje i hardware. Wymaga to bardzo dobrego zaimplementowania szkieletu biblioteki - według mnie jako zestawu klas abstrakcyjnych . Po drugie już w drugim poście tego wątku była mowa o C++ (*.cpp) i nikt wtedy nie protestował.

Pozdrawiam

Link do komentarza
Share on other sites

@FlyingDutch Zawsze można napisać obiektowo w C, używanie C++ wcale nie jest do tego niezbędne 🙂 Masz rację, że konieczne jest zebranie założeń, a później wybranie rozwiązania, nie odwrotnie. Inaczej nic z tego nie wyjdzie - chociaż jeśli mam być szczery, to uważam że właśnie tak się ten projekt skończy.

Edytowano przez Elvis
  • Lubię! 2
Link do komentarza
Share on other sites

22 minuty temu, _LM_ napisał:

To że dyskusja jest taka żywa świadczy o tym że taka biblioteka jest potrzebna. Być może już na tym etapie przydałoby się zbudować coś co można zaprezentować tak aby każdy mógł wyrazić swoją opinię i wskazać miejsca gdzie należy coś poprawić. 

Według mnie jest  wręcz przeciwnie: taka biblioteka nie jest nikomu potrzebna. Gdyby była potrzebna to takie firmy jak "Seed Studio", czy "SparkFun" już dawno by ją napisały (jak wiele innych bardzo przydatnych bibliotek do Arduino). Dlaczego to nie ma sensu: ponieważ,  aby biblioteka była tak uniwersalna jak w  to zakładacie wyjdzie za duża dla np. Arduino Uno, czy Nano. Według mnie lepiej za pomocą OPP tak zaprojektować główny szkielet, aby korzystając z dziedziczenia można ją było łatwo rozszerzać o wymagane funkcje.

Pozdrawiam

  • Lubię! 1
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

19 minut temu, FlyingDutch napisał:

Po drugie już w drugim poście tego wątku była mowa o C++ (*.cpp) i nikt wtedy nie protestował.

A co było w trzecim poście? 

20 minut temu, FlyingDutch napisał:

OOP i  C++ zapewnia dużo większą warstwę abstrakcji niż C i uważam, że projekt biblioteki powinien być napisany z użyciem OOP. Jeśli powodem wyboru C jest nieznajomość C++ to polecam te tutoriale

To prawda C++ daje ogromne możliwości ale, raczej należy podejść do tego języka z pokorą:

Bjarne Stroustrup: "C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, you blow your whole leg off."

Anonim z poczuciem humoru: "If C gives you enough rope to hang yourself, C++ gives you enough rope to bind and gag your neighborhood, rig the sails on a small ship, and still have enough rope left over to hang yourself from the yardarm."

Z drugiej strony może to być C jak i może być C++. Tak jak napisał @Elvis.

26 minut temu, Elvis napisał:

@FlyingDutch Zawsze można napisać obiektowo w C, używanie C++ wcale nie jest do tego niezbędne

Zgadzam się natomiast, że założenie, aby chodziło to na AVRach jest ambitne. 

21 minut temu, FlyingDutch napisał:

Gdyby była potrzebna to takie firmy jak "Seed Studio", czy "SparkFun" już dawno by ją napisały

A to nie jest czasem tak, że oni piszą coś pod konkretny sprzęt, żeby to łatwiej sprzedać... Tu założenie jednak jest inne, bo właśnie ma być bardziej abstrakcyjne... 

28 minut temu, Elvis napisał:

Inaczej nic z tego nie wyjdzie - chociaż jeśli mam być szczery, to uważam że właśnie tak się ten projekt skończy

A ja tam trzymam kciuki za kolegę. 

Link do komentarza
Share on other sites

21 minut temu, pmochocki napisał:

A to nie jest czasem tak, że oni piszą coś pod konkretny sprzęt, żeby to łatwiej sprzedać... Tu założenie jednak jest inne, bo właśnie ma być bardziej abstrakcyjne...

jak by była potrzebna taka biblioteka to dodawaliby ją do wyświetlaczy LCD 😁

Cytat

To prawda C++ daje ogromne możliwości ale, raczej należy podejść do tego języka z pokorą:

Cyli co ? Nie używać? To dlaczego core (dla board Arduino) - najważniejsze biblioteki są napisane w C++ (z wykorzystaniem OOP). Nie wiem, używam C++ od ponad dwudziestu lat (zarówno na dużych stacjach roboczych - jak np. świętej pamięci SUN i CPU Ultra-SPARC,  jak i na mikro-kontrolerach AVR) i nie wiem o jakie pokorze mówisz. Jeśli się wie co się robi to da się używać C++ nawet na małych mikro-kontrolerach.

BTW: życzę jak najszybszego określenia zakresu funkcjonalności wspomnianej biblioteki (i reszty założeń), tak aby dyskusja w tym wątku zaczęła być merytoryczna.

Pozdrawiam

  • Lubię! 1
Link do komentarza
Share on other sites

2 minuty temu, FlyingDutch napisał:

Cyli co ? Nie używać? To dlaczego core (dla board Arduino) - najważniejsze biblioteki są napisane w C++ (z wykorzystaniem OOP). Nie wiem, używam C++ od ponad dwudziestu lat (zarówno na dużych stacjach roboczych - jak np. świętej pamięci SUN i CPU Ultra-SPARC,  jak i na mikro-kontrolerach AVR) i nie wiem o jakie pokorze mówisz. Jeśli się wie co się robi to da się używać C++ nawet na małych mikro-kontrolerach.

Jak najbardziej używać. Raczej nie używałbym w ważnym projekcie po dwóch tutorialach na YT. Może źle zrozumiałem Twój wpis, ale brzmiało to jak: nie znasz C++ to żaden problem - oglądasz tutoriala i kodujesz. 

Link do komentarza
Share on other sites

Pytanie techniczne: powołując nowy typ danych, opisany wewnątrz struktury a zawierający elementy o różnych rozmiarach, np:

typedef struct{ char a;
              char * ptr}new_t;

To czy zawsze muszę robić dopełnienia do pełnego rozmiaru? Czyli w tym przypadku, zależnie od architektury wskaznik ptr może mieć rożny rozmiar. 

Link do komentarza
Share on other sites

10 godzin temu, _LM_ napisał:

Pytanie techniczne: powołując nowy typ danych, opisany wewnątrz struktury a zawierający elementy o różnych rozmiarach, np:


typedef struct{ char a;
              char * ptr}new_t;

To czy zawsze muszę robić dopełnienia do pełnego rozmiaru? Czyli w tym przypadku, zależnie od architektury wskaznik ptr może mieć rożny rozmiar. 

Wskaźnik wskaźnikiem, ale długość danych na które on wskazuje może być różna. Możesz użyć funkcji malloc(długość_składnika+1) do przydziału pamięci na składową struktury, lub zamiast char* ptr zastosować tablice char o określonej długości np.:  char skladowa[33];

Pozdrawiam

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Trochę ucichło w temacie ale to nie oznacza że nie pracowałem nad projektem 😉 tj w miarę moich skromnych możliwości w ramach wolnego czasu. A więc postanowiłem zbudować szkielet tej biblioteki w oparciu o drzewa struktur odwołujących się do siebie, oraz wstępną funkcją umożliwiającą poruszanie się po menu. Funkcja główna nie zwraca jednak wskaźnika bezpośredniego(na siebie) ale indeks w tablicy na którym miejscu się znajduje. Zrobiłem tak, ponieważ używanie indeksów zamiast wskaźników bezpośrednich według mnie ułatwia późniejsze dodawanie akcji w zależności od wybranej pozycji - mam tu na myśli wejście w daną pozycję, zasadniczą są dwie akcje: wyświetlenie bieżącego stanu menu lub obsługa funkcji powiązanej z zadaną pozycją. Główny "silnik" menu zwraca kilka możliwych komunikatów które umieściłem w enumie i są to: 

typedef enum{
_ok = 0, // mozna przejsc do nast pozycji
_isNull, // pozycja pusta -> mozliwe wywolanie f
_isRunf, // jesli f uruchomiona
_isEndf, // funkcja zakonczyla dzialanie, wroc do menu
_err	 // blad - zle poelcenie
}_menu_stat_t;

A więc w tej chwili prezentuje się to tak, oto główna funkcja umożliwiająca poruszanie się po menu

_menu_stat_t menu_navigator(menu_t ** p, node_t *nodTab, uint8_t * id,uint8_t * node, _menuNavi_t navi) {

	if (navi >= __naviEnd) { // gdy polecenie nieznane
		return _err;
	}

	static nodeType_t localNode = 0;

if (NULL != (*p)->menuNav[navi]) { // spr czy po nie pusta
		*p = (menu_t*) (*p)->menuNav[navi]; // nowa wartosc wskaznika
		*id = (*p)->id;		// nowe ID
		if ((navi == _parent) || (navi == _child)) {// obliczanie nowej wartosci wezla

			for (uint8_t i = 0; i < MAXNODE; i++) {
				if ((*p)->menuNav[_parent] == nodTab[i].ptrNode) {
					localNode = nodTab[i].nodeID; // jesli znaleziono
					*node = localNode; // zapamietaj wezel
					break;
				}
			 }
		 }
		return _ok;
	}
	return _isNull;
}

Oczywiście analiza tego kodu bez znajomości struktury której on dotyczy nie miałaby sensu, więc

typedef struct menu{
const struct menu * menuNav[__naviEnd];
const idType_t id;//,node;
}menu_t;

typedef struct{
	const menu_t * 		ptrNode;
	const nodeType_t 	nodeID;
}node_t;

teraz funkcja obsługująca menu

void MENUEVENT(void) { // event po kazdej zmianie stanu menu
//static uint8_t refresh = 0;
	if (menuSet) {
		if(menu_Event == display)runCmd(cmd);
		if(menu_Event == runFunct)runFun(cmd);
		cmd = cmdEnd;
		menuSet = 0;
	}
}

i funckje pomocnicze, najpewniej później będą statyczne 

void fnInput(cmd_t cmdIn) {
	menuSet = 1;
	cmd = cmdIn;
}

void runCmd(cmd_t cmd) {
	menuVisible = 1;	//info dla prog glownego ze menu powinno byc wyswietlone
	static menu_t * ptrMenu = (menu_t*)start;


	_menu_stat_t stat = menu_navigator(&ptrMenu, (node_t*)&nodeTab, &id, &node,cmd);

	if(node >= MAXNODE)node = 0;

	if(0 == id && ptrMenu == start){ // nie koniecznie do poprawy
		menuVisible = 0;
		ptrMenu = (menu_t*)start;
		node = 0;

		return;
	}

	if (_ok == stat)menu_execute(id,node,menu.outStream);
	if (_isNull == stat){
	//	menu_Event = runFunct;//wykonanie funkcji
	//!!!!!!!!!! zmiana tylko na czas debugu!!!!!!
		menuVisible = 0;
		ptrMenu = (menu_t*)start;
		node = 0;
	//!!!!!!!!!!!
	}
}

void runFun(cmd_t cmd){ // wywolanie funkcji wykonawczej, od teraz menuEvent wskakuje tutaj

_menu_stat_t stat =	menu_run_fnc(id, node, cmd);

	if((stat == _isNull) ||(stat == _isEndf)){
		menu_Event = display; // przelaczenie na wyswietlanie
		menu_execute(id,node,menu.outStream); // odswierzenie menu
	}
}



_menu_stat_t menu_execute(idType_t id,nodeType_t node, void * streamPtr){

lcd_t * p = streamPtr;
p->flocate(0,0);

p->fstr("N:");
p->fint(node);
//p->fstr((char*)menustrtab[node].nazwa);
//p->fstr("            ");
p->fstr(" ID:");
p->fint(id);
//p->flocate(1,0);
//p->fstr((char*)menustrtab[node].pozycje[id]);
//p->fstr("            ");

	return _ok;
}

Jak widać "menu_execute" nie operuje bezpośrednio na wyświetlaczu, czy innym medium ale przyjmuje wskaźniki na strumień wyjściowy, czyni to bibliotekę bardziej elastyczną, z resztą, gdyby libs był przywiązany do jednego strumienia nie mógłby nazywać się libsem 🙂 

Od strony pętli głównej jest tylko jedna funkcja sprawdzająca status biblioteki i program główny wygląda tak(na STM32 teraz)

  while (1)
  {
static uint8_t swState = idle;
static uint32_t swtick = 0;


if(0 == HAL_GPIO_ReadPin(ENCOSW_GPIO_Port, ENCOSW_Pin) && swState == idle)
{
	swtick = HAL_GetTick()+20;
	swState = debounce;
}

if(0 == HAL_GPIO_ReadPin(ENCOSW_GPIO_Port, ENCOSW_Pin) && swState == debounce)
{
	if(HAL_GetTick() > swtick){
		swState = run;
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
		fnInput(cmdOK);
	}

}else if(HAL_GPIO_ReadPin(ENCOSW_GPIO_Port, ENCOSW_Pin) && swState == debounce)swState = idle;

if(swState == run)swState = swend;
if(1 == HAL_GPIO_ReadPin(ENCOSW_GPIO_Port, ENCOSW_Pin) && swState == swend)swState = idle;

ENCODER_EVENT();
MENUEVENT();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Czyli skacze do "MENU_EVENT" i na tej podstawie, jeśli menu jest aktywne, podejmuje akcję.

Tu jeszcze drzewo struktur na którym operuje biblioteka:

#include"stdlib.h"
#include"user_menu_tab.h"
#include"menu_abstract.h"


const node_t nodeTab[] ={
{.ptrNode = start,.nodeID = 0},
{.ptrNode = m,.nodeID = 1},
{.ptrNode = mk,.nodeID = 2},
{.ptrNode = k,.nodeID = 3},
};


const menu_t start[] ={
{
	.menuNav[_next] = start,
	.menuNav[_prev] = start,
	.menuNav[_parent] = start,
	.menuNav[_child] = m,
	.id = START
},
};

const menu_t m[] ={
{
	.menuNav[_next] = &m[M2],
	.menuNav[_prev] = &m[MEND],
	.menuNav[_parent] = m,
	.menuNav[_child] = NULL,
	.id = M1
},
{
	.menuNav[_next] = &m[M3],
	.menuNav[_prev] = &m[M1],
	.menuNav[_parent] = m,
	.menuNav[_child] = mk,
	.id = M2
},
{
	.menuNav[_next] = &m[MEND],
	.menuNav[_prev] = &m[M2],
	.menuNav[_parent] = m,
	.menuNav[_child] = k,
	.id = M3
},
{
	.menuNav[_next] = &m[M1],
	.menuNav[_prev] = &m[M3],
	.menuNav[_parent] = m,
	.menuNav[_child] = &start[START],
	.id = MEND
},
};

const menu_t mk[] ={
{
	.menuNav[_next] = &mk[MK2],
	.menuNav[_prev] = &mk[MKEND],
	.menuNav[_parent] = mk,
	.menuNav[_child] = NULL,
	.id = MK1
},
{
	.menuNav[_next] = &mk[MK3],
	.menuNav[_prev] = &mk[MK1],
	.menuNav[_parent] = mk,
	.menuNav[_child] = NULL,
	.id = MK2
},
{
	.menuNav[_next] = &mk[MKEND],
	.menuNav[_prev] = &mk[MK2],
	.menuNav[_parent] = mk,
	.menuNav[_child] = NULL,
	.id = MK3
},
{
	.menuNav[_next] = &mk[MK1],
	.menuNav[_prev] = &mk[MK3],
	.menuNav[_parent] = mk,
	.menuNav[_child] = &m[M2],
	.id = MKEND
},
};

const menu_t k[] ={
{
	.menuNav[_next] = &k[K2],
	.menuNav[_prev] = &k[KEND],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K1
},
{
	.menuNav[_next] = &k[K3],
	.menuNav[_prev] = &k[K1],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K2
},
{
	.menuNav[_next] = &k[K4],
	.menuNav[_prev] = &k[K2],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K3
},
{
	.menuNav[_next] = &k[K5],
	.menuNav[_prev] = &k[K3],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K4
},
{
	.menuNav[_next] = &k[K6],
	.menuNav[_prev] = &k[K4],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K5
},
{
	.menuNav[_next] = &k[KEND],
	.menuNav[_prev] = &k[K5],
	.menuNav[_parent] = k,
	.menuNav[_child] = NULL,
	.id = K6
},
{
	.menuNav[_next] = &k[K1],
	.menuNav[_prev] = &k[K6],
	.menuNav[_parent] = k,
	.menuNav[_child] = &m[M3],
	.id = KEND
},
};
#ifndef MNEULIB_USER_MENU_TAB_H_
#define MNEULIB_USER_MENU_TAB_H_

#include "menu_abstract.h"




enum start{START};
enum m{M1,M2,M3,MEND};
enum mk{MK1,MK2,MK3,MKEND};
enum k{K1,K2,K3,K4,K5,K6,KEND};


extern const menu_t start[];
extern const menu_t m[];
extern const menu_t mk[];
extern const menu_t k[];


extern const node_t nodeTab[MAXNODE]; // tab ptr
#endif /* MNEULIB_USER_MENU_TAB_H_ */

Wygenerowane za pomocą aplikacji którą piszę do tego celu 🙂 

java_sUPomrLpa1.thumb.png.9ec27dd47714bdb02e164e0aa3f4b457.png

Jest jeszcze sporo rzeczy do zrobienia, takich jak bezpośredni dostęp do pozycji za pomocą klawiszy funkcyjnych, chciałbym mieć też możliwość blokowania niektórych pozycji za pomocą prostego hasła. 

Pozdrawiam 🙂

Edytowano przez _LM_
  • Lubię! 2
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.