1. huubski
  2. Modelbaanautomatisering
  3. vrijdag 20 november 2020
Ik wil hier een recent Arduino-project van me voorstellen. Sommigen zullen het misschien al kennen van het forum van modelspoorwijzer.
Mijn baan wordt aangestuurd door Koploper. In mijn ogen nog steeds een topper op het gebied van automatisch rijden, maar het heeft ook wat mogelijkheden om (beveiligd) handmatig te rijden. En handmatig is voor mij: vingers weg van PC-toetsenbord en muis. Dus gewoon knopjes en handregelaars om een trein over het emplacement te sturen. Koploper heeft daarvoor de mogelijkheid van een extern tableau.
Ik gebruik dit vooral om lekker te rangeren met een buurtgoederentrein, terwijl het overige treinverkeer gewoon doorgaat.
Voor die buurtgoederentrein heb in mijn baan een spoor in het schaduwstation waarop ik die kan samenstellen.
Die goederentrein kan ik dan met een druk op een knop richting station sturen, daar neem ik hem over met het extern tableau.
Oorspronkelijk kon dat met een vaste loc, als ik een andere loc voor de trein wilde gebruiken, moest ik met de PC aan de slag. Later heb nog een aanpassing gedaan waardoor ik met knopjes uit twee loc's kon kiezen.

Maar eigenlijk wilde ik meer mogelijkheden hebben, liefst zelfs dat ik ook de vrijheid heb om dat spoor te gebruiken om ook andere treinen de baan op te sturen. Denk bijvoorbeeld aan die loc die eigenlijk niets op de baan te zoeken heeft, maar ik zo mooi vindt dat hij af en toe een rondje mag rijden, maar waarvoor ik geen ruimte meer heb op de bestaande opstelsporen.

Koploper heeft een optie om buiten de pc om een trein op de baan te kunnen zetten en het automatisch rijden te starten: extern starten locomotief.
Ik meen dat die optie ooit eens specifiek voor een club is ingebouwd, maar zoals vrijwel alles in koploper, daarna gewoon voor iedereen beschikbaar kwam.
De werking in het kort, je zorgt voor 14 schakelaars die aan een bezetmelder hangen. Met 10 van die schakelaars toets je het decoderadres van je loc in, de andere vier zijn om de invoer te resetten, de rijrichting te testen, de rijrichting om te draaien en tenslotte de loc in het blok te plaatsen en het automatisch rijden te starten.

Nu ben ik voornamelijk DB-rijder en daarbij is het heel lastig om het locnummer te vertalen naar een maximaal 4-cijferig decoderadres. De nummers van het materiaal bestaan uit letters en tot zeven cijfers (tp.III) of zes cijfers (tp.IV).
Ik heb geen logica kunnen bedenken, waarbij ik aan het nummer van een loc kan herleiden welk decoderadres die heeft, zonder dat er dubbele adressen voorkomen. Er is altijd wel ergens een uitzondering op de regel. Als ik ook nog mijn niet Duitse materieel erbij betrek, wordt het helemaal erg.

Wat ik dus wilde is een handzaam kastje met een display waar ik door een lijst met de volledige loc-nummers kan scrollen. Uit die lijst eentje kiezen en dan zorgen dat het kastje ervoor zorgt dat het decodernummer in Koploper komt.
Daarbij kwam de Arduino in beeld.
Mijn gedachte was om met wat knoppen en een lcd-scherm door zo'n lijst te scrollen en dat vervolgens de Arduino de virtuele bezetmeldingen genereert die via loconet naar de centrale gaan en waar Koploper dan iets mee doet.
Vorig jaar al eens met Cees en Daan kort over gesproken of mijn idee überhaupt mogelijk zou zijn, maar daarna weer in de spreekwoordelijke la verdwenen.

Tot vorige week dan.

In het oorspronkelijke plan had ik alleen een scroll-lijst voorzien, maar ik heb er uiteindelijk voor gekozen om het kastje twee modi te geven. Aan de ene kant dus die scroll-lijst, maar ik kan ook direct een decodernummer intoetsen. Dan kan ik de scroll-lijst beperken tot regelmatig gebruikte loc's op dit spoor, terwijl ik toch de mogelijkheid hou om elke loc op deze manier te starten.

Men neme dus een Arduino (in dit geval de Uno, de "nette" versie wordt waarschijnlijk een Nano), een loconet-interface een keyboard (4x4) en een LCD-scherm. Voor de laatste twee heb ik niet een I2C versie gebruikt, kan natuurlijk wel.

Hoe werkt het nu?
Ik kom eerst in een soort van welkomstscherm terecht.
Druk ik op * of #, dan kom ik in de scroll-lijst, waar ik met die twee toetsen ook doorheen kan scrollen.
Druk ik als eerste op een cijfer, kom ik in de directe invoer. Daarbij gelden steeds de laatste vier ingetoetste cijfers als adres. Als ik dus een fout maak, gewoon nog een keer opnieuw het adres intikken.
Als ik daarna op de A druk, wordt eerst een reset-opdracht verstuurd naar Koploper om eventueel bestaande informatie te wissen en daarna het decoderadres in de vorm van vier losse bezetmeldingen. Als ik later nog een keer op A druk, wordt de hele boel weer gereset en mag ik weer overnieuw beginnen. Dit werkt tot ik op de D heb gedrukt.
Met een druk op de B kan ik de rijrichting te testen, de locomotief krijgt dan kort de opdracht om te rijden. Als de loc de verkeerde kant op rijdt, kan ik de rijrichting omkeren door op de C te drukken.
En tenslotte kan ik met een druk op de D de loc echt in het blok plaatsen en wordt het automatisch rijden gestart.

Hier een filmpje waar de handelingen in beeld zijn gebracht.

En voor de liefhebber de Arduino-code tot nu toe. Een beetje van wat ik heb gevonden op internet en een beetje van mezelf.
Er zullen nog wel wat verbeteringen in worden aangebracht, want er kan nog wel wat gecomprimeerd worden. Maar met deze code werkt het wel al.
Ook moet natuurlijk alles nog in een mooi kastje worden ingebouwd.


/*
Extern starten locomotief is in Koploper een hulpmiddel om zonder tussenkomst van muis
en_of toetsenbord een locomotief in een blok te plaatsen en te laten starten met
automatisch rijden.
Koploper maakt daarvoor gebruik van 14 bezetmeldpunten:
- 10 voor de cijfers 0 - 9 om het decoderadres in te tikken
- 1 om aanwezige inhoud te resetten en het ingetikte decoderadres over te nemen
- 1 om de rijrichting van de loc te testen (door kort een rijopdracht te geven)
- 1 om de rijrichting indien nodig om te keren
- 1 om de loc daadwerkelijk in het blok te plaatsen en het automatisch rijden te
starten.

Deze sketch maakt het mogelijk dat dit niet alleen werkt met directe invoer van
deocderadressen, maar ook locs in een scroll-lijst te zetten met een omschrijving.
De sketch zorgt dan dat de benodigde bezetmeldingen naar Koploper worden gestuurd.
*/

// include the library codes voor loconet, het lcd-scherm en het keypad
#include <LocoNet.h>
#include <LiquidCrystal.h>
#include <Keypad.h>


///// Knmerken keypad /////
const byte ROWS = 4; // vier rijen
const byte COLS = 4; // vier kolommen

// knoppen koppelen aan een array voor de keymap
// waarden zijn ascii
char hexaKeys[ROWS][COLS] = {
{49, 50, 51, 65}, // 1, 2, 3, A
{52, 53, 54, 66}, // 4, 5, 6, B
{55, 56, 57, 67}, // 7, 8, 9 ,C
{42, 48, 35, 68}, // *, 0, #, D
};

byte rowPins[ROWS] = {10, 7, 14 , 15}; // Pinnen gebruikt voor de rijen van het keypad
byte colPins[COLS] = {16, 17, 18, 19}; // Pinnen gebruikt voor de kolommen van het keypad

// Initialiseer het Keypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//// Loconet ////
#define TX_PIN 9

static LnBuf LnTxBuffer;
static lnMsg *LnPacket;

unsigned int address_received;
unsigned int myAddress;

byte numSensors = 14; // can change to suit the number of sensors used, optional
boolean sensorValue[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // default low
byte addrOffset;
byte locRow = 0;
byte inputMode = 0;
byte inputModeOld;
int i;
int locadr[4] = {0, 0, 0, 0};
int locAdresKey;
int locadres;

struct locList {
String locDescr;
int locAdresList;
}

// de scroll-lijst voor de locomotieven met omschrijving en decoderadres.
// Lijst kan worden ingekrompen of uitgebreid, maar de laatste waarde moet (" ", 0} zijn
locList[] = {
{"DB 141 447", 1417},
{"DB 211 082", 2112},
{"DB 211 159", 2119},
{"DB 212 064", 2121},
{"DB 215 035", 2151},
{"DB 215 070", 2150},
{"DB 260 319", 2609},
{"DB 323 597", 3235},
{"DB 795 201", 7951},
{"DB Klv 12", 12},
{"DR 132 577", 132},
{"NS 2433", 2433},
{"OBB 1044", 1044},
{"SBB 11117", 1117},
{"SBB 13254", 1324},
{" ", 0}
};

//berekent het aantal regels in de scroll-lijst
int listLength = sizeof(locList) / sizeof(locList[0]);

void setup() {
// Configure the serial port for 57600 baud
Serial.begin(57600);

// Initialize the LocoNet interface, specifying the TX Pin
LocoNet.init(TX_PIN);

// Initialize a LocoNet packet buffer to buffer received bytes
initLnBuf(&LnTxBuffer) ;

// Read the decoder base address
myAddress = 33; // basisadres = 33 (detector 3.1 - 3.14

// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
welkom();
}

void loop() {
LnPacket = LocoNet.receive() ; // Check for received LocoNet packets

if (LnPacket)
{
LocoNet.processSwitchSensorMessage(LnPacket); // process the packet and fetch address received
//printRXpacket (); // comment out in working version
if ( LnPacket -> data[0] == 0x83 ) sendAllsensors (); // Global power-on update request

}



// Lees de ingedrukte knop uit
char button = customKeypad.getKey();

if (button) {
lcd.clear();
inputModeOld = inputMode;
// als * of # wordt gedrukt, invoer via loclijst
if (button == 35 || button == 42) {
inputMode = 1;
// Serial.println(inputMode);
}
// als een cijfer wordt gedrukt, directe invoer decodernummer
if (button >= 48 && button <= 57) {
inputMode = 2;
// maximaal vier cijfers voor decodernummer, nummer schuift steeds 1 positie op naar links
locadr[3] = locadr[2];
locadr[2] = locadr[1];
locadr[1] = locadr[0];
locadr[0] = (button - '0'); // maak int van char variabele
// maak int van ingedrukte nummertoetsen
locAdresKey = (locadr[3] * 1000) + (locadr[2] * 100) + (locadr[1] * 10) + (locadr[0]);
lcd.setCursor(0, 0);
lcd.print("Decodernummer:");
lcd.setCursor(0, 1);
lcd.print(locAdresKey);
}
// negeer letters die te vroeg worden gedrukt
if ((inputMode != 3 & button > 65) || (inputMode == 0 & button == 65)) {
welkom();
}


if (inputMode == 1) {

// scroll met * naar boven
if (button == 42) {
if (locRow > 0) {
locRow = locRow - 1;
}

}
// scroll met # naar beneden
if (button == 35) {
// voorkom dat verder wordt gescrolld dan aantal locs
if (locRow < (listLength - 2)) {
locRow = locRow + 1;
}

}
// bij eerste keer drukken * of #, ga naar eerste loc in lijst
if (inputModeOld != inputMode) {
locRow = 0;
}
lcd.setCursor(0, 0);
lcd.print(">");
lcd.setCursor(2, 0);
lcd.print(locList[locRow].locDescr);
lcd.setCursor(2, 1);
lcd.print(locList[locRow + 1].locDescr);
}

// if (inputMode == 2) {


// }

// als A wordt ingedrukt, voer de resetroutine uit
if (button == 65) {

reset();
// als A door gebruiker als reset is bedoeld, zet het locadres op 0 en keer
// terug naar het welkomst-scherm
if (inputMode == 0 || inputMode == 3) {
locadres = 0;
inputMode = 0;
welkom();
}

// ala A door gebruiker als keuze loc uit lijst is bedoeld, haal het locadres
// van de gekozen loc uit de array
if (inputMode == 1) {
locadres = locList[locRow].locAdresList;
}
Serial.println(locadres);
// ala A door gebruiker als keuze loc uit lijst is bedoeld, gerbuik voor het
// locadres de waarde van de ingetoetste cijfers
if (inputMode == 2) {
locadres = locAdresKey;
}
// als het locadres niet 0 is, splits het dan in vier losse cijfers die de
// corresponderende bezetmelder bepalen
if (locadres != 0) {
// Serial.print("Stuur locadres voor: ");
// Serial.println(locadres);

addrOffset = (locadres % 10000 - locadres % 1000) / 1000;
sendSensorPulse ();
addrOffset = (locadres % 1000 - locadres % 100) / 100;
sendSensorPulse ();
addrOffset = (locadres % 100 - locadres % 10) / 10;
sendSensorPulse ();
addrOffset = locadres % 10;
sendSensorPulse ();
for (int i = 0; i <= 3 ; i++) {
locadr[i] = 0;
}
// ga naar de modus om de gekozen loc in het automatisch rijden op te nemen
inputMode = 3;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("B: test rij-");
lcd.setCursor(0, 1);
lcd.print(" richting");
}
}


if (inputMode == 3) {
// als op B wordt gedrukt, activeer de bezetmelder om de rijrichting te testen
if (button == 66) {
Serial.print("Stuur opdracht voor testen rijrichting van loc: ");
Serial.println(locadres);
Serial.println("11");
addrOffset = 11;
sendSensorPulse ();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("C: loc omkeren");
lcd.setCursor(0, 1);
lcd.print("D: start rijden");
}

// als op C wordt gedrukt, activeer de bezetmelder om de rijrichting om te keren
if (button == 67) {
Serial.print("Stuur opdracht voor wisselen rijrichting van loc: ");
Serial.println(locadres);
Serial.println("12");
addrOffset = 12;
sendSensorPulse ();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("D: start rijden");
}

// als op D wordt gedrukt, activeer de bezetmelder om de loc in het blok te plaatsen
// en automatisch rijden te starten
if (button == 68) {
Serial.print("Stuur opdracht voor automatisch rijden van loc: ");
Serial.println(locadres);
Serial.println("13");
addrOffset = 13;
sendSensorPulse ();
inputMode = 0;
lcd.clear();
welkom();
}
}
}
}



void welkom() {
// welkomstscherm
lcd.setCursor(0, 0);
lcd.print("* of #: loclijst");
lcd.setCursor(0, 1);
lcd.print("getal : dec.nr");

}

void reset() {
// resetroutine: activeer de bezetmelder om de invoer te resetten en ga naar de routine
// om die te verzenden
Serial.println("Stuur resetopdracht");
Serial.println("10");
addrOffset = 10;
sendSensorPulse ();
}


void sendAllsensors () {
// stel alle bezetmelders op 0 (niet bezet) en stuur die naar loconet
unsigned int tempaddr;
tempaddr = myAddress + 1; // point to first sensor address
for (i = 0; i < numSensors; i++)
{
sensorValue[i] = 0;
LocoNet.reportSensor(tempaddr + i, sensorValue[i]);
//Serial.print("Sent sensor : ");
//Serial.println(i);
}
}



/////////////////////////////////////////////////////////////////////
void sendSensorPulse () {
// stel de gekozen bezetmelder een korte tijd op 1 en dan weer teru naar 0 en die naar loconet
for (i = 0; i <= 1; i++) {
sensorValue[addrOffset] = 1 - i;
LocoNet.reportSensor(myAddress + addrOffset, sensorValue[addrOffset]);
Serial.print(myAddress + addrOffset);
Serial.print(" ");
Serial.println(sensorValue[addrOffset]);
if (addrOffset == 11 & i == 0) { // verlenging bezetpuls toets B (testen rijrichting)
delay(2000);
}
delay(50); // pulsduur en tijd tussen twee bezetmeldingen
}
}


groet, Huub
Reacties (2)
Geaccepteerd antwoord In Afwachting Moderatie
Mooi uitvoerig verslag Huub,
Leuk dat andere treinliefhebbers jou ervaring mee kunnen lezen.

Groetjes Rob
Geaccepteerd antwoord In Afwachting Moderatie
Ik heb nog een kleine aanpassing gedaan in de sketch.
Het blijkt namelijk dat koploper bij het testen van de rijrichting alleen een rijopdracht naar de loc stuurt zolang de betreffende bezetmelder bezet is.
Dat was hier 50 ms en dat is echt te weinig om te zien of de rijrichting klopt.
Omdat de keypad library die ik gebruik alleen een korte puls genereert, kan ik niet zien hoe lang de gebruiker de toets B voor het testen indrukt.
Ik heb nu voor een snelle oplossing gekozen, door die bezetmelding met twee seconde te verlengen.
Ook dit gaat met een delay. Op zich niet de meest elegante oplossing, want de arduino pauzeert dan even, maar voor dit doel geen probleem. En wel lekker simpel.
Als die twee seconde toch te kort is, moet ik een andere oplossing bedenken.

groet, Huub
  • Pagina :
  • 1


Er zijn nog geen reacties op dit bericht.
Reageer als een van de eersten op dit bericht!
Nog geen HCC-gebruikersaccount aangemaakt? Klik dan hier.

Inloggen