找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2582|回复: 24
收起左侧

51单片机串口通信,表现奇怪的TI状态位,真心求教(抱拳了老哥)

[复制链接]
ID:1043747 发表于 2022-9-3 19:35 | 显示全部楼层 |阅读模式
TI的状态的为什么会受到一个没有调用的函数(UARTSendString)的影响呢?真百思不得其解!
直接运行,在main函数中测试TI的状态为1,取消注释UARTSendString函数后,TI的状态为0(但该函数并没有被调用)

知道原因的老哥劳烦告诉下(手动抱拳了)

测试设备信息
开发板:普中A2
单片机:stc89c52rc
晶振:11.0592MHz
IDE:keil 5

下面是精简后可以表现该问题的代码

#include <REGX52.H>

//void UARTSendString(char *p) {  // **********一个没有调用的函数竟然会影响TI位
//        
//        while(*p) { // 字符串以0结尾
//                SBUF = *p++;
//                while (TI != 1);
//                TI = 0;
//        }
//}

void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特率不倍速
    SCON = 0x50;                //8位数据,可变波特率
    TI = 1;                        // 初始化传输发送标志位  // **********该处TI已置为1
    ES = 1;                        // 打开串口中断
    EA = 1;                        // 允许中断

    TMOD &= 0x0F;                //清除定时器1模式位
    TMOD |= 0x20;                //设定定时器1为8位自动重装方式
    TL1 = 0xFD;                //设定定时初值
    TH1 = 0xFD;                //设定定时器重装值
    ET1 = 0;                //禁止定时器1中断
    TR1 = 1;                //启动定时器1


}

void main(void) {

    UARTInit();
    if (TI == 1) P2_1 = 0; // **********直接运行TI的值为1,灯会亮。如果取消注释UARTSendString函数,p2_1(led)会熄灭,即TI的状态为0。
    while (1) {

    }
}
回复

使用道具 举报

ID:59202 发表于 2022-9-3 23:35 | 显示全部楼层
TI是由硬件自动置1,软件清0,你就不该去写1。“取消注释UARTSendString函数,p2_1(led)会熄灭,即TI的状态为0”,估计编译器看你没用到TI就没去管它,用到UARTSendString函数了就把TI=1优化掉了
回复

使用道具 举报

ID:121859 发表于 2022-9-4 08:05 | 显示全部楼层
如果仅仅是这些代码的话,估计应该是程序跑飞了,因为你中断打开了,但是并没有写中断服务程序,所以很可能会飞掉,而注释掉的部分程序在前面,可能就被执行了,造成TI=0了。你可以将注释部分程序挪移到后面试试效果。
回复

使用道具 举报

ID:213173 发表于 2022-9-4 09:02 | 显示全部楼层
void UARTSendString(char *p)在没有注释掉的情况下,虽然没有被你调用,但不代表后台不能运用。当你强制TI = 1;后,只要开了中断,CPU必须响应,跳转到while (TI!=1);TI=0;。后面的这句if(TI == 1) P2_1 = 0;已经没有意义了。TI是由硬件自动置1,软件清0。不是不可以人为置1,是在某些特殊运用方式时才采用。在不了解其内在因果关系的情况下盲目使用当然达不到目的。楼主可以在编辑器里走单步就一目了然了。
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 09:13 | 显示全部楼层
zhxiufan 发表于 2022-9-4 08:05
如果仅仅是这些代码的话,估计应该是程序跑飞了,因为你中断打开了,但是并没有写中断服务程序,所以很可能 ...

嗯嗯,感谢老铁。我又测试了一番,确认程序跑飞了使得UARTSendString函数被执行造成的,并和UARTSendString函数所处的位置无关,跑飞的情况下总会被执行。但当我把该函数替换成如下代码void UARTSendByte(char byte) {
    SBUF = byte;
    while (TI != 1);
    TI = 0;
}


此UARTSendByte函数就不会被执行。那么此时程序跑飞了吗?还是说只是没跑到该函数来?

这又让我有了新的疑问,当打开串口中断且未有处理程序时,是否一定会跑飞?跑到哪里由什么决定?
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 09:32 | 显示全部楼层
wulin 发表于 2022-9-4 09:02
void UARTSendString(char *p)在没有注释掉的情况下,虽然没有被你调用,但不代表后台不能运用。当你强制TI ...

