************************************************************************** * * Title: Calculator * * Objective: CMPEN 472 Homework 7 * * Revision: V1.0 * * Date: Mar. 17, 2025 * * Programmer: Jacob McDonnell * * Company: The Pennsylvania State University * Department of Computer Science and Engineering * * Algorithm: Simple Serial I/O, ASCII to decimal conversion, & basic math * * Register Use: A & B to current byte, etc, * X & Y holds address of strings and length of string, * D & Y for arithmatic. * * Memory Use: RAM Locations from $3000 for data, * RAM Locations from $3100 for program * * Input: Serial Port for User Input * * Output: Serial Port for String Output * Memory locations changed if modified by user * * Observation: This program is a simple 16-bit calculator. The input can be * any 4 digit positive decimal integer, the output can be negative. * The +, -, *, and / operators are supported. Only two numbers at * a time. * * Note: ON CSM-12C128 board, * * Comments: This program is developed and simulated using CodeWarrior * development software and targeted for Axion * Manufacturing's CSM-12C128 board running at 24MHz. * ************************************************************************** * Parameter Declearation Section * * Export Symbols xdef pgstart ; export 'pgstart' symbol absentry pgstart ; for assembly entry point * Symbols and Macros PORTA equ $0000 ; i/o port A addresses DDRA equ $0002 ; data direction register for PORTA PORTB equ $0001 ; i/o port B addresses DDRB equ $0003 ; data direction register for PORTB SCIBDH equ $00C8 ; Serial port (SCI) Baud Register H SCIBDL equ $00C9 ; Serial port (SCI) Baud Register L SCICR2 equ $00CB ; Serial port (SCI) Control Register 2 SCISR1 equ $00CC ; Serial port (SCI) Status Register 1 SCIDRL equ $00CF ; Serial port (SCI) Data Register CR equ $0d ; carriage return, ASCII 'Return' key LF equ $0a ; line feed, ASCII 'next line' character NULL equ $00 ; NULL Terminator character ************************************************************************** * Data Section: address used [ $3000 to $30FF ] RAM Memory * org $3000 ; Reserved RAM memory starting address ; for Data for CMPEN 472 class numBuf dc.w $0000 ; Memory location for addition and subtraction ; in Solve subroutine operator dc.b $0000 ; Operator character for VerifyInput buffer ds.b $0010 ; Array of 16 bytes to read a string dc.b NULL ; NULL terminated lenBuf dc.w $0010 ; Length of buffer array buffer2 ds.b $0010 ; Array of 16 bytes for reading and reversal dc.b NULL ; NULL terminated lenBuf2 dc.w $0010 ; length of buffer2 * * There is a section Data Section at the end of the file ************************************************************************** * Program Section: address used [ $3100 to $3FFF ] RAM Memory * org $3100 ; Program start address, in RAM pgstart lds #$3100 ; initialize the stack pointer ldaa #%11110001 ; LED 1,2,3,4 at PORTB bit 4,5,6,7 staa DDRB ; set PORTB bit 4,5,6,7 as output ldaa #$0C ; Enable SCI port Tx and Rx units staa SCICR2 ; disable SCI interrupts ldd #$0001 ; Set SCI Baud Register = $0001 => 1.5M baud at 24MHz (for simulation) std SCIBDH ; SCI port baud rate change ldx #msg ; Load the address of the welcome message into X jsr WriteString ; Write the string to the serial console mainLoop ldx #buffer ; Load the address of the buffer into X ldy lenBuf ; Load the length of the buffer into Y jsr Zeros ; Fill the buffer with zeros ldx #prompt ; Load Address of prompt string into X jsr WriteString ; Write string to serial console ldx #buffer ; Load the address of the buffer into X ldy lenBuf ; Load the length of the buffer into Y jsr ReadString ; Read a line from the serial console ldx #buffer ; Load the address of the buffer into X jsr VerifyInput ; Verify user input beq mainLoop ; Invalid input, jump back to loop ldx #buffer ; Load the address of the buffer into X jsr Solve ; Jump to Solve to solve the user input bra mainLoop ; Loop back to mainLoop always ************************************************************************** * Subroutine Section: address used [ $3100 to $3FFF ] RAM Memory * ;************************************************************************* ; Solve subroutine ; ; This subroutine will solve the math equation in the given string. ; ; Input: Address of null terminated string in X. ; Output: No Output, Control flow changed to proper subroutine. ; Registers in use: X for the address of the string, A & B to read characters from ; from the string. ; Memory locations in use: Memory Address for serial line, address of the string, numBuf word ; ; Comments: This subroutine EXPECTS the input to be valid. RUN VerifyInput BEFORE ; TO MAKE SURE THE STRING IS VALID, OTHERWISE THERE WILL BE ERRORS. ; Solve pshy ; Save Y to the stack pshd ; Save D to the stack pshx ; Save X to the stack jsr ReadDecimal ; Read First number pshy ; Save Y to the stack ldaa -1,x ; Read operator from X and save to A jsr ReadDecimal ; Read First number cmpa #'+' ; Compare A to '+' beq sAdd ; Jump to sAdd to add the numbers cmpa #'-' ; Compare A to '-' beq sSub ; Jump to sSub to add the numbers cmpa #'*' ; Compare A to '*' beq sMul ; Jump to sMul to add the numbers exg y,x ; Exchange X and Y puly ; Restore Y from the stack exg y,d ; Exchange Y and D idiv ; Divide D/X => X exg x,d ; Exchange X & D bra sDone ; Jump to sDone sAdd sty numBuf ; Save Y to numBuf puly ; Restore Y from the stack exg y,d ; Exchange Y and D addd numBuf ; Add D and numBuf bra sDone ; Jump to sDone sSub sty numBuf ; Save Y to numBuf puly ; Restore Y from the stack exg y,d ; Exchange Y and D subd numBuf ; Subtract D and numBuf bra sDone ; Jump to sDone sMul exg y,d ; Exchange Y and D puly ; Restore Y from the stack emul ; Multiply Y*D => Y:D cpy #0 ; Compare Y to 0 bne sOverflow ; If Y != 0, Overflow sDone cpd #9999 ; Compare D to 9999 (Max output) bgt sOverflow ; Branch to sOverflow if D > 9999 cpd #-9999 ; Compare D to -9999 (Min output) blt sOverflow ; Branch to sOverflow if D < -9999 pulx ; Restore X from the stack jsr WriteString ; Write original equation to serial console psha ; Save A to the stack ldaa #'=' ; Load '=' into A jsr putchar ; Print '=' to serial console pula ; Restore A from the stack ldy #buffer ; Load address of buffer into Y jsr PrintDecimalWord; Print the answer to the Serial console ldaa #CR ; Load CR into A jsr putchar ; Jump to putchar to write CR ldaa #LF ; Load LF into A jsr putchar ; Jump to putchar to write CR puld ; Restore D from the stack puly ; Restore Y from the stack rts ; Return to caller sOverflow pulx ; Restore X from the stack pshx ; Save X to the stack jsr WriteString ; Write original string to serial console ldaa #CR ; Load CR into A jsr putchar ; Jump to putchar to write CR ldaa #LF ; Load LF into A jsr putchar ; Jump to putchar to write CR ldx #overflow ; Load address of overflow string into X jsr WriteString ; Write overflow string to serial pulx ; Restore X from the stack puld ; Restore D from the stack puly ; Restore Y from the stack rts ; Return to caller ;************************************************************************* ; VerifyInput subroutine ; ; This subroutine will verify the user input is valid. ; ; Input: An address of a Null terminated string in register X. ; Output: If valid Zero bit = 0 in CCR, if invalid, Zero bit = 1 ; and the string is outputed up to the error on the serial ; console with an error message. ; Registers in use: X for the address of the string, A for reading characters. ; Y to count the number of digits in a number, & B to count the number of numbers ; Memory locations in use: Memory Address for serial line, address of the string, 1 byte for operator ; ; Comments: This subroutine will modify the user string if invalid. ; VerifyInput pshy ; Save Y to the stack pshd ; Save D to the stack pshx ; Save X to the stack clrb ; Set B to Zero stab operator ; Clear operator ldy #0 ; Load Zero into Y vNumLoop ldaa 1,x+ ; Load character from X into A cmpa #'9' ; Compare A to '9' bhi vInvalid ; If A > '9', not valid string cmpa #'0' ; Compare A to '0' blt vIsOp ; If A < '0', check if operator iny ; Increment Y by 1 to count numbers cpy #4 ; Compare Y to 4 bhi vInvalid ; If greater than 4, invalid bra vNumLoop ; Loop back to check for more digits vIsOp cmpa #'+' ; Compare A to '+' beq vOp ; This is an operator cmpa #'-' ; Compare A to '-' beq vOp ; This is an operator cmpa #'*' ; Compare A to '*' beq vOp ; This is an operator cmpa #'/' ; Compare A to '/' beq vOp ; This is an operator cmpa #NULL ; Compare A to NULL character beq vEndOfLine ; Check if end of line vInvalid clra ; Set A to zero staa 0,X ; Write Null terminator to X pulx ; Restore X from the stack pshx ; Save X to the stack jsr WriteString ; Write invalid string to serial console ldaa #CR ; Load CR into A jsr putchar ; Jump to putchar to write the character ldaa #LF ; Load LF into A jsr putchar ; Jump to putchar to write the character ldx #invalidStr ; Load address of invalid string into X jsr WriteString ; Write invalid string to serial console ldaa #4 ; Load 4 into A to set zero bit tap ; Transfer A to CCR pulx ; Restore X from the stack puld ; Restore D from the stack puly ; Restore Y from the stack rts ; Return to caller vOp staa operator ; Store operator in operator buffer ldaa -2,x ; Load previous character into A cmpa #'0' ; Compare A to '0' blt vInvalid ; Invalid string since previous is not number cmpa #'9' ; Compare A to '9' bhi vInvalid ; Invalid string since previous is not number addb #1 ; Increment B by 1 cmpb #1 ; Compare B to 1 bhi vInvalid ; Invalid string since B > 1 and we have another operator ldy #0 ; Set Y to 0 bra vNumLoop ; Check for next number vEndOfLine ldaa -2,x ; Load previous character into A cmpa #'0' ; Compare A to '0' blt vInvalid ; Invalid string since previous is not number cmpa #'9' ; Compare A to '9' bhi vInvalid ; Invalid string since previous is not number ldaa operator ; Load operator character from buffer beq vInvalid ; If operator buffer is zero, invalid clra ; Set A to zero tap ; Transfer A to CCR pulx ; Restore X from the stack puld ; Restore D from the stack puly ; Restore Y from the stack rts ; Return to caller ;************************************************************************* ; ReadDecimal subroutine ; ; This subroutine will read an ASCII string of a number in decimal and convert it to ; its value. ; ; Input: A memory address in register X. ; Output: The value of the number in the Y register, and any errors printed ; to the serial line. Zero bit is set if error occurs. ; Registers in use: X for the address of the contents and for a buffer while printing, ; D for multiplication, B for the character, Y for output value. ; Memory locations in use: Memory Address for serial line, address of the string ; ; Comments: This subroutine will return the value in the Y register, and if an error occurs, ; the Zero bit in the CCR will be set. ; ReadDecimal pshd ; Save D to the stack ldy #0 ; Clear Y register dHLoop ldab 1,x+ ; Read Next character from X beq dHDone ; If B == 0, exit loop cmpb #'+' ; Compare B to '+' beq dHDone ; If B == '+', end of number cmpb #'-' ; Compare B to '-' beq dHDone ; If B == '-', end of number cmpb #'*' ; Compare B to '*' beq dHDone ; If B == '+', end of number cmpb #'/' ; Compare B to '/' beq dHDone ; If B == '-', end of number cmpb #' ' ; Compare B to space character beq dHDone ; If B == ' ', exit loop cmpb #'0' ; Compare B to '0' character blt dHError ; If B < '0', bad address, exit loop cmpb #'9' ; Compare B to '9' character bhi dHError ; If B > '9', check if 'A'-'F' characters subb #'0' ; Subtract '0' from B to get true value pshb ; Save B to the stack ldd #10 ; load 10 into D emul ; Multiply Y and D exg d,y ; Transfer data from D to Y pulb ; Restore b from the stack aby ; Add B to Y bra dHLoop ; Branch always to rHLoop dHDone clra ; clear A accumulator tap ; Transfer A into CCR to clear zero bit puld ; Restore D from the stack rts ; Return to caller dHError ldaa #4 ; Load 4 into A to set zero bit in CCR tap ; Transfer A into CCR to set zero bit and warn error puld ; Restore D from the stack rts ; Return to caller ;************************************************************************* ; strrev subroutine ; ; This subroutine will reverse a string from one buffer into another. ; ; Input: Address of null terminated string in X, address of a large enough ; buffer in Y. ; Output: The string in X reversed in Y. ; Registers in use: X for the address of the string, Y for the address of the buffer, ; A to read characters from the string. ; Memory locations in use: Memory Address for serial line, address of the string & buffer ; ; Comments: This subroutine will not check that the output buffer is large enough, that ; is the job of the caller. ; strrev pshx ; Save X to the stack pshy ; Save Y to the stack psha ; Save A to the stack revLoop ldaa 1,y- ; Load Character from Y into A, decrement Y beq revDone ; If Character is 0, exit loop staa 1,x+ ; Save character in address in X, increment X bra revLoop ; Loop back always clra ; Set A to Zero revDone staa 1,x+ ; Copy Null terminator into new string pula ; Restore A from the stack puly ; Restore Y from the stack pulx ; Restore X from the stack rts ; Return to caller ;************************************************************************* ; PrintDecimalWord subroutine ; ; This subroutine will print a given word of data to the serial in binary. ; ; Input: 1 word of data in register D, Buffer Address in Y ; Output: Decimal representation of the data on the serial console ; Registers in use: Y for the address of the buffer, X to count the number of bits ; written and for division, D for the input, A for characters. ; Memory locations in use: Memory addresses for serial, and operator to hold sign ; ; Comments: This subroutine requires serial to be setup and putchar subroutine. ; PrintDecimalWord pshx ; Save X to the stack pshd ; Save D (A:B) to the stack pshy ; Save Y to the stack cpd #0 ; Compare D to zero beq dIsZero ; Branch to hIsZero blt dIsNegative ; If D < 0, Jump to dIsNegative dAfterNeg psha ; Save A to the stack pshy ; Save Y to the stack pshx ; Save x to the stack ldaa #'0' ; Load the '0' character into A ldx #buffer2 ; Load the address of buffer2 into X ldy #5 ; Load 5 into Y jsr memset ; Write '0' to the first 5 bytes in buffer2 pulx ; Restore X from the stack puly ; Restore Y from the stack clra ; Set A to zero staa 0,y ; Load Zero into Y for Null Terminator pula ; Restore A from the stack dPrintLoop ldx #10 ; Load 10 in X for division idiv ; Divide D / 10 to get Hex Digit cpx #0 ; Compare X to 0 beq dCheck ; If X == 0, branch to check D is zero dDNotZero addb #'0' ; Add '0' to B to get ASCII Character stab 1,+y ; Save character from B to Y exg X,D ; Swap values in X and D bra dPrintLoop ; Loop to hPrintLoop dCheck cpd #0 ; Compare D to 0 bne dDNotZero ; If D != 0, branch back to hDNotZero dPrintDone ldaa operator ; Load operator into A to see if negative cmpa #'-' ; Compare A to '-' bne dNotNeg ; If A != '-', jump to dNotNeg staa 1,+y ; Save '-' into buffer dNotNeg ldx #buffer2 ; Load the address of buffer2 in X jsr strrev ; Reverse string in Y in buffer in X jsr WriteString ; Jump to write string to write the number ldy lenBuf2 ; Load the length of buffer2 into Y ldx #buffer2 ; Load the address of buffer2 into X jsr Zeros ; Fill buffer2 with zeros puly ; Restore Y from the stack puld ; Restore D (A:B) from the stack pulx ; Restore X from the stack rts ; Return to caller dIsZero ldaa #'0' ; Load '0' character into A jsr putchar ; Print character to the screen puly ; Restore Y from the stack puld ; Restore D (A:B) from the stack pulx ; Restore X from the stack rts ; Return to caller dIsNegative psha ; Save A to the stack ldaa #'-' ; Load '-' into A staa operator ; Save '-' to operator buffer pula ; Restore A from the stack nega ; Two's complement of A suba #1 ; Subtract 1 from A negb ; Two'complement of B subb #1 ; Subtract 1 from B addd #1 ; Add 1 to D bra dAfterNeg ; Jump back to dAfterNeg ;************************************************************************* ; Zeros subroutine ; ; This subroutine will write zeros to every byte in a given array. ; ; Input: Address of an array in X and its length in Y ; Output: Zeros in every byte of an array. ; Registers in use: X for the address of the array, Y for the length, and A for 0 ; Memory locations in use: Memory Address of the array ; ; Comments: This subroutine requires serial to be setup and putchar subroutine. ; Zeros psha ; Save A to the Stack clra ; Clear A zerosLoop staa 1,x+ ; Load A into byte at X dbne y,zerosLoop ; Decrement Y and loop if Y != 0 pula ; Restore A from the stack rts ; Return to caller ;************************************************************************* ; memset subroutine ; ; This subroutine will write a given byte to every byte in a given array. ; ; Input: Address of an array in X and its length in Y, the byte in A ; Output: The given byte in every byte of an array. ; Registers in use: X for the address of the array, Y for the length, and A for the given byte ; Memory locations in use: Memory Address of the array ; ; Comments: This subroutine requires serial to be setup and putchar subroutine. ; memset staa 1,x+ ; Load A into byte at X dbne y,memset ; Decrement Y and loop if Y != 0 rts ; Return to caller ;************************************************************************* ; WriteString subroutine ; ; This subroutine will write a given null terminated string to the serial. ; ; Input: Address of null terminated string in X ; Output: Null terminated string written to serial ; Registers in use: X for the address of the string and A for the current byte ; Memory locations in use: Memory Address for serial line, address of the string ; ; Comments: This subroutine requires serial to be setup and putchar subroutine. ; WriteString psha ; Save A to the stack writeLoop ldaa 1,x+ ; Load the byte at addr in X, then add 1 beq doneWrite ; if A == 0, branch to doneWrite jsr putchar ; Jump to putchar to write byte to serial bra writeLoop ; branch always to writeLoop doneWrite pula ; restore A from the stack rts ; return to caller ;************************************************************************* ; ReadString subroutine ; ; This subroutine will read a string from the serial line to a given address. ; ; Input: Address of an array in X ; Output: Null terminated string in the given array ; Registers in use: X for the address of the string Y for the length of the string, ; and A for the current byte ; Memory locations in use: Memory Address for serial line, address of the string ; ; Comments: This subroutine requires serial to be setup and getchar subroutine. ; ReadString psha ; Save accumulator A to the stack pshy ; Save Y to the stack pshx ; Save X to the stack readLoop jsr getchar ; Jump to putchar to write byte to serial beq readLoop ; While A == 0, loop cmpa #CR ; If A == CR, exit loop beq doneRead ; Branch to doneRead if A == CR staa 1,x+ ; Save the byte to the addr in X, then add 1 jsr putchar ; Write Character back to the terminal dey ; Decrement Y by 1 beq doneRead ; If Y == 0, no more room, stop reading bra readLoop ; branch always to readLoop doneRead ldaa #LF ; Load Line Feed into A jsr putchar ; Write LF to terminal pulx ; Restore X from the stack pulY ; Restore Y from the stack pula ; restore A from the stack rts ; return to caller ;************************************************************************* ; putchar subroutine ; ; This subroutine writes a single byte to a serial line ; ; Input: A single ASCII byte in accumulator A ; Output: Sends one character to SCI port ; Registers in use: Accumulator A with input byte ; Memory locations in use: SCISR1 and SCIDRL status and data registers ; putchar brclr SCISR1,#%10000000,putchar ; wait for transmit buffer empty staa SCIDRL ; send a character rts ; Return to caller ;************************************************************************* ; putchar subroutine ; ; This subroutine reads one byte from the SCI port ; ; Input: One byte from the SCI port ; Output: One byte in accumulator A ; Registers in use: Accumulator A for output byte ; Memory locations in use: SCISR1 and SCIDRL status and data registers ; getchar brclr SCISR1,#%00100000,getchar7 ; If no input on SCI port, return 0 ldaa SCIDRL ; Read one byte from SCI port into A rts ; Return to caller getchar7 clra ; Set A to 0 rts ; Return to caller * ************************************************************************** * Data Section 2: address used [ $3100 to $3FFF ] RAM Memory * invalidStr dc.b 'Invalid input format',CR,LF,NULL ; Invalid input error message prompt dc.b 'Ecalc> ',NULL ; Prompt string for calculator overflow dc.b 'Overflow Error',CR,LF,NULL ; Overflow error message ; msg: this is the main option menu string msg dc.b 'Rules for Calculator:',CR,LF dc.b '1) Input positive decimal integer numbers only',CR,LF dc.b '2) Input and output maximum four digit numbers only',CR,LF dc.b '3) Valid operators are: +, -, *, and /',CR,LF dc.b '4) Input number with leading zero is OK',CR,LF dc.b '5) Input only two numbers and one operator in between, no spaces',CR,LF dc.b '6) In case of an invalid input format, repeat print the user input until the error character',CR,LF dc.b '7) Keep 16bit internal binary number format, detect and flag overflow error',CR,LF dc.b '8) Use integer division and truncate any fraction',CR,LF,NULL end ; last line of the file