第一次接觸上位機(jī)的開發(fā),單純是為了玩一下,淺度學(xué)習(xí),參考了一下其他文章,做了一個(gè)簡(jiǎn)單的串口助手,算是邁出了第一步。寫博客記錄一下學(xué)習(xí)的過(guò)程。
一、新建項(xiàng)目
第1步:創(chuàng)建一個(gè)Window窗體應(yīng)用(.NET Framework)
我安裝的是vs2019版本。
第2步:配置項(xiàng)目
提示:框架要選.NET Framework 4以上,如果沒(méi)有,先確認(rèn)項(xiàng)目選的是不是Window窗體應(yīng)用,再確認(rèn)是否安裝NET Framework。
二、控件布局
我們先利用控件把串口助手的界面搭建出來(lái)。
提示:我們用到的控件都在工具箱里面。
我這里主要用了以下幾個(gè)控件:
提示:控件名稱是一個(gè)比較關(guān)鍵的參數(shù),因?yàn)楹竺娴拇a要根據(jù)名稱來(lái)寫。
控件類型 | 控件名稱 | Text | 說(shuō)明 |
---|---|---|---|
TextBox | TextBox1 | TextBox1 | 接收顯示窗口 |
TextBox | TextBox2 | TextBox2 | 發(fā)送輸入窗口 |
label | label1 | 端口號(hào) | 文本提示 |
label | label2 | 波特率 | 文本提示 |
comboBox | comboBox1 | comboBox1 | 端口號(hào)下拉菜單 |
comboBox | comboBox2 | comboBox2 | 波特率下拉菜單 |
button | button1 | 打開串口 | 打開串口按鍵 |
button | button2 | 清除接收 | 清除接收按鍵 |
button | button3 | 發(fā)送 | 發(fā)送按鍵 |
checkBox | checkBox1 | hex發(fā)送 | 切換發(fā)送格式 |
checkBox | checkBox2 | hex接收 | 切換接收格式 |
serialPort | serialPort1 | serialPort1 | 串口通信控件 |
Time | Tiime1 | Tiime1 | 定時(shí)器,用于定時(shí)刷新端口 |
先把控件從工具箱里面拉出來(lái),調(diào)整好大小和布局。
提示:TextBox要自由調(diào)整窗口大小的話需要把屬性里面的MultiLine設(shè)置為True。
修改控件屬性里面的Text,串口助手的界面就出來(lái)了。
再添加serialPort和Time控件,這兩個(gè)是隱藏的控件,在窗口下方,實(shí)際運(yùn)行的時(shí)候是看不見(jiàn)的。
選擇波特率對(duì)應(yīng)的comboBox控件,在Items屬性里面添加常用的波特率。
三、編寫程序
提示:可以雙擊控件窗口打開代碼,也可以選中From,右鍵,選中查看代碼
在實(shí)際應(yīng)用中,最常用到的代碼在Form1.cs和Form1.Designer.cs兩個(gè)文件中。
注:Form1是新建窗體默認(rèn)的名稱,實(shí)際使用也可能不是這個(gè)名字。
Form1.cs是應(yīng)用部分的代碼,也就是我后面要編寫的代碼所在的文件。
Form1.Designer.cs里面存放各種控件的參數(shù)定義,在我們雙擊某一個(gè)控件的時(shí)候,實(shí)際上這個(gè)文件會(huì)在相應(yīng)的生成一行代碼,意思是在這個(gè)控件里面添加一個(gè)事件。
1、端口更新函數(shù)
這個(gè)函數(shù)是自定義的,需要自己添加進(jìn)去
/* 新增自定義函數(shù):更新可用串口 */
private void Updata_Serialport_Name(ComboBox MycomboBox)
{
string[] ArryPort; // 定義字符串?dāng)?shù)組,數(shù)組名為 ArryPort
ArryPort = SerialPort.GetPortNames(); // SerialPort.GetPortNames()函數(shù)功能為獲取計(jì)算機(jī)所有可用串口,以字符串?dāng)?shù)組形式輸出
MycomboBox.Items.Clear(); // 清除當(dāng)前組合框下拉菜單內(nèi)容
for (int i = 0; i < ArryPort.Length; i++)
{
MycomboBox.Items.Add(ArryPort[i]); // 將所有的可用串口號(hào)添加到端口對(duì)應(yīng)的組合框中
}
}
引用命名空間System.IO.Ports:
因?yàn)樯厦嬲{(diào)用了SerialPort.GetPortNames()函數(shù),需要引用這個(gè)命名空間才能使用
using System.IO.Ports;
2、啟動(dòng)窗口加載函數(shù)
雙擊設(shè)計(jì)界面窗口的空白區(qū)域,會(huì)自動(dòng)生成一個(gè)Form1_Load空函數(shù)。
在Form1_Load函數(shù)里面添加以下代碼:
Updata_Serialport_Name(comboBox1); // 調(diào)用更新可用串口函數(shù),comboBox1為端口號(hào)組合框的名稱
在默認(rèn)啟動(dòng)函數(shù)里添加以下代碼:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//設(shè)置該屬性 為false
提示:函數(shù)名不一定是Form1,這個(gè)函數(shù)名和自己的使用的窗體名稱是一致的
3、"打開串口"按鍵回調(diào)函數(shù)
雙擊“打開串口”按鍵,會(huì)自動(dòng)生成一個(gè)空函數(shù)。
在函數(shù)里面添加以下代碼:
if (button1.Text == "打開串口") // 如果當(dāng)前是串口設(shè)備是關(guān)閉狀態(tài)
{
try // try 是嘗試部分,如果嘗試過(guò)程中出現(xiàn)問(wèn)題,進(jìn)入catch部分,執(zhí)行錯(cuò)誤處理代碼
{
serialPort1.PortName = comboBox1.Text; // 將串口設(shè)備的串口號(hào)屬性設(shè)置為comboBox1復(fù)選框中選擇的串口號(hào)
serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text); // 將串口設(shè)備的波特率屬性設(shè)置為comboBox2復(fù)選框中選擇的波特率
serialPort1.Open(); // 打開串口,如果打開了繼續(xù)向下執(zhí)行,如果失敗了,跳轉(zhuǎn)至catch部分
comboBox1.Enabled = false; // 串口已打開,將comboBox1設(shè)置為不可操作
comboBox2.Enabled = false; // 串口已打開,將comboBox2設(shè)置為不可操作
button1.BackColor = Color.Red; // 將串口開關(guān)按鍵的顏色,改為紅色
button1.Text = "關(guān)閉串口"; // 將串口開關(guān)按鍵的文字改為“關(guān)閉串口”
}
catch
{
MessageBox.Show("打開串口失敗,請(qǐng)檢查串口", "錯(cuò)誤"); // 彈出錯(cuò)誤對(duì)話框
}
}
else // 如果當(dāng)前串口設(shè)備是打開狀態(tài)
{
try
{
serialPort1.Close(); // 關(guān)閉串口
comboBox1.Enabled = true; // 串口已關(guān)閉,將comboBox1設(shè)置為可操作
comboBox2.Enabled = true; // 串口已關(guān)閉,將comboBox2設(shè)置為可操作
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為“打開串口”
}
catch
{
MessageBox.Show("關(guān)閉串口失敗,請(qǐng)檢查串口", "錯(cuò)誤"); // 彈出錯(cuò)誤對(duì)話框
}
}
4、"清除接收"按鍵回調(diào)函數(shù)
雙擊“清除接收”按鍵,會(huì)自動(dòng)生成一個(gè)空函數(shù)。
在函數(shù)里面添加以下代碼:
textBox1.Text = "";
5、"發(fā)送"按鍵回調(diào)函數(shù)
雙擊“發(fā)送”按鍵,會(huì)自動(dòng)生成一個(gè)空函數(shù)。
在函數(shù)里面添加以下代碼:
if (serialPort1.IsOpen) // 如果串口設(shè)備已經(jīng)打開了
{
if (!checkBox1.Checked) // 如果是以字符的形式發(fā)送數(shù)據(jù)
{
char[] str = new char[1]; // 定義一個(gè)字符數(shù)組,只有一位
try
{
for (int i = 0; i < textBox2.Text.Length; i++)
{
str[0] = Convert.ToChar(textBox2.Text.Substring(i, 1)); // 取待發(fā)送文本框中的第i個(gè)字符
serialPort1.Write(str, 0, 1); // 寫入串口設(shè)備進(jìn)行發(fā)送
}
}
catch
{
MessageBox.Show("串口字符寫入錯(cuò)誤!", "錯(cuò)誤"); // 彈出發(fā)送錯(cuò)誤對(duì)話框
serialPort1.Close(); // 關(guān)閉串口
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為“打開串口”
}
}
else // 如果以數(shù)值的形式發(fā)送
{
byte[] Data = new byte[1]; // 定義一個(gè)byte類型數(shù)據(jù),相當(dāng)于C語(yǔ)言的unsigned char類型
int flag = 0; // 定義一個(gè)標(biāo)志,標(biāo)志這是第幾位
try
{
for (int i = 0; i < textBox2.Text.Length; i++)
{
if (textBox2.Text.Substring(i, 1) == " " && flag == 0) // 如果是第一位,并且為空字符
{
continue;
}
if (textBox2.Text.Substring(i, 1) != " " && flag == 0) // 如果是第一位,但不為空字符
{
flag = 1; // 標(biāo)志轉(zhuǎn)到第二位數(shù)據(jù)去
if (i == textBox2.Text.Length - 1) // 如果這是文本框字符串的最后一個(gè)字符
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i, 1), 16); // 轉(zhuǎn)化為byte類型數(shù)據(jù),以16進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
}
continue;
}
else if (textBox2.Text.Substring(i, 1) == " " && flag == 1) // 如果是第二位,且第二位字符為空
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 1), 16); // 只將第一位字符轉(zhuǎn)化為byte類型數(shù)據(jù),以十六進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
continue;
}
else if (textBox2.Text.Substring(i, 1) != " " && flag == 1) // 如果是第二位字符,且第一位字符不為空
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 2), 16); // 將第一,二位字符轉(zhuǎn)化為byte類型數(shù)據(jù),以十六進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
continue;
}
}
}
catch
{
MessageBox.Show("串口數(shù)值寫入錯(cuò)誤!", "錯(cuò)誤");
serialPort1.Close();
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為 “打開串口”
}
}
}
6、串口接收函數(shù)
點(diǎn)擊serialPort控件,在該控件的事件里面有一個(gè)DataReceived事件,雙擊它會(huì)生成一個(gè)數(shù)據(jù)接收的空函數(shù)
在函數(shù)里面添加以下代碼:
if (!checkBox2.Checked) // 如果以字符串形式讀取
{
string str = serialPort1.ReadExisting(); // 讀取串口接收緩沖區(qū)字符串
textBox1.AppendText(str + ""); // 在接收文本框中進(jìn)行顯示
}
else // 以數(shù)值形式讀取
{
int length = serialPort1.BytesToRead; // 讀取串口接收緩沖區(qū)字節(jié)數(shù)
byte[] data = new byte[length]; // 定義相同字節(jié)的數(shù)組
serialPort1.Read(data, 0, length); // 串口讀取緩沖區(qū)數(shù)據(jù)到數(shù)組中
for (int i = 0; i < length; i++)
{
string str = Convert.ToString(data[i], 16).ToUpper(); // 將數(shù)據(jù)轉(zhuǎn)換為字符串格式
textBox1.AppendText("0X" + (str.Length == 1 ? "0" + str + " " : str + " ")); // 添加到串口接收文本框中
}
}
7、定時(shí)器中斷回調(diào)函數(shù)
在timer控件的屬性里面打開使能,設(shè)置定時(shí)時(shí)間為500ms
雙擊timer控件,會(huì)自動(dòng)生成一個(gè)空函數(shù)
在函數(shù)里面添加以下代碼:
Updata_Serialport_Name(comboBox1); // 定時(shí)刷新可用串口,可以保證在程序啟動(dòng)之后連接的設(shè)備也能被檢測(cè)到
最后再貼一個(gè)完整的代碼:
提示:不能直接跳過(guò)前面的步驟直接把完整的代碼拷貝過(guò)去,因?yàn)榍懊骐p擊控件的操作不僅僅是生成空函數(shù),也會(huì)在Designer里面添加對(duì)應(yīng)的事件,如果直接拷貝就不會(huì)產(chǎn)生事件。當(dāng)然,如果非要這樣操作也不是不行,只要在Form1.Designer.cs文件里面把每個(gè)控件對(duì)應(yīng)的事件加上即可。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
namespace 串口測(cè)試工具
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//設(shè)置該屬性 為false
}
/* 新增自定義函數(shù):更新可用串口 */
private void Updata_Serialport_Name(ComboBox MycomboBox)
{
string[] ArryPort; // 定義字符串?dāng)?shù)組,數(shù)組名為 ArryPort
ArryPort = SerialPort.GetPortNames(); // SerialPort.GetPortNames()函數(shù)功能為獲取計(jì)算機(jī)所有可用串口,以字符串?dāng)?shù)組形式輸出
MycomboBox.Items.Clear(); // 清除當(dāng)前組合框下拉菜單內(nèi)容
for (int i = 0; i < ArryPort.Length; i++)
{
MycomboBox.Items.Add(ArryPort[i]); // 將所有的可用串口號(hào)添加到端口對(duì)應(yīng)的組合框中
}
}
/* 啟動(dòng)窗口加載函數(shù) */
private void Form1_Load(object sender, EventArgs e)
{
Updata_Serialport_Name(comboBox1); // 調(diào)用更新可用串口函數(shù),comboBox1為端口號(hào)組合框的名稱
}
/* "打開串口"按鍵回調(diào)函數(shù) */
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "打開串口") // 如果當(dāng)前是串口設(shè)備是關(guān)閉狀態(tài)
{
try // try 是嘗試部分,如果嘗試過(guò)程中出現(xiàn)問(wèn)題,進(jìn)入catch部分,執(zhí)行錯(cuò)誤處理代碼
{
serialPort1.PortName = comboBox1.Text; // 將串口設(shè)備的串口號(hào)屬性設(shè)置為comboBox1復(fù)選框中選擇的串口號(hào)
serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text); // 將串口設(shè)備的波特率屬性設(shè)置為comboBox2復(fù)選框中選擇的波特率
serialPort1.Open(); // 打開串口,如果打開了繼續(xù)向下執(zhí)行,如果失敗了,跳轉(zhuǎn)至catch部分
comboBox1.Enabled = false; // 串口已打開,將comboBox1設(shè)置為不可操作
comboBox2.Enabled = false; // 串口已打開,將comboBox2設(shè)置為不可操作
button1.BackColor = Color.Red; // 將串口開關(guān)按鍵的顏色,改為紅色
button1.Text = "關(guān)閉串口"; // 將串口開關(guān)按鍵的文字改為“關(guān)閉串口”
}
catch
{
MessageBox.Show("打開串口失敗,請(qǐng)檢查串口", "錯(cuò)誤"); // 彈出錯(cuò)誤對(duì)話框
}
}
else // 如果當(dāng)前串口設(shè)備是打開狀態(tài)
{
try
{
serialPort1.Close(); // 關(guān)閉串口
comboBox1.Enabled = true; // 串口已關(guān)閉,將comboBox1設(shè)置為可操作
comboBox2.Enabled = true; // 串口已關(guān)閉,將comboBox2設(shè)置為可操作
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為“打開串口”
}
catch
{
MessageBox.Show("關(guān)閉串口失敗,請(qǐng)檢查串口", "錯(cuò)誤"); // 彈出錯(cuò)誤對(duì)話框
}
}
}
/* "清除接收"按鍵回調(diào)函數(shù) */
private void button2_Click(object sender, EventArgs e)
{
textBox1.Text = "";
}
/* "發(fā)送"按鍵回調(diào)函數(shù) */
private void button3_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen) // 如果串口設(shè)備已經(jīng)打開了
{
if (!checkBox1.Checked) // 如果是以字符的形式發(fā)送數(shù)據(jù)
{
char[] str = new char[1]; // 定義一個(gè)字符數(shù)組,只有一位
try
{
for (int i = 0; i < textBox2.Text.Length; i++)
{
str[0] = Convert.ToChar(textBox2.Text.Substring(i, 1)); // 取待發(fā)送文本框中的第i個(gè)字符
serialPort1.Write(str, 0, 1); // 寫入串口設(shè)備進(jìn)行發(fā)送
}
}
catch
{
MessageBox.Show("串口字符寫入錯(cuò)誤!", "錯(cuò)誤"); // 彈出發(fā)送錯(cuò)誤對(duì)話框
serialPort1.Close(); // 關(guān)閉串口
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為“打開串口”
}
}
else // 如果以數(shù)值的形式發(fā)送
{
byte[] Data = new byte[1]; // 定義一個(gè)byte類型數(shù)據(jù),相當(dāng)于C語(yǔ)言的unsigned char類型
int flag = 0; // 定義一個(gè)標(biāo)志,標(biāo)志這是第幾位
try
{
for (int i = 0; i < textBox2.Text.Length; i++)
{
if (textBox2.Text.Substring(i, 1) == " " && flag == 0) // 如果是第一位,并且為空字符
{
continue;
}
if (textBox2.Text.Substring(i, 1) != " " && flag == 0) // 如果是第一位,但不為空字符
{
flag = 1; // 標(biāo)志轉(zhuǎn)到第二位數(shù)據(jù)去
if (i == textBox2.Text.Length - 1) // 如果這是文本框字符串的最后一個(gè)字符
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i, 1), 16); // 轉(zhuǎn)化為byte類型數(shù)據(jù),以16進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
}
continue;
}
else if (textBox2.Text.Substring(i, 1) == " " && flag == 1) // 如果是第二位,且第二位字符為空
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 1), 16); // 只將第一位字符轉(zhuǎn)化為byte類型數(shù)據(jù),以十六進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
continue;
}
else if (textBox2.Text.Substring(i, 1) != " " && flag == 1) // 如果是第二位字符,且第一位字符不為空
{
Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 2), 16); // 將第一,二位字符轉(zhuǎn)化為byte類型數(shù)據(jù),以十六進(jìn)制顯示
serialPort1.Write(Data, 0, 1); // 通過(guò)串口發(fā)送
flag = 0; // 標(biāo)志回到第一位數(shù)據(jù)去
continue;
}
}
}
catch
{
MessageBox.Show("串口數(shù)值寫入錯(cuò)誤!", "錯(cuò)誤");
serialPort1.Close();
button1.BackColor = Color.Lime; // 將串口開關(guān)按鍵的顏色,改為青綠色
button1.Text = "打開串口"; // 將串口開關(guān)按鍵的文字改為 “打開串口”
}
}
}
}
/* 串口接收函數(shù) */
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (!checkBox2.Checked) // 如果以字符串形式讀取
{
string str = serialPort1.ReadExisting(); // 讀取串口接收緩沖區(qū)字符串
textBox1.AppendText(str + ""); // 在接收文本框中進(jìn)行顯示
}
else // 以數(shù)值形式讀取
{
int length = serialPort1.BytesToRead; // 讀取串口接收緩沖區(qū)字節(jié)數(shù)
byte[] data = new byte[length]; // 定義相同字節(jié)的數(shù)組
serialPort1.Read(data, 0, length); // 串口讀取緩沖區(qū)數(shù)據(jù)到數(shù)組中
for (int i = 0; i < length; i++)
{
string str = Convert.ToString(data[i], 16).ToUpper(); // 將數(shù)據(jù)轉(zhuǎn)換為字符串格式
textBox1.AppendText("0X" + (str.Length == 1 ? "0" + str + " " : str + " ")); // 添加到串口接收文本框中
}
}
}
/* 定時(shí)器中斷回調(diào)函數(shù) */
private void timer1_Tick(object sender, EventArgs e)
{
Updata_Serialport_Name(comboBox1); // 定時(shí)刷新可用串口,可以保證在程序啟動(dòng)之后連接的設(shè)備也能被檢測(cè)到
}
}
}
四、運(yùn)行
在vs里面調(diào)試運(yùn)行結(jié)果如下:
我這里連接了一個(gè)樹莓派,數(shù)據(jù)收發(fā)測(cè)試正常
如果需要在其他PC端運(yùn)行,可以把工程目錄下bin文件里面的Debug拷貝出來(lái),運(yùn)行exe文件即可,不需要再安裝vs
五、結(jié)束語(yǔ)
簡(jiǎn)單的做了一個(gè)串口助手,總體來(lái)說(shuō)其實(shí)不難,不熟悉C#語(yǔ)法也沒(méi)關(guān)系,我也是第一次接觸C#,根據(jù)C語(yǔ)言的經(jīng)驗(yàn)去摸索,代碼基本都能看的懂,有些語(yǔ)法也是即學(xué)即用的。好了,關(guān)于這一講的內(nèi)容就到這里,如果有什么問(wèn)題,歡迎在評(píng)論區(qū)留言討論,謝謝。
源碼下載:https://download.csdn.net/download/ShenZhen_zixian/21712034