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

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.
|
Download
- Die Firmware für BiDiBOne-Projekte vom OpenDCC findet sich auf der → Downloadseite.
Weitere Projekte sind u.a. auf https://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:
Projekt | Trägerboard | Beschreibung | Status |
---|---|---|---|
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:
- 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.
Speicheraufteilung atxmega128D3 bzw. A3 | |
---|---|
136 KByte Flash | für Programm, Konstanten, Bootloader |
2 KByte EEPROM | für persistente Daten (CVs, Features) |
8 KByte SRAM | für Variablen, Stack, Heap |
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:
'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.
- 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.
Der Atxmega kennt drei Interruptlevel: HI, MED und LOW:
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 3Diese Defines werden an drei Stellen ausgewertet:
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);