;; filename:	ledancer-20110522.asm
;; Copyright (c) 2006-2011 Phil Short
;;
;;  This program is provided free for you to use in any way that you wish,
;;  subject to the laws and regulations where you are using it.  Due diligence
;;  is strongly suggested before using this code.
;;
;;  The Author makes no warranty of any kind, express or implied, with regard
;;  to this program or the documentation contained in this document.  The
;;  Author shall not be liable in any event for incidental or consequential
;;  damages in connection with, or arising out of, the furnishing, performance
;;  or use of these programs.
;;
 #define CLOCKRATE 8000000
 #define BAUDRATE 250000

;; The DMX start address should be between 1 and 505 inclusive.  Since there
;;   is a mode for programming the DMX start address from the DMX port, there
;;   should be no need to change this:	

 #define DMX_START_ADDRESS 1	

;;  --------------------------------------------------------------------------
;;
;; Revision History.
;; 20071216 PJS Restart for DMX variant.
;; 20110514 PJS Mashup for DMX/Charlie-plex 56-channels
;; 20110516 PJS Changed for PIC16F1826
;; 20110519 PJS Remove interrupt logic, put everything in the foreground.
;; 20110522 PJS Changed LED mappings, removed some test code that is never
;;   executed, corrected a few comments here and there.
;; 20110527 PJS Revised comments (no change to operational code).  The changes
;;  in the 'Hardware mappings' just below are to make it agree with the code
;;  and the prototypes, comments later on are suggestions on how to customize
;;  the firmware for alternative LED mappings.
;;
;;  Hardware mappings
;;  PIN 6  (RB0) - 
;;  PIN 7  (RB1) - uart_in
;;  PIN 8  (RB2) - uart_out
;;  PIN 9  (RB3) - 
;;  PIN 10 (RB4) - Output 1
;;  PIN 11 (RB5) - Output 2
;;  PIN 12 (RB6) - Output 3
;;  PIN 13 (RB7) - Output 4

;;  PIN 17 (RA0) - Output 5
;;  PIN 18 (RA1) - Output 6
;;  PIN 1  (RA2) - Output 7
;;  PIN 2  (RA3) - Output 8
;;  PIN 3  (RA4) - (as output, open drain)
;;  PIN 4  (RA5) - VPP/MCLR - input only
;;  PIN 15 (RA6) - Oscillator
;;  PIN 16 (RA7) - Oscillator
;;

 LIST R=DEC
 INCLUDE "p16f1826.inc"

;; Use internal clock (8 MHz)

  __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_ON & _FCMEN_OFF

  __CONFIG _CONFIG2, _PLLEN_OFF & _WRT_OFF & _STVREN_OFF & _BORV_25 & _LVP_OFF

;; Variables that this firmware want to access from all 'banks'.  Note that
;;   there are only eight spaces in this block.
	
 CBLOCK 0x78
	
   pctr				; variable used for dimming
   row_sel		     ; bit map - selects one of 8 sets of LEDs
   mapx			  ; auxiliary variable used in charlie-plexing

   rx_state, rx_count_lsb, rx_count_msb	
   our_addr_lsb, our_addr_msb

 ENDC

;; memory for the 56 dimmer levels - 0x28 -> 0x5F
	
 CBLOCK 0x40
   channel56
   channel55
   channel54
   channel53
   channel52
   channel51
   channel50
   channel49
   channel48
   channel47
   channel46
   channel45
   channel44
   channel43
   channel42
   channel41
   channel40
   channel39
   channel38
   channel37
   channel36
   channel35
   channel34
   channel33
   channel32
   channel31
   channel30
   channel29
   channel28
   channel27
   channel26
   channel25
   channel24
   channel23
   channel22
   channel21
   channel20
   channel19
   channel18
   channel17
   channel16
   channel15
   channel14
   channel13
   channel12
   channel11
   channel10
   channel9
   channel8
   channel7
   channel6
   channel5
   channel4
   channel3
   channel2
   channel1
 ENDC
	
;; Macro used for refreshing the main display LEDs.

SHIFT_LED MACRO channel
	movf	channel,w
	subwf	pctr,w
	rrf	mapx,f
 ENDM

;; Macro used for changing the PIC outputs going to the LEDs.
	
COMMON_EPILOGUE_RETURN MACRO
	movlw	1		; Bank1 for accessing TRIS registers
	movwf	BSR

	movlw	0xff		; now do the hardware outputting.
	movwf	TRISA		; first, tri-state everything to prevent ghosts
	movwf	TRISB
	
	clrf	BSR
	swapf	row_sel,w	; output to the I/O port (from last time thru)
	movwf	PORTA
	
	swapf	row_sel,w
	movwf	PORTB

	incf	BSR,f
	swapf	mapx,w		; now enable PIC outputs (1 cathode + n anodes)
	movwf	TRISA
	
	swapf	mapx,w
	iorlw	2		; ensure that RB1 (RxD) is an input.
	movwf	TRISB

	clrf	BSR
	rlf	row_sel,w
	rlf	row_sel,f

	retlw	0

 ENDM
	
;; derived constants:

;; initializer for baudrate generator (based on formula from datasheet)
;;   divide-by-4 is appropriate because BRGH and BRG16 are both set
	
 #define BAUD_INIT (CLOCKRATE/BAUDRATE/4 - 1)

;; I/O bit definition

;; rx_state machine bit definitions - see description of rx_state usage later
;; in this program (after mainloop label)

 #DEFINE ANY_BIT 2
 #DEFINE DISCARD_BIT 1
 #DEFINE CMD_BIT 0
		
;; ---------------------------------------------------------------------------

 org 0
	nop
	goto initialize

;;
;; Interrupt Code (none in this design)
;;
	
;;
;; Start of Foreground code.
;;

initialize:
;;
;; initialize memory.
;;
	clrf	BSR
	
	movlw	channel1
	movwf	FSR0L
	clrf	FSR0H
	
init_clr_loop:	
	clrf	INDF0
	decf	FSR0L,f
	btfsc	FSR0L,6
	goto	init_clr_loop ; loop as long

	movwf	FSR0L
;;
;; initialize some registers
;;
	movlw	(1 << ANY_BIT) | (1 << DISCARD_BIT)
	movwf	rx_state

	movlw	0xFE
	movwf	row_sel
	
	BANKSEL	OPTION_REG	; Bank1
	movlw	0x08		; do not assign prescaler to timer0
	movwf	OPTION_REG
		
;; configure the clock (change frequency, nothing more).

	movlw	(1<<IRCF3) | (1<<IRCF2) | (1<<IRCF1) ; 8 MHz should do..
	movwf	OSCCON		; register is in bank 1
	
;; configure the I/O ports (turn off hardware features that might conflict with
;;   s/w control of the IO ports).

	bcf	ADCON0,ADON	; disable ADC (register in bank 1)

	BANKSEL	DACCON0
	bcf	DACCON0,DACEN	; disable DAC (register in bank 2)

	bcf	CM1CON0,C1ON	; disable comparator 1 (register in bank 2)
	bcf	CM2CON0,C2ON	; ditto for comparator 2

	BANKSEL	ANSELA		; Bank 4

	clrf	ANSELA
	clrf	ANSELB
	
;; configure the UART

	BANKSEL	BAUDCON		; UART functions (registers in bank 3)

	movlw	(1<<BRG16)
	movwf	BAUDCON
	movlw	BAUD_INIT;
	movwf	SPBRG
	clrf	SPBRGH
		
	movlw	(1<<TXEN) | (1<<BRGH); enable 8-bit transmitter in async mode
				; (x16 baud divisor)
	movwf	TXSTA
	movlw	(1<<SPEN) | (1<<CREN); enable serial port, continuous 8-bit rx
	movwf	RCSTA
;;
;; read the dmx address from the EEPROM
;;
	movlw	0x00
	movwf	EEADR ^ 0x80
	movwf	EEADRH ^ 0x80
	movwf	EECON1 ^ 0x80

	bsf	EECON1 ^ 0x80,RD
	movf	EEDAT ^ 0x80,w

	movwf	our_addr_msb

	incf	EEADR ^ 0x80,f
	
	bsf	EECON1 ^ 0x80,RD; initiate read operation
	movf	EEDAT ^ 0x80,w

	btfsc	STATUS,Z	; tweak the MSB if LSB==0 to make is usable
	decf	our_addr_msb,f	; with the logic in the Rx section.
	
	movwf	our_addr_lsb
	
skip_addr_manipulation:
	
;; Most of the main loop should be in bank0, to access the channelx registers
;;   and PortA/B.  The code in do_common_stuff is in bank3 (to access the UART
;;   and EEPROM registers), but it returns to bank0 for the epilogue code.

	BANKSEL	PORTA
	
;; Each of the blocks in the following code will always take fixed amount of
;;   time to execute.  This time may vary as the code changes, but should be
;;   less than 44 uS (88 instructions) in order to be able to successfully
;;   receive DMX frames.  Each block is 27 instructions long plus instructions
;;   in do_common_stuff.

;;	bsf	channel28,6 ; test code - turn on the 28th LED (only)

  #DEFINE CONFIG_JUMP PORTA,5

;; Do not change the code in these eight blocks, except to change the 'channel
;;   numbers' to agree with your actual LED mapping (e.g. you can change the
;;   the 'channel1' a few lines hence to 'channel 56', but don't move any of
;;   the other code around unless you really know what you are doing).
;;
mainloop:
;; ---------- First Block - LEDs 1-7
	nop
	bcf	mapx,7

	SHIFT_LED channel1	; Cathode = Output1, Anode = Output2
	SHIFT_LED channel2	; Cathode = Output1, Anode = Output3
	SHIFT_LED channel3	; Cathode = Output1, Anode = Output4
	SHIFT_LED channel4	; Cathode = Output1, Anode = Output5
	SHIFT_LED channel5	; Cathode = Output1, Anode = Output6
	SHIFT_LED channel6	; Cathode = Output1, Anode = Output7
	SHIFT_LED channel7	; Cathode = Output1, Anode = Output8

	call	do_common_stuff

	btfss	CONFIG_JUMP
	goto	enter_config_mode

;; ---------- Second Block - LEDs 8-14

	SHIFT_LED channel14	; Cathode = Output2, Anode = Output1
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel8	; Cathode = Output2, Anode = Output3
	SHIFT_LED channel9	; Cathode = Output2, Anode = Output4
	SHIFT_LED channel10	; Cathode = Output2, Anode = Output5
	SHIFT_LED channel11	; Cathode = Output2, Anode = Output6
	SHIFT_LED channel12	; Cathode = Output2, Anode = Output7
	SHIFT_LED channel13	; Cathode = Output2, Anode = Output8

	call	do_common_stuff

	nop
	nop
	
;; ---------- Third Block - LEDs 15-21
	
	SHIFT_LED channel20	; Cathode = Output3, Anode = Output1
	SHIFT_LED channel21	; Cathode = Output3, Anode = Output2
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel15	; Cathode = Output3, Anode = Output4
	SHIFT_LED channel16	; Cathode = Output3, Anode = Output5
	SHIFT_LED channel17	; Cathode = Output3, Anode = Output6
	SHIFT_LED channel18	; Cathode = Output3, Anode = Output7
	SHIFT_LED channel19	; Cathode = Output3, Anode = Output8

	call	do_common_stuff

	nop
	nop
	
;; ---------- Fourth Block - LEDs 22-28
	
	SHIFT_LED channel26	; Cathode = Output4, Anode = Output1
	SHIFT_LED channel27	; Cathode = Output4, Anode = Output2
	SHIFT_LED channel28	; Cathode = Output4, Anode = Output3
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel22	; Cathode = Output4, Anode = Output5
	SHIFT_LED channel23	; Cathode = Output4, Anode = Output6
	SHIFT_LED channel24	; Cathode = Output4, Anode = Output7
	SHIFT_LED channel25	; Cathode = Output4, Anode = Output8

	call	do_common_stuff

	nop
	nop
	
;; ---------- Fifth Block - LEDs 29-35
	
	SHIFT_LED channel32	; Cathode = Output5, Anode = Output1
	SHIFT_LED channel33	; Cathode = Output5, Anode = Output2
	SHIFT_LED channel34	; Cathode = Output5, Anode = Output3
	SHIFT_LED channel35	; Cathode = Output5, Anode = Output4
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel29	; Cathode = Output5, Anode = Output6
	SHIFT_LED channel30	; Cathode = Output5, Anode = Output7
	SHIFT_LED channel31	; Cathode = Output5, Anode = Output8

	call	do_common_stuff

	nop
	nop
	
;; ---------- Sixth Block - LEDs 36-42
	
	SHIFT_LED channel38	; Cathode = Output6, Anode = Output1
	SHIFT_LED channel39	; Cathode = Output6, Anode = Output2
	SHIFT_LED channel40	; Cathode = Output6, Anode = Output3
	SHIFT_LED channel41	; Cathode = Output6, Anode = Output4
	SHIFT_LED channel42	; Cathode = Output6, Anode = Output5
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel36	; Cathode = Output6, Anode = Output7
	SHIFT_LED channel37	; Cathode = Output6, Anode = Output8

	nop
	nop
	
	call	do_common_stuff

