Handling Concurrent UART Transmission and Reception on 8051 Using Interrupts
Challenges in Polling-Based UART Communication
In embedded systems based on the 8051 architecture, managing simultaneous serial transmission and reception often presents timing conflicts. Consider a scenario where the microcontroller must periodically transmit status data to a host PC while remaining responsive to incoming control commands. A naive implementation typically relies on polling the receive flag within the main execution loop.
The Latency Problem
When the main loop includes blocking delay functions to regulate transmission intervals, the system cannot check for incoming data during those wait periods. For instance, if the code executes a 1-second delay to pace outgoing messages, any command receivde from the host during this window will not be processed until the delay completes. This results in notiecable lag between issuing a command and observing the physical response, such as toggling an LED.
The root cause lies in the sequential nature of polling. The CPU is occupied with the delay routine and cannot inspect the receive interrupt flag (RI) until it returns to the specific check point in the code. Consequently, real-time responsiveness is compromised.
Leveraging Hardware Interrupts
To resolve this latency, the UART peripheral should be configured to trigger a hardware interrupt upon data reception. This mechanism allows the microcontroller to pause its current task, execute a specific service routine to handle the incoming byte, and then resume the previous task immediately. This ensures that incoming commands are processed regardless of what the main loop is doing.
Configuration Requirements
Enabling UART interrupts involves setting specific bits in the Interrupt Enable register:
- ES: Enables the serial port interrupt.
- EA: Enables the global interrupt system.
The 8051 UART shares a single interrupt vector (Vector 4) for both transmission and reception events. Inside the Interrupt Service Routine (ISR), the software must distinguish between the two by checking the TI (Transmit Interrupt) and RI (Receive Interrupt) flags.
Implementation: Interrupt-Driven Reception
The following implementation demonstrates how to maintain periodic status updates while ensuring immediate response to control characters. The LED control logic is moved from the main loop into the ISR.
#include <REGX52.H>
#include <intrins.h>
/* Hardware Definitions */
sfr AUXR = 0x8E;
sbit system_led = P3^7;
/* Function Prototypes */
void delay_ms(unsigned int count);
void uart_init(void);
void uart_send_char(unsigned char data);
void uart_send_string(unsigned char *text);
void serial_isr(void) interrupt 4;
/* Global Variables */
unsigned char rx_byte;
/* Delay Routine */
void delay_ms(unsigned int count) {
unsigned char i, j, k;
_nop_();
/* Approximate delay logic for 11.0592MHz */
while(count--) {
i = 2;
j = 199;
k = 15;
do {
do {
while (--k);
} while (--j);
} while (--i);
}
}
/* UART Initialization */
void uart_init(void) {
PCON &= 0x7F; /* Disable baud rate doubling */
SCON = 0x50; /* 8-bit UART, variable baud, enable receiver */
AUXR = 0x01; /* Timer 1 clock source */
TMOD &= 0x0F; /* Clear Timer 1 mode bits */
TMOD |= 0x20; /* Timer 1 in 8-bit auto-reload mode */
TH1 = 0xFD; /* Reload value for 9600 baud */
TL1 = 0xFD; /* Initial value */
TR1 = 1; /* Start Timer 1 */
ES = 1; /* Enable Serial Interrupt */
EA = 1; /* Enable Global Interrupts */
}
/* Single Byte Transmission */
void uart_send_char(unsigned char data) {
SBUF = data;
while(!TI); /* Wait for transmission complete */
TI = 0; /* Clear flag */
}
/* String Transmission */
void uart_send_string(unsigned char *text) {
while(*text != '\0') {
uart_send_char(*text);
text++;
}
}
/* Main Execution Loop */
void main(void) {
system_led = 1; /* LED Off */
uart_init();
while(1) {
delay_ms(300); /* Initial stabilization */
uart_send_string("status_ok\r\n");
delay_ms(1000); /* Periodic interval */
/* Note: LED control is now handled in ISR */
}
}
/* Interrupt Service Routine */
void serial_isr(void) interrupt 4 {
if(RI == 1) {
RI = 0; /* Clear receive flag */
rx_byte = SBUF; /* Read data */
/* Command Processing */
if(rx_byte == 'o') {
system_led = 0; /* LED On */
}
else if(rx_byte == 'c') {
system_led = 1; /* LED Off */
}
}
}
Advanced Handling: String Command Buffering
For more complex control schemes, single-character commands may be insufficient. Implementing a buffer within the ISR allows the system to recognize multi-character sequences. The following example accumulates incoming data into an array and scans for specific command strings like "op" (open) or "cl" (close).
#include <REGX52.H>
#include <intrins.h>
#include <string.h>
/* Hardware Definitions */
sfr AUXR = 0x8E;
sbit system_led = P3^7;
/* Constants */
#define BUF_SIZE 12
/* Global Variables */
unsigned char cmd_buffer[BUF_SIZE];
unsigned char buf_index = 0;
/* Delay and UART Helper Functions */
void delay_ms(unsigned int count);
void uart_init(void);
void uart_send_string(unsigned char *text);
/* Delay Implementation */
void delay_ms(unsigned int count) {
unsigned char i, j, k;
_nop_();
while(count--) {
i = 2; j = 199; k = 15;
do { do { while(--k); } while(--j); } while(--i);
}
}
/* UART Setup */
void uart_init(void) {
PCON &= 0x7F;
SCON = 0x50;
AUXR = 0x01;
TMOD &= 0x0F;
TMOD |= 0x20;
TH1 = 0xFD;
TL1 = 0xFD;
TR1 = 1;
ES = 1;
EA = 1;
}
/* Transmission */
void uart_send_string(unsigned char *text) {
while(*text) {
SBUF = *text++;
while(!TI);
TI = 0;
}
}
/* Main Loop */
void main(void) {
system_led = 1;
uart_init();
memset(cmd_buffer, 0, BUF_SIZE);
while(1) {
delay_ms(300);
uart_send_string("system_ready\r\n");
delay_ms(1000);
}
}
/* ISR with Buffering */
void serial_isr(void) interrupt 4 {
if(RI == 1) {
RI = 0;
cmd_buffer[buf_index] = SBUF;
buf_index++;
/* Circular Buffer Logic */
if(buf_index >= BUF_SIZE) {
buf_index = 0;
}
/* Command Detection */
if(strstr((char*)cmd_buffer, "op")) {
system_led = 0;
buf_index = 0;
memset(cmd_buffer, 0, BUF_SIZE);
}
else if(strstr((char*)cmd_buffer, "cl")) {
system_led = 1;
buf_index = 0;
memset(cmd_buffer, 0, BUF_SIZE);
}
}
}
In this buffered approach, every received byte triggers the interrupt service routine. The data is stored sequentially in the array. The system checks the buffer content after each insertion. Once a matching command sequence is identified, the corresponding action is executed, and the buffer is cleared to prepare for the next command. This ensures that multi-byte instructions are recognized correctly without blocking the main program flow.