yzhkpli 发表于 2026-5-3 14:14:42

帮忙测试一个串口发送接收程序

stc89c52rc,keilc

不管是发送文本模式还是hex模式,明明程序SBUF=SBUF=2;得到的全是+1.

但是如果把代码改成+4或者+1,都是得到正确结果。怪哉!!!
REN = 0; // 暂时禁止接收
和REN = 1;          // 重新允许接收

是deepseek给出的修改建议,加不加都一样。。{:4_192:}

#include <STC89C5xRC.H>

void UART_Init(void);

void main(void)
{
    UART_Init();         // 调用串口初始化,配置 9600, 8N1

    while(1)             // 无限循环,不断执行回显功能
    {
      // 1. 等待接收一个字节
      while(!RI);      // RI 是接收中断标志位,硬件在接收到一帧完整数据后自动置 1
                         // 这里用查询方式:只要 RI 为 0,就在这里死等,直到接收完成

      RI = 0;          // 接收完成后,必须由软件手动清除 RI 标志,
                         // 否则下一次 while(!RI) 会立刻通过,造成重复回显
                                REN = 0;          // 暂时禁止接收
      // 2. 将接收到的字节发送回去(实现回显)
      SBUF = SBUF+2;   // 当读出 SBUF 时,得到的是接收到的数据;
                         // 当写入 SBUF 时,会启动一次串行发送。
                         // 这条语句相当于:
                         //   unsigned char temp = SBUF;// 读接收到的数据
                         //   SBUF = temp;                // 写入发送缓冲器,启动发送
                                REN = 1;          // 重新允许接收
      while(!TI);      // TI 是发送中断标志位,硬件在发送完一帧数据后自动置 1
                         // 等待发送完成,避免在发送过程中又去写 SBUF 造成数据破坏

      TI = 0;          // 发送完成后,软件清除 TI 标志,为下一次发送做准备
    }
}

void UART_Init(void)
{
    SCON = 0x50;      // 方式1,允许接收。注意这里是0x50,TI/RI由硬件操作,通常初始0即可
    // 或直接 SCON = 0x51; 也完全没问题,手动物清了TI和RI

    TMOD &= 0x0F;   // 修改的是 高 4 位(定时器 1),而 低 4 位(定时器 0)完全没有改变。
        //TMOD的高四位是关于TImer1,低四位是Timer0.
    TMOD |= 0x20;   // 设定定时器1为方式2 (8位自动重装)
    TH1 = 0xFD;       // 波特率9600 (晶振11.0592MHz)
    TL1 = 0xFD;
    ET1 = 0;          // 禁止定时器1中断,用作波特率发生器时通常不产生中断
    TR1 = 1;          // 启动定时器1

    // 如果用到中断接收,再开总中断和串口中断
    ES = 0;         // 允许串口中断
    EA = 0;         // CPU开总中断
}


angmall 发表于 2026-5-3 14:29:16

你这个现象本质上不是“串口硬件把 +2 变成了 +1”,而是 你在读写同一个 SBUF 时发生了写回/读回混淆,再加上表达式里对 SBUF 的访问顺序不可靠,导致结果看起来像“总是少 1”。8051 的 SBUF 不是普通 RAM,它是两个物理上不同的寄存器:写 SBUF 是发射缓冲,读 SBUF 是接收缓冲;SBUF 不能被当成一个可读可写且值一致的变量来用 。

正确写法
建议改成下面这种方式:

#include <STC89C5xRC.H>

void UART_Init(void)
{
    SCON = 0x50;      // 8位UART, REN=1
    TMOD &= 0x0F;
    TMOD |= 0x20;   // T1方式2
    TH1 = 0xFD;       // 11.0592MHz, 9600bps
    TL1 = 0xFD;
    TR1 = 1;
    ES = 0;
    EA = 0;
}

void main(void)
{
    unsigned char dat;

    UART_Init();

    while(1)
    {
      while(!RI);
      dat = SBUF;   // 先把接收到的数据存到普通变量
      RI = 0;

      dat += 2;       // 再做运算
      while(!TI);   // 如果你要等发送完成,先确保初始TI状态正确
      TI = 0;
      SBUF = dat;   // 最后发送
    }
}


