在給Arduino編程的時候,因為沒有調試工具,經常要通過使用串口通訊的方式調用Serial.print和Serial.println輸出Arduino運行過程中的相關信息,然后在電腦上用Arduino IDE的Serial Monitor來查看print出來的信息。Serial Monitor不僅可以接受Arduino發送到電腦的數據,還可以向Arduino發送數據,進行雙向通訊。但是這種通訊方式太過于簡陋,是純粹的手工方式,只適合調試。如果需要在電腦上通過可視化界面與Arduino進行交互,或者對Arduino發送到電腦上的數據進行處理,就需要在電腦上編程了。說的專業一點就是上位機與下位機的通訊。本文就介紹一下如何使用C#實現Arduino與電腦進行串行通訊。
1、C#串口編程基礎
在C#中有一個串口類System.IO.Ports.SerialPort,這個類的實例就對應設備管理器中的串口。
比如 SerialPort port = new SerialPort("COM4")
這句代碼就定義了一個串口實例,對應下圖中的USB Serial Port(COM4)

SerialPort常用方法包括Open, Close,?Read, ReadLine, Write, WriteLine。這些方法通過名稱就很容易理解它們的用法。
具體類信息可以參考MSDN:http://msdn.microsoft.com/zh-cn/library/vstudio/System.IO.Ports.SerialPort(v=vs.100).aspx
2、Arduino串口編程基礎
Arduino中的Serial和C#的SerialPort用法類似,有available, begin,?read, readBytes,?write, print, println,從名稱上也很容易理解。具體用法可以參考官方文檔:http://arduino.cc/en/Reference/Serial
一般我們會在Arduino代碼的setup方法中添加Serial.begin(9600),然后在serialEvent方法中讀取接收到的數據。
3、實例
實例的場景為:
1、Arduino上接一個光線傳感器,通過模擬口周期性讀取亮度值。
2、在電腦上向Arduino發送一個開始發送數據的命令后,點亮Arduino上13號數字口的LED,然后Arduino通過串口向電腦發送亮度值。
3、在電腦上向Arduino發送一個停止發送數據的命令后,關閉Arduino上13號數字口的LED,然后Arduino停止通過串口向電腦發送亮度值。
這個場景包含了Arduino和電腦的雙向通訊。
示例采用WinForm,界面如下:
“串口列表”中自動加載電腦上的可用串口名稱。
點擊“開始讀取”按鈕,根據選擇的串口名稱實例化一個串口對象,指定串口的DataReceived事件處理方法。然后調用ChangeArduinoSendStatus方法向Arduino發送“serial start”命令。
點擊“停止讀取”按鈕,向Arduino發送“serial stop”命令,關閉串口并銷毀實例。
點擊“開始發送”或“停止發送”按鈕,調用ChangeArduinoSendStatus方法向Arduino發送“serial start”或“serial stop”命令,讓Arduino開始通過串口向電腦發送數據或停止向電腦發送數據。
串口在接收到數據后出發DataReceived事件,在事件處理方法中調用RefreshInfoTextBox方法,讀取串口的數據并追加到界面的文本框。注意:串口的DataReceived事件是由后臺線程執行,要把讀取到的數據顯示在WinFrom界面,需要使用控件的Invoke方法才能刷新界面。
C#核心代碼如下:
private SerialPort port = null;
/// <summary>
/// 初始化串口實例
/// </summary>
private void InitialSerialPort()
{
????try
????{
??????? string portName = this.cmbSerials.SelectedItem.ToString();
????????port = new SerialPort(portName, 9600);
??????? port.Encoding = Encoding.ASCII;
??????? port.DataReceived += port_DataReceived;
????????port.Open();
????????this.ChangeArduinoSendStatus(true);
????}
????catch (Exception ex)
????{
????????MessageBox.Show("初始化串口發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????}
}
?
/// <summary>
/// 關閉并銷毀串口實例
/// </summary>
private void DisposeSerialPort()
{
????if (port != null)
????{
????????try
????????{
????????????this.ChangeArduinoSendStatus(false);
????????????if (port.IsOpen)
????????????{
????????????????port.Close();
????????????}
????????????port.Dispose();
????????}
????????catch (Exception ex)
????????{
????????????MessageBox.Show("關閉串口發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????????}
????}
}
?
/// <summary>
/// 改變Arduino串口的發送狀態
/// </summary>
/// <param name="allowSend">是否允許發送數據</param>
private void ChangeArduinoSendStatus(bool allowSend)
{
????if (port != null && port.IsOpen)
????{
????????if (allowSend)
????????{
????????????port.WriteLine("serial start");
????????}
????????else
????????{
????????????port.WriteLine("serial stop");
????????}
????}
}
?
/// <summary>
/// 從串口讀取數據并轉換為字符串形式
/// </summary>
/// <returns></returns>
private string ReadSerialData()
{
????string value = "";
????try
????{
????????if (port != null && port.BytesToRead > 0)
????????{
????????????value = port.ReadExisting();
????????}
????}
????catch (Exception ex)
????{
????????MessageBox.Show("讀取串口數據發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????}
?
????return value;
}
?
/// <summary>
/// 在讀取到數據時刷新文本框的信息
/// </summary>
private void RefreshInfoTextBox()
{
????string value = this.ReadSerialData();
????Action<string> setValueAction = text => this.txtInfo.Text += text;
?
????if (this.txtInfo.InvokeRequired)
????{
????????this.txtInfo.Invoke(setValueAction, value);
????}
????else
????{
????????setValueAction(value);
????}
}
/// <summary>
/// 初始化串口實例
/// </summary>
private void InitialSerialPort()
{
????try
????{
??????? string portName = this.cmbSerials.SelectedItem.ToString();
????????port = new SerialPort(portName, 9600);
??????? port.Encoding = Encoding.ASCII;
??????? port.DataReceived += port_DataReceived;
????????port.Open();
????????this.ChangeArduinoSendStatus(true);
????}
????catch (Exception ex)
????{
????????MessageBox.Show("初始化串口發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????}
}
?
/// <summary>
/// 關閉并銷毀串口實例
/// </summary>
private void DisposeSerialPort()
{
????if (port != null)
????{
????????try
????????{
????????????this.ChangeArduinoSendStatus(false);
????????????if (port.IsOpen)
????????????{
????????????????port.Close();
????????????}
????????????port.Dispose();
????????}
????????catch (Exception ex)
????????{
????????????MessageBox.Show("關閉串口發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????????}
????}
}
?
/// <summary>
/// 改變Arduino串口的發送狀態
/// </summary>
/// <param name="allowSend">是否允許發送數據</param>
private void ChangeArduinoSendStatus(bool allowSend)
{
????if (port != null && port.IsOpen)
????{
????????if (allowSend)
????????{
????????????port.WriteLine("serial start");
????????}
????????else
????????{
????????????port.WriteLine("serial stop");
????????}
????}
}
?
/// <summary>
/// 從串口讀取數據并轉換為字符串形式
/// </summary>
/// <returns></returns>
private string ReadSerialData()
{
????string value = "";
????try
????{
????????if (port != null && port.BytesToRead > 0)
????????{
????????????value = port.ReadExisting();
????????}
????}
????catch (Exception ex)
????{
????????MessageBox.Show("讀取串口數據發生錯誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
????}
?
????return value;
}
?
/// <summary>
/// 在讀取到數據時刷新文本框的信息
/// </summary>
private void RefreshInfoTextBox()
{
????string value = this.ReadSerialData();
????Action<string> setValueAction = text => this.txtInfo.Text += text;
?
????if (this.txtInfo.InvokeRequired)
????{
????????this.txtInfo.Invoke(setValueAction, value);
????}
????else
????{
????????setValueAction(value);
????}
}
Arduino代碼
代碼注釋很詳細,就不再做解釋。
int pinLed = 13;//定義連接LED的數字口,當允許通過串口發送數據時,點亮LED,否則關閉LED
boolean sendFlag = false;//指示是否允許通過串口發送數據
boolean sendFlag = false;//指示是否允許通過串口發送數據
boolean readCompleted = false;//指示是否完成讀取串口數據
String serialString = "";//串口數據緩存字符串
String serialString = "";//串口數據緩存字符串
//Author:Alex Leo, Email:conexpress@qq.com, Blog:http://conexpress.cnblogs.com/
//參考:http://arduino.cc/en/Reference/Serial
void setup()
{
??pinMode(pinLed,OUTPUT);
??Serial.begin(9600);
??serialString.reserve(200);//初始化字符串
}
?
void loop()
{
??int lightValue = analogRead(A0);//從A0口讀取光線傳感器的值
??if(readCompleted)//判斷串口是否接收到數據并完成讀取
??{
????Serial.print("read value:");
????Serial.println(serialString);//將讀取到的信息發送給電腦
????if(serialString == "serial start")//當讀取到的信息是"serial start"時,設置發送標志設置為true
????{
??????sendFlag = true;
????}
????else if(serialString == "serial stop")//當讀取到的信息是"serial stop"時,設置發送標志設置為false
????{
??????sendFlag = false;
????}
????serialString = "";
//參考:http://arduino.cc/en/Reference/Serial
void setup()
{
??pinMode(pinLed,OUTPUT);
??Serial.begin(9600);
??serialString.reserve(200);//初始化字符串
}
?
void loop()
{
??int lightValue = analogRead(A0);//從A0口讀取光線傳感器的值
??if(readCompleted)//判斷串口是否接收到數據并完成讀取
??{
????Serial.print("read value:");
????Serial.println(serialString);//將讀取到的信息發送給電腦
????if(serialString == "serial start")//當讀取到的信息是"serial start"時,設置發送標志設置為true
????{
??????sendFlag = true;
????}
????else if(serialString == "serial stop")//當讀取到的信息是"serial stop"時,設置發送標志設置為false
????{
??????sendFlag = false;
????}
????serialString = "";
readCompleted = false;
??}
?
??if(sendFlag)//如果允許通過串口發送數據,則點亮LED并發送數據,否則關閉LED
??}
?
??if(sendFlag)//如果允許通過串口發送數據,則點亮LED并發送數據,否則關閉LED
{
????digitalWrite(pinLed, HIGH);
????Serial.print("light value:");
????Serial.println(lightValue);
??}
??else
??{
????digitalWrite(pinLed, LOW);
??}
??delay(1000);//延時1000ms
}
?
void serialEvent()//串口事件處理方法,參考:http://arduino.cc/en/Tutorial/SerialEvent
{
??while(Serial.available())//參考://arduino.cc/en/Serial/Available
??{
????char inChar = (char)Serial.read();
????if(inChar != '\n')//以換行符作為讀取結束標志
????{
??????serialString += inChar;
????}
????else
????{
??????readCompleted = true;
????}
??}
}
????digitalWrite(pinLed, HIGH);
????Serial.print("light value:");
????Serial.println(lightValue);
??}
??else
??{
????digitalWrite(pinLed, LOW);
??}
??delay(1000);//延時1000ms
}
?
void serialEvent()//串口事件處理方法,參考:http://arduino.cc/en/Tutorial/SerialEvent
{
??while(Serial.available())//參考://arduino.cc/en/Serial/Available
??{
????char inChar = (char)Serial.read();
????if(inChar != '\n')//以換行符作為讀取結束標志
????{
??????serialString += inChar;
????}
????else
????{
??????readCompleted = true;
????}
??}
}
完整代碼下載:http://files.cnblogs.com/files/conexpress/Arduino%E5%92%8CCSharp%E9%80%9A%E8%AE%AF.rar
來自為知筆記(Wiz)