-[[.: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>}}