標(biāo)題: 51單片機(jī)串口通信,表現(xiàn)奇怪的TI狀態(tài)位,真心求教(抱拳了老哥) [打印本頁]

作者: censv    時間: 2022-9-3 19:35
標(biāo)題: 51單片機(jī)串口通信,表現(xiàn)奇怪的TI狀態(tài)位,真心求教(抱拳了老哥)
TI的狀態(tài)的為什么會受到一個沒有調(diào)用的函數(shù)(UARTSendString)的影響呢?真百思不得其解!
直接運(yùn)行,在main函數(shù)中測試TI的狀態(tài)為1,取消注釋UARTSendString函數(shù)后,TI的狀態(tài)為0(但該函數(shù)并沒有被調(diào)用)

知道原因的老哥勞煩告訴下(手動抱拳了)

測試設(shè)備信息
開發(fā)板:普中A2
單片機(jī):stc89c52rc
晶振:11.0592MHz
IDE:keil 5

下面是精簡后可以表現(xiàn)該問題的代碼

#include <REGX52.H>

//void UARTSendString(char *p) {  // **********一個沒有調(diào)用的函數(shù)竟然會影響TI位
//        
//        while(*p) { // 字符串以0結(jié)尾
//                SBUF = *p++;
//                while (TI != 1);
//                TI = 0;
//        }
//}

void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特率不倍速
    SCON = 0x50;                //8位數(shù)據(jù),可變波特率
    TI = 1;                        // 初始化傳輸發(fā)送標(biāo)志位  // **********該處TI已置為1
    ES = 1;                        // 打開串口中斷
    EA = 1;                        // 允許中斷

    TMOD &= 0x0F;                //清除定時器1模式位
    TMOD |= 0x20;                //設(shè)定定時器1為8位自動重裝方式
    TL1 = 0xFD;                //設(shè)定定時初值
    TH1 = 0xFD;                //設(shè)定定時器重裝值
    ET1 = 0;                //禁止定時器1中斷
    TR1 = 1;                //啟動定時器1


}

void main(void) {

    UARTInit();
    if (TI == 1) P2_1 = 0; // **********直接運(yùn)行TI的值為1,燈會亮。如果取消注釋UARTSendString函數(shù),p2_1(led)會熄滅,即TI的狀態(tài)為0。
    while (1) {

    }
}

作者: xxxevery    時間: 2022-9-3 23:35
TI是由硬件自動置1,軟件清0,你就不該去寫1。“取消注釋UARTSendString函數(shù),p2_1(led)會熄滅,即TI的狀態(tài)為0”,估計(jì)編譯器看你沒用到TI就沒去管它,用到UARTSendString函數(shù)了就把TI=1優(yōu)化掉了
作者: zhxiufan    時間: 2022-9-4 08:05
如果僅僅是這些代碼的話,估計(jì)應(yīng)該是程序跑飛了,因?yàn)槟阒袛啻蜷_了,但是并沒有寫中斷服務(wù)程序,所以很可能會飛掉,而注釋掉的部分程序在前面,可能就被執(zhí)行了,造成TI=0了。你可以將注釋部分程序挪移到后面試試效果。
作者: wulin    時間: 2022-9-4 09:02
void UARTSendString(char *p)在沒有注釋掉的情況下,雖然沒有被你調(diào)用,但不代表后臺不能運(yùn)用。當(dāng)你強(qiáng)制TI = 1;后,只要開了中斷,CPU必須響應(yīng),跳轉(zhuǎn)到while (TI!=1);TI=0;。后面的這句if(TI == 1) P2_1 = 0;已經(jīng)沒有意義了。TI是由硬件自動置1,軟件清0。不是不可以人為置1,是在某些特殊運(yùn)用方式時才采用。在不了解其內(nèi)在因果關(guān)系的情況下盲目使用當(dāng)然達(dá)不到目的。樓主可以在編輯器里走單步就一目了然了。
作者: censv    時間: 2022-9-4 09:13
zhxiufan 發(fā)表于 2022-9-4 08:05
如果僅僅是這些代碼的話,估計(jì)應(yīng)該是程序跑飛了,因?yàn)槟阒袛啻蜷_了,但是并沒有寫中斷服務(wù)程序,所以很可能 ...

