Discussion
Forum
Home
Company Information
Information Request
Linux How-to Guides
ADSP 21xx Digital Signal
Processing Tutorials
SW Utilities
On-line Order Form
Aerospace Projects
Commercial Projects
Circuit Boards
Server Support
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;
|
|
|