2015年9月1日 星期二

Arduino 串列埠測試 (UART)

今天要下午才進辦公室, 早上都在家, 所以研究測試了一下 Arduino 的串列埠, 紀錄整理如下. 所謂串列埠是源自 IBM PC 的 RS-232 通訊協定, 也就是個人電腦後面的 COM 埠 (9 針公座 DB-9), 現在新的桌上型電腦與筆電大都沒有接出 COM 埠了, 已經被 USB 取代, 因為透過轉換晶片, USB 串列通訊協定也可以轉成傳統的 RS-232 協定.

RS-232 是全雙工非同步串列通訊, 乃通用非同步收發器 UART (Universal Asynchronous Receiver/Transmitter) 技術的一種, 用來在兩個裝置之間互相進行資料傳遞, 其他工業界常用之非同步串列通訊協定有 RS-422 與 RS-485 等等. RS-485 是 RS-232 的改良版, 採用差動式電壓, 抗雜訊能力較強, 參考 : 

# WiKi : RS-232
# WiKi : UART
# Serial and UART Tutorial  

RS-232 串列埠通訊只用 TX (Out) 與 RX (In) 兩條線同時雙向互傳資料 (當然兩邊 GND 要共接才行, 共三條線), 在介面上比並列通訊要單純. 所謂全雙工是指傳送與接收線分開, 所以兩個方向的通訊可以同時進行. 而非同步是指需要傳送時才起始通訊程序, 它不需要時脈同步線, 而是在協定上設有通訊開始與結束訊息, 在兩邊 Baud rate 相同情況下就可以正確傳送與接收資料. Arduino 所用的 ATmega 微控器除了支援 UART 非同步串列通訊外, 也支援 I2C 與 SPI 這兩種同步串列傳輸 (需同步時脈). 

串列埠的實體位置在 UNO 板子上是 TX (Pin 0) 與 RX (Pin 1) 腳, 如下圖黃色框所示 :


而 Pro mini 的 TX/RX 腳如下圖所示 (上面是插入麵包板的針腳, 右方是連結上傳線的針腳), 注意, 板子上標得是 TXO (TX Out) 與 RXI (RX In), 是 I/O 不是針腳 1/0, 在針腳定義上仍然是 TX1 與 RX0 :


Nano 的接腳則僅有插入麵包板的針腳而已, 因為它本身就有 USB 接頭, 所以 TX/RX 也同時接到 USB 去. 注意, Nano 標的是 TX1 與 RX0 :


串列埠是 Arduino 用來與其他的控制器通訊的窗口, 最早的 Arduino 是採用 RS-232 介面與電腦相連通訊, 用來上傳程式或顯示狀態. 例如下面這張取自 Arduino 官網的 Arduino v3 (Severino) 與 Arduino Serial 就是採用 RS-232 介面 :

Arduino v3 Severino

Arduino Serial

上圖中左上角就是 DB-9 (D 型 9 針) 的 RS-232 的接口. RS-232 實體層採用 -15V 與 +15V 代表正負邏輯 (注意, 負電壓是正邏輯), 所以進 Arduino 後需要做位準轉換, 轉成 0 與 3.3V/5V 的所謂 TTL 邏輯 (Transistor-Transistor Logic), 同時也需要一顆如 MAX232 那樣的晶片來處理訊號, 參考 :

# RS232 → TTL 轉換介面

後來 USB 成為個人電腦串列通訊主流, 就改成了 USB 介面, 雖然實體層是 TTL 位準的 5V, 但 Arduino 仍需要一顆像 FTDI 或 CH340G 這樣的晶片將其訊框轉換成 RS-232 軟體層的 TX/RX 訊號, 參考 :

# 【整理】TTL和RS232之间的详细对比
# TTL介面、I2C 介面、RS232介面、UART 差別?
# 串列傳輸設計(UART Design by Verilog language)

RS232 與 TTL 只是在實體層之電壓位準不同, 在軟體協議層是完全一樣的, 其通訊協定如下圖所示 :


當無資料傳送時, TX 是在 High 準位, 此為 idle 狀態; 開始傳送時 TX 會變成 Low, 此週期為開始位元, 接下來就會連續傳送資料位元, 由 LSB (最低位元) 開始傳送直到 MSB (最高位元), 然後是同位檢查位元, 最後回到 High 之 idle 狀態. 以上是完整之協定, 但實際上要看串列埠設定, 例如 Arduino 一般是用 8, N, 1, 9600, 即資料有 8 bits, 無同位元檢查, 1 個停止位元, 速率 9600 bps. 收送端的設定必須一樣才能成功地通訊, 否則就會出現亂碼或無反應. 所謂非同步是指有資料要傳送時才將 TX 拉到低電位, 通知對方接收; 沒有時就停在高電位, 並非隨時都在傳送資料之意.

Arduino 內建的 Serial 函式庫 (物件) 提供了串列埠連線, 資料傳送與接收等等函式, 使得串列埠通訊程式設計得以大大地簡化. Serial 函式庫的說明文件詳見官網  :

https://www.arduino.cc/en/Reference/Serial

以下為常用的幾個函式整理 :

1. 串列埠連線 : begin

Serial.begin(speed)
Serial.begin(speed [, config])

此函式有一或兩個參數, 必要的第一參數 speed 為連線速率 (baud rate, 每秒傳送幾位元), 可傳入值為 300~115200 bps, 通常設為 9600, 此乃 Arduino IDE 的序列埠監控視窗預設為 9600, 為了避免每次開啟監控視窗還要去改視窗右下角的連線速率, 建議用 9600 即可.

開啟序列埠監控視窗

預設速率 9600 bps

第二參數 config 是可有可無的常數, 用來設定串列信號格式, 預設值為 SERIAL_8N1, 即傳送 8 個資料位元, 沒有同位檢查, 以及 1 個停止位元.

此函式無傳回值, 要放在 setup() 函式中, 因為它只需執行一次.

2. 傳送資料 (輸出) : print, println, write

long Serial.print(val [, format])
long Serial.println([val, format])

這三個函式都是用來在 TX 腳傳送資料, 傳回值為所傳送之 byte 數 (長整數). print 與 println 是將 val 值的每個字元都轉成可讀的 ASCII 字元後才輸出, 而 write 則是直接以二進位碼 (即 byte) 輸出. 亦即, print/println 處理的是字串, 而 write 處理的是字元. print 與 println 差別是 println 會在輸出資料後自動加上跳行 (\r\n), 而且 println 可以無參數, 這相當於是傳送跳行字元, 而 print 至少要有一個參數, 否則會編譯失敗.

參數 val 可以是任何型態資料 (字串, 數值, 布林), 參數 format 只有在 val 是數值 (整數, 浮點數) 時才能用, 字串或布林不可用. 當 val 是字串時, print/println 直接輸出字串, 例如 :

