Doel:€250.00
Donaties:€85.00

Per saldo:€-165.00

Steun ons nu!

Laatst bijgewerkt
op 20-05-2022

Vacature: secretaris bestuur
Algemeen

De stichting

Recente berichten

Roco 1600, versie 2022 door Ascona
Vandaag om 00:03:14
Hoe zaag je een Lima hondekop door? door Duikeend
Vandaag om 00:01:05
Toon hier je (model)TRAM foto's. door Chris Westerduin
24 mei 2022, 23:43:33
Mijn eerste H0-modeltreinbaan in aanbouw door NS1220
24 mei 2022, 23:33:49
Airbrushes, wat is de beste tool en wat zijn de verschillen? door Tomasso
24 mei 2022, 23:22:08
Eén compressor en een hoop vragen door jerrytrein
24 mei 2022, 23:00:46
Korneschans. Een fictief Duits Nederlands grensstation. door DJV
24 mei 2022, 22:54:14
Raadplaatje door Modellbahnwagen
24 mei 2022, 22:38:51
NTM goederenwagens in Spoor-0 door RutgerSHM
24 mei 2022, 22:37:43
Bruikbare etsplaten voor NS4000 en NS4700 tenders, en NS slijptrein? door Eric B
24 mei 2022, 22:30:29
1st Dutch US Convention, 21 en 22 mei 2022, Aalten door Ronald Halma
24 mei 2022, 21:52:14
Treinbaan verticaal opbergen. Is dat handig? door Olav
24 mei 2022, 21:49:55
Heen, En en Weer: de tram. door Huup
24 mei 2022, 21:49:31
Roco 2242 rijdt belabberd door Hielke Breider
24 mei 2022, 21:41:47
Artitec Hondekop IC-uitvoering, model 2020, materieelbespreking. door Benelux795
24 mei 2022, 21:14:49
Ontwikkelingen bij de Selfkantbahn (1.000mm) door SimonSt
24 mei 2022, 20:30:07
Baanplannen van langgerekte stations door phdirk
24 mei 2022, 20:29:09
TEE RAm of DE4 door borotof
24 mei 2022, 19:11:35
Onlangs gespot - gefotografeerd, de foto's door MichielB
24 mei 2022, 19:01:23
Toon hier je nieuwe (model-) spooraanwinst(en)... door sncf231e
24 mei 2022, 18:55:34
Handleiding/documentatie gezocht Pola-Maxi ? BR89 door Silvolde
24 mei 2022, 17:55:46
Rondom Charlois door Alewijn
24 mei 2022, 17:38:52
Frans loodsje et cetera door Noordernet
24 mei 2022, 17:35:27
Waar ik mee bezig ben en wat ik gemaakt heb door janvanbemmel
24 mei 2022, 17:25:51
Actuele bediening Oosterhout met goederentreinen door Rick
24 mei 2022, 15:51:06
BNLS modulebaan Werkspoor-Kathedraal hervat. door NS264
24 mei 2022, 15:01:36
Stoomtrein Goes-Borsele: Wat zijn we aan het bouwen door martijnhaman
24 mei 2022, 13:43:22
Gezocht: beschrijving Roco Modellen door eelco
24 mei 2022, 13:14:02
EifelBurgenBahn door Hendrik Jan
24 mei 2022, 11:24:06
Ex-DB TEE VT11.5 krijgt nieuw leven bij Railadventure door Modellbahnwagen
24 mei 2022, 10:47:05
  

Auteur Topic: De CanBus komt naar Kranenberg, Arduino's en de CanBus  (gelezen 46756 keer)

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #150 Gepost op: 22 december 2021, 22:09:39 »
Ik heb dus nog wat vertimmerd. Daarom laat ik toch even zien wat er veranderd is.

Eerst heb ik de kalibratie in de Turntable klasse (KB_Turntable.cpp en KB_Turntable.h) verbouwd. Dat betekend concreet dat de void calibrate() van public naar private is gegaan, en ook het prototype daarvan iets veranderd is. Dus is nu alleen void calibrate(long) publiek. Dat heeft wel tot gevolg dat ik iets moest veranderen in de schets.

Dat heeft zich beperkt tot de functie die door de Canbus wordt aangeroepen als er een accessory commando binnenkomt. In de setup blijft alles zoals het was.