yzhkpli 发表于 2026-5-3 15:03:38

angmall 发表于 2026-5-3 14:29
你这个现象本质上不是“串口硬件把 +2 变成了 +1”,而是 你在读写同一个 SBUF 时发生了写回/读回混淆,再 ...


我现在用的是没有dat+2,而是原样输出,也没有输出。根本没报告接收。

#include <STC89C5xRC.H>

void UART_Init(void);

void main(void)
{
                unsigned char dat;
    UART_Init();         // 调用串口初始化,配置 9600, 8N1

    while(1)             // 无限循环,不断执行回显功能
    {
      // 1. 等待接收一个字节
      while(!RI);
                                dat=SBUF;

      RI = 0;//dat+=2;
      while(!TI);
      TI = 0;
                        SBUF=dat;
                }
}

void UART_Init(void)
{
    SCON = 0x50;      // 方式1,允许接收。注意这里是0x50,TI/RI由硬件操作,通常初始0即可
    // 或直接 SCON = 0x51; 也完全没问题,手动物清了TI和RI

    TMOD &= 0x0F;   // 修改的是 高 4 位(定时器 1),而 低 4 位(定时器 0)完全没有改变。
        //TMOD的高四位是关于TImer1,低四位是Timer0.
    TMOD |= 0x20;   // 设定定时器1为方式2 (8位自动重装)
    TH1 = 0xFD;       // 波特率9600 (晶振11.0592MHz)
    TL1 = 0xFD;
    ET1 = 0;          // 禁止定时器1中断,用作波特率发生器时通常不产生中断
    TR1 = 1;          // 启动定时器1

    // 如果用到中断接收,再开总中断和串口中断
    ES = 0;         // 允许串口中断
    EA = 0;         // CPU开总中断
}
// 串口中断服务函数(空实现,什么都不做)
void UART_ISR(void) interrupt 4
{
    // 空函数体
    // 这里可以后续添加接收/发送处理
}


神农鼎 发表于 2026-5-3 18:10:01


https://www.stcaimcu.com/data/download/Datasheet/STC89C52RC-RD+.pdf

深圳国芯人工智能有限公司-工具软件







#include "STC89C5xRC.H"                                //包含STC89的头文件

#include "intrins.h"                        //使用_nop_()函数所必须要包含的头文件,
                                                                //否则延时函数中调用的_nop_()函数没有被头文件引用过来,
                                                                //会导致编译器找不到这个而函数而报错。


unsigned char int0_flag = 0;        //定义1个位变量,INT0事件位变量标志,记录INT0已产生中断
// 供主循环查询INT0是否已产生中断,在主循环中处理INT0的中断事件任务,不堵塞其他中断
unsigned char int1_flag = 0;        //定义1个位变量,INT1事件位变量标志,记录INT1已产生中断
// 供主循环查询INT1是否已产生中断,在主循环中处理INT1的中断事件任务,不堵塞其他中断
unsigned char int2_flag = 0;        //定义1个位变量,INT2事件位变量标志,记录INT2已产生中断
// 供主循环查询INT2是否已产生中断,在主循环中处理INT2的中断事件任务,不堵塞其他中断
unsigned char int3_flag = 0;        //定义1个位变量,INT3事件位变量标志,记录INT3已产生中断
// 供主循环查询INT3是否已产生中断,在主循环中处理INT3的中断事件任务,不堵塞其他中断

unsigned char t0_flag = 0;                //定义1个位变量,T0事件位变量标志,记录定时器0已产生中断
// 供主循环查询定时器0是否已产生中断,在主循环中处理定时器0的中断事件任务,不堵塞其他中断
unsigned char t1_flag = 0;                //定义1个位变量,T1事件位变量标志,记录定时器1已产生中断
// 供主循环查询定时器1是否已产生中断,在主循环中处理定时器1的中断事件任务,不堵塞其他中断

unsigned char uart_txflag = 0;        //定义1个位变量,UART事件位变量标志,记录UART已产生发送中断
// 供主循环查询UART是否已产生发送中断,在主循环中处理UART的中断事件任务,不堵塞其他中断
unsigned char uart_rxflag = 0;        //定义1个位变量,UART事件位变量标志,记录UART已产生接收中断
// 供主循环查询UART是否已产生接收中断,在主循环中处理UART的中断事件任务,不堵塞其他中断

void Timer0_Init(void)                        //定时器0初始化,10毫秒@22.1184MHz
{
        TMOD &= 0xF0;                                //设置定时器模式
        TL0 = 0x00;                                        //设置定时初始值
        TH0 = 0xB8;                                        //设置定时初始值
        TF0 = 0;                                        //清除TF0标志
        TR0 = 1;                                        //定时器0开始计时
        ET0 = 1;                                        //使能定时器0中断
}

void Timer1_Init(void)                        //定时器1初始化,20毫秒@22.1184MHz
{
        TMOD &= 0x0F;                                //设置定时器模式
        TL1 = 0x00;                                        //设置定时初始值
        TH1 = 0x70;                                        //设置定时初始值
        TF1 = 0;                                        //清除TF1标志
        TR1 = 1;                                        //定时器1开始计时
        ET1 = 1;                                        //使能定时器1中断
}

void Uart_Init(void)                        //串口初始化,115200bps@22.1184MHz
{
        SCON = 0x50;                                //8位数据,可变波特率
        T2CON &= ~0x03;                                //设置定时器2工作模式为16位自动重载模式
        T2CON |= 0x30;                                //设置定时器2作为串口的发送和接收时钟
        RCAP2L = TL2 = 0xFA;                 //设置定时初始值
        RCAP2H = TH2 = 0xFF;                 //设置定时初始值
        TR2 = 1;                                        //定时器2开始计时
        ES = 1;                                                //使能串口中断
}

