title "PIC16F870 Unilink Interface by Werner Johansson (c) 2003" subtitl "Definitions" list c=132,P=16F870,R=DEC,F=inhx8m include "p16f870.inc" ; Standard equates & Macros ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs! ;---------------------------------------------------------------- ; The Configuration Word __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF ;---------------------------------------------------------------- ; HISTORY ;---------------------------------------------------------------- ; Version ; ; 0.1 Receives Unilink data OK, relays it to serial ; 0.0 Very first "Fucking No Work!" version ; ;---------------------------------------------------------------- ; Unilink BUSON IN (blue) connected to RC2/CCP1 ; Unilink DATA (green) connected to RC3 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining) ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin) ; Unilink RST (lilac) connected to RA4 ; LCD RS connected to pin RB1 ; LCD RW connected to pin RB2 ; LCD E connected to pin RB3 ; LCD DB4-DB7 connected to RB4-RB7 ; RS-232 TX from computer connected to RC7/RX ; RS-232 RX to computer connected to RC6/TX ; RS-232 RI to computer connected to RC5 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now... #define LCD_RS_BIT PORTB,1 #define LCD_RW_BIT PORTB,2 #define LCD_E_BIT PORTB,3 #define LCD_DB4_BIT PORTB,4 #define LCD_DB5_BIT PORTB,5 #define LCD_DB6_BIT PORTB,6 #define LCD_DB7_BIT PORTB,7 ;---------------------------------------------------------------- ; File register usage Dcount equ 20h e_LEN equ 21h Icount equ 2Dh ; Offset of string to print TxTemp equ 2Eh ; blahblah TxTemp2 equ 2Fh ; Blahblah2 LCDWTmp equ 30h Dcount2 equ 31h temp equ 32h DataCount equ 33h DataStore equ 34h IRQW equ 7fh IRQSTATUS equ 7eh IRQPCLATH equ 7dh subtitl "Startup" page ;---------------------------------------------------------------- ; Power up/Reset starting point [den rulerar] org 0 call Bootstrap ; Call Flash Load routine call LCDInit ; Initialize LCD I/F call IRQInit ; Set up and start the IRQ handler goto Main ; Run the main program loop (skip the IRQ handler) subtitl "IRQ Handler" ;---------------------------------------------------------------- ; Interrupt handler always starts at addr 4 org 4 ; Must be on Address 4! movwf IRQW ; Save W swapf STATUS,w ; Get the status register into w clrf STATUS ; Zero out the status reg, gives us Bank0 all the time movwf IRQSTATUS movf PCLATH,w movwf IRQPCLATH clrf PCLATH btfss INTCON,INTF ; Check if it's INT (CLK) goto IRQNotINT ; Nope ; If there's activity on the clock line (the clock goes high) we stay in here until we have clocked eight bits ; - this saves us a lot of context switching (and it's just a few hundred cpu cycles after all (20us*8 bits= ; 160us=800 instruction cycles (5 MIPS @ 20MHz), not even a problem for serial input if we're not getting more than ; 6250 bytes per second from the UART, and the 2-byte FIFO somehow fills up (this should be impossible even @ 115200 ; 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, ; this gives the CPU ample of time to deal with all bytes from the USART. I'm checking the OERR (Serial Overrun) bit ; to catch this though.. movlw 8 ; Loop this many times movwf DataCount CLKWaitHigh btfss PORTC,2 ; Check for BUSON goto IRQAfterINT btfss PORTB,0 ; Wait for clock to go high goto CLKWaitHigh CLKWaitLow btfss PORTC,2 ; Check for BUSON goto IRQAfterINT btfsc PORTB,0 ; Wait for clock to go low goto CLKWaitLow clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before) btfss PORTC,3 ; Test DATA setc ; Set carry if data is LOW (data is inverted!) rlf DataStore,f ; Shift it into our accumulator decfsz DataCount,f ; Loop once more perhaps? goto CLKWaitHigh ; Successfully received a byte here, run it through a state machine to figure out what to do ; (several possibilites exists here: ; If more than 1.1ms has passed since last receive, reset receive counter to zero ; If receive counter is zero and the received byte is a zero byte, discard it ; Otherwise store the byte in our receive buffer and increment receive counter ; If the receive counter is 3 check the two upper bits of recv'd byte (CMD1) - this tells us the length of the packet ; 00 = short 6 byte packet ; 10 = medium 11 byte packet ; 11 = long 16 byte packet ; Update the receive length byte accordingly ; Check whether receive length and receive count are equal, that means that we're finished, flag this by setting ; the high bit of receive length IRQAfterINT bcf INTCON,INTF ; Clear our IRQ IRQNotINT movf IRQPCLATH,w movwf PCLATH ; Restore PCLATH swapf IRQSTATUS,w movwf STATUS ; Restore STATUS swapf IRQW,f swapf IRQW,w ; Restore W retfie ; Interrupt return subtitl "Main loop" page ;---------------------------------------------------------------- ; Data can be stored between here and 100h... StartUpText1 DT "-WJ UniLink I/F-" StartUpText2 DT "Code and design:" StartUpText3 DT "**TCC of Yodel**" LookUp movwf PCL ; Go to it ;---------------------------------------------------------------- ; Main program begins here. [Called after bootloader, lcdinit and irqinit...] org 100h Main bsf STATUS,RP0 bsf TXSTA,TXEN ; Enable UART TX bcf STATUS,RP0 ; Back to bank 0 bsf RCSTA,SPEN ; Enable serial port bsf RCSTA,CREN ; Enable UART RX retry ; movlw 8 ; Loop this many times ; movwf DataCount ;CLKWaitHigh ; btfsc PORTA,4 ; Check for RST ; goto retry ; btfss PORTC,2 ; Check for BUSON ; goto retry ; btfss PORTB,0 ; Wait for clock to go high ; goto CLKWaitHigh ;CLKWaitLow ; btfsc PORTA,4 ; Check for RST ; goto retry ; btfss PORTC,2 ; Check for BUSON ; goto retry ; btfsc PORTB,0 ; Wait for clock to go low ; goto CLKWaitLow ; clrc ; Clear carry ; btfss PORTC,3 ; Test DATA ; setc ; Set carry if data is LOW (data is inverted!) ; rlf DataStore,f ; Shift it into our accumulator ; decfsz DataCount,f ; Loop once more perhaps? ; goto CLKWaitHigh bcf LCD_RS_BIT ;Command mode movlw 80h ;DisplayRam 0 call TxLCDB bsf LCD_RS_BIT movlw '0' btfsc PORTA,4 ; Test RST movlw 'R' call TxLCDB movlw '0' btfsc PORTB,0 ; Test CLK movlw 'C' call TxLCDB movlw '0' btfsc PORTC,2 ; Test BUSON-IN movlw 'B' call TxLCDB movlw '0' btfsc PORTC,3 ; Test DATA movlw 'D' call TxLCDB movf DataCount,w ; Load bit counter (if 0 then byte is available) skpz goto retry movf DataStore,w ; Get the result decf DataCount,f ; Set it non-zero call BootTXB ; Send to terminal goto retry movlw StartUpText1 call TxLCD16B call LongDelay bsf PORTA,4 ; turn off LED movlw StartUpText2 call TxLCD16B call LongDelay bcf PORTA,4 ; turn on LED movlw StartUpText3 call TxLCD16B call LongDelay goto retry ;---------------------------------------------------------------- ; IRQInit - Sets up the IRQ Handler IRQInit bsf STATUS,RP0 ; Reg bank 1 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge bsf INTCON,INTE ; Enable the RB0/INT bsf INTCON,GIE ; Enable global interrupts bcf STATUS,RP0 ; Back to bank 0 return ;---------------------------------------------------------------- ; Initialize LCD Controller... LCDInit clrf PORTB bsf STATUS,RP0 ; Hi Bank movlw 0cfh ; RC4 & RC5 should be outputs... movwf TRISC ; Yep. movlw 001h ; All but RB0 are outputs. movwf TRISB ; Yep bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up bcf STATUS,RP0 ; Restore Lo Bank ; bcf PORTA,4 ; turn on LED ; movlw 44 ; Should be 16ms delay movlw 255 ; Should be 16ms delay call DelayW movlw 3 ; Write 3 to the LCD call TxLCD ; Send to LCD ; movlw 12 ; Should be 5ms delay movlw 255 ; Should be 16ms delay call DelayW movlw 3 ; Write 3 to the LCD call TxLCD ; movlw 12 ; Should be 16ms delay movlw 255 ; Should be 16ms delay call DelayW movlw 3 ; Write 3 to the LCD call TxLCD ; movlw 44 movlw 255 ; Should be 16ms delay call DelayW movlw 2 ;\ call TxLCD ; | 4-bit interface ; movlw 55 ; | After this we are ready to ROCK! movlw 255 ; Should be 16ms delay call DelayW ;/ ; bsf PORTA,4 ; turn off LED movlw 28h ; Some random commands :))) call TxLCDB movlw 0ch ; hmmm call TxLCDB movlw 01h ; hmmm call TxLCDB movlw 06h ; hmmm call TxLCDB return ;---------------------------------------------------------------- ; LongDelay - Well, guess that for yourself... LongDelay ; btfss PORTB,6 ; Talk to da PC? ; goto PCTalk ; Oh yeah... movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW movlw 255 call DelayW return ;---------------------------------------------------------------- ; TxLCD16B ; Send a string to the LCD. TxLCD16B movwf Icount bcf LCD_RS_BIT movlw 80h ;DisplayRam 0 call TxLCDB bsf LCD_RS_BIT call TxLCD8B bcf LCD_RS_BIT movlw 80h+40 ;DisplayRam 40 (row 2) call TxLCDB bsf LCD_RS_BIT call TxLCD8B return ;---------------------------------------------------------------- ; TxLCD8B ; Send a string to the LCD. TxLCD8B ; movwf Icount ; Icount = W movlw 8 movwf e_LEN ; Move to e_LEN Txm_lp movf Icount,w ; get the byte call LookUp incf Icount,f ; ...else ++Icount (table index) call TxLCDB ; Send out the byte decfsz e_LEN,f goto Txm_lp return ;---------------------------------------------------------------- ; TxLCDB - send a byte to the LCD TxLCDB movwf TxTemp ; Store byte to send for a while... bcf temp,0 ; Clear my temp bit btfss LCD_RS_BIT ; Check if we try the correct reg goto RxNoProb bcf LCD_RS_BIT bsf temp,0 ; Indicate RS change RxNoProb NotReady call RxLCDB ; Receive byte from LCD, status reg andlw 80h btfss STATUS,Z ; If the bit was set, the zero flag is not goto NotReady btfsc temp,0 ; If we had to clear RS reset it now bsf LCD_RS_BIT swapf TxTemp,w ; Hi nibble of data to send in lo w bits call TxLCD ; Send them first... movf TxTemp,w ; Then we have the low nibble in low w bits call TxLCD ; And send that one as well return ;---------------------------------------------------------------- ; RxLCDB - recv a byte from the LCD RxLCDB call RxLCD ; Receive the high nibble movwf LCDWTmp swapf LCDWTmp,f ; Swap it back to file call RxLCD ; Receive the low nibble addwf LCDWTmp,w ; Put the nibbles together and return in W return ;---------------------------------------------------------------- ; TxLCD - send a nibble to the LCD TxLCD movwf LCDWTmp ; Write nibble to tmp bcf LCD_DB4_BIT ; Clear previous data bcf LCD_DB5_BIT ; bcf LCD_DB6_BIT ; bcf LCD_DB7_BIT ; btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT bsf LCD_DB4_BIT btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT bsf LCD_DB5_BIT btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT bsf LCD_DB6_BIT btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT bsf LCD_DB7_BIT bsf LCD_E_BIT ; And set E to clock the data into the LCD module nop ; Let it settle bcf LCD_E_BIT ; And clear the Enable again. return ; Returns without modifying W ;---------------------------------------------------------------- ; RxLCD - recv a nibble from the LCD RxLCD clrw ; Clear W register, return data in lower 4 bits bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed bsf LCD_DB4_BIT ; This sets the port bit as an input bsf LCD_DB5_BIT bsf LCD_DB6_BIT bsf LCD_DB7_BIT bcf STATUS,RP0 ; Back at reg bank 0 bsf LCD_RW_BIT ; Set Read mode for the LCD bsf LCD_E_BIT ; And set E to clock the data out of the LCD module nop ; Let the bus settle btfsc LCD_DB4_BIT ; Transfer a set port bit into W addlw 1 btfsc LCD_DB5_BIT ; Transfer a set port bit into W addlw 2 btfsc LCD_DB6_BIT ; Transfer a set port bit into W addlw 4 btfsc LCD_DB7_BIT ; Transfer a set port bit into W addlw 8 bcf LCD_E_BIT ; And clear the Enable again. bcf LCD_RW_BIT ; Set Write mode for the LCD bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed bcf LCD_DB4_BIT ; Set the port as an output again bcf LCD_DB5_BIT ; bcf LCD_DB6_BIT ; bcf LCD_DB7_BIT ; bcf STATUS,RP0 ; Back at reg bank 0 return ; Returns with data in W ;---------------------------------------------------------------------- ; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz) ; 2.73* # of ms is good... DelayW movwf Dcount ; Set delay counter clrf Dcount2 decf Dcount2,f DelayLp decfsz Dcount,f goto DelayIn retlw 0 DelayIn decfsz Dcount2,f goto DelayIn2 decf Dcount2,f goto DelayLp DelayIn2 goto $+1 goto $+1 goto $+1 goto DelayIn subtitl "Bootstrap/Bootloader code" page ;---------------------------------------------------------------------- ; Bootstrap code - Allows PIC to flash itself with data from async port ; Startup @9600bps org 700h ; Place the boot code at the top of memory BootRXState equ 7fh ; What are we waiting for @RX BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog BootAddrL equ 7dh BootAddrH equ 7ch BootDataL equ 7bh BootDataH equ 7ah BootTimerL equ 79h BootTimerM equ 78h BootTimerH equ 77h BootNumBytes equ 76h BootDataVL equ 75h BootDataVH equ 74h BootHEXTemp equ 73h BootStrTemp equ 72h Bootstrap movlw 7 movwf PCLATH bsf STATUS,RP0 ; Access bank 1 bsf TXSTA,TXEN ; Enable UART TX movlw 31 ; Divisor for 9k6 @ 20MHz Fosc movwf SPBRG ; Store bcf STATUS,RP0 ; Back to bank 0 bsf RCSTA,SPEN ; Enable serial port bsf RCSTA,CREN ; Enable UART RX ; clrf BootRXState ; Waiting for command movlw BootStartText call BootTXStr movlw 0e8h movwf BootTimerL movwf BootTimerM movwf BootTimerH BootTimeout incf BootTimerL,f ; A 24-bit counter skpnz incf BootTimerM,f skpnz incf BootTimerH,f skpnz ; When overflowing here.. goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program btfss PIR1,RCIF ; Wait for RX to complete goto BootTimeout call BootRXB xorlw 27 ; ESC btfss STATUS,Z goto BootTimeout ; If it wasn't space, wait for another key BootFlash movlw BootFlashText call BootTXStr bsf BootBits,1 clrf BootAddrL clrf BootAddrH BootLoop call BootRXB ; First find the ':' xorlw ':' skpz goto BootLoop ; Loop until we find it! call BootRXHEX ; Get one ASCII encoded byte (two chars) movwf BootNumBytes ; This is the number of bytes to be programmed on the line ; Maybe clear cary here? rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format ; Note carry should be clear here as there cannot be odd number of bytes in this format call BootRXHEX ; Receive AddrH movwf BootAddrH call BootRXHEX ; Receive AddrL movwf BootAddrL rrf BootAddrH,f ; Fix the addressing again rrf BootAddrL,f bcf BootBits,2 ; Assume we should program bsf BootBits,1 ; And assume we should program flash not ee movf BootAddrH,w xorlw 020h ; Check if it's configuration, which we can't program skpnz ; Skip the bit set if it was false alarm bsf BootBits,2 ; No programming for this line xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h) skpnz ; Skip the bit set instr if not EE data address bcf BootBits,1 ; We should program EE, will ignore the AddrH call BootRXHEX ; Receive Record Type (must be 0 for real records) skpz ; Check if zero goto BootFlashComplete BootLineLoop call BootRXHEX ; Receive low-byte of data word movwf BootDataVL call BootRXHEX ; Receive high-byte of data word movwf BootDataVH btfsc BootBits,2 ; Check whether this line should be programmed at all goto BootWriteSkip bcf BootBits,0 ; Read mode first, verify if we actually have to write call BootEE movf BootDataVL,w xorwf BootDataL,f ; Compare and destroy DataL movwf BootDataL ; Write new data to DataL skpz ; Skip if no difference, have to check high byte as well goto BootWrite ; Jump directly to write movf BootDataVH,w xorwf BootDataH,f ; Compare skpnz ; Skip if no difference, no programming necessary goto BootWriteSkip BootWrite movf BootDataVH,w movwf BootDataH ; Have to put the new H byte data in as well ; movlw '+' ; call BootTXB ; Send progword indicator bsf BootBits,0 call BootEE ; Write directly into program mem ; Here a verify can take place, the read-back results are now in DataL/H movlw '+' goto BootWriteDone BootWriteSkip movlw '-' BootWriteDone call BootTXB incf BootAddrL,f ; Advance counter to next addr skpnz incf BootAddrH,f ; And add to high byte if needed decfsz BootNumBytes,f goto BootLineLoop movlw 13 ; Progress call BootTXB movlw 10 ; Progress call BootTXB goto BootLoop BootFlashComplete BootReturn movlw BootRunText call BootTXStr bsf STATUS,RP0 BootReturnWait btfss TXSTA,TRMT ; Wait for last things to flush goto BootReturnWait bcf TXSTA,TXEN ; Disable UART TX bcf STATUS,RP0 ; Back to bank 0 bcf RCSTA,SPEN ; Enable serial port bcf RCSTA,CREN ; Enable UART RX clrf PCLATH return ; Return to code ;---------------------------------------------------------------------- ; BootTXB - Sends one byte to the UART, waits for transmitter to become ; free before sending BootTXB BootTXW1 btfss PIR1,TXIF ; Wait for TX to empty goto BootTXW1 movwf TXREG ; Send the byte return ;---------------------------------------------------------------------- ; BootTXStr - Sends ASCII string pointed to by W, zero terminated BootTXStr movwf BootStrTemp ; Store offset call BootLookup ; Lookup char addlw 0 skpnz return call BootTXB ; Send char incf BootStrTemp,w ; Retrieve goto BootTXStr ;---------------------------------------------------------------------- ; BootRXB - Receives one byte from the UART, waits if nothing available BootRXB BootRXW1 btfss PIR1,RCIF ; Wait for RX to complete goto BootRXW1 movf RCREG,w ; Get the recvd byte return ;---------------------------------------------------------------------- ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary BootRXHEXNibble call BootRXB ; Receive nibble addlw -'A' ; Convert from BCD to binary nibble skpc ; Test if if was 0-9 or A-F, skip if A-F addlw 'A' - 10 - '0' ; It was numeric '0' addlw 10 ; Add 10 (A get to be 0ah etc.) return ;---------------------------------------------------------------------- ; BootRXHEX - Receives two bytes from the UART, waits if nothing available ; Decodes the bytes as ASCII hex and returns a single byte in W BootRXHEX call BootRXHEXNibble movwf BootHEXTemp swapf BootHEXTemp,f ; Swap it up to the high nibble call BootRXHEXNibble addwf BootHEXTemp,w ; And add the two nibbles together return ;---------------------------------------------------------------------- ; BootEE - Reads or writes EE or Flash memory, BootBits specify the ; exact action to take. BootAddrL and BootAddrH has to be initialized ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash ; The data to be written has to be put in BootDataL and BootDataH, and ; data will be to the same place when read back BootEE bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0) movf BootAddrH,w ; Load desired address movwf EEADRH movf BootAddrL,w movwf EEADR movf BootDataH,w ; And load the data (only used when writing) movwf EEDATH movf BootDataL,w movwf EEDATA bsf STATUS,RP0 ; Go to bank 3 bsf EECON1,EEPGD ; Point to Program Flash mem btfss BootBits,1 ; Test if that was correct or if we have to clear again bcf EECON1,EEPGD ; Point to EE DATA mem btfss BootBits,0 ; Check from read or write goto BootEERD ; Skip the WR if we were going for a read bsf EECON1,WREN ; Enable writes movlw 55h movwf EECON2 movlw 0AAh movwf EECON2 ; Unlock write operation bsf EECON1,WR ; And start a write cycle BootWRLoop btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish goto BootWRLoop ; These two instructions gets NOPed when flashing bcf EECON1,WREN ; Finally disable writes again ; Here we read the data back again, can be used as verify BootEERD bsf EECON1,RD ; Start a read cycle nop ; Only necessary for flash read, same thing as when writing above nop ; Except I could use the two words for something useful there.. :) BootEEX bcf STATUS,RP0 ; Back to bank 2 movf EEDATA,w ; Store our EE-data movwf BootDataL movf EEDATH,w movwf BootDataH bcf STATUS,RP1 ; And finally back to bank 0 return BootStartText DT "WJBoot - press ESC to flash",0 BootFlashText DT 13,10,"Send INHX8 file now...",13,10,0 BootRunText DT 13,10,"Exiting loader",13,10,0 BootLookup movwf PCL ; Go fetch the char data ;---------------------------------------------------------------------- ; EE Data (64 bytes), located at 2100h org 2100h ; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h END