Aerospace



Citadel Forums

Home

Company Information

Information Request

Linux How-to Guides

ADSP 21xx
Digital Signal Processing
Tutorials

SW Utilities

On-line Order Form

Server Support


bonk

Have you found this site useful? Did we save you time? Did we cure your head-ache? Is your hair growing back now?

Please make a donation to help with maintenance.


Objective Real-Time Software on the ADSP21XX

Serial UART Objects on the ADSP 21XX

General

One of the big shortcomings of the ADSP 21XX in terms of general purpose real-time software development, is the lack of an asynchronous serial port. These extremely handy UARTs are required to interface to external devices such as PCs for test, debug, setup and so forth. The advantages of asynchronous serial ports are low cost cabling, long distance transmission of control signals, and almost universal availability. In this chapter, we shall develop a universal software based UART, which can be used to operate on any pair of I/O pins. Furthermore, the code shall be developed as an object which can be scaled to allow the use of multiple instances, for one or more serial ports.

Do you have an application for 2 serial ports running at 9600 baud? Or how about 32 ports running at 19200 baud? Well, I would think that 32 ports is pushing your luck a bit, but if the processor doesn't need to do much else, it will probably work just dandy!

As usual, there is a tradeoff. The code in this chapter has been kept simple and presents a UART design without parity, flow control and the like. Also, all instances of the UART have to use the same speed setting. Otherwise, the ports are completely independent and can receive and transmit full duplex, asynchronously on multiple channels. The full code for the UART objects are supplied in the directory \work\uart on the CD-ROM.

Frame Format

An asyncronous data frame consists of 10 bits of data:

  • Start bit = Zero
  • Data bits D0 to D7
  • Stop bit = One

Sometimes, a parity bit is inserted before the stop bit, to aid with error detection. If you are using block formatted messages appended with a cyclic redundancy check such as the CRC16, or even just an 8 bit modulo 2 sum, then the parity bits are not required. You can of course modify the UART code to include parity if really want to have it.

Another condition is evident from the stop bit: The line will idle at the High or One state. This is required to enable the detection of a start bit. If the line would be stuck in the low state, then a stop bit frame error would result. The use of the Low/High start and stop bits therefore prevents the reception of a continuous stream of garbage on a disconnected (floating) or failed line.

Timer Ticks

We mentioned in the introduction that the UARTS are independent and can simultaneously receive and transmit. This is done by driving two separate Receive and Transmit finite state machines from the timer tick interrupt. In order to transmit a byte of data at 9600 baud, all we would need as a 9600 timer tick rate. Then we can implement a software based shift register and toggle an I/O pin as required to transmit the data. The problem with this approach is that we don't have any control over received data. Therefore, there is no guarantee that the incoming data will arrive with a convenient skew to allow us to sample the bits properly.

When receiving a stream of bits, it is evident that if we would sample the bits on a bit edge, unpredictable results would be obtained. The purpose of the start bit in the data frame is to allow us to calculate the data mid bit positions, based upon the falling edge of the start bit. We could adjust the timer tick a little to allow sampling in the middle of a bit, but if the transmitter was already running and using the same clock tick, any adjustment of the clock tick would be disastrous to the transmission.

A simple solution is to increase the clock tick rate to say, three times the data rate. For transmissions, we simply shift out a bit on every third tick, while the receiver could twiddle its tick counter to enable it to sample the incoming data approximately in the middle of the data bits. The higher the tick rate, the more accurate the mid bit sampling that could be obtained. Hardware based UARTS usually employ a sixteen times clock and multiple bit sampling with majority voting. In a software based UART design it is desirable to limit the clock interrupt rate to something sensible, so as to conserve processing cycles. Therefore multiple bit sampling is a luxury that is usually ill afforded. This design uses a three times clock and single sampling, which has been found to be quite adequate over short distances and with good cabling.

Synchronization

The receiver either has to sample the incoming data on every clock tick until a start bit is detected, or an interrupt input could be used to detect the start bit. In this example, an Interrupt Service Routine (ISR) would set a semaphore (flag) when a start bit is detected. This semaphore will remain set until the UART processes the stop bit, thus allowing the ISR to detect the next start bit. We thereby have a very simple signaling system between the receive ISR and the timer ISR which is running the finite state machines.

If the system uses a power saving mode, then the receive ISR would wake the CPU from its slumber as soon as data is received. This would allow the system to power down and yet be responsive to user inputs. This is essential for battery operated devices.

Each serial UART would require a separate receive ISR. While the bulk of the UART code presented here is object oriented and can be scaled with the modification of a single definition for the number of channels desired, specific small hardware input/output (I/O) routines need to be created for each individual port and they may be quite different, depending upon the physical hardware used.

Example 1 shows the details of a typical receive ISR.

Example 1. Receive Interrupt Service Routine
/*********************************************************************
* Name:        ser_rx_isr1
* Description: synchronize UART on start bit
* Constraints: zero timer to sample data next timer tick
*              set start bit detect semaphore if in idle state
*********************************************************************/
ser_rx_isr1:
   MAC_ISR_ENTER

ser_isr1_enter:
   ay0 = SER_CHAN1;
   MAC_RD_DM(ar, ^ser_rx_state, ay0)

   ay1 = SER_IDLE_STATE;
   af = ar - ay1;
   if ne jump ser_rx_isr1_exit;           /* test channel state */
   
   MAC_RD_DM(ar, ^ser_rx_semaphore, ay0)
   ay1 = 1;
   af = ar - ay1;
   if ne jump ser_rx_isr1_exit;           /* test semaphore */
   
   ar = 1;
   MAC_WR_DM(ar, ^ser_rx_semaphore, ay0)  /* set semaphore */

   ar = SER_SYNCH_TICK;
   MAC_WR_DM(ar, ^ser_rx_timer, ay0)      /* sample data next time */

ser_rx_isr1_exit:
   MAC_ISR_EXIT
   rti;

The trick is to reduce the amount of overhead to a minimum when the UART is idle. The UART code presented here was written to present a clear example which is easy to maintain and debug. If you want to improve the design, work on the idle state first. The UART is idling most of the time and that is when cycles are exclusively going to waste. The receive semaphore mentioned above, could easily be used to short circuit the receiver when it is not performing useful work. A similar switch could be employed to completely disable the transmitter when it is not is use. Also note that most systems would perform perfectly satisfactorily with a 1200 baud UART, which due to the reduced overhead will bring a significant saving in processing cycles.

Buffering the Data

We have discussed circular buffering techniques in another chapter. These multi channel buffer routines are ideal for use with our multi channel UART. In order to keep the code simple, we use separate routines for the transmitters and receivers. It could be combined, but since for every channel, there are two buffers, the transmit and receive routines were kept separate, to keep the counted loops simple. Therefore, channel one uses transmit buffer one and receive buffer one, etc., otherwise channel zero would be using buffers one and two and channel two would use buffers three and four etc., which was too much of a complication for this example.

Example 2 shows the definitions and initialization code for the buffer objects. It can be seen here, that everything depends on only one definition SER_CHAN_MAX. The number of UART channels can be varied by changing this single literal to four, eight, thirty two or whatever you need!

Example 2. Serial Buffers
/***************************** Literals *****************************/
#define  SER_CHAN0            0x0000
#define  SER_CHAN1            0x0001
#define  SER_CHAN_MAX         0x0002      /* number of channels */

#define  SER_BUF_SIZE         0x0100
#define  SER_BUF_MAX          (SER_BUF_SIZE * SER_CHAN_MAX)
#define  SER_BYTE_MASK        0x00FF
#define  SER_EMPTY_MASK       0xFFFF

/***************************** Variables ****************************/
.VAR/DM/RAM/SEG=INT_DM
   ser_tx_data[SER_CHAN_MAX],
   ser_rx_data[SER_CHAN_MAX],
   ser_tx_rd[SER_CHAN_MAX],
   ser_tx_wr[SER_CHAN_MAX],
   ser_rx_rd[SER_CHAN_MAX],
   ser_rx_wr[SER_CHAN_MAX],
   ser_rx_semaphore[SER_CHAN_MAX],
   ser_rx_semaphore[SER_CHAN_MAX],
   ser_rx_timer[SER_CHAN_MAX],
   ser_tx_timer;


.VAR/DM/RAM/SEG=INT_DM/CIRC
   ser_tx_buf[SER_BUF_MAX],
   ser_rx_buf[SER_BUF_MAX];

/*********************************************************************
* Name:        ser_init
* Description: Initialize the SW UART
* Constraints: none
*********************************************************************/
ser_init:
   MAC_ENTER

   /*
    * init the Rx buffer pointers
    */
ser_init_enter:
   ay0 = 0;
   ay1 = ^ser_rx_buf;         /* buffer base address */
   cntr = SER_CHAN_MAX;
   do ser_init_rx until ce;
      /* init the rd/wr pointers */
      MAC_WR_DM(ay1, ^ser_rx_rd, ay0)
      MAC_WR_DM(ay1, ^ser_rx_wr, ay0)

      ar = SER_BUF_SIZE;      /* next buffer address */
      ar = ar + ay1;
      ay1 = ar;

      ar = ay0 + 1;           /* next pointer */
      ay0 = ar;
ser_init_rx: nop;

   /*
    * Init the Tx buffer pointers
    */
   ay0 = 0;
   ay1 = ^ser_tx_buf;         /* buffer base address */
   cntr = SER_CHAN_MAX;
   do ser_init_tx until ce;
      /* init the rd/wr pointers */
      MAC_WR_DM(ay1, ^ser_tx_rd, ay0)
      MAC_WR_DM(ay1, ^ser_tx_wr, ay0)

      ar = SER_BUF_SIZE;      /* next buffer address */
      ar = ar + ay1;
      ay1 = ar;

      ar = ay0 + 1;           /* next pointer */
      ay0 = ar;
ser_init_tx: nop;

   /*
    * Init the Finite State Machines
    */
   ay0 = 0;
   cntr = SER_CHAN_MAX;
   do ser_init_state until ce;
      ar = 0;
      MAC_WR_DM(ar, ^ser_rx_state, ay0)
      MAC_WR_DM(ar, ^ser_tx_state, ay0)
      MAC_WR_DM(ar, ^ser_rx_semaphore, ay0)
      MAC_WR_DM(ar, ^ser_rx_timer, ay0)
      
      ar = ay0 + 1;           /* next pointer */
      ay0 = ar;
ser_init_state: nop;
   ar = 0;
   dm(ser_tx_timer) = ar;

ser_init_exit:
   MAC_EXIT
   rts;

Data is written to and read from the circular buffers using special circular buffer management routines. Example 3 shows a pair of buffer managers. Note that the routine which would be called from a foreground process, needs to be protected against interrupts, to prevent the timer ISR from clobbering the pointers when it calls the finite state machines. Since we have separate routines for transmit and receive, we can remove the the interrupt disabling from the pair of routines that will be called exclusively from the ISR context. Interrupt disabling is managed via the IMASK register. Before disabling the mask, we save it in an unused register and then restore it before leaving the routine.

Note that this is a very subtle trick. If we would disable interrupts using the dis ints and ena ints instructions, then we would have to be very careful to ensure that whenever a higher level routine disabled interrupts, any lower level routine it calls, would not inadvertently enable interrupts prematurely. By saving and restoring the IMASK register, we ensure that if interrupts are disabled, any lower level routine would leave the interrupts disabled upon return. This anti bugging method can save you many hours of frustrating debug time!

Example 3. Circular Buffer Management
/*********************************************************************
* Name:        ser_put_txbuf
* Description: Put data in circular buffer
* Constraints: ar = data
*              ay0 = port number
* NOTE: For simplicity, there is no protection against overruns.
*       Overruns will only occur if there is a SW error, in which
*       case the system is bust and needs a reset.
*********************************************************************/
ser_put_txbuf:
   MAC_ENTER
   MAC_DISABLE(ax1)

ser_put_tx_buf_enter:
   MAC_RD_DM(ax0, ^ser_tx_wr, ay0)     /* get the wr pointer */
   i3 = ax0;

   m3 = 1;                             /* 1 byte at a time */
   l3 = SER_BUF_SIZE;                  /* circ buffer size */
   dm(i3, m3) = ar;                    /* put the data */
   l3 = 0;                             /* change back to linear mode */
   
   ar = i3;
   MAC_WR_DM(ar, ^ser_tx_wr, ay0)      /* save the new wr pointer */

ser_put_txbuf_exit:
   MAC_ENABLE(ax1)
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        ser_put_rxbuf
* Description: Put data in circular buffer
* Constraints: ar = data
*              ay0 = port number
* NOTE: Protection against interrupts removed since the routine
*       is always called from an ISR and nesting is disabled.
*       For simplicity, there is no protection against overruns.
*       Overruns will only occur if there is a SW error, in which
*       case the system is bust and needs a reset.
*********************************************************************/
ser_put_rxbuf:
   MAC_ENTER
   /*MAC_DISABLE(ax1)*/

ser_put_rx_buf_enter:
   MAC_RD_DM(ax0, ^ser_rx_wr, ay0)     /* get the wr pointer */
   i3 = ax0;

   m3 = 1;                             /* 1 byte at a time */
   l3 = SER_BUF_SIZE;                  /* circ buffer size */
   dm(i3, m3) = ar;                    /* put the data */
   l3 = 0;                             /* change back to linear mode */
   
   ar = i3;
   MAC_WR_DM(ar, ^ser_rx_wr, ay0)      /* save the new wr pointer */

ser_put_rxbuf_exit:
   /*MAC_ENABLE(ax1)*/
   MAC_EXIT
   rts;

UART Finite State Machine Definitions

The functions which need to be performed is quite straight forward and absolutely repetitive. We can therefore base our finite state machines on simple bit counters. Each transmission or reception has to process exactly 10 bits, therefore we can use the same state definitions for both the receivers and the transmitters. Example 4 shows the definition of the states and lookup tables for the UART.

Example 4. UART Finite State Machines
#define  SER_IDLE_STATE        0x0000
#define  SER_STBIT_STATE       0x0001
#define  SER_D0BIT_STATE       0x0002
#define  SER_D1BIT_STATE       0x0003
#define  SER_D2BIT_STATE       0x0004
#define  SER_D3BIT_STATE       0x0005
#define  SER_D4BIT_STATE       0x0006
#define  SER_D5BIT_STATE       0x0007
#define  SER_D6BIT_STATE       0x0008
#define  SER_D7BIT_STATE       0x0009
#define  SER_SPBIT_STATE       0x000A
#define  SER_END_STATE         0x000B
#define  SER_LAST_STATE        0x000C

#define  SER_SYNCH_TICK        0x0003


/***************************** Constants ****************************/
#ifdef SER
.VAR/PM/RAM/SEG=INT_PM
   SER_TX_TBL[SER_LAST_STATE],
   SER_RX_TBL[SER_LAST_STATE],
   SER_OUT_TBL[SER_CHAN_MAX],
   SER_IN_TBL[SER_CHAN_MAX];
/*
 * Tx UART State Handler Lookup Table
 */
.INIT SER_TX_TBL:
      /**************************************************************/
      /* State                 Handler                              */
      /**************************************************************/
      /* SER_IDLE_STATE  */    ^ser_tx_idle,
      /* SER_STBIT_STATE */    ^ser_tx_start,  
      /* SER_D0BIT_STATE */    ^ser_tx_data0,  
      /* SER_D1BIT_STATE */    ^ser_tx_databits,  
      /* SER_D2BIT_STATE */    ^ser_tx_databits,  
      /* SER_D3BIT_STATE */    ^ser_tx_databits,  
      /* SER_D4BIT_STATE */    ^ser_tx_databits,  
      /* SER_D5BIT_STATE */    ^ser_tx_databits,  
      /* SER_D6BIT_STATE */    ^ser_tx_databits,  
      /* SER_D7BIT_STATE */    ^ser_tx_databits,  
      /* SER_SPBIT_STATE */    ^ser_tx_stop,
      /* SER_END_STATE   */    ^ser_tx_end;
      /**************************************************************/
/*
 * Rx UART State Handler Lookup Table
 */
.INIT SER_RX_TBL:
      /**************************************************************/
      /* State                 Handler                              */
      /**************************************************************/
      /* SER_IDLE_STATE  */    ^ser_rx_idle,
      /* SER_STBIT_STATE */    ^ser_rx_start,  
      /* SER_D0BIT_STATE */    ^ser_rx_databits,  
      /* SER_D1BIT_STATE */    ^ser_rx_databits,  
      /* SER_D2BIT_STATE */    ^ser_rx_databits,  
      /* SER_D3BIT_STATE */    ^ser_rx_databits,  
      /* SER_D4BIT_STATE */    ^ser_rx_databits,  
      /* SER_D5BIT_STATE */    ^ser_rx_databits,  
      /* SER_D6BIT_STATE */    ^ser_rx_databits,  
      /* SER_D7BIT_STATE */    ^ser_rx_databits,  
      /* SER_SPBIT_STATE */    ^ser_rx_stop,
      /* SER_END_STATE   */    ^ser_rx_idle;
      /**************************************************************/

In the transmitter, the handling of the first data bit is special. In this case, we have to read the circular buffer and save the byte in the transmit shift register. The rest of the data can be handled in the same way, since they all involve a mere shift and output of a data bit. Likewise, the receiver can handle all bits the same way, except for the last bit, when it has to go and save the data in the receive buffer. Therefore, both the transmitter and the receiver can be realised with a small set of state handlers.

Example 5 shows the transmit and receive finite state machines and the macros that make them tick.

Example 5. UART Finite State Machines
/*
 * Macro:         MAC_MACHINE
 * Description:   Basic Finite State Machine macro
 *                Performs a jump to an event handler
 * Parameters:    table = State Transition Table address (^TABLE)
 *                event = Event number (ar)
 * Constraints:   modifies i4, m4, i6, af
 * Returns:       none
 */
#define  MAC_MACHINE(table, event) \
   m4 = event;          /* get the event */ \
   i4 = table;          /* get the PM table address */ \
   modify(i4, m4);      /* create a table pointer */ \
   af = pass ar;        /* save ar */ \
   ar = pm(i4, m4);     /* lookup the jump address */ \
   i6 = ar;             /* put the address in an index register */ \
   ar = pass af;        /* restore ar */ \
   jump (i6);           /* jump to the event handler */
/*
 * Macro:         MAC_RD_DM
 * Description:   Read a data memory array
 * Parameters:    addr = DM address (^TABLE)
 *                index = DM index (ar)
 * Constraints:   m3 = modified
 *                i3 = modified
 * Returns:       data (ar)
 */
#define  MAC_RD_DM(data, addr, index)  \
   i3 = addr;           /* get the table address */ \
   m3 = index;          /* get the table index */ \
   l3 = 0; \
   modify(i3, m3);      /* post modify i1 */ \
   data = dm(i3, m3);   /* read the value */


/*
 * Macro:         MAC_WR_DM
 * Description:   Write to a data memory array
 * Parameters:    addr = DM address (^TABLE)
 *                data = data to write to DM (ar)
 *                index = DM index (ay1)
 * Constraints:   m3 = modified
 *                i3 = modified
 * Returns:       none
 */
#define  MAC_WR_DM(data, addr, index)  \
   i3 = addr;           /* get the DM table address */ \
   m3 = index;          /* get the table index */ \
   l3 = 0; \
   modify(i3, m3);      /* post modify i1 */ \
   dm(i3, m3) = data;   /* save the value */



/*********************************************************************
* Name:        ser_tx_uart
* Description: SW UART Tx Finite State Machine
* Constraints: called once for every bit to tx
*              ar = channel number
*********************************************************************/
ser_tx_uart:
   MAC_ENTER

ser_tx_uart_enter:
   ay0 = ar;                           /* get present state */
   MAC_RD_DM(ax0, ^ser_tx_state, ay0)

ser_tx_uart_mac:                       /* jump to the handler */
   MAC_MACHINE(^SER_TX_TBL, ax0)

ser_tx_uart_return:                    /* change state */
   MAC_WR_DM(ar, ^ser_tx_state, ay0)

   MAC_EXIT
   rts;

/*********************************************************************
* Name:        ser_rx_uart
* Description: SW UART Rx Finite State Machine
* Constraints: called once for every bit to rx
*              ar = channel number
*********************************************************************/
ser_rx_uart:
   MAC_ENTER

ser_rx_uart_enter:
   ay0 = ar;                        /* get present state */
   MAC_RD_DM(ax0, ^ser_rx_state, ay0)

ser_rx_uart_mac:                    /* jump to the handler */
   MAC_MACHINE(^SER_RX_TBL, ax0)

ser_rx_uart_return:                 /* change state */
   MAC_WR_DM(ar, ^ser_rx_state, ay0)

   MAC_EXIT
   rts;

Twidling the Data Bits

The transmitter runs every third timer tick. While it is in the idle state, it always scans the transmit buffer and ensures that the line is idling high. While the two buffer pointers are equal, the buffer is assumed to be empty. As soon as the pointers are not the same, it changes state to send a zero level start bit. On the first bit b0, the transmitter reads one byte from the transmit buffer and outputs it using a dedicated I/O routine for the relevant pin. Thereafter, it continues incrementing the state for every bit sent, until it reaches the stop bit, which is a high level, after which is returns to watching the buffer again.

If a foreground process would output a long string to a terminal, the buffer would be rapidly filled up, since the serial port is quite slow. A buffer size of 256 or 512 bytes should provide sufficient buffering to allow a typical ASCII screen refresh to take place without overunning the buffer. Example 6 shows the five routines making up the body of the transmitter.

Example 6. Transmitter
/*********************************************************************
* Name:        ser_tx_idle
* Description: Tx Idle do nothing, idle the line high
* Constraints: returns ar = idle state
*********************************************************************/
ser_tx_idle:
   MAC_ENTER

   ar = 1;
   call ser_output;                  /* idle the line high */

   MAC_RD_DM(ar, ^ser_tx_rd, ay0)    /* something in buffer? */
   MAC_RD_DM(ay1, ^ser_tx_wr, ay0)
   af = ar - ay1;
   if eq jump ser_tx_idle_empty;

   ar = SER_STBIT_STATE;
   jump ser_tx_idle_exit;

ser_tx_idle_empty:
   ar = SER_IDLE_STATE;
   
ser_tx_idle_exit:
   MAC_EXIT
   jump ser_tx_uart_return;

/*********************************************************************
* Name:        ser_tx_start
* Description: tx start bit
* Constraints: ar = port number
*              returns ar = next state
*********************************************************************/
ser_tx_start:
   MAC_QUICK_ENTER

ser_tx_start_enter:
   ay0 = ar;
   ar = 0;                          /* zero start bit */
   call ser_output;
   ar = SER_D0BIT_STATE;

ser_tx_start_exit:
   MAC_QUICK_EXIT
   jump ser_tx_uart_return;

/*********************************************************************
* Name:        ser_tx_data0
* Description: get byte and tx LSB
* Constraints: ar = port nmber
*              returns ar = next state
*********************************************************************/
ser_tx_data0:
   MAC_ENTER
   
ser_tx_data0_enter:
   ay0 = ar;
   call ser_get_txbuf;               /* get byte from buffer */

   ay1 = SER_EMPTY_MASK;
   af = ar - ay1;
   if eq jump ser_tx_data0_empty;

   sr = LSHIFT ar by -1 (lo);       /* LSB first - right shift */
   MAC_WR_DM(sr0, ^ser_tx_data, ay0)   

   ar = ar AND 1;
   call ser_output;

   ar = SER_D1BIT_STATE;
   jump ser_tx_data0_exit;

ser_tx_data0_empty:
   ar = SER_IDLE_STATE;

ser_tx_data0_exit:
   MAC_EXIT
   jump ser_tx_uart_return;

/*********************************************************************
* Name:        ser_tx_databits
* Description: tx rest of byte one bit at a time
* Constraints: ar = port number
*              returns ar = next state
*********************************************************************/
ser_tx_databits:
   MAC_ENTER
   
ser_tx_data_enter:
   ay0 = ar;
   MAC_RD_DM(ar, ^ser_tx_data, ay0)
   sr = LSHIFT ar by -1 (lo);          /* LSB first - right shift */
   MAC_WR_DM(sr0, ^ser_tx_data, ay0)

   ar = ar AND 1;
   call ser_output;

   MAC_RD_DM(ar, ^ser_tx_state, ay0)
   ar = ar + 1;

ser_tx_data_exit:
   MAC_EXIT
   jump ser_tx_uart_return;

/*********************************************************************
* Name:        ser_tx_stop
* Description: tx stop bit
* Constraints: ar = port number
*              returns ar = next state
*********************************************************************/
ser_tx_stop:
   MAC_QUICK_ENTER

   ay0 = ar;
   ar = 1;                                /* One Stop Bit */
   call ser_output;

   ar = SER_END_STATE;

   MAC_QUICK_EXIT
   jump ser_tx_uart_return;

/*********************************************************************
* Name:        ser_tx_end
* Description: end of tx stop bit
* Constraints: ar = port number
*              returns ar = next state
*********************************************************************/
ser_tx_end:
   ar = SER_IDLE_STATE;
   jump ser_tx_uart_return;


The receiver is synchronized by the receive ISR, which reloads the receiver timer to enable mid bit sampling of the data stream. The ISR also sets a semaphore to signal that the receiver should get going. The receiver runs every third timer tick and continously monitors the semaphore while in the idle state. As soon as the semaphore is set, the receiver moves directly to the data bit zero state. On the next and following runs, it reads the data bit and shifts it into the receive shift register. Upon receipt of the stop bit, it saves the complete byte in the receive buffer. If the stop bit is invalid, the data is discarded. This prevents the receipt of a continuous stream of zeros if the line is shorted to ground due to an error condition.

Example 7 shows the body of the receiver. Note that the system does not test to see whether the receive buffer is full, it will simply over write old data. This should be good enough for most non-critical applications.

EXAMPLE 7. Receiver
/*********************************************************************
* Name:        ser_rx_idle
* Description: rx idle, test ISR semaphore
* Constraints: ar = port number
*              returns ar = next state
*********************************************************************/
ser_rx_idle:
   MAC_ENTER

   ay0 = ar;
   MAC_RD_DM(ar, ^ser_rx_semaphore, ay0)
   af = pass ar;
   if eq jump ser_rx_idle_clr;       /* test semaphore */

   /*ar = SER_STBIT_STATE;*/         /* low baud rate */
   ar = SER_D0BIT_STATE;             /* high baud rate */

   ar = 0;
   MAC_WR_DM(ar, ^ser_rx_data, ay0)  /* zero the data byte */

   ar = SER_D0BIT_STATE;
   jump ser_rx_idle_exit;            

ser_rx_idle_clr:
   ar = SER_IDLE_STATE;
   
ser_rx_idle_exit:
   MAC_EXIT
   jump ser_rx_uart_return;
   
/*********************************************************************
* Name:        ser_rx_start
* Description: rx sample start bit - error if not zero
* Constraints: ar = port number
*              returns ar = next state
* NOTE: only useable at low baud rates, not for 9600 baud.
*********************************************************************/
ser_rx_start:
   MAC_ENTER

ser_rx_start_enter:
   ay0 = ar;
   call ser_input;
   af = pass ar;
   if ne jump ser_rx_start_false;       /* confirm start bit is zero */
   jump ser_rx_start_done;
   
ser_rx_start_false:
   ar = SER_IDLE_STATE;
   
ser_rx_start_done:
   MAC_EXIT
   jump ser_rx_uart_return;

/*********************************************************************
* Name:        ser_rx_databits
* Description: rx data LSB first, one bit at a time
* Constraints: none
*********************************************************************/
ser_rx_databits:
   MAC_ENTER

ser_rx_data_enter:
   ay0 = ar;
   call ser_input;
   sr = LSHIFT ar by 8 (lo);        /* move data bit to MSB then OR */

   MAC_RD_DM(ay1, ^ser_rx_data, ay0)
   ar = sr0 OR ay1;

   sr = LSHIFT ar by -1 (lo);       /* LSB first - right shift */
   MAC_WR_DM(sr0, ^ser_rx_data, ay0)

   MAC_RD_DM(ar, ^ser_rx_state, ay0) /* next state */
   ar = ar + 1;
   
ser_rx_data_done:
   MAC_EXIT
   jump ser_rx_uart_return;

   
/*********************************************************************
* Name:        ser_rx_stop
* Description: rx stop bit and clear ISR semaphore, write to buffer
* Constraints: none
*********************************************************************/
ser_rx_stop:
   MAC_ENTER
   
ser_rx_stop_enter:
   ay0 = ar;
   call ser_input;                   /* get the stop bit */
   af = pass ar;
   if eq jump ser_rx_stop_bad;       /* ignore if line stuck low */

   MAC_RD_DM(ar, ^ser_rx_data, ay0)  /* put the data in the buffer */
   call ser_put_rxbuf;

ser_rx_stop_bad:                     
   ar = 0;                          /* clear the ISR semaphore */
   MAC_WR_DM(ar, ^ser_rx_semaphore, ay0)

   ar = SER_IDLE_STATE;
   
ser_rx_stop_done:
   MAC_EXIT
   jump ser_rx_uart_return;

Input/Output

The UART object is designed for portability. Since no two sets of hardware designs are ever alike, the I/O differences are handled by a set of simple dedicated routines. These routines share a common interface, which encapsulates the hardware differences in neat I/O handlers. These handlers use lookup tables to call the relevant I/O routine for each port, the same way as the finite state machine and they actually use the exact same macro which you are already familiar with.

Example 8 shows the I/O handlers and dedicated I/O routines. You will have to supply a pair of small dedicated I/O routines for each channel in your system to replace ser_out1, ser_in1 etc. as applicable.

Example 8. I/O Handler
#ifdef SER
.VAR/PM/RAM/SEG=INT_PM
   SER_OUT_TBL[SER_CHAN_MAX],
   SER_IN_TBL[SER_CHAN_MAX];

/*
 * Crazy I/O handlers
 * Encapsulate crazy I/O in similar handler procedures
 */
.INIT SER_IN_TBL:
      ^ser_in0,
      ^ser_in1;

.INIT SER_OUT_TBL:
      ^ser_out0,
      ^ser_out1;


/*********************************************************************
* Name:        ser_output
* Description: output a bit on a UART
*              handles crazy HW through indirect jumps to special
*              handlers
* Constraints: ar = data bit
*              ay0 = port number
*********************************************************************/
ser_output:
   MAC_ENTER

ser_out_enter:
   MAC_MACHINE(^UT_OUT_TBL, ay0)

ser_out_return:
   MAC_EXIT
   rts;
   
/*********************************************************************
* Name:        ser_input
* Description: input a bit from a UART
*              handles crazy HW through indirect jumps to special
*              handlers
* Constraints: ar = port number
*              returns ar = data bit
*********************************************************************/
ser_input:
   MAC_ENTER

ser_in_enter:
   MAC_MACHINE(^UT_IN_TBL, ar)

ser_in_return:
   MAC_EXIT
   rts;

/*********************************************************************
* Name:        ser_out1
* Description: output a bit to channel 1 
* Constraints: ar = data bit
*********************************************************************/
ser_out1:
   MAC_QUICK_ENTER

   af = pass ar;
   if eq jump ser_out1_zero;
   ar = dm(PFDATA);
   ar = setbit 6 of ar;
   jump ser_out1_done;
   
ser_out1_zero:
   ar = dm(PFDATA);
   ar = clrbit 6 of ar;

ser_out1_done:
   dm(PFDATA) = ar;
   
   MAC_QUICK_EXIT
   jump ser_out_return;

/*********************************************************************
* Name:        ser_in1
* Description: input a bit from channel 1
* Constraints: return ar = data bit
*********************************************************************/
ser_in1:
   MAC_QUICK_ENTER

   ar = dm(PFDATA);
   ay1 = 1;
   sr = LSHIFT ar by -7 (lo);
   ar = sr0 AND ay1;
   
   MAC_QUICK_EXIT
   jump ser_in_return;



Copyright © 1996-2008, Aerospace Software Ltd., GPL.