2011年7月17日 星期日

5.2) USART Receiver

這篇將示範如何使用 USART 收資料。

首先,先看使用 Arduino 的版本:

程式很簡單,所以就不解釋了。接著我們改用直接控制 USART 暫存器來接收資料。

非中斷版本

底下這支程式會從 Serial Port 接收資料,並且把收到的資料回傳回去 (Echo back):

Serial Port 是否有資料可讀,必須檢查 UCSR0A 的 RXC0 位元,當 RXC0 位元豎起來時代表有收到資料。程式接著從 UDR0 讀取資料並放到 ReceivedChar,然後再利用 serial_putchar() 把資料傳送回去。

下圖是程式執行結果:

image  
▲ 輸入 12345,按下 Send,Arduino 會回傳回來

中斷版本

底下則是使用中斷的版本:

現在用 USART_RX_vect 這個 ISR 來處理中斷,一收到資料,就馬上放到 UDR0 讓 USART 傳送回去。

要特別注意是,mySerial_begin() 這次打開了 Receive Complete Interrupt:

注意事項

在中斷的版本中,我們是自己撰寫 ISR 處理中斷,由於 Arduino 也有同樣的 ISR,因此你不能同時使用 Arduino 的 Serial API,如 Serial.begin(), Serial.read(), Serial.println() 等函式,不然的話程式在編譯時會失敗,編譯器會抱怨 ISR 有重覆定義的問題。

31 意見:

Sven Wang 提到...

請問Cooper前輩~

1. 為什麼使用中斷的方法時直接使用下面的指令
ISR(USART_RX_vect)
{
char ReceivedChar;
ReceivedChar = UDR0;
UDR0 = ReceivedChar;
}
而沒有使用檢查UDRE0的下列這指令呢?
loop_until_bit_is_set(UCSR0A, UDRE0);

2.請教下列指令的意思是什麼
ReceivedChar = UDR0;
UDR0 = ReceivedChar;
為什麼UDR0的資料要存給ReceivedChar
然後又要在把ReceivedChar存回UDR0呢?
意義又是什麼呢?

謝謝Cooper百忙之中抽空解答:)

Cooper Maa 提到...

我先回答你第 2 個問題:

ReceivedChar = UDR0;
UDR0 = ReceivedChar;
為什麼UDR0的資料要存給ReceivedChar
然後又要在把ReceivedChar存回UDR0呢?

好問題!你可以做簡單的實驗,把 UDR0 = ReceivedChar; 這行拿掉,執行程式的時候,你會發現,在你輸入資料後,Serial Monitor 裏不會看到任何回應。

我加了 UDR0 = ReceivedChar; 這一行
目的是讓 Arduino 把收到的資料原封不動的傳回電腦端達到一個 Echo 的效果,所以在你輸入資料後,Serial Monitor 上就會看到剛剛輸入的資料

Cooper Maa 提到...

loop_until_bit_is_set(UCSR0A, UDRE0);

會用這一行,是因為 UART 傳輸資料速度有限,不能讓程式丟資料丟太快給 UART,不然會來不及消化。

為什麼中斷程式的版本沒有用同一行指令檢查 UDRE0 呢? 嗯....是因為我偷懶沒有做..哈
不過,其實也沒有必要,因為收資料速度跟送資料的速度是一樣的,所以這會放一個 byte 到 UDR0 準備傳送出去,下會兒收到一個 byte 時,前面那個 byte 就已經送完畢了,所以不加也沒關係

Sven Wang 提到...

這邊又有些許問題請教拉~~~麻煩Cooper

1.所以我解釋一下
ReceivedChar = UDR0;
上面的UDR0是RXD
UDR0 = ReceivedChar;
上面的UDR0是TXD
這樣解釋對嗎?

2.我們只要把資料放進UDR0馬上就會自動傳出去嗎?還需要做什麼設定嗎?

3.請教TXC0跟UDRE0的差別
我困惑的點是:
當資料被傳送出去時,TXC0與UDRE0都會豎起
當UDR0裡面有資料時,TXC0跟UDRE0都不會豎起
請問這樣好似同步的效果, 實際上應用有什麼差別呢?

