Strekdambaan, Arduino en Fotografie
/

 


De software code van keerlusmodule is te vinden in het volgende bestand (download):  Keerlusprogramma.txt  

Toelichting

Het ontwerp is eigenlijk eenvoudig. Alle in en uitgangen zijn op één van de I/O pinnen van de Nano aangesloten. Door steeds weer de status van de ingangen te lezen kan op basis van de detecties de stand van het relais  bepaald worden. Dan kan het relais al of niet bekrachtigd worden via een I/O pin. Daarnaast moet een melding op de detectie ingang A of E aan Koploper worden gestuurd. Hier voor worden de terugmeld uitgangen even hoog gemaakt (een puls).
De test knoppen worden gelezen en simuleren een detectie op de A of E ingang. De rest van de LED's is in feite verfraaiing (handig voor testen).

De hardware opzet is simpel, er zijn geen speciale software bibliotheken nodig.

Structuur van een Arduino programma

De structuur van Arduino programma is altijd hetzelfde:

  • Definieer de nodige constanten en variabelen.
    Deze zijn geldig in het hele programma (scoop).
  • Definieer alle procedures die je in het programma wilt gebruiken.
    Dit geeft een beter overzicht in het programma en is vooral handig als je vaker dezelfde functies wil aanroepen.
    Deze zijn geldig in het hele programma (scoop).
  • Definieerde procedure Setup
    De code in deze procedure wordt bij het starten van het programma éénmaal uitgevoerd. Hier kunnen alle nodige instellingen gemaakt worden.
  • Definieer de procedure Loop.
    Deze procedure wordt door basis software van de Arduino constant (dus heel vaak) aangeroepen. Alle acties die uitgevoerd moeten worden zullen hier worden geprogrammeerd.

Constanten en variabelen

Hieronder de definitie van alle I/O toewijzingen. Er zijn vele manieren van programmeren. Ik vind het belangrijk dat de code gestructureerd en leesbaar is.
Een paar regels:

  • Wees niet bang om veel tekst te gebruiken, het kost geen geheugenruimte.
  • Constanten altijd benoemen, zet nergens in de code getallen. Wijzigen is dan simpel.
  • Als ze iets aan moet passen, bv. een waarde van een timer gebruik dan hiervoor een constante (of variabele) en leid in de code hier alles van af.
  • Goed leesbare namen en definities zijn tegelijk de broodnodige documentatie van de software.

Hieronder de gebruikte variabelen. Ze kunnen bij de declaratie gelijk geïnitialiseerd worden maar dit kan ook in de Setup procedure bij opstart van het programma.

Procedures

Voor alle 'basis' handelingen maak ik altijd een procedure. Hier binnen wordt de functionaliteit geïmplementeerd. Hierdoor wordt het hoofdprogramma zo overzichtelijk mogelijk gehouden. Op deze manier documenteer je het ook programma.

Timers

Timers moet je realiseren in de Loop omdat dat de enige procedure is die constant wordt aangeroepen. De beste techniek omdat te doen heb ik beschreven op een aparte pagina Timers in Arduino.

Hieronder een overzicht van de procedures met een korte toelichting.

void LeesInverteerRichting()
    // Lees de toestand van de strap waarmee de uitgang
    // van het relais omgekeerd kan worden.

void LeesDetectieInputs()
    // Lees de status van de 4 Hall sensors A B D en E
    // Deze zijn aangesloten op analoge ingangen.

void LeesTestButtons()
    // Leest de toestand van de test druktoetsen voor
    // het handmatig omkeren van de rijrichting op de keerlus.

void HandleBasicTimer()
    // Wordt van uit de Loop elke 10ms (instelbaar) aangeroepen
    // en voert de volgende procedures uit:
    LeesInverteerRichting();
    LeesDetectieInputs();
    LeesTestButtons();

void HandleOnStateTimers()
    // Wordt vanuit de Loop elke 10ms (instelbaar) aangeroepen
    // nadat eerst de HandleBasicTimer() is uitgevoerd
    // Elke detectie A, B, D of E wordt ge-latched, vast gehouden voor
    // een minimum tijd. Deze procedure handelt dit af.

void HandleActionTimer()
    // Wordt elke 100ms aangeroepen  
    // Handelt de aansturing van de LED's en de S88-melding

    // en de toestand van het relais:
    ZetMeldingLed(A, TimerOnState_A);
    ZetMeldingLed(B, TimerOnState_B);
    ZetMeldingLed(D, TimerOnState_D);
    ZetMeldingLed(E, TimerOnState_E);
    ZetS88TerugMelding(S88MeldingTimer);
    ZetRichtingsLed(richting);
    ZetRelais(richting);

