6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 17, 2024 1:12 am

All times are UTC




Post new topic Reply to topic  [ 48 posts ]  Go to page Previous  1, 2, 3, 4  Next
Author Message
PostPosted: Sun Nov 10, 2013 10:17 pm 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
I tried changing the code to poll the receive buffer. No interrupts involved at all. And it still falls over eventually (20 to 30 seconds of running - it varies).

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 12:31 am 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1385
Simon wrote:
OK, more info.

I don't think it's to do with the MS Basic parsing/storing code.

Sending it a file that just contains "10" over and over falls over too although not with total corruption. That command should simply remove line 10 if it's not there. Typing a "10" should never cause an error. If the line isn't there it just returns OK.

Same if I send it "REM" over and over. At slow speeds it works fine. It receives the command, runs it (which just return OK) and keeps going. If I up the speed eventually I get SN ERRORs. That's what I expect if characters are being missed. Eventually though (several seconds) the machine gets corrupted again.

This is my ISR and associated code as it is now. Perhaps someone can see some thing really obvious I am missing? The ACIA rx interrupt is the only one in the system.

Code:
; =============================================================================
; ACIA write buffer.
; =============================================================================

ACIA_Wr_Buffer:
    LDX     acia1_wr_ptr         ; Get the current index.
    STA     acia1_rx_buffer, X   ; Get the pointer value and store the data where it says
    INC     acia1_wr_ptr         ; then increment the pointer for the next write.
    RTS

; =============================================================================
; ACIA read buffer.
; =============================================================================

ACIA_Rd_Buffer:
    SEI      ;disable interrupts
    TXA                          ; Store X.
    PHA
    LDX     acia1_rd_ptr         ; Ends with A containing the byte just read from buffer.
    LDA     acia1_rx_buffer, X   ; Get the pointer value and read the data it points to.
    STA     acia1_rx_byte        ; Store the data temporarily.
    INC     acia1_rd_ptr         ; Then increment the pointer for the next read.
    PLA                          ; Restore X.
    TAX
    LDA     acia1_rx_byte
    CLI    ;enable interrupts
    RTS

; =============================================================================
; ACIA buffer difference.
; =============================================================================

ACIA_Buffer_Diff:
    LDA     acia1_wr_ptr        ; Find difference between number of bytes written
    SEC                         ; and how many read.
    SBC     acia1_rd_ptr        ; Ends with A showing the number of bytes left to read.
    RTS

; =============================================================================
; ACIA buffer get character.
; =============================================================================

ACIA_Buffer_Get: 
    SEI   ;disable interrupts   
    JSR     ACIA_Buffer_Diff     ; See if there is any serial data.
    BEQ     ACIA_Buffer_None     ; No data so exit.   
    JSR     ACIA_Rd_Buffer       ; There is data so get it.

    JSR     ACIA_Buffer_Diff     ; How many bytes are left to read?
    CMP     #$E0                 ; Is it at least 224?
    BCS     ACIA_Buffer_Get_Done ; If so, leave the sending end turned off.
    LDA     #%00001001           ; Set up N parity/echo off/tx int off/rts low/rx int on/dtr active.
    STA     acia1_cm             ; Write to ACIA command register to re-enable receiving.
   
ACIA_Buffer_Get_Done:
    LDA     acia1_rx_byte        ; Retrieve the data into A.
    SEC                          ; Carry set if character available.
    CLI   ;enable interrupts
    RTS
ACIA_Buffer_None:
    CLC                          ; Carry clear if no character available.
    CLI   ;enable interrupts
    RTS   
   
   
; =============================================================================
; ACIA Interrupt.
; =============================================================================

ACIA_Interrupt:
   
    PHA                             ; Store A.   
    TXA                             ; Store X.
    PHA 
    TYA                             ; Store Y.
    PHA 
 
