RobotLib HowTo

Uit RobotMC.be
Ga naar: navigatie, zoeken

Zie deze pagina voor een overzicht van alle RobotLib documentatie op deze wiki.

Hoe gebruik ik voorgeprogrammeerde functies?

Robotlib heeft een aantal voorgeprogrammeerde functies. Deze zijn gegroepeerd in 'programmeerbare functie key groepen' (PFKEY). Een overzicht van deze functies en groepen vindt je hier.

Activeren PFKEY groep in het programma

Met de volgende functie-aanroep wordt een PFKEY groep geactiveerd:

  PfKey.HandlerSet(FP_FNAME(PFKeyMotorCalibrate1));  

De PFKEY groep blijft actief totdat een andere groep wordt geactiveerd.

Activeren PFKEY groep via het console

Als een PFKEY groep voor de eerste keer in het programma wordt geactiveerd, wordt hij tevens toegevoegd aan de lijst van beschikbare PFKEY groepen. En de PFKEY groep blijft in de lijst staan, ook als een volgende PFKEY groep wordt geactiveerd. Deze lijst is op te vragen via het console met het commando 'pf list':

Rlib> pf list
0 * PFKeyMotorCalibrate1
    1 - MotorL Step up (for Motorcontroller integration)
    2 - MotorL Step down
    3 - MotorR Step up
    4 - MotorR Step down
    7 - Start MSM_MotorCal
    8 - Start MSM_PwmCurve
    9 - Activate PFKeyMotorCalibrate2
   10 - (EMERGENCY) Stop
1   PFKeyMotorCalibrate2
2   PFKeyUmTest1
3   PFKeyUmTest2
4   PFKeyRepository

Het * voor PFKeyMotorCalibrate1 geeft aan dat deze groep op dit moment actief is. Van deze handler worden ondersteunde toetsen weergegeven. Met het console-commando 'pf set', gevolgd door het nummer van de groep, kan een andere groep uit de lijst worden geactiveerd:

Rlib> pf set 2
PFKeyHandler function 'PFKeyUmbMark' (2) activated.
Rlib>

Aanroep functie uit actieve PFKEY groep

Als op de afstandsbediening functietoetsen zijn toegewezen, kunnen deze direct gebruikt worden om de overeenkomstige functie aan te roepen. (Zie RobotLib_HAL#RC5 voor meer info over de afstandsbediening.)

Alternatief is het console commando 'pf key', gevolgd door het nummer van de toets:

Rlib> pf key 7
MissionHandlerStart: start mission MSM_MotorCal.
...

Hoe voeg ik een bestand toe aan mijn project?

Maak het gewenste bestand aan in de project directory. Meestal zal dit een .cpp bestand zijn en soms een .h bestand. Neem een .cpp bestand op in de lijst 'project' in 'makefile.do'.

Voorbeeld

Ons nieuwe bestand 'Voorbeeld.cpp' wordt als volgt toegevoegd aan de lijst 'project' in 'makefile.do':

createlist(project) {     
   "($PRJ_path)main.cpp"   
   "($PRJ_path)Prj_Cli.cpp"   
   "($PRJ_path)Prj_Hal_Motors.cpp"
   "($PRJ_path)Prj_Stubs_Globals.cpp"
   "($PRJ_path)Prj_Umbmark.cpp"
   "($PRJ_path)Voorbeeld.cpp"
}

Hoe voeg ik een functie toe aan mijn project?

Plaatst de gewenste functie in een bestaande file, of maak een nieuwe file aan. Voeg het functie prototype toe in 'project.h' zodat de functie vanuit alle robotlib files toegankelijk wordt.

Voorbeeld

Het skelet van onze nieuwe functie, in dit geval een MissionStateMachine met de naam 'MSM_Vierkant1', ziet er als volgt uit:

bool MSM_Vierkant1(TMissionHandler *M, int &State, int NewState)
{        
   switch (State) {  
      case 0 :    // Rij naar 1000, 0 met een maximale snelheid van 500 mm/sec
            if (NewState) {
               // voor het eerst in deze state
               Mover.SetDirectMode();        // Set direct mode (Um* commando's werken direct)               
               UmXY(1000, 0, 500, 0);        // X, Y, Speed, EndSpeed - alles in mm(/sec)
            }                                                          
            if (Mover.IsDone()) {            // Als de beweging klaar is
               State++;                      // naar volgende state       
            }
         break;               

              
      default :   // error
            return true;   // mission end
         break;
   }           
   return false;  // mission nog niet gereed
} 

Het prototype wordt toegevoegd in 'project.h'. Let op de ';' aan het einde.

bool MSM_Vierkant1(int &State, int NewState);

Als toegift nog een uitbreiding met twee variabelen, Var1 en Var2. Als je variabelen nodig hebt om dingen op te slaan voor een volgende keer dat de state machine wordt aangeroepen, zijn static variabelen de logische keuze:

bool MSM_Vierkant1(TMissionHandler *M, int &State, int NewState)
{ static Var1;
  static Var2;
     
   switch (State) {  

Let op: 'static' variabelen worden bij het opstarten van de processor gereserveerd voor deze missie. Het geheugen is daardoor niet beschikbaar voor andere zaken. Of je de missie nu gebruikt of niet...

Hoe voeg ik een commando toe aan het console?

zie RobotLib_Module CommandInterface - Gebruik (commando's toevoegen)

Hoe koppel ik een actie aan een knop op de afstandsbediening?

Lees dit verhaal en je weet precies wat de mogelijkheden zijn. De korte versie:

  • Met een 'UserDispatch' functie kun je een vrije toets van de afstandsbediening afvangen.
  • Je kunt een 'handler schrijven om de 10 of 12 functietoetsen af te handelen.
  • Een commando wat je in kunt tikken kun je via de repository koppelen aan een toets op de afstandsbediening (zowel functietoets als vrije toets).

De laatste optie is het meest flexibel. Via de command line kun je eenvoudig testen met verschillende parameters. Aanroepen met de afstandsbediening is een kwestie van configuratie.

Hoe start ik een missie in een missie ?

Hierboven staat beschreven hoe je een missie kunt starten, door deze te koppelen aan een knop van de afstandsbediening. Eventueel kan voor het starten ook een parameter worden ingesteld:

Mission.ParamSet(0, 1);     // bonus 1 = rond blik
Mission.Start(FP_FNAME(MSM_Roborama_HeenWeer));

Soms is het handig om een missie te starten vanuit een andere missie, als een soort subroutine. Met 'Mission.Start()' kunnen we (ook vanuit een missie) een nieuwe missie starten, maar dan wordt de huidige missie afgebroken. En dat is niet altijd de bedoeling.

bool MSM_Roborama_HeenWeer(TMissionHandler *M, int &State, int NewState)   
...
case 50 : // Calibreer startpositie
      if (NewState) {         
         M->Sub->Start(FP_FNAME(MSM_Roborama_Startpositie));
      }                                                          
                   
      if (M->Sub->IsDone()) { // secondary mission gereed
         State += 10;
      }
   break;    

Hierboven staat de code van state 50 van de missie 'MSM_Roborama_HeenWeer'. De eerste keer dat state 50 wordt uitgevoerd is, zoals altijd, de variabele NewState 'true' en in dit geval wordt de sub-missie 'MSM_Roborama_Startpositie' gestart. Vervolgens wordt steeds gecontroleerd of de sub-missie gereed is (M->Sub->IsDone()). Als dit het geval is, gaat de heen-en-weer missie naar state 50.


In essentie wordt de de referentie 'Missie.' vervangen door 'M->Sub->'. En dat geldt ook voor het instellen van parameters:

M->Sub->ParamSet(0, 300);     // maximale snelheid

Op deze wijze kunnen missies in missies in missies worden uitgevoerd. Handig, want zo hoef je regelmatig terugkerende bewegingen maar 1x te programmeren. Het maximaal aantal missies is in StubsGlobals.cpp vast ingesteld op 4:

TMissionHandler      Mission(4);          // Mission State Machine 'driver'

Desgewenst kan een eigen versie van StubsGlobals.cpp in de projectdirectory worden geplaatst met een groter aantal missies.


Hoe ga ik verder als de Default_Handler wordt aangeroepen

De default handler wordt aangeroepen als er iets mis gaat bij de uitvoering van de code en de processor niet weet hoe nu verder. Een serieuze fout, vergelijkbaar met de oude 'blue screens' op windows. Normaal zou je een debugger gebruiken om te kijken wat er gebeurt, maar die hebben we niet...

Polled serial

Als de default handler wordt aangeroepen, stopt de serial output van het console. Omdat deze console output normaal gebufferd is, hebben we daardoor niet de laatste informatie. Dit kunnen we aanpassen in prj_hal_conf.h:

// choose one of the two below
//#define CUART_QUEUED
 #define CUART_POLLED

In 'polled' mode wordt ieder karakter direct via de serial port verstuurd en hebben we de laatste info op het console. De timing van je programma wordt wel wat anders, met name als je veel print. Dus niet vergeten om dit terug te zetten als het probleem is opgelost.

Voeg print statements toe om (stapje voor stapje) te bepalen op welk punt het fout gaat. Door de polled mode zie je die alle output.

Exception info

Als je er niet uit komt met debug op basis van printf, moeten we zelf aan de slag met de informatie die bij de exception op het scherm staat. In de meeste gevallen is 'SCB->CFSR' gelijk aan 0x00008200. Zoek in dit geval de program counter op:

pc:  0x800d852

Hier was de processor bezig toen het mis ging en in doel.map kunnen we opzoeken wat dit is:

.text          0x0800d5c8      0x3e0 C:\RobotLib\RobotLib\TaktList.o
               0x0800d5c8                TTaktObject::Takt()
...
               0x0800d814                TTaktList::Report()
               0x0800d838                TTaktList::TaktNext()
               0x0800d8ac                TTaktList::TaktFirst()
               0x0800d8d8                TTaktList::Takt()
...
.text          0x0800d9a8      0xb2c C:\RobotLib\RobotLib\MotorCalibrate.o

Adres 0x800d852 is dus onderdeel van TaktList.cpp (waarvan TaktList.o het object is). En om precies te zijn, de methode (functie) TaktNext.

De volgende stap is de TaktList.lst openen om te bepalen welke manier van adresseren is gebruikt. Op de STM32F4 is de adressering van de .lst file doorlopend, terwijl die van de STM32VL bij iedere functie bij 0 begint. In het eerste geval (doorlopende addressering, STM32F4) is het handig om het startadres van de TaktList.lst te gebruiken (zie onder).

In het tweede geval (adressen per fucntie, STM32VL) nemen we het startadres van de functie als uitgangspunt.

De volgende stap is berekening van het relatieve adres in TaktList. Dit kan met een hex rekenmachine (windows calculator, ALT-3) het begin (startadres) van de module (STM32F4) of functie (STM32VL) af te trekken van de program counter:

0x800d852 - 0x0800d5c8 = 0x028a

Dit adres kunnen we opzoeken in TaktList.lst, die in de zelfde directory staat als TaktList.cpp.

239:C:\RobotLib\RobotLib\TaktList.cpp ****          NextExecTakt->TaktObj->Takt();               // voer taak uit
679              		.loc 1 239 0
680 0288 0368     		ldr	r3, [r0]
681 028a 1B68     		ldr	r3, [r3]
682 028c 9847     		blx	r3
683              	.LVL62:
684 028e E368     		ldr	r3, [r4, #12]
685              	.L58:

Het gaat dus mis in de code, die hoort bij regel 239 in TaktList.cpp. 9 van de 10 keer wordt een adres gebruikt dat niet geldig is. Een null pointer, of een waarde die niet (meer) klopt, een array die niet groot genoeg is. Wat precies? Dat zul je zelf uit moeten zoeken... Succes!


PS: De fout hierboven trad op als de communicatie met een nieuwe sensor een aantal maal op rij mis ging. Dat het mis gaat in bij de aanroep van een taak (NextExecTakt->TaktObj->Takt(), code die al een tijd niet meer is gewijzigd en zonder problemen werkt) was dan ook een symptoom van het echte probleem: een array index die op hol was geslagen en allerlei dingen kapot maakte. Totdat hij een taakpointer overschreef en de exception het systeem stopte...

PPS: Bovenstaande zou de indruk kunnen wekken dat ARM processoren je het extra moeilijk maken. Het tegenovergestelde is hier het geval. Iedere processor kan dit soort problemen tegenkomen bij het uitvoeren van het programma, zeker als deze in C of assembler wordt geprogrammeerd. Het verschil met bijvoorbeeld een Arduino Uno is dat de Arduino gewoon blijft proberen, ook al is duidelijk dat het niet goed is. Je hebt dan een onbetrouwbaar systeem, dat soms niet goed werkt en zelfs af en toe vast loopt. De exception van een ARM processor geeft duidelijkheid plus informatie om de oorzaak op te sporen.