SN8F5701 – 8051 UART 功能介紹

這篇雖說是簡易雷達調光的最終章,不過事實上 UART 和 ADC 並沒有應用在這次的專案之上,所以文章的主題是針對這顆埋控做功能上的測試和介紹,我們就直接進入主題吧!

UART

剛開始玩 Arduino 的時候,只知道 Serial.println() 可以在 console 中印出某些字串,變數的值等等,沒有去探究怎麼實現的,玩了一陣子後,瞭解到 USB 轉 TTL 的事實,TXD,RXD 背後的UART protocol 是怎麼運作的,即便知道背後原因,但因為 Arduino 把整個東西做掉因此當時我沒機會了解得非常深入,應該說沒機會真的去親身實作背後原理。

因為這次專案的機會,雖說的確不會用到 UART 的部分 (松翰的 SN-LINK-V3 處理了燒錄的部分),不過 Winson 希望我把所有功能都玩一遍,因此頭一次自己寫 UART 的機會就來了。

看了 datasheet,說真的,松翰這份文件內 sample code 的部分真的寫得比較不完整,而且有些地方是有問題的,另外就是一些細節的部分沒有交代清楚,因而必須自己做實驗來驗證。

不知道是不是自己功力還不夠的問題,我在測試 SN8F5701 這顆埋控有關於 UART 部分的時候,發現了它竟然不是全雙工 (Full Duplex)!(但文件明明清楚表示 UART 是全雙工的沒錯),接下來就來分享我做實驗的過程。

實驗是這樣,透過像是 coolterm 這樣的串口軟體工具 (就是 Arduino serial console 的概念),我希望在鍵盤上輸入文字或是數字 (RXD),SN8F5701 能夠吐回來到 console 中顯示 (TXD)。

而講到 UART 不論哪顆埋控一定都會有一個或多個 buffer 來接目標物 (不論是要送出去還是丟進來都是),SN8F5701 當然也不例外,S0BUF 就是那個 buffer,表示他要用來接我鍵盤輸入的文字或數字,也要用來接要丟出去的東西,那…那豈不是會打架嗎?此時心想,既然文件都肯定是全雙工了,或許有什麼東西是沒寫出來但是能夠順利 work 的。

首先,我先做了個基本的輸送實驗,以下是部分的程式碼 (就不寫數字部分的了,文字會數字一定會):

void UART_Init(void);
void txsenddata(short);
char rxreceivedata(void);
uint8_t enableWrite=0;
char ch;
char chstring[]="Please type something to test: ";
void main(){
//initial function setting..
for (int i=0; i<sizeof(chstring); i++) txsenddata(chstring[i]);
while(1){
ch=rxreceivedata();
if (enableWrite){
txsenddata(ch);
enableWrite=0;
}
}
}
void UART_Init(){
//register setting
//include tx, rx port, uart mode (use typical one), baudrate, interrupt...
}
void txsenddata(short da){
S0BUF=da;
while (TI0==0){}
TI0=0;
}
char rxreceivedata(){
while(RI0==0){WDTR = 0x5A;}
RI0=0;
enableWrite=1;
return(S0BUF);
}
view raw uart_test.c hosted with ❤ by GitHub

稍微解釋一下上方的程式碼,uart_init() 作為 uart 功能的初始設定,tx, rx 分別寫成兩個一般的 function,chstring[] 這個字元陣列是在開機 (reset) 後,會先顯示在 coolterm console 上的字串。

我覺得這裡最值得分享的還是 while (TI0==0) {},這個寫法比較不直覺,不過卻很必要。TI0 (和 RI0) 是個 flag,如果 S0BUF 滿了,也就是說東西可以丟出去 (或接進來),TI0 (或是 RI0) 會變成 1,必須手動清除 flag 將其變回 0 才能再收 (或丟) 下一批東西,而之所以要這樣寫,是因為 MCU 的速度太快,還來不及清除 flag 就已經跑過 TI0=0 這個指令了。這樣講或許有點模糊,以上方的程式碼舉例,如果沒有 while (TI0==0) {} 這行,因為 MCU (fcpu) 太快,可能再 S0BUF = da 後,在 TI0 還沒變成 1 之前,就已經先跑完了 TI0=0 這行程式碼,導致 TI0 清不掉,無法繼續有效運作。

雖然這個寫法是對的但卻很不合理,一方面,我花了所有 main 的資源只做了把我的輸入印在 console 上,做實驗可以但實際與其他功能配合運用時就是個災難。另一方面,大家應該很疑惑在 rxreceivedata() 裡面,WDTR = 0x5A 的用意,事實上不加這行又在有開 watch dog 的情形下,只要使用者沒有在 watch dog 所設定的時間內真的輸入東西 (相信我,一般人是不可能跟上的),系統就會直接判定當機而進行 reset,而為了避免這種窘況,WDTR = 0x5A 就是用來清 watch dog 的指令,好讓 rxreceivedata() 可以順利等下去,不過說真的沒有人會這樣寫,沒東西來就卡在那個 while 裡面一直等,什麼都不用做了…。

另外,我用了個 flag (enableWrite) 讓 txsenddata() 在沒有東西進來的情況不會送東西出去。

既然上述兩個 function 都放在 main 的情形這麼不優雅,那就是時候派 ISR 上場了。UART 當然也有 interrupt 的機制,據 Winson 說,UART 通常會把 rx 放在 ISR,tx 放在 main,我覺得很合理,試想如果把 rx 放在 main,tx 放在 ISR,那上述的問題就沒獲得解法,依舊在等。寫法就變以下 (程式碼片段):

void txsenddata(short);
char ch;
char chstring[]="Please type something to test: ";
void main(){
for(int i=0; i<sizeof(chstring); i++) txsenddata(chstring[i]);
while(1){
if(enableWrite){
txsenddata(ch);
enableWrite=0;
}
}
}
void txsenddata(short da){
S0BUF=da;
while (TI0==0){}
TI0=0;
}
void UART_ISR(void) interrupt ISRUart {
if (!enableWrite){//let S0BUF just do one job at the same time
RI0=0;
ch = S0BUF;
enableWrite=1;
}
}
view raw uart_test_2.c hosted with ❤ by GitHub

rx 在 ISR,文件明確表示啟動的機制是 RI0 變 1 時便 trigger ISR,意思是說我不用用 while 再繼續等,進來的時候把 RI0 flag 清掉就好,也因為全雙工,不用太在意收丟交錯的情形,有東西進來把 enableWrite flag 打開就行。

不過可以看到上面我在 ISR 裡面多加了一個 if statement,這是因為我發現如果不加,竟然 tx 傳到螢幕上的字元陣列會漏字!於是我開始懷疑不是全雙工,寫了那個 if 就是當他是半雙工的意思 (收的時候不能丟,丟的時候不能收)。果然這樣寫,終於鍵盤輸入什麼,螢幕就會顯示什麼了,UART 的功能測試也就告一個段落。

我發現講到這裡篇幅已經太長,請原諒我下一篇再接著講 ADC!

*另外,我上個月開始有在 MakerPRO 共筆專欄,歡迎大家來交流!(鞭策小力一點拜託><)

本文同步發表於MakerPRO

發表留言