PJRC.COM Offline Archive, February 07, 2004 Visit this page on the live site |
| ||
Shopping Cart Checkout Shipping Cost Download Website |
Home | MP3 Player | 8051 Tools | All Projects | PJRC Store | Site Map |
You are here: 8051 Tools Code Library Serial I/O, Intr. | Search PJRC |
The purpose of this code is to allow your application code to be
able to send and receive data via the serial port, without having
to wait for each byte. This is done with buffers (internal ram
memory), one for transmitting
and one for receiving. When your program needs to transmit data,
it will call cout
. Using the
simple polled serial I/O routines,
cout would check if the 8051's UART is still sending a previous byte and
wait until it's done, then put the byte it was given when called
into SBUF to send it. This code does not wait. Instead, the byte
is written into a buffer in memory, and a pointer is changed which
tracks what portion of the buffer is holding data. If there is
room available in the buffer, this cout
routine
returns to your program immediately. At some point later in time,
when the 8051's UART is ready to transmit another byte, it will
generate an interrupt and the uart_intr
will transfer
one of the bytes from the buffer to the SBUF register to send it
and update the pointers to reflect that a byte was removed from
the buffer. This process allows your program, for example, to
send a large message "all at once". Repetitive calls to
cout
place all the bytes into the buffer, and then
they are later sent by the UART while your program is free to
do other tasks. As long as the transmit buffer is not full,
your program may call cout
and not have to wait
for the time it takes the UART to send the bits.
Similarily, for reception of data, the 8051's UART generates an
interrupt when it has received a byte. Again, uart_intr
is run and it removes the byte from the UART by reading SBUF, and
it writes the byte into a receive buffer (which is a separate
from the transmit buffer). Later, when your program needs to
receive data, it calls cin
to fetch a byte from the
receive buffer. If there is no data in the receive buffer,
cin
will wait until a byte is received and placed
there. Your program may call num_recv
to find out
how many bytes are currently waiting in the receive buffer. Usually
this is done to verify that there is at least one byte, as your
application may have other tasks to perform and would skip calling
cin
and processing received data when there hasn't
actually been anything received.
For example, your program
may have some lengthy computation task to perform, and during this
time data may arrive at the UART. The uart_intr
will take care of receiving it, so that your computational code
need not be written to also handle data reception. When the
computation is done, your program may call num_recv
to see if any data has been received, and cin
to
retrieve the bytes. As long as the receive buffer has space,
bytes may be properly received and later fetched with cin
.
The advantage of using this code is that it allows your program send a group of bytes, without waiting for each one to be sent, and to receive one or many bytes, without having to regularily poll the RI bit. Many applications can not tollerate waiting while bytes are sent, or must be able to receive bytes while the code is busy doing some computation, and later read them from the buffer.
There is a price to be paid. The code is more complex, though it's already written and tested, so with some luck you can use it as-is. If your application needs to perform tasks while data is being sent or received, using these routines is usually much simpler than trying to make your application check the serial port as it does its work. On the other hand, if your program doesn't do much while using the serial port, the simpler polled I/O routines may be a better choice.
Approximately 30-40 instructions are executed for each byte sent or received, which consumes some CPU time, particularily at fast baud rates. At the 8051's fastest possible baud rate (timer1 auto-reload set to 0xFF, which is 57600 baud at 11.0592 MHz), there are 160 instruction cycles per byte transmitted by the UART, so in this extreem case, CPU usage can be substantial.
Of course, you must allocate memory in the 8051's internal ram. The two buffers may be any size (3 bytes or more each), and they may be located anywhere, even in the indirect-only area (0x80 to 0xFF) in 8052 chips with 256 bytes of internal ram. You must also choose four bytes of ram to hold the pointers that track how much data is stored in each buffer. These four pointers much be in the directly addressable internal ram (0x00 to 0x7F).
For these routines to work, the UART interrupt much remain
enabled. Calling to init_uart_intr
will enable
it. Some operations performed by your application may not
be able to tollerate an interruption. For example, when driving
a motor with a push/pull circuit, it may be very undesirable
if the UART causes an interupt between an instruction that
turns off the pullup and the next instruction which turns on
the pulldown device. Another example is reading a Dallas
one-wire device, where a bit must be read within 2 to 15 µs.
The interrupt routines temporarily stop your program when the
UART needs to transfer a byte. During timing critical portions
of your code (if there are any), you will need to disable
interrupts ("CLR EA") before beginning the timing critical code,
and then re-enable interrupts afterwards ("SETB EA"). If
interrupts are disabled for too long, it is possible that the
transmission will be paused or a received byte could be lost.
There are two general techniques to choose buffer sizes. The first way is to consider how frequently the application sends and attempts to receive data, compared with the actual time taken to send and receive the bytes at the configured baud rate. For example, at 9600 baud (approx 1ms per byte), if the longest task the program performs without checking for reception (cin or num_recv) is 12 ms, then a buffer size of at least 13 would be needed (one byte of the buffer is not used).
A second approach is to ignore timing and consider the content of messages sent and received. For example, if the application receives messages from a user, and the longest valid message is 9 bytes, then a receive buffer of 10 bytes may be acceptable. This assumes that it is illegal for the user to send a second message until having received a response from the first one.
Every application is different, and these routines have been designed to be configurable to the buffer sizes required. It is critically important to choose large enough buffers, and to set these eight constants to properly allocate the memory needed by these routines.
This code is available as plain text or in a zip file.