Here is the patch, shows all modifications in my working folder.
Acutally, I did more than FIFO stuff, I also made it support 2 diffent 16x5x
chips on my target(different register-offset-step), and something else.
BTW, I prefer to solve half-duplex problem on hardware level, which avoid lots
of effort on software level.
> From: [EMAIL PROTECTED]
> To: [EMAIL PROTECTED]; [EMAIL PROTECTED]; [EMAIL PROTECTED]
> Subject: RE: [ECOS] Re: Half Duplex RS485
> Date: Fri, 25 Jan 2008 12:59:54 +1100
>
> Hi Wang,
> I'd be interested to see what you had to modify to make the FIFO work.
>
> The eCos source does not know about the fractional divider.
> Did you enable fix that as well?
>
> ...Laurie:{)
>
> -----Original Message-----
> From: wangcui [mailto:[EMAIL PROTECTED]
> Sent: Friday, 25 January 2008 12:33 PM
> To: Laurie Gellatly; Grant Edwards; [EMAIL PROTECTED]
> Subject: RE: [ECOS] Re: Half Duplex RS485
>
>
>
> Just FYI, the LPC2XXX's UART0 is not fully functional. So when the 16x5x
> serial driver check FIFO(in serial_config_port()), it will fail, thus FIFO
> is disabled for UART0. But UART1 works fine.
>
> To resolve it, I have to modified 16x5x driver code, force enable FIFO for
> UART0 and UART1.
>
>> From: [EMAIL PROTECTED]
>> To: [EMAIL PROTECTED]; [EMAIL PROTECTED]
>> Date: Thu, 24 Jan 2008 08:12:17 +1100
>> Subject: RE: [ECOS] Re: Half Duplex RS485
>>
>> Wang/Grant, thanks for the replies.
>>
>>>> My project has RS485 half duplex driven by UART0 of an
>>>> LPC2112. At present I've modified pc_serial_start_xmit and
>>>> pc_serial_stop_xmit to change a pin state so that the same
>>>> wires can be used for transmit and receive.
>>>>
>>>> This does not see to work when the FIFO is enabled.
>>>
>>>Then you probably did it wrong. :)
>>>
>>>I imagine that RTS is shutting off too soon. The problem is
>>>that pc_serial_stop_xmit() is called when the driver has no
>>>more data to send _to_ the UART. That's not when you need to
>>>shut off RTS. You need to shut off RTS when the UART is done
>>>sending data and both the FIFO and shift register are empty.
>>>
>>>You're probably shutting off RTS while the UART still has data
>>>in the tx FIFO and the tx shift register.
>> Funny thing is that it appears that the first part of the
>> transmission is lost.
>>
>>>> Has anyone else done RS485 half duplex?
>>>
>>>Many, many times.
>>>
>>>> Did you modify these routines or write your own?
>>>
>>>I usually pick a UART that supports half-duplex operation, and
>>>then just enabled that feature in the UART. [I use a custom
>>>eCos serial driver that supports quite a few more advanced UART
>>>features than the standard driver (e.g. flow control,
>>>half-duplex, inter-byte timeouts, 9-bit modes, FIFO control,
>>>etc.).
>>>
>>>If you don't have a proper UART, you need to enable the tx
>>>shift register empty interrupt and use that to trigger code
>>>that de-asserts RTS. If you're using a broken UART that
>>>doesn't have a tx shift register empty interrupt, then you'll
>>>have to poll for the tx shift register empty status. If you're
>>>using a really broken UART that doesn't have a _working_
>>>shift-register empty status[1], then you may have to start a
>>>timer that will wake you up at the point in time where RTS
>>>needs to be changed.
>>>
>>>> Did you get the FIFO to work?
>>>
>>>When there was one, yes.
>>>
>>>
>>>[1] There are broken UARTs (including a few PC chipsets) whose
>>> shift-register empty bit gets set _before_ the stop bit has
>>> been sent. In that case, you may need to use some sort of
>>> time-delay to wait until after the stop bit has been sent
>>> to toggle RTS. On a properly implimented RS-485 bus, there
>>> should be pull-up and pull-down resisters so that the bus
>>> idles in the mark state (same value as a stop bit), but to
>>> be on the safe side you should leave the bus driver on
>>> until after the stop bit has been sent.
>>
>> Grant, do you know if the LPC series have 'broken' UARTS?
>> Specifically LPC2212 and LPC2103?
>> U0TSR looks promising as an indication of when the bits
>> 'have left the building' - is it accurate or do I
>> need to add a time delay?
>>
>> Really appreciate the help. ...Laurie:{)
>>
>> --
>> Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
>> and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss
>>
>
> _________________________________________________________________
> 天凉了,添衣了,心动了,“七件”了
> http://get.live.cn
>
_________________________________________________________________
MSN 中文网,最新时尚生活资讯,白领聚集门户。
http://cn.msn.comdiff -r -u5 -N -x CVS
/cygdrive/z/Share/Resource/eCos/anoncvs/ecos/packages/devs/serial/generic/16x5x/current/src/ser_16x5x.c
/cygdrive/d/work/dev/ecos/packages/devs/serial/generic/16x5x/current/src/ser_16x5x.c
---
/cygdrive/z/Share/Resource/eCos/anoncvs/ecos/packages/devs/serial/generic/16x5x/current/src/ser_16x5x.c
2007-06-22 19:41:49.000000000 +0800
+++
/cygdrive/d/work/dev/ecos/packages/devs/serial/generic/16x5x/current/src/ser_16x5x.c
2008-01-25 11:47:46.232562700 +0800
@@ -62,15 +62,18 @@
#include <cyg/hal/hal_io.h>
// Only compile driver if an inline file with driver details was selected.
#ifdef CYGDAT_IO_SERIAL_GENERIC_16X5X_INL
-#ifndef CYGPRI_IO_SERIAL_GENERIC_16X5X_STEP
-#define CYGPRI_IO_SERIAL_GENERIC_16X5X_STEP 1
-#endif
-
-#define SER_REG(_x_) ((_x_)*CYGPRI_IO_SERIAL_GENERIC_16X5X_STEP)
+//here we use a 'step' variable to indicate the address delta between a 16X5X
channel's registers
+//user should new a variable named 'step' and set appropriate value before
access 16X5X's register.
+//For example:
+//{
+// int step = ser_chan->step;
+// HAL_WRITE_UINT8(base+REG_thr, c);
+//}
+#define SER_REG(_x_) ((_x_)*step)
// Receive control Registers
#define REG_rhr SER_REG(0) // Receive holding register
#define REG_isr SER_REG(2) // Interrupt status register
#define REG_lsr SER_REG(5) // Line status register
@@ -180,10 +183,15 @@
// selec_baud[] must be define by the client
typedef struct pc_serial_info {
cyg_addrword_t base;
int int_num;
+ int int_prio;
+ int step;
+ int baud_num;
+ int *select_baud;
+ int (*baud_generator)(unsigned int baud);
cyg_interrupt serial_interrupt;
cyg_handle_t serial_interrupt_handle;
#ifdef CYGPKG_IO_SERIAL_GENERIC_16X5X_FIFO
enum {
sNone = 0,
@@ -233,12 +241,17 @@
serial_config_port(serial_channel *chan,
cyg_serial_info_t *new_config, bool init)
{
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
- unsigned short baud_divisor = select_baud[new_config->baud];
+ int step = ser_chan->step;
+ unsigned short baud_divisor;
unsigned char _lcr, _ier;
+
+ if (new_config->baud >= ser_chan->baud_num) return false; // Invalid
configuration
+
+ baud_divisor = ser_chan->select_baud[new_config->baud];
if (baud_divisor == 0) return false; // Invalid configuration
// Disable port interrupts while changing hardware
HAL_READ_UINT8(base+REG_ier, _ier);
HAL_WRITE_UINT8(base+REG_ier, 0);
@@ -253,21 +266,22 @@
if (init) {
#ifdef CYGPKG_IO_SERIAL_GENERIC_16X5X_FIFO
unsigned char _fcr_thresh;
cyg_uint8 b;
- /* First, find out what kind of device it is. */
- ser_chan->deviceType = sNone;
- HAL_WRITE_UINT8(base+REG_mcr, MCR_LOOP); // enable loopback mode
- HAL_READ_UINT8(base+REG_msr, b);
- if (0 == (b & 0xF0)) { // see if MSR had CD, RI, DSR or CTS set
- HAL_WRITE_UINT8(base+REG_mcr, MCR_LOOP|MCR_DTR|MCR_RTS);
- HAL_READ_UINT8(base+REG_msr, b);
- if (0xF0 != (b & 0xF0)) // check that all of CD,RI,DSR and CTS set
- ser_chan->deviceType = s8250;
+ if (ser_chan->deviceType == sNone) {
+ /* First, find out what kind of device it is. */
+ HAL_WRITE_UINT8(base+REG_mcr, MCR_LOOP); // enable loopback mode
+ HAL_READ_UINT8(base+REG_msr, b);
+ if (0 == (b & 0xF0)) { // see if MSR had CD, RI, DSR or CTS set
+ HAL_WRITE_UINT8(base+REG_mcr, MCR_LOOP|MCR_DTR|MCR_RTS);
+ HAL_READ_UINT8(base+REG_msr, b);
+ if (0xF0 != (b & 0xF0)) // check that all of CD,RI,DSR and
CTS set
+ ser_chan->deviceType = s8250;
+ }
+ HAL_WRITE_UINT8(base+REG_mcr, 0); // disable loopback mode
}
- HAL_WRITE_UINT8(base+REG_mcr, 0); // disable loopback mode
if (ser_chan->deviceType == s8250) {
// Check for a scratch register; scratch register
// indicates 16450 or above.
HAL_WRITE_UINT8(base+REG_scr, 0x55);
@@ -308,10 +322,11 @@
CYGNUM_IO_SERIAL_GENERIC_16X5X_FIFO_TX_SIZE;
// Enable and clear FIFO
HAL_WRITE_UINT8(base+REG_fcr, _fcr_thresh);
}
else {
+ CYG_FAIL("Can't enable FIFO on this device.");
ser_chan->tx_fifo_size = 1;
HAL_WRITE_UINT8(base+REG_fcr, 0); // make sure it's disabled
}
ser_chan->tx_fifo_avail = ser_chan->tx_fifo_size;
@@ -348,36 +363,39 @@
#ifdef CYG_IO_SERIAL_GENERIC_16X5X_BAUD_GENERATOR
// Fill in baud rate table - used for platforms where this cannot
// be determined statically
int baud_idx, baud_val;
- if (select_baud[0] == 9999) {
+
+ if (ser_chan->select_baud[0] == 9999) {
+ CYG_ASSERTC(ser_chan->baud_generator != NULL);
+
// Table not yet initialized
// Assumes that 'select_baud' looks like this:
// static int select_baud[] = {
// 9999, -- marker
// 50, -- first baud rate
// 110, -- second baud rate
// etc.
- for (baud_idx = 1; baud_idx <
sizeof(select_baud)/sizeof(select_baud[0]); baud_idx++) {
- baud_val =
CYG_IO_SERIAL_GENERIC_16X5X_BAUD_GENERATOR(select_baud[baud_idx]);
- select_baud[baud_idx] = baud_val;
+ for (baud_idx = 1; baud_idx < ser_chan->baud_num; baud_idx++) {
+ baud_val =
ser_chan->baud_generator(ser_chan->select_baud[baud_idx]);
+ ser_chan->select_baud[baud_idx] = baud_val;
}
- select_baud[0] = 0;
+ ser_chan->select_baud[0] = 0;
}
#endif
#ifdef CYGDBG_IO_INIT
- diag_printf("16x5x SERIAL init - dev: %x.%d\n",
- ser_chan->base, ser_chan->int_num);
+ diag_printf("16x5x SERIAL init - dev: base=%x.step=%d.int_num=%d\n",
+ ser_chan->base, ser_chan->step, ser_chan->int_num);
#endif
// Really only required for interrupt driven devices
(chan->callbacks->serial_init)(chan);
if (chan->out_cbuf.len != 0) {
cyg_drv_interrupt_create(ser_chan->int_num,
- CYG_IO_SERIAL_GENERIC_16X5X_INT_PRIORITY,
+ ser_chan->int_prio == -1 ?
CYG_IO_SERIAL_GENERIC_16X5X_INT_PRIORITY : ser_chan->int_prio,
(cyg_addrword_t)chan,
pc_serial_ISR,
pc_serial_DSR,
&ser_chan->serial_interrupt_handle,
&ser_chan->serial_interrupt);
@@ -409,10 +427,11 @@
#ifndef CYGPKG_IO_SERIAL_GENERIC_16X5X_FIFO
cyg_uint8 _lsr;
#endif
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
+ int step = ser_chan->step;
#ifdef CYGPKG_IO_SERIAL_GENERIC_16X5X_FIFO
if (ser_chan->tx_fifo_avail > 0) {
HAL_WRITE_UINT8(base+REG_thr, c);
--ser_chan->tx_fifo_avail;
@@ -436,10 +455,11 @@
{
unsigned char c;
cyg_uint8 _lsr;
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
+ int step = ser_chan->step;
// Wait for char
do {
HAL_READ_UINT8(base+REG_lsr, _lsr);
} while ((_lsr & LSR_RSR) == 0);
@@ -469,10 +489,11 @@
case CYG_IO_SET_CONFIG_SERIAL_HW_RX_FLOW_THROTTLE:
{
cyg_uint8 _mcr;
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
+ int step = ser_chan->step;
cyg_uint32 *f = (cyg_uint32 *)xbuf;
unsigned char mask=0;
if ( *len < sizeof(*f) )
return -EINVAL;
@@ -509,26 +530,34 @@
static void
pc_serial_start_xmit(serial_channel *chan)
{
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
- cyg_uint8 _ier;
+ int step = ser_chan->step;
+ cyg_uint8 _ier, _lsr;
HAL_READ_UINT8(base+REG_ier, _ier);
_ier |= IER_XMT; // Enable xmit interrupt
HAL_WRITE_UINT8(base+REG_ier, _ier);
#ifdef CYGPKG_IO_SERIAL_GENERIC_16X5X_XMIT_REQUIRE_PRIME
- (chan->callbacks->xmt_char)(chan);
+ //some chip(at least LPC2XXX's embedded DUART) may not generate ISR_Tx
interrupt after enable IER_XMT,
+ //so we try transmitting here to trigger the ISR_Tx intertupt.
+ HAL_READ_UINT8(base+REG_lsr, _lsr);
+ if ((_lsr & LSR_THE)) {
+ // transmitter holding register ready
+ (chan->callbacks->xmt_char)(chan);
+ }
#endif
}
// Disable the transmitter on the device
static void
pc_serial_stop_xmit(serial_channel *chan)
{
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
+ int step = ser_chan->step;
cyg_uint8 _ier;
HAL_READ_UINT8(base+REG_ier, _ier);
_ier &= ~IER_XMT; // Disable xmit interrupt
HAL_WRITE_UINT8(base+REG_ier, _ier);
@@ -550,10 +579,11 @@
pc_serial_DSR(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
serial_channel *chan = (serial_channel *)data;
pc_serial_info *ser_chan = (pc_serial_info *)chan->dev_priv;
cyg_addrword_t base = ser_chan->base;
+ int step = ser_chan->step;
cyg_uint8 _isr;
// Check if we have an interrupt pending - note that the interrupt
// is pending of the low bit of the isr is *0*, not 1.
HAL_READ_UINT8(base+REG_isr, _isr);
@@ -645,14 +675,16 @@
}
}
break;
#endif
default:
- // Yes, this assertion may well not be visible. *But*
- // if debugging, we may still successfully hit a breakpoint
- // on cyg_assert_fail, which _is_ useful
- CYG_FAIL("unhandled serial interrupt state");
+ #if 0
+ //some chip(at least Philips SC16C2550B) may generate a interrupt
without valid source,
+ //so we print a error message rather than die here.
+ diag_printf("unhandled serial interrupt state""\r\n");
+ #endif
+ break;
}
HAL_READ_UINT8(base+REG_isr, _isr);
} // while
--
Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss