Arduino序列埠通訊程式Serial.read()讀取到 ÿ 字元的補充說明

本文旨在補充《超圖解Arduino互動設計入門》第五章「序列埠通訊程式」,以及第十章「透過序列埠調整燈光亮度」的讀取序列埠值。動手做10-3與10-4的程式碼,都有一個判斷傳入值是介於’0’~’9’的條件式,例如,底下是diy10_4.ino的程式片段:

if (Serial.available()) {
    // 讀取傳入的字元值
  while ((chr = Serial.read()) != '\n') {
      // 確認輸入的字元介於'0'和'9',且索引i小於3(確保僅讀取前三個字)
    if (chr >= '0' && chr < = '9' && i < 3) {
      data[i] = chr;
      i++;
    }
  }
    :
    :
}

將此上傳程式到Arduino,再開啟「序列埠監控視窗」,並輸入"123"字串,將能看到Arduino接收到完整的數字123:

Arduino的序列埠監控視窗

若修改程式碼,刪除「確認輸入的字元介於'0'和'9'」的判斷條件:

if (Serial.available()) {
    // 讀取傳入的字元值
  while ((chr = Serial.read()) != '\n') {
       // 確認索引i小於3(確保僅讀取前三個字)
     if ( i < 3 ) {
       data[i] = chr;
       i++;
     }
  }
   :
   :
}

再度測試程式碼,Arduino都只能收到第一個字元。

Arduino的序列埠監控視窗

這是因為當程式察覺到有序列資料進入時,就開始連續讀取三個字元。可是,序列資料的傳入速度遠不及程式迴圈的執行速度,所以存入data陣列的第2和第3個元素值都是-1(代表序列埠沒有輸入值)

Arduino的Serial序列埠程式庫,接收字元的流程。

因此,上面的程式碼必須加入判斷條件;除了篩選出數字之外,還可以用排除 -1 的方式,像這樣:

if (Serial.available()) {
    // 讀取傳入的字元值
  while ((chr = Serial.read()) != '\n') {
       // 確認字元值不等於-1,而且索引i小於3(確保僅讀取前三個字)
     if (chr != -1 && i < 3 ) {
       data[i] = chr;
       i++;
     }
  }
   :
   :
}

補充說明,-1的字元值會在「序列埠監控視窗」顯示成 'ÿ'。所以,假如序列埠監控視窗顯示出 'ÿ' 字元,可能需要檢查序列通訊程式是否有正確收到資料。

Posts created 467

