Arduino DCC dekóder

Eszközdekóder építése
Vasútmodell méretarányok

Eszközdekóder Arduino Nanoval

Ahogy a DCC bevezető részében említettük, a rendszer fontos jellemzője az egyszerűség.


Ez azért is fontos, mert a járművekbe telepített dekóderek mérete kulcsfontosságú, egy modellben az elektronikának a lehető legkisebb helyet kell elfoglalnia.


Egy Arduino Nano mérete miatt nem alkalmas arra, hogy járművekbe kerüljön. Arra viszont, hogy eszközdekóderként használva megismerkedjünk a DCC működésével, tökéletesen megfelel.


Ráadásul, az Arduinohoz rengeteg kiegészítő és modul érhető el (pl. szervó vezérlő), amelyek megkönnyítik a fejlesztést.

A hardware

💡 Breadboard: Ha csak most kezdünk ismerkedni a az elektronikával, érdemes beszereznünk egy próbapanelt, "breadboard"-ot. Ezzel forrasztás nélkül, azonnal összeállíthatjuk a kapcsolásunkat, és könnyedén módosíthatjuk is azt.

🔧 Hozzávalók

  • Arduino Nano (vagy kompatibilis)
  • 1N4148 dióda
  • 1 kΩ ellenállás
  • 6N137 optocsatoló
  • Csatlakozó (opcionális, hogy a sínjelet csatlakoztathassuk)
A kapcsolás nagyon egyszerű. Az optocsatoló bemenetére érkezik a dióda és az ellenállás segítségével átalakított DCC jel. Az optocsatoló kimenete pedig közvetlenül az Arduino egyik digitális bemenetére csatlakozik. A breadboardon az alábbi módon köthetjük össze az alkatrészeket: Arduino DCC dekóder kapcsolás ... és így néz ki a valóságban (itt egy kicst már módosítottunk, hozzáadtuk a címkiválasztó gombot is, erről később még lesz szó):
Arduino DCC dekóder valóságban A kapcsolási rajzot az alábbi ábra mutatja: Arduino DCC dekóder kapcsolási rajz

A software

A kódot - az érthetőség kedvéért - a lehető legegyszerűbben próbáljuk megvalósítani, ezért az Arduino egyik LED-jét fogjuk vezérelni.


Így a hardware és a software is a lehető legkevesebb elemből áll.

🔧 Hozzávalók

  • Számítógép :)
  • USB C kábel
  • Arduino IDE fejlesztő környezet
Kössük össze az Arduino Nano-t a számítógépünkkel az USB C kábellel, majd indítsuk el az Arduino IDE-t.
Válasszuk ki a megfelelő portot az "Eszközök" menüben.
Port
... és a megfelelő boardot az "Eszközök" menüben. Board

Arduino kód

Másoljuk be az alábbi kódot az Arduino IDE-be.


Mielőtt feltöltenénk az eszközre, állítsuk be a dekóder címét. Ezt a 20-as sorban találjuk. A 0-t kell kicserélni a kívánt címre.

⚠️ Figyelem! A DCC eszközcímek a Roco MultiMaus-nál 1-től kezdődnek, tehát a beállított címhez 1-et hozzá kell adni a MultiMaus-on!

Ezután csatlakoztassuk az Arduino-t a számítógéphez, és töltsük fel a kódot.

💡 A bemutatott megoldásnál törekedtem az egyszerűségre és hogy a rendszer a legérthetőbb legyen. Kísérletezzünk bátran a továbbfejlesztésével!
📄 dcc_arduino.ino
#include "Arduino.h"

#define PIN_DCC_IN 2
#define PIN_BUILTIN_LED 13

#define BITARRAY_LEN 145
#define BYTEARRAY_LEN BITARRAY_LEN / 8

static bool interruptOccured = false;
static unsigned long interruptTime;
static unsigned long lastTime;
static bool preambleCompleted = false;
static byte preambleCounter = 0;
static byte bitArray[BITARRAY_LEN];
static byte byteArray[BYTEARRAY_LEN];

static byte  bitCounter = 0;
static char  byteCounter = 0;
static int interruptOn = RISING;
static int decoderAddress = 0; // Roco Address-1

void interruptHandler() {
  interruptOccured = true;
  interruptTime = micros();
}

void setup() {
  pinMode(PIN_DCC_IN, INPUT_PULLUP);
  pinMode(PIN_BUILTIN_LED, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_DCC_IN),interruptHandler, interruptOn);
}

void loop() {
  packetParse();
}

void fillByteArray() {
  byteCounter=0;
  byte mask=0x80;
  byteArray[byteCounter]=0;
  for(int i=0;i < bitCounter;i++) {
    if (i % 9 != 8) {
      byteArray[byteCounter] = bitArray[i] ? byteArray[byteCounter] | mask : byteArray[byteCounter] & (~mask);
      mask >>= 1;
    } else {
      mask=0x80;
      byteCounter++;
      byteArray[byteCounter]=0;
    }
  }
  byteCounter++;
}

void processPacket() { 
  if (checkCrc()) {
    int absAddress = ((byteArray[0] & 0x7F) | (((~byteArray[1]) & 0x70) << 2)) << 3 | (byteArray[1] & 0x7);
    int address = absAddress >> 1;
    int side = absAddress & 1;
    int state = (byteArray[1] >> 3) & 1;
    if (address == decoderAddress) {
      digitalWrite(PIN_BUILTIN_LED,state);
    }
  }
}

bool checkCrc() {
  fillByteArray();
  byte crc=0;
  for(int i=0;i < byteCounter-1;i++) crc ^= byteArray[i];
  return (crc == byteArray[byteCounter-1]);
}

void packetParse() {
  if(interruptOccured) {
    int elapsedTime = interruptTime - lastTime;
    if(elapsedTime > 10) {
      if (elapsedTime > 128 && elapsedTime < 180) { // Invalid Bit, let's change interrupt
          preambleCompleted = false;
          interruptOn = interruptOn == RISING ? FALLING : RISING;
          detachInterrupt(digitalPinToInterrupt(PIN_DCC_IN));
          attachInterrupt(digitalPinToInterrupt(PIN_DCC_IN),interruptHandler, interruptOn);
      } else {
        bool thisBit = elapsedTime < 129;
        if (preambleCompleted) {
          bitArray[bitCounter] = thisBit;
          if ((bitCounter == BITARRAY_LEN-1) || ((bitCounter > 25) && thisBit && ((bitCounter % 9) == 8))) {
            if (bitArray[0] && !bitArray[1] && bitArray[9]) processPacket(); // Accessory packet
            preambleCompleted = false;
          }
          bitCounter++;
        } else {
           if (thisBit) preambleCounter++;
           else {
            if (preambleCounter > 11) {
              preambleCompleted = true;
              bitCounter=0;
            }
            preambleCounter = 0;
           }          
        }
      }
    }
    lastTime = interruptTime;
    interruptOccured = false;
  }
}