PIC 16F88 Märklin Motorola Sein/Wissel Monitor
Schema JAL code (programma) JAL code (MM Lookup) JAL code (MM interrupt)
Märklin Motorola sein/wissel monitor is een stukje hard- en software om de Märklin Motorola data die naar seinen/wissels gestuurd wordt, te kunnen analyseren.
Het programma doet tevens dienst als wrap-around rond een universele interrupt gestuurde decoder.
Om het Märklin Motorola protocol te kunnen decoderen, moeten we eerst weten hoe dit protocol is opgebouwd. Er zijn over dit protocol reeds genoeg pagina's geschreven en hier een greep uit de links:
New Märklin Motorola Format
Dr. König“s Märklin-Digital-Page
Digitale spanning
Märklin Digital (Wikipedia)
Het schema is opgebouwd uit 6 blokken:
- het processorgedeelte (PIC 16F88)
- de Märklin interface (optocoupler)
- een (optionele) I2C interface (niet nodig in dit programma)
- een (optionele) LCD interface (nodig in dit programma)
- een (optionele) schakelinterface (niet nodig in dit programma)
- een (optionele) LED interface (niet nodig in dit programma)
Het programma is ook opgebouwd uit verschillende onderdelen (blokken): eerst is er de initialisatie van de processor, het laden en initialiseren van de verschillende hardwaremodules, het declareren en initialiseren van de nodige variabelen en tot slot het laden van het kloppend hart van dit programma: de interruptroutine (in een afzonderlijke module).
Het eigenlijke programma speelt zich af in de forever loop: hier worden de door de interruptroutine gezette vlaggen in het oog gehouden en wordt er gepast op gereageerd.
Op het LCD wordt de sein/wissel data op twee manieren weergegeven: op de eerste regel komt de ruwe data (zoals die door het Märklin Motorola protocol wordt aangeboden): Ruw - Adres - Subadres - Schakelstatus . Op de tweede regel is deze data ontleed.
Enkele voorbeelden:
Schakelaar 12 rood ingedrukt:
0 | 0 | C | 0 | 3 | F | - | 3 | 0 | - | 3 | C | - | 0 | 3 | |
1 | 2 | - | R | e | d | - | O | N |
Schakelaar 19 groen na het verstrijken van het afschakelcommando:
0 | 2 | C | 0 | C | C | - | B | 0 | - | C | C | - | 0 | 0 | |
1 | 9 | - | G | r | n | - | O | F | F |
Het eerste deel van het programma is de initialisatie van de processor en het laden van de processorbibliotheek.
Er wordt geen gebruik gemaakt van een externe reset (heeft als voordeel dat er een extra poort vrijkomt - RA5 - wel enkel als input te gebruiken!).
Ook de interne oscillator wordt gebruikt (terug twee extra poorten vrij). Deze interne oscillator werkt op 8MHz en nauwkeurig genoeg voor deze toepassing.
Verder maken we de processor duidelijk dat alle poorten digitale IO zijn (geen analoge ingangen) en worden de input (Motorola signaal) en de uitgang (statusled) geļnitialiseerd.
;------------------------------------------------------------------------------- ; 16F88 ; interne oscillator op 8MHz ; interne MCLR ;------------------------------------------------------------------------------- include 16f88 pragma target OSC INTOSC_NOCLKOUT -- HS crystal or resonator pragma target clock 8_000_000 -- oscillator frequency pragma target WDT disabled -- no watchdog pragma target LVP disabled -- no low-voltage programming pragma target CCP1MUX pin_B3 -- ccp1 pin on B3 pragma target MCLR internal -- reset internally OSCCON_IRCF = 7 -- set prescaler to 1 (8 MHz) enable_digital_io() ;alle pinnen digitale IO ;LED is via voorschakelweerstand aangesloten op pin B7 alias led is pin_B7 pin_B7_direction = output led = off alias Motorola is pin_B0 pin_B0_direction = input |
Timer0 instellingen. Timer0 zorgt voor de tijdsbasis van het programma (in de interruptroutine) en voor de tijdsmeting van een volledige bittrein (18 bits). Afhankelijk van de tijdsduur van die 18bits wordt beslist of het om locomotiefdata ofwel om sein/wisseldata gaat.
De timer heeft een interruptherhaling van 512µs. Telkens wanneer de interruptroutine een puls van een commando ontvangt, wordt de timer herstart. De interrupt zal dus pas afgaan wanneer er 512µs geen data ontvangen wordt. Dit interruptsignaal wordt gebruikt om de statusled te laten knipperen als teken dat er geen data meer ontvangen wordt.
;------------------------------------------------------------------------------- ;instellingen voor Timer0 ;------------------------------------------------------------------------------- INTCON_TMR0IE = high ;interrupt OPTION_REG_T0CS = low ;Timer0 timer mode OPTION_REG_PSA = low ;prescaler op Timer0 ;de prescaler op 1/4 OPTION_REG_PS = 0b001 TMR0 = 0 |
De LCD declaraties en initialisatie. Het LCD maakt gebruik van 4 datalijnen (A0 tot A3) en 2 stuurlijnen (A4 en A6).
Na het laden van de printbibliotheek (met procedures voor het weergeven van getallen en strings op het LCD) wordt het LCD gewist.
;------------------------------------------------------------------------------- ;LCD declaratie en initialisatie ;------------------------------------------------------------------------------- ;Volgende constanten moeten gedeclareerd worden: const byte LCD_ROWS = 2 -- 1, 2 or 4 lines const byte LCD_CHARS = 16 -- 8, 16 or 20 chars per line ;Alisassen voor de handshake-lijnen: alias lcd_rs is pin_A4 -- cmd/data select alias lcd_en is pin_A6 -- trigger pin_A4_direction = output pin_A6_direction = output ;Aliassen voor de vier datalijnen: alias lcd_dataport is portA_low -- 4 databits pin_A0_direction = output pin_A1_direction = output pin_A2_direction = output pin_A3_direction = output ;laadt de eigenlijke bibliotheek include lcd_hd44780_4 ;en initialiseer het display lcd_init() -- init the lcd controller ;de printbibliotheek include print ;wis het scherm lcd_clear_screen() |
Een klein stukje code maar wel heel belangrijk: twee onderdelen van het programma zijn voor de duidelijkheid (en herbruikbaarheid) in een afzonderlijke bibliotheek opgenomen.
MMInterrupt is een bibliotheek die de interruptroutine bevat en MMLookup bevat een lookuptabel om de omrekening van de adressen te vereenvoudigen.
Beide bibliotheken komen verder nog aan bod.
;------------------------------------------------------------------------------- ;Het kloppend hart van de decoder include MMInterrupt ;De marklin adres lookuptabel include MMLookup |
De declaratie en initialisatie van de verschillende programmavariabelen.
Er wordt hier veelvuldig gebruik gemaakt van 24 bits variabelen (om op een geheugen-efficiėnte manier de 18 bits tellende Motorola-commando's te kunnen opslaan). In JAL is een 8 bit variabele een byte, een 16 bit variabele een word en een 32 bit variabele een dword.
Een 24 bit variabele bestaat niet, maar gelukkig laat JAL op een eenvoudige manier toe om zelf een variabele aan te maken. Via var byte*3 maken we 3 8 bit variabelen in het geheugen direct na elkaar zodat we over een 24 bit variabele kunnen beschikken.
Verder worden nog enkele strings gedefiniėerd om op een eenvoudige wijze (vaste) tekst op het LCD te krijgen.
;------------------------------------------------------------------------------- ;PROG variabelen ;------------------------------------------------------------------------------- var byte*3 dwRawData = 0 var byte RawData[3] at dwRawData var byte DataLow var byte DataMid var byte DataAddr var byte Temp var word wSwitchAdres ;LCD strings const byte RED[3] = "Red" const byte GREEN[3] = "Grn" const byte SW_ON[3] = "ON " const byte SW_OFF[3] = "OFF" |
Vlak voor de forever loop wordt de globale interrupt vrijgegeven waardoor zowel Timer0 als B0 een interrupt kunnen genereren.
;------------------------------------------------------------------------------- INTCON_GIE = high ;enable interrupts |
De forever loop:
In één blok wordt er gekeken of er geldige Motorola-data is en in een ander blok wordt gekeken of Timer0 afgelopen is.
In eerste instantie wordt gekeken of er geldige data is (DataStatus heeft dan de waarde DATA_LOCO of DATA_TURNOUT).
Om zeker te zijn dat de led uit is (kan eventueel aan zijn na een onderbreking of wanneer de decoder vroeger opgestart is dan de centrale), doven we deze eerst.
De geldige data komt toe in de variabele dwPassData. Deze variabele werd in de interruptroutine ingevuld. Om nu zeker te zijn dat die interruptroutine niet terug nieuwe data in deze variabele zet (wat gebeurd als er een nieuw commando komt - zowel loc als wissel/seindata), kopiėren we voor alle zekerheid eerst de inhoud van deze variabele.
Nu kunnen we de ruwe data wat aanpassen en ervoor zorgen dat de 18 bits data netjes verdeeld worden over 3 * 8 bit variabelen.
De hoogste 8 bits van het 18 bit tellende commando verschuiven we 6 keren naar links om zo te passen in één byte.
;------------------------------------------------------------------------------- forever loop if(DataStatus > DATA_NONE)then ;Er is data, de led mag uit Led = off ;schuif data door zodat de interruptvariabele terug 'vrij' komt om ;terug aangepast te worden door de interruptroutine dwRawData = dwMMPassData ;Verdeel de data: ;DataLow bevat de 'ruwe' data ;bij seindata is dit de schakeldata (subadres + schakelstatus) DataLow = RawData[0] ;DataMid heeft bij seindata geen zin (is steeds 0) DataMid = RawData[1] & 0x03 ;DataAddr bevat het adres (eerst van 18 bits 24 bits maken om eenvoudiger ;te kunnen bewerken) dwRawData = dwRawData << 6 DataAddr = RawData[2] |
Aan de hand van de vlag DataStatus kunnen we weten of het om loco- ofwel om sein/wissel-data gaat. Momenteel interesseert ons enkel de wissel/sein-data (DATA_TURNOUT).
Op het LCD zetten we vervolgens ruwe data - adres - subadres - schakelstatus. De 'ruwe data staat nog in MMPassData. Op die enkele µs die vergaan zijn na het sein "geldige data" en nu is deze data door de interruptroutine nog niet gewijzigd (een volledig commando interpreteren duurt enkele ms). De ruwe data wordt als drie bytes hexadecimaal weergegeven.
Na de ruwe data komt het adres (hexadecimaal), het subadres (eveneens hexadecimaal) en tenslotte de schakelstatus. Die schakelstatus is bij het inschakelen (of het ingedrukt houden van de schakelknop) steeds 0x03. Na de (ingestelde) afschakeltijd van de centrale wordt deze 0x00.
;enkel wisseldata interesseert ons voor het ogenblik if(DataStatus == DATA_TURNOUT)then ;seindata ;Op het display geven we op de bovenste regel de 'ruwe' data ;Ruw - Adres - Subadres - schakelstatus lcd_cursor_position(0,0) print_byte_hex(lcd, MMPassData[2]) print_byte_hex(lcd, MMPassData[1]) print_byte_hex(lcd, MMPassData[0]) _lcd_write_data("-") ;Adres print_byte_hex(lcd, DataAddr) _lcd_write_data("-") ;SubAdres print_byte_hex(lcd, (DataLow & 0xFC)) _lcd_write_data("-") ;Schakelstatus print_byte_hex(lcd, (DataLow & 0x03)) |
Op de tweede regel van het LCD zetten we 'leesbare' data:
- Het schakeladres.
- Het kleur van de schakelaar (rood/groen) conform Märklin.
- De schakelstatus (on/off).
Het adres komt in de ruwe data in de vorm van 4 trits. Een procedure zou deze trinaire data kunnen omzetten naar binaire (voor verdere bewerking). Omdat een PIC 16F88 toch ruim voorzien is van programmageheugen opteren we voor een lookuptabel. Voor elke mogelijke trinaire waarde krijgen we via de lookuptabel een binaire waarde. De lookuptabel zelf bevindt zich in de bibliotheek MMLookup.
Vanuit die lookupwaade wordt het adres berekend.
Het subadres wordt via een case ... of uitgevoerd.
Afhankelijk van de schakelstatus komt 'on' of 'off' op display.
Nadat beide regels op het LCD geplaatst zijn, melden we nog dat de data verwerkt is.
;Op de tweede regel zetten we 'leesbare' data
;Schakeladres - Rood/groen - Schakelstatus ;bereken het decimaal adres wSwitchAdres = MMAdresLookup[DataAddr] ;wSwitchAdres - 1 * 4 wSwitchAdres = (wSwitchAdres - 1) << 2 ;bereken de individuele schakelaar Temp = (DataLow & 0xFC) case Temp of 0x00 : wSwitchAdres = wSwitchAdres + 1 0xC0 : wSwitchAdres = wSwitchAdres + 1 0x30 : wSwitchAdres = wSwitchAdres + 2 0xF0 : wSwitchAdres = wSwitchAdres + 2 0x0C : wSwitchAdres = wSwitchAdres + 3 0xCC : wSwitchAdres = wSwitchAdres + 3 0x3C : wSwitchAdres = wSwitchAdres + 4 0xFC : wSwitchAdres = wSwitchAdres + 4 end case lcd_cursor_position(1,0) ;Adres print_byte_dec(lcd, wSwitchAdres) _lcd_write_data(" ") _lcd_write_data("-") _lcd_write_data(" ") ;Kleur schakelaar Temp = DataLow & 0xC0 if(Temp == 0)then print_string(lcd, RED) else print_string(lcd, GREEN) end if _lcd_write_data(" ") _lcd_write_data("-") _lcd_write_data(" ") ;Status Temp = DataLow & 0x03 if(Temp == 0)then print_string(lcd, SW_OFF) else print_string(lcd, SW_ON) end if _lcd_write_data(" ") _lcd_write_data(" ") _lcd_write_data(" ") end if ;data verwerkt DataStatus = DATA_NONE end if |
Rest ons nog na te gaan of er nog een geldig MM signaal is.
Dit doen we door te kijken of de Timer0 interrupt is afgegaan. In 'normale' omstandigheden (dwz wanneer er een regelmatige datastroom is) gaat deze interrupt niet af (bij iedere opgaande flank (of neergaande flank bij omgepoolde decoder) op port B0 wordt Timer0 gereset).
Timer0 die een interrupt genereert wil dus zeggen dat er geen signaal meer ontvangen wordt. Dit melden we met een knipperende led.
;T0 afgegaan? if(T0Passed != 0)then T0Passed = 0 BlinkTimer = BlinkTimer - 1 if(BlinkTimer == 0)then BlinkTimer = BLINKTIME ;knipper de led om aan te tonen dat er geen MM signaal is! Led = !Led end if end if end loop ;------------------------------------------------------------------------------- |
Links