Check2:         
    LDA     acia1_s
    AND     #%00001000              ; Check for the receive data buffer full.
    BEQ     ACIA_Interrupt_Done     ; If empty we are done.
   
    LDA     acia1_s
    AND     #%00000111              ; Check for error conditions (lower 3 bits of status).
    BNE     ACIA_Interrupt_Error    ; If there was any error go report it.
   
    LDA     acia1_d                 ; Get the data from the ACIA.
    STA     IO
    JSR     ACIA_Wr_Buffer          ; Write it to the buffer. 

    JSR     ACIA_Buffer_Diff        ; Now see how full the buffer is.
    CMP     #$F0                    ; If it has less than 240 bytes unread
    BCC     ACIA_Interrupt_Done     ; just exit the ISR here.

    LDA     #%00000001              ; Else, tell the other end to stop sending data before
    STA     acia1_cm                ; Write to ACIA command register to re-enable receiving.
 
    JMP     Check2          ;loop back and check for a second character
   
ACIA_Interrupt_Error:                 
    ;JSR     SAVE                    ; Go out of load mode.   
         
ACIA_Interrupt_Done:     
    LDA     acia1_d                 ; Read the data register to clear it out.
    PLA                             ; Restore Y.
    TAY           
    PLA                             ; Restore X.
    TAX
    PLA                             ; Restore A.
   
    RTI

; =============================================================================
; ISR
; =============================================================================

ISR:
    BIT     acia1_s         ; Check the status register (loads bit 7 into N and bit 6 into V).   
    BMI     ACIA_Interrupt  ; Result negative (bit 7 set) so service ACIA.           
    RTI

; =============================================================================



A few things;

1- in your interrupt service routine, you initially check for the receive buffer holding a character and exit if empty, as you never check again in the routine, the branch will never happen as there are no other sources of interrupt. As the 6551 can hold a second character in the buffer, you should branch back and execute the load and check again as you could possibly miss a character.

2- in your routines: "ACIA read buffer" & "ACIA buffer get character"; you manage buffer pointers and call the "ACIA_Buffer_Diff" routine. Note that the IRQ service routine also calls the "ACIA_Buffer_Diff" routine manages the same pointers. Yet, interrupts are enabled when running either of the two routines ("ACIA read buffer" & "ACIA buffer get character"). You should disable interrupts so you don't have the IRQ service routine change pointers when either of the above routines are being executed. You should set interrupts off (SEI) at the start of each routine then clear it (CLI) when exiting the routine.

3- as you are managing flow control, you might want to check this up front against buffer space and set CTS to off before servicing the 6551.

I made changes above for 1/2.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 4:55 am 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
Hi, thanks for the help.

Can the 6551 ever have two bytes in that state? From the datasheet I have it only ever indicates there will be one. If another comes in before you read out the buffer you hit the overflow error condition?

That's a good point about the buffer pointers. I will fix that. Even if the pointers get messed with while being accessed the worst that should happen is I lose characters (or read too many possibly?). It should cause memory corruption like I am seeing thought I think?

The CTS change is interesting too. So far I don't think I have ever had it actually halt the sending side. The terminal program I have indicates it's status and in the past I have run the scope on it to see what it's doing and I don't think I have seen it actually change. Again I think the worst that can happen is I lose characters.

What I am seeing though is the memory on the machine gets corrupted. I just don't see how.

Simon

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 6:50 am 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
Without taking time to thoroughly analyze the code, I'll make some comments.

Quote:
Can the 6551 ever have two bytes in that state? From the datasheet I have it only ever indicates there will be one. If another comes in before you read out the buffer you hit the overflow error condition?

You can only have one byte in the ACIA's buffer and one on its way in. If the one in the buffer hasn't been read by the time the new one is done coming in, it gets replaced by the new byte and an overrun error is flagged.

Quote:
2- in your routines: "ACIA read buffer" & "ACIA buffer get character"; you manage buffer pointers and call the "ACIA_Buffer_Diff" routine. Note that the IRQ service routine also calls the "ACIA_Buffer_Diff" routine manages the same pointers. Yet, interrupts are enabled when running either of the two routines ("ACIA read buffer" & "ACIA buffer get character"). You should disable interrupts so you don't have the IRQ service routine change pointers when either of the above routines are being executed. You should set interrupts off (SEI) at the start of each routine then clear it (CLI) when exiting the routine.

There is no need to disable interrupts while reading the circular receive buffer, because since you "turn off the faucet" (by setting CTS false) with plenty of space left in the buffer to keep the write pointer from overruning the read pointer, there will be space between them, and incrementing either one while the other is being accessed will not cause problems. I have done it this way for 20+ years with no problems whatsoever. I do not disable interrupts to read the buffer. The write pointer is only altered by the ISR, and the read pointer is only altered by the routine reading the circular buffer. The ACIA_Buffer_Diff routine does not alter either pointer, and each pointer is only one byte so there's no chance of getting a wildly wrong result from having an interrupt-caused rollover from say $4FF to $500 between reading the two bytes which would result in reading $400 or $5FF.