void HandleKnipperTimer()
    // Zorgt voor het knipperen van de on-board LED
    // en de groene LED op de printplaat

void ZetRelais(Richting richting)
    // Zorgt voor de aansturing van het relais (zijn er in feite 2 parallel
    // Handelt ook het eventueel inverteren af

void ZetMeldingLed(Sectie sectie,int detectieTimer)
    // Stuurt één van de 4 detectiemeldings LED's aan.

void ZetS88TerugMelding(Richting richting, int detectieTimer)
    // Stuurt afhankelijk van de rijrichting richting één van de 2
    // terugmeldingen A of E aan.

void ZetRichtingsLed(Richting richting)
    // Stuurt de 2 richtings LED's aan (Rood en Groen)

void loop()
    // Wordt constant door de Arduino kernel aangeroepen
    // Handelt alle zaken af van input, verwerking en aansturing.
    // Zorgt voor de timing.
    // Roept de volgende procedures aan:
    HandleBasicTimer();
    HandleOnStateTimers();
    HandleActionTimer();
    HandleKnipperTimer();

void setup()
    // Alle initialisatie die moet gebeuren voordat het programma (de Loop) start.
    I/O pinnen moeten nog worden ingesteld
    Initialisatie van de timers:
      BasicTimerValue = 10;         // in ms
      OnStateTimerValue = 100;   // Count down voor signaal ON state (in basic timer intervals, dus 1000ms)
      ActionTimerValue = 100;      // in ms
      KnipperTimerValue = 500;    // in ms

Constanten

//
// IO pin definities
//
const byte PIN_LED_TEST_RICHTING_ROOD = 2;                // handmatig richting rood
const byte PIN_LED_TEST_RICHTING_GROEN = 3;             // handmatig richting groen
const byte PIN_LED_INDICATOR_RICHTING_GROEN = 4;   // richting is groen
const byte PIN_LED_INDICATOR_RICHTING_ROOD = 5;     // richting is rood

const byte PIN_LED_MELDING_SECTIE_A = 6;         // Detectie A
const byte PIN_LED_MELDING_SECTIE_B = 7;         // Detectie B
const byte PIN_LED_MELDING_SECTIE_D = 8;         // Detectie D
const byte PIN_LED_MELDING_SECTIE_E = 9;         // Detectie E

// Terugmelding S88 bus
const byte PIN_TERUGMELDING_SECTIE_A = 11;    // Detectie A
const byte PIN_TERUGMELDING_SECTIE_E = 10;    // Detectie E
// Aansturing relais
const byte PIN_RELAIS_AANSTURING = 12;              // Detectie A

const byte PIN_ONBOARD_LED_D13 = 13;

//
// Hall sensor inputs (pullup is ook external)
//
const byte ANA_DET_A = A0;        // Detectie sectie A
const byte ANA_DET_B = A1;        // Detectie sectie B
const byte ANA_DET_D = A2;        // Detectie sectie D
const byte ANA_DET_E = A3;        // Detectie sectie E
//
// Input voor inversie
//
const byte ANA_INVERTEER_RICHTING = A7;
//
// Enumerations
//
enum Richting { rood = 0, groen = 1 };
enum Sectie { A, B, D, E};

Variabelen

/*
 We maken timers op basis van millis()
*/
unsigned long CurrentMillis;        // huidige waarde van de millis() timer
unsigned long BasicTimer;          // basis milliseconden countdown teller
unsigned long OnStateTimer;      // timer voor de aantijd van een detectie
unsigned long ActionTimer;         // timer voor afhandeling
unsigned long KnipperTimer;       // timer voor knippereren
int BasicTimerValue;                    // in ms
int OnStateTimerValue;                // Count down voor signaal ON state (in basic timer intervals)
int ActionTimerValue;                   // in ms
int KnipperTimerValue;                // in ms

// Detectie inputs
bool Detectie_A = false;
bool Detectie_B = false;
bool Detectie_D = false;
bool Detectie_E = false;

// OnState timer (count down)
int TimerOnState_A = 0;
int TimerOnState_B = 0;
int TimerOnState_D = 0;
int TimerOnState_E = 0;

// S88 melding timer voor vaste lengte van de bezetmelding puls
int S88MeldingTimer = 0;
//
bool InverteerRichting;        // met strap mogelijkheid om relais gedrag te inverteren

Richting richting = rood;      // Initialiseer de richting op rood
bool KnipperOn = false;      // toggle voor knipperen On Board LED
int AnaloogIn;                      // voor het inlezen van een analoge input

Setup procedure

void setup()
{
    // Start serial interface
    Serial.begin(115200);
    Serial.setTimeout(50); // Time out set to 50ms to get faster resonse
    Serial.println(F("==================== "));
    Serial.println(F("Nano startup"));
    Serial.println(F("Versie 1.2 2021-02-13"));
    Serial.println(F("- Test LED pin assigment corrected"));
    Serial.println(F("- Duration output signal set to 1s (was 2s)"));

    /*
        I/O pinnen moeten nog worden ingesteld
    */
    
    pinMode(PIN_LED_TEST_RICHTING_ROOD, INPUT_PULLUP);
    pinMode(PIN_LED_TEST_RICHTING_GROEN, INPUT_PULLUP);
    pinMode(PIN_LED_INDICATOR_RICHTING_GROEN, OUTPUT);
    pinMode(PIN_LED_INDICATOR_RICHTING_ROOD, OUTPUT);

    pinMode(PIN_LED_MELDING_SECTIE_A, OUTPUT);
    pinMode(PIN_LED_MELDING_SECTIE_B, OUTPUT);
    pinMode(PIN_LED_MELDING_SECTIE_D, OUTPUT);
    pinMode(PIN_LED_MELDING_SECTIE_E, OUTPUT);

    pinMode(PIN_TERUGMELDING_SECTIE_A, OUTPUT);
    pinMode(PIN_TERUGMELDING_SECTIE_E, OUTPUT);

    pinMode(PIN_RELAIS_AANSTURING, OUTPUT);
    //
    pinMode(PIN_ONBOARD_LED_D13, OUTPUT);
    // ingangen voor Hall detectors
    pinMode(ANA_DET_A, INPUT_PULLUP);
    pinMode(ANA_DET_B, INPUT_PULLUP);
    pinMode(ANA_DET_D, INPUT_PULLUP);
    pinMode(ANA_DET_E, INPUT_PULLUP);

    pinMode(ANA_INVERTEER_RICHTING, INPUT_PULLUP);
    /*
     * Init de timers
    */
    // Timer values
    BasicTimerValue      = 10;         // in ms
    OnStateTimerValue =100;        // Count down voor signaal ON state (in basic timer intervals)
    ActionTimerValue    = 100;       // in ms
    KnipperTimerValue  = 500;      // in ms
    //
    LeesInverteerRichting();
}

Loop procedure

void loop()
{
    CurrentMillis = millis();    // neem eerst een sample van de millis timer
    if ((CurrentMillis - BasicTimer) > BasicTimerValue)
    {
        BasicTimer = CurrentMillis;       // Restart timer
        HandleBasicTimer();
        HandleOnStateTimers();
    }
    if ((CurrentMillis - ActionTimer) > ActionTimerValue)
    {
        ActionTimer = CurrentMillis;     // Restart timer
        HandleActionTimer();
    }
    if ((CurrentMillis - KnipperTimer) > KnipperTimerValue)
    {
        KnipperTimer = CurrentMillis;    // Restart timer
        HandleKnipperTimer();
    }
}

Inlees procedures

void LeesInverteerRichting()
{
    AnaloogIn = analogRead(ANA_INVERTEER_RICHTING);
    if (AnaloogIn > 512)
    {
        InverteerRichting = true;
    }
    else
    {
        InverteerRichting = false;
    }
}

void LeesDetectieInputs()
{
    // Aktief als input laag is
    // Alleen set van de detectie, reset na afhandeling
    AnaloogIn = analogRead(ANA_DET_A);
    if (AnaloogIn < 512) {Detectie_A = true;}
    AnaloogIn = analogRead(ANA_DET_B);
    if (AnaloogIn < 512) {Detectie_B = true;}
    AnaloogIn = analogRead(ANA_DET_D);
    if (AnaloogIn < 512) { Detectie_D = true; }
    AnaloogIn = analogRead(ANA_DET_E);
    if (AnaloogIn < 512) { Detectie_E = true; }
}

void LeesTestButtons()
{
    if (!digitalRead(PIN_LED_TEST_RICHTING_ROOD))
    {
        Detectie_A = true;
        Serial.println(F("Druktoets rood"));

    }
    if (!digitalRead(PIN_LED_TEST_RICHTING_GROEN))
    {
        Detectie_E = true;
        Serial.println(F("Druktoets groen"));
    }
}

Afhandelen van de timers

void HandleBasicTimer()
{
    LeesInverteerRichting();
    LeesDetectieInputs();
    LeesTestButtons();
}

void HandleOnStateTimers()
{
    // moet de timer (weer) gestart worden?
    if (Detectie_A)
    {
        TimerOnState_A   = OnStateTimerValue;
        S88MeldingTimer  = OnStateTimerValue;
        richting = rood;
        Detectie_A = false;   // reset detectie
    }
    if (Detectie_B)
    {
        TimerOnState_B  = OnStateTimerValue;
        richting = rood;
        Detectie_B = false;  // reset detectie
    }
    if (Detectie_D)
    {
        TimerOnState_D  = OnStateTimerValue;
        richting = groen;
        Detectie_D = false;  // reset detectie
    }
    if (Detectie_E)
    {
        TimerOnState_E   = OnStateTimerValue;
        S88MeldingTimer = OnStateTimerValue;
        richting = groen;
        Detectie_E = false;  // reset detectie
    }
    if (TimerOnState_A > 0) { TimerOnState_A = TimerOnState_A - 1; }
    if (TimerOnState_B > 0) { TimerOnState_B = TimerOnState_B - 1; }
    if (TimerOnState_D > 0) { TimerOnState_D = TimerOnState_D - 1; }
    if (TimerOnState_E > 0) { TimerOnState_E = TimerOnState_E - 1; }
    if (S88MeldingTimer > 0) { S88MeldingTimer = S88MeldingTimer - 1; }
}

void HandleActionTimer()
{
    //
    ZetMeldingLed(A, TimerOnState_A);
    ZetMeldingLed(B, TimerOnState_B);
    ZetMeldingLed(D, TimerOnState_D);
    ZetMeldingLed(E, TimerOnState_E);
    //
    ZetS88TerugMelding(richting, S88MeldingTimer);
    //
    ZetRichtingsLed(richting);
    ZetRelais(richting);
}

void HandleKnipperTimer()
{
    if (KnipperOn)
    {
        digitalWrite(PIN_ONBOARD_LED_D13, HIGH);
    }
    else
    {
        digitalWrite(PIN_ONBOARD_LED_D13, LOW);
    }
    KnipperOn = !KnipperOn;
}

De output procedures

void ZetRelais(Richting richting)
{
    bool AanState = true;
    if (InverteerRichting) { AanState = false;  }

    switch (richting)
    {
    case rood:
        digitalWrite(PIN_RELAIS_AANSTURING, AanState);
        break;
    case groen:
        digitalWrite(PIN_RELAIS_AANSTURING, !AanState);
        break;
    default:
        break;
    }
}

void ZetMeldingLed(Sectie sectie,int detectieTimer)
{
    bool OnState = false;
    if (detectieTimer > 0) { OnState = true; }
    switch (sectie)
    {
    case A:
        digitalWrite(PIN_LED_MELDING_SECTIE_A, OnState);
        break;
    case B:
        digitalWrite(PIN_LED_MELDING_SECTIE_B, OnState);
        break;
    case D:
        digitalWrite(PIN_LED_MELDING_SECTIE_D, OnState);
        break;
    case E:
        digitalWrite(PIN_LED_MELDING_SECTIE_E, OnState);
        break;
    default:
        break;
    }
}

void ZetS88TerugMelding(Richting richting, int detectieTimer)
{
    if (detectieTimer > 0)
    {
        // melding moet aan
        if (richting == rood)
        {
            digitalWrite(PIN_TERUGMELDING_SECTIE_A, HIGH);
            digitalWrite(PIN_TERUGMELDING_SECTIE_E, LOW);
        }
        else
        {
            digitalWrite(PIN_TERUGMELDING_SECTIE_A, LOW);
            digitalWrite(PIN_TERUGMELDING_SECTIE_E, HIGH);
        }
    }
    else
    {
        // melding moet uit
        digitalWrite(PIN_TERUGMELDING_SECTIE_A, LOW);
        digitalWrite(PIN_TERUGMELDING_SECTIE_E, LOW);
    }
}

void ZetRichtingsLed(Richting richting)
{
    switch (richting)
    {
    case rood:
        digitalWrite(PIN_LED_INDICATOR_RICHTING_ROOD, HIGH);
        digitalWrite(PIN_LED_INDICATOR_RICHTING_GROEN, LOW);
        break;
    case groen:
        digitalWrite(PIN_LED_INDICATOR_RICHTING_ROOD, LOW);
        digitalWrite(PIN_LED_INDICATOR_RICHTING_GROEN, HIGH);
        break;
    default:
        break;
    }
}