void main (void)
{
        int0_flag = 0;                          //初始化用户标志位
        int1_flag = 0;                          //初始化用户标志位
        int2_flag = 0;                          //初始化用户标志位
        int3_flag = 0;                          //初始化用户标志位
   
        t0_flag = 0;                          //初始化用户标志位
        t1_flag = 0;                          //初始化用户标志位
   
        uart_txflag = 0;                        //初始化用户标志位
        uart_rxflag = 0;                        //初始化用户标志位

//        IT0 = 0;                                         //使能 INT0 低电平中断
        IT0 = 1;                                         //使能 INT0 下降沿中断
        EX0 = 1;                                         //使能 INT0 中断
        IE0 = 0;                                        //清INT0中断标志

//        IT1 = 0;                                         //使能 INT1 低电平中断
        IT1 = 1;                                         //使能 INT1 下降沿中断
        EX1 = 1;                                         //使能 INT1 中断
        IE1 = 0;                                        //清INT1中断标志

//        IT2 = 0;                                         //使能 INT2 低电平中断
        IT2 = 1;                                         //使能 INT2 下降沿中断
        EX2 = 1;                                         //使能 INT2 中断
        IE2 = 0;                                        //清INT2中断标志

//        IT3 = 0;                                         //使能 INT3 低电平中断
        IT3 = 1;                                         //使能 INT3 下降沿中断
        EX3 = 1;                                         //使能 INT3 中断
        IE3 = 0;                                        //清INT3中断标志
       
        Timer0_Init();                                //调用定时器0初始化函数
        Timer1_Init();                                //调用定时器1初始化函数

        Uart_Init();                                //调用UART初始化函数

        EA = 1;                                                //总中断允许位打开
        while(1)                                       //主循环中查询需要处理的各种事件
        {
                /*本演示程序中,主循环查询各中断有无需要继续处理的事件的次序,
                依次是 INTx/TIMERx/UARTx, 用户可以自己根据实际情况,
                调整查询各中断有无需要继续处理的事件的优先次序*/

                //查询外部中断0事件
                if(int0_flag)                        //主循环中查询,INT0是否已产生中断,是否有需要处理的INT 0事件
                {
                        int0_flag = 0;                //清0,INT0事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询外部中断1事件
                if(int1_flag)                        //主循环中查询,INT1是否已产生中断,是否有需要处理的INT1事件
                {
                        int1_flag = 0;                //清0,INT1事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询外部中断2事件
                if(int2_flag)                        //主循环中查询,INT2是否已产生中断,是否有需要处理的INT2事件
                {
                        int2_flag = 0;                //清0,INT2事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询外部中断3事件
                if(int3_flag)                        //主循环中查询,INT3是否已产生中断,是否有需要处理的INT3事件
                {
                        int3_flag = 0;                //清0,INT3事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询定时器0中断事件
                if(t0_flag)                                //主循环中查询,定时器0是否已产生中断,是否有需要处理的定时器0事件
                {
                        t0_flag = 0;                //清0,T0事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询定时器1中断事件
                if(t1_flag)                                //主循环中查询,定时器1是否已产生中断,是否有需要处理的定时器1事件
                {
                        t1_flag = 0;                //清0,T1事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                //查询串口中断事件
                if(uart_txflag)                //主循环中查询,UART是否已产生发送中断,是否有需要处理的UART发送事件
                {
                        uart_txflag = 0;        //清0,UART发送事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }

                if(uart_rxflag)                //主循环中查询,UART是否已产生接收中断,是否有需要处理的UART接收事件
                {
                        uart_rxflag = 0;        //清0,UART接收事件位变量标志
                        _nop_();                        //用户在此添加需要处理的事件
                        _nop_();
                }
        }
}


void int0_isr(void) interrupt 0
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        int0_flag = 1;                                // int0_flag置1是通知主循环处理部分INT0中断事件不需要特急处理的任务
                                                                //置1,记录INT0已产生中断,供主循环查询判断有无需处理的INT0任务
}

void int1_isr(void) interrupt 2
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        int1_flag = 1;                                // int1_flag置1是通知主循环处理部分INT1中断事件不需要特急处理的任务
}

void int2_isr(void) interrupt 6
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        int2_flag = 1;                                // int2_flag置1是通知主循环处理部分INT2中断事件不需要特急处理的任务
}

void int3_isr(void) interrupt 7
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        int3_flag = 1;                                // int3_flag置1是通知主循环处理部分INT3中断事件不需要特急处理的任务
}

void Timer0_Isr(void) interrupt 1                //定时器0中断服务程序
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        t0_flag = 1;                                // t0_flag置1是通知主循环处理部分T0中断事件不需要特急处理的任务
                                                                //置1,记录定时器0已产生中断,供主循环查询判断有无需处理的定时器0任务
}

void Timer1_Isr(void) interrupt 3
{
        _nop_();                                        //特急处理,中断服务程序中尽量少执行长的任务,防止堵塞其他中断
        //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
        //但时间不要太长,否则会影响其他中断事件的实时响应速度
        t1_flag = 1;                                // t1_flag置1是通知主循环处理部分T1中断事件不需要特急处理的任务
                                                                //置1,记录定时器1已产生中断,供主循环查询判断有无需处理的定时器1任务
}

void Uart_Isr(void) interrupt 4
{
        if (TI)                                                //检测串口发送中断
        {
                TI = 0;                                        //清除串口发送中断请求位
                _nop_();                                //特急处理
                //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
                //但时间不要太长,否则会影响其他中断事件的实时响应速度
                uart_txflag = 1;                // uart_txflag置1是通知主循环处理部分串口发送中断事件不需要特急处理的任务
                                                                //置1,记录UART已产生发送中断,供主循环查询判断有无需处理的UART发送任务
        }
        if (RI)                                                //检测串口接收中断
        {
                RI = 0;                                        //清除串口接收中断请求位
                _nop_();                                //特急处理
                //以上程序代表部分需特急处理的中断事件,可在中断服务程序中直接处理
                //但时间不要太长,否则会影响其他中断事件的实时响应速度
                uart_rxflag = 1;                // uart_rxflag置1是通知主循环处理部分串口接收中断事件不需要特急处理的任务
                                                                //置1,记录UART已产生接收中断,供主循环查询判断有无需处理的UART接收任务
        }
}


狂热主宰 发表于 2026-5-4 00:58:24

可能程序的读和写操作
对应底层操作各自的寄存器
页: [1]
查看完整版本: 帮忙测试一个串口发送接收程序