void UARTSendString(char *p)在没有注释掉的情况下,虽然没有被你调用,但不代表后台不能运用。当你强制TI = 1;后,只要开了中断,CPU必须响应,跳转到while (TI!=1);TI=0;。后面的这句if(TI == 1) P2_1 = 0;已经没有意义了。TI是由硬件自动置1,软件清0。不是不可以人为置1,是在某些特殊运用方式时才采用。在不了解其内在因果关系的情况下盲目使用当然达不到目的。楼主可以在编辑器里走单步就一目了然了。


谢谢解惑,但还是有疑问,cpu为什么会跳转到「while (TI!=1);TI=0;」处呢?
当我把UARTSendString函数换成UARTSendByte函数,TI位并不会受到影响
UARTSendByte函数同样具有「while (TI!=1);TI=0;」,为什么cpu又不跳转了呢?


void UARTSendByte(char byte) {
    SBUF = byte;
    while (TI != 1);
    TI = 0;
}



至于为何我需要手动置1,因为我想使用库函数printf来调试输出,但printf输出需要TI位为1才执行


回复

使用道具 举报

ID:213173 发表于 2022-9-4 10:30 | 显示全部楼层
censv 发表于 2022-9-4 09:32
谢谢解惑,但还是有疑问,cpu为什么会跳转到「while (TI!=1);TI=0;」处呢?
当我把UARTSendString函 ...

改函数名导致出错的形式变化并没有改变出错的本质。在编辑器里走单步!走单步!走单步!重要的事情说3遍!
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 10:57 | 显示全部楼层
wulin 发表于 2022-9-4 10:30
改函数名导致出错的形式变化并没有改变出错的本质。在编辑器里走单步!走单步!走单步!重要的事情说3遍 ...

你可能没有仔细看我的回复,修改的并不只是函数名,而且也和函数名无关。
修改的内容包括:函数参数(由指针变成整型),函数体外层while去掉了

关于单步调试,我手头没有仿真器。

还有关于为什么会跳转以及修改后不会,在源代码层面(不涉及汇编)的单步调试真的能看出来?
即便能看到跳转,但为什么会跳转以及为什么会跳转到此处仍难解惑

盼回复!
回复

使用道具 举报

ID:59202 发表于 2022-9-4 15:07 | 显示全部楼层
楼主提出这个问题其实还是很有意思的,但我觉得已经超出了你目前的知识范围,这已经涉及到硬件底层操作和编译器底层编译逻辑了,我们还是先学会爬再去学跑吧。其实我对硬件底层操作和编译器底层编译逻辑也没太多了解,在这只是说说自己的见解吧。单片机在打开全局中断和相应中断后,如果相应的中断标志位置1,单片机检测到后会保护当前现场,既把相关寄存器压入栈中保存,然后将指令地址跳转到中断向量地址,通常中断向量地址处也是一条跳转指令,跳转到真正的中断函数处,这些保护现场和跳转命令都是编译器自动生成的,如果我们人为置位中断标志又没有编写中断函数,编译器编译也能通过,我们可以想象那些跳转指令后面大概率会跟着空指令,系统也大概率会死鸡。其实这些可以做个小实验验证一下也不难

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:213173 发表于 2022-9-4 16:04 | 显示全部楼层
censv 发表于 2022-9-4 10:57
你可能没有仔细看我的回复,修改的并不只是函数名,而且也和函数名无关。
修改的内容包括:函数参数(由 ...

无标题.jpg
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 16:37 | 显示全部楼层

首先感谢你帮我调试截图!图中看TI=0;这句代码一定是执行了。

但为什么会跳转到一个不相关的函数,即程序计数器pc为什么会指向该函数内部,源代码级别的调试难以释疑,也可能是我没看出来

回复

使用道具 举报

ID:1043747 发表于 2022-9-4 16:48 | 显示全部楼层
xxxevery 发表于 2022-9-4 15:07
楼主提出这个问题其实还是很有意思的,但我觉得已经超出了你目前的知识范围,这已经涉及到硬件底层操作和编 ...

多谢回帖,给我提供了新的思路,你说的合理。程序跑飞到另一个函数的的原因,很可能是串口中断服务程序的跳转地址被编译器错误的填写导致,而且keil 5 ide也有很多bug,如果keil在此能给个err或warning就更好了
回复

使用道具 举报

ID:624769 发表于 2022-9-4 18:31 | 显示全部楼层
void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特率不倍速
    SCON = 0x50;                //8位数据,可变波特率
    TI = 1;                        // 初始化传输发送标志位  // **********该处TI已置为1
   ES = 1;                        // 打开串口中断     <==  只要有这句  如果你写了 串口中断函数,那么TI 一定会变0  如果没有写串口 中断函数,那么程序一定跑飞!!!
    EA = 1;                        // 允许中断

    TMOD &= 0x0F;                //清除定时器1模式位
    TMOD |= 0x20;                //设定定时器1为8位自动重装方式
    TL1 = 0xFD;                //设定定时初值
    TH1 = 0xFD;                //设定定时器重装值
    ET1 = 0;                //禁止定时器1中断
    TR1 = 1;                //启动定时器1


}

评分

参与人数 1黑币 +50 收起 理由
admin + 50 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:624769 发表于 2022-9-4 18:52 | 显示全部楼层
censv 发表于 2022-9-4 09:32
谢谢解惑,但还是有疑问,cpu为什么会跳转到「while (TI!=1);TI=0;」处呢?
当我把UARTSendString函 ...

谁说 printf 必须TI 为1 才可以的?

printf 调用的是 putchar
而你只要把 putchar 判断的标志位 从TI改成其他的,比如我们常用的 TIbusy 就完全可以不考虑 TI 状态,何必给自己找麻烦呢?
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 18:56 | 显示全部楼层
188610329 发表于 2022-9-4 18:31
void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特 ...

多谢回复
写了中断处理函数,TI也不一定变0,比如空的处理函数。没写中断函数,跑飞能理解。
但这种处理方式不合理。因为根本就不应该编译通过

你能确定没中断函数一定跑飞吗?
回复

使用道具 举报

16#
无效楼层,该帖已经被删除
ID:624769 发表于 2022-9-4 19:04 | 显示全部楼层
censv 发表于 2022-9-4 18:56
多谢回复
写了中断处理函数,TI也不一定变0,比如空的处理函数。没写中断函数,跑飞能理解。
但这种处 ...

没中断处理函数, 只要你开了中断, 中断请求标志位被置位,程序 100% 跑飞,至于跑飞后,是否能再跑回原程序,这就要看运气了。 我不知道你 Debug 是怎么看的, 当你 ES = 1; EA = 1; 只要你 TI = 1; 走下一步,程序必定会 跳转到: C: 0023  而如果你写了中断函数, C:0023  这里就是一个长跳转(LJMP),到你的中断函数,如果你没有写中断函数, C:0023 这里,就什么都有可能了。这就是 “跑飞”
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 19:13 | 显示全部楼层
188610329 发表于 2022-9-4 19:04
没中断处理函数, 只要你开了中断, 中断请求标志位被置位,程序 100% 跑飞,至于跑飞后,是否能再跑回原 ...

理解了,多谢,没仿真器debug不了

串口中断触发时程序计数器必定跳到 C:0023,这是人为规定的?
回复

使用道具 举报

ID:624769 发表于 2022-9-4 19:26 | 显示全部楼层
censv 发表于 2022-9-4 18:56
多谢回复
写了中断处理函数,TI也不一定变0,比如空的处理函数。没写中断函数,跑飞能理解。
但这种处 ...

你觉得不合理,是因为你的知识储备不够,如果,你对单片机运作原理有足够的了解,你就不会有这种想法了。

你打开  REGX52.H   你会看到: sbit ES         =   IE^4;
换句话说, 对KEIL 来说,你只是给 某个 BIT 位 置1了 而已, 鬼知道你是在开中断?  
知道你是在开中断的,是单片机,不是KEIL。 知道为什么中断函数要用 interrupt 4 来定位么? 那是为了给 C:0023 加一句长跳转。 你知识储备足够的话,你可以直接 _at_ 0x0023 直接给代码,当然如果中断函数足够短的话。而开了 串口中断 要 跳到 0023 去执行,这个也是 单片机自己知道,不是KEIL 知道。举个最简单的例子,T2, 在STC89 系列时是 interrupt 5,  在STC 15 系列之后,是: interrupt 16  这能去控制?? 不出事??

因此,KEIL 拿什么(或者说凭借什么来判断)来控制你编译不通过?
回复

使用道具 举报

ID:624769 发表于 2022-9-4 19:36 | 显示全部楼层
censv 发表于 2022-9-4 19:13
理解了,多谢,没仿真器debug不了

串口中断触发时程序计数器必定跳到 C:0023,这是人为规定的?

这是由单片机公司规定的,比如,你的 STC89 系列:
interrupt.png

他定义在 0023, 其他单片公司,只要 51 核的为了兼容,基本都 定义在 0023 当然,如果愿意 也可以定义在0063,或者 006B 主要看厂家的喜好了。
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 20:09 来自触屏版 | 显示全部楼层
188610329 发表于 2022-9-4 18:52
谁说 printf 必须TI 为1 才可以的?

printf 调用的是 putchar

直接修改lib文件夹中的putchar.c文件就可以吗?还需要重新编译吗?

btw,TIbusy是什么?变量吗?
回复

使用道具 举报

ID:1043747 发表于 2022-9-4 20:45 来自触屏版 | 显示全部楼层
188610329 发表于 2022-9-4 19:26
你觉得不合理,是因为你的知识储备不够,如果,你对单片机运作原理有足够的了解,你就不会有这种想法了。 ...

keil还是知道的,比如在新建工程时,会让选择芯片类型
回复

使用道具 举报

ID:624769 发表于 2022-9-4 21:05 | 显示全部楼层
censv 发表于 2022-9-4 20:45
keil还是知道的,比如在新建工程时,会让选择芯片类型

KEIL 连 你没有用 STC89C5xRC.H  这个头文件 用的是: REGX52.H 都不知道,他能知道啥?
另外,你都知道选择芯片类型了,头文件还在用 REGX52.H,连这点最基本的统一都做不到。KEIL 要真的管那么宽的话(极端严谨的查验策略),估计,你跑马灯的代码一天都编译不出来……

选择芯片型号,只是一个简单框架,不说要在设置里勾选项目,让KEIL知道,去控制,
就STC的芯片型号来讲,很多芯片的参数他还是错的,这样都不影响编译,你觉得这个选择芯片型号的用处到底有多大? 最后,你分析一下芯片型号的设定参数,你觉得里面能有中断控制的记号么? 里面无非就是几个RAM ROM 的大小, 头文件的指定,以及MCU的速度 仅此而已……
回复

使用道具 举报

ID:624769 发表于 2022-9-4 21:46 | 显示全部楼层
censv 发表于 2022-9-4 20:09
直接修改lib文件夹中的putchar.c文件就可以吗?还需要重新编译吗?

btw,TIbusy是什么?变量吗?

自定的标志, 你随便找几个  开串口中断的 范例,或者STC的范例就可以。就会看到 TIbusy, T1busy , Uartbusy 这类标志以及用法了了, while(!TI) 这种方式 淘汰太久了……
回复

使用道具 举报

ID:59202 发表于 2022-9-4 23:37 | 显示全部楼层
看来还是对keil的底层编译逻辑不太了解啊,上个回帖我说在开了串口中断的情况下,手工将TI置1,如果没有写中断函数编译器也会生成现场保护程序并跳到中断向量地址然后很大概率死机,但今天用stc8H8K64U单片机做了个小实验(虽说比stc89c5x系列高级多了,但中断情况应该差不多),结果就是打开全局中断和串口中断,手工设置TI=1,如果写了中断函数则可以进入中断函数并顺利退出,如果没有写中断函数则没什么影响,TI一直保持不变,不会因为一个没有执行的其他函数中对它有操作而变化,也没死机,main主程序顺利执行,看来编译器应该在没有中断函数的情况下,只是把TI置1,并没有生成压栈,跳转等指令,已经很智能了。所以说楼主不用再这个问题上再纠结了,你本身就是一个非常规操作,在一些低版本的编译器中可能会产生一些奇怪的指令(当然也不排除你的程序本身就有问题,毕竟我们也没看到所有程序),不如你升级一下keil版本再试试。

评分

参与人数 1黑币 +50 收起 理由
admin + 50 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:1043747 发表于 2022-9-6 19:59 来自触屏版 | 显示全部楼层
188610329 发表于 2022-9-4 21:46
自定的标志, 你随便找几个  开串口中断的 范例,或者STC的范例就可以。就会看到 TIbusy, T1busy , Uartb ...

多谢,受教了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表