嗯嗯,感謝老鐵。我又測試了一番,確認(rèn)程序跑飛了使得UARTSendString函數(shù)被執(zhí)行造成的,并和UARTSendString函數(shù)所處的位置無關(guān),跑飛的情況下總會被執(zhí)行。但當(dāng)我把該函數(shù)替換成如下代碼void UARTSendByte(char byte) {
    SBUF = byte;
    while (TI != 1);
    TI = 0;
}


此UARTSendByte函數(shù)就不會被執(zhí)行。那么此時程序跑飛了嗎?還是說只是沒跑到該函數(shù)來?

這又讓我有了新的疑問,當(dāng)打開串口中斷且未有處理程序時,是否一定會跑飛?跑到哪里由什么決定?

作者: censv    時間: 2022-9-4 09:32
wulin 發(fā)表于 2022-9-4 09:02
void UARTSendString(char *p)在沒有注釋掉的情況下,雖然沒有被你調(diào)用,但不代表后臺不能運(yùn)用。當(dāng)你強(qiáng)制TI ...

void UARTSendString(char *p)在沒有注釋掉的情況下,雖然沒有被你調(diào)用,但不代表后臺不能運(yùn)用。當(dāng)你強(qiáng)制TI = 1;后,只要開了中斷,CPU必須響應(yīng),跳轉(zhuǎn)到while (TI!=1);TI=0;。后面的這句if(TI == 1) P2_1 = 0;已經(jīng)沒有意義了。TI是由硬件自動置1,軟件清0。不是不可以人為置1,是在某些特殊運(yùn)用方式時才采用。在不了解其內(nèi)在因果關(guān)系的情況下盲目使用當(dāng)然達(dá)不到目的。樓主可以在編輯器里走單步就一目了然了。


謝謝解惑,但還是有疑問,cpu為什么會跳轉(zhuǎn)到「while (TI!=1);TI=0;」處呢?
當(dāng)我把UARTSendString函數(shù)換成UARTSendByte函數(shù),TI位并不會受到影響
UARTSendByte函數(shù)同樣具有「while (TI!=1);TI=0;」,為什么cpu又不跳轉(zhuǎn)了呢?


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



至于為何我需要手動置1,因?yàn)槲蚁胧褂脦旌瘮?shù)printf來調(diào)試輸出,但printf輸出需要TI位為1才執(zhí)行



作者: wulin    時間: 2022-9-4 10:30
censv 發(fā)表于 2022-9-4 09:32
謝謝解惑,但還是有疑問,cpu為什么會跳轉(zhuǎn)到「while (TI!=1);TI=0;」處呢?
當(dāng)我把UARTSendString函 ...

改函數(shù)名導(dǎo)致出錯的形式變化并沒有改變出錯的本質(zhì)。在編輯器里走單步!走單步!走單步!重要的事情說3遍!
作者: censv    時間: 2022-9-4 10:57
wulin 發(fā)表于 2022-9-4 10:30
改函數(shù)名導(dǎo)致出錯的形式變化并沒有改變出錯的本質(zhì)。在編輯器里走單步!走單步!走單步!重要的事情說3遍 ...

你可能沒有仔細(xì)看我的回復(fù),修改的并不只是函數(shù)名,而且也和函數(shù)名無關(guān)。
修改的內(nèi)容包括:函數(shù)參數(shù)(由指針變成整型),函數(shù)體外層while去掉了

關(guān)于單步調(diào)試,我手頭沒有仿真器。

還有關(guān)于為什么會跳轉(zhuǎn)以及修改后不會,在源代碼層面(不涉及匯編)的單步調(diào)試真的能看出來?
即便能看到跳轉(zhuǎn),但為什么會跳轉(zhuǎn)以及為什么會跳轉(zhuǎn)到此處仍難解惑

