[ C#+Winform开发 ] 基于C#+Winform开发的串口通讯小工具

发布时间:2024年01月03日

一、写在前面

????????本文所用例子为个人学习的小结,如有不足之处请各位多多海涵,欢迎小伙伴一起学习进步,如果想法可在评论区指出,我会尽快回复您,不胜感激!

? ? ? ? 所公布代码或截图均为运行成功后展示。

? ? ?

二、本文内容

? ? ? ? 基于桌面应用程序的用户界面框架Winform和C#开发的串口小工具,实现了:

? ? ? ? 1.串口通讯

? ? ? ? 2.用户界面可视化

? ? ? ? 3.根据串口返回的结果,切换工作状态,如:变换提示灯,弹窗警告等

? ? ? ? 4.配置数据由界面控件控制改为配置文件控制

三、开发环境

????????1.开发语言:C#

????????2.开发框架:.Net Framework 4.8

????????2.界面框架:Winform

????????3.开发工具:Visual Studio 2022

四、业务需求

? ? ? ? 部分业务需求:

自动连接夹具COM口,发送5,回复5
? ? 1:双手按下压按钮,治具合盖,发送6,夹具回复6,可以检查cover是否到位,发送1,开始扫码,夹具回复1,检测到barcode.txt文件,如果超时,发送4,夹具open,回复4。
? ? 2:检测到文件后,发送2,治具动作
? ? 3:如果治具动作到位,再发送7,夹具回复7,软件生成test.txt
? ? 4:机台开始测试,测试结束,机台会生成testend.txt,软件检测到testend.txt,生成airout.txt,开始退真空,软件检测到airoutok.txt,发送3,夹具全部对出,打开,回复3。
? ? 所有txt文件均是放在软件安装的目录下的,测试结束后全部清空

五、业务实现

? ? ? ? 业务实现分为两部分(只想看串口通讯的同学可以直接拉到5.4.3):

????????1.主界面的构建、提示盒子的构建、高光圆形Panel的构建?

?????????2.业务代码实现

(一)主界面的构建

? ? ? ? 主界面,图a

图a

????????使用MenuStrip控件构建菜单栏

????????使用Button控件构建播放、暂停、停止、生成三按钮、上下箭头

????????使用Label控件构建标识框

????????使用RichTextBox控件构建日志打印

????????使用Panel控件构建LED指示灯

(二)提示盒子的构建

? ? ? ? 消息盒子,图b

图b

????????改造Form使其满足需求,并随需要弹出。

?public partial class CustomMessageBox : Form
? ? {
? ? ? ? public CustomMessageBox(string message)
? ? ? ? {
? ? ? ? ? ? InitializeComponent();

? ? ? ? ? ? // 设置窗体的StartPosition属性为CenterParent
? ? ? ? ? ? this.StartPosition = FormStartPosition.CenterParent;

? ? ? ? ? ? // 设置窗体的内容
? ? ? ? ? ? this.label1.Text = message;
? ? ? ? ? ? this.ShowDialog();
? ? ? ? }

? ? ? ? private void button1_Click(object sender, EventArgs e)
? ? ? ? {
? ? ? ? ? ? this.Close();
? ? ? ? ? ? this.DialogResult = DialogResult.OK;
? ? ? ? }
? ? }

(三)高光圆形Panel的构建?

????????RoundPanel存在三种状态,图c:治具未通讯时;图d:治具打开时;图e:治具盒盖时

? ? ? ? 这里也可以使用PictureBox制作,我想练手重构控件,选择了把Panel改造为圆角Panel。

??????

? ? ? ? ? ? ? ? ? ? ? ? 图c? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 图d? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 图e

????????创建并继承Panel,改造代码如下:

????????改变Color1的赋值可以切换状态,实现上方三色转换。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class GradientPanel : Panel
{
? ? private Color color1;
? ? private Color color2;
? ? private float angle;

? ? [Browsable(true)]
? ? [Category("Custom")]
? ? [Description("Custom text property.")]
? ? [DefaultValue(typeof(Color), "Red")]
? ? public Color Color1
? ? {
? ? ? ? get { return color1; }
? ? ? ? set { color1 = value; }
? ? }?
? ??
? ? [Browsable(true)]
? ? [Category("Custom")]
? ? [Description("Custom text property.")]
? ? [DefaultValue(typeof(Color), "Yellow")]
? ? public Color Color2
? ? {
? ? ? ? get { return color2; }
? ? ? ? set { color2 = value; }
? ? }
? ??
? ? [Browsable(true)]
? ? [Category("Custom")]
? ? [Description("Custom text property.")]
? ? [DefaultValue("0F")]
? ? public float Angle
? ? {
? ? ? ? get { return angle; }
? ? ? ? set { angle = value; }
? ? }


? ? protected override void OnPaintBackground(PaintEventArgs e)
? ? {
? ? ? ? using (LinearGradientBrush brush = new LinearGradientBrush(this.ClientRectangle, color1, color2, angle))
? ? ? ? {
? ? ? ? ? ? e.Graphics.FillRectangle(brush, this.ClientRectangle);
? ? ? ? }
? ? }
}

(四)业务代码实现

? ? ? ? 5.4.1 从配置文件获取并赋值

????????????????//获取当前执行程序所在目录
? ? ? ? ? ? ? ? string currentDirectory = Directory.GetCurrentDirectory();
? ? ? ? ? ? ? ? //获取同级目录下的config.ini
? ? ? ? ? ? ? ? string filePath = Path.Combine(currentDirectory, "config.ini");
? ? ? ? ? ? ? ? //读取文件
? ? ? ? ? ? ? ? StreamReader r = new StreamReader(filePath);
? ? ? ? ? ? ? ? string json = r.ReadToEnd();
? ? ? ? ? ? ? ? //以json格式
? ? ? ? ? ? ? ? dynamic config = JsonConvert.DeserializeObject(json);
? ? ? ? ? ? ? ? //赋值
? ? ? ? ? ? ? ? ComTextBox.Text = _Com = config["Com"];
? ? ? ? ? ? ? ? _directoryPath = config["directoryPath"];
? ? ? ? ? ? ? ? _serialPortOpenTimeoutcount = config["serialPortOpenTimeout"];

? ? ? ? ?5.4.2定义更新状态方法

????????????????通过该方法控制循环中当前处于哪个阶段和状态。

? ? ? ? //更新状态
? ? ? ? private void StatusUpdate(CurrentStep cstep, CurrentStatus cstatus)
? ? ? ? {
? ? ? ? ? ? //当前阶段
? ? ? ? ? ? currentStep = cstep;
? ? ? ? ? ? //当前状态
? ? ? ? ? ? currentStatus = cstatus;
? ? ? ? }

?????????????????CurrentStep和CurrentStatus为自定义的步骤和状态,为枚举类型。

? ? ? ? //当前执行步骤
? ? ? ? private enum CurrentStep
? ? ? ? {
? ? ? ? ? ? SoftwareInit,
? ? ? ? ? ? SerialPortOpen,
? ? ? ? ? ? SerialPortClose,
? ? ? ? ? ? FixtureCoverOperate,
? ? ? ? ? ? FixtureOpenOperate,
? ? ? ? ? ? ScanOperate,
? ? ? ? ? ? CheckBarcodeOperate,
? ? ? ? ? ? DimmCpuOperate,
? ? ? ? ? ? CheckTestIsEndOperate,
? ? ? ? ? ? CheckAiroutOperate,
? ? ? ? ? ? DeleteTestFileOperate,
? ? ? ? }

? ? ? ? //当前执行状态
? ? ? ? public enum CurrentStatus
? ? ? ? {
? ? ? ? ? ? SoftwareIniting,
? ? ? ? ? ? SoftwareInitOK,
? ? ? ? ? ? SoftwareInitFail,
? ? ? ? ? ? SerialPortConnecting,
? ? ? ? ? ? SerialPortConnected,
? ? ? ? ? ? SerialPortOpening,
? ? ? ? ? ? SerialPortOpened,
? ? ? ? ? ? SerialPortOpenFail,
? ? ? ? ? ? SerialPortClosing,
? ? ? ? ? ? SerialPortClosed,
? ? ? ? ? ? SerialPortCloseFail,
? ? ? ? ? ? FixtureCoverCommandsending,
? ? ? ? ? ? FixtureCovering,
? ? ? ? ? ? FixtureCovered,
? ? ? ? ? ? FixtureCoverFail,
? ? ? ? ? ? ScanCommandSending,
? ? ? ? ? ? Scanning,
? ? ? ? ? ? ScannOK,
? ? ? ? ? ? ScannFail,
? ? ? ? ? ? CheckingBarcode,
? ? ? ? ? ? CheckBarcodeOK,
? ? ? ? ? ? CheckBarcodeFail,
? ? ? ? ? ? FixtureOpeneCommandSending,
? ? ? ? ? ? FixtureOpening,
? ? ? ? ? ? FixtureOpened,
? ? ? ? ? ? FixtureOpenFail,
? ? ? ? ? ? AllFixtureOpeneCommandSending,
? ? ? ? ? ? AllFixtureOpening,
? ? ? ? ? ? AllFixtureOpened,
? ? ? ? ? ? AllFixtureOpenFail,
? ? ? ? ? ? DimmCpuCommandsending,
? ? ? ? ? ? DimmCpuLoading,
? ? ? ? ? ? DimmCpuLoadOK,
? ? ? ? ? ? DimmCpuLoadingFail,
? ? ? ? ? ? TestEndFileGenerating,
? ? ? ? ? ? TestEndFileGenerateOK,
? ? ? ? ? ? TestEndFileGenerateFail,
? ? ? ? ? ? AiroutFileGenerating,
? ? ? ? ? ? AiroutFileGenerateOK,
? ? ? ? ? ? AiroutFileGenerateFail,
? ? ? ? ? ? AiroutOKFileGenerating,
? ? ? ? ? ? AiroutOKFileGenerateOK,
? ? ? ? ? ? AiroutOKFileGenerateFail,
? ? ? ? ? ? AiroutOKFileButtonClickOK,
? ? ? ? ? ? AllTestFileDeleting,
? ? ? ? ? ? AllTestFileDeleteOK,
? ? ? ? ? ? AllTestFileDeleteFail,
? ? ? ? }

5.4.3 串口通讯

????????通过指定的串口名,定义好串口相符的波特率等属性,并添加接收数据的委托方法后,通过Open()方法开启串口。

//串口初始化
? ? ? ? private bool SerialPortInit()
? ? ? ? {
? ? ? ? ? ? //串口初始化,正在打开串口
? ? ? ? ? ? StatusUpdate(CurrentStep.SerialPortOpen, CurrentStatus.SerialPortConnecting);

? ? ? ? ? ? //获取所有串口名
? ? ? ? ? ? string[] allSp = SerialPort.GetPortNames();

? ? ? ? ? ? Console.WriteLine("current COM list: " + string.Join(",", allSp));

? ? ? ? ? ? try
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //获取指定的通讯串口名
? ? ? ? ? ? ? ? Com = ComTextBox.Text;
? ? ? ? ? ? ? ? mySerialPort = new SerialPort(Com);
? ? ? ? ? ? ? ? //定义串口的波特率等属性
? ? ? ? ? ? ? ? mySerialPort.BaudRate = 115200;
? ? ? ? ? ? ? ? mySerialPort.Parity = Parity.None;
? ? ? ? ? ? ? ? mySerialPort.StopBits = StopBits.One;
? ? ? ? ? ? ? ? mySerialPort.DataBits = 8;
? ? ? ? ? ? ? ? mySerialPort.Handshake = Handshake.None;
? ? ? ? ? ? ? ? //开启接收数据的委托
? ? ? ? ? ? ? ? mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

? ? ? ? ? ? ? ? //打开串口
? ? ? ? ? ? ? ? mySerialPort.Open();
? ? ? ? ? ? ? ? if (mySerialPort.IsOpen)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //串口初始化,正在打开串口
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortConnected);
? ? ? ? ? ? ? ? ? ? LogPrintFunction("COM口打开成功...");
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }

? ? ? ? ? ? }
? ? ? ? ? ? catch (IOException ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpenFail);
? ? ? ? ? ? ? ? // 处理输入/输出错误
? ? ? ? ? ? ? ? LogPrintFunction("输入/输出错误: " + ex.Message);
? ? ? ? ? ? }
? ? ? ? ? ? catch (UnauthorizedAccessException ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpenFail);
? ? ? ? ? ? ? ? // 处理权限错误
? ? ? ? ? ? ? ? LogPrintFunction("权限错误: " + ex.Message);
? ? ? ? ? ? }
? ? ? ? ? ? catch (InvalidOperationException ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpenFail);
? ? ? ? ? ? ? ? // 处理无效操作错误
? ? ? ? ? ? ? ? LogPrintFunction("无效操作: " + ex.Message);
? ? ? ? ? ? }
? ? ? ? ? ? catch (Exception ex)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpenFail);
? ? ? ? ? ? ? ? LogPrintFunction("其他异常:" + ex.Message);
? ? ? ? ? ? }
? ? ? ? ? ? return false;
? ? ? ? }

?????????接收数据的方法定义:

private void DataReceivedHandler(
? ? ? ? ? ? ? ? object sender,
? ? ? ? ? ? ? ? SerialDataReceivedEventArgs e)
? ? ? ? {
? ? ? ? ? ? ? ? SerialPort sp = (SerialPort)sender;
? ? ? ? ? ? ? ? string indata = sp.ReadExisting();
? ? ? ? ? ? ? ? Console.WriteLine("Data Received:" + indata);
? ? ? ? ? ? ? ? if (indata == "5" )
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于5,则COM口通信成功
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpened);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else if (indata == "6" )
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于6,则治具已合盖
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.FixtureCovered);

? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else if (indata == "1")
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于1,则扫码成功
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.ScannOK);

? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else if (indata == "4" )
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于4,则夹具打开成功
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.FixtureOpened);

? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else if (indata == "7" )
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于7,则CPU装载成功
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.DimmCpuLoadOK);

? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else if (indata == "3" )
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //如果回复值等于3或,则全部治具打开成功
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.AllFixtureOpened);

? ? ? ? ? ? ? ? }


? ? ? ? }

5.4.4部分业务代码展示:

? ? ? ? 根据业务逻辑实现 发送命令,切换状态,使LED灯、提示文言、Log打印等。

? ? ? ? 下方为部分代码,具体请尝试自己实现吧~~你是最棒的!

? ? ? ? private async Task WorkFlow()
? ? ? ? {
? ? ? ? ? ? FixtureOpenLabel.Text = "Fixture Open OK";
? ? ? ? ? ? FixtureOpenRoundPanel.Color1 = PassColor;
? ? ? ? ? ? StartButton.Enabled = false;

? ? ? ? ? ? if (!GetConfigFile())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }

? ? ? ? ? ? while (true)
? ? ? ? ? ? {

? ? ? ? ? ? ? ? Console.WriteLine("cstep: " + currentStep + " ?cstatus: " + currentStatus);
? ? ? ? ? ? ? ? //串口打开时
? ? ? ? ? ? ? ? if (mySerialPort.IsOpen)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if (currentStep == CurrentStep.SerialPortOpen)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? if (currentStatus == CurrentStatus.SerialPortConnected)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //发送5,等待回复 5ok
? ? ? ? ? ? ? ? ? ? ? ? ? ? LogPrintFunction("COM口通信命令正在发送...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? CommandWrite("5", CurrentStatus.SerialPortOpening, "COM通信命令发送成功", CurrentStatus.SerialPortOpenFail, "COM通信命令发送失败");
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else if (currentStatus == CurrentStatus.SerialPortOpening)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (timeoutcount >= serialPortOpenTimeoutcount)
? ? ? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //通信超时
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortOpenFail);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? LogPrintFunction("COM口通信已超时...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? timeoutcount += 1;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? LogPrintFunction("COM口正在等待回复..." + timeoutcount + "s");
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else if (currentStatus == CurrentStatus.SerialPortOpened)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //通信成功,切换治具盒盖模式
? ? ? ? ? ? ? ? ? ? ? ? ? ? LogPrintFunction("COM口通信成功...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? timeoutcount = 0;
? ? ? ? ? ? ? ? ? ? ? ? ? ? StatusUpdate(CurrentStep.FixtureCoverOperate, CurrentStatus.FixtureCoverCommandsending);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? else if (currentStatus == CurrentStatus.SerialPortOpenFail)
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //TODO 通信超时动作
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? //省略了部分代码

? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //串口关闭了
? ? ? ? ? ? ? ? ? ? StatusUpdate(currentStep, CurrentStatus.SerialPortClosed);
? ? ? ? ? ? ? ? ? ? LogPrintFunction("串口被关闭了...");
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //间隔值500ms
? ? ? ? ? ? ? ? await Task.Delay(1000);
? ? ? ? ? ? }
? ? ? ? ? ? StartButton.Enabled = true;
? ? ? ? }

六、实机效果

????????6.1串口通讯成功

????????6.2治具合盖命令发送成功,治具已盒盖

? ? ? ? 6.3扫码成功

????????6.4测试完成,删除所有文件

? ? ? ? 6.5错误时的提示框

????????????????检测barcode.txt文件超时,弹出提示框,点击后,治具将恢复初始状态。

? ? ? ? 6.6配置文件展示

七、项目小结

????????通过该项目实现了c#的串口间通信,并熟悉了winform中控件的使用以及重构。对一个简单的业务进行实现,捋清逻辑关系,通过步骤和状态控制工作流程。

? ? ? ? 项目中还有很多不足之处,比如没有使用设计模式进行优化等,请各位海涵~

? ? ? ? 希望大家一起交流学习,共同成长!

八、感谢

????????感谢各位大佬的莅临,学习之路漫漫,吾将上下而求索。有任何想法请在评论区留言哦!

????????再次感谢!

文章来源:https://blog.csdn.net/wxf5522/article/details/135290862
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。