Serial.print("Hello World");     //無跳行, 輸出 Hello World
Serial.println("Hello World");  //有跳行, 輸出 Hello World
Serial.println();                         //純跳行=print("\r\n")

當 val 是整數時, format 可以用 DEC (十進位), BIN (二進位), OCT (八進位), HEX (十六進位) 四個值來設定以何種格式輸出此整數, 預設是 DEC, 例如 :

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

void loop() {
  int a=65;
  boolean b=true;
  Serial.println(a);             //輸出 65 (預設 DEC)
  Serial.println(a,DEC);    //輸出 65
  Serial.println(a,BIN);     //輸出 1000001
  Serial.println(a,HEX);    //輸出 41
  Serial.println(a,OCT);    //輸出 101
  Serial.println(b);             //輸出 1
  Serial.println(false);        //輸出 0
  delay(2000);
}

注意, 舊版 Arduino 的 format 可以用 BYTE 這個參數, 功能與 write 一樣是以 byte 為單位輸出. 但現在新版已經不再支援 BYTE 了. 參考 :

Arduino 筆記 – Serial Library 介紹

若輸出浮點數, 參數 format 須為正整數, 表示小數點後幾位 (四捨五入), 預設是兩位, 例如 :

Serial.print(3.14159);       //輸出 3.14
Serial.print(3.14159, 4);   //輸出 3.1416

至於輸出二進位資料 (byte) 的 write 函式, 其格式如下 :

Serial.write(val)
Serial.write(str)
Serial.write(buf, len)

單一參數時, 其值只能為字元, 字串, 整數, 或布林值, 不能輸出浮點數 (編譯失敗). 輸出整數時, 會輸出此整數所代表之 ASCII 字元, 因 ASCII 的可見字元編碼範圍為 32~126, 其他任何整數都不可見或顯示 "口". 布林值不論是 true 或 false 都只輸出空字元, 因為 0 與 1 在 ASCII 碼是空字元 NUL 與標題開始 SOH (控制字元).

例如 :

Serial.write(65);             //輸出 A (印出此整數所代表之 ASCII 字元)
Serial.write("A");           //輸出 A
Serial.write("Hello!");    //輸出 Hello!
Serial.write(999);            //輸出 "口"

第三個格式第一參數 buf 是一個 char 或 byte 陣列 (int 不行, 會編譯失敗), 參數 len 是要輸出的元素長度, 例如 :

 byte a[]={65,66,67};
 Serial.write(a,sizeof(a));  //輸出 ABC

另外值得一提的是, 當我們呼叫 Serial.print() 與 Serial.println() 時, 這兩個函式會立刻返回, 繼續執行下一個指令, 不會停在那裏等字串傳送完畢. 它們會建立一個緩衝區來存放要傳送的字串, 然後透過中斷來一個一個傳送字元. 所以如果需要讀取序列埠對傳送此字串後的回應 (RX 端) 來判斷下一步要執行的邏輯的話, 必須用 delay() 來暫時停住執行程序等待資料傳送完畢以及對方回應, 不過因為有兩項等待因素 (傳送+回應), 這樣有時較難評估該延遲多久. 這時可用 Serial.flush() 函式來停住程序, 直到資料傳送完畢才會往下執行, 這樣 delay() 只要針對可能的回應時間去估計即可. 注意, Serial.flush() 在 1.0 版以前的功能是用來清空序列埠的接收緩衝器, 但 1.0 版之後改為停住程序直到傳送緩衝區資料傳送完畢. 參考 :

# When do you use the Arduino’s Serial.flush()?

3. 接收資料 (輸入) : available, read

Serial.available()
Serial.read()

當 Arduino 從 RX 接腳接收對方傳來的資料時, 會儲存在緩衝記憶區 (Buffer), 可以用 available() 函式檢查緩衝區是否已經有資料, 其傳回值是 8 位元的 byte 數值 (即字元數, 型態為 byte 或 char). 如果傳回值大於 0 表示已收到對方傳來放在緩衝區之資料.

緩衝區大小是可以設定的, 在 Arduino 安裝目錄下的 HardwareSerial.h 檔案裡 :

D:\arduino-1.6.1\hardware\arduino\avr\cores\arduino\HardwareSerial.h

#if (RAMEND < 1000)
#define SERIAL_TX_BUFFER_SIZE 16
#define SERIAL_RX_BUFFER_SIZE 16
#else
#define SERIAL_TX_BUFFER_SIZE 64
#define SERIAL_RX_BUFFER_SIZE 64
#endif
#endif

可見若可用之記憶體 RAMEND 小於 1KB 時, 緩衝區只有 16 Bytes; 若大於 1KB, 緩衝區則有 64 Bytes, 一般而言緩衝區應該是 64 bytes.

而 read 函式就是用來從緩衝區將資料讀出一個 byte, 若緩衝區無資料就傳回 -1. 讀取後該 byte 資料就被緩衝區刪除了 (FIFO 先進先出). 還有一個 peek 函式, 其功能與 read 一樣都是從緩衝區中讀出一個 byte, 但不同的是, peek 不會在讀出後將該 byte 資料自緩衝區刪除, 故若連續呼叫 peek 函式, 將讀取到相同的 byte 資料 (我現在還想不出 peek 有啥用? 讀取後不刪除, 緩衝區不會爆掉嗎?).

下面範例是 Arduino 傳送資料到 PC 的實驗, 程式取自碁峰楊明豐 "Arduino 最佳入門與應用" 6-3 節稍作修改, 傳送 95 個可見的 ASCII 碼 (32~126) 給 PC :

測試 1 :  Arduino 傳送資料到 PC : serial_transmit.ino

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