;; ---------- Seventh Block - LEDs 43-49
	
	SHIFT_LED channel44	; Cathode = Output7, Anode = Output1
	SHIFT_LED channel45	; Cathode = Output7, Anode = Output2
	SHIFT_LED channel46	; Cathode = Output7, Anode = Output3
	SHIFT_LED channel47	; Cathode = Output7, Anode = Output4
	SHIFT_LED channel48	; Cathode = Output7, Anode = Output5
	SHIFT_LED channel49	; Cathode = Output7, Anode = Output6
	rrf	mapx,f
	bcf	mapx,7
	SHIFT_LED channel43	; Cathode = Output7, Anode = Output8

	movlw	8
	addwf	pctr,f
	
	call	do_common_stuff

;; ---------- Last Block - LEDs 50-56
	
	SHIFT_LED channel50	; Cathode = Output8, Anode = Output1
	SHIFT_LED channel51	; Cathode = Output8, Anode = Output2
	SHIFT_LED channel52	; Cathode = Output8, Anode = Output3
	SHIFT_LED channel53	; Cathode = Output8, Anode = Output4
	SHIFT_LED channel54	; Cathode = Output8, Anode = Output5
	SHIFT_LED channel55	; Cathode = Output8, Anode = Output6
	SHIFT_LED channel56	; Cathode = Output8, Anode = Output7
	rrf	mapx,f
	bcf	mapx,7

	call	do_common_stuff

	goto	mainloop	
	
;; ----------------- Serial Rx Data Processing (DMX512) -----------------------
;; rx_state:
;; xxxxx110 -> discard data (non-zero start code or uart error)
;; xxxxx101 -> process START_CODE byte (just after rx Break)
;; xxxxx100 -> copy data to transmitter (START_CODE <> 0, after last byte for
;;               this PIC).
;; xxxxx000 -> process user data (START_CODE through last byte for this PIC)

do_common_stuff:

	btfss	PIR1,RCIF	; skip if UART has a rx character to process
	goto	common_epilogue0 ; no character available

	movlw	3		; bank3 for UART and EEPROM functions
	movwf	BSR
	
	btfsc	RCSTA,FERR	; read framing error before reading char
	goto	frame_err

	movf	RCREG,w		; read the character
;;
;; must preserve w until the movwf INDF0 below
;;
	btfsc	RCSTA,OERR	; and check the overrun error bit.
	goto	overrun_err

	btfsc	rx_state,ANY_BIT
	goto	not_data_octet
;;
;; we are expecting data for this node or data between the START_CODE and the
;; data for this code.
;; 

	decfsz	rx_count_lsb,f
	goto	common_epilogue1

	btfsc	rx_count_msb,0
	goto	still_waiting

;; fall through to here when msb == 0 && lsb == 1 (from before the decfsz)
	movwf	INDF0
	decf	FSR0L,f

	btfss	FSR0L,6
	bsf	rx_state,ANY_BIT; go into copy-through state if done storing

	incf	rx_count_lsb,f	; restore lsb (to '1')
	
common_epilogue:

	COMMON_EPILOGUE_RETURN
	
still_waiting:			; 14 instructions to here.
	decf	rx_count_msb,f
	nop
	goto	common_epilogue
		
;; ANY_BIT was set (in copy, discard, or process_start_code state)
not_data_octet:
	addlw	0		; check for START_CODE == 0

	btfss	rx_state,CMD_BIT;
	iorlw	1		; clear carry if not expecting START_CODE

	btfsc	STATUS,Z
	clrf	rx_state	; CMD bit was set and START_CODE == 0
	
	bcf	rx_state,CMD_BIT
	goto	common_epilogue

;;
;; could be either the sync byte (RCREG == 0) or true framing error
;;
frame_err:

	movf	RCREG,w		; read the character

	btfss	STATUS,Z
	goto	true_frame_err

;;
;; A sync byte, not a framing error.
;;
	movlw	(1<<ANY_BIT) | (1 << CMD_BIT)
	movwf	rx_state

	movlw	channel1
	movwf	FSR0L

	movf	our_addr_msb,w
	movwf	rx_count_msb

	movf	our_addr_lsb,w
	movwf	rx_count_lsb

	goto	common_epilogue

true_frame_err:
	movlw	(1 << ANY_BIT) | (1<<DISCARD_BIT)
	movwf	rx_state

	nop
	call	delay_4instructions
		
	goto	common_epilogue
	
;;
;; overrun is a true error ... should not happen in real life, is either a
;; coding error or a PIC hardware error (oscillator is mis-functioning, some
;; other hardware fault in the PIC).
;;
overrun_err:			; eight instructions to here
	bcf	RCSTA,CREN
	bsf	RCSTA,CREN
	bsf	rx_state,DISCARD_BIT

	nop
	call	delay_4instructions
	
	goto	common_epilogue

;;
;; Some delay routines to equalize the delay through the various paths in the
;;   DMX receive code.
;;
common_epilogue0:		; three instructions to here

	nop
	call	delay_4instructions
	call	delay_4instructions
common_epilogue1:		; twelve instructions to here.
	call	delay_4instructions

	goto	common_epilogue

;;
;; A call to this routine causes a 4-instruction delay in execution.
;;
delay_4instructions:	
	return
	
;;
;; So-called jumperless configuration - entered when the main loop detects
;;   that PORTA.5 has been jumpered to ground

enter_config_mode:	

	movlw	0xFF
	movwf	mapx		; turn off the LEDs

	movlw	(1 << ANY_BIT) | (1<<DISCARD_BIT)
	movwf	rx_state

	movlw	1
	movwf	our_addr_lsb
	clrf	our_addr_msb
	
config_mode_rx_loop:
	call	do_common_stuff	; allow the rx state machine to progress

	btfsc	rx_state,ANY_BIT; processing rx data yet?
	goto	config_mode_rx_loop

	movf	FSR0L,w
	xorlw	channel3	; have we received two bytes yet?

	btfss	STATUS,Z
	goto	config_mode_rx_loop

	movf	channel1,w	; sanity check the DMX address
	andlw	0xFE		; must be 0 or 1 (i.e. address < 512)
bad_address_loop:	
	btfss	STATUS,Z
	goto	bad_address_loop

	movf	channel2,w
	iorwf	channel1,w

bad_address_loop1:
	btfsc	STATUS,Z	; address must also not be zero.
	goto	bad_address_loop1

write_2_EEPROM:
	BANKSEL	EEADRL
	
;; write MSB to EEPROM
	movlw	0x0
	movwf	EEADRL
	clrf	EEADRH

	BANKSEL	channel1
	movf	channel1,w
	BANKSEL	EEDATL
	movwf	EEDATL
	
	bcf	EECON1,CFGS
	bcf	EECON1,EEPGD
	bsf	EECON1,WREN
	
	movlw	0x55
	movwf	EECON2
	movlw	0xAA
	movwf	EECON2
	bsf	EECON1,WR
	bsf	INTCON,GIE
	bcf	EECON1,WREN

	btfsc	EECON1,WR
	goto	$-2

	BANKSEL	PIR2
	bcf	PIR2,EEIF
	BANKSEL	EEADRL
 ; write LSB to EEPROM
	
	movlw	0x01		; LSB goes into higher-addressed location
	movwf	EEADRL

	BANKSEL	channel2
	movf	channel2,w
	BANKSEL	EEDATL
	movwf	EEDATL

	bcf	EECON1,CFGS
	bcf	EECON1,EEPGD
	bsf	EECON1,WREN
	
	movlw	0x55
	movwf	EECON2
	movlw	0xAA
	movwf	EECON2
	bsf	EECON1,WR
	bsf	INTCON,GIE
	bcf	EECON1,WREN

	btfsc	EECON1,WR
	goto	$-2

	BANKSEL	PIR2
	bcf	PIR2,EEIF
	BANKSEL	EEADRL
	
;; fall through the end_write_func routine.
		
;;
;; Turn on first seven LEDs
;;
end_write_func:

	BANKSEL	PORTB
	
	movlw	0xEF
	movwf	PORTB
	movlw	0xFF
	movwf	PORTA

	BANKSEL	TRISA
	
	movlw	0xF0
	movwf	TRISA
	movlw	0x0F
	movwf	TRISB

	BANKSEL	PORTA
	
testloop:
	goto	testloop
	
;; data stored in the eeprom.  First byte is MSB of our address (0/1), second
;; byte is the LSB of our address (0-255).

  ORG 0xF000
   de (DMX_START_ADDRESS / 256)
   de (DMX_START_ADDRESS & 255)
  
  END