盼回復(fù)!
作者: xxxevery    時間: 2022-9-4 15:07
樓主提出這個問題其實(shí)還是很有意思的,但我覺得已經(jīng)超出了你目前的知識范圍,這已經(jīng)涉及到硬件底層操作和編譯器底層編譯邏輯了,我們還是先學(xué)會爬再去學(xué)跑吧。其實(shí)我對硬件底層操作和編譯器底層編譯邏輯也沒太多了解,在這只是說說自己的見解吧。單片機(jī)在打開全局中斷和相應(yīng)中斷后,如果相應(yīng)的中斷標(biāo)志位置1,單片機(jī)檢測到后會保護(hù)當(dāng)前現(xiàn)場,既把相關(guān)寄存器壓入棧中保存,然后將指令地址跳轉(zhuǎn)到中斷向量地址,通常中斷向量地址處也是一條跳轉(zhuǎn)指令,跳轉(zhuǎn)到真正的中斷函數(shù)處,這些保護(hù)現(xiàn)場和跳轉(zhuǎn)命令都是編譯器自動生成的,如果我們?nèi)藶橹梦恢袛鄻?biāo)志又沒有編寫中斷函數(shù),編譯器編譯也能通過,我們可以想象那些跳轉(zhuǎn)指令后面大概率會跟著空指令,系統(tǒng)也大概率會死雞。其實(shí)這些可以做個小實(shí)驗(yàn)驗(yàn)證一下也不難
作者: wulin    時間: 2022-9-4 16:04
censv 發(fā)表于 2022-9-4 10:57
你可能沒有仔細(xì)看我的回復(fù),修改的并不只是函數(shù)名,而且也和函數(shù)名無關(guān)。
修改的內(nèi)容包括:函數(shù)參數(shù)(由 ...



作者: censv    時間: 2022-9-4 16:37
wulin 發(fā)表于 2022-9-4 16:04

首先感謝你幫我調(diào)試截圖!圖中看TI=0;這句代碼一定是執(zhí)行了。

但為什么會跳轉(zhuǎn)到一個不相關(guān)的函數(shù),即程序計(jì)數(shù)器pc為什么會指向該函數(shù)內(nèi)部,源代碼級別的調(diào)試難以釋疑,也可能是我沒看出來


作者: censv    時間: 2022-9-4 16:48
xxxevery 發(fā)表于 2022-9-4 15:07
樓主提出這個問題其實(shí)還是很有意思的,但我覺得已經(jīng)超出了你目前的知識范圍,這已經(jīng)涉及到硬件底層操作和編 ...

多謝回帖,給我提供了新的思路,你說的合理。程序跑飛到另一個函數(shù)的的原因,很可能是串口中斷服務(wù)程序的跳轉(zhuǎn)地址被編譯器錯誤的填寫導(dǎo)致,而且keil 5 ide也有很多bug,如果keil在此能給個err或warning就更好了

作者: 188610329    時間: 2022-9-4 18:31
void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特率不倍速
    SCON = 0x50;                //8位數(shù)據(jù),可變波特率
    TI = 1;                        // 初始化傳輸發(fā)送標(biāo)志位  // **********該處TI已置為1
   ES = 1;                        // 打開串口中斷     <==  只要有這句  如果你寫了 串口中斷函數(shù),那么TI 一定會變0  如果沒有寫串口 中斷函數(shù),那么程序一定跑飛。!
    EA = 1;                        // 允許中斷

    TMOD &= 0x0F;                //清除定時器1模式位
    TMOD |= 0x20;                //設(shè)定定時器1為8位自動重裝方式
    TL1 = 0xFD;                //設(shè)定定時初值
    TH1 = 0xFD;                //設(shè)定定時器重裝值
    ET1 = 0;                //禁止定時器1中斷
    TR1 = 1;                //啟動定時器1


}


作者: 188610329    時間: 2022-9-4 18:52
censv 發(fā)表于 2022-9-4 09:32
謝謝解惑,但還是有疑問,cpu為什么會跳轉(zhuǎn)到「while (TI!=1);TI=0;」處呢?
當(dāng)我把UARTSendString函 ...

誰說 printf 必須TI 為1 才可以的?

printf 調(diào)用的是 putchar
而你只要把 putchar 判斷的標(biāo)志位 從TI改成其他的,比如我們常用的 TIbusy 就完全可以不考慮 TI 狀態(tài),何必給自己找麻煩呢?
作者: censv    時間: 2022-9-4 18:56
188610329 發(fā)表于 2022-9-4 18:31
void UARTInit(void)                //9600bps@11.0592MHz
{
    PCON &= 0x7F;                //波特 ...

多謝回復(fù)
寫了中斷處理函數(shù),TI也不一定變0,比如空的處理函數(shù)。沒寫中斷函數(shù),跑飛能理解。
但這種處理方式不合理。因?yàn)楦揪筒粦?yīng)該編譯通過

你能確定沒中斷函數(shù)一定跑飛嗎?
作者: 188610329    時間: 2022-9-4 19:04
censv 發(fā)表于 2022-9-4 18:56
多謝回復(fù)
寫了中斷處理函數(shù),TI也不一定變0,比如空的處理函數(shù)。沒寫中斷函數(shù),跑飛能理解。
但這種處 ...

沒中斷處理函數(shù), 只要你開了中斷, 中斷請求標(biāo)志位被置位,程序 100% 跑飛,至于跑飛后,是否能再跑回原程序,這就要看運(yùn)氣了。 我不知道你 Debug 是怎么看的, 當(dāng)你 ES = 1; EA = 1; 只要你 TI = 1; 走下一步,程序必定會 跳轉(zhuǎn)到: C: 0023  而如果你寫了中斷函數(shù), C:0023  這里就是一個長跳轉(zhuǎn)(LJMP),到你的中斷函數(shù),如果你沒有寫中斷函數(shù), C:0023 這里,就什么都有可能了。這就是 “跑飛”
作者: censv    時間: 2022-9-4 19:13
188610329 發(fā)表于 2022-9-4 19:04
沒中斷處理函數(shù), 只要你開了中斷, 中斷請求標(biāo)志位被置位,程序 100% 跑飛,至于跑飛后,是否能再跑回原 ...

理解了,多謝,沒仿真器debug不了

串口中斷觸發(fā)時程序計(jì)數(shù)器必定跳到 C:0023,這是人為規(guī)定的?
作者: 188610329    時間: 2022-9-4 19:26
censv 發(fā)表于 2022-9-4 18:56
多謝回復(fù)
寫了中斷處理函數(shù),TI也不一定變0,比如空的處理函數(shù)。沒寫中斷函數(shù),跑飛能理解。
但這種處 ...

你覺得不合理,是因?yàn)槟愕闹R儲備不夠,如果,你對單片機(jī)運(yùn)作原理有足夠的了解,你就不會有這種想法了。

你打開  REGX52.H   你會看到: sbit ES         =   IE^4;
換句話說, 對KEIL 來說,你只是給 某個 BIT 位 置1了 而已, 鬼知道你是在開中斷?  
知道你是在開中斷的,是單片機(jī),不是KEIL。 知道為什么中斷函數(shù)要用 interrupt 4 來定位么? 那是為了給 C:0023 加一句長跳轉(zhuǎn)。 你知識儲備足夠的話,你可以直接 _at_ 0x0023 直接給代碼,當(dāng)然如果中斷函數(shù)足夠短的話。而開了 串口中斷 要 跳到 0023 去執(zhí)行,這個也是 單片機(jī)自己知道,不是KEIL 知道。舉個最簡單的例子,T2, 在STC89 系列時是 interrupt 5,  在STC 15 系列之后,是: interrupt 16  這能去控制?? 不出事??

因此,KEIL 拿什么(或者說憑借什么來判斷)來控制你編譯不通過?
作者: 188610329    時間: 2022-9-4 19:36
censv 發(fā)表于 2022-9-4 19:13
理解了,多謝,沒仿真器debug不了

串口中斷觸發(fā)時程序計(jì)數(shù)器必定跳到 C:0023,這是人為規(guī)定的?

這是由單片機(jī)公司規(guī)定的,比如,你的 STC89 系列:


他定義在 0023, 其他單片公司,只要 51 核的為了兼容,基本都 定義在 0023 當(dāng)然,如果愿意 也可以定義在0063,或者 006B 主要看廠家的喜好了。

作者: censv    時間: 2022-9-4 20:09
188610329 發(fā)表于 2022-9-4 18:52
誰說 printf 必須TI 為1 才可以的?