void loop() {
  for (byte i=32;i<=126; i++) { 
    Serial.write(i);
    Serial.print("=");
    Serial.println(i);
    delay(1000);
  }


結果如下 :

 =32
!=33
"=34
#=35
$=36
...
...
4=52
5=53
6=54
7=55
8=56
9=57
=125
~=126

接著來測試 Arduino 從 PC 接收資料 :

測試 2 : Arduino 從 PC 接收資料 : serial_receive.ino

int i=0;
void setup() {
  Serial.begin(9600);
}
void loop() {
  if (Serial.available() > 0) {
    i=Serial.read();
    Serial.write(i);
  }
}

上傳 Arduino 後, 打開序列埠監控視窗, 在輸入框中敲一些字元, 按傳送 PC 就對 Arduino 送出此字串, 底下就會顯示 Arduino 從 RX 腳收到的字元 :

PC 傳送資料給 Arduino 

Arduino 接收到 PC 傳來的資料


接下來的測試是要讓兩個 Arduino 透過串列埠互傳資料, 範例參考碁峰柯博文 "Arduino 互動設計專題與實戰" 第七章 7.1.2 節 "兩個 Arduino 透過 UART 相互傳遞資料", 但我使用兩個 Arduino Nano 來實驗, 而不是書中範例用的 UNO.

測試 3 : Arduino 傳送資料給另一塊 Arduino : serial_transmit_receive.ino

第一塊 Nano 板子上傳範例 1 的 serial_transmit.ino 傳送程式, 第二塊 Nano 則上傳範例 2 的serial_receive.ino 接收程式, 然後將傳送板的 TX 腳連到接收板的 RX 腳, 然後打開接收板的序列埠監控視窗, 就可以看到從第一塊板子傳來的資料了 :


上面測試 3 只是單向傳送資料, 接下來要來測試兩個 Arduino 互傳資料 :

測試 4 : 兩塊 Arduino 互傳資料 : nano_left.ino, nano_right.ino

 此例取自全華黃新賢等著 "微電腦原理與應用 Arduino" 7-2 節兩個 Arduino 互傳資料的範例, 書中使用兩塊 UNO, 我則是使用 Nano 板, 其接線如下 :


上圖中兩塊 Nano 之 TX 與 RX 交叉互接 (紅黃線), 即左邊的 TX 接右邊的 RX, 左邊的 RX 接右邊的 TX, 最重要的是, 兩塊的 GND 要相接 (黑線), 因為我是用兩個獨立的行動電源供電, 必須 GND 共接才會形成迴路. 我在初次測試時忘記這個 GND 共接, 結果沒反應.

然後替左邊這塊撰寫程式如下 nano_left.ino :

int LED=13;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  }

void loop() {
  Serial.write('Y');   //先對 TX 送出字元 Y
  while (!Serial.available()) {}  //檢查 RX 緩衝器, 直到有資料進來
  if (Serial.read()=='Y') {   //若收到 Y, LED 閃兩下
    led_blink();
    led_blink();
    }
  }

void led_blink() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(500);
  }

寫完後上傳到左邊這塊, 然後拔掉 USB, 換接右邊那塊 Nano, 為其撰寫程式如下  nano_right.ino :

int LED=13;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  }

void loop() {
  while (!Serial.available()) {}   //檢查 RX 緩衝區, 直到有資料進來
  if (Serial.read()=='Y') {  //若收到字元 Y, LED 閃兩下
    led_blink();
    led_blink();
    }
  Serial.write('Y');   //在 TX 送出字元 Y 給對方
  }

void led_blink() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(500);
  }

接下來要測試 SoftwareSerial 函式, Arduino 板子一般常用的 UNO, Nano, Pro mini 都只有一組硬體 UART 串列埠, 即 DIO Pin0 (RX) 與 Pin1 (TX), 而 Mega 則有四組, 因為 UNO 與 Nano 的串列埠與 USB 並接, 因此如果 USB 接 PC 時就只能與 PC 通訊, 無法同時接其他設備, 例如 ESP8266 WiFi 模組. 於是 Mikal Hart 開發了SoftwareSerial 函式庫, 可以用軟體模擬方式定義多組 DIO 腳當作 UART 埠, 速率可達 11500 bps, Arduino IDE 1.0 版之後已經納入此函式庫, 參考 :

# SoftwareSerial Library

注意, 如果定義了多組軟體串列埠, 同時只能接收一組串列埠資料, 如果想同時收送多組串列埠, 須使用 AltSoftwareSerial 函式庫, 參考 :

# AltSoftSerial Library

測試 5 : 兩塊 Arduino 互傳資料 (使用軟體串列埠) 

以下參考全華黃新賢等著 "微電腦原理與應用 Arduino" 7-3 節範例稍做修改進行 SoftwareSerial 函式庫測試, 仍然使用兩塊 Nano 板子, nano_left 左方板子定義 10 (RX), 11 (TX) 腳做為軟體串列埠, 而硬體串列埠 (0, 1) 則保留給 USB 連接 PC, 以便從 PC 傳送資料給左方板. 右方 Nano 板的硬體串列埠與左方板的軟體串列埠對接, 即 nano_left 的 10 (RX) 接 nano_right 的 1 (TX); 而 nano_left 的 11 (TX) 接 nano_right 的 0 (RX), 接線圖如下 :


在上面測試 4 中, 兩個板子一送電, 左方板即率先送出 'Y' 字元並監測是否收到右方板回送之 'Y' 字元, 右方板收到後閃燈兩下回送 'Y' 字元, 如此周而復始. 此處們不要自動收送, 而是等待我們從 PC 向左方板送出 'Y' 字元才起始週閃燈程序, 而且不是周而復始, 而是下一次 'Y', 右方板先閃兩次, 換左方板閃兩次就停了. 程式部分只要將上面測試 4 稍改 nano_left.ino 即可, 右方板程式 nano_right.ino 不用改.

下面是左方板程式 nano_left.ino (接 PC) :

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10,11);  //建立軟體串列埠腳位 (RX, TX)
int LED=13;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);        //設定硬體串列埠速率
  mySerial.begin(9600);   //設定軟體串列埠速率
  }

void loop() {
  while (!Serial.available()) {}  //等到PC傳送字元才到下一步
  mySerial.write(Serial.read());  //讀取PC傳送之字元,從軟體串列埠TX送給右方板
  while (!mySerial.available()) {}  //等到右方板傳送字元才到下一步
  if (mySerial.read()=='Y') {    //等到軟體串列埠RX收到右方板傳來'Y'字元
    led_blink();
    led_blink();
    Serial.println("Hello!");  //左方板向PC傳送字串
    }
  }

void led_blink() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(500);
  }

下面是右方板 nano_right.ino 程式 (與測試 4 之 nano_postsend.ino 相同) :

int LED=13;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  }

void loop() {
  while (!Serial.available()) {}
  if (Serial.read()=='Y') {
    led_blink();
    led_blink();
    }
  Serial.write('Y');
  }

void led_blink() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(500);
  }

下面為實驗影片, 因為導線太短, 所以右方板我移到上面 (由行動電源供電), 左方板在下面 (由 PC 之 USB 供電), 先打開左方板之串列埠監視視窗, 輸入 Y, 按傳送, 右方板 (上方) 會閃兩次, 然後傳送 Y 給左方板 (下方) 的軟體串列埠, 使左方板也閃兩下 :


可見軟體串列埠有正常運作, 這樣就可以一邊用 PC 監看硬體串列埠, 一邊用軟體串列埠傳送資料給對方, 就不會互相干擾了.

上面的串列埠測試一次只能傳送一個欄位資料給對方, 如果要傳兩筆以上, 必須自行定義傳送協定. 下面測試 6 是在上面測試 5 的基礎上稍作改變, 一次傳送兩筆資料給對方, 例如讓對方的 LED 閃幾次, 以及一次亮滅持續的時間, 此處我們定義傳送協定為兩個欄位之間以逗號隔開. 主控端還是接 PC USB 的左方板, 我們在其串列埠監視視窗輸入 3,500 再按傳送, 左方板就透過軟體串列埠傳給右方板, 右方板收到後要自行解析接收的資料, 以逗號拆開資料, 前者為閃爍次數, 後者為持續時間.

測試 6 : 兩塊 Arduino 傳送多筆資料 (使用軟體串列埠) 

此測試接線圖與上面測試 5 一樣, 只是左右兩塊板子程式要改, 我是參考 O'REILLY "Arduino Cookbook 錦囊妙計" 4-4 節與 4-5 節範例修改的. 首先是左方板程式 nano_left.ino 改成如下 :

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10,11);  //建立軟體串列埠腳位 (RX, TX)
int LED=13;
const int FIELDS=2;  //定義有2個資料欄位
int field_idx=0;  //目前接收之欄位索引
int data[FIELDS];  //定義儲存全部欄位資料之陣列

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);        //設定硬體串列埠速率
  mySerial.begin(9600);   //設定軟體串列埠速率
  }

void loop() {
  while (!Serial.available()) {}  //等到PC傳送字串到硬體串列埠RX才到下一步
  char ch=Serial.read();             //讀取PC傳來之字元
  if (ch >= '0' && ch <= '9') {   //收到之字元為0~9數字字元
    if (field_idx < FIELDS) {     //資料還沒收完, 索引尚未碰頂
      data[field_idx]=data[field_idx] * 10 + (ch - '0');        //轉成整數, 位數累進
      }
    }
  else if (ch == ',') {field_idx++;}      //遇到欄位分隔字元逗號, 欄位索引增量
  else {                              //除了0~9與逗號以外字元均結束接收工作
    if (field_idx != 0) {     //收足兩個欄位資料,經軟體串列埠TX向右方板送出閃燈指令
      mySerial.print(data[0]);      //送出閃燈次數
      mySerial.print(",");             //送出欄位分隔字元
      mySerial.print(data[1]);      //送出持續時間(毫秒)
      mySerial.println();              //送出跳行字元
      Serial.print("Transmit:");    //在監視視窗顯示傳送至軟體串列埠之資料
      Serial.print(data[0]);            //送出顯示閃燈次數
      Serial.print(",");                   //送出欄位分隔字元
      Serial.print(data[1]);            //送出持續時間(毫秒)
      Serial.println();                    //送出跳行字元
      //等待右方板做完閃燈工作
      while (!mySerial.available()) {}  //等到右方板傳送字元才到下一步
      if (mySerial.read()=='Y') {          //等到軟體串列埠RX收到右方板傳來'Y'字元
        Serial.println("Done!");             //左方板向PC傳送字串
        }
      //清空資料, 重新開始
      data[0]=0;
      data[1]=0;
      field_idx=0;
      }
    }
  }

送電後左方板會持續偵測硬體串列埠是否有資料傳送進來, 有則讀進 data 陣列, 其中用逗號當欄位分隔字元, 每次遇到逗號就將索引增量, 以儲存下一個欄位. 我們需要辨別的僅有 0~9 與逗號字元, 其餘一律當作是傳送結束. 當收到這兩種以外字元時, 就檢查資料陣列的索引, 如果有值, 表示有收到資料, 就取出來透過軟體串列埠傳送給右方板, 然後清除索引與陣列, 準備下一次接收指令. 此處接收字元轉成整數的方法利用 ASCII 字元的編碼 :

data[field_idx]=data[field_idx] * 10 + (ch - '0');

其中 '0' 之 ASCII 編碼為 48, 若收到 '1' 字元, 則 ch 值為 '1' 之 ASCII 編碼 49, 所以相減就得到 '1' 的整數值. 若接收到 123, 則每收一位數, 前面一位就變 10 倍, 故要乘以 10.

而右方板的程式 nano_right.ino 如下 :

int LED=13;
const int FIELDS=2;  //定義有2個資料欄位
int field_idx=0;  //目前接收之欄位索引
int data[FIELDS];  //定義儲存全部欄位資料之陣列
int count; //閃燈次數
int ms;  //亮滅持續時間(毫秒)

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  }

void loop() {
  while (!Serial.available()) {}  //等到PC傳送字串到硬體串列埠RX才到下一步
  char ch=Serial.read(); //讀取左方板傳來之字元
  if (ch >= '0' && ch <= '9') { //收到之字元為0~9數字字元
    if (field_idx < FIELDS) { //資料還沒收完, 索引尚未碰頂
      data[field_idx]=data[field_idx] * 10 + (ch - '0'); //轉成整數, 位數累進
      }
    }
  else if (ch == ',') {field_idx++;} //遇到欄位分隔字元逗號, 欄位索引增量
  else { //除了0~9與逗號以外字元均結束接收工作
    count=data[0];  //更新閃燈次數
    ms=data[1];   //更新持續時間
    led_blink();  //閃燈
    //清除接收資料, 重新開始
    data[0]=0;
    data[1]=0;
    field_idx=0;
    Serial.write('Y');  //向左方板回報閃燈完成
    }
  }

void led_blink() {
  for (int i=0; i<count; i++) {
    digitalWrite(LED, HIGH);
    delay(ms);
    digitalWrite(LED, LOW);
    delay(ms);
    }
  }

右方板程式定義了 count 與 ms 分別儲存接收到的閃燈次數與持續時間, 其處理接收之欄位資料方法與左方板是一樣的 (協定要相同), 接收到資料後呼叫 led_blink() 函式去控制 LED 顯示, 完成後向左方板傳送 'Y' 字元, 左方板收到後輸出 "Done!" 於監視視窗.

要注意, 在左方板的監視視窗傳送 LED 控制指令時, 要把下方的結尾方式改為 NL(New Line), 這樣按下傳送時才會在最後面加上 Line Feed 字元 (ASCII 編碼 10), 我們的接收處理程式才會知道接收結束了 :


從測試 6 可知, 要傳送兩筆以上的資料須自行處理資料結構的協定有點繁雜, 所以在全華黃新賢等著 "微電腦原理與應用 Arduino" 7-4 節有提到 Bill Porter 設計了一個 EasyTransfer 的函式庫來簡化硬體串列埠傳送多個變數的程序, 只要將要傳遞的變數用 struct 定義在資料結構中, 再呼叫 sendData 函式即可, 不需要自行處理資料結構之收送, 要增加變數也很方便, 參考 :

EasyTransfer Library for Arduino
# Bill Porter's "EasyTransfer Arduino Library"