//
//  A function that executes whenever a DCC_ACC message is received from the Canbus
//  It's purpose is to analyze the received event (Signal or turnout) and
//  update the track position of the TT or update the position of the doors.
//
bool calibrated = false;
void dccAccReceiver(unsigned char aMsgLen, DCC_ACC_MSG *msgData)
{
#if (DEBUG)
  Serial.print("[dccAccReceiver] dccId: ");
  Serial.print(msgData->address);
  Serial.print(", dccState: ");
  Serial.println(msgData->direction);
  Serial.flush();
#endif //DEBUG

  unsigned short address = msgData->address;
 
  //
  //  Do we perform a straight(-) or a diverging(/) operation?
  //  For a straight operation enable is true, for a diverging operation
  //  enable is false.
  //
  bool enable = (msgData->direction) ? 0 : 1;

  //
  //  Search for the specified accessory address, when found lookup the
  //  specified position for the straight or diverging operation and start moving
  //  to that target position.
  //
  for (int i = 0; i < nrOfTracks; i++)
  {
    if ( address == trackPositions[i].address )
    {
#if (DEBUG)
      Serial.print("[dccAccReceiver] TT Address: ");
      Serial.print(address, DEC);
#endif //DEBUG
      //
      //  The new position of the TT
      //
      long newTargetPosition = 0;

      if (enable)     // Received a "-" command
      {
#if (DEBUG)
        Serial.print(" (-)");
#endif //DEBUG
        //
        //  Go to the straight position (0 degree)
        //
        newTargetPosition = trackPositions[i].position0;

        //
        //  If the new target address equals zero, a calibration is asked for
        //  In that case we don't save the new position, because the current
        //  position stay active after the calibration.
        //
        if (newTargetPosition != 0)
        {
          EepromControl->setTTposition(i);
          EepromControl->saveData();
        }
      }
      else            // Received a "/" command
      {
#if (DEBUG)
        Serial.print(" (/)");
#endif //DEBUG
        //  Go to the diverging (180 degree) position.
        //
        newTargetPosition = trackPositions[i].position180;

        //
        //  If the new target address equals zero, a calibration is asked for
        //  In that case we don't save the new position, because the current
        //  position stay active after the calibration.
        //
        if (newTargetPosition != 0)
        {
          EepromControl->setTTposition(i+nrOfTracks);
          EepromControl->saveData();
        }
      }

#if (DEBUG)
      Serial.print(" Position: ");
      Serial.println(newTargetPosition, DEC);
      Serial.flush();
#endif //DEBUG

      //
      //  If the choosen position equals 0, then we have to recalibrate
      //  After the calibration we return to the current position, so we have to
      //  get that from Eeprom. Further after a calibration, we block new calibrations
      //  until a regular movement is executed.
      //
      if ((newTargetPosition == 0) && (!calibrated))
      {
          //
          //  Load the EEPROM data for the turnout directions
          //
          EepromControl->loadData();
       
          byte eePromData = EepromControl->getTTposition();

          //
          //  Savety measure, for when the Eeprom contains garbage
          //
          if (eePromData > 2*nrOfTracks)
          {
            eePromData = 1;
          }
         
          //
          //  Get the in EEPROM stored track position, it wil be used after calibration
          //  of the turntable to return to the current position
          //
          if (eePromData < nrOfTracks)
          {
            TT.calibrate(trackPositions[eePromData].position0);
          }
          else
          {
            TT.calibrate(trackPositions[eePromData-nrOfTracks].position180);
          }

          calibrated = true;
      }
      else if (newTargetPosition != 0)
      {
        //
        //  Not a recalibrate, goto the choosen position
        //
        TT.gotoTTPosition(newTargetPosition);

        //
        //  Free to recalibrate
        //
        calibrated = false;
      }

      return;
    }
  }

  //
  //  If we didn't find an address match with a track location
  //  check if a door servo matches the received address
  //
  for (int i = 0; i < NR_OF_DOORS; i++)
  {
    if ( address == doors[i].address )
    {
#if (DEBUG)
      Serial.print("[dccAccReceiver] Door address: ");
      Serial.print(address, DEC);
#endif //DEBUG
      if (enable)
      {
#if (DEBUG)
        Serial.println(" (-)");
        Serial.flush();
#endif //DEBUG
        //
        //  Go to the closed position
        //
        doors[i].doorServo->closeDoor();

        EepromControl->setDoorPosition(i,DoorClosed);
        EepromControl->saveData();
      }
      else
      {
#if (DEBUG)
        Serial.println(" (/)");
        Serial.flush();
#endif //DEBUG
        //
        //  Go to the open position.
        //
        doors[i].doorServo->openDoor();

        EepromControl->setDoorPosition(i,DoorOpen);
        EepromControl->saveData();
      }

      return;
    }
  }

#if (DEBUG)
  Serial.print("[dccAccReceiver] Unknown address: ");
  Serial.println(address, DEC);
  Serial.flush();
#endif //DEBUG
}
Er zijn nu twee zaken gewijzigd.
  • Als er om een calibratie wordt verzocht, halen we de laatste actieve railpositie op uit het Eeprom en geven die positie door aan de kalibratie methode.
  • Een ander probleem waar ik tegen aanliep, was dat een DCC accessory commando door een centrale een aantal keren wordt herhaald. Dat had tot gevolg dat als ik via de centrale een herkalibratie aanvraag, dat dan de draaischijf een aantal keren achter elkaar de kalibratie ging uitvoeren. Er is nu een mechanisme ingebouwd wat dat voorkomt.

Dat was het verhaal over de draaischijf controller.

Overigens als er belangstelling is dat ik ook de interne werking van de Turntable klasse zelf bespreek, laat me het weten. Dat moet dan wel in een aantal seperate posts gebeuren omdat de source van deze klasse te groot is voor een enkel posting.

Groet Meino
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #151 Gepost op: 09 mei 2022, 15:58:20 »
Naar aanleiding van een vraagje over het aansturen van wissels met DCC en een schakelaar, realiseerde ik me dat ik een soortgelijk probleem had aangepakt. Een geruime tijd geleden heb ik een systeem gebouwd voor het aansturen van een paneel. De tableaux-controller. Dit systeem is ook ontstaan naar aanleiding van mijn poging om de spaghetti aan kabels onder de baan te reduceren, verder wilde ik wat extra faciliteiten via het paneel mogelijk maken.

Oorspronkelijk had ik een simpel paneel met ledjes die de stand van de door Koploper gecontroleerde wissels aan gaven, en voor de overige wissels een simpele schakelaar die direct de betreffende servo aanstuurde. Deze laatste wissels zijn niet gedefinieerd in Koploper en deel van het emplacement wat alleen via de hand bedient kan worden.

Dat zag er toen zo uit..


De spaghetti aan kabels.

De ledjes voor de wisselstand waren rechtstreeks via een diode netwerkje aangesloten op de aandrijving van de wissels en de polariteit daarvan bepaalde welk led aan ging.

