I'm hoping to get some help with an issue I have been unable to track down over the past few days. In short, I am attempting to initialize an HD44780 LCD by the "Initializing by Instruction" sequence listed in the data sheet. Right now my clock is being generated using a 555 timer circuit with a potentiometer to increase or decrease the clock speed. If I have the clock turned down the code is executing beautifully and just as I expect (ie. lcd is initialized, irq handler is called 8 times (once for each lcd instruction), and program resumes in the main loop just waiting with nothing to do. As soon as I turn up the clock speed everything falls apart (lcd isn't initialized and program is just counting off in RAM somewhere). Below is an exploded view of the current app. Any help would be greatly appreciated. I'm sure I am just missing something obvious.
Code:
/*
Memory Map
RAM:
ZP: $0000 - $00ff
STACK: $0100 - $01ff
...
...
IO: $6000 - $07ff
ROM:
$8000 - $ffff
*/
* = $8000 "Main Program"
// reset vector points here
main:
setup_stack:
ldx #$ff
txs
jsr initialize_via
jsr initialize_lcd
loop:
jsr send_instruction
jmp loop
* = $8050 "Symbols"
/*
65C22 Registers
*/
.const VIA1REGB = $6000 // Input/Output Register "B"
.const VIA1REGA = $6001 // Input/Output Register "A"
.const VIA1DDRB = $6002 // Data Direction Register "B" 0 Input 1 Output
.const VIA1DDRA = $6003 // Data Direction Register "A" 0 Input 1 Output
.const VIA1T1CL = $6004 // T1 Counter/Low Order Latches
.const VIA1T1CH = $6005 // T1 High Order Counter
.const VIA1T1LL = $6006 // T1 Low Order Latches
.const VIA1T1LH = $6007 // T2 High Order Latches
.const VIA1T2CL = $6008 // T2 Counter/Low Order Latches
.const VIA1T2CH = $6009 // T2 High Order Counter
.const VIA1SR = $600A // Shift Register
.const VIA1ACR = $600B // Auxiliary Control Register
.const VIA1PCR = $600C // Peripheral Control Register
.const VIA1IFR = $600D // Interrupt Flag Register
.const VIA1IER = $600E // Interrupt Enable Register
.const VIA1REGA2 = $600F // Same as IOREGA w/o Handshake
/* Application Specific Values */
.const LCD_BUSY = $0202 // Whether or not the lcd is busy executing an instruction
.const LCD_INITH = $0203 // High byte of wait time after command is executed
.const LCD_INITL = $0204 // Low byte of wait time after command is executed
.const LCD_INX = $0205 // Holds the zp index for the current command to execute
.const LCD_INST = $0206 // Holds the instruction to send to the lcd
.const LCD_DATA = $0207 // Holds the data to send to the lcd
.const LCD_CMDS = $fa // Pointer to a list of commands (data or instructions) to send to the LCD
.const VIA1T1_JMP = $fc // Holds a pointer to a routine that should service the T1 Timer Interrupt
* = $8100 "VIA"
initialize_via:
// Disable all VIA Interrupts
lda #%01111111
sta VIA1IER
lda #%11111111 // Set all pins on port B to output
sta VIA1DDRB
lda #%11100000 // Set top 3 pins on port A to output
sta VIA1DDRA
rts
* = $8200 "LCD"
init_sequence:
.byte $01, $01, $01, $00, $04, $06, $05, $03, $07
init_wait_low:
// .byte $04, $64, $64, $25, $f0, $25, $25, $25, $00
.byte $20, $20, $20, $20, $20, $20, $10, $10, $00
init_wait_high:
// .byte $10, $00, $00, $00, $05, $00, $00, $00, $00
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00
instructions: // list of instructions available to send to the lcd
.byte $38, $30, $0e, $0c, $08, $06, $01, $ff
initialize_lcd:
sei
// initialize index and instruction
lda #$ff
sta LCD_INST
sta LCD_INX
// mark lcd as busy
lda #$80
sta LCD_BUSY
// set pointer to the initialize lcd commands
lda #<init_sequence
sta LCD_CMDS
lda #>init_sequence
sta LCD_CMDS + 1
// set the t1 jmp location
lda #<initialze_irq
sta VIA1T1_JMP
lda #>initialze_irq
sta VIA1T1_JMP + 1
// Enable T1 Interrupts
lda #%11000000
sta VIA1IER
// One Shot Mode
lda #%00000000
sta VIA1ACR
// start first timer 15ms after startup
//lda #$98
lda #$01
sta VIA1T1CL
//lda #$3A
lda #$00
sta VIA1T1CH
cli
rts
/*
When this ISR returns it will have set the LCD_INST, cleared the LCD_BUSY flag,
and stored the amount of time to wait after executing LCD_INST into LCD_INITL/LCDINITH.
Once execution is returned to the main program loop, LCD_INST should then get sent to the lcd via a call
to the send_instruction subroutine.
*/
initialze_irq:
init_irq_save_registers:
pha
phy
phx
ldy LCD_INX
iny
sty LCD_INX
init_irq_set_instruction:
lda (LCD_CMDS), y
tax
lda instructions, x
sta LCD_INST
init_irq_set_wait_time:
lda init_wait_low, y
sta LCD_INITL
lda init_wait_high, y
sta LCD_INITH
init_irq_clear_busy:
lda #0
sta LCD_BUSY
init_irq_restore_registers:
plx
ply
pla
rti
/*
This subroutine will send the instruction held in LCD_INST.
It will then clear the LCD_Busy flag and start the T1 Timer.
*/
send_instruction:
lda LCD_BUSY
bmi send_instruction_exit
lda LCD_INST
cmp #$ff
beq send_instruction_exit
set_lcd_write_ir:
lda #%00000000
sta VIA1REGA
ora #%10000000
sta VIA1REGA
set_instruction:
lda LCD_INST
sta VIA1REGB
send_lcd_instruction:
lda #%00000000
sta VIA1REGA
send_instr_lcd_busy:
lda #$80
sta LCD_BUSY
send_instr_wait: // wait before processing next instruction
lda LCD_INITL
sta VIA1T1CL
lda LCD_INITH
sta VIA1T1CH
send_instruction_exit:
rts
* = $a000 "IRQ Handlers"
// Interrupt handler
irq:
pha
lda VIA1IFR
bmi service_via1
jmp irq_done
service_via1:
and VIA1IER
asl
bmi service_t1
jmp irq_done
service_t1:
pla
lda VIA1T1CL // ack interrupt source
jmp (VIA1T1_JMP) // jump to the routine that is setup to handle this interrupt
irq_done:
pla
rti
// Non-maskable interrupt handler
nmi:
rti
* = $fffa "Vectors"
.word nmi
.word main
.word irq