OpenDCC BiDiBOne

    Vorbemerkung: Diese Seite deckt mehr die technischen Aspekte der Baugruppe ab, für den Anwender finden sich wichtige Informationen auf www.fichtelbahn.de.

Überblick, Entstehung

    BiDiBOne Ansicht oben BiDiBOne ist ein kleines Einsteckmodul, welche alle notwendigen Komponenten für einen BiDiB-Knoten bereits enthält. So ist das Businterface, die Buchsen, der Identify-Taster und Status-LED bereits vorhanden.
    Ein moderner Schaltregler erzeugt die benötigten Spannungen von 5V und 3,3V, diese können auch zur Versorgung der Baugruppe verwendet werden, in welche das Modul einsteckt wird.
    Mit BiDiBOne lassen sich eigene Projekte realisieren und mit dem BiDiBus verbinden. Eine kleine Auswahl an bereits begonnen Projekten findet sich weiter unten, der Phantasie sind kaum Grenzen gesetzt!

    Das Modul entstand, weil man beim Selbstbau mit aktueller Technik vor der Herausforderung steht, dass aktuelle Prozessoren und Schaltregler mit kleinen Pinabständen teils nicht leicht zu beschaffen sind und auch 'gewisse' Herausforderungen an die Lötfähigkeiten stellt.

    Das Modul BiDiBOne ist die Lösung für dieses Problem! Alle 'schwierigen' Bauteile sind SMD-vorbestückt, als Anwender kann man mit einer üblichen Lochrasterplatine weiterbauen.
    Mit BiDiBOne lassen sich eigene Schaltpulte, Sonderdekoder, Optomodule, Zugpaternoster, usw. realisieren.
      Daten BiDiBOne
      Aufsteckbar mit zwei 20-poligen Steckerleisten im Raster 2,54mm, geeignet für Experimentierplatinen.
      max. 28 frei programmierbare Ein- und Ausgänge
      max. 8 analoge Ein- und Ausgänge
      max. 2 serielle Schnittstellen, davon eine für FTDI-Kabel vorbereitet.
      max. 2 SPI und I2C Schnittstellen
      max. 12 PWM Kanäle
      Eingangsspannung 6V-17V, Schaltregler für 5V mit 700mA.
      Maße: 38.5mm x 38.5mm, 2 * 20 Anschlußstifte. Alle Pins im Raster 2,54mm
      1 BiDiBus-Anschluß (zwei RJ45 Buchsen)
      4 Kontroll-LEDs für Power, Identify, Message und BiDiB
      Identify-Taster
      Zwei Prozessorversionen: Standard: Atxmega128D3, 128k Flash, 8k RAM; Plus: Atxmega128A3
      Bootloader, automatische Anmeldung am Bus, FW-Update über Bus (kein Programmer erforderlich)

Download

    Die Firmware für BiDiBOne-Projekte vom OpenDCC findet sich auf der → Downloadseite. Weitere Projekte sind u.a. auf http://www.fichtelbahn.de verfügbar.

    Hinweis:
    Es gibt zwei verschiedene Varianten des BiDiBOne, diese unterscheiden sich im bestückten Prozessor (Atxmega128D3 oder Atxmega128A3). Der A3 bietet zusätzliche Peripheriemodule, mehr CPU-Register und DMA.
    Wird bei der verfügbaren Firmware für die Applikation nicht daraufhingewiesen bzw. nicht durch eine Bezeichnung in der Firmwaredatei gekennzeichnet, dass diese Firmware für einen bestimmten BiDiBone geeignet ist, dann kann die Firmware auf beiden Modulen (BiDiBone und BiDiBonePlus) zum Einsatz kommen.
    Wenn die Firmware mit dem Zusatz STD (oder STANDARD) bzw. PLUS gekennzeichnet ist, dann ist diese Firmware nur auf D3 bzw. A3 (PLUS) lauffähig.
    Sollte eine falsche Firmware geladen worden sein, so blinken die beiden LEDs BiDiB und Power schnell im Wechsel. Durch Druck auf den Taster kommt man zurück in den Bootloader.

Projekte

    Folgende Projekte sind verfügbar / geplant:
ProjektTrägerboardBeschreibungStatus
OneHub OneInterface Ein Baustein zur Buserweiterung, weitere 31 BiDiB-Knoten sind anschließbar. released, download verfügbar.
OneDMX OneInterface Raumlichtsteuerung mittels handelsüblichen DMX-Modulen und DMX-Dimmer released, download verfügbar.
OneControl OneControl 8 Servos, 16 Powerausgänge, 16 IO released, download verfügbar bei fichtelbahn.de
OneST OneST 4-fach Servodekoder mit Herzstückpolarisation, Lagemeldung, opt. GBM16T Anschluß Entwicklung abgeschlossen, Download verfügbar
OneDT OneDriveTurn 8 Servos, 8 umpolbare Leistungsausgänge für Motorweichen, 16 IO in Entwicklung, erste Hardwaremuster, Testphase
OneOC OneOpto 20 Eingänge (Belegtmelder), optogekoppelt beta, abschließende Tests
OneRFM OneRFM 8 Eingänge, Funksender 2,4GHz (auf Basis RFM) für OpenCar-System in Entwicklung
OneStep OneStep Schrittmotorsteuerung für Drehscheiben und Sonderanwendungen in Entwicklung
OneTester OneTester 24 LEDs direkt auf der Platine, Lauflicht verfügbar
OneBreadBoard OneBread 4 LEDs frei jumperbar, Lötfeld, Serial und SPI auf Stecker verfügbar
OneSpy OneTester Spion, mit Debug/Log Möglichkeit und HSI88 bzw. RCTALK verfügbar

Schaltplan BiDiBOne



  • Spannungsversorgung
    Aus der Speisespannung des Moduls wird mit einem Schaltregler eine 5V Spannung gewonnen. Diese steht an einen Pin auch für Module zur Verfügung und darf mit bis zu 500mA belastet werden.
    Der Prozessor läuft mit 3,3V und wird mittels Längsregler (LM3480) aus den 5V versorgt. Diese 3,3V stehen auch extern zur Verfügung und dürfen mit 40mA belastet werden.
    D.h. BiDiBOne wird mit nur mit _einer_ Spannung im Bereich 7..17V versorgt (nominal 12V).
    Hinweis zum Startverhalten des Längsreglers: Der LM3480 hat gelegentlich Startprobleme bei Start in 'prebiased load', d.h. wenn beim Einschalten am Ausgang bereits eine Spannung anliegt. Dies kann z.B. der Fall sein, wenn ein über ein angestecktes FTDI-Debugkabel und dort aktivierte Treiber über die die Datenpins des Prozessors ein Leckstrom auf die 3,3V kommt und diese leicht anhebt.
    Abhilfe: Entweder die 3,3V leicht belasten (z.B. mit einem 1kOhm Widerstand) oder eine Schottkydiode von 3,3V nach 5V anordnen. Die kann auch direkt am Regler nachträglich bestückt werden.
  • BiDiB
    Es ist ein normales Bus-Interface mit einem RS485 Baustein (500kBaud) verbaut. Auf die Terminierungssteckbrücke für die DCC-Verteilung am BiDiBus wurde verzichtet, BiDiBOne sollten als nicht am Ende eines BiDiBus-Systems stecken, weil sonst ev. die zulässige unterminierte Leitungslänge für die DCC-Verteilung überschritten wird. Diese Länge am BiDiBus darf max. 5m betragen.
    ID-Taster und Kontroll-LED zum Anzeigen des Identify-Zustandes sind auf dem Modul vorhanden und brauchen nicht auf dem Grundboard implementiert werden.
  • Prozessor
    Es findet die Atxmega128D3 oder Atxmega128A3 (Variante BiDiBOne-Plus) im QFN64-Gehäuse Verwendung. Alle Ein- und Ausgänge sind mit Serienwiderständen 47 Ohm geschützt. BiDiBonePlus ist der Nachfolger des BiDiBone und unterscheidet sich nur beim eingesetzten Prozessor (ATXmega128A3).
    Die Taktversorgung des Prozessors erfolgt mit einem externen Quarz mit 8 MHz, diese Frequenz wird i.d.R. durch die Einstellungen der Firmware mittels einer PLL auf 32 MHz vervielfacht.

Layout


    Die Platine ist nur 38,5 x 38,5mm groß, alle Anschlüsse sind im Raster 2,54mm verlegt und können mit handelsüblichen Stiftsteckleisten im Raster 2,54mm kontaktiert werden. Im Download befindet sich eine eagle-lbr mit dem BiDiBOne für die leichtere Integration in eigene Schaltungen.

Seriennummern beim BiDiBOne

  • Die Seriennummer des BiDiOne wird mit dem online-Generator erzeugt.
  • Jeder BiDiBOne bekommt eine Serienummer, diese gilt für alle Projekte. Die Projekte haben jedoch unterschiedliche Produkt-IDs.
  • Jedes Projekt sucht die Seriennummer zuerst in der Signatur, wird dort nichts gefunden, wird im EEPROM gesucht (Das File des Serienummergenerators beschreibt das EEPROM). Wird dort eine Nummer gefunden, wird diese in die Signatur kopiert und verwendet. Wird nichts gefunden, bleibt das Applikationsprogramm (mit Fehlercode blinkend) stehen.
  • Der Bootloader macht es genauso, mit einer Ausnahme: Wird nichts gefunden, dann verwendet der Bootloader die im Chip hinterlegten Wafercoordinaten X/Y als Seriennummer und hofft darauf, dass kein zweiter BiDiBOne ohne Seriennummer, jedoch mit den gleichen Wafer-Koordinaten im System ist.

Firmware-Infos (für Entwickler)

    Für BiDiBOne sind die Grundfunktionen als Projekt für einen leichten Start ladbar. Das Projekt enthält folgende Funktionen:
  • Prozessorinitialisierung
  • Echtzeitsystem CORTOS
  • BiDiB-Interface-Library
  • Debug-Inferface inkl. Parser und Ausgaberoutinen

Einbau eigener Funktionen

    Ein eigenen Modul hat typischerweise mehrere Interaktionspunkte mit dem System, im folgenden wird beschrieben, wie diese Interaktion beim BiDiBOne eingebaut wird.

Initialisierung

    Die notwendigen Einstellungen der Hardware (z.B. Portkonfiguration) werden in einer Init-Routine zusammengefaßt. Diese Init-Routine konfiguriert auch notwendige Interrupts und gibt die jeweiligen Interrupt-Level frei, am globalen Interrupt-enable wird jedoch nicht gedreht. Der Aufruf dieser Init-Routine wird in main.c eingetragen.

Laufzeitaufrufe

    Die Laufzeit wird mittels cortos abgebildet. Cortos ist ein sehr einfacher kooperativer Kern: man bekommt die Kontrolle über die CPU, führt die anstehenden Aufgaben durch und gibt wieder zurück (das bedeutet, man ist fair, also cooperative). Cortos kennt nur statische Tasks, d.h. man muß seine eigene Task in die entsprechenden Listen eintragen:
  • cortos_tasklist.h:
    Hier muß man im enum (das ist eine Aufzählliste) t_task_id eine Zeile für die eigene Task hinzufügen. Beispiel:
    typedef enum {  TASK_IDLE,           // must be the first one, low prio
                    TASK_POWER_UP,       // long term init at power up
                    TASK_KEYBOARD,       // local keyboard (here only PROG)
                    TASK_DEBUG_IF,       // host parser for debugger
                    TASK_MACRO,          // Macro Engine
                    TASK_EVENT_HANDLER,  // Handling of DCC events
                    TASK_LED_STATUS,     // LED driver status
                    TASK_SERVO,          // recalc new servo controls
                    TASK_BIDIB,          // BiDiB Client
                    TASK_BIDI_CH1,       // Channel 1 message handler (upstream)
                    TASK_ANALYSE_BIDI,   // Bidi Data recovery
                    TASK_DCC_DECODE,     // DCC decoder (must have higher prio then analyze BiDi)
                    size_of_tasklist,
                  } t_task_id;         
  • cortos_tasklist.c:
    In dieser Datei wird allen definierten TASK-enum das auszuführende Programm zugeordnet und es wird auch hinterlegt, ob und wann dieses Programm das nächste Mal aufzurufen ist.
    t_task tasklist[] =
       // id                     ready, wakeup, call
      {  [TASK_IDLE]          = {    1, 0,      task_idle},             // must be the first entry, lowest prio
         [TASK_POWER_UP]      = {    1, 0,      power_up_sequence},
         [TASK_KEYBOARD]      = {    1, 0,      keyboard},
         [TASK_DEBUG_IF]      = {    0, 0,      run_debug_if},
         [TASK_MACRO]         = {    1, 0,      run_macro},
         [TASK_EVENT_HANDLER] = {    0, 0,      event_handler},
         [TASK_LED_STATUS]    = {    1, 0,      task_led_status},
         [TASK_SERVO]         = {    0, 0,      run_servo},
         [TASK_BIDIB]         = {    0, 0,      run_bidib_client},
         [TASK_BIDI_CH1]      = {    0, 0,      bidi_ch1_message_handler},
         [TASK_DCC_DECODE]    = {    0, 0,      dcc_decode},            // highest priority
      };
     

Beenden

    Sollte ein Modul kontrolliert beendet werden müssen (z.B. Treiber oder Interrupts abschalten), dann muß ein entsprechender Aufruf von close_myModul in der close_all_interrupts() in main.c eingetragen werden. Das ist insofern wichtig, weil sonst bei einen Firmwareupdate (bei dem ja die Interrupttabelle void wird) der Prozessor abstürzt.

Speicheraufteilung

    Am atxmega128D3 oder atxmega128A3 steht folgender Speicher zur Verfügung:
    Speicheraufteilung atxmega128D3 bzw. A3
    136 KByte Flashfür Programm, Konstanten, Bootloader
    2 KByte EEPROMfür persistente Daten (CVs, Features)
    8 KByte SRAMfür Variablen, Stack, Heap
    Die 136k Flash unterteilen sich nochmal in 128k Applikation und 8k Bootloader. Die Applikation wird auf den Bereich ab 0x00000 gelinkt.

    Hinweis: schreibende Zugriffe auf das Flash sind nur aus dem Bootloaderbereich möglich (Einschränkung der Hardware, NVM-Controller): Deshalb ist im Bootloader der Zugriffscode für den NVM-Controller auf einen festen Platz gelinkt .BOOT=0x010FE0. Wenn die Applikation diesen Zugriffscode benutzen möchte (durch Einbinden von sp_driver.s), dann müssen folgende Schritte unternommen werden:
    • Auch in der Applikation muß man diesen Codeteil auf 0x10FE0 linken, d.h. auch dort braucht es die Speicheranweisung .BOOT=0x010FE0. Damit stellt man sicher, dass es zur Laufzeit zusammenfindet.
      Im Makefile: LDFLAGS += -Wl,-section-start=.BOOT=0x21fc0
      Beachte: Speicher wird in 'Word' adressiert, der Linker rechnet jedoch in 'Byte', deshalb ist die Linkeranweisung doppelt so groß.
    • Aus dem erzeugten Hexfiles muß man diesen Codeteil wieder rausnehmen, weil es ja im Bootloader bereits enthalten ist und daher nicht in den Prozessor geschrieben werden kann. Hierzu ergänzt man im Makefile die Zeile:
      HEX_FLASH_FLAGS += -R .BOOT
    • Es ist sinnvoll, das Vorhandensein dieses Codeteils beim Start der Applikation zu kontrollieren. Das geschieht mittels spm_is_available() und das Ergebnis wird in einer globalen Variablen hinterlegt.
         if (spm_is_available()) g_macro_flash_save = 1;
         else g_macro_flash_save = 0;
      Alle Zugriffe ins Flash fragen g_macro_flash_save dann vorher ab.

'Benimm'regeln für Anwendungsprogramme

Interrupts

    Generell gilt: Interruptroutinen sollen kurz und klein sein und keine Unterprogramme oder gar Library-Funktionen aufrufen. Der globale Interruptenable darf nur sehr kurzzeitig (max. für 5us) disabled werden.
    Der Atxmega kennt drei Interruptlevel: HI, MED und LOW:
  • HI ist für BiDiB reserviert.
  • MED wird für sehr zeitkritische Aktionen verwendet (z.B. DCC Analyse).
  • LOW ist bei allen anderen Aktionen zu verwenden, z.B. Servo, Licht, Timer.

  • Im Initteil der Anwendung wird der jeweilige Interrupt enabled (das geschieht 'unter Strom', d.h. das Interruptsystem ist dabei bereits freigegeben). Außerdem muß die Anwendung eine 'close'-Methode mitbringen, Interrupt müssen bei bestimmten Aktionen (wie z.B. Bootloader) abgeschaltet werden.

Flags

    Flags dienen zur Interprozesskommunikation und sind kleine 1-Bit-Variablen.
    Ein Flag wird in config.h in der Rubrik Flags mittels eines #define angelegt, es sind bis zu 16 Flags zulässig. Durch entsprechende inline-Routinen in cortos werden Flags als Einzelassemblerbefehle übersetzt und erfordern sowohl bei Setzen und Abfragen nur einen CPU-Takt.

    Beispiel:
    flag_set(F_update_grayscale);

    if (flag_get(F_update_grayscale))    {      tlc5941_write_grayscale();    }

Delays

    Die Applikationssoftware muß kooperativ sein, d.h. sie darf nie aktiv warten. Das bedeutet: Eine Task muß vor einem Delay ihren Kontext selbst sichern (in statische Variablen des eigenen Moduls) und gibt dann die Kontrolle zurück, wobei der Rückgabewert angibt, in welchem zeitlichen Abstand die Task wieder aktiviert werden soll.
    Beispiel blinkender Ausgang:
    static my_state;
    t_cr_task my_blink(void)  {
       if (my_state) {
          my_state = 0;
          OUTPUT_OFF();
          return(TIME_200ms);
          }
      else {
          my_state = 1;
          OUTPUT_ON();
          return(TIME_200ms);
          }
      }
    Die zeitliche Auflösung beträgt hierbei 1ms und ist im #define SYSTICK_PERIOD (welcher die Einheit µs hat) hinterlegt. Sinnvollerweise schreibt man dann für 200ms:
    return(200000L / SYSTICK_PERIOD);   

Portierbarkeit

    Wenn möglich soll man den Zugriff auf Ports oder Bits in Basisfunktionen auslagern (also etwa: my_setbit(index, value)), und diese Basisfunktion kann man als static und always_inline deklarieren.

    Wenn man diese Ausgabebits an einem externen Baustein hat (z.B. SPI-Bus), dann beschreibt mit der my_setbit-Funktion nur das Ausgabearray und startet den 'Hardware-Zugriff' erst nach Ende einer Aktion. (siehe auch das Beispiel bei den Flags)

