;------------------------------------------------------------------------------ ; ; Simple Debug Kernel for CPU supervisor on Z80 Project Mark 2 ; File Version 1 - 20 - Sep - 2009 ; hairymnstr@gmail.com ; ; Copyright (C) 2009 Nathan Dumont ; ; This program is free software: you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, either version 3 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program. If not, see . ; ; ;------------------------------------------------------------------------------ list p=18f4520 #include errorlevel -302 errorlevel -205 CONFIG OSC = HSPLL CONFIG FCMEN = ON, IESO = OFF, PWRT = ON, BOREN = OFF, BORV = 0 CONFIG WDT = OFF, WDTPS = 1, MCLRE = ON, LPT1OSC = OFF CONFIG PBADEN = OFF, CCP2MX = PORTC, STVREN = ON, LVP = OFF CONFIG XINST = OFF, DEBUG = OFF, CP0 = OFF, CP1 = OFF, CP2 = OFF, CP3 = OFF CONFIG CPB = OFF, CPD = OFF, WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF CONFIG WRTB = OFF, WRTC = OFF, WRTD = OFF, EBTR0 = OFF, EBTR1 = OFF CONFIG EBTR2 = OFF, EBTR3 = OFF, EBTRB = OFF ;== PORT Definitions ========================================================== ;-- PORT A -------------------------------------------------------------------- P_RESET EQU 0 P_BUSRQ EQU 1 P_MREQ EQU 2 P_IORQ EQU 3 P_WAIT EQU 4 P_HI_LAT EQU 5 ;-- PORT C -------------------------------------------------------------------- P_BUSACK EQU 0 P_SD_CS EQU 1 P_CLK EQU 2 P_SD_CK EQU 3 P_SD_DI EQU 4 P_SD_DO EQU 5 P_TX EQU 6 P_RX EQU 7 ;-- PORT E -------------------------------------------------------------------- P_RD EQU 0 P_WR EQU 1 P_CS EQU 2 ;== Variables ================================================================= UDATA count RES 3 LO_ADDR RES 1 HI_ADDR RES 1 DREG RES 1 RX_MODE RES 1 ; -- RX MODE CONSTANTS -------------------------------------------------------- RX_COM EQU 0 RX_LEN EQU 1 RX_DAT EQU 2 RX_CKS EQU 3 RX_BSY EQU 4 RX_COMMAND RES 1 RX_COUNT RES 1 RX_CHECKSUM RES 1 RX_FLAGS RES 1 ; -- FLAG VALUES -------------------------------------------------------------- CHECKSUM_FAIL EQU 0x01 TX_CHECKSUM RES 1 TX_COUNT RES 1 org 0x00 goto init org 0x08 interrupt btfsc PIR1,RCIF ;test serial receive interrupt flag call serial_rx_int ;set, so handle received data retfie FAST ;all interrupts serviced, return reinstating context init clrf RX_FLAGS movlw 0x01 movwf FSR0H ; set FSR0 to BANK1, RX Buffer clrf FSR0L movlw 0x02 movwf FSR1H ; set FSR1 to BANK2, TX Buffer clrf FSR1L movlw RX_COM movwf RX_MODE ; set RX_MODE to look for a command call serial_init ; initialise the serial port call set_master ; Z80 not fitted so PIC is always master atm. movlw 0x00 movwf HI_ADDR movlw 0x01 movwf LO_ADDR ; Port address of "debug port" bcf TRISC,5 ; the extra LED on SD card port needs output bsf LATC,5 ; turn the extra LED on movlw 0x01 movwf DREG ; initialise DREG with a 1 in the lsb main ; call serial_command_dispatch, checks for a whole command received and ; calls an appropriate handler function for the command call serial_command_dispatch goto main ;== Z80 Bus interfacing functions ============================================= ;****************************************************************************** ;* * ;* set_master - configure pins so PIC is master device * ;* * ;* Inputs: NONE * ;* Outputs: NONE * ;* Called: * ;* Changes: TRISB, TRISE, TRISA * ;* * ;****************************************************************************** set_master ; set the address pins to output clrf TRISB movlw b'11101100' ;RD and WR as output, disable PSP andwf TRISE,f movlw 0xFF movwf LATE ;set RD and WR High movlw b'11010000' andwf TRISA,f movlw b'00111100' iorwf LATA,f return ;****************************************************************************** ;* * ;* io_read - Read an address on the IO bus * ;* * ;* Inputs: HI_ADDR, LO_ADDR * ;* Outputs: DREG * ;* Called: * ;* Changes: * ;* * ;* Asssumes the PIC is in master mode (WAIT in, RD,WR,IORQ,MREQ and ADDR out * ;* * ;****************************************************************************** io_read ; set address movf HI_ADDR,w movwf LATB bcf LATA,P_HI_LAT movf LO_ADDR,w bsf LATA,P_HI_LAT movwf LATB ; 200ns delay before IORQ and RD nop bcf LATA,P_IORQ bcf LATE,P_RD nop ; mandatory wait state io_read_wait_loop btfss PORTA,P_WAIT bra io_read_wait_loop ; store the read data movf PORTD,w ;release IORQ and RD bsf LATE,P_RD bsf LATA,P_IORQ movwf DREG return ;****************************************************************************** ;* * ;* io_write - Write an address on the IO bus * ;* * ;* Inputs: HI_ADDR, LO_ADDR, DREG * ;* Outputs: NONE * ;* Called: * ;* Changes: * ;* * ;* Asssumes the PIC is in master mode (WAIT in, RD,WR,IORQ,MREQ and ADDR out * ;* * ;****************************************************************************** io_write ; set address movf HI_ADDR,w movwf LATB bcf LATA,P_HI_LAT movf LO_ADDR,w bsf LATA,P_HI_LAT movwf LATB ; 200ns delay before IORQ and RD movf DREG,w ; write the data to the bus movwf LATD clrf TRISD ; don't forget to drive the bus for a write!! bcf LATA,P_IORQ bcf LATE,P_WR nop ; mandatory wait state io_write_wait_loop btfss PORTA,P_WAIT bra io_write_wait_loop ;release IORQ and RD bsf LATE,P_WR bsf LATA,P_IORQ setf TRISD ; stop driving the bus again return ;****************************************************************************** ;* * ;* mem_read - Read from an address on the memory bus * ;* * ;* Inputs: HI_ADDR, LO_ADDR * ;* Outputs: DREG * ;* Called: * ;* Changes: * ;* * ;* Asssumes the PIC is in master mode (WAIT in, RD,WR,IORQ,MREQ and ADDR out) * ;* * ;****************************************************************************** mem_read ; set address movf HI_ADDR,w movwf LATB bcf LATA,P_HI_LAT movf LO_ADDR,w bsf LATA,P_HI_LAT movwf LATB bcf LATA,P_MREQ bcf LATE,P_RD mem_read_wait_loop btfss PORTA,P_WAIT bra mem_read_wait_loop ; store the read data movf PORTD,w ;release IORQ and RD bsf LATE,P_RD bsf LATA,P_MREQ movwf DREG return ;****************************************************************************** ;* * ;* mem_write - Write to an address on the memory bus * ;* * ;* Inputs: HI_ADDR, LO_ADDR, DREG * ;* Outputs: NONE * ;* Called: * ;* Changes: * ;* * ;* Asssumes the PIC is in master mode (WAIT in, RD,WR,IORQ,MREQ and ADDR out) * ;* * ;****************************************************************************** mem_write ; set address movf HI_ADDR,w movwf LATB bcf LATA,P_HI_LAT movf LO_ADDR,w bsf LATA,P_HI_LAT movwf LATB ; 200ns delay before IORQ and RD movf DREG,w ; write the data to the bus movwf LATD clrf TRISD ; don't forget to drive the bus for a write!! bcf LATA,P_MREQ bcf LATE,P_WR ; mandatory wait state mem_write_wait_loop btfss PORTA,P_WAIT bra mem_write_wait_loop ;release IORQ and RD bsf LATE,P_WR bsf LATA,P_MREQ setf TRISD ; stop driving the bus again return ;== Serial Console Functions ================================================== ;****************************************************************************** ;* * ;* serial_init - set up the serial port for PC comms * ;* * ;* Inputs: NONE * ;* Outputs: NONE * ;* Called: * ;* Changes: SPBRG, RCSTA, TXSTA, BAUDCON, TRISC, RCON, PIR1, PIE1, INTCON * ;* * ;****************************************************************************** serial_init ; first make sure that the RX and TX are both set input, hardware changes ; this once the UART is enabled movlw b'11000000' iorwf TRISC,f ; next set the UART up movlw 0x10 movwf SPBRG movlw 0x04 movwf SPBRGH ; 9600 baud high speed 16 bit mode movlw b'00100100' ; enable sending, 8-bit, high speed baud rate movwf TXSTA movlw b'00010000' ; enable receiving 8-bit movwf RCSTA movlw b'00001000' ; no-auto sensing - 16 bit baud rate movwf BAUDCON bsf RCSTA,SPEN ; enable the serial port ; set up interrupt on receive bsf RCON, IPEN bcf PIR1, RCIF bsf PIE1, RCIE bsf INTCON, GIEH ; enable global interrupts return ;****************************************************************************** ;* * ;* serial_send - send the contents of W via the UART * ;* * ;* Inputs: Byte to send in W * ;* Outputs: NONE * ;* Called: * ;* Changes: TXREG * ;* * ;****************************************************************************** serial_send btfss PIR1,TXIF ;test transmit interrupt flag bra serial_send ;if clear, TXREG is full movwf TXREG ;if set, TXREG is empty, so copy data into it return ; -- serial_tx_send_packet ---------------------------------------------------- serial_tx_send_packet ; send a whole message packet from the TX Buffer ; first set the TX pointer to the start of the buffer clrf FSR1L ; send the first byte (the command code) movf POSTINC1,w call serial_send ; now send the length byte, and initialise the counter movf POSTINC1,w call serial_send movwf TX_COUNT ; now we loop over send and decrement the counter serial_tx_send_packet_data_loop movf POSTINC1,w call serial_send decfsz TX_COUNT,f bra serial_tx_send_packet_data_loop ; lastly send the checksum movf POSTINC1,w call serial_send ; done, return return ;****************************************************************************** ;* * ;* serial_rx_int - UART receive interrupt routine * ;* * ;* Inputs: None * ;* Outputs: None * ;* Called: * ;* Changes: PIR1, w * ;* * ;****************************************************************************** serial_rx_int movlw b'00000110' andwf RCSTA,w ;test if either of the error bits are set bnz serial_rx_int_error ;if result is not zero an error occurred serial_rx_int_no_error ;if no error occurred, just echo input ; if RX_MODE & FC != 0 -> error message, busy movlw 0xFC ; three non-busy modes andwf RX_MODE,w ; don't change the RX_MODE bnz serial_nak_exit ; busy mode so reply NAK, we don't want data at the moment movlw UPPER serial_rx_jump_table movwf PCLATU movlw HIGH serial_rx_jump_table movwf PCLATH movf RX_MODE,w rlncf WREG,f rlncf WREG,f goto serial_rx_jump_table PAGE ; doing a jump table need to make sure it's all in one page of memory serial_rx_jump_table addwf PCL,f goto serial_rx_command ;0 goto serial_rx_length ;1 goto serial_rx_data ;2 goto serial_rx_checksum ;3 ;command byte serial_rx_command ; store the command movf RCREG,w movwf RX_COMMAND ; also store it to start the checksum movwf RX_CHECKSUM ; change to length mode incf RX_MODE,f ; return return ; length byte serial_rx_length ; set the byte count movf RCREG,w movwf RX_COUNT ; xor the length for checksum xorwf RX_CHECKSUM,f ; change to data mode incf RX_MODE,f ; if the byte count is 0 skip data mode movlw 0x00 cpfseq RX_COUNT return incf RX_MODE,f ; inc to checksum mode return serial_rx_data ; store the data byte in the indirect register movf RCREG,w movwf POSTINC0 ; update the checksum xorwf RX_CHECKSUM,f ; decrement the byte counter decfsz RX_COUNT,f return ; if execution got here the full count of bytes is done incf RX_MODE,f ; inc to checksum mode return serial_rx_checksum movf RCREG,w ; do an xor with the calculated checksum so far, should result in zero xorwf RX_CHECKSUM,f bz serial_rx_checksum_passed ; if execution got here the checksum failed xorwf RX_CHECKSUM,f ; return to the calculated checksum for debug bsf RX_FLAGS,CHECKSUM_FAIL ; set a flag incf RX_MODE,f ; switch to busy mode return serial_rx_checksum_passed ; checksum okay, set busy mode and exit incf RX_MODE,f return serial_nak_exit movlw 0x15 call serial_send return serial_rx_int_error bcf RCSTA,CREN ;clear the CREN bit to clear errors movlw 0x15 ;prepare a NAK symbol call serial_send ;and send it bsf RCSTA,CREN ;re-enable receiving movf RCREG,w ;have to read the RCREG to clear the interrupt return ; -- End of interrupt routines ------------------------------------------------ ;****************************************************************************** ;* * ;* serial_command_dispatch - checks command and branches to appropriate * ;* routine * ;* * ;* Called from the main loop to deal with any complete commands * ;* * ;****************************************************************************** serial_command_dispatch ; first check if there is a command btfss RX_MODE,2 ; if bit 2 is set (mode 4 == RX_BUSY) there is a command ready return ; otherwise nothing to do here ; next see if there was an error in the checksum btfsc RX_FLAGS,CHECKSUM_FAIL goto serial_error_checksum ; if there was report the error ; no error, so check the command is within bounds movlw 'A' subwf RX_COMMAND, f ; turn it to a jump number (A=0, B=1...Z=25) movlw d'26' cpfslt RX_COMMAND goto serial_error_bad_command ;command was not in range A-Z ; We know RX_COMMAND is a valid command now. Jump movlw UPPER serial_command_jump_table movwf PCLATU movlw HIGH serial_command_jump_table movwf PCLATH movf RX_COMMAND,w rlncf WREG,f rlncf WREG,f goto serial_command_jump_table PAGE serial_command_jump_table addwf PCL,f goto serial_error_unused_command ; A goto serial_error_unused_command ; B goto serial_error_unused_command ; C goto serial_error_unused_command ; D goto serial_error_unused_command ; E goto serial_error_unused_command ; F goto serial_error_unused_command ; G goto serial_error_unused_command ; H goto serial_error_unused_command ; I goto serial_error_unused_command ; J goto serial_error_unused_command ; K goto serial_error_unused_command ; L goto serial_error_unused_command ; M goto serial_error_unused_command ; N goto serial_error_unused_command ; O goto serial_error_unused_command ; P goto serial_error_unused_command ; Q goto serial_error_unused_command ; R goto serial_error_unused_command ; S goto serial_error_unused_command ; T goto serial_error_unused_command ; U goto serial_error_unused_command ; V goto serial_error_unused_command ; W goto serial_error_unused_command ; X goto serial_error_unused_command ; Y goto serial_error_unused_command ; Z ; -- serial command functions ------------------------------------------------- ; -- serial error functions --------------------------------------------------- serial_error_checksum ; report a message received with a bad checksum ; special first character because we don't know if the command was corrupt ; or data following it so start with a "0" ; set the send pointer to the start of the buffer clrf FSR1L movlw '0' movwf POSTINC1 movwf TX_CHECKSUM ; next is length byte. checksum fail message is always 3 bytes movlw 0x03 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now is the error code. checksum fail = 0x0001 movlw 0x00 movwf POSTINC1 xorwf TX_CHECKSUM,f movlw 0x01 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now send the calculated checksum for debugging movf RX_CHECKSUM,w movwf POSTINC1 xorwf TX_CHECKSUM,f ; now add the checksum movf TX_CHECKSUM,w movwf POSTINC1 ; packet assembled, send the packet call serial_tx_send_packet goto serial_handler_exit serial_error_bad_command ; report a bad command i.e. byte outside [A-Z] ; set the send pointer to the start of the buffer clrf FSR1L movlw '1' ; bad command so can't do the normal upper/lower conversion movwf POSTINC1 movwf TX_CHECKSUM ; next is length byte. always 3 bytes for bad command movlw 0x03 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now is the error code. bad command = 0x0002 movlw 0x00 movwf POSTINC1 xorwf TX_CHECKSUM,f movlw 0x02 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now send the bad command for debug movf RX_COMMAND,w addlw 'A' movwf POSTINC1 xorwf TX_CHECKSUM,f ; finally add the checksum movf TX_CHECKSUM,w movwf POSTINC1 ; packet assembled, send the packet call serial_tx_send_packet goto serial_handler_exit serial_error_unused_command ; valid command letter but not used ; set the send pointer to the start of the buffer clrf FSR1L ; command code is lowercase of the command for an error message movf RX_COMMAND,w addlw 'a' movwf POSTINC1 movwf TX_CHECKSUM ; now the length only the error code now movlw 0x02 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now the error code 0x0003 for unused command movlw 0x00 movwf POSTINC1 xorwf TX_CHECKSUM,f movlw 0x03 movwf POSTINC1 xorwf TX_CHECKSUM,f ; now the checksum movf TX_CHECKSUM,w movwf POSTINC1 call serial_tx_send_packet goto serial_handler_exit serial_handler_exit ; standard exit for all serial response functions clrf FSR0L ; reset RX pointer clrf RX_FLAGS ; get rid of any flags from this message clrf RX_MODE ; go back to waiting return delay clrf count clrf count+1 movlw 0x08 movwf count+2 delay_loop decfsz count,f goto delay_loop decfsz count+1,f goto delay_loop decfsz count+2,f goto delay_loop return end