;======================================================================
; SimpleMaus
;======================================================================
; Program: SimpleMaus -- Lokmaus control
; Orig. Code: Paco Cañada ( http://www.fut.es/~fmco )
; Platform: Microchip PIC16F628, 4 MHz ≙ 1 MIPS
;======================================================================
;Tab width = 8, EOL = LF, Charset = UTF-8
; This program is for a slave LokMaus control (Lenz XBus).
; It uses two LED 7-segment displays with one common resistor each.
; Uses PWM output for DAC that is compared with potentiometer voltage
; in 200 steps (0..199) (= DAC following ADC principle), however, 128 will be
; used due to common voltage range limit of analog comparator inputs
; Revisions:
; 01/02/2006 Start of writing code.
; 07/02/2006 First prototype. RA4 need external pull-up. Communication works
; 10/02/2006 Read pot. routines, added setup pot. and display
; 12/02/2006 Added display flashing
; 13/02/2006 Added save current loco
; 14/02/2006 Added stabilization time on reconection
; 16/02/2006 Changed direction / steps menu and clear keys status on start-up
; 31/10/2006 Change direction directly on pushbutton. F1 and F2 supported.
;190120 heha heavy source code revision with equal hex file
;190125 heha restructuring display, keyboard, potentiometer reading,
; change A/D characteristics, two-segment-at-once display (brighter),
; charlieplexing, common anode, speed control in 127 steps
;190130 PWM period variation for potentiometer maximum value of 128
;190202 TODO: Richtungsauswahl per Lokadresse merken (13 Bytes EEPROM)
; Hardware: Four keys, one potentiometer, COMMON ANODE
; two 1-digit 7-segment displays, one RS485 (MAX485) transceiver ============
;RA0 17 Key "Sel", Segment "a" "e"
;RA1 18 AN1 lowpass filtered PWM to analog comparator 2 (-) *
;RA2 1 AN2 potentiometer wiper to analog comparator 2 (+)
;RA3 2 Left digit CC Segment "dp" of right digit F0/+ "f"
;RA4 3 Right digit CC Segment "dp" of left digit F1/- "g"
;RA5 4 (!MCLR) Keypress detect, H-active (This pin is input-only) L-active
;RA6 15 Key "F1/-", Segment "g" CAH "dp0"
;RA7 16 Key "F0/+", Segment "f" CAL "dp1"
;RB0 6 MAX485 drive signal, H=send, L=receive
;RB1 7 RX MAX485 Receive
;RB2 8 TX MAX485 Send
;RB3 9 CCP1 PWM output for A/D conversion *
;RB4 10 Key "STOP", Segment "b" SEL "a"
;RB5 11 Segment "c" STOP "b"
;RB6 12 Segment "d" "c"
;RB7 13 Segment "e" "d"
;CCA means Common Cathode or Anode, will be detected by firmware at startup.
;Two resistors limit the current and set display brightness.
;Common anode requires reversed diodes for the keys and a pullup
;(instead of pulldown) at RA5/!MCLR input. It is used for auto-detection.
;* = Free pins for PIC16F1826 as that chip has an A/D converter
;Charlieplexing leaves room for up to 8(!) 7-segment digits or some other LEDs
;with no more microcontroller pins needed, and no loss of brightness.
;However, most prewired multi-digit displays cannot be used.
;(VQE displays will fit perfectly here.)
;Use of timers:
;Timer0: Display multiplex with interrupts each 1.024 ms
;Timer1: General timeout generator
;Timer2: PWM generation for potentiometer readback;
; overflow interrupt = 1µs (F_CPU) * 50 (counting range) * 8 (postscaler) = 400 µs
; New potentiometer mean value (in range 00..199) each 256 interrupts = 102.4 ms
list p=16F628A,r=DEC,n=0,st=off
expand
include "P16F628A.INC"
assume 0
__FUSES _BODEN_ON & _CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT
; ----- Macros
DNOP macro
goto $+1
endm
movfwf macro s,d
movfw s
movwf d
endm
movlwf macro l,d
movlw l
movwf d
endm
lslf macro s,d
clrc
rlf s,d
endm
lsrf macro s,d
clrc
rrf s,d
endm
loop macro reg,lbl
decfsz reg,f
goto lbl
endm
BANK0 macro ; Built-in BANKSEL macro is stupid and generates 2 instructions
bcf STATUS,RP0 ; As this program uses only Bank 0 and 1, the RP1 bit is always zero.
endm
BANK1 macro
bsf STATUS,RP0
endm
; ----- Constant values
FXTAL equ 4000000 ; Internal oscillator frequency
BaudRate equ 62500 ; XBus serial speed
; --- Entry mode constants, defines what will be shown on display. Pressing a key will branch to one of four subitems.
MENU_RUN equ 0x00
MENU_LOK equ 0x04
MENU_DIR equ 0x08
MENU_SETUP equ 0x0C
MENU_XBUS equ 0x10
MENU_POT equ 0x14
MENU_FNC equ 0x18
;direct 7segment codes; a space is simply zero
_0 equ b'0111111' ; a
_1 equ b'0000110' ; xxxxxx
_2 equ b'1011011' ; f x x b
_3 equ b'1001111' ; x g x
_4 equ b'1100110' ; xxxxxx
_5 equ b'1101101' ; e x x c
_6 equ b'1111101' ; x x
_7 equ b'0000111' ; xxxxxx
_8 equ b'1111111' ; d
_9 equ b'1101111'
_A equ b'1110111'
_d equ b'1011110'
_E equ b'1111001'
_L equ b'0111000'
_o equ b'1011100'
_P equ b'1110011'
_UP equ b'0000001' ;'¯', upper bar, means maximum value or “on”
_MI equ b'1000000' ;'-', middle bar; minus sign, means middle value
_DN equ b'0001000' ;'_', lower bar; down; underline, means minimum value or “off”
; --- Non-banked variables
cblock 0x70
SaveW ; Interrupt variables
SaveSTATUS
;SavePCLATH
SaveFSR
eeWD ; EEPROM write data
DL ; dividend for division routine
DH
endc
; --- Bank 0
cblock 0x20
HEADER ; first input buffer location = Xbus packet, maximum length = 17 bytes
DATA1
DATA2
DATA3
DATA4
DATA5:12 ; leave space upto RAM address 0x31
;XBUS related:
CALLBYTE ; call byte byte from command station
MY_ADR ; XpressNet Address for normal inquiry messages 'P10AAAAA'
TxPos ; Serial buffer index / xorbyte
TxCnt ; Serial Buffer output index
RxXor ; Serial incoming checksum
RxLen ; Incomming message length
XFLAGS ; XpressNet flags (bits see below)
XBUS_VER ; XpressNet version (write only)
XBUS_STA ; Command Station type (write only)
txData0 ; Temporary values (TODO: Needs cleanup!)
txData1
;Locomotive related:
FLAGS ; Flags
DCC_STATUS ; Command Station Status
CURRMTR ; Current Loco Multi-Unit
CURRADR ; Current Loco address
CURRSPD ; Current Loco speed, in Speed14, Speed28, or Speed128 format
CURRFNC ; Current Loco functions
SPEED ; current speed
;potentiometer (i.e. selfmade ADC) related:
potPWM ; current PWM calue, avoids readback of CCPR1L
potFiltN ; counter for filter accumulation (0..255)
potFiltL ; accumulator for PWM value filtering (16 bit)
potFiltH ; sum up 256 values of potPWM here
potCenter ; A/D value for center position. If 0, assume old behaviour with button direction change only
potValue ; mean value (00..199), updated with flPotValue set
;display related:
digitH ; Left digit for display multiplex
digitL ; Right digit for display multiplex
segMask ; current segment shown, zero or only one bit is set at any time
flashSeq ; flashing sequence, 1-bits mean OFF phase
;key input related:
keyCollect ; key bits to collect while complete multiplexing cycle of 7 ms
keyState ; Keys status for upto 8 keys, -FF---SS
keyNew ; Keys that changed status from released to pressed
keyRepeat ; autorepeat timer
MENU ; current menu
endc
; --- Bank 1
cblock 0xA0
TxBuf:17 ; XpressNet Serial output buffer (only indirect addressing; size: 1header+15data+1checksum=17)
endc
;----- Flags
#define xfNewTx XFLAGS,0 ; new message avaliable to send to command station
#define xfXmit XFLAGS,1 ; transmiting a message
#define xfRcv XFLAGS,2 ; receiving a message
#define xfNewMsg XFLAGS,3 ; received new message to us
#define flPotLeft XFLAGS,5 ; potentiometer physically left from zero position
#define dccEmergStop DCC_STATUS,0 ; System in Emergency Stop
#define dccEmergOff DCC_STATUS,1 ; System in Emergency Off
;#define AutoStart DCC_STATUS,2 ; System in auto start-up
#define dccServiceMode DCC_STATUS,3 ; System in Service Mode
#define flLocoOther FLAGS,0 ; current loco is controlled by other device
#define flMultiUnit FLAGS,2 ; Current is multiunit address
#define flSelSetup FLAGS,3 ; Setup selection, either "A " or "P "
#define flTestAD FLAGS,4 ; Pot change discard (??)
#define flNegDir FLAGS,5 ; direction for bidirectional potentiometer (same bit as in CURRFNC)
#define flPotValue FLAGS,6 ; have new potentiometer value (each 102.4 ms)
;define flCA FLAGS,7 ; Common anode displays are better for PIC16F628A as H driver is weaker than L driver
Loco128 equ 7 ; CURRFNC bit definition
Loco28 equ 6
LocoDir equ 5
; --------------- Program Section --------------------------------------
;PowerUp:
clrf INTCON ; Disable all interrupts
clrf PCLATH ; Tables on page 0
clrf STATUS ; reset flags
goto INIT
; ----- ISR (Interrupt Service Routines)
movwf SaveW ; 1 Save registers W, STATUS, PCLATH, FSR
swapf STATUS,w ; 2
clrf STATUS ; 3 Ensure port / timer access in bank 0
movwf SaveSTATUS ; 4
; movfwf PCLATH,SavePCLATH ; save PCLATH, interrupt uses page 0
; clrf PCLATH
movfwf FSR,SaveFSR ; Save FSR
;IntRx:
btfss PIR1,RCIF ; serial receive interrupt?
goto IntTx
btfss RCSTA,OERR ; yes, handle RX errors
btfsc RCSTA,FERR
goto IntError
btfss RCSTA,RX9D ; is a call byte?
goto IntRxData ; no
;IntRxCallByte:
bcf xfRcv ; yes, abort message in progress
clrf RxLen
movfwf RCREG,CALLBYTE ; get call byte and save it
btfsc xfNewMsg ; decoded last message?
goto IntRestore ; no, ignore this call byte
xorlw 0x60 ; broadcast call byte? ('P1100000')
bz IntRxPacket ; yes, new message
movfw CALLBYTE ; message to us?
xorwf MY_ADR,w ; 'P10AAAAA' anything to transmit?
bz IntRxSend ; yes, answer before 80 µs
xorlw b'10100000' ; no, 'P11AAAAA' message to us?
bz IntRxPacket ; yes, new message
xorlw b'01100000' ; no, 'P00AAAAA' acknowledge?
bz IntRxAck ; yes, send ack
goto IntRestore ; no, discard
IntRxPacket:
clrf RxXor ; set receiving a message
bsf xfRcv
goto IntRestore
IntRxData:
movlw HEADER ; save data
addwf RxLen,w
movwf FSR
movfw RCREG ; get data byte to clear interrupt
btfss xfRcv ; are we receiving a packet for us?
goto IntRestore ; no, discard data byte
movwf INDF ; yes, save data
xorwf RxXor,f ; checksum
incf RxLen,f ; next byte
movfw HEADER ; check end of message: get header byte
andlw 0x0F ; length to receive
addlw 0x02 ; plus header and xor-byte, so maximum length can be 17
xorwf RxLen,w ; equal to received length?
bnz IntRestore ; no, receive more bytes
IntRxEnd:
bcf xfRcv ; yes, end of receiving data
tstf RxXor
skpnz ; xor-byte correct?
bsf xfNewMsg ; yes, received new packet
goto IntRestore
IntRxAck: ; Acknowledge transmision (send 0x20, 0x20)
bsf PORTB,0 ; MAX485 TX enable: Occupy RS485 bus
movlwf 0x20,TXREG
movwf TXREG ; send second byte
goto IntTxEnd ; wait until transmitted
IntRxSend:
btfss xfNewTx ; new message to command station?
goto IntRestore ; no
bsf PORTB,0 ; MAX485 TX enable: Occupy RS485 bus
BANK1
bsf PIE1,TXIE ; Enable TX interrupts
BANK0
;clrf TxPos
bsf xfXmit ; now transmiting
goto IntTxByte
IntError:
movfw RCREG ; on error re-init receiver
bcf RCSTA,CREN
bsf RCSTA,CREN
IntTx:
btfss PIR1,TXIF ; serial transmit interrupt?
goto IntRestore ; no, nothing to do
btfss xfXmit ; yes, still transmiting?
goto IntTxEnd ; no, disable TX interrupts
IntTxByte:
movlw TxBuf ; calc buffer position
addwf TxPos,w
movwf FSR
movfwf INDF,TXREG ; fetch byte from buffer and send it
incf TxPos,f
decfsz TxCnt,f ; last byte?
goto IntRestore ; no
bcf xfNewTx ; yes, stop transmiting
bcf xfXmit
IntTxEnd:
BANK1
IntTxDone:
btfss TXSTA,TRMT ; wait until stop bit of last byte
goto IntTxDone
bcf PIE1,TXIE ; Disable TX interrupts
BANK0
DNOP ; await the stop bit duration
DNOP
DNOP
bcf PORTB,0 ; MAX485 TX disable: Release RS485 bus
IntRestore:
movfwf SaveFSR,FSR ; Restore the Context Registers and Return
; movfwf SavePCLATH,PCLATH
swapf SaveSTATUS,w
movwf STATUS
swapf SaveW,f
swapf SaveW,w
retfie
; ----- Tables on first 256 bytes
Seg7Tab:
addwf PCL,f ; return 7segment patterns for 0..9
dt _0,_1,_2,_3,_4,_5,_6,_7,_8,_9;_A,_b,_c,_d,_E,_F
addr2mask:
swapf CURRADR,w
;bit2mask:
andlw 7
addwf PCL,f
dt 1,2,4,8,16,32,64,128
KeyServ:
btfsc dccServiceMode ; discard in Service Mode
clrf keyNew
tstf keyNew
skpnz
return ; do nothing when no key pressed
; btfsc keyNew,0
clrw ; zero for SEL key
btfsc keyNew,5 ; F0/+ key
movlw 0x01 ; one for F0/+ key
btfsc keyNew,6 ; F1/- key
movlw 0x02 ; two for F1/- key
btfsc keyNew,1 ; STOP key
movlw 0x03 ; three for STOP key
clrf keyNew ; eat up
addwf MENU,w ; apply to current active menu
addwf PCL,f ; jump to options
;Menu00Run:
goto K_EnterLok ; Normal Operations
goto K_ChangeDirRun ; K_F1 ***
goto K_Light ; ***
goto K_Stop
;Menu04Lok:
goto K_EnterFunc ;Dir ; Change Loco number ***
goto K_LokUp
goto K_LokDwn
goto K_Stop
;Menu08Dir:
goto K_EnterRun ; Change Loco Dir/Step
goto K_ChangeDir
goto K_ChangeStep
goto K_Stop
;Menu0CSetup:
return ; Setup
goto K_SelSetup
goto K_EnterPar
goto INIT
;Menu10Xbus:
goto K_SelSetup ; Change Xbus address
goto K_XbusUp
goto K_XbusDwn
goto INIT
;Menu14Pot:
goto K_SelSetup ; Change pot offset
goto K_potSetMax ; Recalculate PWM frequency to have 128 (64 shown) at end of scale, minimize dead zone with maximum speed
goto K_potSetCenter ; Take the current potentiometer value as zero
goto INIT
;Menu18Fnc:
goto K_EnterDir ; Additional functions
goto K_F1
goto K_F2
goto K_Stop
if ($ > 255)
ERROR "Tables exceed page 0!"
endif
; ----- Initialization
INIT:
clrf RCSTA ; disable UART
clrf PORTA
clrf PORTB ; all zero, MAX485: receive
BANK1 ;Bank 1
movlwf 0xF6,TRISB ; all outputs except serial port and display
movlwf 0xFF,TRISA ; RA1,RA2: comparator input,RA5: key input, else display
movlwf 0x20,PIE1 ; RX interrupt (peripheral interrupt)
movlwf 0x81,OPTION_REG ; Option reg: timer prescaler 1:4, no pull-ups
; Port B Pull-up: 1 disabled
; External int edge: 0 falling
; Timer trigger source: 0 clock
; Timer trigger edge: 0 rising
; Prescaler assignment: 0 Timer0
; Prescaler value: 001 1:4
movlwf 0x0B,PCON ; internal oscillator to 4MHz default
movlwf 0x00,VRCON ; VRR off
BANK0 ;Bank 0
movlwf 0x05,CMCON ; RA1, RA2 analog inputs, One comparator No.2
movlwf 0x20,FSR ; Clear variables bank0
_ClearRAM
clrf INDF
incf FSR,f
btfss FSR,7
goto _ClearRAM
call nextMask2Disp ; prepare SEL key for query (= drive RA0 into opposite direction to the pullup/pulldown on RA5)
call SetMyAddress ; get Xbus address
call UART_INI ; Init peripherals
call IniPWM
movlwf 0xC0,INTCON ; GIE enable, PEIE enable
movlwf _MI,digitH ; Diplay '--'
movwf digitL
call keyGetOne
btfsc keyCollect,0 ; Key input (SEL pressed while startup?)
goto Setup
movlw low(EEA_LOK) ; get last loco used
call EE_Read
movwf CURRADR
movlwf b'01100000',CURRFNC ; default 28 steps, forward, FL,F1..F4 off
call GetVersion ; get command station version & type
call startTimeout
FindStation:
call idlepoll
btfsc PIR1,TMR1IF
call NotFound ; timeout
btfsc xfNewTx ; packet sent, command station present
goto FindStation
call GetStatus ; get current status
call startTimeout
call K_EnterRun ; show last loco used and get info
call LoadCurDir
call GetLocoInfo
bsf flTestAD
MainStabilize:
call idlepoll
btfss PIR1,TMR1IF ; wait stabilize time 1 sec
goto MainStabilize
call endTimeout
MLoop:
call idlepoll
call Pot2Speed
call KeyServ ; On new key do action
goto MLoop
startTimeout:
movlwf 0x31,T1CON ; Timer 1. Run, set 1:8 Prescaler, overflow: 0.5 s
clrf TMR1L ; time to transmit changes to command station
clrf TMR1H ; and read keys and pot value
bcf PIR1,TMR1IF
return
endTimeout:
clrf T1CON ; stop timer
bcf PIR1,TMR1IF
return
potPoll:
btfss PIR1,TMR1IF ; timeout occured?
goto _ppnt
call endTimeout
tstf MENU
bnz _ppnt
movfw CURRADR
call DispDez
call SetFlash
_ppnt btfss PIR1,TMR2IF ; Bit 1
return ; no overflow of PWM timing generator (after postscaling)
bcf PIR1,TMR2IF
;check status of analog comparator and increment or decrement the PWM width
btfss CMCON,C2OUT ; 1 if potentiometer voltage is higher than PWM DAC voltage
goto _ppLower
movlw 199
xorwf potPWM,w
skpz ; not above 199
incf potPWM,f
goto _ppSet
_ppLower
tstf potPWM
skpz ; not below zero
decf potPWM,f
;Set CCPR1L and CCP1CON to the new 10-bit PWM value
;Non-atomic setting process allows slight jitter if a Timer2 overflow occurs meanwhile
_ppSet: rrf potPWM,w
movwf FSR ; use as temporary register (no WREG available)
rrf FSR,w
andlw 0x3F ; mask-out shift-in bits (no LSR available)
movwf CCPR1L ; (this value is taken for the next PWM cycle)
swapf potPWM,w
andlw 0x30 ; take the two LSB to right position
iorlw 0x0C
movwf CCP1CON ; set CCP1X and CCP1Y accordingly
;Accumulate current potPWM 256 times to get a mean value
movfw potPWM
addwf potFiltL,f
skpnc
incf potFiltH,f
incfsz potFiltN,f
return ; 256 summations not reached
;Set the newly found mean value for further processing
movfwf potFiltH,potValue
clrf potFiltL ; prepare accumulator for next accumulation
clrf potFiltH
bsf flPotValue ; flag having a new value (nominal each 102.4 ms)
;Furthermore, rotate flash mask each ≈100 ms
lslf flashSeq,f
skpnc
bsf flashSeq,0 ; additionally, rotate flash mask
return
idlepoll: ;idle handler
call potPoll
call poll1ms ; update display and keyboard
btfsc xfNewMsg ; new message received?
call HandleMsg ; yes, handle it
return
; --- No Command Station avaiable
NotFound:
bcf PIR1,TMR1IF
movlwf _E|0x80,digitH ; show error 'E.7'. SimpleMaus not addressed
movlwf _7,digitL
return
; --- Setup Loop
Setup:
call K_EnterSetup ; Select Menu setup
;movlwf 0x01,T1CON ; Timer1 Run, set 1:1 Prescaler
SetupLoop:
call ShowPot ; show pot value if needed
call potPoll
call poll1ms ; update display and keyboard
call KeyServ ; On new key do action
goto SetupLoop
; --- Xbus Incoming messages
HandleMsg: ; Handle incoming messages
Handle6X:
movfw HEADER
Handle61:
xorlw 0x61
bnz Handle6X_2
Handle61_0:
movfw DATA1
bnz Handle61_1
btfsc dccEmergOff ; second transmision
goto HandleEnd
bcf dccServiceMode ; Track power off
bsf dccEmergOff
bcf dccEmergStop
HandleOff:
movlwf _E|0x80,digitH ; display 'E0'
movlwf _0,digitL
call SetFlash
goto HandleHomeEnd
Handle61_1:
xorlw 0x01
bnz Handle61_2
bcf dccServiceMode ; normal operations resume
bcf dccEmergOff
bcf dccEmergStop
HandleResume:
movfw CURRADR ; show current loco
call DispDez
call SetFlash
movfw DCC_STATUS ; second transmision?
andlw b'00001011'
bz HandleHomeEnd ; yes, prevent hanging
bcf xfNewMsg ; message processed
call GetLocoInfo ; get loco info
goto HandleHomeEnd
Handle61_2:
xorlw (0x01 ^ 0x02)
bnz HandleEnd
btfsc dccServiceMode ; second transmision
goto HandleEnd
bsf dccServiceMode ; Service Mode entry
bcf dccEmergOff
bcf dccEmergStop
HandleServ:
movlwf _5,digitH ; display 'SP'
movlwf _P,digitL
call SetFlash
goto HandleHomeEnd
Handle6X_2:
xorlw (0x61 ^ 0x62)
bnz Handle6X_3
Handle62:
movfw DATA1
xorlw 0x22
bnz HandleEnd
movfwf DATA2,DCC_STATUS; Commnad station status response
btfsc dccServiceMode ; set correct status
goto HandleServ
btfsc dccEmergOff
goto HandleOff
btfsc dccEmergStop
goto HandleStop
goto HandleResume
Handle6X_3:
xorlw (0x62 ^ 0x63)
bnz Handle8X
Handle63:
movfw DATA1
xorlw 0x21
bnz HandleEnd
movfwf DATA2,XBUS_VER ; software version
movfwf DATA3,XBUS_STA
goto HandleEnd
Handle8X:
movfw HEADER
Handle81:
xorlw 0x81
bnz HandleEX
movfw DATA1
bnz HandleEnd
btfsc dccEmergStop ; second transmision
goto HandleEnd
bcf dccServiceMode ; Emergency stop
bcf dccEmergOff
bsf dccEmergStop
HandleStop:
movlwf _E|0x80,digitH ; show error 'E.S'. Emergency stop
movlwf _5,digitL
call SetFlash
goto HandleHomeEnd
HandleEX:
movfw HEADER
HandleEX_1:
xorlw 0xE1 ; MU+DH errors
bnz HandleEX_2
HandleE1:
movfw DATA1
xorlw 0x88 ; stack full
bnz HandleEnd
HandleFull:
movlwf _5,digitH ; show error 'S-'. Stack full
movlwf _MI,digitL
movlwf b'11001100',flashSeq ; fast flashing
goto HandleEnd
HandleEX_2:
xorlw (0xE1 ^ 0xE2)
bnz HandleEX_3
HandleE2:
movfw DATA1 ; Loco information
andlw 0xF0
xorlw 0x20
bnz HandleEnd
bsf flMultiUnit ; set Multi Unit
movfwf CURRADR,CURRMTR ; no functions
clrf CURRFNC
goto HandleE4_Data
HandleEX_3:
xorlw (0xE2 ^ 0xE3) ; Loco is being operated by another device
bnz HandleEX_4
movfw DATA1 ; Loco information
xorlw 0x40
bnz HandleEnd
movfw DATA2 ; check with CURRADR
andlw 0x3F
bnz HandleEnd
movfw DATA3
xorwf CURRADR,w
bnz HandleEnd
bsf flLocoOther ; operated by other
call SetFlash
bsf flTestAD
goto HandleEnd
HandleEX_4:
xorlw (0xE3 ^ 0xE4)
bnz HandleEX_5
HandleE4_0:
movfw DATA1 ; Loco information normal locomotive
andlw 0xF0
bnz HandleEnd
bcf flMultiUnit
movfwf CURRADR,CURRMTR ; normal locomotive
HandleE4_Loco:
movfw DATA3 ; function
andlw 0x1F ; clear other bits to be sure
movwf CURRFNC
HandleE4_Data:
btfsc DATA2,7 ; set dir
bsf CURRFNC,LocoDir
btfss DATA1,0 ; set 27 -> steps
btfsc DATA1,1 ; set 28 steps
bsf CURRFNC,Loco28
btfsc DATA1,2 ; set 128 steps
bsf CURRFNC,Loco128
movfw DATA2
andlw 0x7F
movwf CURRSPD
bcf flLocoOther ; loco controlled by other device?
btfsc DATA1,3
bsf flLocoOther
call SetFlash
bsf flTestAD
goto HandleEnd
HandleEX_5:
xorlw 0xE4^0xE5
bnz HandleEnd
HandleE5_1:
movfw DATA1 ; Loco information in multi-unit
andlw 0xF0
xorlw 0x10
bnz HandleEnd
bsf flMultiUnit
movfwf DATA5,CURRMTR ; Loco in a Multi Unit
goto HandleE4_Loco
HandleHomeEnd:
clrf MENU
call SetFlash
HandleEnd:
bcf xfNewMsg ; message processed
return
; --- Request messages
GetVersion:
movlw 0x21 ; get software version request
call SendHeader
movlw 0x21
call SendData
goto SendMsg
GetStatus:
movlw 0x21 ; get status request
call SendHeader
movlw 0x24
call SendData
goto SendMsg
Estop:
movlw 0x80 ; Emergency stop
call SendHeader
goto SendMsg
EOff:
movlw 0x21 ; Emergency Off
call SendHeader
movlw 0x80
call SendData
goto SendMsg
Resume:
movlw 0x21 ; Resume operations request
call SendHeader
movlw 0x81
call SendData
goto SendMsg
GetLocoInfo:
movlw 0xE3 ; Locomotive information request
call SendHeader
movlw 0x00
call SendData
movlw 0x00 ; AH
call SendData
movfw CURRADR ; AL
call SendData
goto SendMsg
LocoOperations:
movlw 0xE4 ; Locomotive operations request
call SendHeader
movlw 0x10 ; 14 steps
btfsc CURRFNC,Loco28
movlw 0x12 ; 28 steps
btfsc CURRFNC,Loco128
movlw 0x13 ; 128 steps
call SendData
movlw 0x00 ; AH
call SendData
movfw CURRADR ; AL
call SendData
movfw CURRSPD
btfsc CURRFNC,LocoDir
iorlw 0x80
call SendData
SaveCurrentLoco:
bcf flLocoOther
call SetFlash
movfwf CURRADR,eeWD ; save current used loco
movlw low(EEA_LOK)
call SaveParam
goto SendMsg
FuncOperations:
movlw 0xE4 ; Function operations request
call SendHeader
movlw 0x20
call SendData
movlw 0x00 ; AH
call SendData
movfw CURRADR ; AL
call SendData
movfw CURRFNC ; Function group 1
andlw 0x1F
call SendData
goto SaveCurrentLoco
; --- XpressNet messages routines
SendHeader: ;doesn't actually send but fills first byte into TxBuf
movwf txData0 ; store W into another location than txData1
_SendHeaderW:
btfsc xfNewMsg ; new message received?
call HandleMsg ; yes, handle it (may call SendData()?)
btfsc xfNewTx ; last message sent?
goto _SendHeaderW ; no, wait
clrf TxPos ; =0
clrf TxCnt ; byte count
movfw txData0
SendData: ;doesn't actually send but fills another byte into TxBuf
movwf txData1 ; (FSR cannot be used here for temporary storage)
movlw TxBuf
addwf TxCnt,w
movwf FSR
movfwf txData1,INDF
xorwf TxPos,f
incf TxCnt,f
return
SendMsg:
movfw TxPos ; Xor byte
call SendData ; after this SendData(), TxPos is zero
bsf xfNewTx ; transmit this (TxCnt is now the block length)
return
SetMyAddress:
movlw low(EEA_ADR) ; read from EEPROM
call EE_Read
andlw 0x1F ; address beetwen 1 to 31
skpnz
movlw 1 ; if invalid address zero was in EEPROM, assume default value of 1
iorlw 0x40 ; anything to transmit default value
movwf MY_ADR
;magic parity calculation, possibly to be optimized for 5 (not 7) variable bit
movwf FSR ; 0000 0011
swapf FSR,w ; 0011 0000
xorwf FSR,f ; 0011 0011
rrf FSR,w ; X001 1001 1
xorwf FSR,f ; X010 1010
btfsc FSR,2
incf FSR,f
btfsc FSR,0 ;parity (1=odd)
bsf MY_ADR,7
return
keyGetOne:
movfw segMask
#ifdef CA
btfss PORTA,5 ; Key input driven low?
#else
btfsc PORTA,5 ; Key input driven high?
#endif
iorwf keyCollect,f ; if yes, remember current segMask bit
return
; --- Display+Keyboard scanning (every 1 ms)
poll1ms:
btfss INTCON,T0IF
return
bcf INTCON,T0IF
;Collect key states for every anode line (up to 8 keys)
;As 1 ms after last display activity has been gone, there is much settling time at this point
call keyGetOne
;Display: Switch to next segment and show up to 2 in parallel
BANK1
movlw ~0x26 ; 5 bits
iorwf TRISA,f ; display off
movlw ~0x0F ; 4 bits
iorwf TRISB,f
BANK0
;PORT values are random for input pins, so ignore them here, don't reset.
call nextMask2Disp ; activate one of 9 anodes
btfsc flashSeq,7 ; mask-off?
return ; dark phase, leave cathodes off
;enable right digit's common cathode/anode?
movfw segMask ; When segMask == 0x00, RA4 is already driven
bz _nr ; Never right digit for the ninth case
andwf digitL,w ; Is this segment on for digitL?
bz _nr
#ifdef CA
BANK1
bcf TRISA,6
BANK0
bsf PORTA,6 ; right digit: enable anode
#else
BANK1
bcf TRISA,4
BANK0
bcf PORTA,4 ; right digit: enable cathode
#endif
;enable left digit's common cathode/anode?
_nr btfsc segMask,7 ; When segMask == 0x80, RA3 is already driven
goto _nl ; Never left digit for the eighth case
movfw segMask
skpnz
movlw 0x80 ; For the ninth case, simulate eighth case
andwf digitH,w ; Is this segment on for left digit?
bz _nl
#ifdef CA
BANK1
bcf TRISA,7
BANK0
bsf PORTA,7 ; left digit: enable anode
#else
BANK1
bcf TRISA,3
BANK0
bcf PORTA,3 ; left digit: enable cathode
#endif
_nl btfss segMask,0 ; handle key events every 9 ms (full multiplex cycle)
return
;Keys: Handle key changes every full display cycle (9 ms) - IMHO no bouncing check needed!
comf keyState,w
andwf keyCollect,w
movwf keyNew ; keys that changed from released to pressed
bz _nonew
movlwf 500/9,keyRepeat ; next event after 0.5 s
_nonew:
movfwf keyCollect,keyState ; keys currently pressed
clrf keyCollect
movfw keyState ; Any keys still pressed?
skpnz
return ; no, do nothing, or let keyNew as-is
decfsz keyRepeat,f ; count-down
return
movwf keyNew ; copy keyState to keyNew
movlwf 200/9,keyRepeat ; next key event after 0.2 s (faster)
return
nextMask2Disp: ;outputs the current digit mask to the 7 anode outputs, one at a time.
movfw segMask
sublw 0 ; set C when segMask is zero
rlf segMask,f ; this way, count nine Segment bits: 0x01-0x02-0x04-0x08-0x10-0x20-0x40-0x80-0x00
;PROBLEM: As output latch cannot be read back, inputs held low in this moment loose their pullup, and vice versa.
;(Moreover, outputs driving high and shortened to ground will then drive low, and vice versa.)
;Disabling interrupts will not help. Luckily, only PortB has pullups and these are disabled.
movlw 0xFF
tstf segMask
skpnz ;dp (right digit)
#ifdef CA
xorlw 1<<7 ;dp (right)
btfsc segMask,4
xorlw 1<<0 ;e
btfsc segMask,5
xorlw 1<<3 ;f
btfsc segMask,6
xorlw 1<<4 ;g
btfsc segMask,7
xorlw 1<<6 ;dp (left)
#else
xorlw 1<<4 ;wirkungslos weil RA4 ein Open-Drain-Ausgang (bis 8,5V) ist!! Ätsch!!
btfsc segMask,0 ;a
xorlw 1<<0
btfsc segMask,5 ;f
xorlw 1<<7
btfsc segMask,6 ;g
xorlw 1<<6
btfsc segMask,7 ;dp (left digit)
xorlw 1<<3
#endif
BANK1
andwf TRISA,f
BANK0
#ifdef CA
andwf PORTA,f
#else
xorlw 0xFF
iorwf PORTA,f
#endif
#ifdef CA
swapf segMask,w ;dcbahgfe
#else
rlf segMask,w ;gfedcbaX
movwf FSR ;use as temporary register
rlf FSR,f ;fedcbaXg
rlf FSR,w ;edcbaXgf
#endif
andlw 0xF0 ;edcb0000 / dcba0000
xorlw 0xFF
BANK1
andwf TRISB,f
BANK0
#ifdef CA
andwf PORTB,f
#else
xorlw 0xFF
iorwf PORTB,f ;allright
#endif
return
; ---- Display
DispDez: ;out binary number <w> = 00..99 or "oL" on overflow
; As display multiplexing isn't done in an ISR, digit positions can be reused here.
movwf digitL
movlw 100
subwf digitL,w
bc _l2 ; non-borrow case
;Simply repeat subtracting 10 until underflow occurs
clrf digitH
movlw 10
_l1: incf digitH,f
subwf digitL,f
bc _l1 ;loop while no underflow at subtraction; inverted carry
addwf digitL,w ; back-correction and load (will set C)
call Seg7Tab ; now convert 0..9 to 7segment codes
movwf digitL
decf digitH,w ; back-correction and load
call Seg7Tab
_l3: movwf digitH
return ; always return with C=1
_l2: movlwf _L,digitL ; show overflow (it's a runtime error)
movlw _o
goto _l3
SetFlash:
clrw ; always on
tstf MENU
skpz
movlw b'00000001' ; minimum flashing
btfsc flLocoOther
movlw b'11001100' ; fast flashing
btfsc dccEmergOff
movlw b'11000011' ; slow flashing
btfsc dccEmergStop
movlw b'11000011' ; slow flashing
btfsc dccServiceMode
movlw b'11000011' ; slow flashing
movwf flashSeq
return
dirbitaddr:
movfw CURRADR
andlw 0x0F
addlw low(EEA_DIR)
return
LoadCurDir:
bcf flNegDir
call dirbitaddr
call EE_Read
movwf eeWD
call addr2mask
andwf eeWD,w
skpz
bsf flNegDir
return
SaveCurDir:
call dirbitaddr
call EE_Read
movwf eeWD
call addr2mask
iorwf eeWD,f ; set bit
btfss flNegDir
xorwf eeWD,f ; reset bit
call SavePar1
retlw 99 ; prepare for wrap around
; ----- Menu Keys
K_EnterLok:
movlwf _L,digitH ; Menu Loco selection. Diplay 'L '
clrf digitL ; TODO: Releasing SEL key should lead to display current loco number
movlwf MENU_LOK,MENU
call SetFlash
return
K_LokUp:
call SaveCurDir
xorwf CURRADR,w
skpnz
clrf CURRADR
incf CURRADR,f ; Increment Loco number
K_LokEnd:
call LoadCurDir
call GetLocoInfo
movfw CURRADR
goto DispDez
K_LokDwn:
call SaveCurDir
decf CURRADR,f ; Decrement Loco number
skpnz
movwf CURRADR
goto K_LokEnd
K_Light:
movlw 0x10 ; Change FL ***
xorwf CURRFNC,f ; Change Fx
goto FuncOperations
K_F2:
movlw 0x02 ; Change F2 ***
goto K_Func
K_F1:
movlw 0x01 ; Change F1
goto K_Func
K_Func:
xorwf CURRFNC,f ; Change Fx
call FuncOperations
K_EnterFunc:
movlw _DN ; '_' F2 status ***
btfsc CURRFNC,1
movlw _UP ; '¯'
movwf digitL
movlw _DN ; '_' F1 status
btfsc CURRFNC,0
movlw _UP ; '¯'
movwf digitH
movlwf MENU_FNC,MENU
call SetFlash
return
K_ChangeDir:
movlw 1<<LocoDir ; Change current loco direction
xorwf CURRFNC,f
xorwf FLAGS,f ; Change both flags!
call LocoOperations
K_EnterDir:
tstf SPEED ; Change Dir / steps only in speed 0
bnz K_EnterRun
btfss CURRFNC,LocoDir ; Menu Direction/Steps.
goto K_EnterDirBack
; Forward 'd-'
movlwf _d,digitH ; 'd'
movlw _DN ; '_' 14 steps
btfsc CURRFNC,Loco28
movlw _MI ; '-' 28 steps
btfsc CURRFNC,Loco128
movlw _UP ; '¯' 128 steps
movwf digitL
goto K_EnterDirEnd
K_EnterDirBack: ; Backward '-d'
movlw _DN ; '_' 14 steps
btfsc CURRFNC,Loco28
movlw _MI ; '-' 28 steps
btfsc CURRFNC,Loco128
movlw _UP ; '¯' 128 steps
movwf digitH
movlw _d ; 'd'
movwf digitL
K_EnterDirEnd:
movlwf MENU_DIR,MENU
call SetFlash
return
K_ChangeDirRun:
movlw 1<<LocoDir ; Change current loco direction ***
xorwf CURRFNC,f
xorwf FLAGS,f ; TODO: Genauso wie K_ChangeDir
call LocoOperations
K_EnterRun:
clrf MENU ; Main menu, show loco number
call SetFlash
movfw CURRADR
goto DispDez
K_ChangeStep:
tstf SPEED ; only if speed 0
skpz
return
movlw b'10000000' ; change step 14,28,128
btfss CURRFNC,Loco128
movlw b'01000000'
btfsc CURRFNC,Loco28
movlw b'11000000'
xorwf CURRFNC,f
call LocoOperations ; update new steps
goto K_EnterDir
K_Stop:
btfsc dccEmergStop ; in E_stop or E_Off?
K_StopOn:
goto Resume ; yes, Resume
K_StopOff:
btfsc dccEmergOff
goto K_StopOn
goto EOff ; no, do Emergency Off
K_XbusUp:
movlw low(EEA_ADR) ; Increment Xbus address
call EE_Read
movwf eeWD
incf eeWD,f
movlw 32
xorwf eeWD,w
movlw 1
skpnz
movwf eeWD
K_XbusUpdate:
movlw low(EEA_ADR)
call SaveParam
movfw eeWD
goto DispDez
K_XbusDwn:
movlw low(EEA_ADR) ; Decrement Xbus address
call EE_Read
movwf eeWD
decf eeWD,f
movlw 31
skpnz
movwf eeWD
goto K_XbusUpdate
K_SelSetup:
btfss flSelSetup ; Change setup menu selection
goto K_EnterSetupPot
K_EnterSetup:
bcf flSelSetup
movlw _A ; show 'A '
goto _ese
K_EnterSetupPot:
bsf flSelSetup
movlw _P ; show 'P '
_ese:
movwf digitH
clrf digitL
movlwf MENU_SETUP,MENU
return
K_EnterPar:
movlw MENU_POT ; Set selected setup menu
btfss flSelSetup
movlw MENU_XBUS
movwf MENU
btfsc flSelSetup
return
movlw low(EEA_ADR) ; Show current Xbus address
call EE_Read
goto DispDez
K_potSetCenter:
movfwf potValue,potCenter ; set new offset
movwf eeWD
movlw low(EEA_POT)
goto SaveParam
K_potSetMax:
;PR2' = PR2 * 128 / potValue, really needs division here!
BANK1
movfw PR2
BANK0
movwf DH
clrf DL
;div16 routine, from http://www.tu-chemnitz.de/~heha/Mikrocontroller/Division.a16
movlwf 8,FSR ; load bit count
goto div16_skipshift ; as DH:DL is loaded with PR2*256, don't shift for first iteration
div16_loop
lslf DL,f
rlf DH,f
bc div16_overbit
div16_skipshift
movfw potValue
subwf DH,w
bnc div16_zerobit
div16_overbit
movfw potValue
subwf DH,f
bsf DL,0
div16_zerobit
loop FSR,div16_loop
;now D_HI is reminder and D_LO is quotient
movfw DL ; only use quotient, discard reminder
call setPR2 ; adjusts W to be in valid range
movwf eeWD
movlw low(EEA_MAX)
goto SaveParam
setPR2: ;set PR2 (Timer2 maximum count) to W but check W for useful (valid) range.
;Returns W with new PR2 value in effect.
movwf FSR ; no WREG available
btfss FSR,7 ; Bit7 must be clear
btfsc FSR,6 ; Bit6 must be clear
movlw 199>>2
btfss FSR,5 ; Bit5 must be set: value between 32 and 63
movlw 199>>2
BANK1
movwf PR2 ; PWM frequency = 20 kHz allows DAC values between 0 and 199 (= handy limits)
;Changing this value allows for easy dead-area compression at the end of potentiometer area.
;At potentiometer end, the PWM should read 128, to compensate slight errors of R7.
BANK0
return
; ----- Analog
IniPWM:
movlw low(EEA_MAX)
call EE_Read
call setPR2
movlwf 0x3C,T2CON ; enable TMR2 1:1 prescaler, 1:8 postscaler (400 µs between interrupts, 2.5 kHz)
movlwf 0x0C,CCP1CON ; enable PWM mode
movlw low(EEA_POT) ; read pot. offset
call EE_Read
movwf potCenter
return
;Read potentiometer value from RAM and — if changed — send it to loco
Pot2Speed:
btfss flPotValue
return
bcf flPotValue
btfsc xfNewTx ; don't read if waiting to send
return
bcf flPotLeft
movfw potCenter
bz _asym
subwf potValue,w ; convert pot. position to speed
bc _pos
sublw 0 ; negate (make positive)
bsf flPotLeft
_pos addlw -4 ; dead potentiometer zone of ±4?
skpc
clrw ; yes, set speed to zero
movwf FSR
lslf FSR,f ; double value
goto _ps
_asym movfwf potValue,FSR
_ps btfsc flTestAD
goto PotDiscard
btfsc CURRFNC,Loco28 ; convert to current speed step
goto PotSpd28
btfsc CURRFNC,Loco128
goto PotSpd128
;PotSpd14:
lsrf FSR,f ; adjust for available range
lsrf FSR,f
lsrf FSR,f ; 14 steps (0, 1..14)
movlw 14
call limitFSR
skpnz
return
call dispFSR ; show 0..14
skpz
addlw 1 ; set 0, 2..15
goto _setspd
PotSpd28: ; 28 steps (4..31)
lsrf FSR,f ; adjust for available range
lsrf FSR,f
movlw 28
call limitFSR
skpnz
return
call dispFSR ; show 0..28
skpz
addlw 3 ; skip Emergency Stop (02) and Reserved (01, 03)
movwf FSR
rrf FSR,w ; set bit 4 as LSB
skpnc
iorlw 0x10
goto _setspd
PotSpd128:
movlw 126
call limitFSR
skpnz
return
xorwf SPEED,f
lsrf SPEED,w ; show 0..63 (no digits available for showing 0..127)
call df1
skpz
addlw 1 ; set 0, 2..127
tstf MENU
bnz _setspd
btfsc SPEED,0
bsf digitL,7 ; show decimal point indicating half-steps
_setspd movwf CURRSPD ; set speed for sending over XpressNet
tstf SPEED
bz LocoOperations ; in case of zero speed, don't change direction in CURRFNC
movfw FLAGS ; flNegDir (normally same as CURRFNC.LocoDir)
xorwf XFLAGS,w ; flPotLeft (always 0 for potCenter==0)
xorwf CURRFNC,w
andlw 1<<LocoDir
xorwf CURRFNC,f ; set LocoDir bit to flPotLeft ^ flNegDir
goto LocoOperations
PotDiscard:
bcf flTestAD
return
;helper routines for FlashPotSpeed()
limitFSR: ;limit FSR value not greater than W, then load FSR to W, then compare to SPEED
subwf FSR,w ; W' = FSR-W, C=1 when FSR >= W
skpnc
subwf FSR,f ; FSR' = FSR-W' = FSR-(FSR-W) = W, now FSR is set to original W
movfw FSR
xorwf SPEED,w
return ; return W = FSR ^ SPEED, Z=1 if speed unchanged
dispFSR: ; set SPEED to FSR, display it, and load SPEED at end for calculating CURRSPD
movfwf FSR,SPEED ; set new SPEED value
;only if address is currently shown
df1: tstf MENU
bnz df2
call DispDez
clrf flashSeq ; nicht blinkend
call startTimeout ; Set timeout for fallback showing current loco address
df2: movfw SPEED
return
ShowPot:
btfss flPotValue
return
bcf flPotValue
movfw MENU ; show only in Setup Pot. Menu
xorlw MENU_POT
skpz
return
lsrf potValue,w
call DispDez
btfsc potValue,0
bsf digitL,7 ; show decimal point for half-steps
movfw potValue
xorwf potCenter,w
skpnz
bsf digitH,7 ; show decimal point when potCenter is hit
return
; ----- Serial port routines (XBus)
UART_INI:
bcf PORTB,0 ; Disable TX driver
clrf XFLAGS ; clear XpressNet flags
clrf RxLen ; clear input buffer
BANK1 ;Bank 1
movlwf ((10*FXTAL/(16*BaudRate))+5)/10-1,SPBRG ; BRGH = 1
movlwf 0x64,TXSTA ; 9 bit, Enable TX, BRGH=1, TXD9=0
BANK0 ;Bank 0
movlwf 0xD0,RCSTA ; Enable RX, 9 bit
clrw ; Settling time
_settle addlw -1
bnz _settle
movfw RCREG ; flush receive buffer (dummy reads)
movfw RCREG
movfw RCREG
return
;----- Internal EEPROM routines ------------------------------------------------
EE_Read: ;w=address, returns w=data
BANK1
movwf EEADR
EE_Read1:
bsf EECON1,RD
movfw EEDATA
BANK0
return
SavePar1: ;EEADR=address, eeWD=data
BANK1
call EE_Read1
goto _sp
SaveParam: ; w=address, eeWD=data. Write only changes
call EE_Read
_sp xorwf eeWD,w
skpnz
return
movfw eeWD
BANK1
movwf EEDATA
bsf EECON1,WREN
bcf INTCON,GIE
movlwf 0x55,EECON2
movlwf 0xAA,EECON2
bsf EECON1,WR
bsf INTCON,GIE
bcf EECON1,WREN
EEWrite0:
btfsc EECON1,WR
goto EEWrite0
BANK0
return
; ----- EEPROM addresses and default values
org 0x2100
EEA_ADR de 0x01 ; XpressNet device address
EEA_LOK de 0x03 ; Last Locomotive used
EEA_POT de 0x00 ; Potentiometer center position, 0 = left
EEA_MAX de 199>>2 ; PWM frequency for maximum potentiometer value = 128
EEA_DIR fill 0,16 ; direction bit save area, per locomotive (space for upto 128 locos)
de "Simple Maus v.4\n"
de "F.Cañada->J.Fučik->H.Haftmann\n"
de "19/02/05"
end
Detected encoding: UTF-8 | 0
|