![]() |
![]() |
#1 |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
Serial communication reliability
Hi coders, I am having some trouble trying to identify a rare random occurrence of a lost byte during serial communication, and I would like your lights and guidance.
Question 1) I often see code with a double-write to INTREQ to clear the relevant interrupt flag, for example in my serial receive buffer full interrupt routine, at the end: move.w #$0800, CHIP_BASE+INTREQ move.w #$0800, CHIP_BASE+INTREQ This appears to be apparently for "68040 compatibility": is that a fact or a myth? Question 2) The reason I am asking is to validate the tiny possibility that in between these two writes to INTREQ, would it be possible that another byte could have been received on the serial port, with this second write potentially clearing out the flag too soon. Since the interrupt code here didn't reach RTE yet, would the interrupt be ignored and lost? Question 3) Might be directly for Toni / WinUAE, but any insights would be appreciated: On WinUAE when testing two Amiga with a serial link connection (using the option -s win32.serial_port=INTERNAL_SERIAL), I saw in WinUAE code that when using this internal serial emulation, winUAE runs in "safe_receive" mode, which we can see it the function: static bool canreceive(void) { // don't replace data in SERDATR until interrupt is cleared in safe receive mode if (safe_receive) { if (intreq & (1 << 11)) { return false; } ... My question is, based on that behavior, if I understand properly, it should be "impossible" to lose a byte since WinUAE politely waits until INTREQ is cleared before injecting a new byte. I cannot explain would I seen to be losing a byte occasionnally. Perhaps it is on writing instead of receiving. But in order to diagnose better I would need to have a WinUAE compiled with SERIALDEBUG=2 probably :| Thanks! |
![]() |
![]() |
#2 |
ex. demoscener "Bigmama"
Join Date: Jun 2012
Location: Fyn / Denmark
Posts: 1,659
|
1) yes, this is normally required if you clear intreq just before rte (see 2).. also add a nop after the intreq writes to make sure instructions execute in-order on 060 (otherwise it could 'rte' before the writes are commited to the bus.
2) I would try clearing intreq at the start of the isr in order not to miss interrupt requests that arrive while you process the interrupt.. so the very first thing to do in your isr is to read the received byte, then clear the intreq bit.. then probably also check if the irf is set again before exiting your isr, and process the byte there instead of having to fire the isr again immediately.. If your isr takes a bit of time, the double write to intreq is probably not needed then (as paula will have processed it before your isr reaches rte).. How much of an effect this change effectively has depends on the size/runtime of your isr, of course.. Edit: you can check for overrun using bit 15 in SERDATR, btw.. Last edited by hooverphonique; 27 July 2024 at 21:48. |
![]() |
![]() |
#3 | |
Registered User
Join Date: Feb 2017
Location: Denmark
Posts: 1,297
|
Quote:
I don't know exactly which models require the "double ack" (and can't find the thread right now unfortunately), but I'm pretty sure it's sufficient to EITHER ack int + nop OR ack int + do other custom reg access. I.e. it's not that first ack is lost, but rather RTE could exit too quickly and still see old (non-acked) IPL value. nop fully guaranteeing bus transfer is done or another custom access providing extra delay should both be fine. On my B1260/50 single ack + RTE works 100% of the time. @OP: One thing to note is that despite WinUAE generally working very well, I found that it didn't behave similar enough to a real machine when debugging corner cases of "high speed" serial transfer (maybe things have improved or my setup was wrong) Also you mentioning interrupt routines and lost bytes makes me a bit suspicious. Are you sure the routine is called and completes in time? hooverphonique suggestion of monitoring of OVRUN is good. |
|
![]() |
![]() |
#4 | ||
ex. demoscener "Bigmama"
Join Date: Jun 2012
Location: Fyn / Denmark
Posts: 1,659
|
Quote:
Quote:
I know the ack isn't lost, I was just playing along with the OP's suggested way of overcoming this problem - I'm not sure just adding a nop between clearing the irf and rte is enough on the A4000 though.. |
||
![]() |
![]() |
#5 | |
Registered User
Join Date: Feb 2017
Location: Denmark
Posts: 1,297
|
Quote:
Probably whole NOP thing is cargo cult. I've just seen it a lot, and assumed it would fix it. 100% sure way should be to ack interrupt early, and ensure >2CCKs have passed before RTE - usually "free" by other work but double ack would work if interrupt routine isn't doing anything else. |
|
![]() |
![]() |
#6 |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
I think I just discovered something very wrong in my code, and it happens during writing to the serial port, not during the read. Here's my writing function:
.label In summary it means: while bit 13 in SERDATR (TBE TRANSMIT BUFFER EMPTY) is zero, wait; then write my data to SERDAT. The problem is that the act of reading SERDATR can in fact EAT the data that could has just been received, apparently before the RBF (READ BUFFER FULL) interrupt code gets the chance to be triggered ![]() Could someone help me figure out how serial writing is supposed to be done on the Amiga without risking losing a byte, since SERDATR is used both for reading and for writing? |
![]() |
![]() |
#7 |
ex. demoscener "Bigmama"
Join Date: Jun 2012
Location: Fyn / Denmark
Posts: 1,659
|
As far as I can gather from the description of SERDATR, reading it doesn't have any side effects.
|
![]() |
![]() |
#8 |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
Ah perhaps then this is WinUAE emulation issue: reading SERDATR might signal to WinUAE that a byte has been received.
I tried using a workaround by checking INTREQR bit 0 instead. This flag is set to 1 when the transmit buffer is empty. The only caveat is that it is not set to one on startup. I needed to send a dummy byte to SERDAT. And also that disabling interrupts on INTREQ appears to clear out this bit. So I had to replace the 'disable all': move.w #$7fff, INTREQ(a1) to move.w #$7ffe, INTREQ(a1) in order to not touch the TBE bit. But this seems a bit hackish. |
![]() |
![]() |
#9 |
Registered User
Join Date: Feb 2017
Location: Denmark
Posts: 1,297
|
What serial protocol are you implementing, and at what rate (and target)? Is it for a generic library of some sorts? Are you running with the OS suspended or enabled?
If it's not an interactive terminal application you're making, you'll usually not be making use of the A part of UART, and would not expect to be receiving characters while you're trying to transmit. Also how are you testing? Like I wrote previously, I didn't find winuae (with serial over TCP and a host windows program) to be a good guide for tricky stuff. |
![]() |
![]() |
#10 | |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
Quote:
I noticed today that if I lower the baud rate to 9600, it seems to be 100% accurate and no bytes are lost. Anything faster than that, and some bytes are getting "lost" on the receiving side. I know they are sent correctly because I see them in my c++ program, so I know they are being sent back to the other instance. For example, at SERPER = 91; // 38400 baud, I see on the sending Amiga log: .. Comm_Write_Byte: 3c Comm_Write_Byte: 3d Comm_Write_Byte: 3e Comm_Write_Byte: 3f Comm_Write_Byte: 40 Comm_Write_Byte: 41 Comm_Write_Byte: 42 Comm_Write_Byte: 43 .. and on my TCP/IP tunnel program, I see the same sequence: Received port=1234 3c 3d 3e 3f 40 41 42 43 Sending port=1235 3c 3d 3e 3f 40 41 42 43 .. but on the receiving Amiga program log, I see holes in the sequence: .. Comm_IsPacketReady: recv: 3c Comm_IsPacketReady: recv: 3d Comm_IsPacketReady: recv: 42 Comm_IsPacketReady: recv: 43 .. i.e.: the bytes 3e, 3f, 40 and 41 are missing. To my knowledge, there is no overrun on the serial port. If I try again with SERPER = 368; // 9600 baud, it appears to work flawlessly. I cannot figure out where the problem is but I'll keep looking. Last edited by remz; 02 August 2024 at 23:55. Reason: few typos |
|
![]() |
![]() |
#11 |
Registered User
Join Date: Jan 2008
Location: Warsaw/Poland
Age: 56
Posts: 2,115
|
You can check Twin program.
If I remember right it uses checksums when sending/receiving data. Important for fast baud rate, if my memory not failed. |
![]() |
![]() |
#12 |
WinUAE developer
Join Date: Aug 2001
Location: Hämeenlinna/Finland
Age: 49
Posts: 26,603
|
What kind of UAE config?
Does same happens in fastest possible (no more compatible ticked) and in cycle-exact modes? (they use slightly different ways to handle serial buffering). Does same also happen when "Direct" is ticked in IO Ports panel? (Keep all other serial checkboxes unticked) |
![]() |
![]() |
#13 |
Registered User
Join Date: Feb 2017
Location: Denmark
Posts: 1,297
|
Make sure you've captured the state of SERDATR before acknowledging the RBF interrupt. SERDATR.OVRUN is reset when you clear bit 11 (RBF) in INTREQ.
You're validating your code with essentially printf("... %x\n", recvd);? How big is your receive buffer and how fast of a setup? You'll be running the screen at ~30x times the serial rate that way. Are you sure it keep up? If you have a buffer depth of one (e.g. volatile int recvd=-1; /*...*/ for(;;) {while (recvd == -1) ; printf(...,recvd); recvd = -1;}) the gaps you see could be from the "main loop" missing characters not the interrupt when e.g. the screen scrolls. |
![]() |
![]() |
#14 |
Registered User
Join Date: Jan 2008
Location: Warsaw/Poland
Age: 56
Posts: 2,115
|
Or maybe Your routine is a few too slow to be called often?
If you want you can check this version: Code:
lea $dff018,a5 move.w #$2000,d7 .... .label move.w (a5),d1 ; SERDATR and.w d1,d7 beq.b .label move.w d0,$30-$18(a5) ; // SERDAT |
![]() |
![]() |
#15 | |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
Quote:
I didn't know 'direct' had an impact on TCP communication: With Direct it appears to become flawless: I never got any lost byte, even at 57600 baud, even with very unbalanced machines (i.e.: one being running at cycle accurate, cpu 1x, which means half-speed on A500, and the other being running in non-cycle-exact + immediate blitter, at CPU 4x. I will need to test my code on two laptops now (since I am using one laptop running two WinUAE), and I use Clumsy.exe to simulate some network lag. |
|
![]() |
![]() |
#16 | |||
Registered User
Join Date: Jan 2019
Location: Germany
Posts: 3,484
|
Quote:
Quote:
The right cure is not to erase the interrupt again, but to perform a dummy read from a custom chip address which will synchronize the CPU to the custom chip clock and thus wait until PAULA had enough time to clear the interrupt flag. Quote:
|
|||
![]() |
![]() |
#17 | |
Registered User
Join Date: Jan 2019
Location: Germany
Posts: 3,484
|
Quote:
Also the 68040 executes writes in-order, unless someone fiddled improperly with the MMU tables and marked the custom chip areas as "non-serialized". The default cache mode (without an MMU setup) is cache-inhibited,which implies serialized access. The default transparent translation register setup the Os leaves behind is also serialized imprecise access to the lower 16MB area. This is also what you need. |
|
![]() |
![]() |
#18 |
Registered User
Join Date: May 2022
Location: Canada
Posts: 149
|
It is working pretty well now; I would like to share my code here because I love to learn from you about best practices and optimizations.
Here's my receive interrupt function (RBF), I optimized it to the best of my knowledge and got it to 148 cycles / 40 bytes. Two variables are used: comm_rx_put is used as a byte which wraps around at 256; comm_rx_buff is a circular buffer of 256 bytes. Code:
Irq_RBF: move.w d1, -(a7) move.l a0, -(a7) move.w CHIP_BASE+SERDATR, d1 ; acknowledge interrupt only after we have finished processing the received byte move.w #$0800, CHIP_BASE+INTREQ lea.l _comm_rx_buff(pc), a0 adda.w _comm_rx_put(pc), a0 move.b d1, (a0) addq.b #1, _comm_rx_put+1 move.l (a7)+, a0 move.w (a7)+, d1 rte SECTION_BSS public _comm_rx_put public _comm_rx_buff _comm_rx_put: dc.w 0 _comm_rx_buff: ds.b 256 The function expects D0 to contain the byte value to send. It uses one variable: comm_tx_checksum, which is used as a cheap sort of checksum. One particularity is if the caller wants to send a byte value of $D5, this function will emit a second byte of value $5D immediately after. This is because I use $D5 as a 'end of packet separator' in the game code. Code:
_Comm_Write_Byte: lea $dff018,a0 ; set SERDATR as base A0 EOR.B D0,_comm_tx_checksum OR.W #$0100,D0 .wait_serdatr_ready: MOVE.W (a0),D1 ; SERDATR AND.W #$2000, D1 BEQ.S .wait_serdatr_ready MOVE.W D0,$30-$18(a0) ; SERDAT CMP.W #$01d5,D0 ; if a 0xD5 is sent, then immediately send a 0x5D BEQ.S .send_5d RTS .send_5d MOVE.W #$015d,D0 .wait_serdatr_ready2: MOVE.W (a0),D1 ; SERDATR AND.W #$2000, D1 BEQ.S .wait_serdatr_ready2 MOVE.W D0,$30-$18(a0) ; SERDAT RTS Thank you everyone for your input and participation in this thread (and any threads in fact!) |
![]() |
![]() |
#19 |
Registered User
Join Date: Jun 2016
Location: europe
Posts: 1,094
|
Code:
Irq_RBF: move.l a0, -(a7) lea _comm_rx_put(pc), a0 adda.w (a0)+, a0 move.b CHIP_BASE+SERDATR+1, (a0) ; acknowledge interrupt only after we have finished processing the received byte move.w #$0800, CHIP_BASE+INTREQ addq.b #1, _comm_rx_put+1 move.l (a7)+, a0 rte Code:
Irq_RBF: move.l a0, -(a7) lea _comm_rx_put(pc), a0 addq.b #1, 1(a0) adda.w (a0)+, a0 move.b CHIP_BASE+SERDATR+1, (a0) ; acknowledge interrupt only after we have finished processing the received byte move.w #$0800, CHIP_BASE+INTREQ move.l (a7)+, a0 rte |
![]() |
![]() |
#20 |
ex. demoscener "Bigmama"
Join Date: Jun 2012
Location: Fyn / Denmark
Posts: 1,659
|
@a/b: perhaps change "addq.b #1, _comm_rx_put+1" to "addq.b #1,(-2,a0)"
|
![]() |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
![]() |
||||
Thread | Thread Starter | Forum | Replies | Last Post |
Serial communication? | MickGyver | Coders. Blitz Basic | 4 | 10 April 2020 16:40 |
serial communication between Amiga and PC | TCH | support.Amiga Forever | 6 | 24 March 2020 00:00 |
CD32 Accelerator reliability .. | leonk | support.Hardware | 4 | 30 April 2019 19:35 |
Serial Port <--> Tablet: no communication | ral-clan | support.WinUAE | 0 | 02 February 2011 19:51 |
Supercars 2 communication screens | jotd | Retrogaming General Discussion | 11 | 04 December 2006 18:06 |
|
|