/* A program to monitor occupance detectors and send their status to the S88 interface Copyright (C) Meino de Graaf, all rights reserved This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. email: meino@innerside.demon.nl*/#include <KB-Bezetmelder.h>#include <CanBus.h>#define DEBUG 0typedef struct { short systemId; Detector *occ;} occDt;
const int nrOfDetectors = 32;occDt occDetectors[nrOfDetectors] ={ /* Detectors on controller 1 */ { 1, new DTC8("S4", 2,1, 1)}, // 1.01 { 1, new DTC8("S4C", 3,2,14)}, // 2.14 { 1, new DTC8("S5C", 4,2,15)}, // 2.15 { 1, new DTC8("S6", 5,1, 2)}, // 1.02 { 1, new DTC8("S6B", 6,1, 8)}, // 1.08 { 1, new DTC8("S11B",7,1, 6)}, // 1.06 /* Detectors on controller 2 */ { 2, new PMP7("S4B", 3,2,12)}, // 2.12 { 2, new LS("S11A",4,1, 3)}, // 1.03 { 2, new PMP7("S7A", 5,2,13)}, // 2.13 { 2, new LS("S10B",6,1,15)}, // 1.15 /* Detectors on controller 3 */ { 3, new DTC8("S2", 2,1,10)}, // 1.10 { 3, new PMP7("S3B", 3,2,11)}, // 2.11 { 3, new PMP7("S7", 4,1,16)}, // 1.16 { 3, new DTC8("S8A", 5,1, 7)}, // 1.07 { 3, new PMP7("S9B", 6,1,14)}, // 1.14 { 3, new PMP7("S10A",7,2,10)}, // 2.10 { 3, new DTC8("S8", 8,1,13)}, // 1.13 { 3, new PMP7("S3", 9,1,11)}, // 1.11 /* Detectors on controller 4 */ { 4, new DTC8("S1B", 2,2, 2)}, // 2.02 { 4, new DTC8("S4A", 3,2, 9)}, // 2.09 { 4, new DTC8("S5A", 4,1, 4)}, // 1.04 { 4, new DTC8("S12C",5,2, 3)}, // 2.03 { 4, new DTC8("S19A",6,1, 5)}, // 1.05 { 4, new DTC8("S19C",7,2, 6)}, // 2.06 /* Detectors on controller 5 */ { 5, new DTC8("S1", 2,1, 9)}, // 1.09 { 5, new DTC8("S2B", 3,2, 7)}, // 2.07 { 5, new DTC8("S5B", 4,2, 1)}, // 2.01 { 5, new DTC8("S8B", 5,2,16)}, // 2.16 { 5, new DTC8("S9A", 6,2, 8)}, // 2.08 { 5, new PMP7("S9C", 7,1,12)}, // 1.12 { 5, new DTC8("S12B",8,2, 4)}, // 2.04 { 5, new DTC8("S19B",9,2, 5)} // 2.05};
//// The CanBus//CanBus *canBus;//// Interrupt routine for the detectors, that is called when an // Detector wants to report a state.//void stateHandler(byte s88Bank, byte s88Id, bool occupiedState){ // // Send the new state to the S88 Interface // OCC_DT_MSG buf; buf.address = ((s88Bank-1)*16)+(s88Id-1); // convert to a bit index if (occupiedState) buf.state = 1; else buf.state = 0; // // Repeat the message, to cover the occasional mishap // canBus->sendMsg(OCC_DT,sizeof(OCC_DT_MSG), &buf); canBus->sendMsg(OCC_DT, sizeof(OCC_DT_MSG), &buf);#if (DEBUG) Serial.print("[stateHandler], detectornr: "); Serial.print(buf.address); Serial.print(", state: "); Serial.println(buf.state); Serial.flush();#endif // DEBUG}
//// Pins used to define the ID of this controller// It is used to filter out all semaphores which aren't controlled by// this instance of the SeinController//#define PIN_ID_0 A0#define PIN_ID_1 A1#define PIN_ID_2 A2#define PIN_ID_3 A3//// ID that defines this specific instance of the controller.// It's actual value is established during setup and is based on some jumpers//int systemId = 0;void setup(){ // // Based on which pin is connected to ground, the controller ID is established // pinMode(PIN_ID_0, INPUT_PULLUP); pinMode(PIN_ID_1, INPUT_PULLUP); pinMode(PIN_ID_2, INPUT_PULLUP); pinMode(PIN_ID_3, INPUT_PULLUP); systemId = 0; if (!digitalRead(PIN_ID_0)) systemId = systemId+1; if (!digitalRead(PIN_ID_1)) systemId = systemId+2; if (!digitalRead(PIN_ID_2)) systemId = systemId+4; if (!digitalRead(PIN_ID_3)) systemId = systemId+8; #if (DEBUG || BEZETMELDER_DEBUG || CANBUS_DEBUG || CANBUS_DEBUG1) Serial.begin(115200); while (!Serial); Serial.print("Starting Controller ID: "); Serial.println(systemId); Serial.flush();#endif //DEBUG // // Setup and start the CanBus communication // canBus = new CanBus(BEZETMELDER_CONTROLLER_ID+systemId, new Mcp2515(10), nullptr); // Only a sender canBus->begin(); // // Activate all the occupancy detectors and attach them to their pins // for (int i = 0; i < nrOfDetectors; i++) { if (occDetectors[i].systemId == systemId) { occDetectors[i].occ->registerStateHandler(stateHandler); occDetectors[i].occ->attach(); } } // // Wait a second, so the S88 interface is ready // delay(1000); #if (DEBUG) Serial.println("ready"); Serial.flush();#endif}
void loop(){ // // Monitor all attached occupancy detectors // for (int i = 0; i < nrOfDetectors; i++) { if (occDetectors[i].systemId == systemId) { occDetectors[i].occ->heartBeat(); // // Keep the canBus running // canBus->heartBeat(); } }}
const int nrOfOccDetectors = 6 * 16;volatile byte pendingStates[nrOfOccDetectors];volatile byte dataRegister[nrOfOccDetectors];
// // Delay makes reading output signal from next Arduino in chain more reliable. // //delayMicroseconds(2);
Dat wordt gedaan door deze twee interupt routines, die gekoppeld zijn aan pin 2 en 3 (op een Arduino UNO).
void setup(){ // // Intialize the dataRegister // memset(pendingStates,0,nrOfOccDetectors); memset(dataRegister,0,nrOfOccDetectors);
Pointers kunnen als parameters aan methods door gegeven worden, de physieke objecten niet.
daar heb ik veel meer ervaring in dan C++ en daar is alles "By Reference", lees pointers. Daardoor hoef ik me in de code niet af te vragen of ik een -> of een . moet gebruiken.
Als elke s88 melder een 4 of 8 bit Crc achter de data zou plakken en dus 20 of 24 bit zou doorschuiven dan zou dit een prima foutongevoelige bus zijn.
DTC8/DTC2 Dit is een stroomdetector die werkt met een resonantie kring.
PMP7 [...] dat betekend dat voor de bezetmelder sectie beide railstaven onderbroken moeten worden en via de schakeling gevoed worden.
Dit is een abstracte klas omdat hij geen constructor bevat, en kan dus niet zelfstandig geconstrueerd worden,
pinMode(PIN_ID_0, INPUT_PULLUP); pinMode(PIN_ID_1, INPUT_PULLUP); pinMode(PIN_ID_2, INPUT_PULLUP); pinMode(PIN_ID_3, INPUT_PULLUP); systemId = 0; if (!digitalRead(PIN_ID_0)) systemId = systemId+1; if (!digitalRead(PIN_ID_1)) systemId = systemId+2; if (!digitalRead(PIN_ID_2)) systemId = systemId+4; if (!digitalRead(PIN_ID_3)) systemId = systemId+8;
Oei, zonde. Waarom niet gewoon de bytes echt vullen? Kost het je maar 1/8e
PS_Int() is een ISR?
attachInterrupt(S88_CLOCK_INTERRUPT, clock_Int, RISING); attachInterrupt(S88_PS_INTERRUPT, PS_Int, RISING);
Zou niet moeten, zet je de data er wel op de juiste edge op?
Wat doet de tweede ISR dan?
Vertrouw je de initialisatie niet?
Is dit niet erg gevoelig voor lange rails etc? Ken het verder niet namelijk.Zag later pas aan het plaatje dat ze op basis van CT's zijn. Maakt hij überhaupt wel gebruik van een resonantie kring?
Ook deze ken ik niet, maar de conclusie dat beide staven onderbroken moeten worden voor stroomdetectie is niet standaard.
Waarom heb je doAttach() en doHeartBeat() niet gewoon als attach() en heartBeat() gedefinieerd en uitgebreid in de afgeleiden? (Puur interesse!)
Foutongevoelig, ja. Robuust, nee. De thoughput zal er dan flink onder leiden.
….ik hoop alleen dat dit voor lezers met een minder diepere kennis niet al te verwarrend wordt en dat ze afhaken.
#define TURNOUT_STRAIGHT false#define TURNOUT_DIVERGING true
//// Type definition for the call-back routine which will handle a state change//typedef void (*toStateHandler_t)(int toDccAddress, bool toDirection);
//// The abstract base class for handling turnouts//class Turnout{protected: int toPin; // Arduino pin connected to the relay bool targetDirection; // Straight or Diverging bool currentDirection; int dccAddress; // DCC address used for this turnout toStateHandler_t toStateHandler = nullptr; void initTo(int newDccAddress, int newToPin);public: void setTargetDirection(bool newDirection); bool getCurrentDirection(); int getDccAddress(); void registerToStateHandler(toStateHandler_t toStateHandler); virtual void attach(bool initialDirection) = 0; virtual void heartBeat() = 0;};
//// Class for turnouts controlled by a relay.//class RelayTurnout : public Turnout{public: RelayTurnout(int newDccAddress, int newToPin); void attach(bool initialDirection); void heartBeat(); };
#include <Arduino.h>#include <EEPROM.h>#define EEPROM_DEBUG 0
class EepromController{private: const byte eepromStart = 255; const byte eepromEnd = 0; int eepromLength; byte turnoutData[2] = {0, 0}; // For 16 turnouts bool eepromRead = false; int eepromIndex = 0; bool eepromDirty = false;public: EepromController(); // // Load turnout states from EEPROM into the cache buffer // void loadData(); // // Save the cache buffer to EEPROM // void saveData(); // // Retrieve the direction of a turnout from the cache buufer // bool getTurnoutDirection(int turnoutId); // // Set the direction of a turnout in the cache buffer // void setTurnoutDirection(int turnoutId, bool turnoutState); };
/* A program to control turnouts (switches) Copyright (C) Meino de Graaf, all rights reserved This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. email: meino@..........*/#include "CanBus.h"#include <KB_Turnout.h>#include <KB_Eeprom.h>
//// ID that defines this specific instance of the controller.// It's actual value is established during setup and is based on some jumpers//int systemId = 1;//// Pins used to define the ID of this controller// It is used to filter out all semaphores which aren't controlled by// this instance of the SeinController//#define PIN_ID_0 A0#define PIN_ID_1 A1#define PIN_ID_2 A2#define PIN_ID_3 A3
//// The CanBus//CanBus *canBus;//// Data that is stored in EEPROM to remember the state of the// turnouts. It is handled by a eepromController class//EepromController *EepromControl = new EepromController();
typedef struct { short toSystemId; Turnout *to;} toTable;const int nrOfTurnouts = 12;toTable turnouts[nrOfTurnouts] = { {1, new RelayTurnout(1, 25)}, {1, new RelayTurnout(2, 27)}, {1, new RelayTurnout(3, 29)}, {1, new RelayTurnout(4, 31)}, {1, new RelayTurnout(5, 33)}, {1, new RelayTurnout(6, 35)}, {1, new RelayTurnout(7, 37)}, {1, new RelayTurnout(8, 39)}, {1, new RelayTurnout(9, 41)}, {1, new RelayTurnout(10, 43)}, {1, new RelayTurnout(11, 45)}, {1, new RelayTurnout(12, 23)} // Replaced to a new shield, one of the originals failed};
//// A function that executes whenever a message is received from the Canbus// It's purpose is to analyze the received event (Signal or turnout) and// update the proper element in the semaphore table or turnout table.//void dccAccReceiver(unsigned char aMsgLen, DCC_ACC_MSG *msgData){ unsigned short dccId = msgData->address; byte dccState = msgData->direction; // // 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 ((turnouts[i].toSystemId == systemId) && (turnouts[i].to->getDccAddress() == dccId)) { if (dccState == 0) { // // Set the straight direction // turnouts[i].to->setTargetDirection(TURNOUT_STRAIGHT); } else { // // Set the diverging direction // turnouts[i].to->setTargetDirection(TURNOUT_DIVERGING); } // // End the for loop // return; } }}
void turnoutStateHandler(int aDccAddress, bool aDirection){ // // The occupance detectors start on bank 4, each bank has 16 occupancies, so a single // bank is enough for the 12 turnouts in use // int s88Bank = 4; // // The turnout tells us that it has reached it new state, so now we can save it to EEPROM // EepromControl->setTurnoutDirection(aDccAddress, 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 = ((s88Bank - 1) * 16) + (aDccAddress - 1); if (aDirection) buf.state = 1; else buf.state = 0; canBus->sendMsg(OCC_DT, sizeof(OCC_DT_MSG), &buf);}
static unsigned long lastTimeSaved = 0;void setup(){ // // Based on which pin is connected to ground, the controller ID is established // pinMode(PIN_ID_0, INPUT_PULLUP); pinMode(PIN_ID_1, INPUT_PULLUP); pinMode(PIN_ID_2, INPUT_PULLUP); pinMode(PIN_ID_3, INPUT_PULLUP); systemId = 0; if (!digitalRead(PIN_ID_0)) systemId = systemId + 1; if (!digitalRead(PIN_ID_1)) systemId = systemId + 2; if (!digitalRead(PIN_ID_2)) systemId = systemId + 4; if (!digitalRead(PIN_ID_3)) systemId = systemId + 8; // // Setup and start the CanBus communication // canBus = new CanBus(WISSEL_CONTROLLER_ID + systemId, new Mcp2515(49), new Mcp2515(53)); // Sender + Receiver canBus->setMsgReceiver(DCC_ACC, dccAccReceiver); // // Before we start setting the turnouts, introduce a small delay, // so the S88 interface is ready // delay(1000); // // Start the Canbus // canBus->begin(); // // Load the EEPROM data for the turnout directions // EepromControl->loadData(); // // Activate the turnout relays // Set them in the direction which was stoored in the EEPROM // for (int i = 0; i < nrOfTurnouts; i++) { if (turnouts[i].toSystemId == systemId) // Handle only our turnouts { // // Set the stateHandler // turnouts[i].to->registerToStateHandler(turnoutStateHandler); // // Activate the pin and set the initial direction // turnouts[i].to->attach(EepromControl->getTurnoutDirection(turnouts[i].to->getDccAddress())); } } // // Initialize the time for the save loop // lastTimeSaved = millis();}//--(end setup )---
void loop(){ // // Keep the turnouts and the CanBus busy // for (int i = 0; i < nrOfTurnouts; i++) { if (turnouts[i].toSystemId == systemId) // Handle only our turnouts { // // Keep on running // turnouts[i].to->heartBeat(); canBus->heartBeat(); } } // // Calculate the time interval between two consequtive saves to EEPROM // long interval = 60000; // Every minute long timeDifference = millis() - lastTimeSaved; // // When the internal timer does a wrap around, reset stuff // if (timeDifference < 0) { lastTimeSaved = 0; timeDifference = 0; } // // When the time exceeds the set interval, save data to EEPROM // if ( timeDifference > interval ) { EepromControl->saveData(); lastTimeSaved = millis(); }}//-- (end loop)--
//// Table with all active turnouts//const int nrOfTurnouts = 12;Turnout turnouts[nrOfTurnouts] ={ Turnout(1), Turnout(2), Turnout(3), Turnout(4), Turnout(5), Turnout(6), Turnout(7), Turnout(8), Turnout(9), Turnout(10), Turnout(11), Turnout(12)};//// Table of all defined semaphores//const int nrOfSemaphores = 20;Semaphore semaphores[nrOfSemaphores] = { // Semaphore 00a on controller 6 (3hoog Voorsein) Semaphore(CONTROLLER(6), SEMAPHORE(00), NO_STOP_DELAY, new Route(SEMAPHORE(58), SPEED_HIGH, DANGER), VOORSEIN46_DRIEHOOG, new RGBLed(PWM_PIN(2), INTENSITY(200), PWM_PIN(3), INTENSITY(150), PWM_PIN(4), INTENSITY(100)), new RGBLed(PWM_PIN(5), INTENSITY(48), PWM_PIN(6), INTENSITY(20), PWM_PIN(7), INTENSITY(50)), new RGBLed(PWM_PIN(8), INTENSITY(175), PWM_PIN(9), INTENSITY(150), PWM_PIN(10), INTENSITY(40))), // Semaphore 51 on controller 2 Semaphore(CONTROLLER(2), SEMAPHORE(51), DELAY_STOP, new Route(TURNOUT(1), new Route(SEMAPHORE(63)), // Turnout 1 straight SPEED_HIGH, new Route(ROUTE_BARRED), // Turnout 1 diverting SPEED_STOP), SEIN46_EENHOOG, new RGLed(PWM_PIN(2), INTENSITY(18), PWM_PIN(3), INTENSITY(20))), // (blok 2) // Semaphore 52 on controller 2 Semaphore(CONTROLLER(2), SEMAPHORE(52), NO_STOP_DELAY, new Route(TURNOUT(3), new Route(TURNOUT(2), // Turnout 3 straight new Route(ROUTE_BARRED), // Turnout 2 straight SPEED_STOP, new Route(TURNOUT(1), // Turnout 2 Diverting new Route(ROUTE_BARRED), // Turnout 1 straight SPEED_STOP, new Route(SEMAPHORE(63),SPEED_LOW,DANGER), // Turnout 1 diverting SPEED_LOW), SPEED_LOW), SPEED_LOW, new Route(ROUTE_BARRED), // Turnout 3 diverting SPEED_STOP), SEIN46_EENHOOG, new RGLed(PWM_PIN(4), INTENSITY(9), PWM_PIN(5), INTENSITY(10))), // (blok 6)........
//// A function that executes whenever a message is received from the Canbus// It's purpose is to analyze the received event (Signal or turnout) and// update the proper element in the semaphore table or turnout table.//void dccAccReceiver(unsigned char aMsgLen, DCC_ACC_MSG *msgData){ // // Find the proper turnout detector with the matching address // and sets its state. // if (Turnout::updateDirection(msgData->address, msgData->direction)) { // // The dcc id matched a turnout no need to try the semaphores // return; } else { // // the dcc id didn't match a turnout, so try the semaphores // Semaphore::updateState(msgData->address, msgData->direction); }}
void loop(){ // // Are there messages on the CanBus? // canBus->heartBeat(); // // Trigger an update on all semaphores // Semaphore::loop();}