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:
UART_Init()
, no difference.The error is to enable the serial interrupt without providing an interrupt service routine (ISR).
You have two options:
The source clearly shows the enabling of the serial interrupt:
EA = 1; // Enable global interrupts
ES = 1; // Enable serial interrupt
But the source does not define an interrupt service routine. It would look like this:
void serial_isr(void) interrupt 4 {
/* statements */
}
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):
LINK MAP OF MODULE: firmware (?C_STARTUP)
TYPE BASE LENGTH RELOCATION SEGMENT NAME
-----------------------------------------------------
* * * * * * * C O D E M E M O R Y * * * * * * *
CODE 0000H 0003H ABSOLUTE
CODE 0003H 008CH UNIT ?C_C51STARTUP
CODE 008FH 0032H UNIT ?PR?_UART_TXSTRING?MAIN
And this (shortened):
SYMBOL TABLE OF MODULE: firmware (?C_STARTUP)
VALUE TYPE NAME
----------------------------------
------- MODULE ?C_STARTUP
C:0003H SEGMENT ?C_C51STARTUP
C:0000H PUBLIC ?C_STARTUP
C:0006H SYMBOL IDATALOOP
C:0003H SYMBOL STARTUP1
C:0000H LINE# 126
C:0003H LINE# 133
C:0005H LINE# 134
C:0006H LINE# 135
C:0007H LINE# 136
C:0009H LINE# 185
C:000CH LINE# 196
------- ENDMOD ?C_STARTUP
------- MODULE MAIN
C:0000H SYMBOL _ICE_DUMMY_
C:00DAH PUBLIC UART_Init
C:008FH PUBLIC _UART_TxString
D:0018H SYMBOL string
There is a gap between the end of the module ?C_STARTUP
at 0x0C and the start of the module MAIN
at 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:
TI
is set.UART_TxString()
is interrupted in its while
loop without a chance to see TI
set.UART_TxString()
at 0x008F (see excerpt of the map file).string
is at the static fixed location 0x18 (see excerpt of the map file).string
still points to the first character, and so SBUF
receives again the first character.TI
is still set, because it does not reset automatically.while
loop quits immediately. TI
is reset by software.SBUF
to the shift register of the UART. So the next character does not overwrite it.TI
is 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 of string
.while
loop where the first interrupt happened. Since TI
will never become set again, it loops forever.Because your program has this endless while
loop in main()
, 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.