[ECOS] AT91 dropping chars at low speeds

Øyvind Harboe oyvind.harboe@zylin.com
Mon Oct 25 12:51:00 GMT 2004


I've been struggling with EB40a dropping chars when both
the serial ports are running. This problem exists even at feeble speeds
like 38400.

Any insights or independent confirmation of this problem would be
welcome! :-)

Attached is the workaround I'm currently testing. Looks much better so
far.

-- 
Øyvind Harboe
http://www.zylin.com

-------------- next part --------------
? fifofix.txt
Index: ChangeLog
===================================================================
RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/ChangeLog,v
retrieving revision 1.11
diff -u -w -r1.11 ChangeLog
--- ChangeLog	26 Jan 2004 08:38:22 -0000	1.11
+++ ChangeLog	25 Oct 2004 12:37:38 -0000
@@ -1,3 +1,10 @@
+2004-10-25 Oyvind Harboe <oyvind.harboe@zylin.com>
+
+	* changed how hardware fifos are handled + added >2 double buffering. This is
+	required to make sure that data is not lost when lots of interrupts are happening
+	in the system. This applies even at low speeds(38400). Typically this can be seen
+	when both serial ports are used simultaneously.
+	
 2004-01-26  Andrew Lunn  <andrew.lunn@ascom.ch>
 
 	* src/at91_serial.c (at91_serial_getc_polled) 
Index: src/at91_serial.c
===================================================================
RCS file: /cvs/ecos/ecos/packages/devs/serial/arm/at91/current/src/at91_serial.c,v
retrieving revision 1.9
diff -u -w -r1.9 at91_serial.c
--- src/at91_serial.c	26 Jan 2004 08:38:24 -0000	1.9
+++ src/at91_serial.c	25 Oct 2004 12:37:39 -0000
@@ -80,6 +80,8 @@
 #define SIFLG_XMIT_BUSY     0x02
 #define SIFLG_XMIT_CONTINUE 0x04
 
+#define BUFNUM 6
+
 typedef struct at91_serial_info {
     CYG_ADDRWORD   base;
     CYG_WORD       int_num;
@@ -87,10 +89,12 @@
     int            transmit_size;
     cyg_interrupt  serial_interrupt;
     cyg_handle_t   serial_interrupt_handle;
-    cyg_uint8      *rcv_buffer[2];
+    cyg_uint8      rcv_buffer[BUFNUM][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE + RCVBUF_EXTRA];
     cyg_uint16     rcv_chunk_size;
-    cyg_uint8      curbuf;
+    cyg_int32	   curbufwrite;
+    cyg_int32	   curbufread;
     cyg_uint8      flags;
+   	cyg_uint8      *end[BUFNUM];
 } at91_serial_info;
 
 static bool at91_serial_init(struct cyg_devtab_entry *tab);
@@ -138,13 +142,10 @@
 #endif
 
 #ifdef CYGPKG_IO_SERIAL_ARM_AT91_SERIAL0
-static cyg_uint8 at91_serial_rcv_buffer_0
-    [2][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_RCV_CHUNK_SIZE + RCVBUF_EXTRA];
 static at91_serial_info at91_serial_info0 = {
     base            : (CYG_ADDRWORD) AT91_USART0,
     int_num         : CYGNUM_HAL_INTERRUPT_USART0,
     rcv_chunk_size  : CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_RCV_CHUNK_SIZE,
-    rcv_buffer      : {at91_serial_rcv_buffer_0[0], at91_serial_rcv_buffer_0[1]}
 };
 
 #if CYGNUM_IO_SERIAL_ARM_AT91_SERIAL0_BUFSIZE > 0
@@ -185,13 +186,10 @@
 #endif //  CYGPKG_IO_SERIAL_ARM_AT91_SERIAL1
 
 #ifdef CYGPKG_IO_SERIAL_ARM_AT91_SERIAL1
-static cyg_uint8 at91_serial_rcv_buffer_1
-    [2][CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE + RCVBUF_EXTRA];
 static at91_serial_info at91_serial_info1 = {
     base            : (CYG_ADDRWORD) AT91_USART1,
     int_num         : CYGNUM_HAL_INTERRUPT_USART1,
     rcv_chunk_size  : CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_RCV_CHUNK_SIZE,
-    rcv_buffer      : {at91_serial_rcv_buffer_1[0], at91_serial_rcv_buffer_1[1]}
 };
 
 #if CYGNUM_IO_SERIAL_ARM_AT91_SERIAL1_BUFSIZE > 0
@@ -260,7 +258,13 @@
     HAL_WRITE_UINT32(base + AT91_US_IDR, 0xFFFFFFFF);
 
     // Start receiver
-    at91_chan->curbuf = 0;
+    at91_chan->curbufwrite = 0;
+    at91_chan->curbufread = 0;
+	int i;
+	for (i=0; i<BUFNUM; i++)
+	{
+	    at91_chan->end[i] = NULL;
+	}
     HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[0]);
     HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT);
     HAL_WRITE_UINT32(base + AT91_US_IER, AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT);
@@ -290,7 +294,13 @@
 #ifdef CYGDBG_IO_INIT
     diag_printf("AT91 SERIAL init - dev: %x.%d\n", at91_chan->base, at91_chan->int_num);
 #endif
-    at91_chan->curbuf = 0;
+	int i;
+	for (i=0; i<BUFNUM; i++)
+	{
+	    at91_chan->end[i] = NULL;
+	}
+    at91_chan->curbufwrite = 0;
+    at91_chan->curbufread = 0;
     at91_chan->flags = SIFLG_NONE;
     at91_chan->stat = 0;
     (chan->callbacks->serial_init)(chan);  // Really only required for interrupt driven devices
@@ -461,17 +471,32 @@
     HAL_READ_UINT32(base + AT91_US_IMR, mask);
     stat &= mask;
 
-    if (stat & (AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT)) {
-        cyg_uint32 x;
-        HAL_WRITE_UINT32(base + AT91_US_IDR, AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT);
+    if (stat & AT91_US_IER_TIMEOUT) {
+    	/* 
+    	 * the FIFO is still running, we have to stop feeding it
+    	 * and wait for it to become idle
+    	 */
+        HAL_WRITE_UINT32(base + AT91_US_IDR, AT91_US_IER_TIMEOUT);
         HAL_WRITE_UINT32(base + AT91_US_RCR, 0);
         HAL_WRITE_UINT32(base + AT91_US_RTO, 0);
-        HAL_READ_UINT32(base + AT91_US_RPR, x);
-        HAL_WRITE_UINT32(
-            base + AT91_US_RCR,
-            (CYG_ADDRESS) at91_chan->rcv_buffer[at91_chan->curbuf]
-                + at91_chan->rcv_chunk_size + RCVBUF_EXTRA - x
-        );
+    }
+    
+    if (stat & AT91_US_IER_ENDRX) {
+    	/* the FIFO is currently stopped, restart it. This needs to happen 
+    	 * at the highest interrupt priority since the AT91 serial hardware has
+    	 * a very tight window for doing this.
+    	 */
+        const cyg_uint32 cb = at91_chan->curbufwrite, nb = (cb+1)%BUFNUM;
+        at91_chan->curbufwrite = nb;
+
+    	cyg_uint8 *end;
+        HAL_READ_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) end);
+        CYG_ASSERT(at91_chan->end[cb]==NULL, ("serial receive DSR was not able to process quickly enough\n"));
+        at91_chan->end[cb]=end;
+        
+		HAL_WRITE_UINT32(base + AT91_US_CR, AT91_US_CR_RSTATUS | AT91_US_CR_STTTO);
+        HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[nb]);
+        HAL_WRITE_UINT32(base + AT91_US_RCR, at91_chan->rcv_chunk_size);
     }
 
     if (stat & (AT91_US_IER_TxRDY | AT91_US_IER_ENDTX))
@@ -479,6 +504,7 @@
 
     at91_chan->stat |= stat;
     cyg_drv_interrupt_acknowledge(vector);
+    
     return CYG_ISR_CALL_DSR;
 }
 
@@ -496,21 +522,19 @@
     at91_chan->stat = 0;
     cyg_drv_interrupt_unmask(vector);    
 
-    if (stat & (AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT)) {
-        const cyg_uint8 cb = at91_chan->curbuf, nb = cb ^ 0x01;
-        const cyg_uint8 * p = at91_chan->rcv_buffer[cb], * end;
-
-        at91_chan->curbuf = nb;
-        HAL_WRITE_UINT32(base + AT91_US_RCR, 0);
-        HAL_READ_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) end);
-        HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT);
-	HAL_WRITE_UINT32(base + AT91_US_CR, AT91_US_CR_RSTATUS | AT91_US_CR_STTTO);
-        HAL_WRITE_UINT32(base + AT91_US_RPR, (CYG_ADDRESS) at91_chan->rcv_buffer[nb]);
-        HAL_WRITE_UINT32(base + AT91_US_RCR, at91_chan->rcv_chunk_size);
-        HAL_WRITE_UINT32(
-            base + AT91_US_IER,
-            AT91_US_IER_ENDRX | AT91_US_IER_TIMEOUT
-        );
+    if (stat & AT91_US_IER_ENDRX) {
+    	while (at91_chan->curbufread!=at91_chan->curbufwrite)
+    	{
+	    	cyg_uint8 *end;
+		    const cyg_uint8 *p;
+	        const cyg_uint32 cb = at91_chan->curbufread;
+	        at91_chan->curbufread=(at91_chan->curbufread+1)%BUFNUM;
+	        
+	        CYG_ASSERT((at91_chan->end[cb]==NULL),("serial DSR received interrupt without data\n"));
+	        
+			end=at91_chan->end[cb];
+			at91_chan->end[cb]=NULL;
+		    p=at91_chan->rcv_buffer[cb];
 
         while (p < end) {
             rcv_req_reply_t res;
@@ -537,12 +561,16 @@
                 default:
                     // Buffer full or unknown error, can't do anything about it
                     // Discard data
-                    CYG_FAIL("Serial receiver buffer overflow");
+			  //                    CYG_FAIL("Serial receiver buffer overflow");
                     p = end;
                     break;
             }
         }
     }
+        // now we want timeout interrupts again.
+        HAL_WRITE_UINT32(base + AT91_US_RTO, RCV_TIMEOUT);
+        HAL_WRITE_UINT32(base + AT91_US_IER, AT91_US_IER_TIMEOUT);
+    }
 
     if (stat & AT91_US_IER_TxRDY) {
         at91_chan->flags &= ~SIFLG_XMIT_BUSY;

-------------- next part --------------
-- 
Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss


More information about the Ecos-discuss mailing list