使用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傳送一次資料出去;若不是,則等待下一次資料的接收。


程式的主體共有三個函式:
1.void setup()
    這個函式是在Arduino啟動後會執行一次,可以用來做參數的初始設定。其中也設定的Serial port的傳輸速度,在這裡使用的是19200的傳輸速度(Baud rate),也就是說電腦端那邊的程式也要是設定相同的速度才可以正常工作。

2.void serialEvent()
這個函式比較像是為處理器中的串列中斷函式,當有資料接收時就會執行函式的內容,通常都是用來做資料的接收與判斷。

3.void loop()
這就是最基本的主程式位置了,在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;             //完成傳送後可再將開關關閉
    }
}



留言

  1. 請問一個問題:
    while (!Serial) only for Leonardo board, 可是Leonardo board 又不support serialEvent()
    函式,不是嗎?

    回覆刪除
    回覆
    1. 其實我這支程式是使用MEGA2560在運行的

      刪除
  2. 回覆
    1. 上面的範例是傳送9bytes資料,不過這是可以調整的,不一定要照範例實作。

      刪除

張貼留言

Facebook

這個網誌中的熱門文章

[房屋]裝潢紀錄分享- 基本包冷氣管包梁工程。把冷氣管隱藏得無影無蹤

[心得]天作之合音樂劇「阿堯 Shemenayha」 at 台北表演藝術中心 大劇院

[食記]將捷金鬱金香酒店- 河畔餐廳 主餐+自助餐吃到飽心得

[印尼]印尼 巴淡島 旅遊 TOP 100量販超市必買商品推薦。咖啡、生活用品、泡麵

[工具]線上模擬器。簡單的硬體電路模擬小工具Falstad: Circuit Simulatior Applet

[開箱]三星平板Samsung Tab A7 Lite 使用心得分享

[開箱]超迷你筆電平板二合一隨身機入手。華碩ASUS Transformer Mini T103HAF開箱心得

為您推薦