C# ISP 串口调试工具的开发(一)

前言

由于公司需要开发一个 ISP 串口调试工具,而我也具有一点 C# 的基础,所以给我安排了这个任务。这里将开发过程中的心得记录一下。

开发环境

项目简介:
这个串口调试工具需要支持常用的 300-115200bqs 波特率,能设置校验位、数据位、停止位,在接入下位机后能完成对图像参数的读取与设置,方便工程师通过上位机程序对下位机进行 ISP 参数调试。

项目开发环境:

  • VS2017
  • .NET Framework 4

Winform

首先创建一个 C# 的 Winform 程序。打开 VS2017 选择创建新项目,选择 Visual C# 下的 Windows 桌面,再点击 Windows 窗体应用
创建新的 WinForm

界面布局

UI 界面
先布置一个简单的串口调试工具的 UI 布局,Winform 的 UI 可能不怎么美观,皮肤更换可以使用第三方控件 IrisSkin 快速对 Winform 的样式进行更改,但是会有诸多缺陷,所以想要更加美观的界面还是需要个人花精力来自己设计。

SerialPort 控件

微软 WInform 提供了串口控件 SerialPort,只要两条语句就能够使用:

using System.IO.Ports;

//实例化串口对象
SerialPort serialPort = new SerialPort();

初始化串口参数

先调用 SerialPort.GetPortNames() 方法获取本机所有窗口,然后将获取到的串口添加到 ComboBox 控件 cbSerials 作为候选列表:

/// <summary>
/// 初始化串口界面参数设置
/// </summary>
private void Init_Port_Confs()
{
    /*------串口界面参数设置------*/
    //检查是否含有串口
    string[] ports = SerialPort.GetPortNames();
    if (ports == null)
    {
        //cbSerials.SelectedIndex = -1;// 可以设置默认为 -1
        MessageBox.Show("本机没有串口!", "Error");
        return;
    }
    //添加串口选项
    cbSerials.Items.AddRange(ports);
    //设置默认串口选项
    cbSerials.SelectedIndex = 0;

    /*------波特率设置-------*/
    string[] baudRate = { "9600", "19200", "38400", "57600", "115200" };
    cbBaudRate.Items.AddRange(baudRate);
    cbBaudRate.SelectedIndex = 0;

    /*------数据位设置-------*/
    string[] dataBit = { "5", "6", "7", "8" };
    cbDataBits.Items.AddRange(dataBit);
    cbDataBits.SelectedIndex = 3;


    /*------校验位设置-------*/
    string[] checkBit = { "None", "Even", "Odd", "Mask", "Space" };
    cbParity.Items.AddRange(checkBit);
    cbParity.SelectedIndex = 0;

    /*------停止位设置-------*/
    string[] stopBit = { "None", "One", "Two", "OnePointFive" };
    cbStopBits.Items.AddRange(stopBit);
    cbStopBits.SelectedIndex = 1;
}

另外设置了一个扫描端口的按钮,能够刷新本机的串口列表:

/// <summary>
/// 扫描端口
/// </summary>
private void BtnGetPort_Click(object sender, EventArgs e)
{
    cbSerials.Text = "";
    cbSerials.Items.Clear();

    string[] str = SerialPort.GetPortNames();
    if (str == null)
    {
        MessageBox.Show("本机没有串口!", "Error");
        return;
    }

    //添加串口
    foreach (string s in str)
    {
        cbSerials.Items.Add(s);
    }

    //设置默认串口
    cbSerials.SelectedIndex = 0;
}

窗体初始化

在窗体初始化时,会对串口参数设置初始化:

/// <summary>
/// 加载主窗体
/// </summary>
private void Demo_Load(object sender, EventArgs e)
{
    // 初始化串口
    Init_Port_Confs();
}

连接串口

SerialPort 自带了串口的打开操作 SerialPort.Open()和串口关闭操作 SerialPort.Close(),在连接串口时,要将选择的串口参数设置串口的属性,并且检测串口异常抛出,然后再打开串口。如果出现打开串口异常时,可以打开警告提示框,也可以重新实例化一个窗口继续操作:

