XaiJu
beneater
beneater

patreon


RS232 flow control

Here’s a video adding flow control to the input buffering from my last video. Unfortunately, I ended up running into a problem that I haven’t been able to figure out yet. As you’ll see in the video, when sending a large amount of text, the computer receives it all correctly—which is the goal. But as it’s receiving it, it doesn’t echo it correctly. It seems like when asserting RTS, the ACIA doesn’t transmit properly, almost like it can’t do full-duplex. But I can’t find any reason for this. I’m really hoping I’m missing something obvious and one of you will point it out. I was expecting this to be a relatively straightforward video, but I guess not.

I’ve tried increasing the transmit delay. I’ve tried disabling interrupts while transmitting. I’ve swapped chips, including using a non-WDC ACIA. I’ve used a completely different serial interface on a different laptop running a different operating system. I’m not sure what I’m missing. Either way, I’ll probably end up adding some of those troubleshooting steps before publishing the final video—hopefully having figured it out by then. In the meantime, please hit me with your theories.

And thanks as always for your support!

-Ben

RS232 flow control

Comments

Thank you!

Doyle M. B. Baxter

It's a paid app called Serial: https://www.decisivetactics.com/products/serial/ CoolTerm also works well, and is free: http://freeware.the-meiers.org

Flying Toaster

Can anyone tell me what macOS program Ben is using to read and write the serial port? I've been pretty stuck on the "Fixing a hardware bug in software" (Video #22). Seen some success with the built in serial monitor in VSCode, but would love to try and reduce the number of possible problem areas.

Doyle M. B. Baxter

Funny, 16 hours later, Ben posts an updated version of this video to youtube that works around the input echo problem. Nice.

Mikol Ryon

Am I missing something or are these changes not in the gitlab repo? I added them as seen in the video and it seems to have fixed my flow control problems so it looks like it's the case. And what do we think the odds are that Ben will return and add the ability to write to the LCD display from BASIC?

Mikol Ryon

G'day Ben, Is this a case of "out-by-one"? From the datasheet (https://www.westerndesigncenter.com/wdc/documentation/w65c51n.pdf), the bit allocation on the Command Register is as follows, BIT (VALUE) FUNCTION 0 1 Data Terminal Ready (DTR) (Sets/Clears CTS and DTR pins) 1 2 Receiver Interrupt Request Disabled (IRD) 2 4 Transmitter Interrupt Control (TIC) 3 8 Transmitter Interrupt Control (TIC) 4 16 Receiver Echo Mode (REM) 5 32 Parity Mode Enabled (PME) 6 64 Parity Mode Control (PMC) 7 128 Parity Mode Control (PMC) I noticed two things... When disabling the receiver, you are setting bit 0 high, by writing #$01 to the Command Register. From the datasheet, at least, I would expect this value to be #$00. When re-enabling the DTR, you used #$09. This sets the DTR/CTS pins and the second bit of TIC. Here, I would expect a #$11, which would re-assert the REM. My understanding is that bit 3 of the Command Register should not be used, due to the transmit bug in the ACIA. If I am correct, this brings up the real question... Why does your current code work, in that it properly receives all the data? Also, why does the REM get properly set at the end of the buffer? Of course, I might be wrong - I've only been looking at this for a few minutes and I haven't caught up with the hardware implementation yet. Even so, I hope it gives you the clue you need to go forward. I can't tell you the joy your videos have brought to me. Hope this helps, Matt

Matt

What happened to Ben? Will he ever post again?

Jim Kelly

Pretty simple - wire PORTB.7 to MAX232 pin 10. Then pin 7 of MAX232 goes out on the serial cable just as in the video. Code that deals with RTSB then has to "play nice" with the LCD code that manipulates the rest of PORTB. Both sets of code can limit their changes using tsb/trb. Any more complex read/modify/writes should wrap with sei/cli because interrupt code below can turn RTSB on. .macro SET_RTSB_A lda #$80 tsb PORTB .endmacro .macro CLEAR_RTSB_A lda #$80 trb PORTB .endmacro ;; the _A suffix is there to remind me that the macro steps on the A register. ;; In serial port initialization.... ;; RTSB goes out PORTB.7 ;; Enable output on that bit only, and drive it low lda #%10000000 trb PORTB tsb DDRB BYTEIN: lda iproducer_ptr cmp iconsumer_ptr bne @key_was_pressed ; if none, we can return failure right away clc rts @key_was_pressed: .ifdef HARDWARE_FLOW_CONTROL ;; If #buffers chars is < 128, call CLEAR_RTSB_A ;; Note we didn't do this in the zero case above, but one would expect ;; that between the time the buffer reached 127 bytes and now, CLEAR_RTSB_A ;; would have been called 127 times already. bmi @skip_flow_reenable bit forced_rtsb bmi @skip_flow_reenable CLEAR_RTSB_A @skip_flow_reenable: .endif phx ldx iconsumer_ptr lda input_buffer,x inc iconsumer_ptr sec ; indicate success plx ; restore X rts In IRQ: lda ACIA_STATUS bit #$08 beq @nochar lda ACIA_DATA ldx iproducer_ptr sta input_buffer,x inx .ifdef HARDWARE_FLOW_CONTROL txa sec sbc iconsumer_ptr cmp #240 bcc @not_too_full SET_RTSB_A @not_too_full: .endif stx iproducer_ptr @nochar:

Jim Rees

Can you share your code and how you wired it up?

David McGrath

I reproduced the issue. I also ran an experiment. Run the following program: 10 FOR I = 0 to 1200 20 PRINT I 30 NEXT I ...and while it is running paste in the program (preceded by "NEW"). The moment RTSB is asserted, the program output is halted. Eventually, the program ends, and the new program is entered perfectly as before. But all that program output is lost. If I print the ACIA_STATUS to the lcd during this "black-out" it's all zeros (which is mildly interesting but perhaps not useful). My conclusion is that setting ACIA_CMD to $01 is having the undesired side effect of shutting off the transmitter as well. EDIT: The easy fix was to use a VIA pin for RTSB. It works. I made it more of a challenge for myself to use the last unused pin on PORTB, whereas the other pins are used for LCD. All writes with tsb/trb only.

Jim Rees

My guess to the source of missing echo: when sending data, you added a sleep, to compensate for the missing "ready to transmit" signal (cpu errata workaround?) . Is it possible that this sleep-by-counting algorithm has been slightly broken by the IRQ?

dans

Could it be that BASIC is also parsing the line too and that sometimes takes too much time and interferes with the output routine.

Mitch Reynolds

Maybe instead of always using a set delay for the send, you have a buffer and use the fact that you must be receiving at the fastest possible rate? IE if you received and processed a byte you can assume it is safe to send one since the sending computer will never be faster than the baud rate set. Since you are now receiving at full speed and you want to echo from basic/wozmon you must send at full speed or you will loose some of the text. The process would be to send you first check if there is anything in the receive buffer, if not, do a normal send and wait. If there are bytes in the incoming buffer put the outgoing byte in a output buffer. Then in the IRQ after you receive a byte and put it in the receive buffer you can check the outgoing buffer for a byte and send if there is one. I'm thinking maybe you need to do this because on page 7 of this: https://www.princeton.edu/~mae412/HANDOUTS/Datasheets/6551_acia.pdf There is this table in the bottom corner and it says 'RTS LEVEL' and in the OTHER spot it says 'Transmit BRK' for when Transmit Interrupt is disabled. I think that maybe regardless of what the spec is or how it should work, that when you set the RTS to stop bytes from being received on a 6551 that you need to also first stop sending bytes or they will be lost?

NormalLuser

It would be interesting to see how you troubleshoot the problem. Possibly using some tool that can help you see the issue.

Mitch Reynolds

Can you use a logic analyzer or o'scope to check the RS-232 transmit/receive data and control signals from each component in order to 1) verify the signals are good and 2) to try and narrow down which component is not behaving correctly?

Brian Manning

Does this happen with WOZMON as well, or only in Microsoft BASIC? I think that's the only variable left that isn't accounted for... though that shouldn't make a difference since it's your code doing the echoing?

Pietro Gagliardi

My guess as to what is happening that is corrupting the echo is that with this change, CHRIN has new data every time it runs while the computer is sending data. Due to the silicon bug in the W65C51, you can't actually verify that it's done sending the data. So I'm guessing you're just relying on timing to make sure CHROUT doesn't send a new character before it's done with the previous one. I'm guessing the timing is just about on the edge, so when RTS is not asserted the interrupts from receiving data slows down the code just enough that it's able to keep up, but when RTS is asserted the code runs just a little bit quicker and keeps wiring new data into the ACIA before it's done sending out the previous character. If my theory is right, you should be able to see it sending invalid data while RTS is asserted by hooking up a scope.

Kristian Høy Horsberg

I understand your point, and you are right, i thought of it as handshaking and i'm wrong ;-) I can see now where i made my mistake, thanks to J Thorpe & you. Incoming signal from RFR to CTS is handled by the UART, it just shuts down transmit. The software doesnt even know that that happend... I thought, this signal is handled by the driver software... Thank you and J Thorpe for taking time answering me!!

Daniel Varga

This honestly feels like a software bug in the terminal software on the Mac side to me, or maybe something internal to the serial port to USB converter, as you're implementing CTS properly from what I can tell. Can you check to see if the same dropped character display issue happens with a different hardware device?

Michael Tedder

So, if the baud is set really low, say 150 or 300, does everything come through? If it does, my suspicion is that characters are “sneaking” into the buffer, but the comparator boundaries are being violated before the code can react. There are characters that are pushing past the limit/boundary when other parts of the code think that isn’t the case. Also - do you need to do a carry flag clear before the comparison(s)? Maybe it’s set from a previous operation.

George Harris

I’m trying to figure out of there is any way the a register gets modified before CHRIN is called, but I can’t see how just yet. Somehow I wonder if the change to CHRIN is causing the problem.

Paul Heller

What happens if you move the echo into the interrupt handler? The RTS state is variable outside that.

James Chacon

Well, the WDC ACIA has a broken Tx block, so any time you have trouble with a WDC ACIA, it's always a good idea to try an older NMOS 6551 or even Motorola 6850 part.

Jason Thorpe

Hm. Remind me the baud rate you're running at again? Are you absolutely certain that 255 through the transmit drain delay loop is sufficient? Although, if you were tripping over that ACIA bug, I'd expect properly garbled output, not dropped output.

Jason Thorpe

Daniel, I think you're confusing the original function of RTS with the newer, alternate RFR function for that pin. The original RTS is for the handshaking you describe where one side asserts "request-to-send" when it wants to send and waits for the other side to give it the "clear-to-send". As I discussed in the video, the "RTS" pin has been repurposed as a "ready-for-receiving" and should properly be called RFR. Everyone still calls it RTS, leading to conflating the two different functions. Unless I'm missing something, the description in the 6551 datasheet for the so-called-"RTS" pin actually describes RFR functionality.

Ben Eater

But there is no “I want to send something” signal, and no handshake. There is only "RTS" (which as Ben pointed out is really "RFR" a.k.a. "Ready For Receiving", or as I like to think of it, "Request for you over there on the other end To Send"). It's connected like this: Computer A ----- Computer B TXD ----- RXD RXD ----- TXD RTS ----- CTS CTS ----- RTS So, when Computer A says "Ready to receive!", computer B sees "Ah, it's clear to send!" Each of these flow control signals is independent of the other. From each computer's perspective, RTS in an output (raising or lowering the stop sign), and CTS is an input (looking at the peer's stop sign). Now, while the ACIA UART does not have auto-RTS handling, which necessitates that Ben twiddle the bit by hand, it *does* have auto-CTS handling; if the CTSB signal is high, the transmitter will be disabled. Make sure that input signal on the ACIA is not left floating!

Jason Thorpe

Just tried this and it makes it much worse. The behavior seems to be that setting RTS interferes with echoing somehow, so asserting RTS as soon as there's at least 1 byte in the buffer causes that interference much sooner and more often. The good news is that this does simplify the repro quite a bit, so I'll try playing with this a bit more to see what I can learn.

Ben Eater

Jason is correct, except that the interrupt doesn't even echo the character. All it does is clear the interrupt, get the data byte, and put it in the buffer. Echoing it happens later when the program reads from the buffer. And yeah, the fact that it receives everything perfectly is pretty strong evidence that we're not missing any interrupts.

Ben Eater

It's better to clear the IRQ right away in case another interrupt comes in. But in any event, since the interrupt routine is only reading the character and putting it in the buffer, it will always run much, much faster than the time it takes for another character to arrive.

Ben Eater

Yep, CTSB is tied to ground so that should never inhibit transmission. And I've verified with a scope that the laptop is never trying to assert it. So I don't see a way that CTS could be interfering with transmitting.

Ben Eater

Clearing the interrupt first thing is actually the correct thing to do. Clear the interrupt, get the data byte, echo the character, get out. If another data byte comes in, the IRQ line will be re-asserted and as soon as the RTI instruction is executed, it will fire again because /IRQ will be low. If you wait until the end of the interrupt handler to clear it, then you'll never know if you missed a byte because you might clear the status after receiving it. It's pretty clear from the symptom in the video that the problem is with echoing the received byte, not the actual reception.

Jason Thorpe

at 8:40 you say you could run the buffer size check earlier instead of pushing the character from the a register to the stack and popping it back later. But in fact you ARE already calling it already for the beq @no_keypressed. So you can just run a second comparison at that point.

Patrick Kilian

I gave this problem another bit of hard thinking, and I think, RTS/CTS handshaking is the problem. I think, all communication must be halted, when any of the two partners turn off their RTS->CTS line. Here’s why I think this (sorry for the lengthy post by the way – English is not my native language, that’s why I find it hard to make it short and clear at the same time…) Let’s assume, you have a two-way communication between A and B with the same privileges on either side (no master-slave-situation). There are two lines for communication – one from A to B and one from B back to A (i.e. Rx/Tx lines crossing over). Now let’s assume A wants to send data to B. A will turn on it’s “I want to send something” signal. B will have to answer over the “I am ready to receive / I am NOT ready to receive” line. Let’s now analyse the special case with the buffer being full on one side and assume, B is the 6502-computer with the full buffer. Here’s what could happen in this setup: A turns on the “A wants to send something to B” signal. B responds: “B is NOT ready to receive” (i.e. turning of the “B ready to receive” signal). So A will wait and not send anything yet. Now B having a full input buffer is not ready to receive, but B wants to SEND something. It turns on its “B wants to send something to A” signal. A is ready to receive from B. It will respond by turning ON “A is ready to receive from B”. Thus, communication from A to B is on hold, but communication from B to A can proceed. But for this to happen, you need 4 control lines in total: - One for “A wants to send to B” - One for “B can / cannot receive from A” - One for “B wants to send to A” - And one for “A can / cannot receive from B” So here is the problem: With RTS/CTS you only have to signal wires. How can you differentiate between: “I want to send” and “I am ready to receive”? Well, I think, you cannot. That’s why I think, that when CTS is turned off by either of the two, communication is halted in both directions. Let’s play through the communication above with only two signal wires: A wants to send something to B, so it turns on the line “RTS from A to CTS on B”. This tells B, that A wants to send something. As B is NOT ready (remember: buffer full) it has to turn off its own “RTS from B to CTS on A” line. Now how to tell A, that even if it might not be ready to receive, it would like to send something? It has only one signal line going from itself to the communication partner, namely the line “from my RTS to the CTS of the other”. But if it turns on THAT signal again, it also turns back on incoming data! (it’s the same line, isn’t it?) With only two handshake-wires, there can only be one logical solution: BOTH RTS-from-one-to-CTS-on-the-other MUST be turned on to allow any communication to happen. And as a consequence, communication in BOTH directions must be halted, if any one of the two signals was OFF. So “RTS to CTS = ON” does not only mean: “I can receive data” but can also mean “I want to send data”. And “RTS to CTS = OFF” means “I can not receive data” but also “I do not want to send data” The other communication partner can NOT differentiate between these two meanings; therefore, it must do the only safe thing: shut down communication in both directions. The side waiting to send data could, of course, just keep listening for incoming data even if CTS was off… But that is probably not advisable, because if the other side is NOT sending, the line could float or something and you could pick up noise and falsely interpret it as data… So the RS232 protocol will most probably advise to halt communication in both directions, if either one of the CTS is turned off (well, at least that is what I think – and it seems to nicely fit what happens: The missing echoes most probably correspond to the times when you turn off CTS due to input buffer being full) The solution, I think, would be an output buffer with your echoes going primarily to that output buffer. When communication is open (= both RTS->CTS lines ON), you can send and receive characters in turns taking outgoing data from output buffer and putting incoming data to the input buffer... Does this make sense? I sure hope it helps to identify the problem…

Daniel Varga

Hi Ben - enjoyed the video! As J. Chacon, I think, it could be related to RTS/CTS . I'm no expert in any way, but what I found seems to indicate, that both lines must be taken in acount. I took this from "Fundamentals of RS-232 Serial Communications" on the www.analog.com website: "Request to Send (RTS): When the host system (DTE) is ready to transmit data to the peripheral system (DCE), RTS is turned ON. In simplex and duplex systems, this condition maintains the DCE in receive mode. In half-duplex systems, this condition maintains the DCE in receive mode and disables transmit mode. The OFF condition maintains the DCE in transmit mode. After RTS is asserted, the DCE must assert CTS before communication can commence. Clear to Send (CTS): CTS is used along with RTS to provide handshaking between the DTE and the DCE. After the DCE sees an asserted RTS, it turns CTS ON when it is ready to begin communication." So when you turned on RTS/CTS handshaking, the modern computer probably began to send RTS signals which you normally acknowledge (without checking the RTS line, I assume??) by having CTS asserted most of the time. When your buffer is full, you deactivate CTS thus stopping transmission from the modern computer. In this condition, the modern computer is probably not ready to receive. Could it be, that in THIS condition, the 6502-computer must take over the role oft DTE and assert RTS line before echoing back?... Or maybe the setup has to be symmetric using an output buffer for the case, when communication is shut off by CTS off: When CTS is off, you echo to an output buffer instead of to the tx line - than, when CTS is on again, you send echoes from the output buffer in full-duplex communication as before?

Daniel Varga

Glad to see that you swapped the ACIA - in these unfortunate times, my first concern was a counterfeit chip!

Jack Rubin

The ACIA UART doesn't reflect CTS in any of the status registers. The manual states: The CTSB input pin controls the transmitter operation. The enable state is with CTSB low. The transmitter is automatically disabled if CTSB is high. So, if nothing else, Ben, make sure the CTSB pin is tied to ground.

Jason Thorpe

My gut would be, as a test as a sanity check, set the buffer boundary to 1. It would be slow, but you should see every character. If that works, then it is a logic puzzle and not an electronics issue. I am leaning towards ProgrammerDor's thought, also.

George Harris

Wild guess, probably wrong: in IRQ_HANDLER you “lda ACAI_STATUS” first thing to indicate you have handled the interrupt before you have actually handled it. What happens if another interrupt comes during the WRITE_BUFFER subroutine? The reason I think I am wrong is because all the code goes OK into and then out of the buffer and into memory.

Paul Heller

I think the problem might be in your incoming character Interrupt handler. [bios.s IRQ_HANDLER] You are clearing the Interrupt flag too soon. You should clear it at the end of the interrupt routine. It is getting interrupted again before you write the output to the screen. Just move the "lda ACIA_STATUS" to at least below the "jsr WRITE_BUFFER" maybe even just before the "plx".

ProgrammerDor

I think once you're using flow control it applies both ways. So the computer has to signal it can receive your echo'd chars. But I didn't see you hookup the other side of RTS/CTS for that. Have you tried that?

James Chacon


More Creators