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

一、写在前面

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

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

     

二、本文内容

        基于桌面应用程序的用户界面框架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中控件的使用以及重构。对一个简单的业务进行实现,捋清逻辑关系,通过步骤和状态控制工作流程。

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

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

八、感谢

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

        再次感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值