4.請問您的TXC0教學文裡面 Shift Register是什麼東西呢?

麻煩Cooper幫我解惑~~~非常感謝!!!!

Cooper Maa 提到...

About Q1 & Q2:

Q1: USART 的 transmitter 和 receiver 共用 UDR0 暫存器的 I/O bus:

ReceivedChar = UDR0;

當程式這樣對 UDR0 進行讀取動作,資料會從 RXB (Receive Data Buffer) 暫存器取出,而:

UDR0 = ReceivedChar;

當程式對 UDR0 進行寫入動作時,資料會寫入到 TXB (Transmit Data Buffer) 暫存器

Q2: 是的,只要把資料放進 UDR0 馬上就會自動傳出去,條件是 transmitter 要 enable (要設定 UCSR0B 的 TXEN0 位元)

Cooper Maa 提到...

一併回答 3 跟 4:

故事是這樣的,有位管理者和一位工程師,管理者一次只收一件工作,而當工程師收到工作時,會把工作拆成八個動作,一次執行一個動作,當八個動作全部執行完畢時便算完成一件工作。

如果說一個動作等於一個位元,八個動作就等於一個位元組,也就是一件工作。這工程師相當於是 Shift Register,每一個 clock 推送出一位元。

而管理者呢,其實他就是 UDR0,或者更精確一點是 TXB 暫存器

也就是說:

工程師 = Shift Register
管理者 = UDR0 (TXB)

當我寫入資料到 UDR0 時,便是交付一件工作給管理者 (UDR0),而管理者會轉給工程師 (Shift Register) 去執行。而工程師真是真正執行工作的角色,會一個位元一個位元的把資料傳出去。

與之相關的兩個旗號: TXC0 和 UDRE0:

當管理者手上沒有工作時,代表可以再接一件工作,這時 UDRE0 會豎起。
當管理者手上沒有工作,而且工程師前一件工作也做完了的時候,這時 TXC0 就會豎起。

差別在: UDRE0 會比較早豎起來,因為管理者把工作交給工程師執行時,它就會說「老闆,我有空了」雖然這時工程師還在辛苦工作中...

而 TXC0 則是用來確認資料是不是已經全部傳出去的旗號,因為只要 TXC0 豎起,代表資料已經全部傳給接收端了。

呼~~ 好像在繞口令,講得鬍子都打結了! haha~

Sven Wang 提到...

這樣說明超級清楚!!!
打通了!!
:))

Cooper Maa 提到...

哈哈,有些時候打個比喻會比較容易理解。

Unknown 提到...

// Enable receiver and transmitter, enable RX interrupt
UCSR0B |= _BV(RXEN0) | _BV(TXEN0);
UCSR0B |= _BV(RXCIE0);
UCSR0B &= ~_BV(TXCIE0);


-----------------------------------
// Set frame format: No parity check, 8 Data bits, 1 stop bit

UCSR0C |= _BV(UCSZ01) | _BV(UCSZ00);

-----------------------------------
請問一下,為什麼UCSR0B與UCSR0C這樣設定,就有可以致能的功用,可以講解詳細點嗎?謝謝(第一次學)

Unknown 提到...

UCSR0B
UDRIE0: USART Data Register Empty Interrupt Enable 0
這個UDRIE0不用設定嗎?

Cooper Maa 提到...

UCSR0B 暫存器比較重要的是 RXEN0 和 TXEN0 位元,這兩個元位用來啟用接收器 (receiver) 和發射器 (transmitter),一般來說,我們會啟用這兩個元。

這行的目的就是啟用 receiver 和 transmitter,下完之後,UART 就可以接收與傳送資料了:

// Enable receiver and transmitter
UCSR0B |= _BV(RXEN0) | _BV(TXEN0);

至於這行則是啟用接收中斷:

// enable RX interrupt
UCSR0B |= _BV(RXCIE0);

所以一旦有資料進來,就會產生中斷跑去執行 ISR:

ISR(USART_RX_vect)
{
char ReceivedChar;
ReceivedChar = UDR0;
UDR0 = ReceivedChar;
}

Cooper Maa 提到...

如果只是要讓 receiver 和 transmitter 致能,只需設定 RXEN0 和 TXEN0 這兩個位元就好。

UDRIE0 這個位元是 "USART Data Register Empty Interrupt Enable 0",如果致能這個位元,那麼當 UDRn (Data Register) 暫存器有空時就會觸發中斷。

Unknown 提到...

假如我用Arduino 的 uart與wifi通訊(傳送與接收)
不曉得有沒有範例可以參考
或是去設定那些東西?

Cooper Maa 提到...

你用的是哪一塊 WiFi Shield?
目前好像有三種 WiFi Shield:

1. WiFly Shield (Rovering Network)
https://www.sparkfun.com/products/9954

2. WiFi Shield (ZeroG)
http://www.cutedigi.com/wireless/wifi/wifi-shield-wishield-v2-0-for-arduino.html

3. WizFi (WizNet)
http://blog.wiznet.co.kr/sw-release-of-wizfi-shield-for-arduino/#.UCefvqFlT44

看了一下,這三塊目前跟 Arduino 之間走的介面都是 SPI
Rovering Network 其實本來是走 UART,不過 WiFly Shield 還是用了一個 SPI to UART chip 轉換跟 Arduino 銜接,這樣做的好處是不佔用 Arduino UART

Unknown 提到...

http://www.roundsolutions.com/techdocs/ds/MOD-WXX-CXX20131.pdf

1.我用的是RN-131C的WIFI

然後再接UART轉USB(PL2303)的轉接頭

重點是Arduino 的 uart與wifi通訊(傳送與接收)的參考範例!?

2.板子都只有一組RX.TX,可以外接多組控制器(聲納,WIFI...等等)

感謝!

Unknown 提到...

第2個是問題,可以外接多組控制器嗎?

Cooper Maa 提到...

這個可能是你要的:
https://github.com/perezd/arduino-wifly-serial

外接多組控制器? 你是說 Arduino 嗎? 外接多組控制是指什麼? 走什麼介面?

Unknown 提到...

外接控制器的意思是指

我用arduino的Wild Thumper 6WD Robot Chassis(馬達驅動電路板) 或 arduino(開發板)這塊,當中的腳位有RX,TX,而我要做的功能是有WIFI (需要用到RX,TX) 跟 maxsonar(聲納也需要用到RX,TX),但板子上面只有一組RX,TX,我可以接兩組 或 兩組以上嗎?還是只能接一組?

Cooper Maa 提到...

要看你用的 arduino 開發板版本的能力喔
你需要接很多個 UART 裝置,可能要選 Arduino mega 之類的比較適合,因為它有 4 個 UART,如果是 UNO,因為只有一個 UART 可用,所以就只能接一個

Unknown 提到...

http://www.google.com/imgres?imgurl=http://robosavvy.com/store/images/DAGU/wild-thumper-connection.jpg&imgrefurl=http://robosavvy.com/forum/viewtopic.php?t%3D7082&h=482&w=918&sz=88&tbnid=RoRTbLxyCRqhgM:&tbnh=65&tbnw=124&zoom=1&usg=__CVMDN918zF-9aShiok5yf-SeHaY=&docid=VhPXB3gszYI1uM&hl=zh-TW&sa=X&ei=pyMrUN7CAeftmAX_5oD4Cg&ved=0CGcQ9QEwBA&dur=368
馬達驅動板(ATmega168)

http://coopermaa2nd.blogspot.tw/
用來當開發板(ATmega328)UNO

我所使用的板子是這兩個,最後是用馬達驅動板那塊板子!

Cooper Maa 提到...

這塊是用 atmega168,只有一個 UART 可用
你要做車子嗎?

Unknown 提到...

http://www.pololu.com/catalog/product/1561

是,要做車子!
關於車子周邊的功能,全部都是第一次接觸,問題很多!
而且你提供的WIFI範例,Verify過不了,不知道哪裡出問題,跟設定有關嗎?
EX.NewSoftSerial wifi(2,3); -> error: 1.'wifi' was not declared in this
or
2.Client client("google.com", 80);->error: 'client' was not declared in this scope

3.error: MemoryFree.h: No such file or directory
以上幾乎類似問題一直出現,這是缺少什麼檔案,還是設定?



Cooper Maa 提到...

老實說,我還沒用過 Arduino Wifi shield
所以也不知道是怎麼回事

感覺好像是少了什麼 library.

BTW, 你前面問的問題: "板子都只有一組RX.TX,可以外接多組控制器(聲納,WIFI...等等)?" 有答案了,你有注意到 WiFlySerial library 的 code 嗎? 如果你要接多組控制器,那麼 SoftwareSerial 應該是可能的解

Unknown 提到...

我有嘗試使用SoftwareSerial指令去測試!
印象有在其他網站看到,似乎沒辦法擴充!
有沒有一些簡單的測試可以讓WIFI回傳值?

Cooper Maa 提到...

那個 WiFlySerial library 還是沒試成功嗎?
看起來可能需要裝 Arduino Streaming library:
http://arduiniana.org/libraries/streaming/

Unknown 提到...

因為馬達驅動板(ATmega168)這塊,USB燒入功能損壞,所以現在改用atmel studio 6.0的生成檔再配合progisp,把ISP轉USB燒路進去,因此程式碼可能暫時沒辦法使用,我知道可以沿用原本arduino裡面的程式碼,但是"arduino.h"載入檔案,需要什麼檔案,我不太清楚,只好先搞懂Atmel stuio 6.0的語法,指令!
很感謝你的幫忙!

Cooper Maa 提到...

不客氣! 其實我也沒幫上忙啊。 逃~~

阿飛飛 提到...

Coopermaa你好~

我想請問一下
就是以上arduino和非中斷版本和中斷版本的程式可以執行
讓Arduino UNO接收Wifly RN-131從智慧型手機傳輸的資料嗎?

我是剛接觸Arduino的學生
所以對以上的程式還不太了解

所以也想請問arduino和非中斷版本和中斷版本的最大差異是?

不好意思麻煩您解答了
謝謝

Cooper Maa 提到...

我沒用過 WiFly RN-131,不過應該是可以通的

非中斷跟中斷兩種方法,最大的差異是一個是用中斷的方式處理啊
(額~~ 有回答等於沒有回答...Orz)

這問題其實用 polling v.s interrupt 來聯想比較簡單
就像醫院掛號系統一樣,我們是讓服務小姐一個一個問病人「你掛號了沒?」用這種 polling
的方式幫病人掛號比較好呢? 還是讓病人抽號碼牌,只要處理完成一個案件,服務小姐就按一下燈號(產生中斷)請下一個病人來掛號這種 interrupt 的方式掛號好呢?

這就是非中斷與中斷的差別喔!

BTW, Rovering Network (就是做 RN-131 的公司) 今年被 microchip 收購了
如果你想對 RN-131 有進一步的了解,可以到 microchip 去上課,microchip 大部份課程都是免費的喔!

Microchip 網站 (注意右手邊選單的教育訓練):

http://www.microchip.com.tw/

阿飛飛 提到...

Coopermaa你好~

我已經可以讓Arduino正確接收從Android手機傳輸到Arduino了

現在想要讓Arduino感測到的數值回傳給手機端,我把RN131無線晶片的RX接到Arduino的TX,我手機傳輸資料給Arduino時,Arduino會把我手機傳輸的資料再原封不動地回傳至手機...

還有
讓Arduino回傳到手機是用digitalWrite沒錯吧?

Cooper Maa 提到...

線路接法沒錯,應該是 Arduino 的 Tx 接 RN131 的 Rx, 然後 Arduino 的 Rx 接 RN131 的 Tx。

Arduino 要傳資料給 Android 是用 Serial.print() 或 Serial.write()
你是怎麼讓 Android 傳資料給 Arduino 的? 在 Arduino 這邊是用 Serial.read() 讀到資料的嗎?