6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue Jun 04, 2024 11:16 am

All times are UTC




Post new topic Reply to topic  [ 12 posts ] 
Author Message
PostPosted: Tue Jul 14, 2020 10:02 pm 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
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
 


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 14, 2020 10:55 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
The fact that your code works at low speed suggests that the code may be correct, but you should look at your hardware design.

The 555 isn't really a good clock source for a CPU. It's meant for use in analogue circuits in the audio range, and doesn't have the kind of sharp transitions between logic states that a logic circuit demands. As a stop-gap, you could try inserting a schmitt-trigger inverter between the 555 and the Phi2 input of the CPU. The schmitt-trigger input is designed to handle slow transitions well, and the output will be sharp and clean enough to use.

You should also ensure that your power supply is properly stabilised, with a bulk electrolytic capacitor near the power input and, most importantly, ceramic 100nF capacitors across the power pins of all active logic devices. This will eliminate a phenomenon known as "ground bounce", in which the power drawn to drive a logic output causes the local power to be disrupted, and this in turn sometimes changes the logic value seen at inputs.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jul 15, 2020 12:14 am 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
Chromatix wrote:
The fact that your code works at low speed suggests that the code may be correct, but you should look at your hardware design.

The 555 isn't really a good clock source for a CPU. It's meant for use in analogue circuits in the audio range, and doesn't have the kind of sharp transitions between logic states that a logic circuit demands. As a stop-gap, you could try inserting a schmitt-trigger inverter between the 555 and the Phi2 input of the CPU. The schmitt-trigger input is designed to handle slow transitions well, and the output will be sharp and clean enough to use.

You should also ensure that your power supply is properly stabilised, with a bulk electrolytic capacitor near the power input and, most importantly, ceramic 100nF capacitors across the power pins of all active logic devices. This will eliminate a phenomenon known as "ground bounce", in which the power drawn to drive a logic output causes the local power to be disrupted, and this in turn sometimes changes the logic value seen at inputs.


I am using the 555 timer just for development/debugging purposes. I plan on switching to my 1mhz oscillator afterwards. I have never heard of ground bounce before. I definitely think that could be my issue. Makes me wish I would have posted this 3 days ago. I'm a software engineer by trade. I should have known the issue could be hardware related :D


Top
 Profile  
Reply with quote  
PostPosted: Wed Jul 15, 2020 3:40 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8453
Location: Southern California
Be sure to go through the 6502 primer. It has 22 logically organized pages, or chapters, which covers the many facets of making your own 6502 computer. AC performance is covered in chapter 9, and groundbounce is discussed in the last quarter of the page. I show operational code in various forms for driving the intelligent character LCDs in one of the accompanying files at http://wilsonminesco.com/6502primer/LCDcode.asm . It is important to note that there's a trick to making the LCD reset routine reliable, a trick that is elusive in most of the data sheets. Where I was working in the late 1980's, we finally got it from an applications engineer after wasting a lot of time; so make sure you're following that. One of the ways to connect an LCD to a VIA is shown halfway down chapter 22, the "Circuit potpourri" page. You can connect lots of peripherals to a single VIA, simultaneously, if you do strategic multi-purposing of the pins. It's not clear if you're understanding a few things about interrupts on the '02. It might be good to go through the 6502 interrupts primer.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 03, 2020 9:15 pm 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
GARTHWILSON wrote:
Be sure to go through the 6502 primer. It has 22 logically organized pages, or chapters, which covers the many facets of making your own 6502 computer. AC performance is covered in chapter 9, and groundbounce is discussed in the last quarter of the page. I show operational code in various forms for driving the intelligent character LCDs in one of the accompanying files at http://wilsonminesco.com/6502primer/LCDcode.asm . It is important to note that there's a trick to making the LCD reset routine reliable, a trick that is elusive in most of the data sheets. Where I was working in the late 1980's, we finally got it from an applications engineer after wasting a lot of time; so make sure you're following that. One of the ways to connect an LCD to a VIA is shown halfway down chapter 22, the "Circuit potpourri" page. You can connect lots of peripherals to a single VIA, simultaneously, if you do strategic multi-purposing of the pins. It's not clear if you're understanding a few things about interrupts on the '02. It might be good to go through the 6502 interrupts primer.


Thanks Garth for taking the time to put together such a treasure trove of information. I have found the resource invaluable. I am currently gathering serial data from an Arduino, which is shifting the data out to the 6522 VIA using input mode 011. I am currently using a flip flop like you suggested to get around the bug in the VIA. I found another post of yours where you mentioned another potential workaround that doesn't require the extra hardware using mode 111. If you could elaborate more on this idea I would appreciate it. I would like to pursue this idea with the Arduino and 6502 SBC I am currently working on. My end goal being to use the shift register to load a program into the free portion of RAM and have the 6502 SBC jump to this portion of ram and execute it. This should greatly speed up my testing/development cycle not having to take out and reprogram the ROM each time I want to make a change.


Top
 Profile  
Reply with quote  
PostPosted: Mon Aug 03, 2020 11:54 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8453
Location: Southern California
bgulotta wrote:
I am currently gathering serial data from an Arduino, which is shifting the data out to the 6522 VIA using input mode 011. I am currently using a flip flop like you suggested to get around the bug in the VIA. I found another post of yours where you mentioned another potential workaround that doesn't require the extra hardware using mode 111. If you could elaborate more on this idea I would appreciate it.

The only mention of the SR's mode 111 I could find in my posts is in Tip #6 in my "Tip of the Day" column, regarding using the 6522's SR as the "T" part of a UART. Tip #8 (two posts down) tells about the 011 bug, and I go further into the SS22 interface between two computers at viewtopic.php?p=19484#p19484, and then mention there's another way I could have gotten around the 011 bug, if you scroll down to where it says in bold, "Forehead slapper:" The idea is similar except you'll have the receiver drive the clock line instead of having the sender do it.

Quote:
I would like to pursue this idea with the Arduino and 6502 SBC I am currently working on. My end goal being to use the shift register to load a program into the free portion of RAM and have the 6502 SBC jump to this portion of ram and execute it. This should greatly speed up my testing/development cycle not having to take out and reprogram the ROM each time I want to make a change.

This sounds pretty much like having a monitor of some sort, which of course works well with a host computer where you do the editing, assembling, etc., just without a UART in this case. The 6522's VIA would work great for this, too. I carry the idea further with my Forth system which serves the purpose of a monitor but goes much, much further, as described here. The host computer can be anything with a text editor that can send out a marked block of source-code text, since the workbench computer can take that and do its own compiling, assembling, or interpreting of the input stream, as appropriate.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 04, 2020 1:00 am 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
GARTHWILSON wrote:
bgulotta wrote:
I am currently gathering serial data from an Arduino, which is shifting the data out to the 6522 VIA using input mode 011. I am currently using a flip flop like you suggested to get around the bug in the VIA. I found another post of yours where you mentioned another potential workaround that doesn't require the extra hardware using mode 111. If you could elaborate more on this idea I would appreciate it.

The only mention of the SR's mode 111 I could find in my posts is in Tip #6 in my "Tip of the Day" column, regarding using the 6522's SR as the "T" part of a UART. Tip #8 (two posts down) tells about the 011 bug, and I go further into the SS22 interface between two computers at viewtopic.php?p=19484#p19484, and then mention there's another way I could have gotten around the 011 bug, if you scroll down to where it says in bold, "Forehead slapper:" The idea is similar except you'll have the receiver drive the clock line instead of having the sender do it.

Quote:
I would like to pursue this idea with the Arduino and 6502 SBC I am currently working on. My end goal being to use the shift register to load a program into the free portion of RAM and have the 6502 SBC jump to this portion of ram and execute it. This should greatly speed up my testing/development cycle not having to take out and reprogram the ROM each time I want to make a change.

This sounds pretty much like having a monitor of some sort, which of course works well with a host computer where you do the editing, assembling, etc., just without a UART in this case. The 6522's VIA would work great for this, too. I carry the idea further with my Forth system which serves the purpose of a monitor but goes much, much further, as described here. The host computer can be anything with a text editor that can send out a marked block of source-code text, since the workbench computer can take that and do its own compiling, assembling, or interpreting of the input stream, as appropriate.


Awesome stuff. That article gives me some more food for thought. You definitely took what I am wanting to do a step further. I was planning to continue to code and assemble on my Mac and then have a serial transfer take place to the Arduino which then sends the data to the VIA via it's SR.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 04, 2020 4:10 am 
Offline
User avatar

