Keil c51 声明数组时,uart 发送第一个字符两次,uVision 4 的奇怪行为

问题描述 投票:0回答:1

我目前面临一个特殊问题,即使用 Keil C51 在 89C52 微控制器上进行 UART 传输并在 Proteus 中进行模拟。代码片段如下:

#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) {
    }
}

我遇到的问题是,当声明一个数组时,例如

unsigned long myArrayDeclaration[4] = { 0x00FFF789, };
,UART 传输将第一个字符发送两次,导致“xxyz”而不是预期的“xyz”。如果我删除数组声明,传输就会正常工作。

任何类型的数组都会引发问题。

我使用 Keil C51 环境和 uVision 4 IDE 并在 Proteus 中模拟代码。微控制器是89C52芯片。

这里是一个错误再现最少的存储库,以及 Proteus 模拟:GITHUB REPO

我感谢任何关于如何解决 UART 传输异常的见解或建议。预先感谢您的帮助。

我已经尝试过:

  • 仅发送一个字节,结果相同。
  • UART_Init()
    之后等待(很多),没有区别。
  • 设置uVision 4的优化参数,但我不太明白我在做什么。
optimization uart keil 8051 c51
1个回答
0
投票

错误是在没有提供中断服务程序(ISR)的情况下启用串行中断。

我是怎么找到这个的?

源码清楚地显示了串行中断的启用:

    EA = 1;          // Enable global interrupts
    ES = 1;          // Enable serial interrupt

但是源码没有定义中断服务程序。它看起来像这样:

void serial_isr(void) interrupt 4 {
  /* statements */
}

89C52 的串行中断 ISR 在固定地址 0x0023 处启动,请参见数据手册。

下一步是查看链接器生成的映射文件以查找此处的内容。相关部分是这样的(缩短):

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

这个(缩短):

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

0x0C 处的模块结尾

?C_STARTUP
和 0x8F 处的模块开头
MAIN
之间存在间隙。由于没有其他模块使用此空间,因此它用零填充。

零字节是

NOP
的机器代码指令。处理器不执行任何操作并前进到下一条指令。

事情是这样的:

  1. 执行第一个字符的传输时会触发串行中断,因此设置
    TI
  2. 函数
    UART_TxString()
    在其
    while
    循环中被中断,没有机会看到
    TI
    设置。
  3. 处理器将其程序计数器 (PC) 压入堆栈并跳转到 0x0023。
  4. 有提到的零字节,处理器很高兴地运行它们而无需任何其他操作。
  5. 最后到达下一个非零字节,这恰好是 0x008F 处
    UART_TxString()
    的第一条指令(请参阅映射文件的摘录)。
  6. 由于生成的机器代码的内部工作,参数
    string
    位于静态固定位置0x18(请参阅映射文件的摘录)。
  7. string
    仍然指向第一个字符,因此
    SBUF
    再次接收到 第一个字符。
  8. TI
    仍处于设置状态,因为它不会自动重置。
  9. 因此
    while
    循环立即退出。
    TI
    由软件重置。
  10. 幸运的是,因为第一个字符已完全发送,所以重复的字符只是将
    SBUF
    留在UART的移位寄存器中。所以下一个字符不会覆盖它。
  11. 下次设置
    TI
    时,不会触发中断服务例程,因为ISR不会中断自身。 (注:是的,你可以这样做,但需要相应的说明,这里没有。)
  12. 所以函数
  13. UART_TxString()
    做了应该做的事情,发送
    string
    的所有字符。
  14. 发送完所有字符后,函数返回。
  15. 它返回到第一个中断发生的
  16. while
    循环。由于 
    TI
     永远不会再次设置,因此它会永远循环。
© www.soinside.com 2019 - 2024. All rights reserved.