;mc404ativ2.asm 1º semestre 2011 Prof Célio Guimarães ; implementação de calculadora com as 4 operações aritméticas ; entrada e saída via Hapsim com lcd e keypad (ver arquivo de configuração lcdkeypad.xml) ; operações aritméticas com inteiros sem sinal e precisão de 16 bits (0 - 65535 na base 10) ; Atualizado em 25 Maio 2011 ;*********************************************************** .nolist .include "m88def.inc" .list .listmac .dseg res: .byte 2 ; for 16 bit command result (binary) buf1: .byte 16 ; for command result in ASCII format op1bcd: .byte 8 ; to store operand 1 digits in packed BCD format op2bcd: .byte 8 ; to store operand 2 digits in packed BCD format command: .byte 20 ; to store command typed by user .cseg .def temp=r16 .def opr = r23 .def sign =r24 .def ct = r22 .def b0 = r21 .def b1 = r20 .def d0 = r19 .def d1 = r18 .def d2 = r17 rjmp reset ;*********************************************************************** .macro addi ; exemplo de uso: addi r16, 100 subi @0, (-@1) .endmacro ;***************************************************************** .macro add16 ;@0,@1 + @2,@3 -> @0,@1 (low,high) Obs: overflow sets CY add @0,@2 adc @1,@3 .endm ;********************************************************************** .macro cmpregpair ; sets flags for conditional branches cp @0l,@1l cpc @0h,@1h .endmacro ;*************************************************************************************** .macro sub16 ;(@0,@1) -(@2,@3) -> (@0,@1) all low,high sub @0,@2 sbc @1,@3 .endmacro ;******************************************************************* .macro neg16 ; -(@0,@1) 2-complement negation com @0 com @1 ; 1-complement first subi @0,-1 ; now add 1 to pair sbci @1, -1 .endmacro ;************************************************************************************* shleft: ; shifts d2,d1,d0,b1,b0 1 bit left (starting with b0) lsl b0 rol b1 rol d0 rol d1 rol d2 ret ;******************************************************************************** shright: ; shifts d2,d1,d0,b1,b0 1 bit right starting with d2 lsr d2 ror d1 ror d0 ror b1 ror b0 ret ;************************************************************************* .macro r2bcd ; adjust 2 digit packed bcd register after shift left mov temp, @0 andi temp, $0f cpi temp,$05 brlo chkhigh addi @0,3 chkhigh: mov temp, @0 andi temp, $f0 cpi temp, $50 brlo r2done addi @0, $30 r2done: .endmacro ;******************************************************************* .macro bcd2r ; adjust 2 digit packed bcd register after shift right mov temp, @0 andi temp, $0f ; check low nibble cpi temp, $08 brlo bcdhigh subi @0, $03 bcdhigh: mov temp, @0 andi temp, $f0 ; check high nibble cpi temp, $80 brlo bcdone subi @0, $30 bcdone: .endmacro ;**************************************************************************** .macro mult16 ; 16x16 -> 32 bit unsigned integer multiply ; (@0,@1)x(@2,@3) -> (@4,@5) all parameters (low,high) ; @4 and @5 are registers pairs X, Y or Z ;************************************************************* clr @4l ; clear 32 bit result clr @4h movw @5,@4 mul @0,@2 ; low x low add @4l, r0 ; add low byte of product adc @4h, r1 ; add high byte mul @0,@3 ; low x high add @4h, r0 adc @5l, r1 mul @1, @2 ; high x low add @4h, r0 adc @5l, r1 mul @1, @3 ; high x high add @5l, r0 adc @5h, r1 .endmacro ;******************************************************************* .macro div16 ; 16 / 16 -> 16 unsigned integer divide ;(@0,@1) / (@2,@3) -> @4 (order: low,high) quoc: register pair @4 remainder (@0,@1) ;******************************************************************************** .endmacro ;****************************************************************************** bin2bcd: ; converts binary 16 bit in b1,b0 to 5 packed bcd digits in d2,d1,d0 clr d0 clr d1 clr d2 ldi ct,16 binl0: r2bcd d0 r2bcd d1 r2bcd d2 rcall shleft dec ct brne binl0 ret ;*************************************************************************** bcd2bin: ; converts a 5 digit packed bcd in d2,d1,d0 to a 16 binary in b1,b0 clr b0 ; clear 16 bit binary result clr b1 ldi ct,16 ; will shift 16 bits bcdl: rcall shright bcd2r d2 bcd2r d1 bcd2r d0 dec ct brne bcdl or d0,d0 breq bcddone sec ; bcd number does not fit in 16 bits bcddone: ret ;*************************************************************************************** bcd2ascii: ; converte digitos bcd em d2,d1,d0 para ASCII ; armazenando no buffer apontado por X ; e delimita cadeia com 0 binário ;******************************************************************* mov temp,d2 addi temp,$30 st x+,temp mov temp,d1 swap temp andi temp,$0f ; get high nibble addi temp,$30 st x+, temp mov temp, d1 andi temp, $0f ; get low nibble addi temp,$30 st x+, temp mov temp,d0 swap temp andi temp, $0f ; get high nibble addi temp, $30 st x+, temp mov temp, d0 andi temp, $0f addi temp,$30 ; low nibble to Ascii st x+,temp clr temp st x+, temp ; end string with binary zero ret ;************************************************************************************* skipzeros: ; skip leading ascii zeros on buffer pointed by z ; z will point to first non zero digit or, if none, to last ascii zero sk0: ld temp,z+ cpi temp, $30 breq sk0 sbiw z,1 cpi temp,0 brne sk1 sbiw z,1 ; x will point to last zero or first non zero digit sk1: ret ;**************************************************************************************** ;************************************************************************************** lcdinit: ;initialize LCD ldi r16,0xff out ddrd,r16 ;portd is the LCD data port, 8 bit mode set for output out ddrc,r16 ;portc is the LCD control pins set for output ldi lcdinput,56 ; init the LCD. 8 bit mode, 2*16 rcall lcd_cmd ; execute the command rcall lcd_busy ; test busy ldi lcdinput,1 ; clear screen rcall lcd_cmd rcall lcd_busy ldi lcdinput,12 ; do not show cursor, no blink rcall lcd_cmd rcall lcd_busy ldi lcdinput,2 ; cursor home command rcall lcd_cmd ; execute command rcall lcd_busy ret ;************************************************************************************ clearall: rcall toline2 ldi zl,low(msg2*2) ;point to blank message to clear line 2 ldi zh,high(msg2*2) rcall writemsg ; display it rcall toline2 ret ;*************************************************************************** toline2: ; move cursor to line2 ldi lcdinput, $c0 ; go to the beginning of 2nd line rcall lcd_cmd ; do it rcall lcd_busy ret ;******************************************************************************** msg: .db "MC404 Calculator",0 msg2: .db " ",0 msg3: .db "16 bit overflow",0 msg4: .db "Divide by zero", 0 msg5: .db "Operand too big", 0 ;*************************************************************************************** ; ROTINAS BÀSICAS PARA O LCD E O KEYPAD ;******************************************************************************** .equ LCDDATA=PORTB .equ LCDCTL=PORTC .EQU ENABLE=0 .EQU RS=1 .EQU RW=2 .def lcdinput = r25 rjmp RESET ;************************************************************************************* lcd_busy: ;test the busy state sbi portc,RW ; RW high to read cbi portc,RS ; RS low to read ldi r16,00 ; make port input out ddrd,r16 out portd,r16 loop: sbi portc,ENABLE ; begin read sequence in r16,pind ; read it cbi portc,ENABLE ; set enable back to low ;cbi portc,RW ; clear the RW back to write mode sbrc r16,7 ; test bit 7, skip if clear rjmp loop ; jump if set ldi r16,0xff ; make port output out ddrd,r16 ret ;**************************************************************************************** lcd_cmd: ; lcd_cmd writes the LCD command in r19 to the LCD cbi portc,RS ; RS low for command mode cbi portc,RW ; RW low to write sbi portc,ENABLE ; Enable HIGH out portd,lcdinput ; output cbi portc,ENABLE ; Enable LOW to execute ret ;***************************************************************************************************** lcd_write: ; lcd_write writes the value in r19 to the LCD sbi portc,RS ; RS high cbi portc,RW ; RW low to write sbi portc,ENABLE ; Enable HIGH out portd,lcdinput ; output cbi portc,ENABLE ; Enable LOW to execute ret ;***************************************************************** writemsg: lpm lcdinput,z+ ; load r0 with the character to display ; increment the string counter cpi lcdinput, 0 breq writedone rcall lcd_write rcall lcd_busy rjmp writemsg writedone: ret ;***************************************************************************************************** writemsgram: ld lcdinput,z+ ; load r0 with the character to display ; increment the string counter cpi lcdinput, 0 breq writeramdone rcall lcd_write rcall lcd_busy rjmp writemsgram writeramdone: ret ;***************************************************************************************************** keypadinit: ldi temp,0b11110000 ;PB0,1,2,3 inputs, PB4,5,6,7 outputs out DDRB,temp ldi temp,0b00001111 ;enable internal pull-ups on PB0-PB4 out PORTB,temp ret ;***************************************************************************************** magica: ret ;***************************************************************************************************** ;***** Global register variables for Keypad routines ******* .def wreg =R16 ;General use working register .def timeout =R17 ;Timeout value passed to subroutine .def lcdstat =R18 ;LCD busy/wait status .def longtime=R19 ;Long timer for powerup .def temp =R20 .def test =R21 ;test register .def key =R22 ;***** Other equates .equ col1 =0b11101111 .equ col2 =0b11011111 .equ col3 =0b10111111 .equ col4 =0b01111111 ;========================================================================= ; Check all keys (1-16). ; The comments from the first 4 keys are the same with all other keys. ;========================================================================= check_keys: ldi temp, $FF ; no circuito do Serasidis PINB fica em FF out PINB, temp ; e algum bit vai a 0 qdo alguma tecla é acionada rcall longdelay ldi temp,col1 ;Enable column1 out PORTB,temp rcall delay sbic PINB,PB0 ; Pressed key No1 ? rjmp key2 ; if Not, check next key ldi key,$31 ; if yes... ret ; goto type it to LCD key2: sbic PINB,PB1 ; Pressed key No2 ? rjmp key3 ; if Not, check next key ldi key,$34 ; if yes... ret ; goto type it to LCD key3: sbic PINB,PB2 ; Pressed key No3 ? rjmp key4 ; if Not, check next key ldi key,$37 ; if yes... ret ; goto type it to LCD key4: sbic PINB,PB3 ; Pressed key No4 ? rjmp key5 ; if Not, check next key ldi key,'C' ; if yes... ret ; goto type it to LCD key5: ldi temp,col2 ; Disable the first column... out PORTB,temp ; and enable the second rcall delay sbic PINB,PB0 ; Pressed key No5 ? rjmp key6 ; if Not... ldi key,$32 ; ret key6: sbic PINB,PB1 ; Pressed key No6 ? rjmp key7 ; if Not... ldi key,$35 ; ret key7: sbic PINB,PB2 ; Pressed key No7 ? rjmp key8 ; if Not... ldi key,$38 ; ret key8: sbic PINB,PB3 ; Pressed key No8 ? rjmp key9 ; if Not... ldi key,$30 ; ret key9: ldi temp,col3 ; out PORTB,temp rcall delay sbic PINB,PB0 ; Pressed key No9 ? rjmp key10 ; if Not... ldi key,$33 ; ret key10: sbic PINB,PB1 ; Pressed key No10 ? rjmp key11 ; if Not... ldi key,$36 ; ret key11: sbic PINB,PB2 ; Pressed key No11 ? rjmp key12 ; if Not... ldi key,$39 ; ret key12: sbic PINB,PB3 ; Pressed key No12 ? rjmp key13 ; if Not... ldi key,'E' ; ret key13: ldi temp,col4 ; out PORTB,temp rcall delay sbic PINB,PB0 ; Pressed key No13 ? rjmp key14 ; if Not... ldi key,'+' ; 'A' ret key14: sbic PINB,PB1 ; Pressed key No14 ? rjmp key15 ; if Not... ldi key,'-' ; 'S' ret key15: sbic PINB,PB2 ; Pressed key No15 ? rjmp key16 ; 'M' ldi key,'*' ; ret key16: sbic PINB,PB3 ; Pressed key No16 ? rjmp check_keys ; if Not... ldi key,'/' ; 'D' ret ;***************************************************************** longdelay: ldi longtime, 255 ; ldi timeout, 255 ; puwait: rcall delay dec longtime brne puwait ret ;***************************************************************************** delay: ret ;Delay n*64 microseconds using timer 0, delay time passed in timeout ldi test,64 cagain: dec test brne cagain ret dec timeout brne delay ret ;*************************************************************************************