;********************************************************************** ;* 8052.com Single Board Computer -- SBCMON Monitor Program * ;* ---> SBCMON MONITOR PROGRAM version 1.3.2 <--- * ;* For 8052.com SBC, hardware rev 1.3 * ;* * ;* 8052.com: http://www.8052.com * ;* Vault Information Services LLC: http://www.vaultbbs.com * ;* Author Contact: csteiner@8052.com * ;*--------------------------------------------------------------------* ;* This program is offered as-is with no warranty or guarantee of any * ;* kind. Its purpose is to serve as an educational aid in mastering * ;* the topics and concepts covered by the program. You are free to * ;* use this code for any purpose you see fit--including commercial-- * ;* but the exclusive responsibility for its use is asumed by the * ;* user. This code is offered completely free of charge and VIS' and * ;* 8052.com's liability will be limited to the amount paid: zero. * ;*--------------------------------------------------------------------* ;* This program was coded to be compatible with the Pinnacle 52 IDE. * ;* Pinnacle 52 is available at http://www.vaultbbs.com/pinnacle. The * ;* program may require minor modifications in order to assemble with * ;* other assemblers. * ;*====================================================================* ;* Filename: SBCMON.ASM * ;* Description: This is a monitor program designed to be used with * ;* the 8052.com SBC. For further information, please visit the * ;* http://www.8052.com/sbc/sbcmon. * ;*====================================================================* ;* Version History * ;* 1.3.0 (23/Jul/05): Initial public release. * ;* 1.3.1 (27/Aug/05): 1. Added a "CLR LCDDirect4" to the memory- * ;* mapped "M" mode so that the 4-bit mode is * ;* always cleared when memory-mapped mode is * ;* selected. This fixes a bug that prevented * ;* the LCD from being configured for memory- * ;* mapped mode in some conditions. * ;* 2. Removed code that was appending LF's to the * ;* CRs in some conditions. Since it was not * ;* appending LFs in all conditions, this was * ;* not desirable. In your terminal program * ;* configure it to "Apennd LFs to CRs" if * ;* lines appear on top of each other. * ;* 1.3.2 (28/Aug/05): 1. Fixed a problem in the GetKeyDebounced * ;* routine regarding incorrect address of * ;* memory-mapped keypad. * ;* 2. Modified GetKeyDebounced and WaitKeyReleased * ;* so that all registers are protected. * ;********************************************************************** ENABLE_ASM DEFINE 1 ;If defined, the mini-assembler is built into SBCMON ;Version constants: 1.3.2 = Hi=13h Low=02 SBCMONVERHI DEFINE 13 SBCMONVERLO DEFINE 02 ; Memory address constants -- points to areas of memory used ; as variables by the monitor AsmAddrHi EQU 0A0h ;High byte of next assembly language assemble address AsmAddrLo EQU 0A1h ;Low byte of next assembly language assemble address Param1Type EQU 0A2h ;Parameter 1 type Param1Hi EQU 0A3h ;Parameter 1 high byte (for numerics) Param1Lo EQU 0A4h ;Parameter 1 low byte (for numerics) Param2Type EQU 0A5h ;Parameter 2 type Param2Hi EQU 0A6h ;Parameter 2 high byte (for numerics) Param2Lo EQU 0A7h ;Parameter 2 low byte (for numerics) Param3Type EQU 0A8h ;Parameter 3 type Param3Hi EQU 0A9h ;Parameter 3 high byte (for numerics) Param3Lo EQU 0AAh ;Parameter 3 low byte (for numerics) KeypadDPH EQU 0ABh ;Last DPH used to access the keypad KeypadDPL EQU 0ACh ;Last DPL used to access the keypad AsmRRegister EQU 7Bh ;Hold the R register that was detected in this instruction AsmOpcode EQU 7Ch ;Holds the counter used to determine the opcode when searching OpTable AsmOpcodeParmCount EQU 7Dh ;Holds how many parameters this instruction SHOULD have AsmParmCount EQU 7Eh ;Parameter count AsmOpcodeIndex EQU 07Fh ;Holds the index of the opcode that was found CmdBuffer EQU 0B0h ;Points to where incoming input to the monitor is stored. CmdMaxBuffer EQU 0FFh ;Points to the last byte of the buffer ;Memory-mapped device address constants LCD_WR_COMMAND EQU 4000h LCD_WR_TEXT EQU 4001h LCD_RD_COMMAND EQU 4002h LCD_RD_TEXT EQU 4003h KEYPAD_ZONE EQU 5000h KEY_ROW0 EQU 500Eh KEY_ROW1 EQU 500Dh KEY_ROW2 EQU 500Bh KEY_ROW3 EQU 5007h ;Constant characters CR EQU 0Dh ;Carriage Return character BS EQU 08h ;Backspace character LF EQU 0Ah ;Linefeed character ESC EQU 27 ;Escape character CTRLX EQU 18h ;CTRL-X character ; Port Pin Assignments YellowLED EQU P1.0 ;Yellow LED pin SDA EQU P1.1 ;I2C SDA pin SCL EQU P1.2 ;I2C SCL pin SCLK EQU P1.7 ;SPI SCLK pin MISO EQU P1.6 ;SPI MISO pin MOSI EQU P1.5 ;SPI MOSI pin RS EQU P3.5 ;LCD Register Select pin when in direct-connect mode RW EQU P3.4 ;LCD Read/Write pin when in direct-connect mode EN EQU P3.3 ;LCD Enable pin when in direct-connect mode AT250X0_CS EQU P1.4 ;SPI chip select for the AT250X0 SPI EEPROM LCDPORT EQU P1 ;The byte-wide port for LCD data ;AT25010 command constants AT250X0_WREN EQU 06h AT250X0_RDSR EQU 05h AT250X0_WR EQU 02h AT250X0_RD EQU 03h AT250X0_STAT_BUSY EQU 01h AT250X0_STAT_WREN EQU 02h ;Bit setting variables LCDMMMode EQU 00h ;Bit 0. Clear=Memory-Mapped LCD, Set=Direct connect LCDDirect4 EQU 01h ;Bit 1. Clear=8-bit mode, Set=4-bit mode ;The following constants are used by the mini-assembler. PARM_C EQU 10h ;Parameter is 'C' (Carry bit) PARM_A EQU 20h ;Parameter is 'A' (Accumulator) PARM_IND_R0 EQU 30h ;Parameter is @R0 PARM_IND_R1 EQU 40h ;Parameter is @R1 PARM_Rx EQU 50h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R0 EQU 51h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R1 EQU 52h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R2 EQU 53h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R3 EQU 54h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R4 EQU 55h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R5 EQU 56h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R6 EQU 57h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_R7 EQU 58h ;Parameter is Rx, add "x" to current opcode, otherwise add 8 and continue PARM_ADPTR EQU 60h ;Parameter is @A+DPTR PARM_APC EQU 70h ;Parameter is @A+PC PARM_AB EQU 80h ;Parameter is AB PARM_DPTR EQU 90h ;Parameter is DPTR PARM_IND_DPTR EQU 0A0h ;Parameter is @DPTR PARM_NOBYTES EQU 0A1h ;Any parameter value less than this requires no operand output bytes ; The following parameters take 16-bit addresses as inputs PARM_2KADDR EQU 0B0h ;Parameter is a 2k jump/call (16-bit address) PARM_LADDR EQU 0B1h ;Parameter is a 16-bit jump/call (16-bit address) PARM_RELATIVE EQU 0B2h ;Parameter is a relative address (16-bit address) ;The following parameters take 8-bit values as inputs PARM_ADDR EQU 0B3h ;Parameter is a bit, IRAM, or SFR with just a hex address (8-bit address) PARM_INVADDR EQU 0C0h ;Parameter is /bit (8-bit address) ;The following takes 8-bit/16-bit values after a # PARM_DATA8 EQU 0D0h ;Parameter is a #data8 parameter PARM_DATA16 EQU 0D1h ;Parameter is #data16 ; PARM_NUMERIC EQU 0E0h ;Parameter detected was a numeric address PARM_DATA EQU 0F0h ;Parameter detecterd was a #value ;Mini-assembler constants for identifying instruction types numerically OP_INVALID EQU 00h OP_NOP EQU 01h OP_AJMP EQU 02h OP_LJMP EQU 03h OP_RR EQU 04h OP_INC EQU 05h OP_JBC EQU 06h OP_ACALL EQU 07h OP_LCALL EQU 08h OP_RRC EQU 09h OP_DEC EQU 0Ah OP_JB EQU 0Bh OP_RET EQU 0Ch OP_RL EQU 0Dh OP_ADD EQU 0Eh OP_JNB EQU 0Fh OP_RETI EQU 10h OP_RLC EQU 11h OP_ADDC EQU 12h OP_JC EQU 13h OP_ORL EQU 14h OP_JNC EQU 15h OP_ANL EQU 16h OP_JZ EQU 17h OP_XRL EQU 18h OP_JNZ EQU 19h OP_JMP EQU 1Ah OP_MOV EQU 1Bh OP_SJMP EQU 1Ch OP_MOVC EQU 1Dh OP_DIV EQU 1Eh OP_SUBB EQU 1Fh OP_MUL EQU 20h OP_UND EQU 21h OP_CPL EQU 22h OP_CJNE EQU 23h OP_PUSH EQU 24h OP_CLR EQU 25h OP_SWAP EQU 26h OP_XCH EQU 27h OP_POP EQU 28h OP_SETB EQU 29h OP_DA EQU 2Ah OP_DJNZ EQU 2Bh OP_XCHD EQU 2Ch OP_MOVX EQU 2Dh OP_DB EQU 2Eh OP_ORG EQU 2Fh ;========================================================== ; PROGRAM LOCATION ; ;The program is designed to be located at 0000h in program ;memory. The following lines locate the reset/startup ;vector at 0000h. The program then includes an entry for ;each possible ISR vector address. In each case, a jump to ;the vector address +2000h is included so that any ;interrupts are executed at 20003h, 200Bh, etc. instead of ;0003h, 000Bh, etc. ;=========================================================== ORG 0000h LJMP InitSBC ; ;INTERRUPT JUMP TABLE -- SBCMON doesn't use any interrupts ; but automatically jumps the address +8000h so that an ; external user program at 8000h-FFFFh can use interrupts. ; ORG 0003h ;External 0 Interrupt LJMP 8003h ORG 000Bh ;Timer 0 Interrupt LJMP 800Bh ORG 0013h ;External 1 Interrupt LJMP 8013h ORG 001Bh ;Timer 1 Interrupt LJMP 801Bh ORG 0023h ;Serial Port Interrupt LJMP 8023h ORG 002Bh ;Timer 2 Interrupt LJMP 802Bh ORG 0033h ;Unused in basic 8052 LJMP 8033h ORG 003Bh ;Unused in basic 8052 LJMP 803Bh ORG 0040h RETI ;A single RETI so that any interrupts that may have been ;triggered and not handled return from interrupt. ;========================================================== ; SBCMON ROUTINE ENTRY POINT VECTORS ; ;This is a sequence of LJMPs to useful routines in the ;SBCMON program. This is primarily so that programs that ;are loaded into external RAM with the 'L' command can ;call these routines without having to rewrite the code ;to perform these functions that already exist in the ;SBCMON firmware. ;=========================================================== ;Serial Routines LJMP SendSerial ;0041h LJMP SendSerialHexByte ;0044h LJMP SendSerialByte ;0047h LJMP GetSerialByte ;004Ah LJMP GetSerialLine ;004Dh ;LCD Routines LJMP SendLCDText ;0050h LJMP SendLCDCommand ;0053h ;I2C Routines LJMP SI2CIN ;0056h LJMP SI2COUT ;0059h LJMP SI2CRES ;005Ch LJMP SI2CSTA ;005Fh LJMP SI2CSTO ;0062h ;SPI Routines LJMP SPI_ReadByte ;0065h LJMP SPI_SendWait ;0068h ;General Library Routines LJMP ByteTo2Hex ;006Bh LJMP HexToNibble ;006Eh LJMP GetHexValue ;0071h LJMP GetSerialHex ;0074h LJMP ToUpper ;0077h ;Other routines LJMP SendDecimalR4567 ;007Ah LJMP DivideBy10 ;007Dh LJMP Check4BytesForZero ;0080h LJMP ShiftR765Left ;0083h LJMP AddR67to23 ;0086h LJMP AddR4567to0123 ;0089h ;Add on routines LJMP InitializeLCD ;008Ch LJMP ReadLCDStatus ;008Fh LJMP ReadLCDText ;0092h LJMP SetDptrMMKey1 ;0095h LJMP SetDptrMMKey2 ;0098h LJMP SetDptrMMKey3 ;009Bh LJMP SetDptrMMKey4 ;009Eh LJMP SetDptrMMLcdWC ;00A1h LJMP SetDptrMMLcdWT ;00A4h LJMP SetDptrMMLcdRC ;00A7h LJMP SetDptrMMLcdRT ;00AAh LJMP GetSBCMONVersion ;00ADh LJMP GetKeyDebounced ;00B0h LJMP WaitKeyReleased ;00B3h LJMP Make2DigitDecimal ;00B6h ;*********************************************************** ;* InitSBC: This routine is called immediately on power-up * ;* and initializes the SBC's stack pointer, establishes * ;* the baud rate, etc. * ;*********************************************************** InitSBC: MOV SP,#2Fh ;Stack pointer initialization past reg banks and bits MOV TMOD,#22h ;Timer 1 and timer 0in Auto-reload mode MOV TH1,#0FDh ;Reload value for 9600 baud @ 11.059 Mhz SETB TR1 ;Turn on timer 1 for baud rate generator MOV SCON,#52h ;Receiver Enable/Timer 1 Baud Rate 8-bit CLR EN ;Make sure the LCD is not enabled by default ;Delay 600ms to make sure that the AT89S8252 is not going to ;access the SPI lines before it has a chance to reset and avoid ;the problem described in the Atmel errata. The pause consists ;of 6 100ms pauses and the LED on P1.0 is flashed after each; ;pause. This causes the yellow LED to flash on and off briefly ;when SBCMON is powered up. CLR YellowLED ;Turns yellow led on LCALL Delay100ms SETB YellowLED ;Turns yellow led off LCALL Delay100ms CLR YellowLED ;Turns yellow led on LCALL Delay100ms SETB YellowLED ;Turns yellow led off LCALL Delay100ms CLR YellowLED ;Turns yellow led on LCALL Delay100ms SETB YellowLED ;Turns yellow led off LCALL Delay100ms ;Initialize SBC parameters LCALL DetectRAM ;Detect the amount of RAM, returns the base address of XRAM MOV B,A ;Save it for later MOV R0,#AsmAddrHi ;Point to high byte of asm address MOV @R0,A ;Set high byte to address returned by DetectRAM INC R0 ;Point to low byte of asm address MOV @R0,#0 ;Point to beginning of XRAM/CODE area ;Send initialization message LCALL SendSerial DB 13,"SBCMON v",0 ;Display the SBCMON version number in format "x.y.z" based on ;SBCMONVERHI and SBCMONVERLO. MOV A,#(SBCMONVERHI/10+'0') LCALL SendSerialByte MOV A,#'.' LCALL SendSerialByte MOV A,#(SBCMONVERHI % 10+'0') LCALL SendSerialByte MOV A,#'.' LCALL SendSerialByte MOV A,#(SBCMONVERLO % 10+'0') LCALL SendSerialByte LCALL SendSerial DB " Initialized.",13,0 MOV A,B ;Restore base address CJNE A,#0FFh,ReportRAM ;If there's RAM, report it LCALL SendSerial DB "(No XRAM was detected!)",13,0 LJMP SBCMain ReportRAM: LCALL SendSerial DB "(Detected ",0 MOV A,R1 ;Get amount of XRAM LCALL SendSerialHexByte ;Display it as hex value LCALL SendSerial DB "k of XRAM at ",0 MOV A,B ;Get base address of XRAM LCALL SendSerialHexByte ;Display it as hex value LCALL SendSerial DB "00h)",13,0 ;*********************************************************** ;* SBCMain: The main menu routine where the SBC prompts for* ;* a command, receives the command, and branches to the * ;* right routine based on the command that was entered. * ;*********************************************************** SBCMain: MOV P1,#0FFh ;Make sure P1 is all high so ISP can work on AT89S8252 LCALL SendSerial DB 13,"SBC Ready> ",0 LCALL GetSerialLine ;Get a line of input, ACC holds first character of command MOV R7,A ;Save first character of line in R7 MOV DPTR,#CmdTable ;Point to the command table CmdLoop: CLR A ;Clear offset to 0 MOVC A,@A+DPTR ;Get the command character from the table JZ CmdNotFound ;If 0 then end of table, command not found XRL A,R7 ;XRL it with the command that was entered JZ CmdFound ;If same then command was found INC DPTR ;Increment DPTR 4 times to point to next entry INC DPTR INC DPTR INC DPTR SJMP CmdLoop ;Look for next command entry CmdFound: INC DPTR ;Point to first byte of LJMP instruction CLR A ;Make sure offset is zero JMP @A+DPTR ;Jump to the command's LJMP instruction, R7 holds command CmdNotFound: LCALL SendSerial DB "Invalid command: ",0 LCALL DumpCmdBuffer ;Echo it back LJMP SBCMain ;Get next line of input CmdTable: DB '?' ;Display help menu LJMP CmdHelp ifdef ENABLE_ASM DB 'A' ;Execute the mini-assembler LJMP CmdAssembler endif DB 'I' ;Display/Modify IRAM LJMP CmdIram DB 'X' ;Display/Modify XRAM LJMP CmdXram DB 'R' ;Run code at specified address LJMP CmdRunCode DB 'L' ;Load HEX file into XRAM LJMP CmdLoadHEXFile DB 'C' ;Read/Set DS1307 RTC LJMP CmdRTC DB 'V' ;Verify RAM LJMP CmdVerifyRAM DB 'w' ;Write command to LCD LJMP CmdWriteLCDCommand DB 'W' ;Write text to LCD LJMP CmdWriteLCDText DB 'M' ;Set LCD Mode and/or initialize LCD LJMP CmdLCDMode DB 'E' ;EEPROM Routine LJMP CmdEeprom DB 'K' ;Keypad Routine LJMP CmdKeypadTest DB 'Q' ;Quick Load & Run routine LJMP CmdQuickLoadAndRun DB 0 ;***************************************************************** ;* Command '?': Help menu * ;* Function: Displays a list of all the commands that SBCMON * ;* recognizes. * ;***************************************************************** CmdHelp: LCALL SendSerial DB 13, "SBC Command Summary:",13,13 DB "? - This help menu", 13 ifdef ENABLE_ASM DB "Axxxx - Start assembling code at xxxx", 13 endif DB "C[ss nn hh w dd mm yy] - Read DS1307 RTC [or set]", 13 DB "E[text] - Read AT25010A memory [or write text]",13 DB "Ixx[,yy][=zz[zz zz zz]] - Display IRAM xx to yy (or set xx to zz)", 13 DB "K[D] - Keypad test [KD=Disable debounce]", 13 DB "L - Load HEX file into XRAM", 13 DB "M[0|1|2][I] - Display mode, 0=Memory mapped, 1=Direct 8-bit, 2=Direct 4-bit, I=Initialize", 13 DB "Rxxxx - Execute code at xxxx", 13 DB "V[F] - Quick test of XRAM 2000h - 3FFFh (VF=Full test)", 13 DB "W[Text] - Write text to LCD", 13 DB "w[aa bb cc] - Write commands aa,bb,cc, etc. to LCD", 13 DB "Xxxxx[,yyyy][=zz[zz zz zz]] - Display XRAM xxxx to yyyy (or set xxxx to zz)", 13 DB "Note: ESC at beginning of line repeats last command", 13 DB 0 LJMP SBCMain ;***************************************************************** ;* Command 'K': Test memory-mapped keypad * ;* Function: Constantly reads and debounces the keypad and echos * ;* the keypresses to the serial port. The loop is ended by * ;* sending anything to the serial port. The code will * ;* use a 1/20th of a second debounce routine--this debounce * ;* can be disabled by entering anything else on the command * ;* line after the command itself; such as 'KD' to run the * ;* keypad test without debounce. * ;***************************************************************** CmdKeypadTest: CLR RI ;Clear all input at the beginning LCALL SendSerial DB 13,"Keypad test: Will echo keypresses to serial port",13 DB "Press any key on PC keyboard to terminate",13,0 MOV R2,#01h ;Default to debounce on MOV R0,#CmdBuffer + 1 ;Point to the first character after 'X' MOV A,@R0 ;Get the first character, if null (0) then debounce on, != debounce off JZ KP_Loop MOV R2,#00h ;Debounce turned off LCALL SendSerial DB "NOTE: DEBOUNCE DISABLED!",13,0 KP_Loop: MOV R3,#03h KP_Cycle: ;Check to see if a key has been pressed on the PC. If it hasn't then ;continue JNB RI,KP_ScanRow LJMP SBCMain ;If we get here then that means a key was pressed ;on the PC and received by the MCU serial port, so ;we go back to main KP_ScanRow: ;We must first set DPTR to an address that corresponds to the address ;of the keypad row we wish to read. R3 holds the row we want to read ;so we read the high byte of DPTR (DPH) out of the KP_AddrTable. We ;don't care what the low byte of DPTR (DPL) is since any value of DPL ;will still address the right line of the keypad. MOV A,R3 ;Get current row (0-3) MOV DPTR,#KP_AddrTable ;Point to the address table MOVC A,@A+DPTR ;Get the DPTR low byte from the table MOV DPTR,#KEY_ROW0 ;Set DPTR keypad address MOV DPL,A ;Move to low byte DPL, DPH already set ;We now have the memory address for the keypad row we wish to access. So ;we now read it into the accumulator with the MOVX command. MOVX A,@DPTR ;Read the status of the keypad row ANL A,#0Fh ;Only interest in low 4 bits XRL A,#0Fh ;We now invert all bits, so pressed key=1, not-pressed=0 JNZ KP_ProcessKey ;If the value is not zero then a key was pressed, so process it DEC R3 ;Decrement row counter CJNE R3,#0FFh,KP_Cycle ;If it hasn't decremented to FF then process next row SJMP KP_Loop ;Once we've checked all four rows, start with row 3 again KP_ProcessKey: MOV R1,A ;Hold the keypress in R1 CJNE R2,#01h,KP_DebounceDone ;If debounce disabled, don't do debounce ;If we get here it means a key was pressed. The first thing we need to do is debounce it. ;We do this by re-reading the same row for 1/20th of a second. If the value does not ;change for 1/20th of a second then we assume it's a valid keypress. Otherwise we jump ;out back to the keypad scan routine. 1/20th of a second at 11.0592MHz is about ;46,080 instruction cycles MOV R6,#23 ;23 * 256 = 7680 * 8 = approx. 46,080 cycles = Approx 1/20th second KP_DebounceLoop2: MOV R7,#00h ;Set R7 to 0 for debounce loop (256 loops) KP_DebounceLoop: MOVX A,@DPTR ;Re-read the same keypad row 2 ANL A,#0Fh ;Only interest in low 4 bits XRL A,#0Fh ;Invert all the bits 1 XRL A,R1 ;Exclusive OR with the original keypress 1 JNZ KP_Loop ;If new value not same as last value, debounce it! 2 DJNZ R7,KP_DebounceLoop ;Loop R7 times 2 DJNZ R6,KP_DebounceLoop2 ;Loopr R6 times KP_DebounceDone: ;If we get here then the key has been debounced, so we need to decide what key ;was pressed. The value of R1 will either be 1 (first key), 2 (second key), ;4 (third key), or 8 (fourth key). So we set R0=0 and increment R0 once each ;time we shift the value right until the carry bit is set, then we're done and ;R0 will hold 0, 1, 2 or 3 for which key in the row was pressed. CLR C ;Make sure carry starts clear MOV A,R1 ;Accumulator gets copy of the value read MOV R0,#00h ;R0 starts at zero KP_FindKeyNum: RRC A ;Rotate the value right into the carry JC KP_KeyFound ;If it's set then we found our pressed key and exit INC R0 ;Increment the key number indicator SJMP KP_FindKeyNum ;Keep searching until we find the key KP_KeyFound: ;First we protect DPTR so we can restore the keypad read address later PUSH DPL PUSH DPH ;We now have R0 with the offset for the key (0, 1, 2, or 3). And R3 holds the ;row number. So we take the row number (R3) multiply it by 4 (two rotates to the ;left) and then add R0. We then get the key from the offset in the KeyTable. MOV A,R3 ;Get row number RL A ;Multiply by 2 RL A ;Multiply by 4 ADD A,R0 ;Add the key offset MOV DPTR,#KP_KeyTable ;Get the start address of the key table MOVC A,@A+DPTR ;Get the key ;Accumulator now has the key we pressed, so we send it to the serial port LCALL SendSerialByte ;We now go back to reading the same row of the keypad and wait until it ;goes back to FF which represents no keys pressed. POP DPH ;Restore the DPTR for accessing the keypad POP DPL CJNE R2,#01h,KP_Done ;If debounce disabled, don't do debounce KP_Wait: MOVX A,@DPTR ;Read the keypad ANL A,#0Fh ;Only interest in low 4 bits CJNE A,#0Fh,KP_Wait ;If it's not 0Fh then keep waiting KP_Done: LJMP KP_Loop ;Wait for another keypad press KP_KeyTable: DB "ABCD" DB "369#" DB "2580" DB "147*" KP_AddrTable: DB 0Eh ;Address to access row 0 (500Eh) DB 0Dh ;Address to access row 1 (500Dh) DB 0Bh ;Address to access row 2 (500Bh) DB 07h ;Address to access row 3 (5007h) ;***************************************************************** ;* Command 'E': EEPROM Communication * ;* Function: Reads or writes to the AT25010A serial EEPROM using * ;* SPI protocol. * ;***************************************************************** CmdEeprom: ; Check to see if we're reading the EEPROM or writing to it MOV B,#00h ;Read/Write Address 0 of EEPROM MOV R0,#CmdBuffer + 1 ;Point to second character of command buffer MOV A,@R0 ;Get next character in buffer JZ CE_Read ;Nothing but an 'E' on the line so we're reading ;This means we're writing data to the serial EEPROM SETB P3.2 ;Make sure EEPROM write-protect is NOT set CE_WLoop: PUSH ACC ;Protect current character to write LCALL AT25_WriteByte ;Write it to EEPROM POP ACC ;Restore the character we just wrote JZ CE_WDone ;If we just sent a null then we're done INC B ;Point to next address in EPROM INC R0 ;Point to next data in buffer MOV A,@R0 ;Get next byte in buffer SJMP CE_WLoop ;Send next byte CE_WDone: LCALL SendSerial DB "Wrote to EEPROM",13,0 CLR P3.2 ;Set EEPROM write protect LJMP SBCMain ;Go back to main menu CE_Read: ;This means we're reading data from the serial EEPROM LCALL SendSerial DB "Reading EEPROM: ",0 CE_RLoop: LCALL AT25_ReadByte ;Get the value from EEPROM JZ CE_RDone ;If null then we're done LCALL SendSerialByte ;Send the byte to the serial port INC B ;Point to next address in EEPROM MOV A,B ;Get new value of address CJNE A,#00h,CE_RLoop ;Display a maximum of 255 characters CE_RDone: LCALL SendSerial DB 13,0 LJMP SBCMain ;Go back to main menu ;Reads the current status of the AT250X0 serial EEPROM AT25_GetStatus: CLR AT250X0_CS ;Select the AT250X0 MOV A,#AT250X0_RDSR ;Set Read Status Register command LCALL SPI_SendWait ;Send byte MOV A,#0FFh ;Send dummy byte LCALL SPI_SendWait ;Send byte SETB AT250X0_CS ;Deselect the AT250X0 RET ;Enables write to the AT250X0 serial EEPROM AT25_WriteEnable: CLR AT250X0_CS ;Select the AT250X0 MOV A,#AT250X0_WREN ;Enable writing LCALL SPI_SendWait ;Send command wait for response SETB AT250X0_CS ;Deselect the AT250X0 RET ; Writes the value in ACC to address B in AT250X0 memory AT25_WriteByte: PUSH ACC ;Push accumulator which holds byte to write AT25_Loop: LCALL AT25_GetStatus ;Get current status of AT250X0 ANL A,#AT250X0_STAT_BUSY ;Only interested in the busy bit JNZ AT25_Loop ;As long as it's still busy, keep waiting ;Chip is not busy so go ahead and write byte LCALL AT25_WriteEnable ;Make sure AT250X0 is not write protected CLR AT250X0_CS ;Select the AT250X0 MOV A,#AT250X0_WR ;Write command LCALL SPI_SendWait ;Send the byte and wait MOV A,B ;Load the accumulator with address which is in B LCALL SPI_SendWait ;Send the byte and wait POP ACC ;Restore accumulator which holds byte to write LCALL SPI_SendWait ;Send the byte and wait SETB AT250X0_CS ;Deselect the AT250X0 RET ; Reads address B from AT250X0 memory into accumulator AT25_ReadByte: CLR AT250X0_CS ;Select the AT250X0 MOV A,#AT250X0_RD ;Read command LCALL SPI_SendWait ;Send the byte and wait MOV A,B ;Load the accumulator with address which is in B LCALL SPI_SendWait ;Send the byte and wait MOV A,#0FFh ;Dummy value to clock in data LCALL SPI_SendWait ;Send byte and get response SETB AT250X0_CS ;Deselect the AT250X0 RET ;***************************************************************** ;* Command 'M': Set LCD Mapping Configuration and Initialize * ;* Function: Receives a command such that the first character * ;* after the command is either 0 (direct connect mode) or * ;* 1 (memory-mapped mode). Optionally, the letter 'I' may * ;* follow the 0 or 1 to instruct the SBC to initialize the * ;* LCD. * ;***************************************************************** CmdLCDMode: MOV R0,#CmdBuffer + 1 ;Point to next character in buffer MOV A,@R0 ;Get it CLR LCDMMMode ;Default to clearing the LCD mode to memory-mapped CLR LCDDirect4 ;(v1.3.1) - Make sure 4-bit mode is turned off CJNE A,#'1',CmdMSkipSet ;If it's anything but '1' then memory-mapped it is ;It was mode '1' so set to direct mode, 8-bit mode CLR LCDDirect4 ;Default to 8-bit mode if in direct mode SETB LCDMMMode ;If it was '1' then set LCD to direct mode SJMP CmdMFinish CmdMSkipSet: CJNE A,#'2',CmdMFinish ;If it's not '2' then it is memory-mapped ;It ws mode '2' so set the direct mode and 4-bit mode SETB LCDDirect4 ;2=4-bit direct SETB LCDMMMode ;Set direct mode CmdMFinish: LCALL SendSerial DB "LCD mode set to ",0 LCALL DisplayLCDMode ;Display "direct connect" or "memory mapped" ;See if there is an 'I' after the 0/1 character. If so we need to ;initialize the LCD. MOV R0,#CmdBuffer + 2 ;Get second character of command MOV A,@R0 ;Get the character CJNE A,#'I',CmdMDone ;If not "I" then continue LCALL SendSerial DB "Initializing LCD",13, 0 LCALL InitializeLCD ;Initialize the LCD in new mode LCALL ClearLCD ;Clear the LCD display CmdMDone: LJMP SBCMain ;Display whether the LCD is in memory-mapped or direct connect ;mode based on the status of the LCDMMMode bit. DisplayLCDMode: JB LCDMMMode,DLM_Direct ;If set then we're in direct-connect mode LCALL SendSerial DB "memory-mapped",13,0 RET DLM_Direct: LCALL SendSerial DB "direct connect ", 0 MOV A,#'8' JNB LCDDirect4,DLM_Direct2 MOV A,#'4' DLM_Direct2: LCALL SendSerialByte LCALL SendSerial DB "-bit",13,0 RET ;***************************************************************** ;* Command 'W': Write text to LCD * ;* Function: Writes the text that immediately follows the * ;* command to the LCD. * ;***************************************************************** CmdWriteLCDText: LCALL SendSerial DB "Writing text to LCD ", 0 LCALL DisplayLCDMode ;Display "direct connect" or "memory mapped" MOV R0,#CmdBuffer + 1 ;Point to first character of text in buffer CmdW_Loop: MOV A,@R0 ;Get next character from the command buffer JZ CmdW_Done ;If it's a null then we're done LCALL SendLCDText ;Otherwise write it the LCD INC R0 ;Point to the next character in buffer SJMP CmdW_Loop ;Loop through all the characters CmdW_Done: LCALL SendSerial DB "LCD Write complete", 13, 0 LJMP SBCMain ;Go back to main menu ;***************************************************************** ;* Command 'w': Write Command to LCD * ;* Function: Writes the command(s) that immediately follows the * ;* command to the LCD. For example to clear LCD: w01 * ;***************************************************************** CmdWriteLCDCommand: LCALL SendSerial DB "Writing commands to LCD ", 0 LCALL DisplayLCDMode ;Display "direct connect" or "memory mapped" MOV R0,#CmdBuffer + 1 ;Point to first character of text in buffer Cmdw_Loop: MOV R2,#00h ;Clear R2, used to hold the decoded value of the LCD command MOV R3,#00h ;Clear R3, used to hold the decoded value of the LCD command MOV R1,#02h ;Point to "start address" for decoded value LCALL GetHexValue ;Decode the next hex value into R2/R3 JC CmdW_Done ;If we had a hex decode error then done setting clock MOV A,R3 ;Get the low byte of the decoded value LCALL SendLCDCommand ;Send it to the LCD as a command INC R0 ;Point to the next byte in the buffer SJMP Cmdw_Loop ;Process the next command in the buffer, if any ;***************************************************************** ;* Command 'V': Verify XRAM Memory 8000h-FFFFh * ;* Function: Verifies that external RAM memory from 2000h-3FFFh * ;* is able to store and recall data correctly. With no * ;* parameters the verification will be a "quick test" which * ;* is generally sufficient. If there are any doubts about * ;* memory a full test can be executed with the VF command * ;* which will store every value between 00 and FF in every * ;* address between 8000h and FFFFh and make sure it reads * ;* back correctly. Actually, the full test will be used if * ;* anything at all follow the 'V' on the command line--not * ;* just 'VF'. * ;***************************************************************** CmdVerifyRAM: CLR RI ;Clear RI so it can be aborted later MOV R7,#10h ;Start at 10h (test values 10h down to 00h) MOV R0,#CmdBuffer + 1 MOV A,@R0 ;Get the next character in the command buffer JNZ CV_FullTest ;If anything besides just 'V' then do full test LJMP CV_TestLoop ;Go to the quick test CV_FullTest: MOV R7,#00h ;Start test at zero (test values FFh down to 00h) CV_TestLoop: MOV DPTR,#8000h CV_XRAMWrite: JNB RI,CV_NoCancel1 ;If no key pressed, keep going LJMP CV_Cancel CV_NoCancel1: MOV A,R7 ;R7 holds current test value MOVX @DPTR,A ;Move it to the current DPTR XRAM INC DPTR ;Increment DPTR MOV A,DPH ;Get high byte of DPTR CJNE A,#00h,CV_XRAMWrite ;If not 40h then keep writing ;Now we've loaded 2000h-3FFFh with specific value. We now ;repeat the loop, read back, and make sure it's the same ;value we wrote. MOV DPTR,#8000h ;Reset DPTR to beginning of XRAM CV_XRAMRead: JNB RI,CV_NoCancel2 ;If no key pressed, keep going LJMP CV_Cancel CV_NoCancel2: MOVX A,@DPTR ;Read that value of XRAM CJNE A,07h,CV_BadRead ;Error, value is wrong, so report it CV_ReadContinue: INC DPTR ;Increment DPTR MOV A,DPH ;Get high byte of DPTR CJNE A,#00h,CV_XRAMRead ;If not 40h then keep reading ;If we got here it means that the current cyclced worked. So ;repeat with the next R7 test value DJNZ R7,CV_TestLoop ;Repeat the test with next test value ;When we get here it means we did the entire test ok LCALL SendSerial DB "XRAM tested ok 8000h-FFFFh",13,0 LJMP SBCMain CV_BadRead: PUSH ACC ;Save the value we got back MOV R2,DPH ;Save DPTR High MOV R3,DPL ;Save DPTR low LCALL SendSerial DB "XRAM Error[",0 MOV A,R2 ;Get high byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it MOV A,R3 ;Get low byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB "]=",0 POP ACC ;Get the value we read LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB " (should have been ", 0 MOV A,R7 ;Get current value of LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB ")",13, 0 MOV DPH,R2 ;Restore DPTR High MOV DPL,R3 ;Restore DPTR low LJMP CV_ReadContinue ;Continue with test CV_Cancel: CLR RI ;We're not interested in what they pressed LCALL SendSerial DB "XRAM test aborted", 13,0 LJMP SBCMain ;***************************************************************** ;* Command 'C': Set/Read DS1307 RTC * ;* Function: Allows the user to either set the RTC or read the * ;* current time from the RTC. If the 'C' command is issued * ;* alone then the clock will be read. Otherwise, the * ;* rest of the line will be read as a sequence of BCDs in * ;* this format: * ;* Css nn hh d dd mm yy * ;* ss=Seconds (00-59) dd=Day of month (01-31) * ;* nn=Minutes (00-59) mm=Month (01-12) * ;* hh=Hous (00-23) yy=Year (00-99) * ;* d=Day of week (1-7) * ;***************************************************************** CmdRTC: LCALL SI2CRES ;Reset I2C Bus LCALL SI2CSTA ;Send start MOV A,#0D0h ;DS1307 RTC Write address = D0h CLR C LCALL SI2COUT ;Send command to DS1307 JC RRNoAck ;If no ack then communication failed MOV A,#00h ;Set address pointer to first byte CLR C LCALL SI2COUT ;Send address pointer to RTC ;Check to see if we're reading the clock or setting it ;If the next byte after the 'C' command is a NULL then we ;are *reading* the RTC--otherwise we're writing to it. MOV R0,#CmdBuffer + 1 ;Point to second character in command buffer MOV A,@R0 ;Get next character in buffer JZ RTCRead ;If it's a NULL then we're reading the RTC LJMP RTCSet ;If there is more on this line then set the RTC RRNoAck: LCALL SendSerial ;Report error condition DB "No reply from DS1307 IC", 13,0 LJMP SBCMain ;Go back to main menu RTCFaultZ: LJMP RTCFaultZ ;Springboard to RTCFault RTCRead: LCALL SI2CSTA ;Send start MOV A,#0D1h ;DS1307 Read address = A3h CLR C LCALL SI2COUT ;Send read command JC RTCFaultZ LCALL SendSerial DB "Secs: ", 0 CLR C ;Get seconds LCALL SI2CIN ANL A,#7Fh ;Only interested in low 7 bits LCALL SendSerialHexByte LCALL SendSerial DB 13,"Mins: ", 0 CLR C LCALL SI2CIN ;Get minutes ANL A,#7Fh LCALL SendSerialHexByte LCALL SendSerial DB 13,"Hour: ", 0 CLR C LCALL SI2CIN ;Get hour ANL A,#3Fh LCALL SendSerialHexByte LCALL SendSerial DB 13,"Wday: ", 0 CLR C LCALL SI2CIN ;Get day of week LCALL SendSerialHexByte LCALL SendSerial DB 13,"Day: ",0 CLR C LCALL SI2CIN ;Get Day ANL A,#3Fh LCALL SendSerialHexByte LCALL SendSerial DB 13,"Month: ",0 CLR C LCALL SI2CIN ;Get month ANL A,#1Fh LCALL SendSerialHexByte LCALL SendSerial DB 13,"Year: ",0 SETB C LCALL SI2CIN ;Get year LCALL SendSerialHexByte LCALL SI2CSTO ;Send I2C stop comand LJMP SBCMain RTCFault: LCALL SendSerial ;Report an I2C communication fault DB "I2C Comm Fault", 13, 0 LJMP SBCMain RTCSet: LCALL SendSerial DB "Setting RTC", 13,0 MOV R7,#07h ;We're going to read and write 7 bytes RTCSetLoop: MOV R2,#00h ;Clear R2, used to hold the decoded BCD to be sent to RTC MOV R3,#00h ;Clear R3, used to hold the decoded BCD to be sent to RTC MOV R1,#02h ;Point to "start address" for decoded value LCALL GetHexValue ;Get the hex value into R2/R3 JC RTCSetDone ;If we had a hex decode error then done setting clock MOV A,R3 ;Get the low byte of the decoded value CJNE R7,#07h,RTCSkipSec ;If this isn't the second register then continue ANL A,#7Fh ;If seconds register then always set clear bit to start clock RTCSkipSec: LCALL SI2COUT ;Send the BCD to the DS1307 JC RTCFault ;If there was an I2C communication problem, report it DJNZ R7,RTCSetLoop ;Loop through each of the 7 bytes RTCSetDone: LCALL SI2CSTO ;Send I2C stop comand LCALL SendSerial DB "DS1307 RTC was set", 13,0 LJMP SBCMain ;Go back to main menu ;***************************************************************** ;* Command 'L': Load HEX File into XRAM * ;* Function: Allows the user to send a HEX file to SBCMON in * ;* normal test format. This can be sent to the SBC as a text* ;* file or copy/pasted. The SBC will attempt to load the * ;* contents of the HEX file at the addresses indicated by * ;* the hex file. Normally this will only work from * ;* 2000h-3FFFh which is where the 6264 RAM IC is located in * ;* memory. This allows up to 8k to be loaded in this * ;* manner. * ;***************************************************************** CmdLoadHEXFile: LCALL HandleHexFile ;Get the HEX file and load it into memory LJMP SBCMain ; This is the actual routine that receives the HEX file, sets carry ; if there was an error and clears it if there was no error. HandleHexFile: MOV R5,#00h ;Byte count low byte MOV R4,#00h ;Byte count high byte LCALL SendSerial DB "Send Intel HEX file in text format now, CTRL-X to abort", 13,0 HexGetLine: LCALL GetSerialByte ;Get the next byte CJNE A,#CTRLX,HexNotCtrlX ;If it's not CTRL-X continue LCALL SendSerial ;CTRL-X, so we abort transfer DB "Intel HEX file upload was aborted with CTRL-X", 13,0 SETB C ;Flag error RET Hex_Error: LCALL SendSerial ;Report bad HEX file data DB "Intel HEX file contained invalid data, aborted",13,0 SETB C ;Flag error RET HexNotCtrlX: CJNE A,#':',HexGetLine ;If it wasn't a colon then we keep waiting. ;If we get here it means we got the first colon in the line LCALL GetSerialHex ;Get the number of data bytes in this line JC Hex_Error ;Error, exit out MOV R7,A ;Move # of data bytes to R7 ;Add number of bytes to byte count ADD A,R5 MOV R5,A MOV A,R4 ADDC A,#00h MOV R4,A ;Now we get two bytes which is the address. This is loaded directly into ;DPTR. LCALL GetSerialHex ;Get address high byte JC Hex_Error ;Error, exit out MOV DPH,A ;Store the high byte of address LCALL GetSerialHex ;Get address low byte JC Hex_Error ;Error, exit out MOV DPL,A ;Store the low byte of address ;Now we get 1-byte "record type" and store it in R6 LCALL GetSerialHex ;Get record type byte JC Hex_Error ;Error, exit out MOV R6,A ;Store record type in R6 ;If this isn't a data record we skip data bytes CJNE R6,#00h,HexSkipData ;Data record is 00, if not 00 then skip data HexGetData: LCALL GetSerialHex ;Get next data byte JC Hex_Error ;Error, exit out MOVX @DPTR,A ;Store it in XRAM INC DPTR ;Point to next byte in XRAM DJNZ R7,HexGetData ;Cycle getting each byte of data in line HexSkipData: ;Wait for end of line LCALL GetSerialByte ;Get a serial byte CJNE A,#CR,HexCheckLF ;If not EOL, keep waiting SJMP HexEOL ;CR found HexCheckLF: CJNE A,#LF,HexSkipData ;Not CR nor LF HexEOL: ;Now we have an EOL. If the record type was NOT 01 (end of file) ;then get another line CJNE R6,#01h,ZHexGetLine ;Not EOL so keep going ;Upload complete! LCALL SendSerial DB "Intel HEX file upload complete.", 13,0 MOV A,R4 MOV R6,A MOV A,R5 MOV R7,A MOV R4,#00h MOV R5,#00h LCALL SendDecimalR4567 LCALL SendSerial DB " bytes loaded.",13,0 CLR C RET ZHexGetLine: LJMP HexGetLine ;***************************************************************** ;* Command 'Q': Quick-Load and Run @ 2000h * ;* Function: This combines the 'L' instruction to load a HEX file* ;* and the 'R8000' instruction to execute code at 8000h in a * ;* single command. Since most code loaded in SBCMON will be * ;* located at 8000h this provides a convenient way to quickly* ;* load and run a HEX file in a single step. * ;***************************************************************** CmdQuickLoadAndRun: LCALL HandleHexFile ;Get the HEX file and load it into memory JC Q_Exit ;There was an error so don't execute code ;Execute code at 8000h LCALL SendSerial DB "Executing code at 8000h",13,0 LCALL 8000h ;Call LJMP CmdRReturn ;Display "returned" message Q_Exit: LJMP SBCMain ;***************************************************************** ;* Command 'R': Runs code at the specified address * ;* Function: Takes the following parameter on the command line * ;* and starts executing code at that address. For example, * ;* R2000 will execute code at 2000h. Note that the code is * ;* executed via the equivalent of an LCALL instruction which * ;* means the code that is executed should terminate with a * ;* RET instruction to return to the main SBCMON menu. * ;***************************************************************** CmdRunCode: LCALL SendSerial DB "Executing code at ", 0 MOV R0,#CmdBuffer +1;Point to next address MOV R1,#02h ;Point to "start address" for decoded value LCALL GetHexValue ;Get the hex value into R2/R3 MOV A,R2 ;Get high byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it MOV A,R3 ;Get low byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB 13,0 MOV DPTR,#CmdRReturn;Return address that called code will RETurn to PUSH DPL ;Push the return address that RET will return PUSH DPH ;Push the return address MOV DPH,R2 ;Load high byte MOV DPL,R3 ;Load low byte CLR A ;Clear accumulator JMP @A+DPTR ;Jump to the code in question CmdRReturn: ;The code that is called should return with "RET" which ;will return here LCALL SendSerial DB 13,"Code execution complete", 13,0 LJMP SBCMain ;***************************************************************** ;* Command 'X': Read/Modify XRAM/IRAM memory * ;* Function: The first parameter is the XRAM address. If the * ;* next character is a comma then the command will display * ;* all the XRAM between those two addresses. If the next * ;* character is an equals sign then it will set the specified* ;* XRAM address to the value that follows. Examples: * ;* X2000 Display XRAM 2000h * ;* X2000,200F Display XRAM from 2000h to 20FFh * ;* X2000=25 Sets XRAM address 2000h to 25h * ;* X2000=10,25,30 Sets XRAM 2000h to 10h, 2001h to * ;* 25h, and 2002h to 30h * ;* The command also handles Internal RAM. If the command * ;* used to call the routine is 'I' then all the addresses * ;* refer to internal RAM rather than XRAM. * ;***************************************************************** CmdXram: CmdIram: SETB B.0 ;If B0.0 is set then working with XRAM CJNE R7,#'I',CX2 ;If command wasn't 'I' then we *are* working with XRAM CLR B.0 ;If 'I' then display IRAM, so clear B0.0 CX2: MOV R0,#CmdBuffer + 1 ;Point to the first character after 'X' or 'I' CLR A ;Clear accumulator and other 'R' registers that follow MOV R2,A ;R2=High byte of start address MOV R3,A ;R3=Low byte of start address MOV R4,A ;R4=High byte of end address MOV R5,A ;R5=low byte of end address MOV R6,A ;R6=Value to write to byte MOV R7,A ;R7=Low byte of value to write to XRAM CmdXLoop: MOV R1,#02h ;Point to "start address" for decoded value LCALL GetHexValue ;Get the hex value into R2/R3 CJNE A,#',',CmdXNotComma ;No comma was found so we don't have a second XRAM address ;This means we have two XRAM values separated by a comma which ;means we need to dump all XRAM between those two addresses MOV R1,#04h ;Point to "end address" for decode value LCALL GetHexValue ;Get the hex value into R2/R3 ;Here we display a range of values LJMP CmdXDumpRange CmdXNotComma: CJNE A,#'=',CmdXNotEqual ;If the command was not an equal sign then we display that address SJMP CmdXSetXRAMValues ;Jump to code that SETS memory to given values CmdXNotEqual: ;If we're here it means we need to display the address requested JB B.0,CXNE ;If B0.0 is set then we are displaying XRAM, so jump to XRAM LCALL SendSerial DB "IRAM[",0 SJMP CXNE3 ;Jump to low byte of display since IRAM address is only 1 byte in length CXNE: LCALL SendSerial DB "XRAM[",0 CXNE2: MOV A,R2 ;Get high byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it CXNE3: MOV A,R3 ;Get low byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB "]=",0 JB B.0,CXNE4 ;If B0.0 is set then in XRAM mode, so jump to XRAM code MOV A,R3 ;Get low byte MOV R0,A ;Store it in R0 MOV A,@R0 ;Get the value SJMP CXNE5 CXNE4: MOV DPH,R2 ;Get high byte of address MOV DPL,R3 ;Get low byte of address MOVX A,@DPTR ;Read that value of XRAM CXNE5: LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB 13,0 LJMP SBCMain CmdXSetXRAMValues: ;If we get here that means we got an = which means we need to set one ;or more addresses MOV DPH,R2 ;Get high byte of address MOV DPL,R3 ;Get low byte of address CmdXSetLoop: MOV R4,#00h ;Clear buffer for decode MOV R5,#00h ;Clear buffer for decode MOV R1,#04h ;Point to "end address" for decode value LCALL GetHexValue ;Get the hex value into R2/R3 PUSH ACC ;Store return value JB B.0,CXWL MOV A,DPL ;Get the low byte MOV R1,A ;Store it in R1 MOV A,R5 ;Get the value to store MOV @R1,A ;Store it in IRAM SJMP CXWC ;Continue CXWL: MOV A,R5 ;Get the low byte of the decoded value MOVX @DPTR,A ;Store it in XRAM CXWC: INC DPTR ;Point to next XRAM address POP ACC ;Restore return value CJNE A,#00h,CmdXSetLoop ;Get next value if we're not at end of line SJMP CmdXLoopDone CmdXLoopDone: LJMP SBCMain CmdXDumpRange: ;If we get here it means we have to dump multiple addresses ;So we start dumping at the specified address and once every ;16 addresses we add a new line and print the address. MOV DPH,R2 ;Get high byte of address MOV DPL,R3 ;Get low byte of address MOV R6,#10h ;Reset dump counter CmdXDumpRangeLoop: CJNE R6,#10h,CmdXDumpData ;If not 16 addresses then no need to print line header ;Display the current address MOV R2,DPH ;Save DPTR high MOV R3,DPL ;Save DPTR low CmdXEntry: JB B.0,CXDR LCALL SendSerial DB 13,"IRAM[",0 SJMP CXDR2 CXDR: LCALL SendSerial DB 13,"XRAM[",0 MOV A,R2 ;Get high byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it CXDR2: MOV A,R3 ;Get low byte of address LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it LCALL SendSerial DB "]=",0 MOV DPH,R2 ;Restore DPTR high MOV DPL,R3 ;Restore DPTR low MOV R6,#00h ;Reset dump counter CmdXDumpData: JB B.0,CXDR3 MOV R0,DPL ;Get low byte of address MOV A,@R0 ;Get that value of IRAM SJMP CXDR4 CXDR3: MOVX A,@DPTR ;Get the value in question CXDR4: LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it MOV A,#' ' ;Send space LCALL SendSerialByte ;Send it ;Now we need to see if the address we just displayed was the last one MOV A,DPH ;Get DPTR high CJNE A,04h,CmdXContinue ;High byte is different so continue MOV A,DPL ;Get DPTR low CJNE A,05h,CmdXContinue ;Low byte is different so continue MOV A,#CR ;Send CR LCALL SendSerialByte ;Send it LJMP SBCMain ;Done CmdXContinue: INC DPTR ;Point to next DPTR INC R6 ;Increment dump counter SJMP CmdXDumpRangeLoop ;Repeat cycle ;***************************************************************** ;* Command 'A': Mini-Assembler * ;* Function: The mini-assembler allows the user to assemble small* ;* programs without the need for full assembler. It can also* ;* be used to modify programs that have been loaded into XRAM* ;* if the changes are simple. The assembler supports all * ;* standard 8051 instructions in standard format. Addresses * ;* must be referenced by their numeric address, not labels. * ;* If an address immediately follows the 'A' on the command * ;* line then that becomes the assemble address. Otherwise * ;* it defaults to the address immediately following the last * ;* instruction assembled. When assembling the assemble addr * ;* can be changed by using the ORG XXXX where XXXX is the * ;* new assemble address. * ;***************************************************************** ifdef ENABLE_ASM CmdAssembler: MOV R0,#CmdBuffer + 1 ;Point to the first character after 'A' MOV R1,#04h ;Point to "start address" for decoded value CLR A ;Clear accumulator MOV R4,A ;Clear the decode buffer MOV R5,A ;Clear the decode buffer LCALL GetHexValue ;Get the hex value into R2/R3 JC AsmGetLine ;If nothing decoded, use current asm address ;This means an address was provided so set the AsmAddrHi pointer to ;th new value MOV R0,#AsmAddrHi ;Point to the high assemble address byte MOV A,R4 ;Get high byte of new assemble address MOV @R0,A ;Move the new high byte to AsmAddrHi INC R0 ;Point to low assemble address byte MOV A,R5 ;Get low byte of new assemble address byte MOV @R0,A ;Move the new low byte to AsmAddrLo AsmGetLine: ;First display the current asm address MOV R0,#AsmAddrHi ;Point to current assemble address hi MOV A,@R0 ;Get high byte of current assemble address LCALL SendSerialHexByte ;Display it INC R0 ;Ooint to current assemble address lo MOV A,@R0 ;Get low byte of current assemble address LCALL SendSerialHexByte ;Display it LCALL SendSerial DB ": ",0 MOV R0,#CmdBuffer ;Point to command buffer MOV @R0,#00h ;Make sure it starts with null LCALL GetSerialLine ;Get a line of input JNZ AsmProcess ;If the line contains something then process it LJMP SBCMain ;Done getting lines AsmProcess: ;First we must break up input line into up to four parameters ;First we get the instruction which will be terminated with a ;space or NULL (0). We use the OpTable to look for the ;instruction. Each entry in the optable is 5 bytes long plus ;an additional byte which indicates how many parameters this ;instruction has. R2 holds the index MOV DPTR,#OpTable ;Point DPTR to first entry in command table MOV R2,#00h ;Clear the index to the parm FindOpLoop: ;Loops through each op in the optable CLR A MOVC A,@A+DPTR ;Get first character of op JNZ FOS_StartScan ;If it's not zero then check for op LCALL SendSerial DB "Invalid opcode",13,0 LJMP AsmGetLine FOS_StartScan: MOV R0,#CmdBuffer ;Point to first character of buffer FindOpScan: MOV A,@R0 ;Get next character user typed CJNE A,#00h,FOS_NotNull ;It isn't a null MOV A,#' ' ;If it's a null then we make it a space to compare FOS_NotNull: ;So we're still in an instruction LCALL ToUpper ;Make sure character is uppercase MOV R1,A ;Hold typed character in R1 temporarily CLR C ;Clear carry for subtraction MOV A,R0 ;Get current offset SUBB A,#CmdBuffer ;Subtract initial buffer MOVC A,@A+DPTR ;Get character of buffer XRL A,R1 ;See if it's the same as what the user typed JNZ FOS_NextOp ;Different, so check next op in table ;It's the same... so if it's a space then we have a valid command MOV A,R1 ;Get the character typed again CJNE A,#' ',FOS_NextChar ;If it wasn't a space then keep checking SJMP FOS_Found ;Jump to valid opcode FOS_NextChar: INC R0 ;Point to next character SJMP FindOpScan ;Check next character of this op FOS_NextOp: INC DPTR ;Increment DPTR 7 times to point to next op in table INC DPTR ;2 INC DPTR ;3 INC DPTR ;4 INC DPTR ;5 INC DPTR ;6 INC DPTR ;7 INC R2 ;Increment offset SJMP FindOpLoop ;Check next op in the optable FOS_Found: INC R2 MOV AsmOpcodeIndex,R2 ;Store the index in AsmOpcodeIndex MOV A,#06h ;We want to get the number of parms in this instruction MOVC A,@A+DPTR ;Get it MOV AsmOpcodeParmCount,A ;Store it in our holding variable MOV AsmRRegister,#00h ;Offset to add by default 0 since no Rx operand ;At this point we've identified a valid opcode. So now we look at the rest of the ;input line to see if there are any parameters. There can be a maximum of 3 parameters ;which may be one of the fixed parameters, a hex value, or a hex value with a slash ;in front of it. Each parameter is terminated by a comma, a space, or a null. MOV AsmParmCount,#00h ;Clear the parameter count MOV A,R0 ;Moving start of this parameter to A MOV R2,A ;Hold the start of this parameter in R2 AsmNextParm: MOV A,R2 ;Move current pointer to accumulator MOV R0,A ;Put current pointer in R0 CJNE @R0,#00h,AsmNextParm2 ;If not a null then continue LJMP AGP_ParmsDone ;Done AsmNextParm2: INC R0 ;Point to next character MOV A,R0 ;Move a copy to accumulator MOV R2,A ;Now R2 ponits to beginning of next parameter MOV DPTR,#ParmTable ;Point to parameter table AsmGetParms: ;This gets each parameter and identifies it and also translates numeric parms MOV A,R2 ;Get pointer to start of parameter MOV R0,A ;Set pointer to start of parameter MOV A,@R0 ;Get the next character JNZ AGP_StartParm ;If there's something else to get LJMP AGP_ParmsDone ;All done with all parms AGP_StartParm: ;First we make sure this parameter doesn't start with a # which ;would imply immediate data CJNE A,#'#',AGP_NotData MOV R7,#PARM_DATA INC R2 ;Point to numeric value SJMP AGP_Numeric AGP_NotData: CJNE A,#'/',AGP_ParmScan MOV R7,#PARM_INVADDR INC R2 ;Point to numeric value SJMP AGP_Numeric AGP_ParmScan: CLR A MOVC A,@A+DPTR ;Get first character of op JNZ AGP_StartScan ;If it's not zero then check for op MOV R7,#PARM_NUMERIC AGP_Numeric: ;If we get here it's not a fixed operand but it still might be a hex value ;If we get here then we may have a numeric value. If we have a numeric value ;store it and name it as a numeric parameter. Otherwise we have an error MOV R4,#00h ;Clear buffer for decode MOV R5,#00h ;Clear buffer for decode MOV A,R2 ;Get start of parameter pointer MOV R0,A ;Store it in current address MOV R1,#04h ;Point to "end address" for decode value LCALL GetHexValue ;Get the hex value into R2/R3 JC AGP_BadParm ;Invalid hex ;We have a valid hex value here DEC R0 LJMP AGP_ProcOperand ;Process the operand AGP_BadParm: LCALL SendSerial DB "Invalid operand",13,0 LJMP AsmGetLine AGP_StartScan: MOV A,@R0 ;Get next character user typed CJNE A,#00h,AGP_NotNull ;It isn't a null MOV A,#' ' ;If it's a null then we make it a space to compare SJMP AGP_ProcChar AGP_NotNull: ;So we're still in an instruction CJNE A,#',',AGP_ProcChar ;If it's not a comma, process it MOV A,#' ' ;If it's a comma we treat it like a space for comparison purposes AGP_ProcChar: LCALL ToUpper ;Make sure character is uppercase MOV R1,A ;Hold typed character in R1 temporarily CLR C ;Clear carry for subtraction MOV A,R0 ;Get current offset SUBB A,R2 ;Subtract initial parameter position MOVC A,@A+DPTR ;Get character of buffer XRL A,R1 ;See if it's the same as what the user typed JNZ AGP_NextParm ;Different, so we have to check for numeric ;It's the same... so if it's a space then we have a valid command MOV A,R1 ;Get the character typed again CJNE A,#' ',AGP_NextChar ;If it wasn't a space then keep checking SJMP AGP_FoundOperand ;Jump to valid parameter AGP_NextChar: INC R0 ;Point to next character SJMP AGP_ParmScan ;Check next character of this op AGP_NextParm: INC DPTR ;Increment DPTR 9 times to point to next op in table INC DPTR ;2 INC DPTR ;3 INC DPTR ;4 INC DPTR ;5 INC DPTR ;6 INC DPTR ;7 INC DPTR ;8 INC DPTR ;9 LJMP AsmGetParms ;Check next op in the optable AGP_FoundOperand: ;We now get the parameter type for this parameter which is at DPTR+8 MOV A,#08h MOVC A,@A+DPTR MOV R7,A ;Hold parameter type in R7 AGP_ProcOperand: MOV A,R0 ;Get current pointer MOV R2,A ;Store it in R2 ;Here R7 holds the type of parameter. MOV A,AsmParmCount ;Get the current parameter count CJNE A,#03h,AGP_StoreParm ;If it's not the 4th (3 + 1) parameter, store it LCALL SendSerial DB "Too many operands",13,0 LJMP AsmGetLine AGP_StoreParm: ;A has the number of parms, each parm requires 3 bytes so we ;multiply it by 3 and add it to the base Param1Type address PUSH ACC MOV A,R7 ;Get current type ANL A,#0F0h ;Only interested in high nibble CJNE A,#PARM_Rx,AGP_StoreParm2 ;If it's not an RX register then continue ;Here we have an Rx register which means we need to figure out if it was ;R0, R1, etc. and store the value (0-7) in R5 which will be used later. MOV A,R7 ;Get current type which is an "Rx" register ANL A,#0Fh ;Only interested in low nibble DEC A ;Decrement it because R0=51, R1=52, etc. MOV AsmRRegister,A ;Save it for later AGP_StoreParm2: POP ACC MOV B,#03h MUL AB ;Multiply by 3 ADD A,#Param1Type ;Param1Type is the base of the storage area MOV R1,A ;Use R1 as a pointer MOV A,R7 ;Get the parameter type MOV @R1,A ;Store it in the buffer INC R1 ;Point to high byte storage MOV A,R4 ;Get the high byte MOV @R1,A ;Store it in the buffer INC R1 ;Point to low byte storage MOV A,R5 ;Get the high byte MOV @R1,A ;Store it in the buffer ;We've now stored everything we need to know about this parameter in the buffer INC AsmParmCount ;Increment the parameter count LJMP AsmNextParm ;Process next parameter AGP_ParmsDone: ;First thing we do is see if the number of parameters that the user provided ;is the correct number of parameters for this assembly language instruction MOV A,AsmParmCount ;Get the number of parameters read CJNE A,AsmOpcodeParmCount,AGP_BadParmCount SJMP AsmFindOpcode AGP_BadParmCount: LCALL SendSerial DB "Wrong number of operands",13,0 LJMP AsmGetLine AsmFindOpcode: ;We get here when we're done converting parameters ;We now have parsed out an instruction that we know has the right number of ;operands for the instruction in question. We know the instruction. So now ;we go through the AsmTable and look for this instruction and, when we find it, ;we see if the operands match what we detected in the provided instruction. ;First step is to make sure it's not a "fake" operand (like DB) which is not ;an instruction MOV A,AsmOpcodeIndex CJNE A,#OP_DB,AFO_NotDB ;If it's not a DB command, continue ;It is a DB command so we just need to insert the first parameter value in ;the next point in code memory MOV R0,#AsmAddrHi ;Point to current assemble address MOV DPH,@R0 ;Get the current assemble address INC R0 ;Point to low byte MOV DPL,@R0 ;Get the current assemble address MOV R0,#Param1Lo ;Point to the low byte of the DB value MOV A,@R0 ;Get the DB value MOVX @DPTR,A ;Store it in code memory INC DPTR ;Increment DPTR to next address LJMP AsmDone ;Store updated DPTR and finish AFO_NotDB: CJNE A,#OP_ORG,AFO_Start ;It's not the ORG command so continue ;This is the ORG instruction. Just get the 16-bit parameter and make the ;current assemble address the same. MOV R0,#Param1Hi ;Point to the high byte of the ORG MOV DPH,@R0 ;Get the high byte INC R0 ;Point to the low byte of the ORG MOV DPL,@R0 ;Get the low byte LJMP AsmDone ;Update the current assemble address and get next line AFO_Start: MOV AsmOpcode,#00h ;We start with opcode 00 MOV DPTR,#AsmTable ;Point to the beginning of the asm table AFO_Scan: CLR A ;No offset MOVC A,@A+DPTR ;Get the next byte in the table, this holds the OpCode index JNZ AFO_NotDone ;If the next entry is a zero then we're done with failure LJMP AFO_NotFound AFO_NotDone: MOV R3,A ;R3 holds this instruction from the AsmTable ;We now know the opcode of the next instruction. So now we need to use the OpTable to ;determine how many parameters this instruction has and get the parameters. We first ;save DPTR on the stack to not corrupt it, then we load DPTR with the OpCode PUSH DPL ;Save DPTR PUSH DPH ;Save DPTR LCALL GetOpTableInfo ;Get the number of operands for this instruction MOV R4,A ;R4 holds the number of operands in this instruction POP DPH ;Restore DPTR POP DPL ;Restore DPTR PUSH ACC ;Save # of operands on the stack ;We now have R3 as this instruction from AsmTable we are traversing, R4 holds the ;number of operands this instruction has, AsmOpcodeIndex holds the opcode index ;the user entered. MOV R0,#05h ;We're going to put operands in memory starting at R5 *BANK MOV R1,#00h ;Set "Rx" flag to false AFO_CopyLoop: INC DPTR ;Point to first operand, if any CJNE R4,#00h,AFO_CopyOperands ;If R4 != 0 then keep copying operands SJMP AFO_Compare ;We've copied all the operands AFO_CopyOperands: CLR A ;No offset MOVC A,@A+DPTR ;Get next operand MOV @R0,A ;Store it in the next location ANL A,#0F0h ;Only interested in high nibble CJNE A,#PARM_Rx,AFO_CO2 ;If it's not an Rx operand, continue ;This is an R register so we set the flag with the Rx operand MOV R1,#01h ;Set flag that indicates we have an Rx parameter AFO_CO2: INC R0 ;Point to next storage location (R5, R6, R7) DEC R4 ;Decrement operand count SJMP AFO_CopyLoop ;Check for next operand AFO_NextZ: LJMP AFO_Next AFO_Compare: POP ACC MOV R4,A ;Store number of operands in this instruction ;So now we have R3=current instruction from AsmTable, R4=number of operands in this ;instruction, R5-R7 hold the actual operand codes, R1=Rx flag (0=no Rx operand, 1=Rx operand) ;So we see if R3=AsmOpcodeIndex. If it is then we need to see if the parameters match. ;If it isn't then we continue looking MOV A,R3 ;Accumulator now holds the isntruction from AsmTable CJNE A,AsmOpcodeIndex,AFO_NextZ ;This isn't the instruction we're looking for so continue ;We have a match, we need to see if the operands match because there are ;multiple instructions with different operands. CJNE R4,#00h,AFO_CheckOperands ;If the # of operands is non-zero, compare them AFO_Definitive: MOV A,AsmRRegister ;Get the R register in question ADD A,AsmOpcode ;Add the current opcode to the Rx offset MOV AsmOpcode,A ;Update it MOV R0,#Param1Type ;Point to first parameter MOV A,R5 ;Get first definitive parameter MOV @R0,A ;Store it MOV R0,#Param2Type ;Point to second parameter MOV A,R6 ;Get seconddefinitive parameter MOV @R0,A ;Store it MOV R0,#Param3Type ;Point to third parameter MOV A,R7 ;Get third definitive parameter MOV @R0,A ;Store it LJMP AsmInstruction ;Found the instruction, assemble it AFO_CheckOperands: MOV A,R1 PUSH ACC ;Protect R1 flag MOV R0,#Param1Type ;Point to first user parameter in buffer MOV R1,#05h ;Point to first parameter from AsmTable MOV A,R4 ;Get number of operands MOV R2,A ;Store number of operands temporarily in R2 AFO_CheckOpLoop: MOV A,@R0 ;A now holds the user parameter ANL A,#0F0h ;We only compare based on high nibble MOV B,A ;B now holds the unshifted user parameter MOV A,@R1 ;A now holds the table parameter ;We now need to do some conversion. If the table indicates that this is a ;PARM_2KADDR,PARM_LADDR,PARM_RELATIVE,PARM_ADDR then we map it to PARM_NUMERIC ;If it's PARM_DATA8 or PARM_DATA16 we map it to PARM_DATA. CJNE A,#PARM_2KADDR,AFO_Check2 AFO_SetNumeric: MOV A,#PARM_NUMERIC SJMP AFO_COLCont AFO_Check2: CJNE A,#PARM_LADDR,AFO_Check3 SJMP AFO_SetNumeric AFO_Check3: CJNE A,#PARM_RELATIVE,AFO_Check4 SJMP AFO_SetNumeric AFO_Check4: CJNE A,#PARM_ADDR,AFO_Check5 SJMP AFO_SetNumeric AFO_Check5: CJNE A,#PARM_DATA8,AFO_Check6 AFO_SetData: MOV A,#PARM_DATA SJMP AFO_COLCont AFO_Check6: CJNE A,#PARM_DATA16,AFO_COLCont SJMP AFO_SetData AFO_COLCont: CJNE A,B,AFO_NotOp ;If they're different then this is wrong syntax ;The operands were the same INC R1 ;Increment table parameter from R5 to R6 to R7 INC R0 ;Increment user parameter three times for Parm1Type to Parm2Type to Parm3Type INC R0 INC R0 DJNZ R2,AFO_CheckOpLoop ;Check next operand POP ACC ;We have R1 on the stack, discard it SJMP AFO_Definitive ;If we get here then all operands matched AFO_NotOp: POP ACC MOV R1,A ;Restore R1 AFO_Next: CJNE R1,#00h,AFO_Next8 ;If R1 !=0 then we have an Rx parameter that needs to be inc'd by 8 INC AsmOpcode ;Increment asm counter LJMP AFO_Scan ;Check next operand in table AFO_Next8: MOV A,AsmOpcode ;Get opcode counter ADD A,#08h ;Increment by 8 MOV AsmOpcode,A ;Update it LJMP AFO_Scan ;Check next operand in table AFO_NotFound: LCALL SendSerial DB "Invalid instruction syntax",13,0 LJMP AsmGetLine ; ; If we get here it means we've identified the opcode for this instruction ; (in AsmOpcode). Now we need to assemble it into memory and update the ; PC to the next address. AsmInstruction: ;We first need to replace the parameters in the Param1Type of IRAM ;with the real parameter types from the instruction MOV A,AsmOpcode ;Get the opcode that we determined LCALL GetOpTableInfo ;Find it in the table, returns with A=# of operands MOV R0,#AsmAddrHi ;Point to current assemble address MOV DPH,@R0 ;Move it to DPTR INC R0 ;Point to low byte MOV DPL,@R0 ;Move it to DPTR MOV A,AsmOpcode ;Get the instruction opcode MOVX @DPTR,A ;Move it into the code area INC DPTR ;Point to next address in code memory MOV R0,#Param1Type ;Point to the first parameter MOV R1,#AsmParmCount ;Get the address of the parameter count MOV A,@R1 ;Get the parameter count into the accumulator MOV R7,A ;Store it in R7 to use as a count-down index ;We now go through each parameter... if it is a no-bytes parameter (i.e. ;it is encoded in the instruction rather than requiring an additional byte) ;then we don't copy anything. Otherwise we need to copy that operand into ;memory. JNZ AsmCopyParms ;If there are operands then process them LJMP AsmDone ;Otherwise we're done AsmCopyParms: MOV A,@R0 ;Get the next parameter type byte CJNE A,#PARM_NOBYTES,$+3 ;Is the current instruction less than PARM_NOBYTES JNC AsmCopyCont ;This is a parameter that has bytes so copy it LJMP ACP_NoCopy ;This is a no-byte parameter so don't copy anything AsmCopyCont: CJNE A,#PARM_2KADDR,ACP_Not2k ;Not a 2K address ;Handle 2k jump -- We take the address of the high byte of the next instruction ;(DPTR + 1) and AND it with F800h which returns the 2k block in which it is ;located. We then take the high byte of the target address ANDed with F800 and ;see if it is the same as the DPTR ANDed value. If they are different then it ;is an invalid AJMP/ACALL. ;Copy target address into R2/R3. INC R0 ;Point to high byte of target address MOV A,@R0 ;Get high byte of target address MOV R2,A ;Store high byte of target address in R2 INC R0 ;Point to low byte of target address MOV A,@R0 ;Get low byte of target address MOV R3,A ;Store low byte of target address in R3 ;Calculate the address of the instruction following the AJMP/ACALL MOV A,DPL ;Get low byte of DPTR ADD A,#01h ;Add 1 to it MOV A,DPH ;Get high byte of DPTR ADDC A,#00h ;Add 0 (plus carry) to it ANL A,#0F8h ;Only interested in high 5 bits MOV B,A ;Store the 2k block of the next instruction in B MOV A,R2 ;Get the high byte of the target address ANL A,#0F8h ;Only interested in high 5 bits CJNE A,B,ACP_Inv2kx ;If they're not the same then they're in different 2k blocks ;If we get here then they're in the same block MOV A,R3 ;Get the low byte of the target address MOVX @DPTR,A ;Store it in the next program memory address MOV A,R2 ;Get high byte of the targat address ANL A,#07h ;Only interested in the low 3 bits of the high address RR A ;Rotate right RR A ;Rotate right RR A ;Rotate right, now A8-A10 in bits 13-15 MOV B,A ;Hold it in B ;Decrement DPTR CLR C MOV A,DPL ;Get low byte of DPTR SUBB A,#01h ;Decrement DPTR MOV DPL,A ;Update low byte of DPTR MOV A,DPH ;Get high byte of DPTR SUBB A,#00h ;Decrement DPTR MOV DPH,A ;Update high byte of DPTR ;DPTR now points to opcode of the instruction MOVX A,@DPTR ;Get the instruction opcode ORL A,B ;Apply the bits from B into the opcode register MOVX @DPTR,A ;Update the opcode for the proper 2k offset INC DPTR ;Point to the second byte of the opcode INC DPTR ;Point to the first byte of next instruction LJMP ACP_Loop ;Get next parameter ACP_Inv2kx: LJMP AsmInvalid2k ;Report 2k jump/call ACP_Not2k: CJNE A,#PARM_RELATIVE,ACP_NotRel ;Not a relative address ;Handle relative address - this means we need to add one to the ;current address pointer (DPTR) and subtract the requested ;relative address that is contained in @R0+1 and @R0 + 2 INC R0 ;Point to high byte of target address INC R0 ;Point to low byte of target address MOV A,DPL ;Get low byte of DPTR ADD A,#01h ;Add 1 to it MOV R3,A ;Store low byte of next address in R3 MOV A,DPH ;Get high byte of DPTR ADDC A,#00h ;Add 0 (plus carry) to it MOV R2,A ;Store high byte of next address in R2 CLR C ;Clear carry MOV A,@R0 ;Get low byte of target address SUBB A,R3 ;Subtract low byte of next address MOV R3,A ;Set R3 to low byte of relative address DEC R0 ;Point to high byte of target address MOV A,@R0 ;Get high byte of target address SUBB A,R2 ;Subtract high byte of next address MOV R2,A ;Set R2 to high byte of relative address JZ ACP_RelPos ;If high byte is 0 then the target address is positive CJNE A,#0FFh,ACP_InvRelx ;If not 00 and not FFh then out of range ;This means the relative address is negative. That means that the low byte ;must be 80-FFh. If it is less than 80h then it's out of range. MOV A,R3 ;Get the low byte CJNE A,#80h,$+3 ;Is it less than 80h? JC ACP_InvRelx ;Yes, so that relative address was out of range ACP_RelGood: ;We have a valid relative address, so copy it to the next byte MOVX @DPTR,A ;Store it in the next program memory address INC DPTR ;Point to next address LJMP ACP_Loop ;Get next parameter ACP_RelPos: ;This means the relative address was positive. That means that the low byte ;must be 00h-7Fh. If it is greater than 7Fh then it's out of range MOV A,R3 ;Get low byte CJNE A,#80h,$+3 ;Is it less than 80h? JNC ACP_InvRelx ;Yes, so that rleative address was out of range SJMP ACP_RelGood ;Deal with this address ACP_InvRelx: LJMP AsmInvalidRelative ;Report Invalid Relative address ACP_NotRel: CJNE A,#PARM_LADDR,ACP_NotLADDR ;Not a 16-bit address ACP_Copy16bit: ;Copy the 2 bytes into code memory INC R0 ;Point to high byte of parameter value MOV A,@R0 ;Copy it to accumulator MOVX @DPTR,A ;Move it into next code address INC DPTR ;Point to next address ACP_Copy8bit: INC R0 ;Point to low byte of parameter value MOV A,@R0 ;Copy it to accumulator MOVX @DPTR,A ;Move it into next code address INC R0 ;Point to next instruction INC DPTR ;Point to next address LJMP ACP_Loop ACP_NotLADDR: CJNE A,#PARM_DATA16,ACP_NotData16 SJMP ACP_Copy16bit ;This is a 16-bit value, like mov DPTR,#1234h, so copy 2 bytes ACP_NotData16: CJNE A,#PARM_DATA8,ACP_NotData8 ACP_Do8bit: INC R0 SJMP ACP_Copy8bit ACP_NotData8: CJNE A,#PARM_ADDR,ACP_NotDataAddr SJMP ACP_Do8bit ACP_NotDataAddr: CJNE A,#PARM_INVADDR,ACP_NotDataInvAddr SJMP ACP_Do8bit ACP_NotDataInvAddr: ACP_NoCopy: INC R0 INC R0 INC R0 ;Point to next parameter record ACP_Loop: DJNZ R7,ACP_NextLoop LJMP AsmDone ACP_NextLoop: LJMP AsmCopyParms AsmDone: MOV R0,#AsmAddrHi ;Point to current assemble address MOV @R0,DPH ;Update the address pointer INC R0 ;Point to low byte MOV @R0,DPL ;Update the address pointer LJMP AsmGetLine AsmInvalidRelative: LCALL SendSerial DB "Relative address out of range (+127/-128)",13,0 LJMP AsmGetLine AsmInvalid2k: LCALL SendSerial DB "Absolute address outside of 2k block",13,0 LJMP AsmGetLine ;*********************************************************** ;* GetOpTableInfo: This routine accesses OpTable and * ;* returns A with the number of operands contained in * ;* this operand. DPTR is left pointing to the first byte* ;* of the entry in the table. * ;*********************************************************** GetOpTableInfo: DEC A MOV DPTR,#OpTable ;Point to the OpTable MOV B,#07h ;Each entry in Optable is 7 bytes long MUL AB ;Multiply A (opcode offset) by length of each record ADD A,DPL ;Add low byte of offset to DPTR MOV DPL,A ;Update DPTR MOV A,DPH ;Get high byte ADDC A,B ;Add high byte MOV DPH,A ;Update it ;We're now pointing to the record in question. At +6 is the number of bytes ;contained in this instruction MOV A,#06h ;Point to the # of parameters byte MOVC A,@A+DPTR ;Get the value RET ParmTable: DB "C ",PARM_C DB "A ",PARM_A DB "@R0 ",PARM_IND_R0 DB "@R1 ",PARM_IND_R1 DB "R0 ",PARM_R0 DB "R1 ",PARM_R1 DB "R2 ",PARM_R2 DB "R3 ",PARM_R3 DB "R4 ",PARM_R4 DB "R5 ",PARM_R5 DB "R6 ",PARM_R6 DB "R7 ",PARM_R7 DB "@A+DPTR ",PARM_ADPTR DB "@A+PC ",PARM_APC DB "AB ",PARM_AB DB "DPTR ",PARM_DPTR DB "@DPTR ",PARM_IND_DPTR DB 0h ;End of parm table OpTable: ;Instruction text, # of parameters the instruction has DB "NOP ",0 ;01 DB "AJMP ",1 ;02 DB "LJMP ",1 ;03 DB "RR ",1 ;04 DB "INC ",1 ;05 DB "JBC ",2 ;06 DB "ACALL ",1 ;07 DB "LCALL ",1 ;08 DB "RRC ",1 ;09 DB "DEC ",1 ;0A DB "JB ",2 ;0B DB "RET ",0 ;0C DB "RL ",1 ;0D DB "ADD ",2 ;0E DB "JNB ",2 ;0F DB "RETI ",0 ;10 DB "RLC ",1 ;11 DB "ADDC ",2 ;12 DB "JC ",1 ;13 DB "ORL ",2 ;14 DB "JNC ",1 ;15 DB "ANL ",2 ;16 DB "JZ ",1 ;17 DB "XRL ",2 ;18 DB "JNZ ",1 ;19 DB "JMP ",1 ;1A DB "MOV ",2 ;1B DB "SJMP ",1 ;1C DB "MOVC ",2 ;1D DB "DIV ",1 ;1E DB "SUBB ",2 ;1F DB "MUL ",1 ;20 DB "??? ",0 ;21 DB "CPL ",1 ;22 DB "CJNE ",3 ;23 DB "PUSH ",1 ;24 DB "CLR ",1 ;25 DB "SWAP ",1 ;26 DB "XCH ",2 ;27 DB "POP ",1 ;28 DB "SETB ",1 ;29 DB "DA ",1 ;2A DB "DJNZ ",2 ;2B DB "XCHD ",2 ;2C DB "MOVX ",2 ;2D DB "DB ",1 ;2E DB "ORG ",1 ;2F DB 0 ;End of instruction marker AsmTable: ;instruction, # operands, operand1, operand2, operand3 ;00h DB OP_NOP DB OP_AJMP,PARM_2KADDR DB OP_LJMP,PARM_LADDR DB OP_RR,PARM_A DB OP_INC,PARM_A DB OP_INC,PARM_ADDR DB OP_INC,PARM_IND_R0 DB OP_INC,PARM_IND_R1 DB OP_INC,PARM_Rx ;08-0Fh ;10h DB OP_JBC,PARM_ADDR,PARM_RELATIVE DB OP_ACALL,PARM_2KADDR DB OP_LCALL,PARM_LADDR DB OP_RRC,PARM_A DB OP_DEC,PARM_A DB OP_DEC,PARM_ADDR DB OP_DEC,PARM_IND_R0 DB OP_DEC,PARM_IND_R1 DB OP_DEC,PARM_Rx ;08-0Fh ;20h DB OP_JB,PARM_ADDR,PARM_RELATIVE DB OP_AJMP,PARM_2KADDR DB OP_RET DB OP_RL,PARM_A DB OP_ADD,PARM_A,PARM_DATA8 DB OP_ADD,PARM_A,PARM_ADDR DB OP_ADD,PARM_A,PARM_IND_R0 DB OP_ADD,PARM_A,PARM_IND_R1 DB OP_ADD,PARM_A,PARM_Rx ;30h DB OP_JNB,PARM_ADDR,PARM_RELATIVE DB OP_ACALL,PARM_2KADDR DB OP_RETI DB OP_RLC,PARM_A DB OP_ADDC,PARM_A,PARM_DATA8 DB OP_ADDC,PARM_A,PARM_ADDR DB OP_ADDC,PARM_A,PARM_IND_R0 DB OP_ADDC,PARM_A,PARM_IND_R1 DB OP_ADDC,PARM_A,PARM_Rx ;40h DB OP_JC,PARM_RELATIVE DB OP_AJMP,PARM_2KADDR DB OP_ORL,PARM_ADDR,PARM_A DB OP_ORL,PARM_ADDR,PARM_DATA8 DB OP_ORL,PARM_A,PARM_DATA8 DB OP_ORL,PARM_A,PARM_ADDR DB OP_ORL,PARM_A,PARM_IND_R0 DB OP_ORL,PARM_A,PARM_IND_R1 DB OP_ORL,PARM_A,PARM_Rx ;50h DB OP_JNC,PARM_RELATIVE DB OP_ACALL,PARM_2KADDR DB OP_ANL,PARM_ADDR,PARM_A DB OP_ANL,PARM_ADDR,PARM_DATA8 DB OP_ANL,PARM_A,PARM_DATA8 DB OP_ANL,PARM_A,PARM_ADDR DB OP_ANL,PARM_A,PARM_IND_R0 DB OP_ANL,PARM_A,PARM_IND_R1 DB OP_ANL,PARM_A,PARM_Rx ;60h DB OP_JZ,PARM_RELATIVE DB OP_AJMP,PARM_2KADDR DB OP_XRL,PARM_ADDR,PARM_A DB OP_XRL,PARM_ADDR,PARM_DATA8 DB OP_XRL,PARM_A,PARM_DATA8 DB OP_XRL,PARM_A,PARM_ADDR DB OP_XRL,PARM_A,PARM_IND_R0 DB OP_XRL,PARM_A,PARM_IND_R1 DB OP_XRL,PARM_A,PARM_Rx ;70h DB OP_JNZ,PARM_RELATIVE DB OP_ACALL,PARM_2KADDR DB OP_ORL,PARM_C,PARM_ADDR DB OP_JMP,PARM_ADPTR DB OP_MOV,PARM_A,PARM_DATA8 DB OP_MOV,PARM_ADDR,PARM_DATA8 DB OP_MOV,PARM_IND_R0,PARM_DATA8 DB OP_MOV,PARM_IND_R1,PARM_DATA8 DB OP_MOV,PARM_Rx,PARM_DATA8 ;80h DB OP_SJMP,PARM_RELATIVE DB OP_AJMP,PARM_2KADDR DB OP_ANL,PARM_C,PARM_ADDR DB OP_MOVC,PARM_A,PARM_APC DB OP_DIV,PARM_AB DB OP_MOV,PARM_ADDR,PARM_ADDR DB OP_MOV,PARM_ADDR,PARM_IND_R0 DB OP_MOV,PARM_ADDR,PARM_IND_R1 DB OP_MOV,PARM_ADDR,PARM_Rx ;90h DB OP_MOV,PARM_DPTR,PARM_DATA16 DB OP_ACALL,PARM_2KADDR DB OP_MOV,PARM_ADDR,PARM_C DB OP_MOVC,PARM_A,PARM_ADPTR DB OP_SUBB,PARM_A,PARM_DATA8 DB OP_SUBB,PARM_A,PARM_ADDR DB OP_SUBB,PARM_A,PARM_IND_R0 DB OP_SUBB,PARM_A,PARM_IND_R1 DB OP_SUBB,PARM_A,PARM_Rx ;A0h DB OP_ORL,PARM_C,PARM_INVADDR DB OP_AJMP,PARM_2KADDR DB OP_MOV,PARM_C,PARM_ADDR DB OP_INC,PARM_DPTR DB OP_MUL,PARM_AB DB OP_UND DB OP_MOV,PARM_IND_R0,PARM_ADDR DB OP_MOV,PARM_IND_R1,PARM_ADDR DB OP_MOV,PARM_Rx,PARM_ADDR ;B0h DB OP_ANL,PARM_C,PARM_INVADDR DB OP_ACALL,PARM_2KADDR DB OP_CPL,PARM_ADDR DB OP_CPL,PARM_C DB OP_CJNE,PARM_A,PARM_DATA8,PARM_RELATIVE DB OP_CJNE,PARM_A,PARM_ADDR,PARM_RELATIVE DB OP_CJNE,PARM_IND_R0,PARM_DATA8,PARM_RELATIVE DB OP_CJNE,PARM_IND_R1,PARM_DATA8,PARM_RELATIVE DB OP_CJNE,PARM_Rx,PARM_DATA8,PARM_RELATIVE ;C0h DB OP_PUSH,PARM_ADDR DB OP_AJMP,PARM_2KADDR DB OP_CLR,PARM_ADDR DB OP_CLR,PARM_C DB OP_SWAP,PARM_A DB OP_XCH,PARM_A,PARM_ADDR DB OP_XCH,PARM_A,PARM_IND_R0 DB OP_XCH,PARM_A,PARM_IND_R1 DB OP_XCH,PARM_A,PARM_Rx ;D0h DB OP_POP,PARM_ADDR DB OP_ACALL,PARM_2KADDR DB OP_SETB,PARM_ADDR DB OP_SETB,PARM_C DB OP_DA,PARM_A DB OP_DJNZ,PARM_ADDR,PARM_RELATIVE DB OP_XCHD,PARM_A,PARM_IND_R0 DB OP_XCHD,PARM_A,PARM_IND_R1 DB OP_DJNZ,PARM_Rx,PARM_RELATIVE ;E0h DB OP_MOVX,PARM_A,PARM_IND_DPTR DB OP_AJMP,PARM_2KADDR DB OP_MOVX,PARM_A,PARM_IND_R0 DB OP_MOVX,PARM_A,PARM_IND_R1 DB OP_CLR,PARM_A DB OP_MOV,PARM_A,PARM_ADDR DB OP_MOV,PARM_A,PARM_IND_R0 DB OP_MOV,PARM_A,PARM_IND_R1 DB OP_MOV,PARM_A,PARM_Rx ;F0h DB OP_MOVX,PARM_IND_DPTR,PARM_A DB OP_ACALL,PARM_2KADDR DB OP_MOVX,PARM_IND_R0,PARM_A DB OP_MOVX,PARM_IND_R1,PARM_A DB OP_CPL,PARM_A DB OP_MOV,PARM_ADDR,PARM_A DB OP_MOV,PARM_IND_R0,PARM_A DB OP_MOV,PARM_IND_R1,PARM_A DB OP_MOV,PARM_Rx,PARM_A ;Failure DB OP_INVALID endif ;################################################################# ;# # ;# SBCMON SUPPORT ROUTINES # ;# # ;################################################################# ;*********************************************************** ;* GetSBCMONVersion: Gets the version of SBCMON and returns* ;* it in accumulator and B. Accumulator holds the major * ;* version number * 10 plus the minor version number. The* ;* B register holds the minor version number. These * ;* values are DEFINED earlier in the source code. * ;*********************************************************** GetSBCMONVersion: MOV A,#SBCMONVERHI MOV B,#SBCMONVERLO RET ;*********************************************************** ;* DetectRAM: Writes to XRAM where it is expected and * ;* determines if there is actually any RAM there. It * ;* then returns the high byte of the base of XRAM in the * ;* accumulator. Assumes either 32k, 8k, or 0k. * ;*********************************************************** DetectRAM: MOV DPTR,#8000h ;Check if there is RAM at 8000h MOVX A,@DPTR ;Get the value that's already there PUSH ACC ;Store it on the stack for future restore LCALL RAMCheck ;Check to see if RAM at 8000h is valid POP ACC ;Restore XRAM value MOVX @DPTR,A ;Save it JC DR_TryE000 ;Not valid, so try at E0000 MOV A,#80h ;Set high byte to 80h MOV R1,#32h ;Set value to amount of RAM (32k) RET DR_TryE000: MOV DPTR,#0E000h ;Check if there is RAM at E000h MOVX A,@DPTR ;Get the value that's already there PUSH ACC ;Store it on the stack for future restore LCALL RAMCheck ;Check to see if RAM at E000h is valid POP ACC ;Restore XRAM value MOVX @DPTR,A ;Save it JC DR_NoRAM ;Not valid, so ther is no XRAM MOV A,#0E0h ;Set high byte to E0h MOV R1,#08h ;Set value to amount of RAM (8k) RET DR_NoRAM: MOV A,#0FFh MOV R1,#00h RET ; Stores each value from 0 through FF and checks to see if it's ; read back correctly. If so, it clears carry and returns. A set ; carry indicates na error. RAMCheck: MOV R0,#00h RAMCheck2: MOV A,R0 MOVX @DPTR,A ;Store the current value of R0 in XRAM MOVX A,@DPTR ;Read the value we just wrote back XRL A,R0 ;See if it's the same as the current value JNZ RCError ;It's different, so set C to indicate error DJNZ R0,RAMCheck2 ;It's the same so try next value CLR C ;If we got here then we had succes, so exit RET RCError: SETB C ;Report error RET ;################################################################# ;# # ;# LIBRARY SUBROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: ToUpper * ;* Description: Makes sure any lower case character in ACC is * ;* shifted to uppercase. * ;***************************************************************** ToUpper: CJNE A,#'a',$+3 ;Is this character < 'a'? JC TU_NotLower ;If it's less than 'a' then not lower case ANL A,#0DFh ;Get rid of bit 6 to make uppercase TU_NotLower: RET ;***************************************************************** ;* Function: GetSerialHex * ;* Description: Reads two bytes from the serial port and treats * ;* them as 2 hex digits that form a 8-bit value. It returns * ;* the value in the accumulator. Sets carry if invalid hex * ;* digits were received and clears carry if everything was * ;* ok. * ;***************************************************************** GetSerialHex: LCALL GetSerialByte ;Get first hex character LCALL HexToNibble ;Convert to value between 0 and 15 JC GSH_Error ;If not a hex digit flag error SWAP A ;This is the high nibble os make it high MOV R1,A ;Store high nibble in R1 LCALL GetSerialByte ;Get first hex character LCALL HexToNibble ;Convert to value between 0 and 15 JC GSH_Error ;If not a hex digit flag error ORL A,R1 ;Combine two values for complete value GSH_Error: RET ;Done! ;***************************************************************** ;* Function: GetHexValue * ;* Description: Reads ASCII from the buffer pointed to by R0 and * ;* converts it to a 16-bit value at @R1 (high) and @R1 + 1 * ;* (low). Returns on first non-hex value. The non-hex value* ;* is returned in ACC and R0 points to the value following * ;* the non-hex value. Sets carry on exit to indicate that * ;* nothing was decoded wheras a clear carry means something * ;* was decoded. * ;***************************************************************** GetHexValue: SETB F0 ;Set the F0 bit to default to nothing decoded GHV_Loop: MOV A,@R0 ;Get the next byte to process LCALL HexToNibble ;Convert it to a binary value JC GHV_Done ;If it wasn't a hex digit then we're done ;Valid hex value so we need to add this to the buffer reg CLR F0 ;Clear F0 because we found at least one digit PUSH ACC ;Store the decoded value on the stack MOV A,@R1 ;Get current high byte SWAP A ;Move low nibble to high nibble ANL A,#0F0h ;Zero out the low nibble MOV @R1,A ;Update it INC R1 ;Point to the low byte MOV A,@R1 ;Get the low byte in memory ANL A,#0F0h ;Only interested in high nibble of low byte SWAP A ;Move high nibble of low byte into low nibble DEC R1 ;Point to high byte again ORL A,@R1 ;OR it with the high byte MOV @R1,A ;Update it INC R1 ;Point to low byte again MOV A,@R1 ;Get low byte into memory ANL A,#0Fh ;Only interested in the low nibble of low byte SWAP A ;Low nibble is now high nibble MOV @R1,A ;Store the updated low byte in reg buffer POP ACC ;Get the decoded value off the stack ORL A,@R1 ;Add it to the low byte MOV @R1,A ;Update the low byte DEC R1 ;Point back to the high byte INC R0 ;Point to the next character in the buffer SJMP GHV_Loop GHV_Done: MOV A,@R0 ;Load the accumulator with the non-hex value INC R0 ;Point to the value following the non-hex value MOV C,F0 ;Set carry based on whether or not F0 was set RET ;***************************************************************** ;* Function: HexToNibble * ;* Description: Takes the ASCII value heald in the accumulator * ;* and converts it to a value between 0 and 15 (00h and 0Fh).* ;* Clears carry if successful and sets carry if it wasn't a * ;* hex digit. * ;***************************************************************** HexToNibble: CJNE A,#'0',$+3 ;Is accumulator < '0'? JC HTB_Error ;Accumulator less than '0', so error CJNE A,#':',$+3 ;Is accumulator between 0 and 9? JNC HTB_NotNumber ;If not it's NOT numeric ;It's a digit so just take the low bits and return it ANL A,#0Fh ;Numeric so just take the low bits CLR C ;Successful, so exit RET ;Return it HTB_NotNumber: ;Not a number so now we check A-Z CJNE A,#'A',$+3 ;Is it less than 'A'? JC HTB_Error ;If so then it's invalid CJNE A,#'G',$+3 ;Is it A-F? JNC HTB_NotAF ;It's not between A and F ;It's between A-F HTB_Adjust: CLR C SUBB A,#55 ;A=65 so suctract 55 and we get 10 RET ;Return the value HTB_NotAF: CJNE A,#'a',$+3 ;Is it less than 'a'? JC HTB_Error ;If so then it's invalid CJNE A,#'g',$+3 ;Is it a-f? JNC HTB_Error ;It's not a-f so it's an error ANL A,#0DFh ;Clear bit 6 to make it upper case SJMP HTB_Adjust HTB_Error: SETB C ;Flag error RET ;Return ;***************************************************************** ;* Function: ByteTo2Hex * ;* Purpose: Convert a single byte into two hex digits * ;* Input: A = Byte to convert (0x00-0xFF) * ;* Output: A = High nibble (ASCII 0x30-0x39,0x41-0x46) * ;* R0 = Low nibble (ASCII 0x30-0x39, 0x41-0x46) * ;* Destroyed Registers: None * ;***************************************************************** ByteTo2Hex: MOV R0,A ANL A,#0Fh ADD A,#0F6h JNC byte_to_bcd_2 ADD A,#07h byte_to_bcd_2: ADD A,#3Ah XCH A,R0 SWAP A ANL A,#0Fh ADD A,#0F6h JNC byte_to_bcd_3 ADD A,#07h byte_to_bcd_3: ADD A,#3Ah RET ;################################################################# ;# # ;# SERIAL SUBROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: DumpCmdBuffer * ;* Purpose: Dumps the ASCII string held in the command buffer * ;* until it reaches a NULL or reaches the end of the * ;* buffer. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: R0, A, PSW * ;***************************************************************** DumpCmdBuffer: MOV R0,#CmdBuffer ;Point to beginning of buffer DCB_Loop: MOV A,@R0 ;Get next character from buffer JZ DCB_Done ;If it's a zero then we're done dumping buffer LCALL SendSerialByte ;Send the byte INC R0 ;Point to next buffer CJNE R0,#CmdMaxBuffer + 1,DCB_Loop ;If we haven't exceeded the buffer we keep going DCB_Done: MOV A,#CR ;Send a CR character LCALL SendSerialByte ;Send it RET ;***************************************************************** ;* Function: GetSerialLine * ;* Purpose: Receives a line of data into the IRAM command * ;* buffer. * ;* Input: None * ;* Output: ACC: First byte of the string entered * ;* R0: Address of first byte of buffer * ;* CmdBuffer: Null-terminated string entered by user * ;* Destroyed Registers: R0, A, PSW * ;***************************************************************** GetSerialLine: MOV R0,#CmdBuffer ;Initialize pointer to first byte of buffer GSL_Wait: ;Wait for a character CLR RI ;Haven't received anything yet JNB RI,$ ;Wait until we've receive a byte MOV A,SBUF ;Get the byte CJNE A,#BS,GSLNotBS ;If it's not a backspace, continue ;This handles a backspace character CJNE R0,#CmdBuffer,GSLBSOk ;If we're not at the beginning of the line, process BS ;If we get here it means we're at the beginning of the line ;already so we don't accept a backspace character. SJMP GSL_Wait GSLBSOk: ; We got a backspace and its valid so process it LCALL SendSerialByte ;Send the backspace character MOV A,#' ' ;Send space character for destructive backspace LCALL SendSerialByte ;Send it MOV A,#BS ;Send a backspace character again LCALL SendSerialByte ;Send it DEC R0 ;Decrement the buffer pointer SJMP GSL_Wait ;Wait for next character GSLNotBS: ;It wasn't a backspace so we check to see if it was CR (0Dh) CJNE A,#CR,GSLNotCR ;It wasn't CR so continue checking MOV @R0,#00h ;Store a null at end of buffer GSLExit: LCALL SendSerialByte ;Echo the CR back to the terminal MOV R0,#CmdBuffer ;Point to beginning of buffer MOV A,@R0 ;Get the first character into ACC RET ;Return, we're done! GSLNotCR: ;Check to see if it was a linefeed CJNE A,#LF,GSLNotLF ;Not a linefeed so continue SJMP GSL_Wait ;We ignore linefeed so wait for next character GSLNotLF: ;Wasn't backspace nor CR so we check for escape CJNE A,#ESC,GSNotESC ;Not escape so continue MOV A,#CR ;Load a CR so that we display that SJMP GSLExit ;Go to the exit routine GSNotESC: ;Interpret as a character CJNE R0,#CmdMaxBuffer,GSLOk ;If we're not already at the end of the buffer, continue SJMP GSL_Wait ;We're at the end of the buffer so don't accept anymore GSLOk: LCALL SendSerialByte ;Echo the character we just received MOV @R0,A ;Store it in the buffer INC R0 ;Increment the pointer to next position SJMP GSL_Wait ;Wait for the next character ;***************************************************************** ;* Function: GetSerialByte * ;* Purpose: Waits for a byte to be received by the serial port * ;* and returns it in the accumulator. * ;* Input: None * ;* Output: ACC: Byte that was received via the serial port * ;* Destroyed Registers: None. * ;***************************************************************** GetSerialByte: JNB RI,$ ;Wait for a character MOV A,SBUF ;Get the character we received CLR RI ;Clear the receive flag RET ;Return ;***************************************************************** ;* Function: SendSerialByte * ;* Purpose: Sends the byte in the accumulator to the serial * ;* port and waits for it to be sent before returning. * ;* Input: ACC: Byte to send * ;* Output: None. * ;* Destroyed Registers: None * ;***************************************************************** SendSerialByte: CLR TI ;Prepare for transmit MOV SBUF,A ;Send the byte out JNB TI,$ ;Wait for byte to be sent RET ;Done! ;***************************************************************** ;* Function: SendSerial * ;* Purpose: Sends the null-terminated string that immediately * ;* follows the LCALL that called the function to the * ;* serial port. * ;* Input: ACC: Byte to send * ;* Output: None. * ;* Destroyed Registers: None * ;***************************************************************** SendSerial: POP DPH POP DPL PUSH ACC PUSH PSW CLR TI SendSerial1: CLR A MOVC A,@A+DPTR INC DPTR JZ SendSerialExit MOV SBUF,A JNB TI,$ CLR TI ;(v1.3.1) - Removed LF-appending ; CJNE A,#13,SendSerial1 ; MOV A,#10 ; MOV SBUF,A ; JNB TI,$ ; CLR TI SJMP SendSerial1 SendSerialExit: POP PSW POP ACC PUSH DPL PUSH DPH RET ;***************************************************************** ;* Function: SendSerialHexByte * ;* Purpose: Sends the byte in the accumulator to the serial * ;* port as two hexadecimal nibbles. * ;* Input: ACC: Byte to send * ;* Output: None. * ;* Destroyed Registers: None * ;***************************************************************** SendSerialHexByte: PUSH 00h ;Save R0 PUSH ACC ;Save ACC LCALL ByteTo2Hex ;Convert high byte to two bytes of BCD LCALL SendSerialByte ;Send the high byte MOV A,R0 ;Send the low byte LCALL SendSerialByte ;Send it POP ACC ;Restore R0 POP 00h ;Restore R0 RET ;################################################################# ;# # ;# SPI PROTOCOL SUBROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: SPI_ReadByte * ;* Purpose: Reads a byte from the SPI port. Accomplished by * ;* sending a FFh dummy byte and falling through to the * ;* SPI_SendWait routine. * ;* Input: None * ;* Output: ACC=Byte receivedd * ;* Destroyed Registers: None * ;***************************************************************** SPI_ReadByte: MOV A,#0FFh ;***************************************************************** ;* Function: SPI_SendWait * ;* Purpose: Sends a byte to the SPI port and waits for the * ;* send to complete. Returns any byte that was read * ;* in the process. * ;* Input: ACC=Byte to send, if any * ;* Output: ACC=Byte received * ;* Destroyed Registers: None * ;***************************************************************** SPI_SendWait: PUSH 07h ;Protect R7 SETB MISO ;Make sure MISO line is configured as input CLR SCLK ;SCLK starts low MOV R7,#08h ;8 bits to clock in/out SPI_SendLoop: MOV C,ACC.7 ;Get the MSB to clock out MOV MOSI,C ;Set MOSI to bit to clock out SETB SCLK ;Raise SCLK to clock data out MOV C,MISO ;Get the bit to clock in CLR SCLK ;SCLK starts low RLC A ;Insert new bit into current position through carry DJNZ R7,SPI_SendLoop ;Send next bit out POP 07h ;Protect R7 RET ;################################################################# ;# # ;# I2C PROTOCOL SUBROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: SI2CSTA * ;* Purpose: Intiates an I2C conversation. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** SI2CSTA: SETB SDA SETB SCL LCALL WAIT47us CLR SDA LCALL WAIT4us CLR SCL SI2CSTAexit: RET ;***************************************************************** ;* Function: SI2CSTO * ;* Purpose: Sends an I2C stop signal. * ;* Input: None * ;* Output: Carry: 0=Success, 1=Error * ;* Destroyed Registers: None * ;***************************************************************** SI2CSTO: CLR SDA LCALL WAIT4us SETB SCL LCALL WAIT47us ORL C,/SCL ;test SCL SETB SDA LCALL WAIT47us ORL C,/SDA ;test SDA RET ;***************************************************************** ;* Function: SI2CRES * ;* Purpose: Resets the I2C bus * ;* Input: None * ;* Output: Carry: 0=Success, 1=Error * ;* Destroyed Registers: None * ;***************************************************************** SI2CRES: ; LCALL SI2CWAIT ; JC ?si2crs2 MOV R2, #9 ;try it 9 times si2crs1: CLR C CLR SCL LCALL SI2CSTO ;try sending stop JNC si2crs2 DJNZ R2, si2crs1 si2crs2: RET ;***************************************************************** ;* Function: SI2COUT * ;* Purpose: Sends byte out I2C bus. * ;* Input: ACC=Byte to be sent * ;* Output: Carry: 0=Success, 1=Error, no ACK received * ;* Destroyed Registers: None * ;***************************************************************** SI2COUT: SETB C SJMP si2cio0 ;***************************************************************** ;* Function: SI2CIN * ;* Purpose: Receives a byte from the I2C bus * ;* Input: ACC=Byte to send, if any. * ;* Carry: 0=Send ACK, 1=Send NAK * ;* Output: ACC=Byte received * ;* Carry: 0=Success, 1=Error, no ACK received * ;* Destroyed Registers: None * ;***************************************************************** SI2CIN: MOV A,#0FFh si2cio0: PUSH 02h si2cio1: MOV R2,#9 ;8 Bit + ACK si2cio2: RLC A ;MSB first MOV SDA,C ;bit out LCALL WAIT47us SETB SCL LCALL WAIT4us MOV C,SDA ;bit in CLR SCL DJNZ R2,si2cio2 POP 02h RET ;################################################################# ;# # ;# LCD COMMUNICATION SUBROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: InitializeLCD * ;* Purpose: Initializes the LCD by sending it the 38h, 0Eh, and * ;* 06h commands. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** InitializeLCD: MOV A,#38h JNB LCDDirect4,InitLCD2 MOV A,#28h InitLCD2: LCALL SendLCDCommand MOV A,#0Eh LCALL SendLCDCommand MOV A,#06h LCALL SendLCDCommand RET ;***************************************************************** ;* Function: ClearLCD * ;* Purpose: Clears the LCD's screen by sending it the 01h * ;* command. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: ACC * ;***************************************************************** ClearLCD: MOV A,#01h LCALL SendLCDCommand RET ;***************************************************************** ;* Function: ReadLCDText * ;* Purpose: Reads the character from the current cursor * ;* position of the LCD. * ;* Input: None * ;* Output: ACC=Character at current cursor position. * ;* Destroyed Registers: * ;***************************************************************** ReadLCDText: JB LCDMMMode,ReadLCDText_d MOV DPTR,#LCD_RD_TEXT ;Address to read text from LCD MOVX A,@DPTR ;Read character PUSH ACC ;Protect the value we just read LCALL WaitForLCD ;Wait for the LCD to become not busy POP ACC ;Restore the value we just read RET ;Done ReadLCDText_d: SETB RS ;It's a text event LCALL WaitForLCD_d2 ;The rest of the routine is the ;same as reading a command, so ;use same function. PUSH ACC ;Protect the value we just read LCALL WaitForLCD ;Wait for the LCD to become not busy POP ACC ;Restore the value we just read RET ;***************************************************************** ;* Function: WaitForLCD * ;* Purpose: Repeatedly polls the LCD and waits for the busy * ;* flag to go low which means the LCD is done doing * ;* whatever it was doing. Returns the final status of * ;* the LCD in the accumulator. * ;* Input: None * ;* Output: ACC=Current status of the LCD. * ;* Destroyed Registers: DPTR, ACC * ;***************************************************************** ReadLCDStatus: WaitForLCD: JB LCDMMMode,WaitForLCD_d ;If in direct mode-jump to direct write routine MOV DPTR,#LCD_RD_COMMAND WaitForLCD_m: MOVX A,@DPTR JB ACC.7,WaitForLCD_m SJMP WaitForLCD_cont WaitForLCD_d: CLR RS ;It's a command WaitForLCD_d2: SETB RW ;It's a read command JNB LCDDirect4,WaitForLCD2 LCALL LCDRead2Nibbles SJMP WaitForLCD3 WaitForLCD2: SETB EN ;Start LCD command MOV P1,#0FFh ;Set all pins to FF initially MOV A,P1 ;Read the return value CLR EN ;Finish the command WaitForLCD3: JB ACC.7,WaitForLCD_d ;If bit 7 high, LCD still busy WaitForLCD_cont: RET LCDRead2Nibbles: ORL LCDPORT,#0F0h ;Be sure to release datalines (set outputlatches ;to '1') so we can read the LCD SETB EN MOV A,LCDPORT ;Read first part of the return value (high nibble) CLR EN ANL A,#0F0h ;Only high nibble is usable PUSH ACC SETB EN MOV A,LCDPORT ;Read second part of the return value (low nibble) CLR EN ANL A,#0F0h ;Only high nibble is usable SWAP A ;Last received is actually low nibble, so put it in place MOV R7,A POP ACC ORL A,R7 ;And combine it with low nibble RET ;***************************************************************** ;* Function: SendLCDText * ;* Purpose: Sends the value in the accumulator to the LCD as * ;* a text character to be displayed on the screen. * ;* Input: ACC=Holds the character to be sent * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** SendLCDText: PUSH ACC PUSH DPH PUSH DPL JB LCDMMMode,SendLCDText_d ;If in direct mode-jump to direct write routine ;Write to LCD via memory-mapped MOV DPTR,#LCD_WR_TEXT MOVX @DPTR,A SJMP SendLCDText_cont SendLCDText_d: CLR RW SETB RS SJMP SendLCDDirectEntry MOV P1,A SETB EN NOP CLR EN SendLCDText_cont: LCALL WaitForLCD POP DPL POP DPH POP ACC RET ;***************************************************************** ;* Function: SendLCDCommand * ;* Purpose: Sends the value in the accumulator to the LCD as * ;* a command to be processed by the LCD. * ;* Input: ACC=Command byte to be sent to the LCD * ;* Output: None * ;* Destroyed Registers: DPTR, ACC * ;***************************************************************** SendLCDCommand: PUSH ACC PUSH DPH PUSH DPL JB LCDMMMode,SendLCDCommand_d ;If in direct mode-jump to direct write routine MOV DPTR,#LCD_WR_COMMAND MOVX @DPTR,A SJMP SendLCDCommand_cont SendLCDCommand_d: CLR RW ;Clear RW for write CLR RS ;Clear RS for command SendLCDDirectEntry: JB LCDDirect4,SendLCDCommand_d4 ;8-bit direct mode MOV LCDPORT,A SETB EN NOP NOP NOP NOP CLR EN SendLCDCommand_cont: LCALL WaitForLCD POP DPL POP DPH POP ACC RET SendLCDCommand_d4: ;4-bit direct mode PUSH ACC ;Save accumulator ORL A,#0Fh ;Don't interfere with P0.0-P0.3 ORL LCDPORT,#0F0h ;Make sure P0.4-P0.7 are set ANL LCDPORT,A ;Send "A" to port SETB EN ;Raise EN for first nibble NOP ;Give LCD time to work NOP NOP NOP CLR EN ;Lower EN POP ACC ;Restore for low nibble SWAP A ;...second nibble ORL A,#0Fh ;Don't interfere with P0.0-P0.3 ORL LCDPORT,#0F0h ;Make sure P0.4-P0.7 are set ANL LCDPORT,A ;Send "A" to port SETB EN ;Raise EN for first nibble NOP ;Give LCD time to work NOP NOP NOP CLR EN ;Lower EN SJMP SendLCDCommand_cont ;################################################################# ;# # ;# DELAY ROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: Delay1ms * ;* Purpose: Delays program execution approximately 1ms. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** Delay1ms: PUSH 06h PUSH 07h MOV R6,#2 Delay1ms2: MOV R7,#223 DJNZ R7,$ DJNZ R6,Delay1ms2 POP 07h POP 06h RET ;***************************************************************** ;* Function: Delay500ms * ;* Purpose: Delays program execution approximately 500ms. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** Delay100ms: PUSH 06h PUSH 07h MOV R6,#2 Delay100ms2: MOV R7,#50 Delay100ms3: LCALL Delay1ms DJNZ R7,Delay100ms3 DJNZ R6,Delay100ms2 POP 07h POP 06h RET ;***************************************************************** ;* Function: WAIT4us * ;* Purpose: Waits for 4 microseconds and returns. The LCALL to * ;* the function and the RET consume 4 cycles alone. At * ;* 11.0592 MHz the system executes 921,600 instruction * ;* cycles per second or one cycle every 1.085 micro- * ;* seconds. So if we want to wait 4 microsseconds we * ;* need to wait 3.6 instruction cycles. So just the * ;* LCALL and RET results in a 4.34 microsecond delay * ;* which is plenty. But additional NOPs could be * ;* inserted here if a different processor or a * ;* different crystal were used * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** WAIT4us: NOP NOP NOP NOP NOP RET ;***************************************************************** ;* Function: WAIT47us * ;* Purpose: Waits for 47 microseconds using the WAIT4us * ;* routinely. Actually waits closer to 52 useconds * ;* but that's ok for our I2C timing needs. * ;* Input: None * ;* Output: None * ;* Destroyed Registers: None * ;***************************************************************** WAIT47us: PUSH 07h MOV R7,#12 Wait47Loop: LCALL WAIT4us DJNZ R7,Wait47Loop POP 07h RET ;################################################################# ;# # ;# OTHER ROUTINES # ;# # ;################################################################# ;***************************************************************** ;* Function: SendDecimalR4567 * ;* Purpose: Takes the value in R4 (high byte), R5, R6, and R7 * ;* (low byte) and sends it to the serial port as a * ;* decimal value with commas. * ;* Input: R4-R7: Value to display as decimal * ;* Output: None * ;* Destroyed Registers: ??? * ;***************************************************************** SendDecimalR4567: PUSH DPH PUSH DPL MOV R1,#00h ;Count number of digits DD4567Loop: MOV R0,#04h ;Point to high byte of value LCALL Check4BytesForZero ;Check to see if remaining value is zero JZ DD4567Done ;If zero then nothing more to process LCALL DivideBy10 ;Divide value by 10 INC R1 ;Increment digit counter PUSH ACC ;Store this value on the stack SJMP DD4567Loop ;Do it again DD4567Done: ;Now must take numbers off the stack and display them CJNE R1,#00h,DD4567NextDigit MOV A,#00h INC R1 PUSH ACC DD4567NextDigit: POP ACC ADD A,#'0' LCALL SendSerialByte MOV A,R1 DEC A JZ DD4567Next MOV B,#03h DIV AB MOV A,B JNZ DD4567Next MOV A,#',' LCALL SendSerialByte DD4567Next: DJNZ R1,DD4567NextDigit POP DPL POP DPH RET ;***************************************************************** ;* Function: Check4BytesForZero * ;* Purpose: Verifies that the internal RAM address pointed to * ;* by R0--and the three following addresses--are all * ;* zero. * ;* Input: R0: Address of first byte to check for zero. * ;* Output: ACC: 0=All 4 bytes are zero, !0=Some byte isn't zero* ;* Destroyed Registers: None * ;***************************************************************** Check4BytesForZero: PUSH B MOV B,R0 ; save entry pointer PUSH B MOV B,R2 ; preserve R2 CLR A ; start out with a zero value marker MOV R2,#4 ; test a 5 byte number C4BFZ_Loop: ORL A, @R0 ; just or each byte into A INC R0 ; advance to next byte DJNZ R2,C4BFZ_Loop ; decrement the byte counter MOV R2,B ; restore R2 value POP B MOV R0,B ; restore the entry pointer POP B RET ; exit A as final or of all 5 bytes ;***************************************************************** ;* Function: DivideBy10 * ;* Purpose: Takes the value contained at @R0 (high byte), * ;* @R0+1, @R0+2, and @R0+3 and divides by 10. Leaves * ;* the quotient in the same IRAM locations and the * ;* remainder in the accumulator. * ;* Author: Michael Karas * ;* Source: http://www.8052.com/forum/read.phtml?id=77273 * ;* Input: R0: Address of first byte to divide by 10. * ;* Output: ACC: Remainder. * ;* Destroyed Registers: None * ;***************************************************************** DivideBy10: PUSH B ; save entry B register MOV B,R0 ; save the entry pointer R0 value PUSH B MOV B,R2 ; save entry R2 value PUSH B MOV B,R3 ; save entry R3 value PUSH B MOV A,@R0 ; get the MS byte of dividend and divide MOV B,#10 ; it by the divisor of 10. DIV AB MOV @R0,A ; store result of division as quotient. ; MOV A,B ; save the remainder (which must be in SWAP A ; the range 0...9) shifted four bits MOV B,A ; to the left. ; INC R0 ; point to next byte of the number MOV R2,#3 ; load loop counter. ;4 bytes ; DIV_BY_10_LOOP: MOV A,@R0 ; get the MS nibble of the SWAP A ; dividend into the accumulator. ANL A,#0x0F ORL A,B ; or in remainder from last iteration. ; MOV B, #10 ; divide MS nibble by the divisor of 10 DIV AB SWAP A ; save partial result (which must be MOV R3, A ; in the range 0...9) shifted 4 bits. MOV A,B ; read remainder (which must be in SWAP A ; the range 0...9) and shift 4 bits left. ; XCHD A,@R0 ; get next nibble of the dividend into acc ; MOV B,#10 ; divide by the divisor of 10. DIV AB ORL A, R3 ; or in the previously saved partial result. MOV @R0,A ; save the MSB of the quotient ready ; to be returned. ; MOV A, B ; save remainder SWAP A ; shifted 4 bits left MOV B, A ; in B. ; INC R0 ; increment the pointer through number buffer DJNZ R2,DIV_BY_10_LOOP ; decrement number of bytes we are working across SWAP A ; final remainder is in upper nibble ; POP B MOV R3, B ; restore original R3 POP B MOV R2, B ; restore original R2 POP B MOV R0, B ; restore the original R0 POP B ; restore entry B RET ;***************************************************************** ;* Function: ShiftR765Left * ;* Purpose: Rotates left one bit R5 (high byte), R6, and * ;* R7 (low byte). Highest bit is discarded. * ;* Input: R5-R7: Value to rotate left by on bit * ;* Output: R5-R7: Shifted value. * ;* Destroyed Registers: ACC * ;***************************************************************** ShiftR765Left: CLR C MOV A,R7 RLC A MOV R7,A ; MOV A,R6 RLC A MOV R6,A ; MOV A,R5 RLC A MOV R5,A RET AddR67to23: PUSH ACC MOV A,R7 ADD A,R3 MOV R3,A MOV A,R6 ADDC A,R2 MOV R2,A POP ACC RET AddR4567to0123: LCALL AddR67to23 PUSH ACC MOV A,R5 ADDC A,R1 MOV R1,A MOV A,R4 ADDC A,R0 MOV R0,A POP ACC RET ;***************************************************************** ;* Function: Make2DigitDecimal * ;* Purpose: Takes a value in accumulator (0-99) and makes it * ;* a value 00h-09h in accumulator (high byte) and * ;* B as low byte. * ;* Input: Acc: Value to calculate * ;* Output: Acc: High byte of result * ;* B: Low byte of result * ;***************************************************************** Make2DigitDecimal: MOV B,#10 ;Going to divde by 10 DIV AB ;Divide it ORL B,#30h ORL A,#30h RET SetDptrMMKey1: MOV DPTR,#KEY_ROW0 ;Keypad row 0 RET SetDptrMMKey2: MOV DPTR,#KEY_ROW1 ;Keypad row 1 RET SetDptrMMKey3: MOV DPTR,#KEY_ROW2 ;Keypad row 2 RET SetDptrMMKey4: MOV DPTR,#KEY_ROW3 ;Keypad row 0 RET SetDptrMMLcdWC: MOV DPTR,#LCD_WR_COMMAND;LCD write command RET SetDptrMMLcdWT: MOV DPTR,#LCD_WR_TEXT ;LCD write text RET SetDptrMMLcdRC: MOV DPTR,#LCD_RD_COMMAND;LCD read command/status RET SetDptrMMLcdRT: MOV DPTR,#LCD_RD_TEXT ;LCD read text RET GetKeyDebounced: ; Return keypad key with debounce PUSH B ;(v1.3.2) Protect B MOV A,R7 ;(v1.3.2) PUSH ACC ;(v1.3.2) Protect R7 MOV A,R6 ;(v1.3.2) PUSH ACC ;(v1.3.2) Protect R6 MOV A,R3 ;(v1.3.2) PUSH ACC ;(v1.3.2) Protect R3 MOV A,R1 ;(v1.3.2) PUSH ACC ;(v1.3.2) Protect R1 PUSH DPH ;(v1.3.2) Protect DPTR PUSH DPL ;(v1.3.2) Protect DPTR GKD_Loop: MOV R3,#03h GKD_Cycle: GKD_ScanRow: ;We must first set DPTR to an address that corresponds to the address ;of the keypad row we wish to read. R3 holds the row we want to read ;so we read the high byte of DPTR (DPH) out of the KP_AddrTable. We ;don't care what the low byte of DPTR (DPL) is since any value of DPL ;will still address the right line of the keypad. MOV A,R3 ;Get current row (0-3) ;1 MOV DPTR,#KP_AddrTable ;Point to the address table ;3 MOVC A,@A+DPTR ;Get the DPTR high byte from the table ;1 MOV DPTR,#KEYPAD_ZONE ;(v1.3.2) High byte is always 50h for area of memory map for keypad MOV DPL,A ;Move to low byte of DPH, DPL doesn't matter ;2 ;We now have the memory address for the keypad row we wish to access. So ;we now read it into the accumulator with the MOVX command. MOVX A,@DPTR ;Read the status of the keypad row ANL A,#0Fh ;Only interest in low 4 bits XRL A,#0Fh ;We now invert all bits, so pressed key=1, not-pressed=0 JNZ GKD_ProcessKey ;If the value is not zero then a key was pressed, so process it DEC R3 ;Decrement row counter CJNE R3,#0FFh,GKD_Cycle ;If it hasn't decremented to FF then process next row ;No key was found, so return CLR A ;Clear accumulator SJMP GKD_Exit ;Exit the routine (to pop the stack) GKD_ProcessKey: MOV R1,A ;Hold the keypress in R1 ; CJNE R2,#01h,GKD_DebounceDone ;If debounce disabled, don't do debounce ;If we get here it means a key was pressed. The first thing we need to do is debounce it. ;We do this by re-reading the same row for 1/20th of a second. If the value does not ;change for 1/20th of a second then we assume it's a valid keypress. Otherwise we jump ;out back to the keypad scan routine. 1/20th of a second at 11.0592MHz is about ;46,080 instruction cycles MOV R6,#23 ;23 * 256 = 7680 * 8 = approx. 46,080 cycles = Approx 1/20th second GKD_DebounceLoop2: MOV R7,#00h ;Set R7 to 0 for debounce loop (256 loops) GKD_DebounceLoop: MOVX A,@DPTR ;Re-read the same keypad row 2 ANL A,#0Fh ;Only interest in low 4 bits XRL A,#0Fh ;Invert all the bits 1 XRL A,R1 ;Exclusive OR with the original keypress 1 JNZ GKD_Loop ;If new value not same as last value, debounce it! 2 DJNZ R7,GKD_DebounceLoop ;Loop R7 times 2 DJNZ R6,GKD_DebounceLoop2 ;Loopr R6 times GKD_DebounceDone: ;If we get here then the key has been debounced, so we need to decide what key ;was pressed. The value of R1 will either be 1 (first key), 2 (second key), ;4 (third key), or 8 (fourth key). So we set R0=0 and increment R0 once each ;time we shift the value right until the carry bit is set, then we're done and ;R0 will hold 0, 1, 2 or 3 for which key in the row was pressed. CLR C ;Make sure carry starts clear MOV A,R1 ;Accumulator gets copy of the value read MOV R0,#00h ;R0 starts at zero GKD_FindKeyNum: RRC A ;Rotate the value right into the carry JC GKD_KeyFound ;If it's set then we found our pressed key and exit INC R0 ;Increment the key number indicator SJMP GKD_FindKeyNum ;Keep searching until we find the key GKD_KeyFound: ;We protect the address of DPTR so we can wait for that key to be ;released later MOV R1,#KeypadDPH ;Point to KeypadDPH buffer MOV @R1,DPH MOV R1,#KeypadDPL ;Point to KeypadDPL buffer MOV @R1,DPL ;We now have R0 with the offset for the key (0, 1, 2, or 3). And R3 holds the ;row number. So we take the row number (R3) multiply it by 4 (two rotates to the ;left) and then add R0. We then get the key from the offset in the KeyTable. MOV A,R3 ;Get row number RL A ;Multiply by 2 RL A ;Multiply by 4 ADD A,R0 ;Add the key offset MOV DPTR,#KP_KeyTable ;Get the start address of the key table MOVC A,@A+DPTR ;Get the key ;Accumulator now has the key we pressed, so we return it GKD_Exit: MOV B,A ;Hold return value in B temporarily POP DPL ;(v1.3.2) Restore DPTR POP DPH ;(v1.3.2) Restore DPTR POP ACC ;(v1.3.2) MOV R1,A ;(v1.3.2) Restore R1 POP ACC ;(v1.3.2) MOV A,R3 ;(v1.3.2) Restore R3 POP ACC ;(v1.3.2) MOV A,R6 ;(v1.3.2) Restore R6 POP ACC ;(v1.3.2) MOV A,R7 ;(v1.3.2) Restore R7 MOV A,B ;(v1.3.2) Restore return value temporarily held in B POP B ;(v1.3.2) Restore B RET WaitKeyReleased: ; Wait for keypad key to be released PUSH ACC ;(v1.3.2) Protect accumulator MOV A,R0 ;(v1.3.2) PUSH ACC ;(v1.3.2)Protect R0 PUSH DPH ;(v1.3.2) Protect DPH PUSH DPL ;(v1.3.2) Protect DPL ;We now go back to reading the same row of the keypad and wait until it ;goes back to FF which represents no keys pressed. MOV R0,#KeypadDPH ;Point to KeypadDPH buffer MOV DPH,@R0 MOV R0,#KeypadDPL ;Point to KeypadDPL buffer MOV DPL,@R0 WKR_Wait: MOVX A,@DPTR ;Read the keypad ANL A,#0Fh ;Only interest in low 4 bits CJNE A,#0Fh,WKR_Wait ;If it's not 0Fh then keep waiting WKR_Done: POP DPL ;(v1.3.2) Restore DPL POP DPH ;(v1.3.2) Restore DPH POP ACC ;(v1.3.2) MOV R0,A ;(v1.3.2) Restore R0 POP ACC ;(v1.3.2) Restore accumulator RET