/// <summary>
/// 串口的开关操作
/// </summary>
private void BtnConnectSwitch_Click(object sender, EventArgs e)
{
    if (!serialPort.IsOpen)// 串口处于关闭状态
    {
        try
        {
            // 判断所选端口是否有效
            if (cbSerials.SelectedIndex == -1)
            {
                MessageBox.Show("错误:无效的端口,请重新选择", "Error");
                return;
            }

            // 获取串口参数
            string strSerialName = cbSerials.SelectedItem.ToString();
            string strBaudRate = cbBaudRate.SelectedItem.ToString();
            string strDataBit = cbDataBits.SelectedItem.ToString();
            string strCheckBit = cbParity.SelectedItem.ToString();
            string strStopBit = cbStopBits.SelectedItem.ToString();

            serialPort.PortName = strSerialName;//串口号
            serialPort.BaudRate = Convert.ToInt32(strBaudRate);//波特率
            serialPort.DataBits = Convert.ToInt32(strDataBit);//数据位

            switch (strStopBit)            //停止位
            {
                case "None":
                    serialPort.StopBits = StopBits.None;
                    break;
                case "One":
                    serialPort.StopBits = StopBits.One;
                    break;
                case "OnePointFive":
                    serialPort.StopBits = StopBits.OnePointFive;
                    break;
                case "Two":
                    serialPort.StopBits = StopBits.Two;
                    break;
                default:
                    MessageBox.Show("错误:停止位参数不正确!", "Error");
                    break;
            }

            switch (strCheckBit)             //校验位
            {
                case "None":
                    serialPort.Parity = Parity.None;
                    break;
                case "Odd":
                    serialPort.Parity = Parity.Odd;
                    break;
                case "Even":
                    serialPort.Parity = Parity.Even;
                    break;
                default:
                    MessageBox.Show("错误:校验位参数不正确!", "Error");
                    break;
            }

            //打开串口
            serialPort.Open();
            CourseRecord("串口已打开!");
        }
        catch (Exception ex)
        {
            //serialPort = new SerialPort();// 可以重新创建一个端口
            MessageBox.Show("错误:" + ex.Message + "请检查端口是否被占用!", "Error");
            return;
        }
    }
    else // 串口处于打开状态
    {
        // 关闭串口
        serialPort.Close();
        CourseRecord("串口已关闭");
    }

    BtnConnectSwitch.Text = serialPort.IsOpen ? "断开连接" : "连接端口";
    connectStatus.Text = serialPort.IsOpen ? "已连接" : "未连接";

    //串口设置参数按钮控制
    cbSerials.Enabled = !serialPort.IsOpen;
    cbBaudRate.Enabled = !serialPort.IsOpen;
    cbDataBits.Enabled = !serialPort.IsOpen;
    cbParity.Enabled = !serialPort.IsOpen;
    cbStopBits.Enabled = !serialPort.IsOpen;
    BtnReset.Enabled = !serialPort.IsOpen;
    BtnScan.Enabled = !serialPort.IsOpen;
    BtnSendParams.Enabled = serialPort.IsOpen;
    BtnGetParams.Enabled = serialPort.IsOpen;
}

在连接串口后就将发送参数和读取参数按钮设置为启用状态,其他按钮设置为禁用状态;关闭串口后就将发送参数和读取参数按钮设置为禁用状态,其他按钮设置为启用状态。

进程记录

在连接串口时调用了一个方法 CourseRecord ,这是在通讯反馈中记录串口的状态和通讯情况,能够监测串口通讯中出现的错误,因为在 TextBox 中追加记录时每次都添加上时间戳,所以封装了一个记录的方法:

// 记录进程
public void CourseRecord(string record)
{
    //输出当前时间
    DateTime dateTimeNow = DateTime.Now;
    CourseText.Text += string.Format("{0}\r\n", dateTimeNow);
    CourseText.Text += string.Format("{0}\r\n\r\n", record);
}

发送参数

发送参数时,先将发送参数和读取参数按钮禁用,然后使用 SerialPort.DiscardOutBuffer 清空发送缓冲区的数据,然后循环判断,计算非空参数的个数,然后用一个字节的 type 区分参数,两个字节来存储参数的值 value,将参数的 typevalue 顺序连接,其中将 short 类型转为 byte 类型用的是位运算方式,最后判断如果有非空参数就通过 SerialPort.Write 发送数据:

/// <summary>
/// 发送参数按钮事件
/// </summary>
private void BtnSendParams_Click(object sender, EventArgs e)
{
    if (!serialPort.IsOpen)
    {
        MessageBox.Show("串口未连接!", "传输错误");
        return;
    }

    BtnSendParams.Enabled = false;
    BtnGetParams.Enabled = false;

    // 清空缓冲区
    serialPort.DiscardOutBuffer();

    ushort length = 0;

    // 计算参数个数
    foreach (Control control in groupParams.Controls)
    {
        if (control is TextBox && control.Text.Length != 0)
        {
            length = (ushort)(length + 3);
        }
    }
    // 获取数据字节数
    byte[] len = new byte[2];
    len[0] = (byte)(length << 8);
    len[1] = (byte)(length << 0);

    // 添加非空参数
    byte[] data = new byte[length];
    string name = "";
    int i = 0;
    byte[] value = new byte[2];
    foreach (Control control in groupParams.Controls)
    {
        if (!(control is TextBox))
        {
            continue;
        }

        if (control.Text.Length != 0)
        {
            name = control.Name.Replace("param", "");
            byte.TryParse(name, out byte type);
            ushort.TryParse(control.Text, out ushort param);
            data[i] = type;
            value[0] = (byte)(param << 8);
            value[1] = (byte)(param << 0);
            value.CopyTo(data, i + 1);
            i += 3;
        }
    }

     //判断参数非空
     if (length == 0)
     {
         MessageBox.Show("参数为空", "警告");
         CourseRecord("参数为空,请重新发送!");
         //CourseRecord("参数为空,请重新发送!");
         TimeCount.Text = "";
         BtnSendParams.Enabled = true;
         BtnGetParams.Enabled = true;
         return;
     }

     //发送数据
     serialPort.Write(data, 0, data.Length);
     
     CourseRecord("发送参数完成!");
     BtnSendParams.Enabled = true;
     BtnGetParams.Enabled = true;
}

读取参数

读取参数时,使用 SerialPort.DiscardInBuffer 清空输入缓冲区的数据,然后循环监听缓冲区的字节数 SerialPort.BytesToRead ,如果接收超过 3 个字节,证明读取到至少一个参数,这时开始通过 SerialPort.Read 读取数据,并循环将数据渲染到对应的 TextBox 中:

/// <summary>
/// 读取参数按钮事件
/// </summary>
private void BtnGetData_Click(object sender, EventArgs e)
{
    if (!serialPort.IsOpen)
    {
        MessageBox.Show("串口未连接!", "传输错误");
        return;
    }

    BtnSendParams.Enabled = false;
    BtnGetParams.Enabled = false;

    // 清空缓冲区
    serialPort.DiscardInBuffer();

    int byteCount = 0;
    while(byteCount < 3)
    {
        byteCount = serialPort.BytesToRead;
    }

    // 获取数据并检查响应数据
    byte[] data = new byte[byteCount];
    serialPort.Read(data, 0, byteCount);

    //展示数据
    int index, value;
    string controlName;
    // 循环读取参数
    for (int i = 0; i < data.Length; i += 3)
    {
        index = (int)data[i];
        // 获取参数名
        controlName = "param" + index;
        // 获取参数值
        value = data[i + 1] * 256 + data[i + 2];

        TextBox textbox = (TextBox)Controls.Find(controlName, true)[0];
        textbox.Text = value.ToString();
    }

    CourseRecord("读取参数完成!");
    BtnSendParams.Enabled = true;
    BtnGetParams.Enabled = true;
}

关闭串口

/// <summary>
/// 退出
/// </summary>
private void FormClosing_Click(object sender, FormClosingEventArgs e)
{
    if (serialPort.IsOpen)
    {
        serialPort.Close();//关闭串口
        CourseRecord("串口已关闭!");
    }
}

总结

这就是一个简单的具有读写功能的串口调试工具,仍有许多待完善的地方,比如没有超时限制,帧验证等等。下一篇会介绍串口调试工具多线程。

代码

下面贴出完整的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;

namespace Demo
{
    public partial class Demo : Form
    {
        //实例化串口对象
        SerialPort serialPort = new SerialPort();

        public Demo()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 初始化串口界面参数设置
        /// </summary>
        private void Init_Port_Confs()
        {
            /*------串口界面参数设置------*/
            //检查是否含有串口
            string[] ports = SerialPort.GetPortNames();
            if (ports == null)
            {
                //cbSerials.SelectedIndex = -1;// 可以设置默认为 -1
                MessageBox.Show("本机没有串口!", "Error");
                return;
            }
            //添加串口选项
            cbSerials.Items.AddRange(ports);
            //设置默认串口选项
            cbSerials.SelectedIndex = 0;

            /*------波特率设置-------*/
            string[] baudRate = { "9600", "19200", "38400", "57600", "115200" };
            cbBaudRate.Items.AddRange(baudRate);
            cbBaudRate.SelectedIndex = 0;

            /*------数据位设置-------*/
            string[] dataBit = { "5", "6", "7", "8" };
            cbDataBits.Items.AddRange(dataBit);
            cbDataBits.SelectedIndex = 3;


            /*------校验位设置-------*/
            string[] checkBit = { "None", "Even", "Odd", "Mask", "Space" };
            cbParity.Items.AddRange(checkBit);
            cbParity.SelectedIndex = 0;

            /*------停止位设置-------*/
            string[] stopBit = { "None", "One", "Two", "OnePointFive" };
            cbStopBits.Items.AddRange(stopBit);
            cbStopBits.SelectedIndex = 1;
        }

        /// <summary>
        /// 加载主窗体
        /// </summary>
        private void Demo_Load(object sender, EventArgs e)
        {
            // 初始化串口
            Init_Port_Confs();
        }

        /// <summary>
        /// 串口的开关操作
        /// </summary>
        private void BtnConnectSwitch_Click(object sender, EventArgs e)
        {
            if (!serialPort.IsOpen)// 串口处于关闭状态
            {
                try
                {
                    // 判断所选端口是否有效
                    if (cbSerials.SelectedIndex == -1)
                    {
                        MessageBox.Show("错误:无效的端口,请重新选择", "Error");
                        return;
                    }

                    // 获取串口参数
                    string strSerialName = cbSerials.SelectedItem.ToString();
                    string strBaudRate = cbBaudRate.SelectedItem.ToString();
                    string strDataBit = cbDataBits.SelectedItem.ToString();
                    string strCheckBit = cbParity.SelectedItem.ToString();
                    string strStopBit = cbStopBits.SelectedItem.ToString();

                    serialPort.PortName = strSerialName;//串口号
                    serialPort.BaudRate = Convert.ToInt32(strBaudRate);//波特率
                    serialPort.DataBits = Convert.ToInt32(strDataBit);//数据位

                    switch (strStopBit)            //停止位
                    {
                        case "None":
                            serialPort.StopBits = StopBits.None;
                            break;
                        case "One":
                            serialPort.StopBits = StopBits.One;
                            break;
                        case "OnePointFive":
                            serialPort.StopBits = StopBits.OnePointFive;
                            break;
                        case "Two":
                            serialPort.StopBits = StopBits.Two;
                            break;
                        default:
                            MessageBox.Show("错误:停止位参数不正确!", "Error");
                            break;
                    }

                    switch (strCheckBit)             //校验位
                    {
                        case "None":
                            serialPort.Parity = Parity.None;
                            break;
                        case "Odd":
                            serialPort.Parity = Parity.Odd;
                            break;
                        case "Even":
                            serialPort.Parity = Parity.Even;
                            break;
                        default:
                            MessageBox.Show("错误:校验位参数不正确!", "Error");
                            break;
                    }

                    //打开串口
                    serialPort.Open();
                    CourseRecord("串口已打开!");

                    //串口开启时设置有效
                    BtnSendParams.Enabled = true;
                    BtnGetParams.Enabled = true;
                }
                catch (Exception ex)
                {
                    //serialPort = new SerialPort();// 可以重新创建一个端口
                    MessageBox.Show("错误:" + ex.Message + "请检查端口是否被占用!", "Error");
                    return;
                }
            }
            else // 串口处于打开状态
            {
                // 关闭串口
                serialPort.Close();
                CourseRecord("串口已关闭");

                //串口关闭时设置有效
                BtnSendParams.Enabled = false;
                BtnGetParams.Enabled = false;
            }

            BtnConnectSwitch.Text = serialPort.IsOpen ? "断开连接" : "连接端口";
            connectStatus.Text = serialPort.IsOpen ? "已连接" : "未连接";

            //串口设置参数按钮控制
            cbSerials.Enabled = !serialPort.IsOpen;
            cbBaudRate.Enabled = !serialPort.IsOpen;
            cbDataBits.Enabled = !serialPort.IsOpen;
            cbParity.Enabled = !serialPort.IsOpen;
            cbStopBits.Enabled = !serialPort.IsOpen;
            BtnReset.Enabled = !serialPort.IsOpen;
            BtnScan.Enabled = !serialPort.IsOpen;
        }