不過 EasyTransfer 函式庫有如下限制 :
  1. 只支援硬體串列埠, 不支援軟體串列埠
  2. 資料結構不可超過 255 Bytes
為了能夠在軟體串列埠中也能使用 EasyTransfer 的功能, Bill Porter 另外寫了 SoftEasyTransfer 函式庫, 因為 Arduino IDE 目前尚未納入此兩個函式庫, 請連線到 Bill Porter 的 GitHub, 點選右下角之 "Download ZIP" 下載 :

https://github.com/madsci1016/Arduino-EasyTransfer/archive/master.zip

解壓縮後可看到一共有四個目錄, 可見除了 EasyTransfer 與 SoftEasyTransfer 外, Bill Porter 也寫了 Wire 與 I2C 函式庫. 可將此四個資料夾複製貼上到 Arduino IDE 安裝目錄或 "我的文件\Arduino" 下的 Libraries 資料夾下面 :


然後 IDE 必須全部關掉重開才會抓到函式庫 :


底下測試 7 使用 EasyTransfer 與 SoftEasyTransfer 函式庫來改寫測試 6 :

測試 7 : 兩塊 Arduino 傳送多筆資料 (使用 EasyTransfer 與 SoftEasyTransfer) 

但此處我們不再由 PC 傳送指令給左方板 (因為還要自行處理指令協定), 而是經軟體串列埠固定傳送 3,500 給右方板. 左方板使用 SoftEasyTransfer 函式庫與右方板溝通 (傳送閃燈指令與接收回應), 因為硬體串列埠要用來接 PC, 以便顯示執行狀態, 右方板程式 nano_left.ino 修改為 :

#include <SoftwareSerial.h>
#include <SoftEasyTransfer.h>

SoftwareSerial mySerial(10,11);    //定義軟體串列埠 (RX,TX)
SoftEasyTransfer SET;                   //建立SoftEasyTransfer物件
struct DS {     //定義資料結構
  int count;
  int ms;
  };
int n;               //迴圈計數器
DS data;          //宣告資料結構實體

void setup() {
  Serial.begin(9600);      //設定硬體串列埠速率
  mySerial.begin(9600);   //設定軟體串列埠速率
  SET.begin(details(data), &mySerial);  //初始化軟體串列埠ET物件
  }

void loop() {
  data.count=3;                        //設定軟體串列埠欄位值
  data.ms=500;                        //設定軟體串列埠欄位值
  SET.sendData();                   //經軟體串列埠對右方板傳送資料
  Serial.print("Count=");         //在監視視窗顯示傳送至軟體串列埠之資料
  Serial.println(data.count);    //顯示閃燈次數
  Serial.print("ms=");              //持續毫秒數
  Serial.println(data.ms);        //持續時間(毫秒)
  while (!mySerial.available()) {}  //等右方板回傳'Y'到軟體串列埠RX才到下一步
  if (mySerial.read()=='Y') {     //收到右方板傳來 'Y' 字元, 對串列埠輸出 Done!
    Serial.print(n);
    Serial.println(" Done!");
    ++n;
    }
  }

右方板則使用 EasyTransfer 函式庫與左方板的 SoftEasyTransfer 函式庫溝通以接收其傳來之指令, 其程式 nano_right.ino 如下 (特別注意 struct 結尾大括號後面必須有分號, 否則無法通過編譯) :

#include <EasyTransfer.h>
int LED=13;
EasyTransfer ET;       //建立EasyTransfer物件
struct DS {  //定義資料結構
  int count;
  int ms;
  };
DS data;   //宣告資料結構實體

void setup() {
  Serial.begin(9600);                   //設定硬體串列埠速率
  ET.begin(details(data), &Serial);   //初始化硬體串列埠ET物件
  }

void loop() {
  if (ET.receiveData()) {  //硬體串列埠有收到資料
    led_blink();              //閃燈
    delay(5000);            //休息 5 秒再向左方板回應 'Y'
    Serial.write('Y');      //向左方板回傳'Y'字元表示完成閃燈
    data.count=0;           //重設接收資料
    data.ms=0;               //重設接收資料    
    }
  }

 void led_blink() {
  for (int i=0; i<data.count; i++) {
    digitalWrite(LED, HIGH);
    delay(data.ms);
    digitalWrite(LED, LOW);
    delay(data.ms);
    }
  }


送電後左方板即透過軟體串列埠向右方板傳送 3,500 指令, 右方板收到後依據收到之資料閃燈三下, 然後休息 5 秒再回傳 'Y' 字元給左方板, 左方板收到後即向串列埠輸出 "Done!" 顯示於監視視窗, 如此周而復始. 傳送資料只要呼叫 sendData() 函式就會將所定義之資料結構傳送出去, 接收資料則是呼叫 receiveData(), 當有收到資料時, 此函式會傳回非 0 值, 接收的資料會放在所定義之欄位變數中. 可見 EasyTransfer/SoftEasyTransfer 真的讓我們省了非常多的工啊 !


51 則留言 :

Unknown 提到...

不好意思高手, 請教一下:
1. 測試三: 兩塊arduino傳遞資料測試, 為何接收端要使用Serial.write()輸出? 若使用Serial.print()可以嗎?
2. 若是要接收傳送端傳來的類比數值, 接收端要使用Serial.write()輸出, 還是Serial.print()輸出?
3. 我最近在設計公司的一個case, 就是要兩塊arduino採rs232連接序列單向傳輸類比值給接收端的arduino再輸出給lcd (Arduino1 -->rs232 -->rs232--->Arduino2--->lcd), 我利用10kohm電阻做實驗, 很奇怪, 接收端輸出採Serial.print給Serial monitor,數值show出來是錯的, 程式下一行我寫lcd.print()給lcd monitor, 數值show出來也是錯的, 後來改成Serial.write 和 lcd.write, Serial monitor數值會正常, 但lcd monitor數值會一次一個數值覆蓋更新, 例如970, lcd會出現9, 然後再出現7覆蓋9, 再0覆蓋7, 沒辦法一次輸出970. 但我測試單一片arduino接lcd(I2C連接), 是正常的..可以幫我看看是哪裡出問題嗎? 附上原碼.
傳送端:
const int portpin = A0;
int val;
void setup() {
Serial.begin(9600); // put your setup code here, to run once:

}

void loop() {
val=analogRead(portpin); // put your main code here, to run repeatedly:
Serial.println(val'\n');
delay(2500);
}
接收端:
#include // Arduino IDE 內建
#include
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
int i=0;
void setup() {
Serial.begin(9600);
lcd.begin(16, 2);

}

void loop() {
if (Serial.available() > 0){
i=Serial.read();
Serial.write(i);
lcd.setCursor(1, 1);
lcd.write(i);
delay(400);
}
}