LEDs

    LEDs zur Kontrolle und Statusanzeige werden gemäßt folgenden Schema in hardware.h definiert:
  • LED-Position festlegen:
    // Port C
    #define LED_DMXp        5
     
  • defines für Zugriff definieren:
    #define LED_DMX_ON()        PORTC.OUTSET = (1<<LED_DMXp)
    #define LED_DMX_OFF()       PORTC.OUTCLR = (1<<LED_DMXp)
     
  • Die Hardware muß noch initialisiert werden (in hardware.c):
    PORTC.DIRSET = (1 << LED_DMXp);
        PORTC.OUTCLR = (1 << LED_DMXp;
         
  • Alle Zugriffe auf diese LED erfolgen dann mittels LED_DMX_ON() oder LED_DMX_OFF().

Eintritt in den Bootloader

    Der Bootloader residiert in einem extra Bereich, die Applikation springt das auf entsprechende Aufforderung vom BiDiB-Host an. Zusammen mit der Aktivierung des Bootbereiches im Prozessor werden auch die Zeiger auf die Interrupttabelle manipuliert, es ist daher wichtig, dass beim Abflug in den Bootloader kein aktivierter Interrupt mehr enabled ist!
    Der Bootloader übernimmt eine etablierte BiDiB-Verbindung, d.h. er meldet sich in diesem Fall nicht neu an. Wenn der Bootloader allein gestartet wird, dann meldet er sich an. Um diese Unterscheidung im BL treffen zu können, werden die GPIO0 und GPIO1 Register entsprechend geladen.

Grundstruktur der Software

Versionsverwaltung

    Die Versionsvergabe wird automatisch per version.h gesteuert. version.h legt folgende Defines an:
    #define MAIN_VERSION                0
    #define SUB_VERSION                 01
    #define COMPILE_RUN                 01
    
    #define BUILD_YEAR                  13
    #define BUILD_MONTH                 6
    #define BUILD_DAY                   3 
    Diese Defines werden an drei Stellen ausgewertet:
  • Im BiDiB-Interface wird die Versionsabfrage des Hostprogrammes aus diesen Werten gespeist.
  • Das Debug-Interface liefert seine Info-Abfrage mit diesen Werten.
  • Beim Übersetzen des Projektes werden die Werte mittels gawk zum Umbenennen der Hex-Dateien verwendet.

Allgemeine Header

    datatypes.h

Datenstrukturen im EEPROM

    Alle BiDiBOne-Projekte benutzen eine einheitliche Grundstruktur für die Daten im EEPROM. Diese besteht aus einem allgemeinen Teil mit der EEPROM-Version, Hersteller/Produktkennung und Softwarestand. Diese Daten werden von der Firmware verwendet, um die 'Verträglichkeit' des EEPROM-Inhalts zu überprüfen. Die bei BiDiB gemeldeten Kennungen sind im Flash festgelegt.
    typedef struct
      {
        unsigned char ee_version;          //  1  Version (of eeprom content)
        unsigned char vid;                 //  2  Vendor ID (0x0D = DIY Decoder)
        unsigned char pid_l;               //  3  Product ID
        unsigned char pid_h;               //  4  Product ID
        unsigned char main_version;        //  5  software
        unsigned char sub_version;         //  6  software
        unsigned char compile_run;         //  7  software
        unsigned char res1;                //  8  reserved
        unsigned char res2;                //  9  reserved
        unsigned char res3;                // 10  reserved
      } t_bidib_cv;
      
    Die Definition der Strukturen erfolgt in cv_define.h, das Festlegen der Inhalte in cv_data.h. Angezogen werden diese beiden Header von config.c.

Timestamping

    Die BiDiB-Zeit wird als umlaufende Zeit mit 1ms Takt realisiert. Zur Verwaltung dieser Zeit wurde folgende Struktur angelegt:
    
    Damit im Interface die Zeit sehr schnell nach Start losläuft, wird dort bei der Abfrage von GET_MAGIC die entsprechende Timeout-
    Variable kurz vor dem Überlauf gestellt:
    
    
    set_bidib_time(65536L - 1000L);         // fake: after get magic here, set bidib_time to start in 1s.
    bidib_time_tx.overflows = BIDIB_TIME_MAX_OVL;
    

    Beim Eintreffen einer Nachricht von der übergeordneten Instanz wird die Zeit gesetzt und auch die Variable .force, damit eine erneute Übertragung ausgelöst wird:
                case MSG_LOCAL_SYNC:        // 1:time_l, 2:time_h
                    set_bidib_time((bidib_rx_msg[4] + (bidib_rx_msg[5] << 8)) + 1);  // add 1ms as local delay
                    bidib_time_tx.force = 1;
                    break;
    

    .force löst dann einen Nachricht an die Unterknoten aus:
    static void transmit_local_sync_bc(void)
      {
        signed int my_time = get_bidib_time();
        t_master_broadcast_message2 bcmsg;
        bcmsg.header.addr_end = 0;                 // we have only the end - message from interface
        bcmsg.header.index = 0;                    // here no get_tx_num(), it is a broadcast
        bcmsg.header.msg_type = MSG_LOCAL_SYNC;
        bcmsg.data[0] = LOW8(my_time);
        bcmsg.data[1] = HIGH8(my_time);
        bcmsg.header.size = 3+2;
    
        bim_down_drop_to_fifo((unsigned char *)&bcmsg);
      }
    

    Wichtig auch, neu angemeldeten Knoten die Systemzeit zu übermitteln, also nach send_bidibus_logon_ack(n); folgt transmit_local_sync(n);