I'm currently facing a peculiar issue with UART transmission on an 89C52 microcontroller using Keil C51 and simulating it in Proteus. The code snippet is provided below:
#include <reg52.h>
#include <intrins.h>
// HERE: When an array is declared then uart will send the first uart character twice,
unsigned long myArrayDeclaration[4] = { 0x00FFF789, };
// If the line above exists , UART will wrongly send "xxyz", instead of "xyz", nonsense.
sbit UART_RXD = P3^0; // RXD pin
sbit UART_TXD = P3^1; // TXD pin
void UART_Init() {
TMOD |= 0x20; // Timer 1, Mode 2 (8-bit auto-reload)
// TH1 = 0xFD; // Set baud rate for 9600 bps at 11.0592 MHz crystal
TH1 = 0xFA; // Set baud rate for 9600 bps at 22.1184 MHz crystal
TL1 = TH1;
TR1 = 1; // Start Timer 1
SCON = 0x50; // Set serial mode 1 (8-bit UART)
EA = 1; // Enable global interrupts
ES = 1; // Enable serial interrupt
}
void UART_TxString(const char *string) {
while (*string != '\0') {
SBUF = *string; // Send character
while (!TI); // Wait for transmit complete
TI = 0; // Clear transmit interrupt flag
string++; // Move to the next character
//_nop_(); // This makes no difference
}
}
void main() {
UART_Init(); // Init uart
// _nop_(); // This makes no difference
UART_TxString("xyz"); // Send 3 chars
while (1) {
}
}
The issue I'm encountering is that when an array is declared for example unsigned long myArrayDeclaration[4] = { 0x00FFF789, };, the UART transmission sends the first character twice, resulting in "xxyz" instead of the expected "xyz." If I remove the array declaration, the transmission works correctly.
Any kind of array provokes the problem.
I'm using a Keil C51 environment with the uVision 4 IDE and simulating the code in Proteus. The microcontroller is an 89C52 chip.
Here is a repository with the minimal reproduction of the error, and the Proteus simulation: GITHUB REPO
I appreciate any insights or suggestions on how to resolve this anomaly in UART transmission. Thank you in advance for your assistance.
I have tried:
- To send just a byte, same result.
- To wait (a lot) after
UART_Init(), no difference. - To set up the optimization parameters of uVision 4 but I don't really understand what I am doing.
The error is to enable the serial interrupt without providing an interrupt service routine (ISR).
You have two options:
Analysis
The source clearly shows the enabling of the serial interrupt:
But the source does not define an interrupt service routine. It would look like this:
The ISR for the serial interrupt starts for the 89C52 at the fixed address of 0x0023, see the data sheet.
The next step was to look into the map file generated by the linker to find what is here. The relevant parts are this (shortened):
And this (shortened):
There is a gap between the end of the module
?C_STARTUPat 0x0C and the start of the moduleMAINat 0x8F. Since no other module uses this space, it is filled with zeroes.A zero byte is the machine code instruction for
NOP. The processor does nothing and advances to the next instruction.This is what happens:
TIis set.UART_TxString()is interrupted in itswhileloop without a chance to seeTIset.UART_TxString()at 0x008F (see excerpt of the map file).stringis at the static fixed location 0x18 (see excerpt of the map file).stringstill points to the first character, and soSBUFreceives again the first character.TIis still set, because it does not reset automatically.whileloop quits immediately.TIis reset by software.SBUFto the shift register of the UART. So the next character does not overwrite it.TIis set, the interrupt service routine is not triggered, because ISRs do not interrupt themselves. (Note: Yes, you can do that, but it needs respective instructions, which are not here.)UART_TxString()does what is shall do, sending all the characters ofstring.whileloop where the first interrupt happened. SinceTIwill never become set again, it loops forever.Because your program has this endless
whileloop inmain(), you cannot notice the difference.Final line: It was pure coincidence that this happened. Any other result emerges when some detail of the program changes.