;****************************************************************************** ; * ; Filename: DMX805_v103.asm * ; Date: 14-10-2012 * ; File Version: 1.0.3 * ; * ; Author: Pete Griffiths * ; Company: http://picprojects.org * ; * ; * ;****************************************************************************** ; * ; Files Required: P16F1823.INC * ; * ;****************************************************************************** ; * ; Notes: * ; * ;****************************************************************************** ; * ; Revision History: * ; * ; Built from dmx688x4l_sc (V1.1.2) for PIC16F688 * ; and modified to use features of the enhanced mid-range PIC16F1823 * ; * ; * ; 02-10-2012 * ; Version 1.0.1 Full feature version passed initial testing on 805B PCB * ; * ; * ; 14-10-2012 * ; Version 1.0.2 +self-test and signature TX on power-up * ; * ; 17-10-2012 * ; Version 1.0.3 + TX ASCII device ID and revision * ;****************************************************************************** list p=16F1823 ; list directive to define processor #include ; processor specific variable definitions ;------------------------------------------------------------------------------ ; ; CONFIGURATION WORD SETUP ; ;------------------------------------------------------------------------------ __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON & _MCLRE_ON & _CP_ON & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF __CONFIG _CONFIG2, _WRT_ALL & _PLLEN_OFF & _STVREN_OFF & _BORV_HI & _LVP_OFF ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ; ; Available Data Memory divided into Bank 0-15. Each Bank may contain ; Special Function Registers, General Purpose Registers, and Access RAM ; ;------------------------------------------------------------------------------ errorlevel -302 ; '__CONFIG' directive is used to embed configuration data within .asm file. ; The labels following the directive are located in the respective .inc file. ; See respective data sheet for additional information on configuration word. ; ***** EEPROM DATA ; The 16F1823 has 256 bytes of non-volatile EEPROM, starting at address 0xF000 ; Initialise values for devices DMX base address and soft jumper settings ORG 0xF000 ; Define device base address (value is in decimal) CdmxAddress equ .1 config.word equ .0 DE HIGH CdmxAddress DE LOW CdmxAddress DE config.word ORG 0xF020 ; data EEPROM address de " DMX805 V1.0.3 " de " picprojects.org.uk " de " (c) 2008-2012 Pete Griffiths " ;***** PROGRAM CONSTANTS CmaxAddr equ .509 ; Highest valid DMX base address this device can use ; since device needs 4 addresses so 509, 510, 511, 512 CsleepTime equ .2 ; No DMX Receive sleep timer ; timeout will occur 1.261S x CsleepTime approx. after last DMX frame Ctimer2 equ .77 ; Timer2 PR2 period register ;---------------------------------- ;Clock 8000000 Mhz ;Tcyc 0.5 uS ;TMR 219 ;PreScaler 2 ;Timer out 37.0 uS ;Timer adjust 3 Tcyc ; ;Adjusted timer 38.5 uS ;Timer frequency 25974.03 Hz ;Instructions / interrupt 77 ; ;PWM resolution 8 bits ;PWM period 9.8560 mS ;PWM freqency 101.5 Hz ;***** VARIABLE DEFINITIONS cblock 0x20 delayTimerL delayTimerH blinkCount blinks mirrorPortC ; working variable used to prepare data for output driveLevel ; bit mask used to invert pwm data for active low outputs dipsw2to9 ; DIP switch settings 2 to 9 dipsw10 ; DIP switch 10 tickCount ; interrupts / 256 pwmCounter ; pwm period counter pwmCh1 ; channel 1 duty cycle - copied from dmxBuf1 pwmCh2 ; channel 2 duty cycle - copied from dmxBuf2 pwmCh3 ; channel 3 duty cycle - copied from dmxBuf3 pwmCh4 ; channel 4 duty cycle - copied from dmxBuf4 endc cblock 0x70 flags ; program flag bits config.flags ; configuration flag bits dmxBuf1 ; holding buffer data byte received at base address dmxBuf2 ; holding buffer data byte received at base address+1 dmxBuf3 ; holding buffer data byte received at base address+2 dmxBuf4 ; holding buffer data byte received at base address+3 sleepTimerL ; 8 bit counter used to shutdown outputs when no DMX data received sleepTimerH ; 8 bit counter used to shutdown outputs when no DMX data received dmxDevAdH ; variable holding high byte of base DMX address for this device dmxDevAdL ; variable holding low byte of base DMX address for this device dmxAdrH ; received data dmx address counter high dmxAdrL ; received data dmx address counter low dmxStartCode ; Start code value for packets to be accepted EEPROM.data ; EEPROM data to write EEPROM.rewrite ; EEPROM rewrite attempts endc ;***** PROGRAM FLAG BITS tick equ 0 ; set when tmrTick variable reaches 0 noRX equ 1 ; set when DMX RX timer times out newData equ 2 ; set when new DMX data is in transfer buffer softConfig equ 4 ; when set, use first DMX packet to set soft config shutdown equ 5 ; when set, outputs are set to inactive state selftest equ 6 dip.sw1 equ 7 ; if set DIP switch 1 is closed (config mode) ; if clear DIP switch 1 is open (address mode) ;***** CONFIG FLAG BITS noDMXmode equ 0 ; sets output behaviour when no DMX data is being received ganged equ 1 ; ganged mode notused2 equ 2 notused3 equ 3 noPwm1 equ 4 ; when set pwmOutput disabled Ch1 noPwm2 equ 5 ; when set pwmOutput disabled Ch2 noPwm3 equ 6 ; when set pwmOutput disabled Ch3 noPwm4 equ 7 ; when set pwmOutput disabled Ch4 ;***** I/O Port configuation bit.ch1 equ .0 ; I/O port bit used by channel 1 bit.ch2 equ .1 ; I/O port bit used by channel 2 bit.ch3 equ .2 ; I/O port bit used by channel 3 bit.ch4 equ .3 ; I/O port bit used by channel 4 bit.modeSC equ .5 ; I/O port for Soft Config mode jumper bit.485RE equ .4 ; I/O port for 485 Transceiver receive enable (active low) bit.statusLED equ .1 ; I/O port bit used for Status LED bit.clk4017 equ .1 ; I/O port for DMX address board 4017 clock out bit.Qn4017 equ .2 ; I/O port for DMX address board 4017 Qn switch data in lightPort equ PORTC controlPort equ PORTA ;********************************************************************** ORG 0x000 ; processor reset vector nop clrf PCLATH ; goto resetInit ; go to initialisation code ; === START OF INTERRUPT HANDLER CODE === ; Timer 2 generated interrupt occurs every 19.25uS ; Instruction cycle time with 16Mhz clock is 0.25uS = 77 instruction ; Enhanced mid-range devices save and restore core register automatically. ; Rather than exiting the ISR at a single point, since we no longer need to restore registers ; on exit, the ISR is exited at multiple locations within the ISR code dependant on flags and timer ; values at the time the ISR is invoked by Timer2 interrupt. ORG 0x004 ; interrupt vector location banksel PIR1 bcf PIR1, TMR2IF ; clear Timer2 interrupt flag incf pwmCounter,F ; increment pwmCounter (duty cycle) skpnz ; skip if not 0 incf pwmCounter,F ; else force count back to 1 ; Summing PWM period with pwmCounter results in carry flag being set when >255, cleared otherwise. ; Since Ch1, Ch2,Ch3, and Ch4 output port bits are on I/O pins 0,1,2 and 3, rotating the resulting ; carry bit into the portMirror variable generates PWM modulation of the outputs. ; Note: This only works when the outputs are on four contiguous port I/O pins (by design) movfw pwmCounter ; load pwmCounter into W addwf pwmCh4,W ; compare to ch4 duty period rlf mirrorPortC,F ; rotate carry into mirrorPort variable, result into W reg movfw pwmCounter ; load pwmCounter into W addwf pwmCh3,W ; compare to ch3 duty period rlf mirrorPortC,F ; rotate carry into mirrorPort variable movfw pwmCounter ; load pwmCounter into W addwf pwmCh2,W ; compare to ch2 duty period rlf mirrorPortC,F ; rotate carry into mirrorPort variable movfw pwmCounter ; load pwmCounter into W addwf pwmCh1,W ; compare to ch1 duty period rlf mirrorPortC,W ; rotate carry into mirrorPort variable, result into W reg btfsc flags, shutdown ; test noDMXmode flag clrw ; if no DMX data has been seen within the time out ; period, force the outputs to turn off ; xorwf driveLevel,W ; if outputs are active low we need invert output ; and finally.... movwf lightPort ; write update the I/O port btfss flags, newData ; test newData flag goto timerTick ; exit if no new DMX data ready... bcf flags, newData ; else clear the newData flag movfw dmxBuf1 ; and transfer the DMX buffers to the PWM duty cycle registers movwf pwmCh1 movfw dmxBuf2 movwf pwmCh2 movfw dmxBuf3 movwf pwmCh3 movfw dmxBuf4 movwf pwmCh4 timerTick decfsz tickCount,F ; tick rolls over every 19.25uS x 256 = 4.928mS retfie ; EXIT INT. tickCount not zero, we're done ; Toggle status LED when NO DMX data is being received. ; This is the reverse functionality of the original 1.1.2 code for 16F688 ; Removed tick reload and push tick timer out to 4.928mS. This gives a slower no-dmx-data ; receiving status LED flash rate ~ 0.4Hz decfsz sleepTimerL,F ; decrement sleep timer low skip next when it reaches zero retfie ; EXIT INT. not zero, we're done (256 * 4.928mS = 1.26S) banksel LATA movfw controlPort ; read control port into W reg (Now reading LAT register not PORT) xorlw (1< pwm Channel 1 ; dmxBuf2 - dmx byte base address+1 -> pwm Channel 2 ; dmxBuf3 - dmx byte base address+2 -> pwm Channel 3 ; dmxBuf4 - dmx byte base address+3 -> pwm Channel 4 _waitData incf dmxAdrL,F ; increment DMX address counter low byte skpnz ; skip next if count didn't rollover incf dmxAdrH,F ; else increment DMX address counter high byte ;----------------------------------------------------- call wait.rx.ready skpc ; skip if no Framing Error goto _dmxIdle ; abort if there was ;----------------------------------------------------- movwf dmxBuf1 ; save channel 1 to buffer, if it's our we want it, if it's not ; it will get overwritten by the next received byte so we don't care movfw dmxDevAdL ; compare low DMX address with device address xorwf dmxAdrL,W ; result will be zero if they match exactly skpz ; if the low 8 bits match skip next to test high 8 bits bra _waitData ; otherwise, not our data so go and wait for next byte movfw dmxDevAdH ; compare high DMX address with device address xorwf dmxAdrH,W ; result will be zero if they match exactly skpz ; if the high 8 bits match this is the first byte we want bra _waitData ; otherwise, not our data so go and wait for next byte ; now we've seen the first channel matching this units base address, clear the noRX flag bcf flags, noRX ; reset no DMX receive flag bcf flags, shutdown ; clear output shutdown flag btfss config.flags, ganged ; test for ganged mode bra no.gang movfw dmxBuf1 movwf dmxBuf2 movwf dmxBuf3 movwf dmxBuf4 bra rxDone no.gang ; Once the first addressed channel has been received get each of the following required channels. ; We do this with inline code rather than a loop since program memory isn't an issue. ; ;----------------------------------------------------- call wait.rx.ready skpc ; skip if no Framing Error bra _dmxIdle ; abort if there was movwf dmxBuf2 ; save channel 2 to buffer ;----------------------------------------------------- call wait.rx.ready skpc ; skip if no Framing Error bra _dmxIdle ; abort if there was movwf dmxBuf3 ; save channel 3 to buffer ;----------------------------------------------------- call wait.rx.ready skpc ; skip if no Framing Error bra _dmxIdle ; abort if there was movwf dmxBuf4 ; save channel 4 to buffer ;-------------------------------------------------------------------------- ; If soft config (DMX inband) active upate config from received DMX data btfss flags, softConfig goto rxDone movlw 0x81 ; test first byte for 0x81 xorwf dmxBuf1,W skpz ; skip next if it was 0x81 bra _dmxIdle ; else go back to dmx idle state banksel PORTC bcf INTCON, PEIE ; disable interrupts, we're done bcf INTCON, GIE ; now save config and address movfw dmxBuf2 movwf dipsw10 movfw dmxBuf3 movwf dipsw2to9 call writeNewAddr movfw dmxBuf4 movwf config.flags call force.config ; When soft config mode has written data to EEPROM blink Status LED 2 times movlw .2 goto blinkStatus ;-------------------------------------------------------------------------- rxDone banksel pwmCh1 ; If noPwm flag is set then we force the value in dmxBufn to either 0 or 255 ; What we're doing is forcing the PWM duty cycle to either 0% or 100% ; based on the value of the DMX data: 0 output off, 255 output on btfss config.flags, noPwm1 bra do.ch2 ; Test dmxBufx and set Z flag if dmxBufx == 0|255 movf dmxBuf1,W ; if dmxBufx == 0, set Z flag set skpz ; if result is 0 skip the following addlw addlw .1 ; Wreg = dmxBufx + 1. If dmxBuf == 255, set Z flag skpnz ; if Z flag not set, skip branch bra do.ch2 movfw pwmCh1 ; copy pwmChx data in to W movwf dmxBuf1 ; write back into dmxBufx ; this will get copied back to pwmChx next interrupt ; -=| done channel 1 |=- do.ch2 btfss config.flags,noPwm2 bra do.ch3 movf dmxBuf2,W ; if dmxBuf == 0 Z flag set skpz addlw .1 ; add +1 . if dmxBuf == 255 Z flag set ; Z flag set if dmxBuf == 0|255 skpnz bra do.ch3 movfw pwmCh2 movwf dmxBuf2 ; -=| done channel 2 |=- do.ch3 btfss config.flags,noPwm3 bra do.ch4 movf dmxBuf3,W ; if dmxBuf == 0 Z flag set skpz addlw .1 ; add +1 . if dmxBuf == 255 Z flag set ; Z flag set if dmxBuf == 0|255 skpnz bra do.ch4 movfw pwmCh3 movwf dmxBuf3 ; -=| done channel 3 |=- do.ch4 btfss config.flags,noPwm4 bra dmxRXdone movf dmxBuf4,W ; if dmxBuf == 0 Z flag set skpz addlw .1 ; add +1 . if dmxBuf == 255 Z flag set ; Z flag set if dmxBuf == 0|255 skpnz bra dmxRXdone movfw pwmCh4 movwf dmxBuf4 ; -=| done channel 4 |=- dmxRXdone bsf flags, newData ; set new data flag ; The flag is cleared by PWM driver once buffered data has been transferred by the ; PWM driver. Since the interrupts occur faster than the DMX Break time, we are ; guaranteed to transfer the data before the next packet overwrites it ; This is not really needed for this application, but where one DMX data channel is ; used with data from another channel, it is important that we work with the data ; from one packet only, and not have data split between the current and previous packets ; which will happen since the interrupts occur faster than the channel receive rate. goto _dmxIdle ; once we have all the channel data bytes we're interested ; in, anything following we don't care about so drop back ; and wait for the next break ; **************************************************************************************************************** ; Reset and Initialisation code section ; **************************************************************************************************************** resetInit ; configure digital I/O on all ports banksel ANSELA clrf ANSELA clrf ANSELC ; change bank register banksel OPTION_REG ; prescaler to Timer0, PS=1:2, Weak Pull-ups enabled movlw 0x00 movwf OPTION_REG ; Enable weak pull ups on required PortA inputs banksel WPUA movlw (1< CmaxAddr movfw dmxDevAdH sublw HIGH (CmaxAddr) skpc goto badAddrH movfw dmxDevAdL sublw LOW (CmaxAddr) skpc goto badAddrH ; address > CmaxAddr return ; Address in range, all good, we're done ;------------------------------------------------------- ; Self test mode ; When Dip Switch 10 and 1 are ON, do self test self.test banksel PORTC bcf flags, newData ; turn outputs on movlw .255 movwf pwmCh1 call st.delay movlw .255 movwf pwmCh2 call st.delay movlw .255 movwf pwmCh3 call st.delay movlw .255 movwf pwmCh4 call st.delay ;turn outputs off clrf pwmCh1 call st.delay clrf pwmCh2 call st.delay clrf pwmCh3 call st.delay clrf pwmCh4 call st.delay bra self.test st.delay clrf sleepTimerH ; clear this to stop the ISR shutting down the output bcf flags, noRX ; reset no DMX receive flag movlw .10 call delayTimer return ;------------------------------------------------------- ; ; if DMX Address =0 badAddrL movlw .3 goto blinkStatus ; if DMX Address >CmaxAddr badAddrH movlw .4 goto blinkStatus ;------------------------------------------- ; if EEPROM write failed 5 times blink Status LED 5 times eepromFault movlw .5 goto blinkStatus ;------------------------------------------- ; if GPR Bank 0 failed to initialise to 0 blink Status LED 6 times gprFault movlw .6 goto blinkStatus ;------------------------------------------- ; Blink Status LED to indicate error condition and type ; ; This function loops forever and does not exit ; blinkStatus movwf blinks blinkRepeat movfw blinks movwf blinkCount blinkLoop banksel LATA clrf LATC bsf controlPort, bit.485RE bsf controlPort, bit.statusLED banksel 0 call delayVshort banksel LATA bcf controlPort, bit.statusLED banksel 0 call delayShort decfsz blinkCount,F bra blinkLoop call delayLong bra blinkRepeat delayVshort movlw .3 ; 3 x 50mS = 150mS bra delayTimer delayShort movlw .9 ; 9 x 45mS = 450mS bra delayTimer delayLong movlw .20 ; 20 x 50mS = 1S ; delay period is 50mS x Wreg (approx) delayTimer movwf delayTimerH clrf delayTimerL movlw .63 addlw .1 skpc bra $-2 decfsz delayTimerL,F bra $-5 decfsz delayTimerH,F bra $-7 return ;* This code block will read 1 word of program memory at the memory address: ;* PROG_ADDR_LO (must be 00h-08h) data will be returned in the variables; read.deviceID BANKSEL EEADRL ; Select correct Bank MOVLW 0x06 MOVWF EEADRL ; Store LSB of address CLRF EEADRH ; Clear MSB of address BSF EECON1,CFGS ; Select Configuration Space BCF INTCON,GIE ; Disable interrupts BSF EECON1,RD ; Initiate read NOP ; Executed (See Figure 11-1) NOP ; Ignored (See Figure 11-1) BSF INTCON,GIE ; Restore interrupts MOVF EEDATL,W ; Get LSB of word MOVWF dmxBuf1 ; Store in user location MOVF EEDATH,W ; Get MSB of word MOVWF dmxBuf2 ; Store in user location return ;--------------------------------------------------------------------------------------------- ; No signal standalone fader - From V1.0.3 ;--------------------------------------------------------------------------------------------- bin2hexHi swapf WREG,F bin2hexLo andlw 0x0F brw dt "0123456789ABCDEF" ver.text brw dt "Picprojects.org.uk DMX805 1.0.3 ID:", 0x00 END