Maar zoals ik al zei, ik wilde van de kabels af en er moest het een en ander aan functionaliteit toegevoegd worden.
  • Versimpeling van bedrading.
  • Aansturing draaischijf via knop
  • Mogelijkheid om blokken handmatig via knoppen op het tableaux te kunnen blokkeren (ten behoeve van rangeerbewegingen0.
  • Mogelijkheid om wissels die onder controle van Koploper staan toch handmatig te kunnen verzetten (ten behoeven van rangeerbewegingen).

Dus een Arduino opgetuigt met een Canbus kaartje, een schets geschreven en aangesloten.
Toen zag het er als volgt uit..



Draaiknop voor de draaischijf, extra drukknoppen voor het bezet maken van blokken en handmatig wisselstanden aanpassen.

Een uitgebreidere beschrijving van de werking en mogelijkheden had ik ooit al eens hier in het Kranenberg draadje behandeld.

Ik ga het nu in het vervolg even hebben over de schets die dit geheel aan knoppen en ledjes beheert.

Groet Meino


A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #152 Gepost op: 13 mei 2022, 22:31:34 »
Voor de Tableau-Controller heb ik dit keer een Arduino Mega gebruikt. Daar had ik er nog een aantal van liggen, en alhoewel de schets betrekkelijk klein is, ten opzichte van het beschikbare geheugen, was het grote aantal pinnen wel erg makkelijk.
Dit systeem is verder weer uitgerust met twee Mcp2515 Canbus kaartjes omdat het berichten moet ontvangen, maar ook zelf produceert.

Dit systeem heeft op dit moment de volgende taken te verzorgen:
  • Zorg dat de indicatie leds van de geautomatiseerde wissels de positie van het wissel correct weergeven.
  • Monitoring van die drukknoppen waarmee de stand van een geautomatiseerd wissel verzet kan worden.
  • Monitoring van drukknoppen waarmee een blok handmatig bezet kan worden.
  • De stand van een draaiknop doorgeven aan de Draaischijf controller zodat de stand van de draaischijfbrug deze stand volgt.
  • Monitoring van twee schakelaars voor het openen/sluiten van de deuren van de lokloods.

Ik begin met de wissel indicatie leds.


Een opmerking, de gebruikte leds hebben een standaard verbruik van 20mA. Maar er zijn 12 wissels, dus 24 leds. Gelukkig brand er altijd per wissel 1, maar het gebruik van die 12 leds is dan wel 240mA totaal, wat meer is dan de Arduino in totaal op zijn pinnen kan leveren. Verder moeten er nog een stel andere leds aangesloten worden. Dus heb ik de voorloop weerstand per led zodanig gekozen dat het verbruik per led gereduceerd is tot 5mA. Het blijkt dat ook bij deze lagere stroomsterkte meer dan voldoende helderheid geeft. Hierdoor kan ik alle leds op het paneel rechtstreeks door de pinnen aansturen.

Ok, tijd voor wat C++ code.
//
//  The occupance detectors for the switches start on bank 4, each bank has 16 occupancies, so a single
//  bank is enough for the 12 turnouts in use.
//  The push buttons used for controlling these switches generate Occupancy detector events
//  starting on bank 6
//
#define S88_SWITCH_BANK 4
#define S88_SWITCHBUTTON_BANK 6

We maken gebruik van het feit dat de wissel-controller de stand van een wissel door middel van een bezetmelding publiceert op de Canbus. Deze bezetmeldingen hebben momenteel als adres 4.01-4.12 (S88 bank 4)

//
//  Table definitions for the turnout/switch leds
//
const int nrOfTurnouts = 12;

typedef struct {
  int s88Bank;
  int s88Nr;
  int pinStraight;
  int pinDiverting;
} tolTable;

tolTable turnoutLed[nrOfTurnouts] = {
  { S88_SWITCH_BANK, 1, 22, 23},
  { S88_SWITCH_BANK, 2, 25, 24},
  { S88_SWITCH_BANK, 3, 26, 27},
  { S88_SWITCH_BANK, 4, 29, 28},
  { S88_SWITCH_BANK, 5, 30, 31},
  { S88_SWITCH_BANK, 6, 33, 32},
  { S88_SWITCH_BANK, 7, 35, 34},
  { S88_SWITCH_BANK, 8, 36, 37},
  { S88_SWITCH_BANK, 9, 39, 38},
  { S88_SWITCH_BANK,10, 40, 41},
  { S88_SWITCH_BANK,11, 43, 42},
  { S88_SWITCH_BANK,12, 44, 45}
};
Voor ieder wissel zijn twee leds gedefinieerd in een simpele array. Ieder element uit de array bevat de S88 bank, S88 nr, de pin van de led voor het rechte pad en de pin van de led voor het afbuigend pad.

In de setup() functie moeten nu een paar zaken geregeld worden.

  //
  //  Setup and start the CanBus communication
  //
  canBus = new CanBus(PANEL_CONTROLLER_ID, new Mcp2515(49), new Mcp2515(53)); // Sender + Receiver
  canBus->setMsgReceiver(OCC_DT, occDtReceiver);
  canBus->setMsgReceiver(DCC_ACC, dccAccReceiver);
  canBus->begin();
De Canbus wordt opgezet en we registreren twee functies, de occDtReceiver(..) functie, die ontvangt alle bezetmeldingen van de Canbus en gaat de wisselleds aan of uit zetten. De dccAccReceiver(..) functie ontvangt alle DCC Accessory berichten. Deze worden gebruikt als een blok handmatig bezet gezet wordt.

  //
  //  Activate the turnout leds
  //
  for (int i = 0; i < nrOfTurnouts; i++)
  {
      //
      //  Switch both leds off
      //
      pinMode(turnoutLed[i].pinStraight,OUTPUT);
      digitalWrite(turnoutLed[i].pinStraight, LOW);
      pinMode(turnoutLed[i].pinDiverting, OUTPUT);
      digitalWrite(turnoutLed[i].pinDiverting, LOW);
  }
De leds worden geactiveerd. Tot zover de activiteiten in setup()

Het echte werk gebeurd in de callback routine die door de Canbus aangeroepen wordt als er een bezetmelding bericht binnen komt.

void occDtReceiver(unsigned char aMsgLen, OCC_DT_MSG *msgData)

  //
  //  Find the proper turnout detector with the matching address
  //  and sets its new direction based on the enable field from the
  //  received dcc command
  //
  for (int i = 0; i < nrOfTurnouts; i++)
  {
    if ((turnoutLed[i].s88Nr+((turnoutLed[i].s88Bank - 1) * 16)-1) == msgData->address)
    {
      //
      //  Switch both leds off
      //
      digitalWrite(turnoutLed[i].pinStraight, LOW);
      digitalWrite(turnoutLed[i].pinDiverting, LOW);

      if (msgData->state == 0)
      {
        //
        //  Set the straight led
        //
        digitalWrite(turnoutLed[i].pinStraight, HIGH);
      }
      else
      {
        //
        //  Set the diverging direction
        //
        digitalWrite(turnoutLed[i].pinDiverting, HIGH);

      }

      //
      //  End the for loop
      //
      return;
    }
  }
}
De bezet melding wordt geanaliseerd, en het adres van de bezetmelder wordt vergeleken met de adressen van de led tabel. Als er een match is, dan wordt de stand van het wissel in de bijbehorende leds uit gewerkt.

Tot zover het afhandelen van de leds voor de wisselstanden.

Groet Meino
« Laatst bewerkt op: 16 mei 2022, 21:30:35 door meino »
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #153 Gepost op: 16 mei 2022, 00:02:32 »
Het aansturen van de Leds is een vrij simpele toepassing en het beperken van de bedrading was misschien niet voldoende reden geweest om dit systeem te introduceren. Maar ik wilde ook op het emplacement handmatig (met een lokmaus R3) kunnen rijden en rangeren terwijl op de rest van de baan alles onder besturing van Koploper (met automatisch rijden) door gaat. Om te kunnen rangeren moet het mogelijk zijn om een aantal wissels die normaal door Koploper gezet worden, handmatig de stand te kunnen aanpassen. Daarnaast moet het ook mogelijk zijn om een aantal blokken in de hoofdbaan op slot te zetten (handmatig bezet) zodat Koploper stopt met rijden naar die blokken. Dat is allemaal prima te doen via het scherm op de PC waar Koploper op draait, maar ik wilde dat via het Tableau doen met drukknoppen. Het idee was om via een knop een bezetmelder aan te zetten, Koploper moet dan via een speciale actie (die reageert op die bezetmelding) de wissel omzetten of het blok handmatig bezet maken.


De knoppen voor de wissels en een knop voor het handmatig bezetten van een blok, de rode led geeft aan dat het betreffend blok handmatig bezet is.

Een klein probleempje is dat voor de goede werking van deze acties in Koploper, de bezetmelding actief moet blijven, zolang de actie uitgevoerd moet worden. De betreffende knoppen maken alleen contact zolang je ze ingedrukt houdt. Daarom een kleine C++ klasse gemaakt die dit oplost. Een object uit deze klasse emuleert een knop die bij de eerste keer indrukken contact maakt en dat vast houdt, om vervolgens bij de volgende keer indrukken dat contact weer te verbreken.

//
//  Class for a push button that controls a switch by generating an
//  occupance detector event when it is pushed.
//
class SwitchButton {
  private:
    bool state = false;          // initial the button is not pushed
   
    int buttonPin;
    bool pushed = false;
    bool stateSet = false;
    bool bounceState = false;
    unsigned long bounceTime;    // Last time a state switch happened
    int bounceWait = 4 ;         // Ignore bounce for 4mS
   
  public:
    //
    //  Create a new instance of a SwitchButton
    //
    SwitchButton(int aButtonPin);
    bool getState();
 
    //
    //  Attach the digital pin to the push button
    //
    void attach();
   
    //
    //  Check the state of the buttonPin. If it stays long enough
    //  send out an occupance detector event.
    //
    void heartBeat();
};
Het .h bestand van deze klasse

//
//  Create a new instance of a SwitchButton
//
SwitchButton::SwitchButton(int aButtonPin)
{
    buttonPin = aButtonPin;

    pushed = false; // If pushed, it asks for diverging.
    state = false;
    bounceState = false;
}

//
//  Return the current state of the SwitchButton
//
bool SwitchButton::getState()
{
    return state;
}

//
//  Attach the digital pin to the push button
//
void SwitchButton::attach()
{
    pinMode(buttonPin, INPUT_PULLUP);
}

//
//  Check the state of the buttonPin. If it stays long enough
//  send out an occupance detector event.
//
void SwitchButton::heartBeat()
{
    if (!pushed)
    {
        //
        //  The button state is not pushed, so wait until the pin goes HIGH
        //  signaling a pushed button.
        //
        if (digitalRead(buttonPin) == HIGH)
        {
            //
            //  Wait a small amount of time, this to make sure that the
            //  state of the pin is constant
            //
            if (!bounceState)
            {
                bounceState = true;
                bounceTime = millis();
            } else if (((millis() - bounceTime) > bounceWait))
            {
                pushed = true; // Flip the state
                bounceState = false; // Reset the bounce state for the next time
            }
        }
    }
    else
    {
        //
        //  The button state is pushed, so wait until the pin goes LOW
        //  signaling a released button.
        //
        if (digitalRead(buttonPin) == LOW)
        {
            //
            //  Wait a small amount of time, this to make sure that the
            //  state of the pin is constant
            //
            if (!bounceState)
            {
                bounceState = true;
                bounceTime = millis();
            } else if (((millis() - bounceTime) > bounceWait))
            {
                pushed = false; // Reset the pushed state
                stateSet = false;
                bounceState = false; // Reset the bounce state for the next time
            }     
        }
    }
   
    //
    //  When the button was pushed and this is the first time we detect that,
    //  flip the state
    //
    if ((pushed) && (!stateSet))
    {
        stateSet = true;    // The state must be set once, so this prevent
                            // that their is a constant flipping of the state
       
        //
        //  Flip the state
        //
        if (state == true)
        {
            state = false;
        }
        else
        {
            state = true;
        }   
    }
}
De C++ klasse zelf.
Het meeste is recht toe recht aan, een creator, een getter voor de state, en een method om de drukknop met zijn Arduino pin te verbinden. Het echte werk gebeurd in de heartbeat() method. Daar wordt de pin gemonitored en als die hoog wordt (en lang genoeg blijft, i.v.m. bounce effecten) wordt de state geinverteerd (als hij aan was dan gaat hij uit, als de state uit was dan gaat hij aan). Als de pin laag gaat en blijft, dan gebeurt er niet zoveel met de state, alleen wordt het drukknop object weer klaar gezet voor de volgende keer dat de knop wordt ingedrukt.

Groet Meino
« Laatst bewerkt op: 16 mei 2022, 21:31:50 door meino »
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #154 Gepost op: 16 mei 2022, 12:05:00 »
We hebben het even gehad over een klasse die wat functionaliteit van een bepaald soort schakelaar nabootst, maar nu hoe gebruiken we dat in de schets.

Eerst even de definities
//
//  Table containing all definitions for pushbuttons on the panel
//
const int nrOfButtons = 10;

typedef struct {
  SwitchButton button;
  bool currentState;
  int s88Bank;
  int s88Nr;
} switchTable;

switchTable buttonTable[nrOfButtons] = {
  {SwitchButton(A2), false, S88_SWITCHBUTTON_BANK, 1}, // Button for "Handmatige bezet melding"
  {SwitchButton(A3), false, S88_SWITCHBUTTON_BANK, 2}, // Button for "Handmatige bezet melding"
  {SwitchButton(A4), false, S88_SWITCHBUTTON_BANK, 3}, // Button for "Handmatige bezet melding"
  {SwitchButton(A5), false, S88_SWITCHBUTTON_BANK, 4}, // Button for "Omzetten wissel 4"
  {SwitchButton(A6), false, S88_SWITCHBUTTON_BANK, 5}, // Button for "Omzetten wissel 5"
  {SwitchButton(A7), false, S88_SWITCHBUTTON_BANK, 6}, // Button for "Omzetten wissel 6"
  {SwitchButton(A8), false, S88_SWITCHBUTTON_BANK, 7}, // Button for "Omzetten wissel 7"
  {SwitchButton(A9), false, S88_SWITCHBUTTON_BANK, 8}, // Button for "Omzetten wissel 8"
  {SwitchButton(A10),false, S88_SWITCHBUTTON_BANK, 9}, // Button for "Omzetten wissel 9"
  {SwitchButton(A11),false, S88_SWITCHBUTTON_BANK, 10} // Button for "Handmatige bezet melding"
};
Een simpele tabel met voor ieder drukknop een SwitchButton object, de bijbehorende S88 beztmeld adres en een status waarmee we bepalen of de status van het SwitchButton object is veranderd.

Wat initialisatie code voor in de setup() method.

  //
  //  Activate the turnout push buttons
  //
  for (int i = 0; i < nrOfTurnouts; i++)
  {
    (buttonTable[i].button).attach();
  }
De SwitchButtons worden met hun Arduino pinnen verbonden, hun huidige status is niet belangrijk omdat die initieel altijd uit staat.

//
  //  Check the turnout push buttons, and if they were pushed, send an occupancy event
  //
  for (int i = 0; i < nrOfButtons; i++)
  {
    (buttonTable[i].button).heartBeat();         // The buttons have to monitor their pins

    //
    //  If the state of the button has changed (it differs from the last stored state)
    //  put an occupance message on the Canbus
    //
    if (buttonTable[i].currentState != (buttonTable[i].button).getState())
    {
      //
      //  The state has changed, so store the new state
      //
      buttonTable[i].currentState = (buttonTable[i].button).getState();   
      if ((buttonTable[i].button).getState() == true)
      {
        occDtHandler(buttonTable[i].s88Bank, buttonTable[i].s88Nr, true);
      }
      else
      {
        occDtHandler(buttonTable[i].s88Bank, buttonTable[i].s88Nr, false);
      }     
    }
  }
In de loop() method zorgen we dat de SwitchButtons actief hun pin monitoren via de heartBeat() method en daarna checken we of de status van de switchbutton is veranderd. Afhankelijk van de nieuwe staat sturen we een Bezetmelder bericht naar de Canbus om de relevante bezetmelder aan of uit te zetten.

Dat sturen van een Bezetmelder bericht doen we in de volgende functie.

void occDtHandler(int aS88Bank, int aS88BankNr, bool aDirection)
{
  //
  //  Send the new state to the S88 Interface
  //
  OCC_DT_MSG buf;
  //
  //  At this moment we have no more the 12 turnouts, so don't bother about the S88 bank
  //
  buf.address = ((aS88Bank - 1) * 16) + (aS88BankNr - 1);
  if (aDirection)
    buf.state = 1;
  else
    buf.state = 0;

  canBus->sendMsg(OCC_DT, sizeof(OCC_DT_MSG), &buf);
}

To zover het afhandelen van de drukknoppen op het tableau.

Groet Meino
« Laatst bewerkt op: 16 mei 2022, 21:33:45 door meino »
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #155 Gepost op: 16 mei 2022, 22:58:22 »
Tot nu toe is het alleen gegaan over de knoppen en hoe die door de Arduino schets worden afgehandeld. Maar de Arduino schets activeert alleen een bezetmelding, die dan door Koploper wordt verwerkt tot een specifieke actie. Alhoewel het een beetje buiten de scope van dit Arduino gerelateerde draadje ligt, wil ik het er toch even over hebben. Binnen Koploper zijn drie Speciale Acties gedefinieerd.
  • Wissel rechtdoor zetten.
  • Blok handmatig bezetten.
  • Led aansturing blok.

De Speciale Actie "Wissel rechtdoor zetten" wordt actief als "Alle bezetmelders bezet/Logische acties waar". De trigger voor deze actie is als de bezetmelder door een drukknop aan gezet wordt. Er zijn geen logische acties gedefinieerd. De actie die dan uitgevoerd wordt is "Adres recht(waar) of afbuigend(niet waar)". Het adres is van de specifieke wissel.

De Speciale Actie "Blok handmatig bezetten" wordt actief als "Alle bezetmelders bezet/Logische acties waar". De trigger voor deze actie is als de bezetmelder door een drukknop aan gezet wordt. Er zijn geen logische acties gedefinieerd. De actie die dan uitgevoerd wordt is "Blok handmatig geblokkeerd(zolang waar)".

De Speciale Actie "Led aansturing blok" wordt actief als "Alle bezetmelders bezet/Logische acties waar". De trigger voor deze actie is als de bezetmelder voor dit blok aan gezet is en als de volgende condities (logische acties) waar zijn, "Blok is niet bezet door trein(alle locomotieven)" en "Blok is niet gereserveerd". De actie die dan wordt uitgevoerd wordt is "Adres afbuigend(waar) of recht(niet waar).
De combinatie van condities, bezetmelder en logische acties, heeft tot gevolg dat het DCC accessory comando pas verstuurd wordt op het moment dat het blok beschikbaar komt (dit is niet helemaal waar, maar goed genoeg voor mij. Het probleem is dat binnen Koploper de conditie "Blok is handmatig bezet" niet beschikbaar is).

Tijd voor wat code.

//
//  Definitions for the table with block leds. Leds that wil signal when a block is
//  reserved and may be used for switching operations.
//
const int nrOfBlokLeds = 4;

typedef struct {
  int dccId;
  int ledPin;
} BlokLed;

BlokLed blokLeds[nrOfBlokLeds] = {
  {101, A12},
  {102, A13},
  {103, A14},
  {110, A15}
};
Er zijn vier blokken die we kunnen reserveren voor het rangeren, dus ook vier leds. Ieder led heeft zijn eigen DCC accessory adres in de tabel. Dat zijn ook de adressen die door de Speciale Acties aangestuurd worden.

In de setup() method worden de Arduino pinnen waar de leds mee verbonden zijn actief gemaakt. Ook hier is de voorloopweerstand van ieder led zodanig gedimensioneerd dat iedere led niet meer dan 5mA verbruikt.

  //
  //  Activate the blok leds
  //
  for (int i = 0; i < nrOfBlokLeds; i++)
  {
      //
      //  Switch led off
      //
      pinMode(blokLeds[i].ledPin,OUTPUT);
      digitalWrite(blokLeds[i].ledPin, LOW);
  }

Omdat de Speciale Actie een DCC accessory commando stuurt, wordt het echte werk gedaan in een callback routine die we bij de Canbus hebben gereserveerd. Zie het volgende stukje code.

  //
  //  Setup and start the CanBus communication
  //
  canBus = new CanBus(PANEL_CONTROLLER_ID, new Mcp2515(49), new Mcp2515(53)); // Sender + Receiver
  canBus->setMsgReceiver(OCC_DT, occDtReceiver);
  canBus->setMsgReceiver(DCC_ACC, dccAccReceiver);
  canBus->begin();

Deze callback routine is dccAccReceiver().

//
//  A function that executes whenever a message is received from the Canbus
//  It's purpose is to analyze the received event and set the led for this address
//
void dccAccReceiver(unsigned char aMsgLen, DCC_ACC_MSG *msgData)
{
  unsigned short dccId = msgData->address;
  byte dccState = msgData->direction;

  //
  //  Find the led with the received address
  //
  for (int i=0; i<nrOfBlokLeds; i++)
  {
    if (blokLeds[i].dccId == dccId)
    {
      if (dccState == 0)
      {
        digitalWrite(blokLeds[i].ledPin, LOW);
      }
      else
      {
        digitalWrite(blokLeds[i].ledPin, HIGH);
      }

      return;
    }
  }
}

Op basis van het ontvangen DCC adres wordt de led geselecteerd en afhankelijk van de aansturing (rechtdoor/afbuigend) wordt de LED aan of uit gezet.

Groet Meino
« Laatst bewerkt op: 16 mei 2022, 23:02:34 door meino »
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #156 Gepost op: 18 mei 2022, 22:48:18 »
Er is nog een stuk functionaliteit dat nog niet besproken is. Ik had ooit een draaischijf gebouwd met een stappenmotor en een Arduino. Die kan ik aan sturen met DCC accessory commando's, ieder spoor aansluiting met een eigen DCC accessory adres. Die wilde ik ook via een draaiknop op het tableau kunnen aansturen. Daartoe een 24polige draaiknop aangeschaft. Dat was voldoende voor de 18 posisties.