Unknown 提到...

您好:
請問我在Mega2560上接了RS232連接板,
測試PC(用AccessPort 1.37)與arduino的RS232傳輸,可以互傳字串,
但是想用Mega2560接收另一個(以RS232通訊的)訊號處理模組卻一直收不到訊號,
有可能是什麼原因?

我已經試過在PC上AccessPort 1.37只要訊號處理模組的線接上,
終端機就可以收到有效的資料串,
而且通訊協定都是38400.8N1

小狐狸事務所 提到...

Sorry! 我沒有用過 Mega, 但 Mega 具有四組硬體串列埠, 每個用法都一樣, 沒道理其中一組收不到. 會不會是 :
1. 接線問題 (tx/rx 接錯, 插槽或線接觸不良等)
2. 傳送的模組有問題, 根本沒送過來
試試看, 硬體問題不好抓喔, 要耐心一步步將問題隔離開來.

網誌管理員 提到...

我用UNO以TX輸出,以PC接收,接收到的資料不對,是不是準位的問題
因為UNO輸出是5V,但PC的RS232是+/-12V?因為我用示波器看,感覺有反相的現象

小狐狸事務所 提到...

一般 Arduino 的 tx/rx 經過 PL2303 之類的轉換後直接插在 USB 接口即可.

Unknown 提到...

請問一下, ESP8266模組的RX/TX要接到 ARDUINO 上的pin 只能接到default的 D0跟D1嗎? 還是可自定義其他pin角當作 TX/RX來用呢? 因為想用一塊ARDUINO 接WIFI /BT & RFID reader, 怕ARDUINO上的RX/TX不夠用!

匿名 提到...

您好 感謝分享這麼棒的教學
有些問題想請教
我想實作出 從Arduino 跟ESP01用UART通訊,並用ESP01以及ESP8266WebServer.h函式庫 來顯示資料 例如溫濕度
首先想用電腦傳字串給ESP01 但是ESP好像不能用Serial.write()
於是我用了Serial.println(i); 並且電腦傳"1"給ESP01
if (Serial.available() > 0) {
i=Serial.read();
Serial.println(i);
}
結果回傳了
49
13
10
就是1\r\n的ASCII
請問我該如何ESP01輸出字串呢?

小狐狸事務所 提到...

您好, 我是使用 software serial 來連接 Arduino 與 ESP8266 的, 參考 :

http://yhhuang1966.blogspot.tw/2015/10/esp8266-wifi-arduino.html

Unknown 提到...

Tony大大!請教ㄧ下!我正在做用人機RS232控制arduino mega的serial0(RX,TX)再由同一台mega的serial2(TX2,RX2)控制變頻器轉速,mega得由232的從變成485的主、不知道這樣是否可行?目前分開控制都可以(人機對Mega,mega對變頻器)串起來時、從人機輸入數值就出現通訊錯誤、謝謝您!

小狐狸事務所 提到...

Dear Oscar,

Sorry, 我沒玩過 Mega, 您的意思是從 PC 透過 SERIAL0 對 MEGA 下指令, 然後利用 SERIAL2 去控制變頻器嗎? 這樣應該可以啊.

Unknown 提到...

請問若改為2塊板子以esp8266的WiFi互傳"Y"字,可以嗎?

小狐狸事務所 提到...

只要兩塊板子連上網路, 彼此可以互傳資料, 看是要用甚麼協定.

Unknown 提到...

不好意思,因對於網路通訊協定不懂。是否可以給予指導。我是以2塊wemos D1,想要以esp8266互傳,若其中一塊為AP,一塊為client。

Unknown 提到...

Tony大哥, 請教一下:

Arduino uno 與 arduino marco pro 板子在序列埠上有什麼不同嗎?

我從以下網址找了一個程式,是一個數學題目的解法
https://sites.google.com/site/csjhmaker/arduino-ji-chu/arduino-xue-xi-fang-fa

我覺的很奇怪的地方是我用「Arduino uno 」上傳程式成功後,可以在序列埠監控視窗得到計算結果
但是我用「arduino marco pro 」上傳程式成功後,序列埠監控視窗是空白沒有任何回應

可以請教這是為什麼嗎?設定問題?

程式碼
int i=1;
void setup() {
Serial.begin(9600);
for(long b=1 ; b<1000 ; b++){ //b=1~1000逐一代入
for(long m=1 ; m<19 ; m++){ //m=1~19 逐一代入
long delta = b*b-10*m ;
double ans = sqrt(delta); // sqrt() 為開根號的動作函式
if(ans-long(ans)==0){ // long()為取整數後存成長整數,原數-取整數=小數部分
Serial.print("i="); // i為第幾組滿除條件解的個數
Serial.print(i);
Serial.print(" , ");
Serial.print("b=");
Serial.print(b);
Serial.print(" , ");
Serial.print("c=");
Serial.print(2*m);
Serial.print(" , ");
Serial.print("delta=");
Serial.print(delta);
Serial.print(" , ");
Serial.print("ans=");
Serial.println(ans);
i++;
}}}}
void loop() {
}

Unknown 提到...

您好

想請教幾個問題

目前再做一個實驗電腦端用別的程式開發介面
透過usb to ttl 與arduino mini進行串列通訊
格式 8N1 鮑率115200

PC端需要大量傳輸
現在碰到兩個問題

1.PC端一次送18 Byte資料
在arduino端收到後會執行相對應資料內容的馬達轉動量

我將送固定18 Byte的資料做成一個按鈕
問題點在於
每當我按一次 馬達相對會轉動
但如果我連續點擊太快(大概是那種狂點連續20下按鈕) 一開始會根據我點的次數 做相對應轉動量
到某個程度 就怎麼點馬達也不會轉動
是因為點擊太快緩衝區塞滿,而接收端釋放速度不夠快造成資料遺失,
導致資料格式偏移,雙方協定跑掉 所以才不會轉動嗎。

2.上面是點擊一次送一次資料,第二個問題是 如果我一樣送18 byte資料但要在短時間內連續送200次以上,接收端應該怎麼處理,因為我發現mini的RAM只有2K,我目前是改用MEGA並把把緩衝區大小改成4096 Byte,接收端的部分有收到值應該會有相對應的轉動,造理說會連續轉200次不同的轉動量,但是我pc端連續送200次馬達卻都不會轉動。讀取函式我是用read造理說有讀取到緩衝區的資料就會拿出來部會造成RAM不夠的情況,況且18byte *200=3600byte RAM應該也還沒滿,煩請大師 給小弟一點意見,感激不盡。



小狐狸事務所 提到...

