OpenDCC BiDiBOne

Ü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 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
      Atxmega128D3, 128k Flash, 8k RAM
      Bootloader, automatische Anmeldung am Bus, FW-Update über Bus (kein Programmer erforderlich)

Projekte

    Folgende Projekte sind verfügbar / geplant. (→ Downloadseite).
:
ProjektTrägerboardBeschreibungStatus
OneHub OneInterface Ein Baustein zur Buserweiterung, weitere 31 BiDiB-Knoten sind anschließbar. alpha, download verfügbar.
OneDMX OneInterface Raumlichtsteuerung mittels handelsüblichen DMX-Modulen und DMX-Dimmer alpha, download verfügbar.
OneControl OneControl 8 Servos, 16 Powerausgänge in Entwicklung, erster Aufbau.
OneOC OneOpto 20 Eingänge (Belegtmelder), optogekoppelt beta, abschließende Tests
OneTester OneTester 24 LEDs direkt auf der Platine, Lauflicht 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 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).
  • BiDiB
    Es ist ein normales Bus-Interface mit einem RS485 Baustein 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 im QFN64-Gehäuse Verwendung. Bestückbar wären hier auch A3-Typen. Alle Ein- und Ausgänge sind mit Serienwiderständen 47 Ohm geschützt.
    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.

Download

    Siehe Downloadseite des BiDiBOne.

    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 steht folgender Speicher zur Verfügung:
    Speicheraufteilung atxmega128D3
    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 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.