printf 調(diào)用的是 putchar

直接修改lib文件夾中的putchar.c文件就可以嗎?還需要重新編譯嗎?

btw,TIbusy是什么?變量嗎?
作者: censv    時間: 2022-9-4 20:45
188610329 發(fā)表于 2022-9-4 19:26
你覺得不合理,是因?yàn)槟愕闹R儲備不夠,如果,你對單片機(jī)運(yùn)作原理有足夠的了解,你就不會有這種想法了。 ...

keil還是知道的,比如在新建工程時,會讓選擇芯片類型
作者: 188610329    時間: 2022-9-4 21:05
censv 發(fā)表于 2022-9-4 20:45
keil還是知道的,比如在新建工程時,會讓選擇芯片類型

KEIL 連 你沒有用 STC89C5xRC.H  這個頭文件 用的是: REGX52.H 都不知道,他能知道啥?
另外,你都知道選擇芯片類型了,頭文件還在用 REGX52.H,連這點(diǎn)最基本的統(tǒng)一都做不到。KEIL 要真的管那么寬的話(極端嚴(yán)謹(jǐn)?shù)牟轵?yàn)策略),估計(jì),你跑馬燈的代碼一天都編譯不出來……

選擇芯片型號,只是一個簡單框架,不說要在設(shè)置里勾選項(xiàng)目,讓KEIL知道,去控制,
就STC的芯片型號來講,很多芯片的參數(shù)他還是錯的,這樣都不影響編譯,你覺得這個選擇芯片型號的用處到底有多大? 最后,你分析一下芯片型號的設(shè)定參數(shù),你覺得里面能有中斷控制的記號么? 里面無非就是幾個RAM ROM 的大小, 頭文件的指定,以及MCU的速度 僅此而已……
作者: 188610329    時間: 2022-9-4 21:46
censv 發(fā)表于 2022-9-4 20:09
直接修改lib文件夾中的putchar.c文件就可以嗎?還需要重新編譯嗎?

btw,TIbusy是什么?變量嗎?

自定的標(biāo)志, 你隨便找?guī)讉  開串口中斷的 范例,或者STC的范例就可以。就會看到 TIbusy, T1busy , Uartbusy 這類標(biāo)志以及用法了了, while(!TI) 這種方式 淘汰太久了……
作者: xxxevery    時間: 2022-9-4 23:37
看來還是對keil的底層編譯邏輯不太了解啊,上個回帖我說在開了串口中斷的情況下,手工將TI置1,如果沒有寫中斷函數(shù)編譯器也會生成現(xiàn)場保護(hù)程序并跳到中斷向量地址然后很大概率死機(jī),但今天用stc8H8K64U單片機(jī)做了個小實(shí)驗(yàn)(雖說比stc89c5x系列高級多了,但中斷情況應(yīng)該差不多),結(jié)果就是打開全局中斷和串口中斷,手工設(shè)置TI=1,如果寫了中斷函數(shù)則可以進(jìn)入中斷函數(shù)并順利退出,如果沒有寫中斷函數(shù)則沒什么影響,TI一直保持不變,不會因?yàn)橐粋沒有執(zhí)行的其他函數(shù)中對它有操作而變化,也沒死機(jī),main主程序順利執(zhí)行,看來編譯器應(yīng)該在沒有中斷函數(shù)的情況下,只是把TI置1,并沒有生成壓棧,跳轉(zhuǎn)等指令,已經(jīng)很智能了。所以說樓主不用再這個問題上再糾結(jié)了,你本身就是一個非常規(guī)操作,在一些低版本的編譯器中可能會產(chǎn)生一些奇怪的指令(當(dāng)然也不排除你的程序本身就有問題,畢竟我們也沒看到所有程序),不如你升級一下keil版本再試試。
作者: censv    時間: 2022-9-6 19:59
188610329 發(fā)表于 2022-9-4 21:46
自定的標(biāo)志, 你隨便找?guī)讉  開串口中斷的 范例,或者STC的范例就可以。就會看到 TIbusy, T1busy , Uartb ...

多謝,受教了




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1