Source file: /~heha/basteln/m/Kram/SimpleMaus.zip/SimpleMaus_v4.a14

;======================================================================
;				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-80