<-[[.:start]] ====== CA65 Setup ====== ** Using CA65 in place of VASM ** ===== Support Files ===== * firmware.cfg MEMORY { # Zero page ZP: start = $00, size = $100, type = rw, define = yes; RAM: start = $200, size = $3dff define=yes; ROM: start=$8000, size=$8000, type=ro, define=yes, fill=yes, fillval=$00, file=%O; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; BSS: load=RAM, type=bss, define=yes; CODE: load=ROM, type=ro, define=yes; VECTORS: load=ROM, type=ro, define=yes, offset=$7ffa, optional=yes; } * cc65.rules.mk include /home/gm4slv/6502/ca65src/tools.mk BUILD_FOLDER=../build TEMP_FOLDER=$(BUILD_FOLDER)/$(ROM_NAME) ROM_FILE=$(BUILD_FOLDER)/$(ROM_NAME).bin MAP_FILE=$(TEMP_FOLDER)/$(ROM_NAME).map ASM_OBJECTS=$(ASM_SOURCES:%.s=$(TEMP_FOLDER)/%.o) # Compile assembler sources $(TEMP_FOLDER)/%.o: %.s @$(MKDIR_BINARY) $(MKDIR_FLAGS) $(TEMP_FOLDER) $(CA65_BINARY) $(CA65_FLAGS) -o $@ -l $(@:.o=.lst) $< # Link ROM image $(ROM_FILE): $(ASM_OBJECTS) $(FIRMWARE_CFG) @$(MKDIR_BINARY) $(MKDIR_FLAGS) $(BUILD_FOLDER) $(LD65_BINARY) $(LD65_FLAGS) -C $(FIRMWARE_CFG) -o $@ -m $(MAP_FILE) $(ASM_OBJECTS) # Default target all: $(ROM_FILE) # Build and dump output test: $(ROM_FILE) $(HEXDUMP_BINARY) $(HEXDUMP_FLAGS) $< $(MD5_BINARY) $< # Clean build artifacts clean: $(RM_BINARY) -f $(ROM_FILE) \ $(MAP_FILE) \ $(ASM_OBJECTS) \ $(ASM_OBJECTS:%.o=%.lst) * tools.mk # cc65 utilities used in this example CA65_BINARY=ca65 CC65_BINARY=cc65 LD65_BINARY=ld65 AR65_BINARY=ar65 VASM_BINARY=vasm6502_oldstyle CPU_FLAG=--cpu 65C02 ARCH_FLAG=-t none CC65_FLAGS=$(CPU_FLAG) $(ARCH_FLAG) $(EXTRA_FLAGS) -O CA65_FLAGS=$(CPU_FLAG) $(EXTRA_FLAGS) LD65_FLAGS= AR65_FLAGS=r VASM_FLAGS=-Fbin -dotdir # Hexdump is used for "testing" the ROM HEXDUMP_BINARY=hexdump HEXDUMP_FLAGS=-C # Checksum generator MD5_BINARY=md5sum # Standard utilities (rm/mkdir) RM_BINARY=rm RM_FLAGS=-f MKDIR_BINARY=mkdir MKDIR_FLAGS=-p CP_BINARY=cp CP_FLAGS=-f * project ''makefile'' CONF_DIR=/home/gm4slv/6502/ca65src ROM_NAME=monitor_dev ASM_SOURCES=monitor_dev.s FIRMWARE_CFG=$(CONF_DIR)/firmware.cfg include $(CONF_DIR)/cc65.rules.mk ===== A typical project ===== ==== monitor_dev.s setup ==== * Put the asm source for the project in a folder (''/home/gm4slv/6502/ca65src/src/monitor/monitor_dev.s'') * Put the ''makefile'' in the same folder * put any source files to be included in the assembly process in the ''includes'' folder * ''/home/gm4slv/6502/ca65src/src/includes/rtc.inc'' * ''/home/gm4slv/6502/ca65src/src/includes/ioports.inc'' * make a ''build'' main folder ''/home/gm4slv/6502/ca65src/build/'' The general tree: ''/home/gm4slv/6502/ca65src/'' = base folder for ca65 projects aka ''$CA65SRC'' * config files : firmware.cfg, cc65.rules.mk, tools.mk are in ''$(CA65SRC)'' * sources directory : ''src'' is in ''$(CA65SRC)'' -> ''$(CA65SRC)/src'' * inside sources directory are ''build'', ''includes'' and the individual project source directories. ''$(CA65SRC)/src/build/'', ''$(CA65SRC)/src/includes/'' and (eg) ''$(CA65SRC)/src/monitor/'' * inside each ''project'' source directory are the asm source file eg ''monitor_dev.s'' and a specific ''makefile'' To include sources from the ''includes'' directory first define the variables, then the includes : === Variables === define ''zeropage'' variables: .zeropage DUMP_POINTER: .res 2 FLAGS: .res 1 TOGGLE_TIME: .res 1 CLOCK_LAST: .res 1 MESSAGE_POINTER: .res 2 TICKS: .res 4 CENTISEC: .res 1 HUNDRED_HRS: .res 1 TEN_HRS: .res 1 HRS: .res 1 TEN_MINUTES: .res 1 MINUTES: .res 1 TEN_SECONDS: .res 1 SECONDS: .res 1 MEM_POINTER: .res 2 and define the other ''.bss'' variables: .bss INKEY: .res 1 ASCII: .res 4 BYTE: .res 2 TENS: .res 1 HUNDREDS: .res 1 HEX: .res 2 HEXB: .res 2 TEMP: .res 1 TEMP2: .res 1 Then the ''includes'' .include "../includes/ioports.inc" .include "../includes/lcd.inc" .include "../includes/getkey.inc" .include "../includes/functions.inc" .include "../includes/rtc.inc" make sure the ''include'' files have appropriate ''.code'' directive to put it in the right place when its all assembled together: e.g. in ''functions.inc'': .code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; convert a binary number from Accumulator, in range 00000000 -> 11111111 ($00 to $FF) ;; to its HEX number encode as ASCII - using a simple lookup table and print it on LCD ;; bintohex: pha lsr lsr lsr lsr tax lda hexascii,x jsr print_char pla and #$0f tax lda hexascii,x jsr print_char rts hexascii: .byte "0123456789ABCDEF" ==== monitor_dev.s source code ==== ++++ monitor_dev.s | .localchar '@' ;.SEGMENT "ZEROPAGE" .zeropage DUMP_POINTER: .res 2 FLAGS: .res 1 TOGGLE_TIME: .res 1 CLOCK_LAST: .res 1 MESSAGE_POINTER: .res 2 TICKS: .res 4 CENTISEC: .res 1 HUNDRED_HRS: .res 1 TEN_HRS: .res 1 HRS: .res 1 TEN_MINUTES: .res 1 MINUTES: .res 1 TEN_SECONDS: .res 1 SECONDS: .res 1 MEM_POINTER: .res 2 ;.SEGMENT "BSS" .bss INKEY: .res 1 ASCII: .res 4 BYTE: .res 2 TENS: .res 1 HUNDREDS: .res 1 HEX: .res 2 HEXB: .res 2 TEMP: .res 1 TEMP2: .res 1 .include "../includes/ioports.inc" .include "../includes/lcd.inc" .include "../includes/getkey.inc" .include "../includes/functions.inc" .include "../includes/rtc.inc" .code reset: ldx #$ff txs cli ; interrupts ON jsr via_1_init ; set-up VIA_1 for LCD/Keypad jsr lcd_init ; set-up 4-bit mode jsr lcd_start ; set-up various features of lcd init_variables: stz TICKS stz TICKS + 1 stz TICKS + 2 stz TICKS + 3 stz DUMP_POINTER stz DUMP_POINTER + 1 stz MESSAGE_POINTER stz MESSAGE_POINTER + 1 stz TOGGLE_TIME stz CLOCK_LAST stz CENTISEC stz FLAGS stz SECONDS stz TEN_SECONDS stz MINUTES stz HRS stz TEN_HRS stz TEN_MINUTES stz HUNDRED_HRS stz TEMP stz TEMP2 stz TENS stz MEM_POINTER stz MEM_POINTER + 1 ;; test then clear RAM between ;; $0200 - $3FFF - avoids the ZP and STACK areas ram_clear: lda #$02 ; start at $0200 sta MEM_POINTER + 1 ldy #$00 loop_ram: lda #$AA sta (MEM_POINTER),y lda #$FF lda (MEM_POINTER),y cmp #$AA bne mem_fail_1 lda #$55 sta (MEM_POINTER),y lda #$FF lda (MEM_POINTER),y cmp #$55 bne mem_fail_2 lda #$00 sta (MEM_POINTER),y iny beq next_page jmp loop_ram next_page: lda MEM_POINTER + 1 inc cmp #$40 beq done_ram sta MEM_POINTER + 1 jmp loop_ram done_ram: lda #mem_pass_msg sta MESSAGE_POINTER + 1 jsr print smb5 FLAGS jmp loop mem_fail_1: lda #mem_fail_msg_1 sta MESSAGE_POINTER + 1 jsr print jmp loop mem_fail_2: lda #mem_fail_msg_2 sta MESSAGE_POINTER + 1 jsr print jmp loop ; go straight to MONITOR at startup ; lda #splash ; sta MESSAGE_POINTER + 1 ; jsr new_address ; main loop loop: wai jsr check_flags jmp loop ;;;;;;;;;;;;; FUNCTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; check_flags: bbs0 FLAGS, update_block_address bbs5 FLAGS, clock_time ; check other flags... other actions.... rts update_block_address: sec lda TICKS sbc TOGGLE_TIME cmp #$32 bcc exit_update_block jsr block_address lda TICKS sta TOGGLE_TIME exit_update_block: rts clock_time: sec lda TICKS sbc CLOCK_LAST cmp #$32 bcc exit_clock jsr lcd_cursor_off jsr lcd_home lda HUNDRED_HRS jsr bintoascii lda TEN_HRS jsr bintoascii lda HRS jsr bintoascii lda #':' jsr print_char lda TEN_MINUTES jsr bintoascii lda MINUTES jsr bintoascii lda #':' jsr print_char lda TEN_SECONDS jsr bintoascii lda SECONDS jsr bintoascii lda #' ' jsr print_char lda TICKS sta CLOCK_LAST exit_clock: rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; update screen when new memory location is selected ;; ;; new_address: jsr lcd_clear jsr lcd_cursor_on print_address: lda #'$' jsr print_char lda DUMP_POINTER + 1 jsr bintohex lda DUMP_POINTER jsr bintohex lda #' ' jsr print_char print_data: ldy #$00 lda (DUMP_POINTER),y jsr bintohex lda #' ' jsr print_char lda (DUMP_POINTER),y jsr print_char message_end: jsr print ; add second line (cursor) after re-writing the top line rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; display 8 bytes of data for a "block" of memory ;; ;; block_address: jsr lcd_clear ldy #$00 print_block_address: lda #'$' jsr print_char lda DUMP_POINTER + 1 jsr bintohex lda DUMP_POINTER jsr bintohex jsr lcd_line_2 print_block: lda (DUMP_POINTER),y jsr bintohex lda (DUMP_POINTER),y iny cpy #$08 bne print_block block_message_end: rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; re-draw line 2 cursor ;; ;; print: jsr lcd_line_2 ldy #0 line1: lda (MESSAGE_POINTER),y beq end_print jsr print_char iny jmp line1 end_print: rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Monitor function - decrement the selected address ;; ;; decrement_address: sec lda DUMP_POINTER sbc #$01 sta DUMP_POINTER sta BYTE lda DUMP_POINTER + 1 sbc #$00 sta DUMP_POINTER + 1 sta BYTE + 1 dec_ok: rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Monitor function - increment the selected address ;; ;; increment_address: clc lda DUMP_POINTER adc #$01 sta DUMP_POINTER sta BYTE bcc inc_ok inc DUMP_POINTER + 1 lda DUMP_POINTER + 1 sta BYTE + 1 inc_ok: rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Monitor function - increment the selected block of addresses by 8 ;; ;; increment_block: clc lda DUMP_POINTER adc #$08 sta DUMP_POINTER sta BYTE lda DUMP_POINTER + 1 adc #$00 sta DUMP_POINTER + 1 sta BYTE + 1 rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Monitor function - decrement the selected block of addresses by 8 ;; ;; decrement_block: sec lda DUMP_POINTER sbc #$08 sta DUMP_POINTER sta BYTE lda DUMP_POINTER + 1 sbc #$00 sta DUMP_POINTER + 1 sta BYTE + 1 rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; use last 4 entered ASCII characters from the keypad and convert ;; them to TWO 8-bit binary bytes in RAM ;; ;; ascii_byte: lda ASCII + 1 jsr ascii_bin clc asl asl asl asl sta BYTE lda ASCII jsr ascii_bin ora BYTE sta BYTE lda ASCII + 3 jsr ascii_bin clc asl asl asl asl sta BYTE + 1 lda ASCII + 2 jsr ascii_bin ora BYTE + 1 sta BYTE + 1 rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; toggle the display/update of Clock on each appropriate keypress ;; show_clock: bbs5 FLAGS, reset_bit5 smb5 FLAGS jmp exit_show_clock reset_bit5: rmb5 FLAGS exit_show_clock: rts ;jmp debounce ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; toggle the automatic update view of the "8-byte memory block" ;; show_block: bbs0 FLAGS, reset_bit0 smb0 FLAGS jmp exit_show_block reset_bit0: rmb0 FLAGS exit_show_block: rts ;jmp debounce ;debounce: ; ldx #$ff ; ldy #$ff ;delay: ; nop ; dex ; bne delay ; dey ; bne delay ; rts ;;;;;;;;;;;;;;;;;; INTERRUPT HANDLERS ;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; CB1 : reset & restart timer ;; cb1_handler: stz HUNDRED_HRS stz TEN_HRS stz TEN_MINUTES stz TEN_SECONDS stz HRS stz MINUTES stz SECONDS smb5 FLAGS rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; CB2 : lap-time pause timer ;; cb2_handler: jsr show_clock rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; MONITOR / KEYPAD ;; ;; keypad_handler: jsr get_key ; READs from PORTA which also re-sets VIA's Interrupt flag sta INKEY ; put the ASCII value of input into RAM ( $00 ) lda PORTB_1 ; check for SHIFT/INSTRUCTION button and #%10000000 beq check_keypress ; done this way to get around the limit in size of branch jumps.... jmp handle_new_char check_keypress: lda INKEY ; choose action of "SHIFTed" key-press check_a: cmp #'A' ; move up one memory address and display contents bne check_b jsr increment_address jsr new_address jmp exit_key_irq check_b: cmp #'B' ; move down one memory address and display contents bne check_c jsr decrement_address jsr new_address jmp exit_key_irq check_c: cmp #'C' ; return to MONITOR bne check_d rmb5 FLAGS jsr lcd_clear lda #splash sta MESSAGE_POINTER + 1 jsr new_address jmp exit_key_irq check_d: cmp #'D' ; move monitor to entered 4-digit memory address bne check_e lda BYTE sta DUMP_POINTER lda BYTE + 1 sta DUMP_POINTER + 1 jsr new_address jsr print jmp exit_key_irq check_e: cmp #'E' ; insert (POKE) byte of data in to current memory address, then increment to next address bne check_f lda BYTE ldy #$00 sta (DUMP_POINTER),y jsr increment_address jsr new_address jsr print jmp exit_key_irq check_f: cmp #'F' ; show 8-byte wide block of memory bne check_1 ldy #$00 lda BYTE sta DUMP_POINTER lda BYTE + 1 sta DUMP_POINTER + 1 jsr block_address jmp exit_key_irq check_1: cmp #'1' ; show/auto-update clock bne check_3 jsr lcd_clear lda #emt sta MESSAGE_POINTER + 1 jsr print smb5 FLAGS ;jsr show_clock jmp exit_key_irq check_3: cmp #'3' bne check_6 ldy #$00 jsr increment_block jsr block_address jmp exit_key_irq check_6: cmp #'6' bne check_9 ldy #$00 jsr decrement_block jsr block_address jmp exit_key_irq check_9: cmp #'9' bne check_4 jsr show_block jmp exit_key_irq check_4: cmp #'4' bne check_5 lda BYTE sta HEXB lda BYTE + 1 sta HEXB + 1 jsr byte_to_hex jmp exit_key_irq check_5: cmp #'5' bne exit_key_irq jsr $3F00 jmp exit_key_irq handle_new_char: lda ASCII + 2 sta ASCII + 3 lda ASCII + 1 sta ASCII + 2 lda ASCII sta ASCII + 1 lda INKEY ; get the new ASCII keypress value and... sta ASCII jsr print_char ; and print it on LCD jsr ascii_byte ; convert the rolling 4-byte ASCII character data into two binary bytes exit_key_irq: jsr scan ; re-enable keypad rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; nmi: rti ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; interrupt is triggered by HIGH edge on VIA CA1 pin ;; PORTA low nibble (keypad columns) inputs are diode ORed to CA1 ;; irq: ; put registers on the stack while handling the IRQ pha phx phy ; find responsible hardware ; Is it VIA_1? lda IFR_1 ; if IFR_1 has Bit7 set (ie sign=NEGATIVE) then it IS the source of the interrupt bpl next_device ; if it's not set (ie sign=POSITIVE) then branch to test the next possible device ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; IFR Flags ;; B7 B6 B5 B4 B3 B2 B1 B0 ;; IRQ TI1 TI2 CB1 CB2 SR CA1 CA2 ;; ;; Interrupt source is found by sequentially shifting IFR bit left to put bit-of-interest into the CARRY place ;; and then branching based on whether CARRY is SET or not ;; ;; Only add tests for IRQ sources in use, and adjust the ASLs in each test as necessary ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; test_timer1: asl ; shift IFR left twice puts the TI1 bit into CARRY.... asl bcc test_cb1 ; carry clear = next test bit T1CL_1 ; clear not clear = handle the TIMER interrupt jsr rtc jmp exit_irq test_cb1: asl asl bcc test_cb2 bit PORTB_1 jsr cb1_handler jmp exit_irq test_cb2: asl bcc test_ca1 bit PORTB_1 jsr cb2_handler jmp exit_irq test_ca1: asl ; shift CA1 bit into the CARRY bit & test asl bcc exit_irq ; carry clear = leave jsr keypad_handler ; carry not clear = handle the CA1 interrupt (keypad) jmp exit_irq next_device: exit_irq: ply plx pla rti emt: .asciiz "hhh mm ss MET" splash: .asciiz "cmon> " error_message: .asciiz "Not Decimal" mem_pass_msg: .asciiz "RAM Test Pass" mem_fail_msg_1: .asciiz "RAM Test 1 Fail" mem_fail_msg_2: .asciiz "RAM Test 2 Fail" ; Reset/IRQ vectors .segment "VECTORS" .word nmi .word reset .word irq ++++ ++++ ioports.inc | .code ; VIA_1 Port addresses VIA_1 = $6000 PORTB_1 = VIA_1 PORTA_1 = VIA_1 + 1 DDRB_1 = VIA_1 + 2 DDRA_1 = VIA_1 + 3 T1CL_1 = VIA_1 + 4 T1CH_1 = VIA_1 + 5 T1LL_1 = VIA_1 + 6 T1LH_1 = VIA_1 + 7 T2CL_1 = VIA_1 + 8 T2CH_1 = VIA_1 + 9 SR_1 = VIA_1 + 10 ACR_1 = VIA_1 + 11 PCR_1 = VIA_1 + 12 IFR_1 = VIA_1 + 13 IER_1 = VIA_1 + 14 PORTA_NO_HS_1 = VIA_1 + 15 ; VIA_2 Port addresses VIA_2 = $5000 PORTB_2 = VIA_2 PORTA_2 = VIA_2 + 1 DDRB_2 = VIA_2 + 2 DDRA_2 = VIA_2 + 3 T1CL_2 = VIA_2 + 4 T1CH_2 = VIA_2 + 5 T1LL_2 = VIA_2 + 6 T1LH_2 = VIA_2 + 7 T2CL_2 = VIA_2 + 8 T2CH_2 = VIA_2 + 9 SR_2 = VIA_2 + 10 ACR_2 = VIA_2 + 11 PCR_2 = VIA_2 + 12 IFR_2 = VIA_2 + 13 IER_2 = VIA_2 + 14 PORTA_NO_HS_2 = VIA_2 + 15 ; VIA_3 Port addresses VIA_3 = $4800 PORTB_3 = VIA_3 PORTA_3 = VIA_3 + 1 DDRB_3 = VIA_3 + 2 DDRA_3 = VIA_3 + 3 T1CL_3 = VIA_3 + 4 T1CH_3 = VIA_3 + 5 T1LL_3 = VIA_3 + 6 T1LH_3 = VIA_3 + 7 T2CL_3 = VIA_3 + 8 T2CH_3 = VIA_3 + 9 SR_3 = VIA_3 + 10 ACR_3 = VIA_3 + 11 PCR_3 = VIA_3 + 12 IFR_3 = VIA_3 + 13 IER_3 = VIA_3 + 14 PORTA_NO_HS_3 = VIA_3 + 15 ; ACIA_1 Port Addresses ACIA_1 = $4400 S_TXRX_1 = ACIA_1 ; TXD / RXD S_STA_1 = ACIA_1 + 1 ; Status S_COM_1 = ACIA_1 + 2 ; Command S_CON_1 = ACIA_1 + 3 ; Control via_1_init: lda #%01000000 sta ACR_1 lda #$0E sta T1CL_1 lda #$27 sta T1CH_1 lda #%11011010 ; T1, CA1 active sta IER_1 lda #$01 ; CA1 active high-transition sta PCR_1 lda #%01111111 ; Set all pins on port B to output except BIT 7 which is used for "SHIFT/INSTRUCTION" button sta DDRB_1 lda #%11110000 ; Set low-nibble pins on port A to input and high-nibble pins to output, for keypad sta DDRA_1 rts ++++ ++++ lcd.inc | .code ; LCD Command masks E = %01000000 RW = %00100000 RS = %00010000 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; LCD Functions ;; ;; ;; lcd_start: lda #%00101000 ; Set 4-bit mode; 2-line display; 5x8 font jsr lcd_instruction jsr lcd_entry_mode jsr lcd_cursor_off jsr lcd_clear rts lcd_entry_mode: lda #%00000110 ; Increment and shift cursor; don't shift display jsr lcd_instruction rts lcd_home: lda #%00000010 ; cursor HOME jsr lcd_instruction rts lcd_clear: lda #%00000001 ; Clear display jsr lcd_instruction rts lcd_cursor_off: lda #%00001100 ; Display on; cursor off; blink off jsr lcd_instruction rts lcd_cursor_on: lda #%00001111 ; Display on; cursor on; blink on jsr lcd_instruction rts lcd_line_2: lda #%10101001 jsr lcd_instruction rts lcd_wait: pha lda #%01110000 ; LCD data is input (don't change MSB BIT7, it has to stay ZERO for SHIFT Button input) sta DDRB_1 lcdbusy: lda #RW sta PORTB_1 lda #(RW | E) sta PORTB_1 lda PORTB_1 ; Read high nibble pha ; and put on stack since it has the busy flag lda #RW sta PORTB_1 lda #(RW | E) sta PORTB_1 lda PORTB_1 ; Read low nibble pla ; Get high nibble off stack and #%00001000 bne lcdbusy lda #RW sta PORTB_1 lda #%01111111 ; LCD data is output (don't change MSB BIT7, it has to stay ZERO for SHIFT Buttion input) sta DDRB_1 pla rts lcd_init: lda #%00000010 ; Set 4-bit mode : DO ONCE AT POWER UP sta PORTB_1 ora #E sta PORTB_1 and #%00001111 sta PORTB_1 rts lcd_instruction: jsr lcd_wait pha lsr lsr lsr lsr ; Send high 4 bits sta PORTB_1 ora #E ; Set E bit to send instruction sta PORTB_1 eor #E ; Clear E bit sta PORTB_1 pla and #%00001111 ; Send low 4 bits sta PORTB_1 ora #E ; Set E bit to send instruction sta PORTB_1 eor #E ; Clear E bit sta PORTB_1 rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; PRINT Characters on LCD - an ASCII value in Accumulator ;; is printed on the display ;; print_char: jsr lcd_wait pha lsr lsr lsr lsr ; Send high 4 bits ora #RS ; Set RS sta PORTB_1 ora #E ; Set E bit to send instruction sta PORTB_1 eor #E ; Clear E bit sta PORTB_1 pla and #%00001111 ; Send low 4 bits ora #RS ; Set RS sta PORTB_1 ora #E ; Set E bit to send instruction sta PORTB_1 eor #E ; Clear E bit sta PORTB_1 rts ++++ ++++ getkey.inc | .code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; READ THE 4x4 keypad using VIA_1 PORTA ;; ;; Accumulator holds the ASCII value of the pressed key when it returns ;; get_key: readKeypad: ldx #$04 ; Row 4 - counting down ldy #%10000000 ; ScanRow: sty PORTA_1 lda PORTA_1 and #%00001111 ; mask off keypad input - only low 4 (keypad column) bits are read cmp #$00 bne Row_Found ; non-zero means a row output has been connected via a switch to a column input dex ; zero means it hasn't been found, so check next row down tya lsr tay cmp #%00001000 bne ScanRow lda #$ff rts Row_Found: stx TEMP ; store row ldy #$ff FindCol: iny lsr bcc FindCol tya asl asl ; col * 4 clc adc TEMP ; add row tax lda keypad_array,x rts keypad_array: .byte "?DCBAF9630852E741" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; set ROW keypad outputs high as a source for triggering interrupt when a key is pressed ;; ;; scan: ldy #%11110000 sty PORTA_1 rts ++++ ++++ functions.inc | ;.zeropage ;MESSAGE_POINTER = $20 ; ; .org $8000 .code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; convert a binary number from Accumulator, in range 00000000 -> 11111111 ($00 to $FF) ;; to its HEX number encode as ASCII - using a simple lookup table and print it on LCD ;; bintohex: pha lsr lsr lsr lsr tax lda hexascii,x jsr print_char pla and #$0f tax lda hexascii,x jsr print_char rts hexascii: .byte "0123456789ABCDEF" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; convert a binary (hex) value in Accumulator into ;; its ASCII equivalent character in decimal 0-99 and print it ;; this converts hex/binary numbers from the RTC into human readable ;; decimal for display on clock bintoascii: cmp #10 bmi single_figure asl tax lda binascii,x jsr print_char inx lda binascii,x jsr print_char rts single_figure: asl tax inx lda binascii,x jsr print_char rts binascii: .byte "00010203040506070809" .byte "10111213141516171819" .byte "20212223242526272829" .byte "30313233343536373839" .byte "40414243444546474849" .byte "50515253545556575859" .byte "60616263646566676869" .byte "70717273747576777879" .byte "80818283848586878889" .byte "90919293949596979899" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Convert a decimal number entered at keypad into its ;; HEX equivalent and display ;; byte_to_hex: jsr lcd_clear lda HEXB + 1 and #$0f jsr bintohex lda HEXB jsr bintohex lda #'d' jsr print_char lda #'=' jsr print_char lda #'$' jsr print_char lda HEXB ; lo byte pha lsr lsr lsr lsr cmp #10 bpl error jsr mult10 sta TENS pla and #%00001111 ; UNITS cmp #10 bpl print_error ; jsr mult10 clc adc TENS sta HEX lda HEXB + 1 ; hi byte and #%00001111 cmp #10 bpl print_error jsr mult10 jsr mult10 ; hundreds adc HEX jsr bintohex jmp exit_byte_to_hex error: pla print_error: lda #error_message sta MESSAGE_POINTER + 1 jsr print ;jsr lcd_cursor_off rts exit_byte_to_hex: jsr lcd_line_2 rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; (A * 8) + (A * 2) = A * 10 mult10: pha asl asl asl sta TEMP2 ; A*8 pla asl ; A*2 adc TEMP2 ; A*10 rts ;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Convert the encoded ASCII character representing a hex digit to its actual binary value. ;; ;; e.g. Letter "A" in ASCII is $41 (0100001) but its "numerical" value as a hex digit is ;; 10 ($0A = 10d = %00001010). ;; ;; We convert "A" in ASCII ($41) to a byte of numerical value 10 by subtracting $37 ;; $41 - $37 = $0A (in decimal 65 - 55 = 10) and the result is a byte 00001010 ;; The same is done for all characters representing upper case letters. ;; ;; Numbers are handled differently according to their place on the ASCII table. ;; ;; The ASCII representation of "9" is $39 (00111001) and to get a byte with a value of 9 we can simply ;; AND it with a mask of 00001111 to save only the lower 4 bits. ;; ascii_bin: clc cmp #$41 bmi ascii_bin_num ; a CMP with $41, from a number character ($30 - $39), will set the negative flag ; and the conversion is done by ANDing with $0F ascii_bin_letter: ; otherwise treat as a letter (A -> F) and the conversion is done by clc ; subtracting $37 sec sbc #$37 jmp end_ascii_bin ascii_bin_num: and #%00001111 end_ascii_bin: ; Accumulator holds the numerical version of the ASCII character supplied rts ++++ ++++ rtc.inc | .code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; RTC / Jiffy Tick ;; rtc: ;; RTC stores ticks at 10ms intervals into a 4-byte (32 bit) value ;; ;; as each byte rolls over the next one is incremented ;; on a tick that doesn't roll over the TIME OF DAY ;; is updated inc TICKS bne inc_MET inc TICKS + 1 bne inc_MET inc TICKS + 2 bne inc_MET inc TICKS + 3 ;; ;; Every time it's called we increment the "hundredths of a second" byte ;; ;; When there's been 100 x 10ms (i.e. 1 second) we increment the seconds ;; ;; When SECONDS reaches 60 we increment MINUTES and reset SECONDS to zero... ;; etc... for HOURS, DAYS etc. ;; ;; days/months years are handled too - although probably moot ;; ;; this routine comes from http://wilsonminesco.com/6502interrupts/#2.1 ;; inc_MET: inc CENTISEC lda CENTISEC cmp #100 bmi end_MET stz CENTISEC inc SECONDS lda SECONDS cmp #10 bmi end_MET stz SECONDS inc TEN_SECONDS lda TEN_SECONDS cmp #6 bmi end_MET stz TEN_SECONDS inc MINUTES lda MINUTES cmp #10 bmi end_MET stz MINUTES inc TEN_MINUTES lda TEN_MINUTES cmp #6 bmi end_MET stz TEN_MINUTES inc HRS lda HRS cmp #10 bmi end_MET stz HRS inc TEN_HRS lda TEN_HRS cmp #10 bmi end_MET stz TEN_HRS inc HUNDRED_HRS end_MET: rts ++++ --- //John Pumford-Green 05/09/22 14:23// ===== Further Information ===== {{tag>}}