您好, 我沒有做過這樣的測試, 所以也不敢肯定是甚麼原因. 照理說 buffer 只要一被讀取就會清空, 但若傳得快 收得慢, 可能就會產生反應不如預期情況. 可以試試將兩邊 baud rate 調低為 9600bps 或更低, 看看有何不同.

Unknown 提到...

Tony大哥您好
小弟最近想要用語您同樣的UNO版做RS485 MODBUS RTU的通訊
請問一樣用板子上的TX,RX腳即可嗎? 還是需要另外買模組

另外問一下,如果我要將0~9999的數字分別寫入兩個控制器裡面(不同時,可能寫入不同數字)請問有可能嗎?
兩個控制器的站號部分要寫在什麼地方?

學藝不精還請海涵,感謝

小狐狸事務所 提到...

抱歉, RS485 我沒用過, 但書上看過是要買個 485 的模組, 通常是 MAX485 晶片. 參考 :

http://boywhy.blogspot.tw/2017/03/arduinors485.html

"將0~9999的數字分別寫入兩個控制器" 是誰要寫呢?

Unknown 提到...

Tony大哥您好
我目前預計用UNO為主站,對兩個副站(站號1,2)分別寫入一個開度的參數,數字在0~99.99%之間
比如說站號1的開度給50.00%
站號2給75.22%這樣

小狐狸事務所 提到...

我覺得可以參考這篇 :

Rs485 Serial Communication Between Arduino Mega and Arduino Pro Mini
http://www.microcontroller-project.com/rs485-communication-between-arduino-mega-and-arduino-pro-mini.html

小狐狸事務所 提到...

下面這篇也可參考 :

http://openopenblog.blogspot.tw/2016/10/arduino-modbus-rtu-via-rs485max485-ic.html

Unknown 提到...

請問arduino可以使用兩組rx tx 嗎
可以的話接腳10.11 跟另外那兩隻接腳?

小狐狸事務所 提到...

使用軟體序列埠即可, 可指定任何 DIO 腳當軟體 TX/RX

Unknown 提到...

文章寫的非常好,請問一下我RS-232 Protocol 傳送ASCII 是#AASUM2CR 及$C,DR,CH*SUM1CRLF
與接收ASCII 是>AA+00,000+00,000+00,000+00,000+00,000+00,000+00,000+00,000hhCR
這要如何編輯其程式呢?!目前接收不到資料 !!謝謝

小狐狸事務所 提到...

似乎是亂碼, 檢查 Baud rate 是否一致或接線是否不良.

Unknown 提到...

你好,那是輸出的資料! 請問一下我RS-232 Protocol 傳送ASCII 是#AASUM2CR 及$C,DR,CH*SUM1CRLF 要如何將該ASCII 傳出去呢?! 用serial.Print?! 另外就你文章提到使用Serial.available去收資料,我試過了,皆無法輸出資料,請問是否有建議程式嗎?!謝謝大師。 ˊ

Unknown 提到...

你好,該UARTt串列程式寫很久了,可否指導一下要如何寫?! 謝謝!!

小狐狸事務所 提到...

不太理解您的意思, 送出字元只要如上文寫入 UART 埠即可.

Unknown 提到...

您好,可否請教一下,我用analogread()讀取感測器輸出(0~1023),但電路加入一個button,使得button被按下後,輸出的讀值是零(如tare/zero)功能。可否請較程式段該如何架構!

小狐狸事務所 提到...

試試看這麼寫 (未測試) :

int sensor; //類比輸入讀取值
int button=2; //按鈕輸入
void setup() {
pinMode(button, INPUT_PULLUP); //啟用提升電阻
Serial.begin(9600);
}

void loop() {
boolean buttonState=digitalRead(button);
if (digitalRead(button)==HIGH) {
Serial.println(analogRead(A0));
}
else {Serial.println(buttonState);}
delay(1000); //每秒讀取一次
}

Unknown 提到...

非常感謝!

Unknown 提到...

您好,可否再請教:
void setup(){
Serial.begin(9600);
}

void loop(){
Int aa;
Int cc=analongRead(A0);
If (cc<100){

aa=cc;
Serial.println(aa);
} else {

Serial.println("NG");
}
delay(1000);
}



請教 當變數cc 值小於100 ,變數aa=cc
假設讀到cc值為 95,當下aa值也會等於95。
當下一秒讀到cc值為86,aa值也變為86。
又在下一秒aa值又被刷新.
我想請教有什麼方法或程式語法,
讓變數aa第一次讀到cc值後(小於100時),
就固定為第一次讀到的值95,
而不再讀下一秒及刷新數值。

Unknown 提到...

不好意思,在此補充,希望是變數aa 讀到第一次值後
就為固定值不要被刷新,而變數cc是analogRead(A0)需要一直讀值進來及刷新。

小狐狸事務所 提到...

Hi, 您可以試試看這樣寫 :

int aa, cc;
void setup(){
Serial.begin(9600);
aa=analongRead(A0)'
if (aa < 100) {Serial.println(aa);}
else {Serial.println("aa:NG");}
}

void loop(){
int cc=analongRead(A0);
If (cc < 100){Serial.println(cc);}
else {Serial.println("cc:NG");}
delay(1000);
}

小狐狸事務所 提到...

只要執行一次的就放在 setup() 中, 重複執行的放 loop() 裡

Unknown 提到...
作者已經移除這則留言。
阿倫巴巴 提到...

您好
一直被SERIAL_RX_BUFFER_SIZE困擾
不論是在標頭檔更改 #define SERIAL_RX_BUFFER_SIZE 512
或是在程式碼內定義
接收仍就維持64 byte

傳輸00~40 共80 byte,僅會輸出00~32 (64 byte)
請問我到底錯在哪呢 謝謝。

#define SERIAL_TX_BUFFER_SIZE 512
#define SERIAL_RX_BUFFER_SIZE 512
void setup()
{
Serial.begin(115200);

}
int i=0;
int Data_count=0;
char Data[300];

void loop()
{
if(Serial.available()> 0)
{
while (Serial.available() )
{
Data[Data_count]= Serial.read();
Data_count++;
delay(20);
}
Serial.println(Data_count); //查看收到資料數量
for(i=0;i<Data_count;i++){Serial.print(Data[i]);} //輸出
Data_count=0;
}

}

小狐狸事務所 提到...

您是否使用 Arduino IDE 1.6.1 或以前版本呢? 請改用 1.6.2 或之後的版本試試看.

Unknown 提到...

你好,
最近再做通訊的測試,如果兩塊Arduino nano用線對接,Arduino發送和接收內容都正確,但是當使用LORA433做無線傳輸時,收到的資料就會錯誤,後來沒有使用LORA433而直接Arduino發給PC的Com Port, UartAssist看接收的內容,一樣也是錯誤的,我用過Serial.print()和Serial.write()結果都一樣,不知道是那裡出問題?

