前言
前一篇文章講了利用Arduino mega2560來設計上下位機串口通信系統的下位機,這一章則要講如何設計上位機界面軟件,這裏我們先選用了VS2013自帶的Visual C#。這是微軟公司開發的C#編程語言規格之集成開發環境使用者接口,可以幫助開發者快速地設計出一款適合自己的界面軟件。
創建窗體應用程序
- 首先打開VS2013,選擇工具欄中的"文件"->“新建”->“項目”->“Visual C#”->“Windows桌面”->“Windows窗體應用程序”,重新命名後,點擊確定就可以了。
- 生成項目文件之後,最主要的是Form1.cs文件和Form1.cs[設計],其中前者是代碼編寫的區域,已經定義了公有類Form1(也可以根據需要修改類名),我們後續只需要在這個類下加進去自己需要的按鈕操作;後者是界面的圖形設計,可以從左側工具箱中選擇公共控件,加入到界面當中去,包括Button、CheckBox等一系列操作。其他的操作項目框架已經幫我們做好了,我們只需要設計這兩個文件即可。
創建一個串口
- 首先我們在Form1.cs[設計]裏選擇左邊工具箱內的ComboBox,拖動到圖形界面上,並且在前面給它一個Label叫“串口選擇”,點擊這個ComboBox的屬性,找到"設計"->(Name),將這個ComboBox命名爲“SerialChoose”,之後在代碼區就可以用“SerialChoose”來指代這個ComboBox,這一步的作用是爲了生成界面後選擇插入到PC上不同的串口;接着同樣做一個波特率的ComboBox,命名爲"BaudRate";最後再從工具箱內選擇一個Button按鈕,給它命名爲"SerialSwitch",並在屬性->“外觀”->“Text”,給這個Button賦上"串口打開"字符。
- 先在代碼區最上面引用Ports類
using System.IO.Ports;
接着在代碼區public Form1()這個函數裏編寫串口相關代碼,因爲串口操作需要界面軟件初始化就開始使用,所以直接將這部分代碼寫在InitializeComponent()之後即可。當然如果有需求的,可以在打開串口操作成功後加上狀態按鈕,顯示更加清楚。
namespace SimpleHostPrint
{
public partial class BOTDR : Form
{
//通過System.IO.Ports實例化一個串口對象s,這一步要定義爲全局變量,後面各個函數才能使用
SerialPort ss = new SerialPort();
public BOTDR() //這裏我把圖形界面名Form1改名成了自己需要的名字,相應的類名也會跟着變化
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false; //放置跨線程訪問出錯
SerialSwitch.Text = "打開串口"; // "SerialSwitch"Button的初始狀態
//定義一個Item數組,遍歷item中每一個波特率變量a,增加到"BaudRate"ComboBox的列表中
int[] item = { 9600, 115200 };
foreach (int a in item)
{
BaudRate.Items.Add(a.ToString());
}
BaudRate.SelectedItem = BaudRate.Items[0]; //默認爲列表第1個變量
}
//這個函數界面軟件啓動時會載入
private void Form1_Load(object sender, EventArgs e)
{
//獲取當前的所有的串口名字,存入字符串數組
String[] ports = SerialPort.GetPortNames();
//把這個串口名字符串數組全都加載進"SerialChoose"ComboBox裏
SerialChoose.Items.AddRange(ports);
//"SerialChoose"ComboBox默認選擇第一個串口名
SerialChoose.SelectedItem = SerialChoose.Items[0];
}
//"SerialSwitch"Button對應的代碼,點擊該Button即觸發此段代碼
private void SerialSwitch_Click(object sender, EventArgs e)
{
try
{
if (!ss.IsOpen) //檢查串口是否打開
{
//將選擇的串口名賦給實例化的串口ss
ss.PortName = SerialChoose.SelectedItem.ToString();
//將選擇的波特率賦給實例化的串口ss
ss.BaudRate = Convert.ToInt32(BaudRate.SelectedItem.ToString());
//串口打開
ss.Open();
//串口接收數據的函數
ss.DataReceived += ss_DataReceived;
SerialSwitch.Text = "關閉串口";
}
else
{
ss.Close();
ss.DataReceived -= ss_DataReceived;
SerialSwitch.Text = "打開串口";
}
}
catch (Exception ee)
{
MessageBox.Show(ee.ToString());
}
}
//異或校驗函數,用於命令字當中
private byte CalBIP8(byte[] data)
{
byte bip16 = Convert.ToByte(data[0] ^ (data[1] & 0x00) ^ data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[6] ^ data[7]);
byte bip8 = Convert.ToByte(((bip16 & 0xf0) >> 4) ^ (bip16 & 0x0f));
return bip8;
}
}
}
界面軟件啓動後串口的初始狀態:
選擇好串口名和波特率,點擊"打開串口"按鈕後的狀態:
串口發送數據
- 我這裏串口發送數據是由界面發送參數下來,再把數據幀通過串口發送到下位機。以光開關爲例,這裏我加了"SetChannelNumber"和"ChannelNumber"兩個TextBox,分別用來設置光通道號和顯示光通道號,同時加了"OpticalSwitchSetting" Button來觸發設置。將數據輸入到"SetChannelNumber" TextBox裏,點擊"設置"Button,即可觸發相應代碼,發送數據幀到下位機。下位機設置好之後,將當前通道號的參數返回給上位機,上位機串口讀取後顯示在"ChannelNumber" TextBox裏。"設置"Button對應的代碼如下所示。
private void OpticalSwitchSetting_Click(object sender, EventArgs e) //設置光開關通道號
{
if (ss.IsOpen)
{
if (SetChannelNumber.Text.Trim() != "") //當輸入框有數據輸入時
{
//自定義的帶有命令字和數值的數據幀
byte [] txdata = {0xfb, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xfe};
//將設置的光開關通道號數值寫入數據幀對應位置
txdata[4] = Convert.ToByte(SetChannelNumber.Text.Trim());
//生成異或校驗碼
txdata[1] = CalBIP8(txdata);
int IsCompleted = 0;
for(int i=0;i<8;i++)
{
//串口發送函數
ss.Write(txdata, i, 1);
IsCompleted += 1;
}
//驗證發送的字節數
if (IsCompleted == txdata.Length)
{
MessageBox.Show("下發數據成功");
}
else
{
MessageBox.Show("下發數據失敗"); return;
}
}
else
{
MessageBox.Show("未設置通道號"); return;
}
}
else
{
MessageBox.Show("串口未打開"); return;
}
}
串口接收數據
- C#窗體應用程序也提供了串口接收函數,可以自動監聽串口,獲取串口緩衝區內的數據,我們要做的就是編寫數據處理部分的代碼。這個串口接收函數在串口打開後,可由實例化的串口ss類辦法實現,語句就是SerialSwitch_Click()裏所寫的,
ss.DataReceived += ss_DataReceived;
寫到ss.DataReceoved += 時,可以根據提示按“TAB”鍵,自動鏈接串口接收函數 ss_DataReceived(),即ss這個串口的數據接收應當由函數ss_DataReceived()來處理,剩下的就是編寫這個函數內的代碼,如下所示。
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//讀取串口緩衝區的數據長度
int count = ss.BytesToRead;
string str = null;
//將串口緩衝區的數據全部存入buff數組
byte[] buff = new byte[count];
ss.Read(buff, 0, count);
//這部分代碼是做了一個RichTextBox並以十六進制顯示,並提供清除數據功能
foreach (byte item in buff)
{
//讀取buff中存的數據,轉換成顯示的十六進制數
if (HEX2.Checked)
{
str += item.ToString("X2") + " ";
}
else
{
str += item.ToString() + " ";
//Convert.ToByte(buff[0]);
}
}
//這是跨線程訪問RichTextBox,原程序和DataReceived事件是兩個不同的線程同時在執行
ReceivedDataTextBox.Text += System.DateTime.Now.ToString() + ": " + Encoding.ASCII.GetString(buff,0,1) + str.Remove(0, 2) + "\n";
//統計接收的數據字節數
ReceivedDataCount.Invoke(new MethodInvoker(delegate
{ ReceivedDataCount.Text = (int.Parse(ReceivedDataCount.Text) + count).ToString(); }));
//返回的數據幀自定義,根據各自需要自行處理
if (buff[0] == 'D')
{
if (buff[1] == OpticalSwitch)
{
//"ChannelNumber" TextBox裏顯示收到的參數值
ChannelNumber.Text = buff[2].ToString();
}
}
else if (buff[0] == 'S')
{
MessageBox.Show("下位機設置成功!");
}
else if (buff[0] == 'W')
{
MessageBox.Show("警告!模塊電流或溫度超過閾值!");
}
else if (buff[0] == 'E')
{
MessageBox.Show("模塊設置錯誤!");
}
}
//點擊"清除接收區"Button按鈕清空串口接收緩衝區的數據
private void ClearReceivedData_Click(object sender, EventArgs e)
{
ReceivedDataTextBox.Clear();
ReceivedDataCount.Invoke(new MethodInvoker(delegate { ReceivedDataCount.Text = "0"; }));
}
運行程序
當所有的工作做完之後,點擊工具欄的"啓動"按鈕,排除錯誤和報警後,即可運行一個Debug下的界面程序(也可以在配置管理器裏選擇生成Release版本的),在"設置通道號"裏輸入數值3,整個上下位機串口通信系統運行成功後,顯示結果如下: