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

JALEdit de editor om JAL code te schrijven
JALlib de bibliotheken (en voorbeelden) en de compiler
MPLab IDE de ontwikkel- en PicProgrammeromgeving
Microchip de site van de PIC's
JalList Hulp en hopen voorbeelden