. ‘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:- 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.
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:
- type,
- ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis
- mode1, twee parameters die specifiek zijn voor elk bestandstype
- 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:
- Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.
- Minimaal - minimale waarde gegevens
- Maximaal - maximale waarde gegevens.
2, -100, 150,
type == S_CONF
Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDERmode1 = 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:
- Gescheiden werken met hardware in minimaal aparte functies apart bestand. HWI omvat:
- 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.
- “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.
- 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.
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: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:
- type,
- ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis
- mode1, twee parameters die specifiek zijn voor elk bestandstype
- 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:
- Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.
- Minimum - minimale gegevenswaarde
- Maximaal - maximale gegevenswaarde.
2, -100, 150,
type == S_CONF
Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDERmode1 = 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:
- Ik heb het werk met de hardware opgedeeld in ten minste afzonderlijke functies in een afzonderlijk bestand. HWI omvat:
- 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.
- “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.
- 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.
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).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.
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:- 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.
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:
- type,
- ouder, het is niet echt nodig, omdat alle informatie in broodkruimels zit, maar het blijft als erfenis
- mode1, twee parameters die specifiek zijn voor elk bestandstype
- 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:
- Cel-ID - Startnummer van de geheugencel voor het opslaan van gegevens. Alle gegevens nemen twee bytes in beslag.
- Minimum - minimale gegevenswaarde
- Maximaal - maximale gegevenswaarde.
2, -100, 150,
type == S_CONF
Interessante (maar nog steeds alleen in de oude code) configuratie, werkt in combinatie met T_SFOLDERmode1 = 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:
- Ik heb het werk met de hardware opgedeeld in ten minste afzonderlijke functies in een afzonderlijk bestand. HWI omvat:
- 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.
- “Toegevoegde” interface voor het werken met RTOS. Of beter gezegd, de standaard taakbeheerder is vrij eenvoudig te vervangen door een andere.
- 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.
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.