        /// <summary>
		/// 读取参数按钮事件
		/// </summary>
		private void BtnGetData_Click(object sender, EventArgs e)
		{
		    if (!serialPort.IsOpen)
		    {
		        MessageBox.Show("串口未连接!", "传输错误");
		        return;
		    }
		
		    BtnSendParams.Enabled = false;
		    BtnGetParams.Enabled = false;
		
		    // 清空缓冲区
		    serialPort.DiscardInBuffer();
		
		    int byteCount = 0;
		    while(byteCount < 3)
		    {
		        byteCount = serialPort.BytesToRead;
		    }
		
		    // 获取数据并检查响应数据
		    byte[] data = new byte[byteCount];
		    serialPort.Read(data, 0, byteCount);
		
		    //展示数据
		    int index, value;
		    string controlName;
		    // 循环读取参数
		    for (int i = 0; i < data.Length; i += 3)
		    {
		        index = (int)data[i];
		        // 获取参数名
		        controlName = "param" + index;
		        // 获取参数值
		        value = data[i + 1] * 256 + data[i + 2];
		
		        TextBox textbox = (TextBox)Controls.Find(controlName, true)[0];
		        textbox.Text = value.ToString();
		    }
		
		    CourseRecord("读取参数完成!");
		    BtnSendParams.Enabled = true;
		    BtnGetParams.Enabled = true;
		}

        /// <summary>
		/// 发送参数按钮事件
		/// </summary>
		private void BtnSendParams_Click(object sender, EventArgs e)
		{
		    if (!serialPort.IsOpen)
		    {
		        MessageBox.Show("串口未连接!", "传输错误");
		        return;
		    }
		
		    BtnSendParams.Enabled = false;
		    BtnGetParams.Enabled = false;
		
		    // 清空缓冲区
		    serialPort.DiscardOutBuffer();
		
		    ushort length = 0;
		
		    // 计算参数个数
		    foreach (Control control in groupParams.Controls)
		    {
		        if (control is TextBox && control.Text.Length != 0)
		        {
		            length = (ushort)(length + 3);
		        }
		    }
		    // 获取数据字节数
		    byte[] len = new byte[2];
		    len[0] = (byte)(length << 8);
		    len[1] = (byte)(length << 0);
		
		    // 添加非空参数
		    byte[] data = new byte[length];
		    string name = "";
		    int i = 0;
		    byte[] value = new byte[2];
		    foreach (Control control in groupParams.Controls)
		    {
		        if (!(control is TextBox))
		        {
		            continue;
		        }
		
		        if (control.Text.Length != 0)
		        {
		            name = control.Name.Replace("param", "");
		            byte.TryParse(name, out byte type);
		            ushort.TryParse(control.Text, out ushort param);
		            data[i] = type;
		            value[0] = (byte)(param << 8);
		            value[1] = (byte)(param << 0);
		            value.CopyTo(data, i + 1);
		            i += 3;
		        }
		    }
		
		     //判断参数非空
		     if (length == 0)
		     {
		         MessageBox.Show("参数为空", "警告");
		         CourseRecord("参数为空,请重新发送!");
		         //CourseRecord("参数为空,请重新发送!");
		         TimeCount.Text = "";
		         BtnSendParams.Enabled = true;
		         BtnGetParams.Enabled = true;
		         return;
		     }
		
		     //发送数据
		     serialPort.Write(data, 0, data.Length);
		     
		     CourseRecord("发送参数完成!");
		     BtnSendParams.Enabled = true;
		     BtnGetParams.Enabled = true;
		}

        /// <summary>
        /// 记录进程
        /// </summary>
        public void CourseRecord(string record)
        {
            //输出当前时间
            DateTime dateTimeNow = DateTime.Now;
            CourseText.Text += string.Format("{0}\r\n", dateTimeNow);
            CourseText.Text += string.Format("{0}\r\n\r\n", record);
        }

        /// <summary>
        /// 扫描端口
        /// </summary>
        private void BtnGetPort_Click(object sender, EventArgs e)
        {
            cbSerials.Text = "";
            cbSerials.Items.Clear();

            string[] str = SerialPort.GetPortNames();
            if (str == null)
            {
                MessageBox.Show("本机没有串口!", "Error");
                return;
            }

            //添加串口
            foreach (string s in str)
            {
                cbSerials.Items.Add(s);
            }

            //设置默认串口
            cbSerials.SelectedIndex = 0;
        }

        /// <summary>
        /// 退出
        /// </summary>
        private void FormClosing_Click(object sender, FormClosingEventArgs e)
        {
            if (serialPort.IsOpen)
            {
                serialPort.Close();//关闭串口
                CourseRecord("串口已关闭!");
            }
        }
    }
}

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值