使用Arduino實作Serial串列通訊的經驗談
Serial Port的通訊在微控制器中是很常用的一種方式,可以用來連接兩個晶片,或是直接跟電腦進行資料交換。在Arduino上同樣具備了Tx,Rx的Serial Port功能,其中一組會建立在板子上的USB連接埠上,不必另外買RS232-USB轉接線以及MAX232這類轉接IC就可以直接跟電腦進行連線,這是非常方便的設計。
在Arduino中的Serial Port傳送可以有多種函示的選擇,例如println()、read()、readByte()、write()等等,可以依據使用習慣來挑選。但Serial對我而言,我只重視一件事情,那就是資料的穩定、無錯誤。使用println()也許可以傳送字串或其他格式,但卻不是我習慣的傳送方式,因為傳送的資料是要給電腦看的,而不是給人看的,因此是不是字串不太重要,反而純數值會好些。
在Serial的資料傳輸中,我習慣將資料轉化成一個Byte陣列,透過read()、write()的方式,把一整個陣列的Byte傳送或者接收,盡可能減少資料出錯的可能性。由於Serial Port傳輸速度有限,資料傳輸的間隔可以自行斟酌,我個人通常都是使用50ms以上的間格時間,避免傳輸時有問題發生。
以下是一支範例程式,包含了資料的傳誦與接收,在看下面全部程式之前,我手繪了一張程式運行的內容(請原諒我很懶惰用電腦繪圖)。這支程式主要是啟動後,會等待電腦端或是另一個連接的設備傳送一串5 bytes的資料,收到後檢測第一個byte的內容,如果剛好是0xFF就開始每隔100ms傳送一次資料出去;若不是,則等待下一次資料的接收。
3.void loop()
再來說明AND位元運算「&」,這邊主要是可以把這個運算設計成一種濾波器的概念,例如將一個4-byte的資料與0x000000FF(寫這樣代表是4-byte形式,如果是0xFF為單一byte形式)進行「&」運算,就會保留最右邊的byte內容,並將其他三個byte位置歸零。如果不懂為什麼會有這樣的效果,我們繼續往下看,以單一bit的維度來看。
其實「&」的運算是把一樣長度的資料,每一個相同位置的位元拿來進行AND比較。如果將0xF0與0xAA進行運算,則前段1111的部分與1010運算後,會保留1010的部分;後段由於0000的關係,輸出結果會全部都是0000。
根據上面的範例,依此,如果我們要保留一個完整byte的內容,就讓他與0xFF進行運算,這樣資他的資料都可以保留;相反的,如果想要把整個byte的資料變成0,就讓他跟0x00進行運算。
這邊附上一張真值表,如果不太懂AND以及OR的運作就看一下,如已經懂了,就跳過吧。
下面是完整的程式:
在Arduino中的Serial Port傳送可以有多種函示的選擇,例如println()、read()、readByte()、write()等等,可以依據使用習慣來挑選。但Serial對我而言,我只重視一件事情,那就是資料的穩定、無錯誤。使用println()也許可以傳送字串或其他格式,但卻不是我習慣的傳送方式,因為傳送的資料是要給電腦看的,而不是給人看的,因此是不是字串不太重要,反而純數值會好些。
在Serial的資料傳輸中,我習慣將資料轉化成一個Byte陣列,透過read()、write()的方式,把一整個陣列的Byte傳送或者接收,盡可能減少資料出錯的可能性。由於Serial Port傳輸速度有限,資料傳輸的間隔可以自行斟酌,我個人通常都是使用50ms以上的間格時間,避免傳輸時有問題發生。
以下是一支範例程式,包含了資料的傳誦與接收,在看下面全部程式之前,我手繪了一張程式運行的內容(請原諒我很懶惰用電腦繪圖)。這支程式主要是啟動後,會等待電腦端或是另一個連接的設備傳送一串5 bytes的資料,收到後檢測第一個byte的內容,如果剛好是0xFF就開始每隔100ms傳送一次資料出去;若不是,則等待下一次資料的接收。
程式的主體共有三個函式:
1.void setup()
這個函式是在Arduino啟動後會執行一次,可以用來做參數的初始設定。其中也設定的Serial port的傳輸速度,在這裡使用的是19200的傳輸速度(Baud rate),也就是說電腦端那邊的程式也要是設定相同的速度才可以正常工作。
2.void serialEvent()
這個函式比較像是為處理器中的串列中斷函式,當有資料接收時就會執行函式的內容,通常都是用來做資料的接收與判斷。
這就是最基本的主程式位置了,在Arduino運行後,此函示就會一次又一次不斷的執行,有點像是一般C語言中的無窮迴圈while(true)的內容。
在程式裡很重要的地方在於每個byte形式的資料型態unsigned char的數值範圍是0~255,如要Arduino要進行高精度的運算,這樣的範圍一定是不夠的。在這個程式裡,大部分的資料運算型態都是使用4 bytes形式的long int,在32位元電腦中,Java或是C語言標準的int預設為4 bytes,由於我們使用的是8位元微控制器Arduino,為了保險起見,宣告時盡量還是使用全名long int。
要將4-byte的long int分配到四個unsigned char的方式,在這裡使用了位移運算「>>」以及「<<」,搭配位元運算「&」以及「|」來處理。首先說明一下位移運算,位移運算的功能可以讓資料型態的位元進行左移或者右移。
以下圖的範例來說,一個long int中具有4個byte,每個byte為8-bit。如果說將資料變數val向右位移8個bit的位置,其運算表示為「val>>8」,此時輸出的結果就會把整串資料往右邊一8個bit的位置,也就是變成原本[3][2][1]的byte往右跑了一個byte的位置,然後原本的[0]byte就不見了。依此類推,也可以位移其他長度。
再來說明AND位元運算「&」,這邊主要是可以把這個運算設計成一種濾波器的概念,例如將一個4-byte的資料與0x000000FF(寫這樣代表是4-byte形式,如果是0xFF為單一byte形式)進行「&」運算,就會保留最右邊的byte內容,並將其他三個byte位置歸零。如果不懂為什麼會有這樣的效果,我們繼續往下看,以單一bit的維度來看。
其實「&」的運算是把一樣長度的資料,每一個相同位置的位元拿來進行AND比較。如果將0xF0與0xAA進行運算,則前段1111的部分與1010運算後,會保留1010的部分;後段由於0000的關係,輸出結果會全部都是0000。
根據上面的範例,依此,如果我們要保留一個完整byte的內容,就讓他與0xFF進行運算,這樣資他的資料都可以保留;相反的,如果想要把整個byte的資料變成0,就讓他跟0x00進行運算。
這邊附上一張真值表,如果不太懂AND以及OR的運作就看一下,如已經懂了,就跳過吧。
unsigned char senBuf[9]={0}; //傳送9byte unsigned char inChar[5]; //接收5byte volatile long int count1=0,count2=0; volatile long int rec1=0; int senToCom=0; //用來當作是否傳送資料的信號 void setup() { //第一個開頭位置,這個byte的用意是用來辨別資料串的開頭,以確認收 //到的9byte是否完整。假設收到一串9byte的資料,開頭第一個byte是 //我們所約定的0x11,則此串資料有效;若不是,則為無效資料串。 senBuf[0]=0x11; // Open serial communications and wait for port to open: Serial.begin(19200); while (!Serial) { ; // wait for serial port to connect. //Needed for Leonardo only } } void serialEvent() { int i; if( (inChar[0]=Serial.read()) == 0xFF ) { for(i=1;i<5;i++) { inChar[i]=Serial.read(); } senToCom=1; //開啟傳送的開關 } rec1=((inChar[1] & 0x000000FF) << 24) | ((inChar[2] & 0x000000FF) << 16) | ((inChar[3] & 0x000000FF) << 8) | ((inChar[4] & 0x000000FF)); } void loop() { //使用delay函數來調整傳送間隔 //要注意的是delay很重要,不可以不放 delay(100); long int tempData; tempData=count1; senBuf[1]=(tempData>>24) & 0xFF; senBuf[2]=(tempData>>16) & 0xFF; senBuf[3]=(tempData>>8) & 0xFF; senBuf[4]=tempData & 0xFF; tempData=count2; senBuf[5]=(tempData>>24) & 0xFF; senBuf[6]=(tempData>>16) & 0xFF; senBuf[7]=(tempData>>8) & 0xFF; senBuf[8]=tempData & 0xFF; //如果確定收到0xFF才開始把資料傳出去,不然就不傳送 if(senToCom) { Serial.write(senBuf,sizeof(senBuf)); senToCom=0; //完成傳送後可再將開關關閉 } } |
請問一個問題:
回覆刪除while (!Serial) only for Leonardo board, 可是Leonardo board 又不support serialEvent()
函式,不是嗎?
其實我這支程式是使用MEGA2560在運行的
刪除是9筆資料?
回覆刪除上面的範例是傳送9bytes資料,不過這是可以調整的,不一定要照範例實作。
刪除