- title "PIC16F870 Unilink Interface by Werner Johansson (c) 2003"\r
+ title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2003"\r
subtitl "Definitions"\r
- list c=132,P=16F870,R=DEC,F=inhx8m\r
- include "p16f870.inc" ; Standard equates & Macros\r
+ list c=150,P=16F870,R=DEC,F=inhx8m\r
+ include "p16f870.inc" ; Standard equates & Macros\r
ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs!\r
\r
\r
__CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF\r
\r
;----------------------------------------------------------------\r
-; HISTORY\r
+; TODO\r
+;----------------------------------------------------------------\r
+; Fix the DelayW routine so it actually delays W/10 ms...\r
+; No checksum checking is done on incoming packets\r
+; Investigate whether I actually have to save PCLATH in ISH, maybe save FSR?\r
+; Move RS232 code into ISH\r
+; Check Overrun errors from the UART\r
+; Implement lots of other Unilink commands (Text display, time display etc.)\r
+; Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)\r
+\r
+;----------------------------------------------------------------\r
+; HISTORY\r
;----------------------------------------------------------------\r
; Version\r
;\r
-; 0.0 Very first "Fucking No Work!" version\r
-;\r
+; 0.4 Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))\r
+; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)\r
+; 0.2 First attempt at responding to the Anyone command\r
+; 0.1 Receives Unilink data OK, relays it to serial\r
+; 0.0 Very first "F**king No Work!" version\r
+\r
+;----------------------------------------------------------------\r
+; I/O LAYOUT\r
;----------------------------------------------------------------\r
+; Unilink BUSON IN (blue) connected to RC2/CCP1\r
+; Unilink DATA (green) connected to RC3\r
+; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)\r
+; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)\r
+; Unilink RST (lilac) connected to RA4\r
+; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)\r
+; LCD RW connected to pin RB2\r
+; LCD E connected to pin RB3\r
+; LCD DB4-DB7 connected to RB4-RB7\r
+; RS-232 TX from computer connected to RC7/RX\r
+; RS-232 RX to computer connected to RC6/TX\r
+; RS-232 RI to computer connected to RC5\r
+;\r
+; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...\r
\r
-; Unilink BUSON IN (blue) connected to RC2/CCP1\r
-; Unilink DATA (green) connected to RC3\r
-; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)\r
-; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)\r
-; Unilink RST (lilac) connected to RA4\r
-; LCD RS connected to pin RB1\r
-; LCD RW connected to pin RB2\r
-; LCD E connected to pin RB3\r
-; LCD DB4-DB7 connected to RB4-RB7\r
-; RS-232 TX from computer connected to RC7/RX\r
-; RS-232 RX to computer connected to RC6/TX\r
-; RS-232 RI to computer connected to RC5\r
-\r
-; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...\r
+#define BUSON_IN_BIT PORTC,2\r
+#define DATA_BIT PORTC,3\r
+#define BUSON_OUT_BIT PORTC,4\r
+#define CLK_BIT PORTB,0\r
+#define RST_BIT PORTA,4\r
\r
#define LCD_RS_BIT PORTB,1\r
#define LCD_RW_BIT PORTB,2\r
#define LCD_DB6_BIT PORTB,6\r
#define LCD_DB7_BIT PORTB,7\r
\r
-;----------------------------------------------------------------\r
-; File register usage\r
-\r
-Dcount equ 20h\r
-e_LEN equ 21h\r
-Icount equ 2Dh ; Offset of string to print\r
-TxTemp equ 2Eh ; blahblah\r
-TxTemp2 equ 2Fh ; Blahblah2\r
-\r
-LCDWTmp equ 30h\r
-Dcount2 equ 31h\r
-temp equ 32h\r
+#define RS232_RI_BIT PORTC,5\r
\r
-DataCount equ 33h\r
-DataStore equ 34h\r
-\r
-IRQW equ 7fh\r
-IRQSTATUS equ 7eh\r
-IRQPCLATH equ 7dh\r
+;----------------------------------------------------------------\r
+; FILE REGISTER USAGE\r
+;----------------------------------------------------------------\r
+Dcount equ 20h\r
+e_LEN equ 21h\r
+\r
+Counter equ 22h\r
+SlaveBreakState equ 23h ; Hold state and time-out information about slave break, indicates when it can happen\r
+Icount equ 2Dh ; Offset of string to print\r
+TxTemp equ 2Eh ; blahblah\r
+TxTemp2 equ 2Fh ; Blahblah2\r
+\r
+LCDWTmp equ 30h\r
+Dcount2 equ 31h\r
+temp equ 32h\r
+\r
+DataCount equ 33h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\r
+DataStore equ 34h ; This is a kludge\r
+\r
+UnilinkSelected equ 3bh\r
+UnilinkBit equ 3ch ; This is my "bitmask" to be used for requests\r
+UnilinkID equ 3dh ; This is my Bus ID\r
+UnilinkCmdLen equ 3eh ; This gets updated with the actual packet length after CMD1 has been received\r
+UnilinkTXRX equ 3fh ; This is a pointer to the Unilink packet below, used with indirect addressing\r
+\r
+UnilinkRAD equ 40h ; Beginning of Unilink packet - the Receiving Address\r
+UnilinkTAD equ 41h ; Transmitter address\r
+UnilinkCMD1 equ 42h ; CMD1 byte\r
+UnilinkCMD2 equ 43h ; CMD2 byte\r
+UnilinkParity1 equ 44h ; First or only parity byte for short packets (6 bytes)\r
+UnilinkData1 equ 45h ; Extra data for medium/large packets, or zero for short packets\r
+UnilinkData2 equ 46h ;\r
+UnilinkData3 equ 47h ;\r
+UnilinkData4 equ 48h ;\r
+UnilinkData5 equ 49h ; Data5 if this is a large packet\r
+UnilinkParity2M equ 49h ; Parity2 shares the same byte if it's a medium sized packet\r
+UnilinkData6 equ 4ah ; Extra data for large packets, or zero for medium packets\r
+UnilinkData7 equ 4bh ;\r
+UnilinkData8 equ 4ch ;\r
+UnilinkData9 equ 4dh ;\r
+UnilinkParity2 equ 4eh ; Parity byte for large packets\r
+UnilinkZero equ 4fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)\r
+\r
+IRQPCLATH equ 7dh ; ISH storage\r
+IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks\r
+IRQW equ 7fh ; \r
\r
subtitl "Startup"\r
page\r
;----------------------------------------------------------------\r
-; Power up/Reset starting point [den rulerar]\r
+; Power up/Reset starting point\r
\r
- org 0\r
- call Bootstrap ; Call Flash Load routine\r
- call LCDInit ; Initialize LCD I/F\r
- call IRQInit ; Set up and start the IRQ handler\r
- goto Main ; Run the main program loop (skip the IRQ handler)\r
+ org 0 ; Start at the beginning of memory (the reset vector)\r
+ call Bootstrap ; Call Flash Load routine\r
+ call LCDInit ; Initialize LCD I/F\r
+ call IRQInit ; Set up and start the IRQ handler\r
+ goto Main ; Run the main program loop (skip the IRQ handler)\r
\r
subtitl "IRQ Handler"\r
;----------------------------------------------------------------\r
; Interrupt handler always starts at addr 4\r
-\r
- org 4 ; Must be on Address 4!\r
- movwf IRQW ; Save W\r
- swapf STATUS,w ; Get the status register into w\r
- clrf STATUS ; Zero out the status reg, gives us Bank0 all the time\r
- movwf IRQSTATUS\r
- movf PCLATH,w\r
- movwf IRQPCLATH\r
- clrf PCLATH\r
-\r
- btfss INTCON,INTF ; Check if it's INT (CLK)\r
- goto IRQNotINT ; Nope\r
-\r
- movlw 8 ; Loop this many times\r
+; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.\r
+; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR\r
+; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to\r
+; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low\r
+; again after that. Other interrupts might introduce latencies, but let's see how this works..\r
+\r
+ org 4 ; ISR vector is at address 4\r
+ movwf IRQW ; Save W\r
+ swapf STATUS,w ; Get the status register into w\r
+ clrf STATUS ; Zero out the status reg, gives Reg Bank0\r
+ movwf IRQSTATUS ; Store the STATUS reg\r
+ movf PCLATH,w ; Get the PCLATH reg\r
+; movwf IRQPCLATH ; And store it\r
+; clrf PCLATH ; Go to low memory\r
+; Maybe save FSR here as well (if there's a need for it in the non-ISR code)\r
+\r
+ btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)\r
+ goto IRQNotINT ; No it's not, check the other sources\r
+\r
+; If there's activity on the clock line (the clock goes high) the CPU will stay in here until eight bits have been clocked in\r
+; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction\r
+; cycles (5 MIPS @ 20MHz), not even a problem for serial input if it's not receiving more than 6250 bytes per second, and the\r
+; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of\r
+; 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\r
+; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX\r
+; and RX at the same time (in order to receive packets one has to make sure that the packet buffer is zeroed out before entering\r
+; here, otherwise collisions will occur..\r
+; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an\r
+; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here\r
+; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)\r
+\r
+ movlw 8 ; Loop through the 8 bits\r
movwf DataCount\r
-\r
-CLKWaitHigh\r
- btfsc PORTA,4 ; Check for RST\r
- goto IRQAfterINT\r
- btfss PORTC,2 ; Check for BUSON\r
- goto IRQAfterINT\r
- btfss PORTB,0 ; Wait for clock to go high\r
- goto CLKWaitHigh\r
-CLKWaitLow\r
- btfsc PORTA,4 ; Check for RST\r
- goto IRQAfterINT\r
+ movf UnilinkTXRX,w ; Get the pointer\r
+ movwf FSR ; Store it to make use of indirect addressing\r
+\r
+IRQINTBitSet\r
+ btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)\r
+ goto IRQINTTristate ; Bit is low, we should tristate bit\r
+ bcf PORTC,3 ; Otherwise set DATA bit low\r
+ bsf STATUS,RP0 ; Select high regs\r
+ bcf TRISC,3 ; And pull low (now it's an output)\r
+ bcf STATUS,RP0 ; Back to regbank 0\r
+ goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in\r
+\r
+IRQINTTristate\r
+ bsf STATUS,RP0 ; Select high regs\r
+ bsf TRISC,3 ; Force the bit to be tristated\r
+ bcf STATUS,RP0 ; Back to regbank 0\r
+\r
+IRQINTCLKWaitLow\r
btfss PORTC,2 ; Check for BUSON\r
goto IRQAfterINT\r
btfsc PORTB,0 ; Wait for clock to go low\r
- goto CLKWaitLow\r
+ goto IRQINTCLKWaitLow\r
\r
clrc ; Clear carry\r
btfss PORTC,3 ; Test DATA\r
setc ; Set carry if data is LOW (data is inverted!)\r
- rlf DataStore,f ; Shift it into our accumulator\r
+ rlf INDF,f ; Shift it into the "accumulator"\r
\r
decfsz DataCount,f ; Loop once more perhaps?\r
- goto CLKWaitHigh\r
+ goto IRQINTCLKWaitHigh ; Yes, again!\r
+ goto IRQINTRecvDone ; No it's done, don't check for clock to go high again\r
\r
-IRQAfterINT\r
+IRQINTCLKWaitHigh\r
+ btfss PORTC,2 ; Check for BUSON\r
+ goto IRQAfterINT\r
+ btfss PORTB,0 ; Wait for clock to go high\r
+ goto IRQINTCLKWaitHigh\r
+ goto IRQINTBitSet ; Loop again\r
+\r
+; Successfully received a byte here, run it through a state machine to figure out what to do\r
+; (several possibilites exists here):\r
+;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero\r
+; If receive counter is zero and the received byte is a zero byte, discard it\r
+; Otherwise store the byte in our receive buffer and increment receive counter\r
+; 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
+; 00 = short 6 byte packet\r
+; 10 = medium 11 byte packet\r
+; 11 = long 16 byte packet\r
+; Update the receive length byte accordingly\r
+; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing\r
+; the packet and take appropriate action.\r
+\r
+IRQINTRecvDone\r
+ clrf SlaveBreakState ; First of all, clear the break state - this got in the way, restart detection..\r
+ movf UnilinkTXRX,w ; Find out which byte # that was received\r
+ andlw 0fh ; Mask\r
+ bnz IRQINTRecvNotFirst ; Not the first byte\r
+ movf UnilinkRAD,w ; Get the first byte received\r
+ bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter\r
+IRQINTRecvNotFirst\r
+ incf UnilinkTXRX,f ; Increment address\r
+\r
+ movf UnilinkTXRX,w ; Get the byte position again\r
+ andlw 0fh ; Only lower 4 bits of interest\r
+ xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)\r
+ bnz IRQINTRecvNotCMD1 ; No, skip the length code for now\r
+ movlw 6 ; Assume it's a short packet\r
+ btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long\r
+ goto IRQINTRecvShort ; Nope, it's a short packet\r
+ addlw 5 ; OK, it's long or medium at least\r
+ btfsc INDF,6 ; Test for long\r
+ addlw 5 ; Yep, it's a long packet\r
+IRQINTRecvShort\r
+ movwf UnilinkCmdLen ; Store the length\r
+\r
+IRQINTRecvNotCMD1\r
+ movf UnilinkTXRX,w ; Get the byte position\r
+ xorwf UnilinkCmdLen,w ; XOR with the calculated command length\r
+ andlw 0fh ; and mask - this results in a zero result when finished receiving\r
+ bnz IRQINTRecvIncomplete ; Packet not ready yet\r
+\r
+; Here a packet is actually received a packet, should check the checksum(s) as well, but I don't care right now\r
+; (I need music in my car! :))\r
+; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here\r
+; (there won't be any more communication for at least another 4.8ms))\r
+\r
+; Unilink command parser:\r
+\r
+; Check for CMD1 = 01h (System bus commands)\r
+ movf UnilinkCMD1,w\r
+ xorlw 01h\r
+ bnz IRQINTParseNot01\r
+\r
+; Check for 01 00 (Bus Re-Initialization)\r
+ movf UnilinkCMD2,w\r
+; xorlw 00h\r
+ bnz IRQINTParseNot0100\r
+\r
+ clrf UnilinkID ; Clear the existing Unilink ID, if any\r
+ bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID\r
+\r
+ goto IRQINTParseComplete ; Don't send any reply to this\r
+\r
+IRQINTParseNot0100\r
+\r
+; Check for 01 02 (Anyone)\r
+ movf UnilinkCMD2,w\r
+ xorlw 02h\r
+ bnz IRQINTParseNot0102\r
+\r
+ movf UnilinkID,w ; Do I have an ID already?\r
+ bnz IRQINTParseNot0102 ; Yep, I don't want another one!\r
+\r
+ movlw 10h ; Sending to Master\r
+ movwf UnilinkRAD\r
+ movlw 0d0h ; I'm in the MD changer group\r
+ movwf UnilinkTAD\r
+ movlw 8ch ; Device discovery command reply\r
+ movwf UnilinkCMD1\r
+ movlw 00h ; 00??\r
+ movwf UnilinkCMD2\r
+ movlw 6ch ; Hard coded parity (!)\r
+ movwf UnilinkParity1\r
+ movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??\r
+ movwf UnilinkData1\r
+ movlw 2ch ; 2c??\r
+ movwf UnilinkData2\r
+ movlw 22h ; 22??\r
+ movwf UnilinkData3\r
+ movlw 00h ; 00??\r
+ movwf UnilinkData4\r
+ movlw 0deh ; Hard coded parity 2 (!)\r
+ movwf UnilinkData5\r
+ clrf UnilinkData6\r
+ goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet\r
+\r
+IRQINTParseNot0102\r
+\r
+; Check for 01 12 (Time poll)\r
+ movf UnilinkCMD2,w\r
+ xorlw 12h\r
+ bnz IRQINTParseNot0112\r
+\r
+ movf UnilinkRAD,w\r
+ xorwf UnilinkID,w ; Is it for me?\r
+ bnz IRQINTParseNot0112 ; Nope\r
+\r
+ clrf UnilinkParity1\r
+ movlw 10h ; Sending to Master\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkRAD\r
+ movf UnilinkID,w ; This is my ID\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkTAD\r
+ movlw 00h\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD1\r
+\r
+ movlw 80h ; Idle unless selected\r
+ btfsc UnilinkSelected,7 \r
+ clrw\r
+ \r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD2\r
+ clrf UnilinkData6\r
+ goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet\r
+\r
+IRQINTParseNot0112\r
+\r
+IRQINTParseNot01\r
+\r
+; Check for CMD1 = 02h (Appoint)\r
+ movf UnilinkCMD1,w\r
+ xorlw 02h\r
+ bnz IRQINTParseNot02\r
+\r
+ bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others to be discovered\r
+\r
+ movf UnilinkRAD,w ; Get the ID the master has given me\r
+ movwf UnilinkID ; Store my id\r
+ movf UnilinkCMD2,w ; Get the bitmask\r
+ movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)\r
+\r
+ clrf UnilinkParity1\r
+ movlw 10h ; Sending to Master\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkRAD\r
+ movf UnilinkID,w ; This is my ID\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkTAD\r
+ movlw 8ch ; Device discovery command again\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD1\r
+ movlw 00h\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD2\r
+\r
+ movf UnilinkParity1,w\r
+ movwf UnilinkParity2M ; That's the parity when sending medium messages\r
+\r
+ movlw 24h\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData1\r
+ movlw 2ch ; My internal MD sends 1c here... (external/internal difference)\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData2\r
+ movlw 22h\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData3\r
+ movlw 00h\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData4\r
+\r
+ clrf UnilinkData6\r
+ goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet\r
+\r
+IRQINTParseNot02\r
+\r
+; Check for CMD1 = 87h (Power control)\r
+ movf UnilinkCMD1,w\r
+ xorlw 087h\r
+ bnz IRQINTParseNot87\r
+\r
+; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)\r
+ btfsc UnilinkCMD2,3\r
+ goto IRQINTParse87PowerOn\r
+\r
+ bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)\r
+ goto IRQINTParseComplete\r
+\r
+IRQINTParse87PowerOn\r
+ bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)\r
+ goto IRQINTParseComplete\r
+\r
+IRQINTParseNot87\r
+\r
+; Check for CMD1 = f0h (Source Select)\r
+ movf UnilinkCMD1,w\r
+ xorlw 0f0h\r
+ bnz IRQINTParseNotF0\r
+\r
+ movf UnilinkCMD2,w\r
+ xorwf UnilinkID,w ; Check if it's selecting me\r
+ bnz IRQINTParseF0Deselect\r
+\r
+ bsf UnilinkSelected,7 ; Now we're selected\r
+ goto IRQINTParseComplete\r
+\r
+IRQINTParseF0Deselect\r
+\r
+ bcf UnilinkSelected,7 ; Now we're de-selected\r
+ goto IRQINTParseComplete\r
+\r
+IRQINTParseNotF0\r
+\r
+IRQINTParseComplete\r
+\r
+; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master\r
+; (that's why we clear out all the packet buffer bytes)\r
+; TODO: Replace this with an FSR access to save space and make the code neater\r
+\r
+ clrf UnilinkRAD\r
+ clrf UnilinkTAD\r
+ clrf UnilinkCMD1\r
+ clrf UnilinkCMD2\r
+ clrf UnilinkParity1\r
+ clrf UnilinkData1\r
+ clrf UnilinkData2\r
+ clrf UnilinkData3\r
+ clrf UnilinkData4\r
+ clrf UnilinkData5\r
+ clrf UnilinkData6\r
+ clrf UnilinkData7\r
+ clrf UnilinkData8\r
+ clrf UnilinkData9\r
+ clrf UnilinkParity2\r
+ clrf UnilinkZero\r
+\r
+IRQINTParseBypassClear\r
+\r
+ movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer\r
+ movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD\r
+\r
+ clrf UnilinkCmdLen ; No command length while waiting for a new packet\r
+\r
+ \r
+IRQINTRecvIncomplete\r
+\r
+IRQINTRecvNullByte\r
+ movf INDF,w\r
+ movwf DataStore ; Store it so the non-irq code can snoop\r
\r
- bcf INTCON,INTF ; Clear our IRQ\r
+IRQAfterINT\r
+ bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again\r
\r
IRQNotINT\r
\r
- movf IRQPCLATH,w\r
- movwf PCLATH ; Restore PCLATH\r
- swapf IRQSTATUS,w\r
- movwf STATUS ; Restore STATUS\r
- swapf IRQW,f\r
- swapf IRQW,w ; Restore W\r
- retfie ; Interrupt return\r
+ btfss PIR1,TMR2IF ; Check if it's the TMR2 interrupt (0.5ms timing)\r
+ goto IRQNotTMR2 ; No it's not, check the other sources\r
+\r
+; Slave break opportunity detection here - the logic works as follows:\r
+; Look for a data low period of at least 5 ms (10 loops)\r
+; Look for a data high period of at least 2 ms (4 loops)\r
+; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 5ms (10 loops)\r
+; If a packet would be received the packet handler automatically clears out the SlaveBreakState, which means start all over\r
+\r
+; incf Counter,f ; Increment the general purpose counter\r
+\r
+ btfsc SlaveBreakState,5 ; Check if already pulling the data line low\r
+ goto IRQTMR2SlaveBreak\r
+\r
+ btfsc SlaveBreakState,7 ; Looking for low or high data\r
+ goto IRQTMR2HighData\r
+ btfss DATA_BIT ; Looking for a low data line, if it's low, increment state, if it's high, reset state\r
+ goto IRQTMR2LowDataOK\r
+ clrf SlaveBreakState ; Got a high data line while waiting for a low one, reset state\r
+ goto IRQAfterTMR2 ; Leave ISR\r
+\r
+IRQTMR2HighData\r
+ btfsc DATA_BIT ; Looking for a high data line, if it's high - increment state, otherwise wait\r
+ goto IRQTMR2HighDataOK\r
+ movlw 080h\r
+ btfss SlaveBreakState,6 ; Test the "first time around" bit\r
+ clrw ; Not the beginning of the state, have to restart the entire thing now, not just this state\r
+ andwf SlaveBreakState,f ; Mask out the 1 upper control bits and restart this state\r
+ goto IRQAfterTMR2\r
+\r
+IRQTMR2HighDataOK\r
+IRQTMR2LowDataOK\r
+ bsf SlaveBreakState,6 ; Set the "first time around" bit\r
+ btfss SlaveBreakState,4 ; Only increment to 0x10\r
+ incf SlaveBreakState,f\r
+ \r
+ movf SlaveBreakState,w\r
+ andlw 1fh\r
\r
+ btfss SlaveBreakState,7 ; Checking whether it's low or high\r
+ goto IRQTMR2FoundLow\r
\r
+ xorlw 4 ; It's high now, and if 4 periods have passed we can activate slave break\r
+ skpz\r
+ goto IRQAfterTMR2\r
+\r
+; Issue slave break here\r
+ incf Counter,f\r
+; clrf SlaveBreakState\r
+ movlw 20h\r
+ movwf SlaveBreakState\r
+ bcf DATA_BIT\r
+ bsf STATUS,RP0\r
+ bcf DATA_BIT\r
+ bcf STATUS,RP0\r
+ goto IRQAfterTMR2\r
\r
- subtitl "Main loop"\r
- page\r
+IRQTMR2FoundLow\r
+ xorlw 7\r
+ skpz\r
+ goto IRQAfterTMR2\r
+ movlw 80h ; Prepare for state 2, looking for data line high\r
+ movwf SlaveBreakState\r
+ goto IRQAfterTMR2\r
+ \r
+IRQTMR2SlaveBreak\r
+ movf SlaveBreakState,w\r
+ andlw 01fh\r
+ xorlw 4\r
+ skpz\r
+ goto IRQAfterTMR2\r
+ bsf STATUS,RP0\r
+ bsf DATA_BIT\r
+ bcf STATUS,RP0\r
+ clrf SlaveBreakState\r
\r
-;----------------------------------------------------------------\r
-; Data can be stored between here and 100h...\r
+IRQAfterTMR2\r
+ bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again\r
\r
-StartUpText1\r
- DT "-WJ UniLink I/F-"\r
-StartUpText2\r
- DT "Code and design:"\r
-StartUpText3\r
- DT "**TCC of Yodel**"\r
+IRQNotTMR2\r
\r
- \r
-LookUp movwf PCL ; Go to it\r
+; Finally restore CPU state and return from the ISR\r
+\r
+; If I have to save the FSR in the beginning I also need to restore it here...\r
+\r
+; movf IRQPCLATH,w\r
+; movwf PCLATH ; Restore PCLATH\r
+ swapf IRQSTATUS,w\r
+ movwf STATUS ; Restore STATUS\r
+ swapf IRQW,f\r
+ swapf IRQW,w ; Restore W\r
+ retfie ; Interrupt return\r
+\r
+\r
+ subtitl "Main loop"\r
+ page\r
\r
;----------------------------------------------------------------\r
; Main program begins here. [Called after bootloader, lcdinit and irqinit...]\r
\r
- org 100h\r
+; org 100h ; Maybe not force this to a specific address later\r
Main\r
+ movlw high LookUp\r
+ movwf PCLATH\r
\r
- bsf STATUS,RP0\r
- bsf TXSTA,TXEN ; Enable UART TX\r
- bcf STATUS,RP0 ; Back to bank 0\r
+ movlw low StartUpText1 ; Show something on the LCD\r
+ call TxLCD16B\r
\r
- bsf RCSTA,SPEN ; Enable serial port\r
- bsf RCSTA,CREN ; Enable UART RX\r
+MainLoop\r
\r
-retry\r
- \r
-; movlw 8 ; Loop this many times\r
-; movwf DataCount\r
-\r
-;CLKWaitHigh\r
-; btfsc PORTA,4 ; Check for RST\r
-; goto retry\r
-; btfss PORTC,2 ; Check for BUSON\r
-; goto retry\r
-; btfss PORTB,0 ; Wait for clock to go high\r
-; goto CLKWaitHigh\r
-;CLKWaitLow\r
-; btfsc PORTA,4 ; Check for RST\r
-; goto retry\r
-; btfss PORTC,2 ; Check for BUSON\r
-; goto retry\r
-; btfsc PORTB,0 ; Wait for clock to go low\r
-; goto CLKWaitLow\r
-\r
-; clrc ; Clear carry\r
-; btfss PORTC,3 ; Test DATA\r
-; setc ; Set carry if data is LOW (data is inverted!)\r
-; rlf DataStore,f ; Shift it into our accumulator\r
-\r
-; decfsz DataCount,f ; Loop once more perhaps?\r
-; goto CLKWaitHigh\r
-\r
- bcf LCD_RS_BIT ;Command mode\r
- movlw 80h ;DisplayRam 0\r
+ bcf LCD_RS_BIT ; LCD Command mode\r
+ movlw 80h ; DisplayRam 0\r
call TxLCDB\r
- bsf LCD_RS_BIT\r
+ bsf LCD_RS_BIT\r
\r
- movlw '0'\r
- btfsc PORTA,4 ; Test RST\r
+; movlw '0'\r
+ movf Counter,w ; Debug timer\r
+ btfsc PORTA,4 ; Test RST\r
movlw 'R'\r
call TxLCDB\r
\r
- movlw '0'\r
- btfsc PORTB,0 ; Test CLK\r
+; movlw '0'\r
+ movf SlaveBreakState,w\r
+ btfsc PORTB,0 ; Test CLK\r
movlw 'C'\r
call TxLCDB\r
\r
movlw '0'\r
- btfsc PORTC,2 ; Test BUSON-IN\r
+ btfsc PORTC,2 ; Test BUSON-IN\r
movlw 'B'\r
call TxLCDB\r
\r
movlw '0'\r
- btfsc PORTC,3 ; Test DATA\r
+ btfsc PORTC,3 ; Test DATA\r
movlw 'D'\r
call TxLCDB\r
\r
+ movf UnilinkCmdLen,w\r
+ bz MainDontPrintCmd\r
+ addlw '0'\r
+ call TxLCDB\r
+\r
+MainDontPrintCmd\r
+\r
movf DataCount,w ; Load bit counter (if 0 then byte is available)\r
skpz\r
- goto retry\r
+ goto MainLoop\r
\r
- movf DataStore,w ; Get the result\r
decf DataCount,f ; Set it non-zero\r
\r
+ movf DataStore,w\r
call BootTXB ; Send to terminal\r
-\r
- goto retry\r
-\r
+ goto MainLoop\r
\r
\r
- movlw StartUpText1\r
- call TxLCD16B\r
- call LongDelay\r
-\r
- bsf PORTA,4 ; turn off LED\r
-\r
- movlw StartUpText2\r
- call TxLCD16B\r
- call LongDelay\r
+;----------------------------------------------------------------\r
+; IRQInit - Sets up the IRQ Handler\r
+; Set up Timer2 to generate 2000 interrupts per second, used for timing - 1/16 prescaler and a PR2 reg of 156 (0x9c) is set\r
\r
- bcf PORTA,4 ; turn on LED\r
+IRQInit\r
\r
- movlw StartUpText3\r
- call TxLCD16B\r
- call LongDelay\r
+; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt\r
+; TODO: Replace this with FSR access\r
+ clrf UnilinkSelected\r
+ clrf UnilinkID\r
+ clrf UnilinkBit\r
+ clrf UnilinkCmdLen\r
+ clrf UnilinkRAD\r
+ clrf UnilinkTAD\r
+ clrf UnilinkCMD1\r
+ clrf UnilinkCMD2\r
+ clrf UnilinkParity1\r
+ clrf UnilinkData1\r
+ clrf UnilinkData2\r
+ clrf UnilinkData3\r
+ clrf UnilinkData4\r
+ clrf UnilinkData5\r
+ clrf UnilinkData6\r
+ clrf UnilinkData7\r
+ clrf UnilinkData8\r
+ clrf UnilinkData9\r
+ clrf UnilinkParity2\r
+ clrf UnilinkZero\r
+\r
+ clrf DataStore\r
+ movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer\r
+ movwf UnilinkTXRX ; Store it\r
+\r
+ clrf SlaveBreakState ; Zero out the status, we're starting from the beginning\r
+\r
+; Fix the output state of RI and BUSON_OUT to a safe default\r
+\r
+ bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)\r
+ bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first\r
+\r
+ movlw 06h ; Timer2 enabled + 1/16 prescaler\r
+ movwf T2CON\r
\r
- goto retry\r
+ bsf STATUS,RP0 ; Reg bank 1\r
\r
+ movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second\r
+ movwf PR2\r
\r
-;----------------------------------------------------------------\r
-; IRQInit - Sets up the IRQ Handler\r
+ bcf RS232_RI_BIT ; Both bits should be outputs\r
+ bcf BUSON_OUT_BIT ;\r
\r
-IRQInit\r
- bsf STATUS,RP0 ; Reg bank 1\r
+; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...\r
; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge\r
+\r
bsf INTCON,INTE ; Enable the RB0/INT\r
+ bsf INTCON,PEIE ; Enable the peripheral interrupts\r
+ bsf PIE1,TMR2IE ; Enable the Timer2 peripheral interrupt\r
bsf INTCON,GIE ; Enable global interrupts\r
+\r
+ bsf TXSTA,TXEN ; Enable UART TX\r
+\r
bcf STATUS,RP0 ; Back to bank 0\r
+\r
+ bsf RCSTA,SPEN ; Enable serial port\r
+ bsf RCSTA,CREN ; Enable UART RX\r
+\r
return\r
\r
;----------------------------------------------------------------\r
; Initialize LCD Controller...\r
\r
LCDInit\r
- clrf PORTB\r
- bsf STATUS,RP0 ; Hi Bank\r
- movlw 0cfh ; RC4 & RC5 should be outputs...\r
- movwf TRISC ; Yep.\r
- movlw 001h ; All but RB0 are outputs.\r
- movwf TRISB ; Yep\r
- bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up\r
- bcf STATUS,RP0 ; Restore Lo Bank\r
-\r
-; bcf PORTA,4 ; turn on LED\r
-\r
-; movlw 44 ; Should be 16ms delay\r
- movlw 255 ; Should be 16ms delay\r
+ clrf PORTB ; First clear PortB data register\r
+ bsf STATUS,RP0 ; Reg bank 1\r
+ movlw 001h ; All but RB0 are outputs.\r
+ movwf TRISB ;\r
+\r
+ bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up\r
+ bcf STATUS,RP0 ; Restore Reg bank 0\r
+\r
+; This is a standard reset sequence for the LCD controller\r
+\r
+ movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay\r
call DelayW\r
\r
- movlw 3 ; Write 3 to the LCD\r
- call TxLCD ; Send to LCD\r
-; movlw 12 ; Should be 5ms delay\r
- movlw 255 ; Should be 16ms delay\r
+ movlw 3 ; Write 3 to the LCD\r
+ call TxLCD ; Send to LCD\r
+ movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay\r
call DelayW\r
\r
- movlw 3 ; Write 3 to the LCD\r
+ movlw 3 ; Write 3 to the LCD\r
call TxLCD\r
-; movlw 12 ; Should be 16ms delay\r
- movlw 255 ; Should be 16ms delay\r
+ movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay\r
call DelayW\r
\r
- movlw 3 ; Write 3 to the LCD\r
+ movlw 3 ; Write 3 to the LCD\r
call TxLCD\r
-; movlw 44\r
- movlw 255 ; Should be 16ms delay\r
+ movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay\r
call DelayW\r
\r
- movlw 2 ;\\r
- call TxLCD ; | 4-bit interface\r
-; movlw 55 ; | After this we are ready to ROCK!\r
- movlw 255 ; Should be 16ms delay\r
- call DelayW ;/\r
+ movlw 2 ; 4-bit interface requested\r
+ call TxLCD ;\r
+ movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay\r
+ call DelayW ;\r
\r
-; bsf PORTA,4 ; turn off LED\r
+; Reset sequence ends here\r
+; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits\r
\r
- movlw 28h ; Some random commands :)))\r
- call TxLCDB\r
+ movlw 28h ; Function Select + 4-bit bus + 2-line display\r
+ call TxLCDB\r
\r
- movlw 0ch ; hmmm\r
- call TxLCDB\r
+ movlw 0ch ; Display Control + LCD On (No cursor)\r
+ call TxLCDB\r
\r
- movlw 01h ; hmmm\r
- call TxLCDB\r
+ movlw 01h ; Clear Display\r
+ call TxLCDB\r
\r
- movlw 06h ; hmmm\r
- call TxLCDB\r
+ movlw 06h ; Auto Increment cursor position\r
+ call TxLCDB\r
\r
return\r
\r
;----------------------------------------------------------------\r
-; LongDelay - Well, guess that for yourself...\r
-\r
-LongDelay\r
-; btfss PORTB,6 ; Talk to da PC?\r
-; goto PCTalk ; Oh yeah...\r
-\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- movlw 255\r
- call DelayW\r
- return\r
-\r
-;----------------------------------------------------------------\r
; TxLCD16B\r
; Send a string to the LCD.\r
\r
TxLCD16B\r
movwf Icount\r
- bcf LCD_RS_BIT\r
- movlw 80h ;DisplayRam 0\r
+ bcf LCD_RS_BIT\r
+ movlw 80h ; DisplayRam 0\r
call TxLCDB\r
- bsf LCD_RS_BIT\r
+ bsf LCD_RS_BIT\r
call TxLCD8B\r
- bcf LCD_RS_BIT\r
- movlw 80h+40 ;DisplayRam 40 (row 2)\r
+ bcf LCD_RS_BIT\r
+ movlw 80h+40 ; DisplayRam 40 (row 2)\r
call TxLCDB\r
- bsf LCD_RS_BIT\r
+ bsf LCD_RS_BIT\r
call TxLCD8B\r
return\r
\r
; Send a string to the LCD.\r
\r
TxLCD8B\r
-; movwf Icount ; Icount = W\r
+; movwf Icount ; Icount = W\r
movlw 8\r
- movwf e_LEN ; Move to e_LEN\r
+ movwf e_LEN ; Move to e_LEN\r
\r
-Txm_lp movf Icount,w ; get the byte\r
+Txm_lp movf Icount,w ; get the byte\r
call LookUp\r
- incf Icount,f ; ...else ++Icount (table index)\r
- call TxLCDB ; Send out the byte\r
+ incf Icount,f ; ...else ++Icount (table index)\r
+ call TxLCDB ; Send out the byte\r
decfsz e_LEN,f\r
goto Txm_lp\r
return\r
; TxLCDB - send a byte to the LCD\r
\r
TxLCDB\r
- movwf TxTemp ; Store byte to send for a while...\r
+ movwf TxTemp ; Store byte to send for a while...\r
\r
- bcf temp,0 ; Clear my temp bit\r
- btfss LCD_RS_BIT ; Check if we try the correct reg\r
+ bcf temp,0 ; Clear my temp bit\r
+ btfss LCD_RS_BIT ; Check if we try the correct reg\r
goto RxNoProb\r
bcf LCD_RS_BIT\r
- bsf temp,0 ; Indicate RS change\r
+ bsf temp,0 ; Indicate RS change\r
RxNoProb\r
\r
NotReady\r
- call RxLCDB ; Receive byte from LCD, status reg\r
+ call RxLCDB ; Receive byte from LCD, status reg\r
andlw 80h\r
- btfss STATUS,Z ; If the bit was set, the zero flag is not\r
+ skpz ; If the bit was set, the zero flag is not\r
goto NotReady\r
\r
- btfsc temp,0 ; If we had to clear RS reset it now\r
+ btfsc temp,0 ; If we had to clear RS reset it now\r
bsf LCD_RS_BIT\r
\r
- swapf TxTemp,w ; Hi nibble of data to send in lo w bits\r
- call TxLCD ; Send them first...\r
- movf TxTemp,w ; Then we have the low nibble in low w bits\r
- call TxLCD ; And send that one as well\r
+ swapf TxTemp,w ; Hi nibble of data to send in lo w bits\r
+ call TxLCD ; Send them first...\r
+ movf TxTemp,w ; Then we have the low nibble in low w bits\r
+ call TxLCD ; And send that one as well\r
\r
return\r
;----------------------------------------------------------------\r
; RxLCDB - recv a byte from the LCD\r
\r
RxLCDB\r
- call RxLCD ; Receive the high nibble\r
+ call RxLCD ; Receive the high nibble\r
movwf LCDWTmp\r
- swapf LCDWTmp,f ; Swap it back to file\r
- call RxLCD ; Receive the low nibble\r
- addwf LCDWTmp,w ; Put the nibbles together and return in W\r
+ swapf LCDWTmp,f ; Swap it back to file\r
+ call RxLCD ; Receive the low nibble\r
+ addwf LCDWTmp,w ; Put the nibbles together and return in W\r
\r
return\r
\r
; TxLCD - send a nibble to the LCD\r
\r
TxLCD\r
- movwf LCDWTmp ; Write nibble to tmp\r
- bcf LCD_DB4_BIT ; Clear previous data\r
- bcf LCD_DB5_BIT ; \r
- bcf LCD_DB6_BIT ;\r
- bcf LCD_DB7_BIT ;\r
+ movwf LCDWTmp ; Write nibble to tmp\r
+ bcf LCD_DB4_BIT ; Clear previous data\r
+ bcf LCD_DB5_BIT ; \r
+ bcf LCD_DB6_BIT ;\r
+ bcf LCD_DB7_BIT ;\r
\r
- btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT\r
+ btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT\r
bsf LCD_DB4_BIT\r
- btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT\r
+ btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT\r
bsf LCD_DB5_BIT\r
- btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT\r
+ btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT\r
bsf LCD_DB6_BIT\r
- btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT\r
+ btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT\r
bsf LCD_DB7_BIT\r
\r
- bsf LCD_E_BIT ; And set E to clock the data into the LCD module\r
- nop ; Let it settle\r
- bcf LCD_E_BIT ; And clear the Enable again.\r
- return ; Returns without modifying W\r
+ bsf LCD_E_BIT ; And set E to clock the data into the LCD module\r
+ nop ; Let it settle\r
+ bcf LCD_E_BIT ; And clear the Enable again.\r
+ return ; Returns without modifying W\r
\r
;----------------------------------------------------------------\r
; RxLCD - recv a nibble from the LCD\r
\r
RxLCD\r
- clrw ; Clear W register, return data in lower 4 bits\r
+ clrw ; Clear W register, return data in lower 4 bits\r
\r
- bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed\r
+ bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed\r
\r
- bsf LCD_DB4_BIT ; This sets the port bit as an input\r
+ bsf LCD_DB4_BIT ; This sets the port bit as an input\r
bsf LCD_DB5_BIT \r
bsf LCD_DB6_BIT \r
bsf LCD_DB7_BIT\r
- bcf STATUS,RP0 ; Back at reg bank 0 \r
\r
- bsf LCD_RW_BIT ; Set Read mode for the LCD\r
- bsf LCD_E_BIT ; And set E to clock the data out of the LCD module\r
- nop ; Let the bus settle\r
- btfsc LCD_DB4_BIT ; Transfer a set port bit into W\r
+ bcf STATUS,RP0 ; Back at reg bank 0 \r
+\r
+ bsf LCD_RW_BIT ; Set Read mode for the LCD\r
+ bsf LCD_E_BIT ; And set E to clock the data out of the LCD module\r
+ nop ; Let the bus settle\r
+ btfsc LCD_DB4_BIT ; Transfer a set port bit into W\r
addlw 1\r
- btfsc LCD_DB5_BIT ; Transfer a set port bit into W\r
+ btfsc LCD_DB5_BIT ; Transfer a set port bit into W\r
addlw 2\r
- btfsc LCD_DB6_BIT ; Transfer a set port bit into W\r
+ btfsc LCD_DB6_BIT ; Transfer a set port bit into W\r
addlw 4\r
- btfsc LCD_DB7_BIT ; Transfer a set port bit into W\r
+ btfsc LCD_DB7_BIT ; Transfer a set port bit into W\r
addlw 8\r
- bcf LCD_E_BIT ; And clear the Enable again.\r
- bcf LCD_RW_BIT ; Set Write mode for the LCD\r
+ bcf LCD_E_BIT ; And clear the Enable again.\r
+ bcf LCD_RW_BIT ; Set Write mode for the LCD\r
\r
- bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed\r
- bcf LCD_DB4_BIT ; Set the port as an output again\r
- bcf LCD_DB5_BIT ; \r
- bcf LCD_DB6_BIT ;\r
- bcf LCD_DB7_BIT ;\r
- bcf STATUS,RP0 ; Back at reg bank 0 \r
+ bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed\r
\r
- return ; Returns with data in W\r
+ bcf LCD_DB4_BIT ; Set the port as an output again\r
+ bcf LCD_DB5_BIT ; \r
+ bcf LCD_DB6_BIT ;\r
+ bcf LCD_DB7_BIT ;\r
+\r
+ bcf STATUS,RP0 ; Back at reg bank 0 \r
+\r
+ return ; Returns with data in W\r
\r
;----------------------------------------------------------------------\r
-; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz)\r
-; 2.73* # of ms is good...\r
-\r
-DelayW movwf Dcount ; Set delay counter\r
- clrf Dcount2\r
- decf Dcount2,f\r
-DelayLp decfsz Dcount,f\r
- goto DelayIn\r
- retlw 0\r
-DelayIn decfsz Dcount2,f\r
- goto DelayIn2\r
- decf Dcount2,f\r
- goto DelayLp\r
-DelayIn2 goto $+1\r
- goto $+1\r
- goto $+1\r
- goto DelayIn\r
+; Delay routines (non-interrupt based, therefore not even close to reliable)\r
+; W=10 gives ~ 1ms of delay\r
+; 1ms=5000 instructions wasted, 100us=500 cycles\r
+; Maximum time waited will be 256*100us=25.6ms\r
+\r
+DelayW\r
+ movwf Dcount ; Set delay counter, number of 100us periods to wait\r
+\r
+DelayOuter\r
+ movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more\r
+ movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount\r
+DelayInner\r
+ decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)\r
+ goto DelayInner ; 2 cycles\r
+ decfsz Dcount,f ; Now decrement number of 100us periods and loop again\r
+ goto DelayOuter\r
+ return\r
+\r
+\r
+;----------------------------------------------------------------\r
+; Data can be stored between 600 and 6ffh...\r
+\r
+ org 600h\r
+StartUpText1\r
+ DT "----- WJ UniLink"\r
+ \r
+LookUp movwf PCL ; Go to it (this assumes PCLATH == 06h)\r
+\r
\r
subtitl "Bootstrap/Bootloader code"\r
page\r
\r
;----------------------------------------------------------------------\r
-; Bootstrap code - Allows PIC to flash itself with data from async port\r
+; Bootstrap code - Allows PIC to flash itself with data from the async port.\r
+; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory\r
+; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver\r
+; (one good way of solving that is to wait for the echo from the PIC before sending anything else)\r
+; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration\r
+; register can't be programmed. That means that any references to the config register in the hex file will be ignored.\r
+;\r
; Startup @9600bps\r
\r
- org 700h ; Place the boot code at the top of memory\r
+; RAM usage for the bootstrap code\r
\r
-BootRXState equ 7fh ; What are we waiting for @RX\r
BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog\r
BootAddrL equ 7dh\r
BootAddrH equ 7ch\r
BootDataVL equ 75h\r
BootDataVH equ 74h\r
BootHEXTemp equ 73h\r
-BootStrTemp equ 72h\r
\r
-Bootstrap\r
- movlw 7\r
- movwf PCLATH\r
+ org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)\r
\r
+Bootstrap\r
bsf STATUS,RP0 ; Access bank 1\r
bsf TXSTA,TXEN ; Enable UART TX\r
movlw 31 ; Divisor for 9k6 @ 20MHz Fosc\r
bsf RCSTA,SPEN ; Enable serial port\r
bsf RCSTA,CREN ; Enable UART RX\r
\r
-; clrf BootRXState ; Waiting for command\r
-\r
- movlw BootStartText\r
+ movlw low BootStartText ; Send boot banner to the serial port\r
call BootTXStr\r
\r
- movlw 0e8h\r
+ movlw 0e8h ; Initialize timeout timer\r
movwf BootTimerL\r
movwf BootTimerM\r
movwf BootTimerH\r
goto BootTimeout\r
call BootRXB\r
xorlw 27 ; ESC\r
- btfss STATUS,Z\r
- goto BootTimeout ; If it wasn't space, wait for another key\r
+ skpz\r
+ goto BootTimeout ; If it wasn't ESC, wait for another key\r
\r
BootFlash\r
- movlw BootFlashText\r
+ movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port\r
call BootTXStr\r
\r
bsf BootBits,1\r
movf BootDataVH,w\r
movwf BootDataH ; Have to put the new H byte data in as well\r
\r
-; movlw '+'\r
-; call BootTXB ; Send progword indicator\r
-\r
bsf BootBits,0\r
call BootEE ; Write directly into program mem\r
\r
; Here a verify can take place, the read-back results are now in DataL/H\r
\r
- movlw '+'\r
- goto BootWriteDone\r
-\r
BootWriteSkip\r
- movlw '-'\r
-BootWriteDone\r
- call BootTXB\r
\r
incf BootAddrL,f ; Advance counter to next addr\r
skpnz\r
decfsz BootNumBytes,f\r
goto BootLineLoop\r
\r
- movlw 13 ; Progress\r
- call BootTXB\r
- movlw 10 ; Progress\r
- call BootTXB\r
-\r
goto BootLoop\r
\r
BootFlashComplete\r
\r
BootReturn\r
- movlw BootRunText\r
+ movlw low BootRunText\r
call BootTXStr\r
\r
- bsf STATUS,RP0\r
+ bsf STATUS,RP0 ; Reg bank 1\r
BootReturnWait\r
btfss TXSTA,TRMT ; Wait for last things to flush\r
goto BootReturnWait\r
bcf TXSTA,TXEN ; Disable UART TX\r
bcf STATUS,RP0 ; Back to bank 0\r
\r
- bcf RCSTA,SPEN ; Enable serial port\r
- bcf RCSTA,CREN ; Enable UART RX\r
+ bcf RCSTA,SPEN ; Disable serial port\r
+ bcf RCSTA,CREN ; Disable UART RX\r
\r
- clrf PCLATH\r
return ; Return to code \r
\r
;----------------------------------------------------------------------\r
; BootTXStr - Sends ASCII string pointed to by W, zero terminated\r
\r
BootTXStr\r
- movwf BootStrTemp ; Store offset\r
- call BootLookup ; Lookup char\r
- addlw 0\r
- skpnz\r
+ movwf BootAddrL ; Store LSB of text pointer\r
+ movlw 07h ; MSB of pointer to the text (0700h in this boot loader)\r
+ movwf BootAddrH\r
+ movlw 02h ; Select "Read Program Memory" operation\r
+ movwf BootBits \r
+BootTXStrLoop\r
+ call BootEE ; Lookup char (actually two packed into one word)\r
+ rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB)\r
+ rlf BootDataH,w ; Shift it into 2nd char\r
+ call BootTXB ; Send the high byte first\r
+ movf BootDataL,w ; Get the low byte\r
+ andlw 07fh ; Mask of the highest bit\r
+ skpnz ; Stop if zero\r
return\r
call BootTXB ; Send char\r
- incf BootStrTemp,w ; Retrieve\r
- goto BootTXStr\r
+ incf BootAddrL,f ; Increment pointer\r
+ goto BootTXStrLoop\r
\r
;----------------------------------------------------------------------\r
; BootRXB - Receives one byte from the UART, waits if nothing available\r
btfss PIR1,RCIF ; Wait for RX to complete\r
goto BootRXW1\r
movf RCREG,w ; Get the recvd byte\r
+ call BootTXB ; Echo to terminal\r
return\r
\r
;----------------------------------------------------------------------\r
\r
BootRXHEXNibble\r
call BootRXB ; Receive nibble\r
-\r
addlw -'A' ; Convert from BCD to binary nibble\r
skpc ; Test if if was 0-9 or A-F, skip if A-F\r
addlw 'A' - 10 - '0' ; It was numeric '0'\r
\r
return\r
\r
+; To produce compact code the end zero byte has to be in the LSB (that means an even number of chars in every string)\r
BootStartText\r
- DT "WJBoot - press ESC to flash",0\r
+ DW 0x2bca,0x216f,0x37f4,0x102d,0x1070,0x3965,0x39f3,0x1045,0x29c3,0x1074,0x37a0,0x336c,0x30f3,0x3400\r
+; DE "WJBoot - press ESC to flash\x00"\r
BootFlashText\r
- DT 13,10,"Send INHX8 file now...",13,10,0\r
+ DW 0x068a,0x29e5,0x3764,0x1049,0x2748,0x2c38,0x1066,0x34ec,0x32a0,0x376f,0x3bae,0x172e,0x0680\r
+; DE "\r\nSend INHX8 file now...\r\x00"\r
BootRunText\r
- DT 13,10,"Exiting loader",13,10,0\r
+ DW 0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680\r
+; DE "\r\nExiting loader\r\x00"\r
\r
-BootLookup\r
- movwf PCL ; Go fetch the char data\r
\r
;----------------------------------------------------------------------\r
; EE Data (64 bytes), located at 2100h\r
\r
org 2100h\r
-; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h\r
+; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
\r
END\r