【Arduino】使用C#實(shí)現(xiàn)Arduino與電腦進(jìn)行串行通訊
在給Arduino編程的時(shí)候,因?yàn)闆]有調(diào)試工具,經(jīng)常要通過使用串口通訊的方式調(diào)用Serial.print和Serial.println輸出Arduino運(yùn)行過程中的相關(guān)信息,然后在電腦上用Arduino IDE的Serial Monitor來查看print出來的信息。Serial Monitor不僅可以接受Arduino發(fā)送到電腦的數(shù)據(jù),還可以向Arduino發(fā)送數(shù)據(jù),進(jìn)行雙向通訊。但是這種通訊方式太過于簡陋,是純粹的手工方式,只適合調(diào)試。如果需要在電腦上通過可視化界面與Arduino進(jìn)行交互,或者對Arduino發(fā)送到電腦上的數(shù)據(jù)進(jìn)行處理,就需要在電腦上編程了。說的專業(yè)一點(diǎn)就是上位機(jī)與下位機(jī)的通訊。本文就介紹一下如何使用C#實(shí)現(xiàn)Arduino與電腦進(jìn)行串行通訊。
1、C#串口編程基礎(chǔ)
在C#中有一個(gè)串口類System.IO.Ports.SerialPort,這個(gè)類的實(shí)例就對應(yīng)設(shè)備管理器中的串口。
比如 SerialPort port = new SerialPort("COM4")
這句代碼就定義了一個(gè)串口實(shí)例,對應(yīng)下圖中的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串口編程基礎(chǔ)
Arduino中的Serial和C#的SerialPort用法類似,有available, begin, read, readBytes, write, print, println,從名稱上也很容易理解。具體用法可以參考官方文檔:http://arduino.cc/en/Reference/Serial
一般我們會在Arduino代碼的setup方法中添加Serial.begin(9600),然后在serialEvent方法中讀取接收到的數(shù)據(jù)。
3、實(shí)例
實(shí)例的場景為:
1、Arduino上接一個(gè)光線傳感器,通過模擬口周期性讀取亮度值。
2、在電腦上向Arduino發(fā)送一個(gè)開始發(fā)送數(shù)據(jù)的命令后,點(diǎn)亮Arduino上13號數(shù)字口的LED,然后Arduino通過串口向電腦發(fā)送亮度值。
3、在電腦上向Arduino發(fā)送一個(gè)停止發(fā)送數(shù)據(jù)的命令后,關(guān)閉Arduino上13號數(shù)字口的LED,然后Arduino停止通過串口向電腦發(fā)送亮度值。
這個(gè)場景包含了Arduino和電腦的雙向通訊。
示例采用WinForm,界面如下:

“串口列表”中自動加載電腦上的可用串口名稱。
點(diǎn)擊“開始讀取”按鈕,根據(jù)選擇的串口名稱實(shí)例化一個(gè)串口對象,指定串口的DataReceived事件處理方法。然后調(diào)用ChangeArduinoSendStatus方法向Arduino發(fā)送“serial start”命令。
點(diǎn)擊“停止讀取”按鈕,向Arduino發(fā)送“serial stop”命令,關(guān)閉串口并銷毀實(shí)例。
點(diǎn)擊“開始發(fā)送”或“停止發(fā)送”按鈕,調(diào)用ChangeArduinoSendStatus方法向Arduino發(fā)送“serial start”或“serial stop”命令,讓Arduino開始通過串口向電腦發(fā)送數(shù)據(jù)或停止向電腦發(fā)送數(shù)據(jù)。
串口在接收到數(shù)據(jù)后出發(fā)DataReceived事件,在事件處理方法中調(diào)用RefreshInfoTextBox方法,讀取串口的數(shù)據(jù)并追加到界面的文本框。注意:串口的DataReceived事件是由后臺線程執(zhí)行,要把讀取到的數(shù)據(jù)顯示在WinFrom界面,需要使用控件的Invoke方法才能刷新界面。
C#核心代碼如下:
private SerialPort port = null;
/// <summary>
/// 初始化串口實(shí)例
/// </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("初始化串口發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
/// <summary>
/// 關(guān)閉并銷毀串口實(shí)例
/// </summary>
private void DisposeSerialPort()
{
if (port != null)
{
try
{
this.ChangeArduinoSendStatus(false);
if (port.IsOpen)
{
port.Close();
}
port.Dispose();
}
catch (Exception ex)
{
MessageBox.Show("關(guān)閉串口發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
/// <summary>
/// 改變Arduino串口的發(fā)送狀態(tài)
/// </summary>
/// <param name="allowSend">是否允許發(fā)送數(shù)據(jù)</param>
private void ChangeArduinoSendStatus(bool allowSend)
{
if (port != null && port.IsOpen)
{
if (allowSend)
{
port.WriteLine("serial start");
}
else
{
port.WriteLine("serial stop");
}
}
}
/// <summary>
/// 從串口讀取數(shù)據(jù)并轉(zhuǎn)換為字符串形式
/// </summary>
/// <returns></returns>
private string ReadSerialData()
{
string value = "";
try
{
if (port != null && port.BytesToRead > 0)
{
value = port.ReadExisting();
}
}
catch (Exception ex)
{
MessageBox.Show("讀取串口數(shù)據(jù)發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return value;
}
/// <summary>
/// 在讀取到數(shù)據(jù)時(shí)刷新文本框的信息
/// </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>
/// 初始化串口實(shí)例
/// </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("初始化串口發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
/// <summary>
/// 關(guān)閉并銷毀串口實(shí)例
/// </summary>
private void DisposeSerialPort()
{
if (port != null)
{
try
{
this.ChangeArduinoSendStatus(false);
if (port.IsOpen)
{
port.Close();
}
port.Dispose();
}
catch (Exception ex)
{
MessageBox.Show("關(guān)閉串口發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
/// <summary>
/// 改變Arduino串口的發(fā)送狀態(tài)
/// </summary>
/// <param name="allowSend">是否允許發(fā)送數(shù)據(jù)</param>
private void ChangeArduinoSendStatus(bool allowSend)
{
if (port != null && port.IsOpen)
{
if (allowSend)
{
port.WriteLine("serial start");
}
else
{
port.WriteLine("serial stop");
}
}
}
/// <summary>
/// 從串口讀取數(shù)據(jù)并轉(zhuǎn)換為字符串形式
/// </summary>
/// <returns></returns>
private string ReadSerialData()
{
string value = "";
try
{
if (port != null && port.BytesToRead > 0)
{
value = port.ReadExisting();
}
}
catch (Exception ex)
{
MessageBox.Show("讀取串口數(shù)據(jù)發(fā)生錯(cuò)誤:" + ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return value;
}
/// <summary>
/// 在讀取到數(shù)據(jù)時(shí)刷新文本框的信息
/// </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代碼
代碼注釋很詳細(xì),就不再做解釋。
int pinLed = 13;//定義連接LED的數(shù)字口,當(dāng)允許通過串口發(fā)送數(shù)據(jù)時(shí),點(diǎn)亮LED,否則關(guān)閉LED
boolean sendFlag = false;//指示是否允許通過串口發(fā)送數(shù)據(jù)
boolean sendFlag = false;//指示是否允許通過串口發(fā)送數(shù)據(jù)
boolean readCompleted = false;//指示是否完成讀取串口數(shù)據(jù)
String serialString = "";//串口數(shù)據(jù)緩存字符串
String serialString = "";//串口數(shù)據(jù)緩存字符串
//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)//判斷串口是否接收到數(shù)據(jù)并完成讀取
{
Serial.print("read value:");
Serial.println(serialString);//將讀取到的信息發(fā)送給電腦
if(serialString == "serial start")//當(dāng)讀取到的信息是"serial start"時(shí),設(shè)置發(fā)送標(biāo)志設(shè)置為true
{
sendFlag = true;
}
else if(serialString == "serial stop")//當(dāng)讀取到的信息是"serial stop"時(shí),設(shè)置發(fā)送標(biāo)志設(shè)置為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)//判斷串口是否接收到數(shù)據(jù)并完成讀取
{
Serial.print("read value:");
Serial.println(serialString);//將讀取到的信息發(fā)送給電腦
if(serialString == "serial start")//當(dāng)讀取到的信息是"serial start"時(shí),設(shè)置發(fā)送標(biāo)志設(shè)置為true
{
sendFlag = true;
}
else if(serialString == "serial stop")//當(dāng)讀取到的信息是"serial stop"時(shí),設(shè)置發(fā)送標(biāo)志設(shè)置為false
{
sendFlag = false;
}
serialString = "";
readCompleted = false;
}
if(sendFlag)//如果允許通過串口發(fā)送數(shù)據(jù),則點(diǎn)亮LED并發(fā)送數(shù)據(jù),否則關(guān)閉LED
}
if(sendFlag)//如果允許通過串口發(fā)送數(shù)據(jù),則點(diǎn)亮LED并發(fā)送數(shù)據(jù),否則關(guān)閉LED
{
digitalWrite(pinLed, HIGH);
Serial.print("light value:");
Serial.println(lightValue);
}
else
{
digitalWrite(pinLed, LOW);
}
delay(1000);//延時(shí)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')//以換行符作為讀取結(jié)束標(biāo)志
{
serialString += inChar;
}
else
{
readCompleted = true;
}
}
}
digitalWrite(pinLed, HIGH);
Serial.print("light value:");
Serial.println(lightValue);
}
else
{
digitalWrite(pinLed, LOW);
}
delay(1000);//延時(shí)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')//以換行符作為讀取結(jié)束標(biāo)志
{
serialString += inChar;
}
else
{
readCompleted = true;
}
}
}
Author:Alex Leo
Email:conexpress@qq.com
Blog:http://conexpress.cnblogs.com/

浙公網(wǎng)安備 33010602011771號