Menu met meerdere niveaus voor Arduino en meer. Beveiligingsinformatieportaal

. ‘Maar wacht,’ dacht ik. “Dit menu heb ik zes jaar geleden geschreven!”

In 2009 schreef ik het eerste project gebaseerd op een microcontroller en display genaamd “Automatic Lighting Control”, waarvoor het nodig was om een ​​menushell te maken die in duizend configuraties zou passen, of zelfs meer. Het project werd met succes geboren, gecompileerd en kan nog steeds werken, en de menuOS-shell begon van project naar project te dwalen, met behulp van beste praktijken Foutgeoriënteerd programmeren. ‘Houd op met dit verdragen,’ zei ik en herschreef de code.

Onder de motorkap vind je oude code van geselecteerde kwaliteit, een verhaal over hoe ik het heb herschreven, evenals instructies voor degenen die het willen gebruiken.

Vereisten en mogelijkheden voor het besturingssysteemmenu

Laten we eerst de vereisten definiëren die we aan het menu stellen:
  1. gebruiksgemak, knoppen links-rechts, omhoog-omlaag, achteruit-vooruit.;
  2. boomstructuur met voldoende diepte (tot 256);
  3. het totale aantal menu-items, wat genoeg is voor iedereen (10^616);
  4. instellingen bewerken;
  5. het lanceren van programma's.
  6. een eenvoudige ingebouwde taakbeheerder.
En het is ook noodzakelijk dat dit alles zo min mogelijk weegt, pretentieloos is voor de middelen en op elk platform draait (momenteel beschikbaar voor AVR, werkt met GLCD en tekst-LCD).
Theoretisch kan dit OS-menu met de juiste stuurprogramma's eenvoudig worden overgenomen en aangesloten op de RTOS.

Bestandsstructuur

Als voorbeeld analyseren we de volgende menustructuur (itemnummer aan de linkerkant):
0 Hoofdmap/ 1 - Map 1/ - map met bestanden 3 -- Programma 1 4 -- Programma 2 5 -- Map 3/ - map met meerdere exemplaren van het programma. De cursorpositie is de startparameter 6 --- Programma 3.1 6 --- Programma 3.2 6 --- Programma 3.3 6 --- xxxxxxx 6 --- Programma 3.64 2 - Map 2/ - map met configuraties 7 -- Booleaanse configuratie 1 8 -- Numerieke configuratie 2 9 -- Numerieke configuratie 3 10 -- Datum/tijd programmeren
Het belangrijkste principe van het OS-menu is: "Alles is een bestand." Het zij zo.
Elk bestand heeft een type, naam, bovenliggende map en andere parameters
Laten we het beschrijven met de structuur:
struct filedata( uint8_t type; uint8_t parent; uint8_t mode1;//parameter 1 uint8_t mode2;//parameter 2 char-naam; );
Voor elk bestand definiëren we 4 bytes in de fileData-array:
  1. type,
  2. ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis
  3. mode1, twee parameters die specifiek zijn voor elk bestandstype
  4. modus2
typ == T_FOLDER
Het hoofdbestand is een map. Hiermee kunt u een boomstructuur van het gehele menu maken.
Het belangrijkste hier is hoofdmap genummerd nul. Wat er ook gebeurt, we zullen er uiteindelijk naar terugkeren.
De mapopties zijn
mode1 = startnummer van het onderliggende bestand, mode2 = aantal bestanden daarin.
In de hoofdmap 0 bevinden zich bestanden 1 en 2, 2 in totaal.
Laten we het als volgt beschrijven:
T_MAP, 0, 1, 2,
type == T_DFOLDER
Map 3 bevat verschillende exemplaren van hetzelfde programma, maar met verschillende startsleutels.
Bij de automatische lichtregeling is het bijvoorbeeld mogelijk om maximaal 64 dagprogramma's in te stellen, met elk 16 intervallen. Als u elk item beschrijft, heeft u 1024 bestanden nodig. In de praktijk zijn twee voldoende. En we zullen de broodkruimels aan het programma toevoegen in de vorm van parameters.
mode1 = nummer van het onderliggende bestand, waarvan kopieën worden gemaakt mode2 = aantal kopieën van het bestand.
Eenvoudige wiskunde leert ons dat als alle 256 bestanden dat zijn dynamische mappen Met maximaal aantal kopieën, zal het totale aantal menu-items in het systeem 256^256 = 3,2 x 10^616 zijn. Dit is PRECIES genoeg voor elk redelijk of niet zo goed geval.
type == T_APP
Sollicitatie. Zijn taak is om te registreren in Taakbeheer (ingebouwd of extern), de controle over de knoppen te onderscheppen en te bewerken.
mode1 = id van de applicatie die wordt gestart.
type == T_CONF
Het configuratiebestand waarvoor alle ophef is begonnen. Hiermee kunt u Booleaanse of instellen numerieke waarde elke parameter. Werkt met int16_t.
mode1 = configuratie-ID
De configuratie heeft zijn eigen configsLimit-array, waarbij voor elke configuratie drie int16_t-configuratienummers zijn:
  1. Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.
  2. Minimaal - minimale waarde gegevens
  3. Maximaal - maximale waarde gegevens.
In cel 2 kunt u bijvoorbeeld een getal van -100 tot 150 schrijven, waarna de regel er als volgt uitziet:
2, -100, 150,
type == S_CONF
Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDER
mode1 = configuratie-ID
type == T_SFOLDER
Een speciaal type map wordt dichter bij de configuratie geplaatst, omdat dit een van de varianten is.
Stel je voor, jouw systeem heeft de mogelijkheid om via RS-485 te werken protocollen A, B of C. Plaats een aantal bestanden zoals S_CONF in een map en selecteer daaruit het gewenste bestand. Bovendien zal de cursor, wanneer we de map opnieuw openen, de actieve optie markeren.
mode1 en mode2 zijn vergelijkbaar voor T_FOLDER. Onderliggende bestanden zijn alleen T_SCONF

Resultaten refactoren

Ik heb mezelf niet de taak gesteld om de architectuur te herzien; op veel plaatsen heb ik zelfs de logica van het werk gelaten zoals het is. Er zijn een paar hele grappige krukken.
De belangrijkste taak is om het systeem opnieuw te ontwerpen, zodat het gebruik ervan in nieuwe projecten eenvoudig is. Als resultaat:
  1. Gescheiden werken met hardware in minimaal aparte functies apart bestand. HWI omvat:
  2. Modules voor lessen zijn herschreven. Verborgen in privé, is al het mogelijke verenigd verschijning, Een truc met klassen en min of meer uniforme interface het zal later van pas komen.
  3. “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.
  4. Ik heb simpelweg de code opgeschoond, duidelijker gemaakt, magische getallen verwijderd en de interface verbeterd. Nu is het geen schande om het te laten zien.
Ik was te lui om de klokinstellingenmodule voor hwi te herschrijven. Het moet nog geheel vernieuwd worden. Hij is verschrikkelijk.
Hoe de refactoring plaatsvond, is duidelijk te zien in de repository.

Het creëren van uw project

De projectconfiguratie omvat de volgende items:
Bestanden maken
Laten we arrays maken met behulp van de eerder besproken structuur
//structure array static const uint8_t fileStruct PROGMEM = ( T_FOLDER, 0, 1, 2, //0 T_FOLDER, 0, 3, 3, //1 T_FOLDER, 0, 7, 4, //2 T_APP, 1, 1, 0, //3 T_APP, 1, 2, 0, //4 T_DFOLDER, 1, 6, 66, //5 T_APP, 5, 2, 0, //6 T_CONF, 2, 0, 0, //7 T_CONF , 2, 1, 0, //8 T_CONF, 2, 2, 0, //9 T_APP, 2, 3, 0 //10 ); //Array met namen statisch PROGMEM const char file_0 = "Root"; statische PROGMEM const char file_1 = "Map 1"; static PROGMEM const char file_2 = "Map 2"; static PROGMEM const char file_3 = "App 1"; static PROGMEM const char file_4 = "App 2"; static PROGMEM const char file_5 = "Dyn-map"; static PROGMEM const char file_6 = "App"; statische PROGMEM const char file_7 = "config 0"; statische PROGMEM const char file_8 = "config 1"; statische PROGMEM const char file_9 = "config 2"; static PROGMEM const char file_10 = "Datum en tijd"; PROGMEM statische const char *fileNames = ( file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9, file_10 );
Laten we een array voor configuraties maken:
//aantal cellen (stap voor 2), minimale waarde, maximale waarde statische const PROGMEM int16_t configsLimit = ( 0,0,0,// config 0: 0 + 0 geeft een booleaanse configuratie 2,-8099,8096,/ /config 1 4,1,48,//config 2);
Aanpassing van knoppen
Het liefst sluit ik knoppen met een aardaansluiting en een pull-up-weerstand aan op de voeding, die altijd aanwezig is in de MK.

In het bestand hw/hwdef.h geven we de namen van de registers en de locatie van de knoppen aan:
#define BUTTONSDDR DDRB #define BUTTONSPORT PORTB #define BUTTONSPIN PINB #define BUTTONSMASK 0x1F #define BSLOTS 5 /**Knopmasker*/ enum( BUTTONRETURN = 0x01, BUTTONLEFT = 0x02, BUTTONRIGHT = 0x10, BUTTONUP = 0x08, NDOWN = 0x04 );

Weergave-instellingen
Nu sleept het project de GLCDv3-bibliotheek mee, wat niet goed is. Historisch gezien is dit hoe het gebeurde.
Link naar google-code - https://code.google.com/p/glcd-arduino
Een applicatie maken
Laten we eens kijken naar een voorbeeldtoepassing die gebruikmaakt van basisfuncties menu.
menuos/app/sampleapp.cpp

Laten we een klasse maken met volgende structuur:
#ifndef __SAMPLEAPP_H__ #define __SAMPLEAPP_H__ #include "hw/hwi.h" #include "menuos/MTask.h" #include "menuos/buttons.h" class sampleapp ( //variabelen openbaar: uint8_t Setup(uint8_t argc, uint8_t *argv );//start de applicatie als parameters - huidige niveau en array broodkruimels uint8_t ButtonsLogic(uint8_t-knop);//knophandler uint8_t TaskLogic(void);//timerhandler beveiligd: privé: uint8_t tick; void Return();//terug naar het hoofdmenu //functions public: sampleapp();~voorbeeldapp(); beveiligd: privé: ); //sampleapp extern sampleapp SampleApp; //Sishnye
krukken
wrappers voor de knophandler en -manager void SampleAppButtonsHandler(uint8_t button); ongeldig SampleAppTaskHandler(); #endif //__SAMPLEAPP_H__ En laten we de belangrijkste functies schetsen: uint8_t sampleapp::Setup(uint8_t argc, uint8_t *argv) ( tick = 0; //registreer onszelf in

systeemmodules
Buttons.Add(SampleAppButtonsHandler);//knophandler toevoegen Task.Add(1, SampleAppTaskHandler, 1000);//taak toevoegen ha GLCD.ClearScreen();//maak het scherm leeg //en schrijf GLCD.CursorTo( (HwDispGetStringsLength( )-11)/2, HwDispGetStringsNumb()/2);

GLCD.Puts("Hallo Habr");
retour 0; )
Wikkeltjes:
void SampleAppButtonsHandler(uint8_t-knop)( SampleApp.ButtonsLogic(knop); ) void SampleAppTaskHandler())( SampleApp.TaskLogic(); )
Knophandler:
void MMenu::AppStart(void)( if (file.mode2 != BACKGROUND)( Task.Add(MENUSLOT, MenuAppStop, 10);//100 ms update Task.ActiveApp = 1;//app moet ActiveApp op nul zetten ) switch (file.mode1)(//AppNumber case 2: SampleApp.Setup(niveau, brCrumbs); break; case 3: Clock.Setup(level, brCrumbs); break; standaard: Task.ActiveApp = 0; break; ) )

U kunt helpen en geld overmaken voor de ontwikkeling van de site

/. “Maar wacht even,” dacht ik, “ik heb dit menu zes jaar geleden geschreven!”
In 2009 schreef ik het eerste project gebaseerd op een microcontroller en display genaamd “Automatic Lighting Control”, waarvoor het nodig was om een ​​menushell te maken die in duizend configuraties zou passen, of zelfs meer. Het project werd met succes geboren, gecompileerd en kan nog steeds werken, en de OS-menushell begon van project naar project te dwalen, met behulp van best practices. ‘Houd op met dit verdragen,’ zei ik en herschreef de code.
Onder de motorkap vind je oude code van geselecteerde kwaliteit, een verhaal over hoe ik het heb herschreven, evenals instructies voor degenen die het willen gebruiken.

Vereisten en mogelijkheden voor het besturingssysteemmenu

Laten we eerst de vereisten definiëren die we aan het menu stellen:
  • gebruiksgemak, knoppen links-rechts, omhoog-omlaag, achteruit-vooruit.;

  • boomstructuur met voldoende diepte (tot 256);

  • het totale aantal menu-items, wat genoeg is voor iedereen (10^616);

  • instellingen bewerken;

  • het lanceren van programma's.

  • een eenvoudige ingebouwde taakbeheerder.
  • En het is ook noodzakelijk dat dit alles zo min mogelijk weegt, pretentieloos is voor de middelen en op elk platform draait (momenteel beschikbaar voor AVR, werkt met GLCD en tekst-LCD).
    Theoretisch kan dit OS-menu met de juiste stuurprogramma's eenvoudig worden overgenomen en aangesloten op de RTOS.

    Bestandsstructuur

    Als voorbeeld analyseren we de volgende menustructuur (itemnummer aan de linkerkant):
    0 Hoofdmap/ 1 - Map 1/ - map met bestanden 3 -- Programma 1 4 -- Programma 2 5 -- Map 3/ - map met meerdere exemplaren van het programma. De cursorpositie zal een startparameter zijn 6 --- Programma 3.1 6 --- Programma 3.2 6 --- Programma 3.3 6 --- xxxxxxx 6 --- Programma 3.64 2 - Map 2/ - map met configuraties 7 -- Boolean configuratie 1 8 -- Numerieke configuratie 2 9 -- Numerieke configuratie 3 10 -- Programmadatum/-tijd
    Het belangrijkste principe van het OS-menu is: "Alles is een bestand." Het zij zo.
    Elk bestand heeft een type, naam, bovenliggende map en andere parameters
    Laten we het beschrijven met de structuur:
    struct filedata( uint8_t type; uint8_t parent; uint8_t mode1;//parameter 1 uint8_t mode2;//parameter 2 char-naam; );
    Voor elk bestand definiëren we 4 bytes in de filedata-array:

    1. type,

    2. ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis

    3. mode1, twee parameters die specifiek zijn voor elk bestandstype

    4. modus2

    typ == T_FOLDER
    Het hoofdbestand is een map. Hiermee kunt u een boomstructuur van het gehele menu maken.
    De belangrijkste hier is de hoofdmap met nummer nul. Wat er ook gebeurt, we zullen er uiteindelijk naar terugkeren.
    De mapopties zijn
    mode1 = startnummer van het onderliggende bestand, mode2 = aantal bestanden daarin.
    In de hoofdmap 0 bevinden zich bestanden 1 en 2, 2 in totaal.
    Laten we het als volgt beschrijven:
    T_MAP, 0, 1, 2,
    type == T_DFOLDER
    Map 3 bevat verschillende exemplaren van hetzelfde programma, maar met verschillende startsleutels.
    Bij de automatische lichtregeling is het bijvoorbeeld mogelijk om maximaal 64 dagprogramma's in te stellen, met elk 16 intervallen. Als u elk item beschrijft, heeft u 1024 bestanden nodig. In de praktijk zijn twee voldoende. En we zullen de broodkruimels in de vorm van parameters aan het programma toevoegen.
    mode1 = nummer van het onderliggende bestand, waarvan kopieën worden gemaakt mode2 = aantal kopieën van het bestand.
    Een eenvoudige rekensom leert ons dat als alle 256 bestanden dynamische mappen zijn met een maximaal aantal kopieën, het totale aantal menu-items in het systeem 256^256 = 3,2 x 10^616 zal zijn. Dit is PRECIES genoeg voor elk redelijk of niet zo goed geval.
    type == T_APP
    Sollicitatie. Zijn taak is om te registreren in Taakbeheer (ingebouwd of extern), de controle over de knoppen te onderscheppen en te bewerken.
    mode1 = id van de applicatie die wordt gestart.
    type == T_CONF
    Het configuratiebestand waarvoor alle ophef is begonnen. Hiermee kunt u een Booleaanse of numerieke waarde voor een parameter instellen. Werkt met int16_t.
    mode1 = configuratie-ID
    De configuratie heeft zijn eigen configsLimit-array, waarbij voor elke configuratie drie int16_t-configuratienummers zijn:

    1. Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.

    2. Minimum - minimale gegevenswaarde

    3. Maximaal - maximale gegevenswaarde.

    In cel 2 kunt u bijvoorbeeld een getal van -100 tot 150 schrijven, waarna de regel er als volgt uitziet:
    2, -100, 150,
    type == S_CONF
    Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDER
    mode1 = configuratie-ID
    type == T_SFOLDER
    Een speciaal type map wordt dichter bij de configuratie geplaatst, omdat dit een van de varianten is.
    Stel u voor dat uw systeem via RS-485 kan werken met de protocollen A, B of C. We plaatsen een aantal bestanden van het type S_CONF in een map en selecteren daaruit het gewenste bestand. Bovendien zal de cursor, wanneer we de map opnieuw openen, de actieve optie markeren.
    mode1 en mode2 zijn vergelijkbaar voor T_FOLDER. Onderliggende bestanden zijn alleen T_SCONF

    Resultaten refactoren

    Ik heb mezelf niet de taak gesteld om de architectuur te herzien; op veel plaatsen heb ik zelfs de logica van het werk gelaten zoals het is. Er zijn een paar hele grappige krukken.
    De belangrijkste taak is om het systeem opnieuw te ontwerpen, zodat het gebruik ervan in nieuwe projecten eenvoudig is. Als resultaat:

    1. Ik heb het werk met de hardware opgedeeld in ten minste afzonderlijke functies in een afzonderlijk bestand. HWI omvat:

    2. Modules voor lessen zijn herschreven. Al het mogelijke is privé verborgen, het uiterlijk is uniform. Een truc met klassen en een min of meer uniforme interface zal later van pas komen.

    3. “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.

    4. Ik heb simpelweg de code opgeschoond, duidelijker gemaakt, magische getallen verwijderd en de interface verbeterd. Nu is het geen schande om het te laten zien.

    Ik was te lui om de klokinstellingenmodule voor hwi te herschrijven. Het moet nog geheel vernieuwd worden. Hij is verschrikkelijk.
    Hoe de refactoring plaatsvond, is duidelijk te zien in de repository.

    Het creëren van uw project

    De projectconfiguratie omvat de volgende items:
    Bestanden maken
    Laten we arrays maken met behulp van de eerder besproken structuur
    //structure array static const uint8_t fileStruct PROGMEM = ( T_FOLDER, 0, 1, 2, //0 T_FOLDER, 0, 3, 3, //1 T_FOLDER, 0, 7, 4, //2 T_APP, 1, 1, 0, //3 T_APP, 1, 2, 0, //4 T_DFOLDER, 1, 6, 66, //5 T_APP, 5, 2, 0, //6 T_CONF, 2, 0, 0, //7 T_CONF , 2, 1, 0, //8 T_CONF, 2, 2, 0, //9 T_APP, 2, 3, 0 //10 ); //Array met namen statisch PROGMEM const char file_0 = "Root"; statische PROGMEM const char file_1 = "Map 1"; static PROGMEM const char file_2 = "Map 2"; static PROGMEM const char file_3 = "App 1"; static PROGMEM const char file_4 = "App 2"; static PROGMEM const char file_5 = "Dyn-map"; static PROGMEM const char file_6 = "App"; statische PROGMEM const char file_7 = "config 0"; statische PROGMEM const char file_8 = "config 1"; statische PROGMEM const char file_9 = "config 2"; static PROGMEM const char file_10 = "Datum en tijd"; PROGMEM statische const char *fileNames = ( file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9, file_10 );
    Laten we een array voor configuraties maken:
    //aantal cellen (stap voor 2), minimale waarde, maximale waarde statische const PROGMEM int16_t configsLimit = ( 0,0,0,// config 0: 0 + 0 geeft een booleaanse configuratie 2,-8099,8096,/ /config 1 4,1,48,//config 2);
    Aanpassing van knoppen
    Het liefst sluit ik knoppen met een aardaansluiting en een pull-up weerstand aan op de voeding, die is altijd aanwezig in de MK.

    In het bestand hw/hwdef.h geven we de namen van de registers en de locatie van de knoppen aan:
    #define BUTTONSDDR DDRB #define BUTTONSPORT PORTB #define BUTTONSPIN PINB #define BUTTONSMASK 0x1F #define BSLOTS 5 /**Knopmasker*/ enum( BUTTONRETURN = 0x01, BUTTONLEFT = 0x02, BUTTONRIGHT = 0x10, BUTTONUP = 0x08, NDOWN = 0x04 );

    Weergave-instellingen
    Nu sleept het project de GLCDv3-bibliotheek mee, wat niet goed is. Historisch gezien is dit hoe het gebeurde.
    Link naar Google-code -
    Een applicatie maken
    Laten we eens kijken naar een voorbeeldtoepassing die basismenufuncties gebruikt.
    menuos/app/sampleapp.cpp

    Laten we een klasse maken met de volgende structuur:
    #ifndef __SAMPLEAPP_H__ #define __SAMPLEAPP_H__ #include "hw/hwi.h" #include "menuos/MTask.h" #include "menuos/buttons.h" class sampleapp ( //variabelen openbaar: uint8_t Setup(uint8_t argc, uint8_t *argv );//start de applicatieparameters - het huidige niveau en een reeks broodkruimels uint8_t ButtonsLogic(uint8_t button);//button handler uint8_t TaskLogic(void);//timer handler protected: private: uint8_t tick; ); //terug naar het hoofdmenu //functies openbaar: sampleapp(); ~sampleapp(); //sampleapp extern sampleapp SampleApp; //City [s]wrappers voor de knophandler en coördinator void SampleAppButtonsHandler(uint8_t button); ongeldig SampleAppTaskHandler(); #endif //__SAMPLEAPP_H__
    En laten we de belangrijkste functies schetsen:
    uint8_t sampleapp::Setup(uint8_t argc, uint8_t *argv) ( tick = 0; //registreer onszelf in de systeemmodules Buttons.Add(SampleAppButtonsHandler);//add button handler Task.Add(1, SampleAppTaskHandler, 1000);/ / taak toevoegen ha GLCD.ClearScreen();//maak het scherm leeg //en schrijf op de meest zichtbare plaats GLCD.CursorTo((HwDispGetStringsLength()-11)/2, HwDispGetStringsNumb()/2); )

    systeemmodules
    Buttons.Add(SampleAppButtonsHandler);//knophandler toevoegen Task.Add(1, SampleAppTaskHandler, 1000);//taak toevoegen ha GLCD.ClearScreen();//maak het scherm leeg //en schrijf GLCD.CursorTo( (HwDispGetStringsLength( )-11)/2, HwDispGetStringsNumb()/2);

    GLCD.Puts("Hallo Habr");
    retour 0; )
    En een functie die elke seconde wordt aangeroepen:
    void SampleAppButtonsHandler(uint8_t-knop)( SampleApp.ButtonsLogic(knop); ) void SampleAppTaskHandler())( SampleApp.TaskLogic(); )
    Nu schrijven we in menu.cpp dat ons programma op nummer 2 zal worden aangeroepen:
    void MMenu::AppStart(void)( if (file.mode2 != BACKGROUND)( Task.Add(MENUSLOT, MenuAppStop, 10);//100 ms update Task.ActiveApp = 1;//app moet ActiveApp op nul zetten ) switch (file.mode1)(//AppNumber case 2: SampleApp.Setup(niveau, brCrumbs); break; case 3: Clock.Setup(level, brCrumbs); break; standaard: Task.ActiveApp = 0; break; ) )
    Laten we het project samenstellen en kijken wat we hebben:

    Hetzelfde geldt voor visuele leerlingen.

    Gedetailleerde en enigszins saaie instructies voor bestandsstructuur en architectuur, evenals een voorbeeld van werk in videomateriaal.

    Links en opslagplaatsen

    Het project is gecompileerd in de Atmel Studio-programmeeromgeving, maar er zal een dag komen dat het in Eclipse zal worden opgenomen. Huidige versie project is beschikbaar in elke repository (Reservering).
  • Opslagplaats op GitHub:
  • In de buurt van de rector 22 januari 2011 om 21:51 uur

    Menu voor Arduino

    • Kast *

    Ik heb altijd van microcontrollers en computers gehouden. Met een computer zou het makkelijker zijn als er geld was, maar met microcontrollers is het iets moeilijker. Er zijn veel modellen die wild zijn, en ze zijn hier ook gratis te koop. voor een lange tijd nooit ontmoet. Eerder waren er pogingen om ermee te leren werken, maar op de een of andere manier lukte het niet. En ATtiny12L ging naar de verre hoek. Ik kwam nieuws tegen over bouwpakketten op basis van microcontrollers, maar in die tijd was het onmogelijk om te bestellen en het was duur. Ergens trok een Arduino mijn aandacht en ik was erin geïnteresseerd. Na een beetje nadenken besloot ik dit wonder en een schild ervoor te bestellen in de vorm van een LCD-scherm met knoppen, omdat knipperende LED's saai waren en ik te lui was om zelf iets te doen.

    En nu ik Arduno in mijn handen heb, is de software er allemaal, in één woord: creëren en creëren. Het idee was om iets vergelijkbaars te maken met een boordcomputer, kilometerstand, spanning, buitentemperatuur. In eerste instantie totaal- en dagkilometers, maar waar zitten de knoppen? Dus laten we spanning en temperatuur toevoegen.
    In totaal krijgen we:

    • Arduino + LCD-toetsenbordschild (5 knoppen, 2 regels van elk 16 tekens);
    • weergave van totaal- en dagkilometers;
    • spanning in het boordnetwerk;
    • temperatuur buiten.
    Ik had geen ervaring met het schrijven van schetsen, dus begon ik uit te zoeken hoe ik tekst op het scherm kon weergeven en hoe knoppen werkten. Het scherm heeft zijn eigen standaard bibliotheek bij wie ik vond gemeenschappelijke taal. Voor de knoppen wordt één analoge lijn gebruikt en afhankelijk van de spanning op de betreffende pin kan worden bepaald welke knop wordt ingedrukt.
    Het lijkt erop dat alle moeilijkheden daar hadden moeten eindigen, schrijf aan jezelf en schrijf. Maar niet alles is zo eenvoudig als het lijkt. Bij het weergeven van een bepaalde parameter voeren de knoppen niet dezelfde functies uit. Wat is daar zo ingewikkeld aan? Als dan zo of zo. Ja, als er twee parameters zijn en ook twee knoppen, maar het blijkt een puinhoop te zijn; ik zwijg over het algemeen over het vinden van fouten en toevoegingen. Over het algemeen is dit niet de optie die ik nodig had.
    Ik herinner me dat ik op de universiteit een lezing leuk vond eindige toestandsmachines, in het bijzonder waar we het over hebben over de Mili-machine. Ik zal niet ingaan op de wiskundige details van zijn werk; ik las over hem op Wikipedia, maar begreep er niets van. Ondanks dit ga ik door. We hebben N-statussen, M-knoppen en K-functies om uit te voeren.
    Ingangssignalen
    We hebben 5 knoppen (rechts, omhoog, omlaag, links, selecteren), dit zijn ingangssignalen. Elke knop kan worden weergegeven door één of drie gebeurtenissen (ingedrukt, ingedrukt gehouden, losgelaten). Je kunt ook een gebeurtenis toevoegen die langer dan X seconden wordt vastgehouden, er is niets ingewikkelds aan. Sterker nog, ik gebruik alleen het loslaten van knoppen om te voorkomen dat functies meerdere keren worden herhaald terwijl ik een toets ingedrukt houd. Ik zal 5 verschillende ingangssignalen gebruiken.
    Machinestatussen
    Er zijn er niet veel meer dan het mij op het eerste gezicht leek. Naast het weergeven van de belangrijkste waarden (kilometerstand, temperatuur, spanning, tijd, datum), voegt het ook een reset van de dagelijkse kilometerstand toe, waarbij de tijd en datum worden ingesteld. Er waren in totaal twaalf staten.
    Functies. Uitgangssignalen
    Elk uitgangssignaal komt overeen met een oproep naar een bepaalde functie; als u alleen de status hoeft te wijzigen zonder een functie uit te voeren, dan is dit voor deze gevallen voorzien lege functie. Alle andere functies voeren de noodzakelijke acties uit.
    Miles machine tafel
    De eerste en tweede regel geven de status van de machine weer: respectievelijk de weergegeven parameter en de statuscode. De eerste en tweede kolom zijn de sleutel en zijn code (invoersignaal).
    Op de kruising van het ingangssignaal en huidige staat de nieuwe status en de functie die wordt uitgevoerd, worden gescheiden door komma's geschreven.

    Hoe het te gebruiken. MenuState slaat de huidige status op, laten we zeggen dat deze gelijk is aan 1, de dagelijkse kilometerstand wordt op het scherm weergegeven. We drukken op Select om de meetwaarden te resetten, het ingangssignaal is 4. We zoeken naar het snijpunt van het ingangssignaal 4 en de huidige status 1, wat ons als resultaat status 6 en de functie ff oplevert. De ff-functie doet niets. In status 6 toont het scherm de bevestiging van het resetten van de dagelijkse kilometerstand, links een weigering om te resetten, rechts een bevestiging. Als signaal 3 nu op het kruispunt arriveert, krijgen we “1,ff”, een overgang naar toestand 1 zonder de functie uit te voeren, en als signaal 0 arriveert, dan “1,0”. Overgang naar toestand 1 en uitvoering van functie 0. Hieronder vindt u een lijst met functies en hun codes.
    Functiecodes:
    F_NOP FF Geen actie
    F_Reset 0 Dagelijkse kilometerstand resetten
    F_IncHour 1 Voeg 1 uur toe
    F_DecHour 2 Trek 1 uur af
    F_IncMin 3 Voeg 1 minuut toe
    F_DecMin 4 Trek 1 minuut af
    F_IncDay 5 Voeg 1 dag toe
    F_DecDay 6 Trek 1 dag af
    F_IncMon 7 Voeg 1 maand toe
    F_DecMon 8 Trek 1 maand af
    F_IncYear 9 Voeg 1 jaar toe
    F_DecYear A Trek 1 jaar af
    F_SetTime B Nieuwe tijd instellen
    F_SetDate C Nieuwe datum instellen

    Naast ingangssignalen Ik wil hieraan toevoegen dat dit niet alleen een reactie kan zijn op het indrukken van knoppen, maar ook een reactie op het wijzigen van parameters of het signaal van dezelfde wekker. Het kan handig zijn om een ​​parameter weer te geven waarvan de waarde buiten de toegestane limieten ligt.

    // Functie om door het menu te bladeren en het nummer van de opgeroepen functie op te halen
    byte GetFunctie(byte InSig)(
    // als er geen knoppen zijn ingedrukt
    als (InSig==Toets niet ingedrukt)
    retour F_NOP;
    booleaanse waarde Up=InSig
    Booleaans Down=InSig
    byte sig=InSig
    // controleer de geldigheid
    if(sig>=InSigCount)
    retour F_NOP;
    bytefunc=F_NOP;
    als(omhoog)(
    // lees de aangeroepen functie
    func=menu.functie;
    // lees de nieuwe staat
    MenuState=menu.status;
    }
    // Retourneer het functienummer
    retourfunctie;
    }

    // Voorbeeld van functie-uitvoering
    byte k=LeesSleutel();
    k=GetFunctie(k);
    als(k!=F_NOP)(
    als(k==F_Reset)(
    Reis=0; // Dagelijkse kilometerstand resetten
    )else if(k==F_IncHour)(
    if(DT.Uur<23){ DT.Hour++; }else{ DT.Hour=0; }
    )else if(k==F_DecHour)(
    if(DT.Uur>0)( DT.Uur--; )else( DT.Uur=23; )
    )else if(k==F_IncMin)(
    if(DT.Minuut<59){DT.Minute++; }else{DT.Minute=0;}
    )else if(k==F_DecMin)(

    Tags: Arduino, menu, eindige toestandsmachine, programmeren

    • Handleiding

    Een paar maanden geleden verscheen op Habré een artikel “Implementatie van een menu met meerdere niveaus voor Arduino met een display”. ‘Maar wacht,’ dacht ik. “Dit menu heb ik zes jaar geleden geschreven!”

    In 2009 schreef ik het eerste project gebaseerd op een microcontroller en display genaamd “Automatic Lighting Control”, waarvoor het nodig was om een ​​menushell te maken die in duizend configuraties zou passen, of zelfs meer. Het project werd met succes geboren, gecompileerd en kan nog steeds werken, en de OS-menushell begon van project naar project te dwalen, gebruikmakend van de best practices van Flaw-Oriented Programming. ‘Houd op met dit verdragen,’ zei ik en herschreef de code.

    Onder de motorkap vind je oude code van geselecteerde kwaliteit, een verhaal over hoe ik het heb herschreven, evenals instructies voor degenen die het willen gebruiken.

    Vereisten en mogelijkheden voor het besturingssysteemmenu

    Laten we eerst de vereisten definiëren die we aan het menu stellen:
    1. gebruiksgemak, knoppen links-rechts, omhoog-omlaag, achteruit-vooruit.;
    2. boomstructuur met voldoende diepte (tot 256);
    3. het totale aantal menu-items, wat genoeg is voor iedereen (10^616);
    4. instellingen bewerken;
    5. het lanceren van programma's.
    6. een eenvoudige ingebouwde taakbeheerder.
    En het is ook noodzakelijk dat dit alles zo min mogelijk weegt, pretentieloos is voor de middelen en op elk platform draait (momenteel beschikbaar voor AVR, werkt met GLCD en tekst-LCD).
    Theoretisch kan dit OS-menu met de juiste stuurprogramma's eenvoudig worden overgenomen en aangesloten op de RTOS.

    Bestandsstructuur

    Als voorbeeld analyseren we de volgende menustructuur (itemnummer aan de linkerkant):
    0 Hoofdmap/ 1 - Map 1/ - map met bestanden 3 -- Programma 1 4 -- Programma 2 5 -- Map 3/ - map met meerdere exemplaren van het programma. De cursorpositie zal een startparameter zijn 6 --- Programma 3.1 6 --- Programma 3.2 6 --- Programma 3.3 6 --- xxxxxxx 6 --- Programma 3.64 2 - Map 2/ - map met configuraties 7 -- Boolean configuratie 1 8 -- Numerieke configuratie 2 9 -- Numerieke configuratie 3 10 -- Programmadatum/-tijd
    Het belangrijkste principe van het OS-menu is: "Alles is een bestand." Het zij zo.
    Elk bestand heeft een type, naam, bovenliggende map en andere parameters
    Laten we het beschrijven met de structuur:
    struct filedata( uint8_t type; uint8_t parent; uint8_t mode1;//parameter 1 uint8_t mode2;//parameter 2 char-naam; );
    Voor elk bestand definiëren we 4 bytes in de fileData-array:
    1. type,
    2. ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis
    3. mode1, twee parameters die specifiek zijn voor elk bestandstype
    4. modus2
    typ == T_FOLDER
    Het hoofdbestand is een map. Hiermee kunt u een boomstructuur van het gehele menu maken.
    De belangrijkste hier is de hoofdmap met nummer nul. Wat er ook gebeurt, we zullen er uiteindelijk naar terugkeren.
    De mapopties zijn
    mode1 = startnummer van het onderliggende bestand, mode2 = aantal bestanden daarin.
    In de hoofdmap 0 bevinden zich bestanden 1 en 2, 2 in totaal.
    Laten we het als volgt beschrijven:
    T_MAP, 0, 1, 2,
    type == T_DFOLDER
    Map 3 bevat verschillende exemplaren van hetzelfde programma, maar met verschillende startsleutels.
    Bij de automatische lichtregeling is het bijvoorbeeld mogelijk om maximaal 64 dagprogramma's in te stellen, met elk 16 intervallen. Als u elk item beschrijft, heeft u 1024 bestanden nodig. In de praktijk zijn twee voldoende. En we zullen de broodkruimels aan het programma toevoegen in de vorm van parameters.
    mode1 = nummer van het onderliggende bestand, waarvan kopieën worden gemaakt mode2 = aantal kopieën van het bestand.
    Een eenvoudige rekensom leert ons dat als alle 256 bestanden dynamische mappen zijn met een maximaal aantal kopieën, het totale aantal menu-items in het systeem 256^256 = 3,2 x 10^616 zal zijn. Dit is PRECIES genoeg voor elk redelijk of niet zo goed geval.
    type == T_APP
    Sollicitatie. Zijn taak is om te registreren in Taakbeheer (ingebouwd of extern), de controle over de knoppen te onderscheppen en te bewerken.
    mode1 = id van de applicatie die wordt gestart.
    type == T_CONF
    Het configuratiebestand waarvoor alle ophef is begonnen. Hiermee kunt u een Booleaanse of numerieke waarde voor een parameter instellen. Werkt met int16_t.
    mode1 = configuratie-ID
    De configuratie heeft zijn eigen configsLimit-array, waarbij voor elke configuratie drie int16_t-configuratienummers zijn:
    1. Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.
    2. Minimum - minimale gegevenswaarde
    3. Maximaal - maximale gegevenswaarde.
    In cel 2 kunt u bijvoorbeeld een getal van -100 tot 150 schrijven, waarna de regel er als volgt uitziet:
    2, -100, 150,
    type == S_CONF
    Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDER
    mode1 = configuratie-ID
    type == T_SFOLDER
    Een speciaal type map wordt dichter bij de configuratie geplaatst, omdat dit een van de varianten is.
    Stel u voor dat uw systeem via RS-485 kan werken met de protocollen A, B of C. We plaatsen een aantal bestanden van het type S_CONF in een map en selecteren daaruit het gewenste bestand. Bovendien zal de cursor, wanneer we de map opnieuw openen, de actieve optie markeren.
    mode1 en mode2 zijn vergelijkbaar voor T_FOLDER. Onderliggende bestanden zijn alleen T_SCONF

    Resultaten refactoren

    Ik heb mezelf niet de taak gesteld om de architectuur te herzien; op veel plaatsen heb ik zelfs de logica van het werk gelaten zoals het is. Er zijn een paar hele grappige krukken.
    De belangrijkste taak is om het systeem opnieuw te ontwerpen, zodat het gebruik ervan in nieuwe projecten eenvoudig is. Als resultaat:
    1. Ik heb het werk met de hardware opgedeeld in ten minste afzonderlijke functies in een afzonderlijk bestand. HWI omvat:
    2. Modules voor lessen zijn herschreven. Al het mogelijke is privé verborgen, het uiterlijk is uniform. Een truc met klassen en een min of meer uniforme interface zal later van pas komen.
    3. “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.
    4. Ik heb simpelweg de code opgeschoond, duidelijker gemaakt, magische getallen verwijderd en de interface verbeterd. Nu is het geen schande om het te laten zien.
    Ik was te lui om de klokinstellingenmodule voor hwi te herschrijven. Het moet nog geheel vernieuwd worden. Hij is verschrikkelijk.
    Hoe de refactoring plaatsvond, is duidelijk te zien in de repository.

    Het creëren van uw project

    De projectconfiguratie omvat de volgende items:
    Bestanden maken
    Laten we arrays maken met behulp van de eerder besproken structuur
    //structure array static const uint8_t fileStruct PROGMEM = ( T_FOLDER, 0, 1, 2, //0 T_FOLDER, 0, 3, 3, //1 T_FOLDER, 0, 7, 4, //2 T_APP, 1, 1, 0, //3 T_APP, 1, 2, 0, //4 T_DFOLDER, 1, 6, 66, //5 T_APP, 5, 2, 0, //6 T_CONF, 2, 0, 0, //7 T_CONF , 2, 1, 0, //8 T_CONF, 2, 2, 0, //9 T_APP, 2, 3, 0 //10 ); //Array met namen statisch PROGMEM const char file_0 = "Root"; statische PROGMEM const char file_1 = "Map 1"; static PROGMEM const char file_2 = "Map 2"; static PROGMEM const char file_3 = "App 1"; static PROGMEM const char file_4 = "App 2"; static PROGMEM const char file_5 = "Dyn-map"; static PROGMEM const char file_6 = "App"; statische PROGMEM const char file_7 = "config 0"; statische PROGMEM const char file_8 = "config 1"; statische PROGMEM const char file_9 = "config 2"; static PROGMEM const char file_10 = "Datum en tijd"; PROGMEM statische const char *fileNames = ( file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9, file_10 );
    Laten we een array voor configuraties maken:
    //aantal cellen (stap voor 2), minimale waarde, maximale waarde statische const PROGMEM int16_t configsLimit = ( 0,0,0,// config 0: 0 + 0 geeft een booleaanse configuratie 2,-8099,8096,/ /config 1 4,1,48,//config 2);
    Aanpassing van knoppen
    Het liefst sluit ik knoppen met een aardaansluiting en een pull-up-weerstand aan op de voeding, die altijd aanwezig is in de MK.


    In het bestand hw/hwdef.h geven we de namen van de registers en de locatie van de knoppen aan:
    #define BUTTONSDDR DDRB #define BUTTONSPORT PORTB #define BUTTONSPIN PINB #define BUTTONSMASK 0x1F #define BSLOTS 5 /**Knopmasker*/ enum( BUTTONRETURN = 0x01, BUTTONLEFT = 0x02, BUTTONRIGHT = 0x10, BUTTONUP = 0x08, NDOWN = 0x04 );
    Weergave-instellingen
    Nu sleept het project de GLCDv3-bibliotheek mee, wat niet goed is. Historisch gezien is dit hoe het gebeurde.
    Link naar google-code - https://code.google.com/p/glcd-arduino
    Een applicatie maken
    Laten we eens kijken naar een voorbeeldtoepassing die basismenufuncties gebruikt.
    menuos/app/sampleapp.cpp

    Laten we een klasse maken met de volgende structuur:
    #ifndef __SAMPLEAPP_H__ #define __SAMPLEAPP_H__ #include "hw/hwi.h" #include "menuos/MTask.h" #include "menuos/buttons.h" class sampleapp ( //variabelen openbaar: uint8_t Setup(uint8_t argc, uint8_t *argv );//start de applicatieparameters - het huidige niveau en een reeks broodkruimels uint8_t ButtonsLogic(uint8_t button);//button handler uint8_t TaskLogic(void);//timer handler protected: private: uint8_t tick; ); //terug naar het hoofdmenu //functies openbaar: sampleapp(); ~sampleapp(); //sampleapp extern sampleapp SampleApp; //Sishnye void Return();//terug naar het hoofdmenu //functions public: sampleapp();~voorbeeldapp(); beveiligd: privé: ); //sampleapp extern sampleapp SampleApp; //Sishnye
    krukken
    uint8_t sampleapp::Setup(uint8_t argc, uint8_t *argv) ( tick = 0; //registreer onszelf in de systeemmodules Buttons.Add(SampleAppButtonsHandler);//add button handler Task.Add(1, SampleAppTaskHandler, 1000);/ / taak toevoegen ha GLCD.ClearScreen();//maak het scherm leeg //en schrijf op de meest zichtbare plaats GLCD.CursorTo((HwDispGetStringsLength()-11)/2, HwDispGetStringsNumb()/2); )

    systeemmodules
    Buttons.Add(SampleAppButtonsHandler);//knophandler toevoegen Task.Add(1, SampleAppTaskHandler, 1000);//taak toevoegen ha GLCD.ClearScreen();//maak het scherm leeg //en schrijf GLCD.CursorTo( (HwDispGetStringsLength( )-11)/2, HwDispGetStringsNumb()/2);

    GLCD.Puts("Hallo Habr");
    retour 0; )
    Wikkeltjes:
    void SampleAppButtonsHandler(uint8_t-knop)( SampleApp.ButtonsLogic(knop); ) void SampleAppTaskHandler())( SampleApp.TaskLogic(); )
    Knophandler:
    void MMenu::AppStart(void)( if (file.mode2 != BACKGROUND)( Task.Add(MENUSLOT, MenuAppStop, 10);//100 ms update Task.ActiveApp = 1;//app moet ActiveApp op nul zetten ) switch (file.mode1)(//AppNumber case 2: SampleApp.Setup(niveau, brCrumbs); break; case 3: Clock.Setup(level, brCrumbs); break; standaard: Task.ActiveApp = 0; break; ) )

    Ik kreeg de opdracht een programma te ontwikkelen voor een apparaat dat een incrementele encoder als besturingselement gebruikt. Daarom besloot ik een ongeplande les te schrijven over het werken met een encoder in het Arduino-systeem.

    Heel kort waar we het over hebben, d.w.z. over de classificatie van encoders.

    Encoders zijn digitale rotatiehoeksensoren. Met andere woorden, hoekcodeconverters.

    Ik benadruk dat dit digitale sensoren zijn omdat er een groot aantal sensoren zijn met analoge uitgangssignalen. Een eenvoudige variabele weerstand kan worden beschouwd als een hoeksensor. Encoders genereren discrete signalen aan de uitgang.

    Encoders kunnen absoluut of accumulatief (incrementeel) zijn.

    Absolute encoders genereren een uitgangscode die overeenkomt met de huidige hoek van de aspositie. Ze hebben geen geheugen. U kunt het apparaat uitschakelen, de encoderas draaien, weer inschakelen en de uitvoer zal een nieuwe code zijn die de nieuwe positie van de as aangeeft. Dergelijke encoders zijn complex, duur en maken voor de aansluiting vaak gebruik van standaard digitale interfaces RS-485 en dergelijke.

    Incrementele encoders genereren uitgangspulsen die verschijnen wanneer de as wordt gedraaid. Het ontvangende apparaat kan de huidige hoek van de encoderas bepalen door het aantal pulsen aan de uitgang te tellen. Incrementele encoders kunnen de aspositie niet detecteren na het inschakelen. Het is noodzakelijk om het aan het begin van het aftellen te binden.

    Maar in de meeste gevallen is het niet nodig om de absolute waarde van de huidige hoek te kennen. Als we bijvoorbeeld een encoder gebruiken om het volumeniveau aan te passen, moeten we dit met verschillende gradaties verhogen of verlagen. We kijken niet naar de encoderknop; er zit geen schaal op. We moeten de hoekverandering ten opzichte van de huidige positie bepalen. Hetzelfde geldt voor het instellen van parameters op het display. We draaien aan de encoderknop en kijken hoe de parameterwaarde op het scherm verandert.

    In dergelijke gevallen worden incrementele encoders ideale besturingsapparaten, parameterinstellingen en menuselectie. Ze zijn veel handiger dan de knoppen “+” en “-”.

    In deze gevallen kunt u de eenvoudigste mechanische incrementele encoders gebruiken, die goedkoop zijn.