發送端程式如下:

#include

String KeyinString;
String SendString;

const byte ROWS = 4;
const byte COLS = 4;

char hexaKeys[COLS][ROWS] = {
{'1','4','7','*'},
{'2','5','8','0'},
{'3','6','9','#'},
{'A','B','C','D'}
};

byte rowPins[ROWS] = {8, 7, 6, 5};
byte colPins[COLS] = {12, 11, 10, 9};

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

void setup()
{
Serial.begin(9600); //初始化COM Port
}


void loop()
{
char customKey = customKeypad.getKey();

if (customKey)
{
KeyinString = KeyinString + customKey;

if (customKey == 'A')
{
SendString = KeyinString.substring(0 , KeyinString.length()-1);

Serial.print(SendString);
KeyinString = "";
SendString = "";
}
}
}

接收端程式如下:

#include
#include

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

String RecMessage;

void setup()
{
Serial.begin(9600);
Serial.setTimeout(50);

lcd.begin(16, 2);

for(int i = 0; i < 3; i++)
{
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
}
lcd.backlight();

lcd.setCursor(0, 0);
lcd.print("<>");
delay(1000);

lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Form to TX MSG:");
}


void loop()
{
String RecMSG;
String DisplayMSG;
char RecChar;

lcd.setCursor(0, 1);
lcd.print("No Message!");

if (Serial.available() > 0)
{
RecMSG = Serial.readString();

DisplayMSG = RecMSG.substring(0 , RecMSG.length()-1);

Serial.println(DisplayMSG);

lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Form to TX MSG:");

lcd.setCursor(0, 1);
lcd.print("MSG:");

lcd.setCursor(4, 1);
lcd.print(DisplayMSG);

delay(5000);

}
}

小狐狸事務所 提到...

Hi, 其實我還沒時間對 LoRa 做仔細測試, 只做了初步的 SPI 介面測試, 參考 :

https://yhhuang1966.blogspot.com/2018/01/arduino-lora.html

nick 提到...

你好诶最近在玩 藍芽hm10 但我要arduino 每隔一段時間就發除一次at 指令
但不知道是板子問題還是藍芽模組問題 我舞法使我的at指令自動閥送並回傳顯示
(可能表達的不是很好 請見諒

東海林將司 提到...

我在arduino v1.8.13,試過了,下面兩行改成512 byte,要在您說的arduino-1.6.1\hardware\arduino\avr\cores\arduino\HardwareSerial.h,位置去修改,而不是在自己編輯的檔案上寫,不然牠會出現重複定義。

#define SERIAL_TX_BUFFER_SIZE 512
#define SERIAL_RX_BUFFER_SIZE 512

剛好我也遇到這個問題,感謝您的教學。

Unknown 提到...

時常拜讀您的文章, 有個應用想請教一下,
如果想用電腦透過 RS232 傳送文字檔給 Arduino 接收與處理, 有這方面的經驗或想法嗎? 感謝!
(想把設定參數直接寫在 TXT 檔案, 而不需要傳輸參數的每個字元)

小狐狸事務所 提到...

您的意思是把要 Arduino 做的事寫在 txt 檔, 然後一次傳給 Arduino 去執行是嗎? 似乎可行, 但您可能需將傳遞之參數 (操作碼) 做個對應編碼, Arduino 讀取後進行解碼取出參數後執行, 但 Arduino ATMEGA328P 的緩衝器只有 64 byte 而已, 指令編碼越精簡越好.

Unknown 提到...

是, 只是簡單的設定, 檔案內容舉例: UART1=ON, BAUD1=9600 開關/設定之類的
但目前針對從 RS232 接收與解析 TXT 檔案部分尚無概念...
(目前資訊大都是將資料存成 TXT 檔案於 SD 卡, 或讀取 SD 卡檔案)

或是先了解第一步, 如何從 RS232 接收 TXT 檔案並存在 SD 卡... Thanks.
PC 端部分, 目前是想用 Tera Term 的 File\Send file

小狐狸事務所 提到...

您是要用來設定 Arduino 本身的 Baud rate 嗎?

Unknown 提到...

不是耶, 主要是設定本身程式功能的參數開關,
目前是將 config.ini 文字檔案放在 SD 卡,
只是突然有個想法,
是否可以直接透過 RS232 讀取此設定檔來省去 SD 卡?
也有實做透過編碼將參數由 RS232 傳入,
缺點就是需要各別一個個傳入,
且不直觀, 需要編碼對照表來看相對應的參數設定...

Kestrel 提到...

Tony老師您好
我用ModbusRtu.h寫了一個程式,但是我沒辦法讀寫255以上的陣列元素,可以請老師幫我看一下錯在哪裡嗎?感謝
這是我用的modbus測試軟體
https://ebook.yourplc.net/modules/wfdownloads/singlefile.php?cid=11&lid=103

以下是我的程式碼,255以前的讀寫都很順利,超過就不行了TVT(我想要利用421元素來點亮LED)


#define ledPin 51
#include
// data array for modbus network sharing
uint16_t au16data[500] = {3, 1415, 9265, 4, 2, 7182, 28182, 8, 65535, 1, 0, 1, 0, 0, 1, -1 };
Modbus slave(1,Serial,0); // this is slave @1 and RS-232 or USB-FTDI
void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin( 19200, SERIAL_8E1 ); // 19200 baud, 8-bits, even, 1-bit stop
slave.start();
}
void loop() {
slave.poll( au16data,500);
switch(au16data[10])//HEX_0A
{
case 0:
{
int DMXCH_high_low = 2;
if(au16data[421] == 1)//HEX_1A5 (01 06 01 A5 00 01) (01 03 01 A5 00 01)
{
digitalWrite(ledPin, HIGH);
}
else if(au16data[421] == 0)
{
digitalWrite(ledPin, LOW);
}
else{
}
break;
}
case 1:
{
break;}

}
}

小狐狸事務所 提到...

Hi, Kestrel! 從您的描述來看, 應該跟您的程式碼無關, 有可能是 Arduino 串列埠 buffer size 的問題, 請問您使用哪一款 Arduino? 一般 UNO 好像預設是 64 個 byte, 如果使用 SoftwareSerial.h 的話試試看用 #define SERIAL_RX_BUFFER_SIZE 421 可不可以, 若是使用 HardwareSerial.h 則要去修改其中的 SERIAL_RX_BUFFER_SIZE 常數設定, 但這是我以前玩 Arduino 的印象, 因為我沒玩過 MODBUSRTU, 所以不知道它是否有自己的設定, 請見諒.

Kestrel 提到...

我解決這個問題了,感謝Tony老師٩( 'ω' )و

小狐狸事務所 提到...

請問原因是甚麼呢?