Naast deze draaiknop heb ik ook nog twee schakelaartjes waarmee ik de deuren van het lokdepot open en dicht kan doen. Aangezien ik die op de zelfde manier afhandel als de draaischakelaar behandel ik die tegelijkertijd.  Ook voor deze schakelaars heb ik een kleine C++ klasse geschreven die
het gedrag van deze schakelaars verwerkt.

De .h definitie.
class TTPositionSwitch {
  enum SWITCHSTATE {
    PIN_HIGH = 1,
    GOING_HIGH = 2,
    PIN_LOW = 3,
    GOING_LOW = 4
  };
 
  private:
    int ttPin;
    bool reverseDirection;
    SWITCHSTATE switchState;
    bool state;

    bool bounceState = false;
    unsigned long bounceTime;   // Last time a Pin state switched, used to handle bounce
    int bounceWait = 1000 ;       // Let the state changes settle for for 1 sec
     
  public:
    TTPositionSwitch(int aTTPin, bool aReverseDirection);
    bool getReverseDirection();

    bool getState();

    //
    //  Attach the digital pin to the push button
    //
    void attach();
   
    //
    //  Check the state of the buttonPin. If it stays long enough
    //  send out an occupance detector event.
    //
    void heartBeat();
};


De C++ implementatie van de klasse.
TTPositionSwitch::TTPositionSwitch(int aTTPin, bool aReverseDirection)
{
    ttPin = aTTPin;
    reverseDirection = aReverseDirection;
    switchState = PIN_HIGH;
    state = false;
}

bool TTPositionSwitch::getReverseDirection()
{
    return reverseDirection;
}

//
//  Return it's current state
//
bool TTPositionSwitch::getState()
{
    return state;
}
De creator en een aantal getter methods. Een opmerking, het atribuut reverseDirection heeft te maken met het DCC commando dat gesturrd wordt om naar een positie te gaan. DCC accessory commandos kan rechtdoor of afbuigend schakelen. Hier maken we gebruik van bij de draaischijf, voor de 18 aansluiting zijn 9 DCC adressen in gebruik, waarbij door een afbuigend commando te geven de draaischijfbrug naar de andere kant gaat.

//
//  Attach the digital pin to the push button
//
void TTPositionSwitch::attach()
{
    pinMode(ttPin, INPUT_PULLUP);
   
    //
    //  Initialize the current state of the switch. Don't wait for bouncing,
    //  because it is save to asume that the switch is currently in a
    //  stable position.
    //
    if (digitalRead(ttPin) == LOW)
    {
        state = true;
        switchState = PIN_LOW;
    }
    else
    {
        state = false;
        switchState = PIN_HIGH;
    }
}
Deze schakelaars hebben bij het starten altijd een bepaalde stand. Dus tijdens de attach, wordt de pin geactiveerd, maar tevens wordt gelijk bepaald of de schakelaar aan of uit staat.

//
//  Check the state of the buttonPin. If it stays long enough
//  send out an occupance detector event.
//

void TTPositionSwitch::heartBeat()
{
    //
    //  The switch is a rotating switch, so always one position is engaged,
    //  so only when a pin goes HIGH we have to do something, send a DCC_ACC message
    //
    switch (switchState)
    {
        case PIN_HIGH:
            if (digitalRead(ttPin) == LOW)
            {
                switchState = GOING_LOW;
                bounceTime = millis();
            }
            break;

        case GOING_HIGH:
            if ((millis() - bounceTime) > bounceWait)
            {
                if (digitalRead(ttPin) == HIGH)
                {
                    state = false;
                    switchState = PIN_HIGH;
                } else
                {
                    switchState = PIN_LOW;
                }
            }
            break;

        case PIN_LOW:
            if (digitalRead(ttPin) == HIGH)
            {
                switchState = GOING_HIGH;
                bounceTime = millis();
            }
            break;

        case GOING_LOW:
            if ((millis() - bounceTime) > bounceWait)
            {
                if (digitalRead(ttPin) == LOW)
                {
                    //
                    // If the state switches from LOW to HIGH, report it
                    //
                    state = true;
                    switchState = PIN_LOW;
                } else
                {
                    //
                    //  After the bounce time, the state of the pin has returned to HIGH
                    //  so reset the ttSwitchState
                    //
                    switchState = PIN_HIGH;
                }
            }
            break;
    }
}
De heartBeat() method (die constant aangeroepen moet worden) monitored de pin, en verandert, indien nodig, de staat van de schakelaar.

Groet Meino

A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg

meino

  • Offline Offline
  • Berichten: 1609
Re: De CanBus komt naar Kranenberg, Arduino's en de CanBus
« Reactie #157 Gepost op: 20 mei 2022, 00:09:47 »
We hebben weer een aantal tabellen in de Arduino schets.

//
//  Table containg all definitions for the rotary turntable switch
//
const int nrOfTTPositions = 18;

typedef struct {
  int dccId;
  bool currentPosition;
  TTPositionSwitch ttPosition;
} TurnoutSwitchTable;

TurnoutSwitchTable TTPositions[nrOfTTPositions] = {
  {201, false, TTPositionSwitch(13, false)},
  {202, false, TTPositionSwitch(12, false)},
  {203, false, TTPositionSwitch(11, false)},
  {204, false, TTPositionSwitch(10, false)},
  {205, false, TTPositionSwitch( 9, false)},
  {206, false, TTPositionSwitch( 8, false)},
  {207, false, TTPositionSwitch( 7, false)},
  {208, false, TTPositionSwitch( 6, false)},
  {207, false, TTPositionSwitch( 5, false)},
  {201, false, TTPositionSwitch( 4, true)},
  {202, false, TTPositionSwitch( 3, true)},
  {203, false, TTPositionSwitch( 2, true)},
  {204, false, TTPositionSwitch(14, true)},
  {205, false, TTPositionSwitch(15, true)},
  {206, false, TTPositionSwitch(16, true)},
  {207, false, TTPositionSwitch(17, true)},
  {208, false, TTPositionSwitch(18, true)},
  {209, false, TTPositionSwitch(19, true)},
};