Quote:
What I am seeing though is the memory on the machine gets corrupted. I just don't see how.

Is unrelated memory getting corrupted, ie, not just what you're receiving?

If your ISR does not handle anything else, it will surely be done before the next byte is in; but if you were transmitting too, and running both on interrupts, then anytime you read the ACIA's status register, you better keep a copy and check it (the copy) for both transmit buffer empty and receive buffer full (without re-reading the status-- just get it from your saved record); because otherwise, if you read it again in the same ISR (like you're doing), and testing for something else, the earlier thing you checked for could have gotten set in between the two readings and will never be serviced because the each reading of the status register clears the high bit so the ISR will not be re-entered when done in order to service the other part. (This is what I addressed in Tip #14 at the bottom of the page at viewtopic.php?f=7&t=342.

_________________
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 Nov 11, 2013 8:19 am 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
I'm only using the receive interrupt. Transmit is done by polling. When I want to send I just read the status register and wait till the transmit register is empty then I write the data.

The circular buffer works fine. Some of my earlier debugging was without using RTS/CTS so I just let the buffer overwrite. I even tried no circular buffer at all and just relied on storing one byte. I still see my crash problem.

The very worst I would expect to see if I was getting this wrong would be dropped characters. Or maybe corrupt characters. What I see instead memory corruption. For example if in the ISR I output the stack point value to my LEDS I see it hovering up and down a little when things are running fine but then when it goes wrong suddenly jumps to a low value. Other things indicate memory has gone bad too like timer 1 on the VIA getting written to so the piezo sounds.

If I slow down the transmission rate (as in delay between each character sent) it all works fine. When I decrease the delay I will start seeing overruns occasionally but it will mostly keep running. Increase the speed more and eventually I see framing errors and then it usually crashes very quickly.

I also tried disabling interrupts except for right when I want to read for a character and it still falls over if I send it too much data. In that case I get a lot of dropped characters and errors from Basic but even then it shouldn't lock up the machine.

Simon

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 8:41 am 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
OK, now I might be onto something. I changed the code so that I don't try to send when I am loading and receiving. This seems to work a lot better. I can still cause overruns by sending too fast but it hasn't crashed so far. Will leave it going overnight.

One thing I have found is not to use a test program with POKEs in it since missing characters could mean a line gets read in without a line number and executed immediately. POKEing random locations could of course cause corruption and locking up but I have also seen it lock up with simple code being loaded too.

Will leave it running overnight here and see what happens.

Hmmm, interesting. If I enable echo mode on the 6551 it will echo out what I am sending it. If I send it data too fast it eventually locks up where I get into a state where the 6551 seems to stop responding and the interrupt line is stuck low.

My code is still running though since I can control-C and that breaks it out of loading and updates the command register on the 6551 which then starts working again. So I am wondering now if previous errors I was seeing was my send_char getting stuck waiting for the transmit register to be empty and it was never becoming so. My code would then be stuck in a tight loop polling it.

The memory corruption I was seeing could have been because of the POKEs in it being corrupted.

EDIT: nope, even without POKEs I can get it locking up. And it's not stuck in the loop waiting for the transmit buffer to be free. Will leave it running with no transmit overnight and see how that does.

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 9:43 am 
Offline

Joined: Thu Mar 03, 2011 5:56 pm
Posts: 284
I think I saw something earlier to the effect that reading the status register had the effect of clearing the interrupt bit(s). If so, the use of polling in the transmit code might interfere with the ability of the interrupt handler to detect that a byte has been received. It might be better to use some other mechanism the rate at which characters are transmitted (e.g, a delay loop - I think somebody else used that as a workaround for a bug in the WDC65C51.)


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 10:12 am 
Online
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8541
Location: Southern California
One of my points precisely, although I was forgetting about the polling in the non-interrupt transmit part. In that case, after reading the status register, keep a copy, so that after you've checked for the transmit buffer being empty, you can also check (without reading the status register again) for the receive buffer being full and service it even though not in the ISR. One could reason that the IRQ should catch the receive-buffer-full status and get that taken care of anyway; but I have not tried to see whether IRQ\ will be pulled down at all if the ACIA's receive buffer is filled in the same clock cycle that the status register is read or within a short time after, like 1/16th of the bit rate which at 19,200bps would be more than 3 microseconds, which would be at least three clock cycles at 1MHz, 6 cycles at 2MHz, etc.. This info is not in the data sheet (even the Rockwell one).

Quote:
(e.g, a delay loop - I think somebody else used that as a workaround for a bug in the WDC65C51.)

Yes, but Simon is not using the WDC one which has the transmit-register-empty bit stuck in the on position full time. I do hope WDC gets that fixed. That's serious.

_________________
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 Nov 11, 2013 4:57 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 411
Location: Minnesota
Regarding the comments that suggest the status register not be read more than once, I can see the code as shown actually reads it three times. From what I can gather, as transmit speed increases it seems possible that the value in the status register can change before it's read again.

Changed to read the status register only once:

Code:

; =============================================================================
; ACIA Interrupt.
; =============================================================================

ACIA_Interrupt:
   
    AND    #%00001111               ; test receive buffer status and error bits
    BEQ    ACIA_Clear_Data          ; nothing in buffer, no error
    AND    #%00000111               ; an error ?
    BEQ    ACIA_Do_Read             ; no, go read valid data
    JSR    ACIA_Error               ; process error (not shown- no idea what you want done here)

ACIA_Clear_Data:
    LDA    acia1_d                  ; clear data register
    RTS

ACIA_Do_Read:

    TXA                             ; Store X.
    PHA 
    TYA                             ; Store Y.
    PHA 
   
    LDA     acia1_d                 ; Get the data from the ACIA.
    STA     IO
    JSR     ACIA_Wr_Buffer          ; Write it to the buffer. 
    JSR     ACIA_Buffer_Diff        ; Now see how full the buffer is.
    CMP     #$F0                    ; If it has less than 240 bytes unread
    BCC     ACIA_Interrupt_Done     ; just exit the ISR here.

    LDA     #%00000001              ; Else, tell the other end to stop sending data before
    STA     acia1_cm                ; Write to ACIA command register to re-enable receiving.

ACIA_Interrupt_Done:
    PLA
    TAY
    PLA
    TAX     
    RTS

; =============================================================================
; ISR
; =============================================================================

ISR:
    PHA                             ; save A register
    LDA     acia1_s                 ; Check if ACIA wants service   
    BPL     isr_exit                ; bit 7 not set, so no
    JSR     ACIA_interrupt          ; service ACIA

isr_exit:
    PLA                             ; restore A register         
    RTI

; =============================================================================


This code doesn't check for more than one received byte. I'm assuming that if another recieve interrupt occurs while the current one is being serviced, then as soon as this routine finishes that interrupt will be acknowledged - and that it will be time to prevent data loss.


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 6:41 pm 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
I think it's something like that. I ran code that doesn't poll the transmit flag at all (so no transmitting) when loading over night. It then loaded the same program some 250000 times overnight. Now the machine was still running, no memory corruption, but the 6551 was locked up. The interrupt line gets stuck low and it stops receiving anything. But then resetting the command register gets it working again.

Running flat out I get lots go overrun errors but that just causes garbled text. But that's what I would expect with characters being missed with overruns.

So I think by really hammering it you can bung up the ACIA somehow. I am surprised my keyboard scanning still works when it seems like it's stuck in a permanent IRQ low state. Need to check that more carefully on the scope. I just tried it again now and it stopped after only a minute or so. IRQ definitely looks low but the machine is still running (the keyboard scan works still).

So I think the solution for me is actually to just make sure you don't hammer the loading. I can put a beeper in the ISR so it indicated errors. When loading you tune the speed so you get no errors. That's a workable solution for now.

But I will also try Garth's suggestion above too and report back.

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 6:50 pm 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
OK, I made one change to start with and that was to only read the status register once (and work with a copy). That's now run 38000 times with no lock-ups. That with it sending (polled, no interrupt) as well as receiving and also with lots of overrun errors since I am going flat out (so the text is garbled).

That's how I expected it to behave though. Dropping characters I don't mind since you know it does it. But locking up totally was bad.

I still don't fully understand the exact condition I was getting into but it definitely seems like multiple reads of the acia status register was causing it.

I was mainly working from the interrupts tutorial. Could we maybe include tip #14 in there too (it may be and I missed it!)? I guess in my case I am not dealing with multiple interrupts but still polling the status involves reading it so the same warning applies.

Thanks for all the help here. Takes me a while sometimes but I usually get there :)

Simon

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 11, 2013 9:27 pm 
Offline

Joined: Mon Apr 16, 2007 6:04 am
Posts: 155
Location: Auckland, New Zealand
OK, more interesting results. I decided to make both TX and RX interrupt driven. I can still get things to break when really hammering the port. Interestingly if I reset the machine while the serial port is being sent a file quickly the machine locks up. That's when it's trying to output it's welcome message. And that's with RX interrupts turned off.

If I don't try reading the ACIA data register in that case (even though there are no interrupts the receiver data flag is set) it is more reliable but it's still possible to break it by sending it lots of fast data. And that's without it trying to transmit at the same time.

It just seems if I really punish the chip I can get it into a bad state somehow. Something to do with exactly when you read the data register? It seems very particular and using full duplex transmit and receive seems to make it more likely to break.

I will stick with polled transmit. At least then if it does lock up I can recover.

_________________
My 6502 related blog: http://www.asciimation.co.nz/bb/category/6502-computer


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 12, 2013 2:02 am 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1385
Well, I must have had the Xmit bit stuck in my mind..... as you can write two characters to the Xmit buffer before the flag changes. Receive, a lonely single. The BIOS I did many moons ago was interrupt driven for xmit/rcv and used circular buffers. I went back to the source and I did disable interrupts inside routines which updated buffer pointers outside of the IRQ service routine.

Having found the Xmit bit problem with the current W65C51 chips, I ended up with a few more samples with different WDC lot numbers and did some additional testing (none had the Xmit bit problem). One thing I noticed with the 3 other samples is that all exhibit much higher noise levels when accessed. At first, none worked as I would get endless Xmit errors. The cause was internal clock jitter using an Xtal for the oscillator. Switching to a can oscillator got things working somewhat, but the internal divider for the baud clock also exhibited jitter so I still had errors. I replaced the 65C51 with an old NMOS 6551 (Rockwell or Synertek) and the problem goes away. I measured the noise on the +5V supply at the chip and found levels as high as 240mv during access. This was only present on three of the 65C51 chips... the latest chip (with the Xmit bug) was okay. I ended up having to significantly increase bypass capacitance to get the others working (which is not the case with older NMOS parts).

If you're still running on a the prototype board, you might want to consider noise as a cause and increase the bypass capacitance. Also, if you have some other 6551 chips, try swapping them out, as they (the 65C51s at least) all don't behave the same I've found.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 13, 2013 5:59 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10981
Location: England
It might be worth noting that CTS is used as a request to the sender that it should stop sending. I gather from the long thread on stardot about the UPURS serial cable that many senders can take their time about responding to this request. In other words, depending on hardware and OS, it may not turn off the sender nearly as quickly as you'd like.
Cheers
Ed


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 13, 2013 9:50 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8493
Location: Midwestern USA
BigEd wrote:
It might be worth noting that CTS is used as a request to the sender that it should stop sending. I gather from the long thread on stardot about the UPURS serial cable that many senders can take their time about responding to this request. In other words, depending on hardware and OS, it may not turn off the sender nearly as quickly as you'd like.

That's correct. This phenomenon was especially pronounced in NMOS UARTs, whose response to CTS/RTS and/or DSR/DTR could be quite leisurely.

The usual practice is to set a "high water mark" in the UART driver code that effectively says if the number of bytes in the receive buffer (not the UART's buffer) exceeds a certain threshold, typically 75-80 percent of the buffer capacity, CTS must be deasserted to stop the sender. This practice allows for a possible lag between when CTS is deasserted and when the sender stops transmission. Similarly, a "low water mark" is established that determines when CTS should be asserted to resume data transmission. I didn't do this in POC's drivers because all the involved hardware reacts in the sub-microsecond range to CTS/RTS transitions. I did leave hooks in the drivers to do so if it ever becomes an issue.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 48 posts ]  Go to page Previous  1, 2, 3, 4  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 9 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: