1 title "PIC16F870 Unilink Interface by Werner Johansson (c) 2003"
\r
2 subtitl "Definitions"
\r
3 list c=132,P=16F870,R=DEC,F=inhx8m
\r
4 include "p16f870.inc" ; Standard equates & Macros
\r
5 ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs!
\r
8 ;----------------------------------------------------------------
\r
9 ; The Configuration Word
\r
10 __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF
\r
12 ;----------------------------------------------------------------
\r
14 ;----------------------------------------------------------------
\r
17 ; 0.1 Receives Unilink data OK, relays it to serial
\r
18 ; 0.0 Very first "Fucking No Work!" version
\r
20 ;----------------------------------------------------------------
\r
22 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
23 ; Unilink DATA (green) connected to RC3
\r
24 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
25 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
26 ; Unilink RST (lilac) connected to RA4
\r
27 ; LCD RS connected to pin RB1
\r
28 ; LCD RW connected to pin RB2
\r
29 ; LCD E connected to pin RB3
\r
30 ; LCD DB4-DB7 connected to RB4-RB7
\r
31 ; RS-232 TX from computer connected to RC7/RX
\r
32 ; RS-232 RX to computer connected to RC6/TX
\r
33 ; RS-232 RI to computer connected to RC5
\r
35 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...
\r
37 #define LCD_RS_BIT PORTB,1
\r
38 #define LCD_RW_BIT PORTB,2
\r
39 #define LCD_E_BIT PORTB,3
\r
40 #define LCD_DB4_BIT PORTB,4
\r
41 #define LCD_DB5_BIT PORTB,5
\r
42 #define LCD_DB6_BIT PORTB,6
\r
43 #define LCD_DB7_BIT PORTB,7
\r
45 ;----------------------------------------------------------------
\r
46 ; File register usage
\r
50 Icount equ 2Dh ; Offset of string to print
\r
51 TxTemp equ 2Eh ; blahblah
\r
52 TxTemp2 equ 2Fh ; Blahblah2
\r
67 ;----------------------------------------------------------------
\r
68 ; Power up/Reset starting point [den rulerar]
\r
71 call Bootstrap ; Call Flash Load routine
\r
72 call LCDInit ; Initialize LCD I/F
\r
73 call IRQInit ; Set up and start the IRQ handler
\r
74 goto Main ; Run the main program loop (skip the IRQ handler)
\r
76 subtitl "IRQ Handler"
\r
77 ;----------------------------------------------------------------
\r
78 ; Interrupt handler always starts at addr 4
\r
80 org 4 ; Must be on Address 4!
\r
82 swapf STATUS,w ; Get the status register into w
\r
83 clrf STATUS ; Zero out the status reg, gives us Bank0 all the time
\r
89 btfss INTCON,INTF ; Check if it's INT (CLK)
\r
90 goto IRQNotINT ; Nope
\r
92 ; If there's activity on the clock line (the clock goes high) we stay in here until we have clocked eight bits
\r
93 ; - this saves us a lot of context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=
\r
94 ; 160us=800 instruction cycles (5 MIPS @ 20MHz), not even a problem for serial input if we're not getting more than
\r
95 ; 6250 bytes per second from the UART, and the 2-byte FIFO somehow fills up (this should be impossible even @ 115200
\r
96 ; as we're only calling this blocking INT handler a maximum of 1000 times per second, halting INT's for 1/6250 of a second,
\r
97 ; this gives the CPU ample of time to deal with all bytes from the USART. I'm checking the OERR (Serial Overrun) bit
\r
98 ; to catch this though..
\r
100 movlw 8 ; Loop this many times
\r
104 btfss PORTC,2 ; Check for BUSON
\r
106 btfss PORTB,0 ; Wait for clock to go high
\r
110 btfss PORTC,2 ; Check for BUSON
\r
112 btfsc PORTB,0 ; Wait for clock to go low
\r
115 clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before)
\r
116 btfss PORTC,3 ; Test DATA
\r
117 setc ; Set carry if data is LOW (data is inverted!)
\r
118 rlf DataStore,f ; Shift it into our accumulator
\r
120 decfsz DataCount,f ; Loop once more perhaps?
\r
123 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
124 ; (several possibilites exists here:
\r
125 ; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
126 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
127 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
128 ; If the receive counter is 3 check the two upper bits of recv'd byte (CMD1) - this tells us the length of the packet
\r
129 ; 00 = short 6 byte packet
\r
130 ; 10 = medium 11 byte packet
\r
131 ; 11 = long 16 byte packet
\r
132 ; Update the receive length byte accordingly
\r
133 ; Check whether receive length and receive count are equal, that means that we're finished, flag this by setting
\r
134 ; the high bit of receive length
\r
138 bcf INTCON,INTF ; Clear our IRQ
\r
143 movwf PCLATH ; Restore PCLATH
\r
145 movwf STATUS ; Restore STATUS
\r
147 swapf IRQW,w ; Restore W
\r
148 retfie ; Interrupt return
\r
152 subtitl "Main loop"
\r
155 ;----------------------------------------------------------------
\r
156 ; Data can be stored between here and 100h...
\r
159 DT "-WJ UniLink I/F-"
\r
161 DT "Code and design:"
\r
163 DT "**TCC of Yodel**"
\r
166 LookUp movwf PCL ; Go to it
\r
168 ;----------------------------------------------------------------
\r
169 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
175 bsf TXSTA,TXEN ; Enable UART TX
\r
176 bcf STATUS,RP0 ; Back to bank 0
\r
178 bsf RCSTA,SPEN ; Enable serial port
\r
179 bsf RCSTA,CREN ; Enable UART RX
\r
183 ; movlw 8 ; Loop this many times
\r
187 ; btfsc PORTA,4 ; Check for RST
\r
189 ; btfss PORTC,2 ; Check for BUSON
\r
191 ; btfss PORTB,0 ; Wait for clock to go high
\r
194 ; btfsc PORTA,4 ; Check for RST
\r
196 ; btfss PORTC,2 ; Check for BUSON
\r
198 ; btfsc PORTB,0 ; Wait for clock to go low
\r
201 ; clrc ; Clear carry
\r
202 ; btfss PORTC,3 ; Test DATA
\r
203 ; setc ; Set carry if data is LOW (data is inverted!)
\r
204 ; rlf DataStore,f ; Shift it into our accumulator
\r
206 ; decfsz DataCount,f ; Loop once more perhaps?
\r
209 bcf LCD_RS_BIT ;Command mode
\r
210 movlw 80h ;DisplayRam 0
\r
215 btfsc PORTA,4 ; Test RST
\r
220 btfsc PORTB,0 ; Test CLK
\r
225 btfsc PORTC,2 ; Test BUSON-IN
\r
230 btfsc PORTC,3 ; Test DATA
\r
234 movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
238 movf DataStore,w ; Get the result
\r
239 decf DataCount,f ; Set it non-zero
\r
241 call BootTXB ; Send to terminal
\r
251 bsf PORTA,4 ; turn off LED
\r
257 bcf PORTA,4 ; turn on LED
\r
266 ;----------------------------------------------------------------
\r
267 ; IRQInit - Sets up the IRQ Handler
\r
270 bsf STATUS,RP0 ; Reg bank 1
\r
271 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
272 bsf INTCON,INTE ; Enable the RB0/INT
\r
273 bsf INTCON,GIE ; Enable global interrupts
\r
274 bcf STATUS,RP0 ; Back to bank 0
\r
277 ;----------------------------------------------------------------
\r
278 ; Initialize LCD Controller...
\r
282 bsf STATUS,RP0 ; Hi Bank
\r
283 movlw 0cfh ; RC4 & RC5 should be outputs...
\r
285 movlw 001h ; All but RB0 are outputs.
\r
287 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
288 bcf STATUS,RP0 ; Restore Lo Bank
\r
290 ; bcf PORTA,4 ; turn on LED
\r
292 ; movlw 44 ; Should be 16ms delay
\r
293 movlw 255 ; Should be 16ms delay
\r
296 movlw 3 ; Write 3 to the LCD
\r
297 call TxLCD ; Send to LCD
\r
298 ; movlw 12 ; Should be 5ms delay
\r
299 movlw 255 ; Should be 16ms delay
\r
302 movlw 3 ; Write 3 to the LCD
\r
304 ; movlw 12 ; Should be 16ms delay
\r
305 movlw 255 ; Should be 16ms delay
\r
308 movlw 3 ; Write 3 to the LCD
\r
311 movlw 255 ; Should be 16ms delay
\r
315 call TxLCD ; | 4-bit interface
\r
316 ; movlw 55 ; | After this we are ready to ROCK!
\r
317 movlw 255 ; Should be 16ms delay
\r
320 ; bsf PORTA,4 ; turn off LED
\r
322 movlw 28h ; Some random commands :)))
\r
336 ;----------------------------------------------------------------
\r
337 ; LongDelay - Well, guess that for yourself...
\r
340 ; btfss PORTB,6 ; Talk to da PC?
\r
341 ; goto PCTalk ; Oh yeah...
\r
377 ;----------------------------------------------------------------
\r
379 ; Send a string to the LCD.
\r
384 movlw 80h ;DisplayRam 0
\r
389 movlw 80h+40 ;DisplayRam 40 (row 2)
\r
395 ;----------------------------------------------------------------
\r
397 ; Send a string to the LCD.
\r
400 ; movwf Icount ; Icount = W
\r
402 movwf e_LEN ; Move to e_LEN
\r
404 Txm_lp movf Icount,w ; get the byte
\r
406 incf Icount,f ; ...else ++Icount (table index)
\r
407 call TxLCDB ; Send out the byte
\r
412 ;----------------------------------------------------------------
\r
413 ; TxLCDB - send a byte to the LCD
\r
416 movwf TxTemp ; Store byte to send for a while...
\r
418 bcf temp,0 ; Clear my temp bit
\r
419 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
422 bsf temp,0 ; Indicate RS change
\r
426 call RxLCDB ; Receive byte from LCD, status reg
\r
428 btfss STATUS,Z ; If the bit was set, the zero flag is not
\r
431 btfsc temp,0 ; If we had to clear RS reset it now
\r
434 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
435 call TxLCD ; Send them first...
\r
436 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
437 call TxLCD ; And send that one as well
\r
440 ;----------------------------------------------------------------
\r
441 ; RxLCDB - recv a byte from the LCD
\r
444 call RxLCD ; Receive the high nibble
\r
446 swapf LCDWTmp,f ; Swap it back to file
\r
447 call RxLCD ; Receive the low nibble
\r
448 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
452 ;----------------------------------------------------------------
\r
453 ; TxLCD - send a nibble to the LCD
\r
456 movwf LCDWTmp ; Write nibble to tmp
\r
457 bcf LCD_DB4_BIT ; Clear previous data
\r
462 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
464 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
466 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
468 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
471 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
472 nop ; Let it settle
\r
473 bcf LCD_E_BIT ; And clear the Enable again.
\r
474 return ; Returns without modifying W
\r
476 ;----------------------------------------------------------------
\r
477 ; RxLCD - recv a nibble from the LCD
\r
480 clrw ; Clear W register, return data in lower 4 bits
\r
482 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
484 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
488 bcf STATUS,RP0 ; Back at reg bank 0
\r
490 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
491 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
492 nop ; Let the bus settle
\r
493 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
495 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
497 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
499 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
501 bcf LCD_E_BIT ; And clear the Enable again.
\r
502 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
504 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
505 bcf LCD_DB4_BIT ; Set the port as an output again
\r
509 bcf STATUS,RP0 ; Back at reg bank 0
\r
511 return ; Returns with data in W
\r
513 ;----------------------------------------------------------------------
\r
514 ; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz)
\r
515 ; 2.73* # of ms is good...
\r
517 DelayW movwf Dcount ; Set delay counter
\r
520 DelayLp decfsz Dcount,f
\r
523 DelayIn decfsz Dcount2,f
\r
532 subtitl "Bootstrap/Bootloader code"
\r
535 ;----------------------------------------------------------------------
\r
536 ; Bootstrap code - Allows PIC to flash itself with data from async port
\r
539 org 700h ; Place the boot code at the top of memory
\r
541 BootRXState equ 7fh ; What are we waiting for @RX
\r
542 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
550 BootNumBytes equ 76h
\r
553 BootHEXTemp equ 73h
\r
554 BootStrTemp equ 72h
\r
560 bsf STATUS,RP0 ; Access bank 1
\r
561 bsf TXSTA,TXEN ; Enable UART TX
\r
562 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
563 movwf SPBRG ; Store
\r
564 bcf STATUS,RP0 ; Back to bank 0
\r
566 bsf RCSTA,SPEN ; Enable serial port
\r
567 bsf RCSTA,CREN ; Enable UART RX
\r
569 ; clrf BootRXState ; Waiting for command
\r
571 movlw BootStartText
\r
580 incf BootTimerL,f ; A 24-bit counter
\r
585 skpnz ; When overflowing here..
\r
586 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
587 btfss PIR1,RCIF ; Wait for RX to complete
\r
592 goto BootTimeout ; If it wasn't space, wait for another key
\r
595 movlw BootFlashText
\r
603 call BootRXB ; First find the ':'
\r
606 goto BootLoop ; Loop until we find it!
\r
608 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
609 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
610 ; Maybe clear cary here?
\r
611 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
613 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
615 call BootRXHEX ; Receive AddrH
\r
617 call BootRXHEX ; Receive AddrL
\r
619 rrf BootAddrH,f ; Fix the addressing again
\r
622 bcf BootBits,2 ; Assume we should program
\r
623 bsf BootBits,1 ; And assume we should program flash not ee
\r
626 xorlw 020h ; Check if it's configuration, which we can't program
\r
627 skpnz ; Skip the bit set if it was false alarm
\r
628 bsf BootBits,2 ; No programming for this line
\r
630 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
631 skpnz ; Skip the bit set instr if not EE data address
\r
632 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
634 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
635 skpz ; Check if zero
\r
636 goto BootFlashComplete
\r
639 call BootRXHEX ; Receive low-byte of data word
\r
641 call BootRXHEX ; Receive high-byte of data word
\r
644 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
647 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
650 xorwf BootDataL,f ; Compare and destroy DataL
\r
651 movwf BootDataL ; Write new data to DataL
\r
652 skpz ; Skip if no difference, have to check high byte as well
\r
653 goto BootWrite ; Jump directly to write
\r
656 xorwf BootDataH,f ; Compare
\r
657 skpnz ; Skip if no difference, no programming necessary
\r
662 movwf BootDataH ; Have to put the new H byte data in as well
\r
665 ; call BootTXB ; Send progword indicator
\r
668 call BootEE ; Write directly into program mem
\r
670 ; Here a verify can take place, the read-back results are now in DataL/H
\r
680 incf BootAddrL,f ; Advance counter to next addr
\r
682 incf BootAddrH,f ; And add to high byte if needed
\r
684 decfsz BootNumBytes,f
\r
687 movlw 13 ; Progress
\r
689 movlw 10 ; Progress
\r
702 btfss TXSTA,TRMT ; Wait for last things to flush
\r
703 goto BootReturnWait
\r
704 bcf TXSTA,TXEN ; Disable UART TX
\r
705 bcf STATUS,RP0 ; Back to bank 0
\r
707 bcf RCSTA,SPEN ; Enable serial port
\r
708 bcf RCSTA,CREN ; Enable UART RX
\r
711 return ; Return to code
\r
713 ;----------------------------------------------------------------------
\r
714 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
715 ; free before sending
\r
719 btfss PIR1,TXIF ; Wait for TX to empty
\r
721 movwf TXREG ; Send the byte
\r
724 ;----------------------------------------------------------------------
\r
725 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
728 movwf BootStrTemp ; Store offset
\r
729 call BootLookup ; Lookup char
\r
733 call BootTXB ; Send char
\r
734 incf BootStrTemp,w ; Retrieve
\r
737 ;----------------------------------------------------------------------
\r
738 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
742 btfss PIR1,RCIF ; Wait for RX to complete
\r
744 movf RCREG,w ; Get the recvd byte
\r
747 ;----------------------------------------------------------------------
\r
748 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
751 call BootRXB ; Receive nibble
\r
753 addlw -'A' ; Convert from BCD to binary nibble
\r
754 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
755 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
756 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
759 ;----------------------------------------------------------------------
\r
760 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
761 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
764 call BootRXHEXNibble
\r
766 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
768 call BootRXHEXNibble
\r
769 addwf BootHEXTemp,w ; And add the two nibbles together
\r
772 ;----------------------------------------------------------------------
\r
773 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
774 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
775 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
776 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
777 ; data will be to the same place when read back
\r
780 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
782 movf BootAddrH,w ; Load desired address
\r
786 movf BootDataH,w ; And load the data (only used when writing)
\r
791 bsf STATUS,RP0 ; Go to bank 3
\r
793 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
794 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
795 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
797 btfss BootBits,0 ; Check from read or write
\r
798 goto BootEERD ; Skip the WR if we were going for a read
\r
800 bsf EECON1,WREN ; Enable writes
\r
804 movwf EECON2 ; Unlock write operation
\r
805 bsf EECON1,WR ; And start a write cycle
\r
807 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
808 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
810 bcf EECON1,WREN ; Finally disable writes again
\r
811 ; Here we read the data back again, can be used as verify
\r
813 bsf EECON1,RD ; Start a read cycle
\r
814 nop ; Only necessary for flash read, same thing as when writing above
\r
815 nop ; Except I could use the two words for something useful there.. :)
\r
818 bcf STATUS,RP0 ; Back to bank 2
\r
819 movf EEDATA,w ; Store our EE-data
\r
823 bcf STATUS,RP1 ; And finally back to bank 0
\r
828 DT "WJBoot - press ESC to flash",0
\r
830 DT 13,10,"Send INHX8 file now...",13,10,0
\r
832 DT 13,10,"Exiting loader",13,10,0
\r
835 movwf PCL ; Go fetch the char data
\r
837 ;----------------------------------------------------------------------
\r
838 ; EE Data (64 bytes), located at 2100h
\r
841 ; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h
\r