//
//  Table with the definition for the micro switches controlling
//  the doors of the locoshed.
//
const int nrOfDoors = 2;

TurnoutSwitchTable doorSwitches[nrOfDoors] = {
  {108, false, TTPositionSwitch(A0, false)},
  {109, false, TTPositionSwitch(A1, false)}
};

Een tabel, TTPositions, bevat 18 TTPositionSwitch objecten en de andere tabel, doorSwitches, bevat de 2 objecten welke de schakelaars voor de deuren representeren. Naast de objecten, bevatten de rijen in de tabellen ook het DCC accessory adres waar de DCC commandos naar toe gestuurd worden. Een ander klein verschil is dat in de TTPositions tabel op een moment slechts een van de 18 TTPositionSwitch objecten aan kan staan (op de positie waar de rotary switch op dat moment gepositioneerd is) terwijl de 2 deurschakelaar beide onafhankelijk van elkaar aan of uit kunnen staan.

In de setup() functie worden de TTPositionSwitch objecten met hun pinnen verbonden en slaan we gelijk de staat van de schakelaar op. Die staat wordt ook gelijk doorgestuurd naar de Draaischijf Controller.

  //
  //  Activate the TT position switches and store the current state
  //
  for (int i=0; i<nrOfTTPositions; i++)
  {
    (TTPositions[i].ttPosition).attach();
    TTPositions[i].currentPosition = (TTPositions[i].ttPosition).getState();

    //
    //  Checkt the state, and if active inform the TT controller
    //
    if ((TTPositions[i].ttPosition).getState())
    {
      //
      //  Send the initial position to the TT controller.
      //
      dccAccHandler(TTPositions[i].dccId, (TTPositions[i].ttPosition).getReverseDirection());
    }
  }

  //
  //  Activate the door switches and store the current state
  //
  for (int i=0; i<nrOfTTPositions; i++)
  {
    (doorSwitches[i].ttPosition).attach();
    doorSwitches[i].currentPosition = (doorSwitches[i].ttPosition).getState();
   
    //
    // Send the initial state to the TT controller.
    //
    dccAccHandler(doorSwitches[i].dccId, !(doorSwitches[i].ttPosition).getState());
  }

In de loop() functie wordt dan vervolgens deze TTPositionSwitches constant gemonitored en als we constateren dat een staat is gewijzigd wordt de Draaischijf Controller daarvan d.m.v een bericht verwittigd.

  for (int i = 0; i < nrOfTTPositions; i++)
  {
    //
    //  Force an update of the switch
    //
    (TTPositions[i].ttPosition).heartBeat();
   
    //
    //  If this Position is changed, send the proper DCC accessory message
    //
    if (TTPositions[i].currentPosition != (TTPositions[i].ttPosition).getState())
    {
      TTPositions[i].currentPosition = (TTPositions[i].ttPosition).getState();

      if ((TTPositions[i].ttPosition).getState())
      {
        //
        // The state of the rotational switch has changed, so send the new position
        // to the TT controller.
        //
        dccAccHandler(TTPositions[i].dccId, (TTPositions[i].ttPosition).getReverseDirection());
      }
    }
  }
Dit is de afhandeling van de rotary switch. In dit geval wordt alleen als de switch aangaat een bericht verstuurd omdat de Draaischijf daardoor een opdracht krijgt om naar een nieuwe positie te gaan. Het uitgaan van een switch is niet relevant  voor de draaischijf.

  for (int i = 0; i < nrOfDoors; i++)
  {
    //
    //  Force an update of the switch
    //
    (doorSwitches[i].ttPosition).heartBeat();

    //
    //  If this Position is changed, send the proper DCC accessory message
    //
    if (doorSwitches[i].currentPosition != (doorSwitches[i].ttPosition).getState())
    {
      doorSwitches[i].currentPosition = (doorSwitches[i].ttPosition).getState();
     
      //
      // The state of the door switch has changed, so send the new position
      // to the TT controller.
      //
      dccAccHandler(doorSwitches[i].dccId, !(doorSwitches[i].ttPosition).getState());
    }
  }
Bij de schakelaar voor de deuren worden alle staat wijzigingen doorgestuurd, een deur kan open gaan, maar ook dicht.

De functie dccAccHandler(..) is verantwoordelijk voor het maken van het bericht en het door geven aan de Canbus voor publicatie.

void dccAccHandler(int dccId, bool aDirection)
{
  //
  //  Create the Accessory message
  //
  DCC_ACC_MSG buf;
 
  buf.address = dccId;
 
  if (!aDirection)
  {
    buf.direction = 0;
  }
  else
  {
    buf.direction = 1;
  }

  //
  //  Send the received accessory command
  //
  canBus->sendMsg(DCC_ACC, sizeof(DCC_ACC_MSG), &buf);
}

Tot zover de huidige TableauController. Er zijn nog wel een aantal zaken die in de toekomst als uitbreiding gemaakt gaan worden, want ook de seinen in het station moeten nog op de een of andere manier via het tableau aan gestuurd worden. Verder wil ik in de toekomst nog een paar rangeerpaalseinen (rouwbrief) toevoegen, die zullen dan ook via het tableau gecontroleerd moeten worden.

Groet Meino
A clean desk is a sign of an empty mind

Kranenberg
De CanBus komt naar Kranenberg