63 thoughts on “Arduino序列埠通訊程式Serial.read()讀取到 ÿ 字元的補充說明

  1. 請問老師,在超圖解Arduino 互動設計入門(第二版),有關於IRremote.h 的使用,範例12-2 中的編繹過程有個問題
    IRrecv irrecv(RECV_PIN); 顯示”IRrecv” does not name a type. 以及以下描述:
    diy12_2.ino: In function ‘void loop()’:
    diy12_2:17: error: ‘irrecv’ was not declared in this scope
    diy12_2:17: error: ‘results’ was not declared in this scope

    請問,這個問題原因在那裡? 謝謝.

    1. hi john:

      請先把光碟「程式庫」資料夾裡的”IRremote”資料夾,複製到Arduino安裝路徑裡的”libraries”資料夾,或者「我的文件」裡的”Arduino\libraries”路徑。

      以後開啟Arduino軟體,就能正確編譯程式了。

      thanks,
      jeffrey

  2. 老師您好
    請問有沒有方法能夠不透過序列埠控制視窗來直接控制arduino呢?謝謝

    1. hi eli:

      arduino軟體內建的「序列埠監控視窗」其實是一個序列埠通訊程式,你可以用其他序列埠通訊軟體取代,例如:AccessPort和CoolTerm,只要序列通訊協議(如:傳輸速率)一致即可。

      thanks,
      jeffrey

  3. 趙老師您好:
    請問Arduino的A0~A5,能當示波器一樣,讀入sin波的訊號嗎?還是只能讀入一般直流電的訊號,因為我拿來接信號產生器,讀出來的數值都是正值! 沒辦法讀出負半週期的值! 謝謝!

    1. hi jenson:

      Arduino的類比輸入埠預設只能接受0~5V的訊號,超過這個範圍的類比訊號(含負值),都要先經過額外的電路,將訊號縮限在0~5V之內。最基本的方法是採用電阻分壓,請先參閱:

      • 6-6頁,電阻分壓
      • 6-14頁,運算放大器的偏壓電路

      我週一或週二再補充說明。

      thanks,
      jeffrey

    2. 假設輸入的類比訊號電壓介於-10V和10V之間,連接Arduino控制板時,要先經過以下的電阻分壓電路,將訊號電壓限制在0~5V之內:

      電阻分壓電路

      經過電阻分壓之後,輸入Arduino的電壓值會降低,而且會低於Arduino的工作電壓(5V)。

      假設我們預期的最高輸入值是4V,R1, R2和R3電阻值的計算方式如下:當輸入訊號為負電位時,電流將朝R1和R2方向流動,不會經過R3,因為「接地」的電位高於「負電位」。

      電阻分壓,負電位。

      所以R1和R2將構成分壓電路,Arduino的類比輸入埠將檢測到0V。為了方便計算,取R2為1KΩ,我們先求取最大負電壓的分壓電阻值,從底下的算式得知R1值為400Ω:

      計算R1電阻值

      當輸入訊號高於5V時,將不會有電流流過R1,此時R2和R3形成分壓電路:

      電阻分壓電路,正電位。

      從底下的算式得知R1值約670Ω:

      計算R3電阻值

      如果你需要放大輸入的類比訊號電壓,可使用運算放大器,詳細請參閱書本6-14頁,運算放大器的偏壓電路

      thanks,
      jeffrey

  4. 老師您好,我想詢問直接將數字經由序列埠傳達給arduino在如何撰寫呢??我設定的數字是溫度值0~30度,但我如何傳達數字給arduino??請老師協助

    1. hi yuan:

      10-10頁「動手做10-3:透過序列埠調整燈光亮度」的程式碼,就能符合你需求,請自行修改程式。

      thanks,
      jeffrey

  5. 趙老師你好!我參照老師新版的ARDUINO超圖解互動設計入門2的第5-18頁
    我程式碼打跟上面完全一樣,但打開序列負監控視窗都沒有任何訊息,按下Reset鍵也是一樣
    我的版子是OZONE的,但應該也是可以用吧,我一直找不到原因,請老師協助!

    1. 你的控制板是Leonardo相容板,請在初始化序列埠敘述之後,加入底下的while迴圈試試看:

      void setup()
      {
        Serial.begin(9600);
        while (!Serial) {
          ; // 等待序列埠連線
        }
      
        : 其餘的程式碼
      }
      

      thanks,
      jeffrey

  6. 老師您好,我目前使用Arduino MEGA 2560,想透過電腦傳訊息(經由Serial)控制Arduino,在由Arduino傳同樣的訊息(經由Serial1)控制其他模組,但是我遇到問題了!
    電腦控制Arduino沒問題,但是Arduino沒辦法經由(Serial1)傳遞同樣的訊息給其他模組,執行過Serial1.write(“有字元”)後,Serial1.available()一直回傳”0″,導致Serial1.read()持續回傳”-1″,請問這大概是甚麼問題呢?是我對Serial1.write()不夠了解嗎?
    抱歉問題有點長和模糊,但困擾我很久了,希望老師能解惑,謝謝!

    1. hi andy:

      根據你的描述,你有兩個Arduino控制板,分別是A和B;電腦接A,A的Serial1(18和19腳)接B的Serial1(19和18腳),在A上執行Serial1.write(),但是B的Serial1.read()始終讀不到資料?請問你的接線正確嗎?

      thanks,
      jeffrey

    2. 老師您好,謝謝您的回覆,我目前是用USB控制Arduino,Arduino上有外接一個無線通訊晶片(從18and19接線),想測試從電腦上直接下命令(AT command),看看Arduino上的外接無線通訊晶片有沒有回應,不是有兩塊Arduino,不知道這樣敘述夠不夠清楚,謝謝老師!!

    3. 謝謝老師!我想我發現程式碼中的問題了!
      想順便請問老師,我有購買Arduino 互動設計入門第二版,請問書裡面有沒有Serial.write()的相關章節呢?
      或是更詳細的Serial函數教學(除了第五章”序列埠通信”之外),再次謝謝老師熱心回應!

    4. Serial.write()和Serial.print()的主要差異是print()會將輸入的數字或文字都轉換成字元,而write()則會把數字轉換成對應的ASCII字元。

      例如:
      Serial.print(65); // 輸出’6’和’5′
      Serial.write(65); // 輸出’A’

      thanks,
      jeffrey

  7. 老師你好,我之前想透過監控視窗顯示從0.0上升到1.0、再從1.0下降到0.0
    程式碼是:
    double brightness = 0.0;
    double fade = 0.1;

    void setup() {
    Serial.begin(9600);
    }

    void loop() {
    Serial.println(brightness);
    brightness = brightness + fade;
    if(brightness == 0.0 || brightness == 1.0)fade = -fade;
    }

    我試過把double改成int,小數改為整數就可以運作正常,但不知道為什麼小數就只會一直加上去,完全不理if的條件…請老師幫忙解惑!!

    1. hi yen:

      浮點數運算經常會遇到精確度的問題,以你的需求來說,最簡單的解決方法是先用整數運算,再換算成浮點數:

      // 顯示1.0, 0.9, 0.8, ...0.0
      int i;
      for (i=10; i>=0; --i) {
        float f = i/10.0;
        Serial.println(f);
      }
      

      thanks,
      jeffrey

    2. 謝謝老師~
      另外可以稍微講一下他進行浮點數運算遇到的問題嗎?

    3. 簡單的說,十進位的小數部份轉換成二進位,要不斷地乘以2,直到小數部份為0;0.1轉成二進位會變成無限循環的數字,但電腦的記憶體容量有限,無法表示無限長度的數字,所以會出現誤差。詳細請參閱冼鏡光老師的「使用浮點數最最基本的觀念」這篇文章說明。

      thanks,
      jeffrey

  8. 老師您好,我想用Arduino來取代示波器讀取電路中訊號的頻率(300kHz附近),是否是如您上面回答,用分壓電路的方法使訊號限制在0-5V,再計數讀到最大值(1023)n次的時間t,然後得到頻率為n/t。不知道這樣的想法是否正確,還是有其他方法?

  9. 老師您好
    我是德明的學生 想請問您
    我用RN131當作wifi module
    之後用android寫了一個socket按按鈕回傳數字
    rn131跟 手機都接上熱點 而rn131也接上了arduino
    但我不知道arduino要寫什麼才能收到回傳的數字
    煩請老師解答了!!

    1. hi DanYiChen:

      我猜想你的Android程式不是採用HTTP協定,請參考Arduino IDE軟體 “檔案 > 範例 > Ethernet > Telnet Client” 範例中的loop()迴圈程式寫法。

      thanks,
      jeffrey

  10. 老師您好我想請問一下
    如何讓感測器測得的值 顯示再序列埠
    使用繼電器去做判斷序列埠那邊測得值是否該斷電
    例如我現在感測器得值 序列埠顯示8
    繼電器的控制程式有一段是
    val = analogRead(SensorPin);
    if (val>=8);
    {
    digitalWrite(relay1,HIGH); // 繼電器1導通;
    digitalWrite(relay2,LOW); // 繼電器2開關斷開;
    但好像不會如我寫的程式去做動作

    1. 老師您好
      看過5-22頁
      如果我的感測器在序列埠中顯示7.44
      我程式改if (val>=’8′);是字元類型的判斷
      這樣好像會無法動作
      有辦法感測器測出得在序列埠顯示5.5
      我繼電器本身去做判斷嗎

    2. 你可以用String類型物件的toFloat()方法將字串轉換成浮點數字,以這個範例程式來說:

      String str = "";   // 暫存輸入字串的變數  
      
      void setup() {
        Serial.begin(9600);
        Serial.println("Ready.");
      }
      
      void loop() {
        while (Serial.available() > 0) {
          char val = Serial.read();
      
          if (val != '\n') {    // 若序列輸入字元不是 '\n'
            str += val;         // 把字元連結成字串
          } else {
            Serial.print("Input data: ");
            Serial.println(str.toFloat());   // 將字串轉換成浮點數字
            str = "";    // 清除字串
          }
        }
      }
      

      編譯並上傳程式碼到Arduino後,在序列埠監控視窗輸入13.5(預設的行結尾請選用 “NL(newline)”),監控視窗將顯示:Input data: 13.50

      thanks,
      jeffrey

  11. 請問老師
    要是遇到讀取類比訊號時再用serial.write TX RX傳到第二塊面板
    但 傳遞過慢 導致已經動完去還沒顯示數值,這該怎麼處理

    1. Arduino Uno的類比數位轉換器的時脈頻率是125 KHz,每一次轉換都要需要13個週期,所以類比取樣頻率是9615 Hz。
      請把序列鮑率提高到115200。

      thanks,
      jeffrey

  12. 請問老師
    因要讀取一個表頭數值
    但要先發送一串16進制過去後,表頭會回報一串16進制數值回來
    但該如何做才能確認它有傳回來且設定讀取幾筆資料呢?

    1. 在UART序列埠收送的資料,沒有「進制」之分,反正通常都是一次傳送一個位元組,你可以說,一個資料是介於0~255的十進位,或者0x00~0xFF的16進位,兩者的本質是一樣的。

      你只要依照規格書的要求(傳輸速度和內容)傳遞資料,並等待接收就好啦~

      thanks,
      jeffrey

  13. 請問老師:

    動手做10-3和10-4都有用到 if(Serial.available()) 這個判斷式
    Serial.available()這個函數會顯示出序列埠暫存的字元(好像是64個字元???)
    我用Uon板在Void loop{}內只寫了Serial.println( Serial.available() );
    然後一直從序列埠送出字串給Arduino Uno,但好想到63之後就不會再增加數值了

    那如果我們輸入的總字元數超過64會發生什麼樣的變化???
    我是著在這兩個範例執行時輸入很多的字元,但是都不會發生錯誤
    所以很好奇暫存滿了後會媽生什麼事情

    10-3是用Serial.read()來抓下一個字元,如果暫存滿了之後它會是如何運作的呢?
    10-4是用i++去撈出新輸入字串的第一個暫存位置,,如果暫存滿了之後它會是如何運作的呢?

    1. Arduino Uno板預設的序列埠緩衝區是64位元組沒錯,請參閱5-20頁底下的說明。
      若緩衝區資料大於64位元組,將無法再接收新進的資料。

      thanks,
      jeffrey

    2. 那有什麼方法可以直接清空緩存嗎?

      上網搜尋好像沒有直接清除序列埠緩存的函式
      但有看到說用Serial.read()取得資料號就會清除已讀取資料
      不知道還有沒有其他的方式

    3. 確認某個程式物件具有哪些功能,可直接查閱官方的指令說明文件,例如Serial說明頁,沒看到直接清空緩存的指令(註:1.0版之前用flush)。

      thanks,
      jeffrey

  14. 老師 我想請問一下 我想用processing接收arduino的序列埠的值 但是我傳到processing的時候 都會是ASCII碼
    例如:從arduino傳1過去 processing接收到的值會是 49 13 10 1
    有沒有辦法只接收到1就好了呢??

  15. 您好想請問一下
    我有兩塊板子mega2560+ramps1.4和Pro Micro
    在Pro Micro上有紅外線壁障模組,當偵測到障礙物時想寫入指令到mega2560這張板子裡
    目前卡在不知道如何寫入指令到mega2560這張板子裡,請問有辦法嗎謝謝

    1. 但mega2560上已經插了ramps1.4沒辦法用I2C的方式

      那請問序列埠的方式要如何讓兩個不同COM的板子溝通呢謝謝

    2. 可以使用I2C,但如果要用UART通訊,可以用SoftwareSerial自訂接腳。

      thanks,
      jeffrey

  16. 老師你好,
    請問available在UNO板能用,
    但是在D1板不能跑進去,請問該怎麼解決呢?
    謝謝您!
    #include
    SoftwareSerial Serial2(0,1);
    while (Serial2.available()) {
    char c;
    c =Serial2.read();
    }

  17. 您好,想請問如果arduino預設rxtx只有一組,可以利用SoftwareSerial增加txrx腳位嗎?

  18. 老師您好
    最近小弟在專題遇到了一些問題,想請老師相助
    是有關於樹莓派python與Arduino通訊的問題
    我想實現的是當python傳送一個訊號給Arduino時Arduino會去控制8×8點矩陣顯示上或下箭頭
    但現在只能實現一半,當我傳送1時可以顯示上箭頭,但當我繼續輸入2訊號時卻不能改變箭頭方向
    下方是Arduino的程式碼
    void loop() {
    if(Serial.available()){
    if(date = Serial.read()){
    while(date == ‘1’){
    p1();
    date = Serial.read();
    }
    while(date == ‘2’){
    p2();
    date = Serial.read();
    }
    }
    }
    }
    下方為python程式碼
    import serial
    Port = “/dev/ttyUSB0” # 串口
    baudRate = 9600 # 波特率
    ser = serial.Serial(Port, baudRate, timeout=1)
    while True:
    a = int(input(“輸入信號”))
    if a == 1:
    send = ‘1’ # 发送给arduino的数据
    elif a == 2:
    send = ‘2’
    elif a == 3:
    send = ‘3’
    else:
    break
    ser.write(send.encode())
    str = ser.readline().decode() # 获取arduino发送的数据
    if(str == ‘U\r\n’):
    print(‘上’)
    if(str == ‘D\r\n’):
    print(‘下’)
    if(str == ‘L\r\n’):
    print(‘左’)
    ser.close()

    1. 應改成類似;

      if (Serial.available()) {
        val = Serial.read();
        
        switch (val) {
          case '1':
            // 一些動作
            break;
          case '2'"
            // 另一些動作
            break;
      
           : 略
        }
      }
      
  19. 老師謝謝您的回覆
    還想請教一個問題
    就是因為我想呈現的是傳送一個訊號後顯示的箭頭是會持續顯示的
    直到訊號改變之後箭頭跟著改變(像是給1顯示上,給2顯示下)
    那老師說的方法我也試過了,也試過很多其他方法
    但出來的結果不是燈只閃一下就是會持續亮但無法再改變箭頭方向
    所以想請老師指點指點

    1. 你的程式架構應該類似這樣:

      char dir = '1';      // 設置動畫方向的字元
      
      void readSerial();   // 讀取序列輸入的函式
      
      void animate();      // 分時顯示動畫的函式
      
      void loop() {
        readSerial();  // 讀取序列輸入,可改變或維持dir值
        animate();     // 執行動畫,依照dir決定方向
      }
      
  20. 老師您好~

    看了這個網頁介紹取從序列埠取輸入字元方式
    我照其主程式用法,可編譯及燒錄,但燒錄進ESP8266後
    序列埠只要輸入任何一個值+enter鍵就會出現像是錯誤文字出來~

    不知道問題在哪?麻煩老師解答謝謝

    主程式
    int i=0;
    char chr;
    char Data[4];

    // 算時間用的
    int startTime;
    int Duration; //已經過時間1
    int OnTime=3000; //1000=1秒,可自訂時間10秒

    void setup() {
    Serial.begin(115200); //set up serial library baud rate to 9600

    //算時間程式區
    startTime=millis();

    }

    void loop() {
    if (Serial.available()) {
    while ((chr = Serial.read()) != ‘\n’) {
    // 確認字元值不等於-1,而且索引i小於3(確保僅讀取前三個字)
    if (chr != -1 && i =OnTime){

    Serial.print(“\n每區間顯示: “);
    Serial.print(“Data= “);
    Serial.print(Data);
    startTime=millis();
    }

    }

    ========
    以下是呈現的序列埠錯誤訊息

    ————— CUT HERE FOR EXCEPTION DECODER —————

    Soft WDT reset

    >>>stack>>>

    ctx: cont
    sp: 3ffffdd0 end: 3fffffc0 offset: 01a0
    3fffff70: 3fffdad0 3ffee4e8 3ffee50c 40202585
    3fffff80: 00000031 00000000 3ffee50c 4020110b
    3fffff90: 3fffdad0 00000000 3ffee50c 40201093
    3fffffa0: feefeffe 00000000 3ffee560 4020190c
    3fffffb0: feefeffe feefeffe 3ffe85dc 40100b51
    <<<stack<<<

    ————— CUT HERE FOR EXCEPTION DECODER —————

    ets Jan 8 2013,rst cause:2, boot mode:(3,6)

    load 0x4010f000, len 3460, room 16
    tail 4
    chksum 0xcc
    load 0x3fff20b8, len 40, room 4
    tail 4
    chksum 0xc9
    csum 0xc9
    v00041c60
    ~ld

  21. 老師您好~我還是會發生可編譯,可寫入,但會出現序列阜視窗錯誤碼
    但該開發版跑其他wifi相關伺服器程式則沒問題~
    序列阜速度也跟程式碼一樣

    我後來取這頁另一個範例,程式碼只有很陽春一點點,一樣情形
    不知道問題在哪?煩請解惑謝謝~
    硬體是ESP8266

    主程式如下
    int i=0;
    char chr;
    char data[4];

    void setup() {
    Serial.begin(115200); //set up serial library baud rate to 9600

    }

    void loop() {

    if (Serial.available()) {
    // 讀取傳入的字元值
    while ((chr = Serial.read()) != ‘\n’) {

    // 確認字元值不等於-1,而且索引i小於3(確保僅讀取前三個字)
    if (chr != -1 && i >>stack>>>

    ctx: cont
    sp: 3ffffd90 end: 3fffffc0 offset: 01a0
    3fffff30: 00000003 00000100 0001c200 00000000
    3fffff40: 4020240b 3ffef1c4 00000000 40202416
    3fffff50: 3fffff80 3ffef1dc 3ffee4d4 3ffee53c
    3fffff60: 007a1200 0f818351 3ffee400 3ffee53c
    3fffff70: 3fffdad0 3ffee4d4 3ffee4b0 402024c5
    3fffff80: 00000031 3ffee4d4 3ffee528 402010ab
    3fffff90: 3fffdad0 00000000 3ffee528 4020106f
    3fffffa0: feefeffe 00000000 3ffee528 4020184c
    3fffffb0: feefeffe feefeffe 3ffe85d8 40100b51
    <<<stack<<<

    ————— CUT HERE FOR EXCEPTION DECODER —————

    ets Jan 8 2013,rst cause:2, boot mode:(3,6)

    load 0x4010f000, len 3460, room 16
    tail 4
    chksum 0xcc
    load 0x3fff20b8, len 40, room 4
    tail 4
    chksum 0xc9
    csum 0xc9
    v00041b80
    ~ld

    1. 再看了一下你上一個程式,邏輯是錯的,根本沒有計算時間間隔,請自行修改。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top