Joined: Mon May 12, 2014 6:18 pm
Posts: 365
HD44780 compatibles can be a little tricky. I would double check that the timings are right. One thing that gets people is using example code that works on one compatible but doesn't on another because it's technically out of spec though works on some but not all manufacturers' parts. Note that some of the commands require an extra 1.5ms delay after being issued. Working at low speed but not at higher speeds sounds like you might be inadvertently violating the timings when things speed up.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 04, 2020 1:30 pm 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
Druzyek wrote:
HD44780 compatibles can be a little tricky. I would double check that the timings are right. One thing that gets people is using example code that works on one compatible but doesn't on another because it's technically out of spec though works on some but not all manufacturers' parts. Note that some of the commands require an extra 1.5ms delay after being issued. Working at low speed but not at higher speeds sounds like you might be inadvertently violating the timings when things speed up.


That definitely seems to be the case. Once I added nop's the script started displaying data. Looks like perhaps the busy flag check is not functional on my model.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 04, 2020 1:39 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1432
Location: Scotland
bgulotta wrote:
Druzyek wrote:
HD44780 compatibles can be a little tricky. I would double check that the timings are right. One thing that gets people is using example code that works on one compatible but doesn't on another because it's technically out of spec though works on some but not all manufacturers' parts. Note that some of the commands require an extra 1.5ms delay after being issued. Working at low speed but not at higher speeds sounds like you might be inadvertently violating the timings when things speed up.


That definitely seems to be the case. Once I added nop's the script started displaying data. Looks like perhaps the busy flag check is not functional on my model.


Also; how it is connected to the CPU? If directly connected to the bus, then my experience is that they're marginal at anything close to 1Mhz, but OK slower. Do reads their data sheets for the timing of the strobe/E signal, etc. (and as above, treat it with caution - there are now many clones and not all the same - e.g. I have some that don't support the character redefining - all to save a few bytes of RAM!)

I've almost always connected them to a latch or to a VIA port. (and generally ignored the busy signal, so treated them as write only and used delays) It does mean that you have to "bit bang" the strobe/E signal, but that's no big deal, really.

Cheers,

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 04, 2020 1:49 pm 
Online
User avatar

Joined: Fri Nov 09, 2012 5:54 pm
Posts: 1395
Another option would be to intentionally not check the busy flag,
(means only to write to the display, but not to read from it),
and to go with wait loops in the software according to the HD44780 datasheet instead.

This way, the computer "won't be waiting forever" if the HD44780 is missing or goes offtrack.
Also, it simplifies buffering the signals with 74LVT245 if the computer is 3.3V powered and the display is 5V powered,
or if one wants to buffer the signals with 74245 or such for having longer cables between display and computer.

Downside is, that the wait loops have to be modified when running the computer at a different clock speed.


Top
 Profile  
Reply with quote  
PostPosted: Tue Aug 18, 2020 10:02 pm 
Offline

Joined: Tue Jul 14, 2020 9:09 pm
Posts: 13
So I have finally had a chance to start debugging the timing issues I am having on my 6502 SBC. Issues that persist unless the phi2 clock is running at a snails pace (~11hz). The stack definitely seems to be getting corrupt and the RTI command is jumping back to some random place in RAM. I hooked up my scope and noticed that the phi2 clock was taking over 3 times longer than spec allowed to rise and fall ~15ns. I then looked at the PCB schematic I used and there was a resistor in line with the clock input into the 6502 that was causing the delay in rise and fall times of phi2. I removed this resistor and added a decoupling capacitor for the crystal oscillator. Now the clock signal looks much better and is now in spec with < 5ns rise and fall times in the 6502 datasheet. I have attached a before and after photo of the clock and SRAM control signals. The program issues persist, but I feel I am making progress. I am currently qualifying the CS signal of the SRAM chip with phi2 being high. I wonder if I shouldn't also be qualifying the WE signal of the SRAM chip with phi2 being high as well? Right now it is connected straight to the R/W signal of the 6502. I noticed in another post on this forum a person was having similar issues which went away when he changed his SRAM WE signal to be qualified with phi2 instead of the CS signal. Short blurb from that post is below.

https://imgur.com/a/0C8ghuW

xbg wrote:
I fixed the problem!

It was an issue with my RAM OE#/WE#/CE# logic. I completely rewired it to switch from CE# to WE# controlled write cycle logic (if this even make sense, basically switched the phi2 nand-strobe from one line to the other) and everything started working fine. I think there was some unwanted delay on the OE# creating this problem (or something else, anyway...). I still can't believe it didn't manifest earlier/worked at all.

Right now my address decoding / logic chips block look like a tangled mess but I'll stick around and post the result once it's all cleared up.

Thanks guys.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 15 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: