zoukankan      html  css  js  c++  java
  • C#串口调试工具 (WPF/MVVM结构完整示例版)

    前文

    由于经常用到串口调试, 尽管有现成的软件, 因为前端时间涉及一个二次开发, 就因为一个RtsEnable设置, 折腾半天,  网上各种版本的也很多, 功能扩展的很开也多。所以现在自己做了一个够用版,基于自己的需求,简单的实现发送接收功能, 至于那些扩展功能可以自己根据需求添加。

    正文

    先上个运行效果图:

    项目架构

    该实例用的GalaSoft.Mvvm, 该插件可以直接在NuGet中并且添加。

    1.串口参数 , 为了方便, 端口号并没有用动态加载的方式, 如下枚举结构:

        /// <summary>
        /// 端口号
        /// </summary>
        public enum Port
        {
            COM1,
            COM2,
            COM3,
            COM4,
            COM5,
            COM6,
            COM7,
            COM8,
            COM9,
            COM10,
            COM11,
            COM12,
            COM13,
            COM14,
            COM15,
            COM16,
            COM17,
            COM18,
            COM19,
            COM20,
            COM21,
            COM22,
            COM23,
            COM24,
            COM25,
            COM26,
            COM27,
            COM28,
            COM29,
            COM30
        }
    
        /// <summary>
        /// 奇偶校验
        /// </summary>
        public enum CheckMode
        {
            None = 0,
            Odd = 1,
            Even = 2,
            Mark = 3,
            Space = 4
        }
    
        /// <summary>
        /// 停止位
        /// </summary>
        public enum StopBit
        {
            One=1,
            Two=2,
            OnePointFive=3,
        }

    2.串口参数配置类 , 

    作用: 主要用于绑定界面的参数选项。

        /// <summary>
        /// 串口参数设置类
        /// </summary>
        public class ComParameterConfig : ViewModelBase
        {
            public ComParameterConfig()
            {
                Port = System.Enum.GetValues(typeof(Port));
                CheckMode = System.Enum.GetValues(typeof(CheckMode));
                StopBit = System.Enum.GetValues(typeof(StopBit));
                BaudRate = new List<int>() { 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, };
                DataBit = new List<int>() { 6, 7, 8 };
            }
    
            private Array port;
            private Array checkMode;
            private Array stopBit;
            private List<int> dataBit;
            private List<int> baudRate;
    
            /// <summary>
            /// 端口
            /// </summary>
            public Array Port
            {
                get { return port; }
                set { port = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 校验模式
            /// </summary>
            public Array CheckMode
            {
                get { return checkMode; }
                set { checkMode = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 停止位
            /// </summary>
            public Array StopBit
            {
                get { return stopBit; }
                set { stopBit = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 波特率
            /// </summary>
            public List<int> BaudRate
            {
                get { return baudRate; }
                set { baudRate = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 数据位
            /// </summary>
            public List<int> DataBit
            {
                get { return dataBit; }
                set { dataBit = value; RaisePropertyChanged(); }
            }
    
        }

    3.当前配置参数类

    作用: 用于保存当前的串口参数、串口功能开关接收数据等业务。

      核心代码:
     /// <summary>
        /// 当前配置参数
        /// </summary>
        public class CurrentParameter : ViewModelBase
        {
            #region Private
    
            private int baudRdate = 9600;
            private int dataBit = 8;
            private Port port;
            private CheckMode checkMode;
            private StopBit stopBit = StopBit.One;
            private SerialPort serialPort;
    
            private string dataReceiveInfo;
            private string sendData;
            private bool isOpen;
            private bool receiveFormat16 = true;
            private bool sendFormat16 = true;
    
            private int sendCount;
            private int receiveCount;
    
            #endregion
    
            #region UI绑定参数
    
            /// <summary>
            /// 发送数量
            /// </summary>
            public int SendCount
            {
                get { return sendCount; }
                set { sendCount = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 接收数量
            /// </summary>
            public int ReceiveCount
            {
                get { return receiveCount; }
                set { receiveCount = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 接收区16进制
            /// </summary>
            public bool ReceiveFormat16
            {
                get { return receiveFormat16; }
                set { receiveFormat16 = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 接收区数据
            /// </summary>
            public string DataReceiveInfo
            {
                get { return dataReceiveInfo; }
                set { dataReceiveInfo = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 发送数据
            /// </summary>
            public string SendData
            {
                get
                {
                    return sendData;
                }
                set { sendData = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 发送区16进制
            /// </summary>
            public bool SendFormat16
            {
                get { return sendFormat16; }
                set { sendFormat16 = value; RaisePropertyChanged(); }
            }
    
            #endregion
    
            #region 串口参数信息
    
            /// <summary>
            /// 开关
            /// </summary>
            public bool IsOpen
            {
                get { return isOpen; }
                set { isOpen = value; RaisePropertyChanged(); }
            }
    
    
            /// <summary>
            /// 数据位
            /// </summary>
            public int DataBit
            {
                get { return dataBit; }
                set { dataBit = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 波特率
            /// </summary>
            public int BaudRdate
            {
                get { return baudRdate; }
                set { baudRdate = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 端口
            /// </summary>
            public Port Port
            {
                get { return port; }
                set { port = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 校验
            /// </summary>
            public CheckMode CheckMode
            {
                get { return checkMode; }
                set { checkMode = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// 停止位
            /// </summary>
            public StopBit StopBit
            {
                get { return stopBit; }
                set { stopBit = value; RaisePropertyChanged(); }
            }
    
            /// <summary>
            /// COM
            /// </summary>
            public SerialPort SerialPort
            {
                get { return serialPort; }
                set { serialPort = value; RaisePropertyChanged(); }
            }
    
    
            #endregion
    
            #region 串口操作方法
    
            /// <summary>
            /// 开启串口
            /// </summary>
            /// <returns></returns>
            public bool Open()
            {
                if (serialPort != null && serialPort.IsOpen)
                {
                    return Close();
                }
                try
                {
                    serialPort = new SerialPort();
                    serialPort.DataBits = this.DataBit;
                    serialPort.StopBits = ComHelper.GetStopBits(this.StopBit.ToString());
                    serialPort.Parity = ComHelper.GetParity(this.CheckMode.ToString());
                    serialPort.PortName = this.Port.ToString();
                    serialPort.RtsEnable = true;
                    serialPort.DataReceived += SerialPort_DataReceived;
                    serialPort.Open();
    
                    if (serialPort.IsOpen)
                        return IsOpen = true;
                    else
                        return IsOpen = false;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
                return IsOpen = false;
            }
    
            /// <summary>
            /// 关闭串口
            /// </summary>
            /// <returns></returns>
            public bool Close()
            {
                try
                {
                    if (serialPort.IsOpen)
                    {
                        serialPort.Close();
                        return IsOpen = serialPort.IsOpen;
                    }
                    else
                    {
                        return IsOpen = serialPort.IsOpen;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    return IsOpen = false;
                }
            }
    
            /// <summary>
            /// 发送数据
            /// </summary>
            public void Send()
            {
    
                if (SendFormat16)
                {
                    byte[] bytes = CRC.StringToHexByte(SendData);
                    this.SerialPort.Write(bytes, 0, bytes.Length);
                    SendCount = bytes.Length; //不做增量
                }
                else
                {
                    this.SerialPort.Write(SendData);
                    SendCount = SendData.Length;
                }
                Messenger.Default.Send("", "PlaySendFlashing");
            }
    
            /// <summary>
            /// 清空接收区
            /// </summary>
            public void Clear()
            {
                this.DataReceiveInfo = string.Empty;
            }
    
            /// <summary>
            /// 清空发送区和缓存区
            /// </summary>
            public void ClearText()
            {
                this.SendData = string.Empty;
                this.SendCount = 0;
                this.ReceiveCount = 0;
            }
    
            #endregion
    
            /// <summary>
            /// 返回事件
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                Messenger.Default.Send("", "PlayReciveFlashing");
    
                byte[] readBuffer = new byte[SerialPort.ReadBufferSize];
                SerialPort.Read(readBuffer, 0, readBuffer.Length);
    
                ReceiveCount = SerialPort.ReceivedBytesThreshold; //不做增量
    
                if (ReceiveFormat16)
                {
                    //不做增量
                    DataReceiveInfo = CRC.ByteToString(readBuffer, true);
                }
                else
                {
                    DataReceiveInfo = Encoding.ASCII.GetString(readBuffer);
                }
    
            }
        }

    4.核心MainViewModel类

    作用: 关联首页的上下文, 通过DataContext绑定, 关联界面元素、命令等作用。

     public class MainViewModel : ViewModelBase
        {
            /// <summary>
            /// Initializes a new instance of the MainViewModel class.
            /// </summary>
            public MainViewModel()
            {
                ComParameterConfig = new ComParameterConfig();
                CurrentParameter = new CurrentParameter();
            }
    
            private ComParameterConfig comParameter;
    
            /// <summary>
            /// 参数类
            /// </summary>
            public ComParameterConfig ComParameterConfig
            {
                get { return comParameter; }
                set { comParameter = value; RaisePropertyChanged(); }
            }
    
            private CurrentParameter currentParameter;
    
            /// <summary>
            /// 当前配置参数
            /// </summary>
            public CurrentParameter CurrentParameter
            {
                get { return currentParameter; }
                set { currentParameter = value; RaisePropertyChanged(); }
            }
    
            #region Command
    
            private RelayCommand _ToOpen;
            public RelayCommand ToOpen
            {
                get
                {
                    if (_ToOpen == null)
                    {
                        _ToOpen = new RelayCommand(Open);
                    }
                    return _ToOpen;
                }
                set
                {
                    _ToOpen = value;
                }
            }
            /// <summary>
            /// 根据配置打开端口
            /// </summary>
            public void Open()
            {
                this.CurrentParameter.Open();
            }
    
            private RelayCommand _ToClick;
            public RelayCommand ToClick
            {
                get
                {
                    if (_ToClick == null)
                    {
                        _ToClick = new RelayCommand(Click);
                    }
                    return _ToClick;
                }
                set
                {
                    _ToClick = value;
                }
            }
    
            /// <summary>
            /// 发送数据
            /// </summary>
            public void Click()
            {
                this.CurrentParameter.Send();
            }
    
    
            private RelayCommand _ToClear;
    
            public RelayCommand ToClear
            {
                get
                {
                    if (_ToClear == null)
                    {
                        _ToClear = new RelayCommand(Clear);
                    }
                    return _ToClear;
                }
                set
                {
                    _ToClear = value;
                }
            }
    
            /// <summary>
            /// 清空接收区
            /// </summary>
            public void Clear()
            {
                this.CurrentParameter.Clear();
            }
    
            private RelayCommand _ToClearText;
    
            public RelayCommand ToClearText
            {
                get
                {
                    if (_ToClearText == null)
                    {
                        _ToClearText = new RelayCommand(ClearText);
                    }
                    return _ToClearText;
                }
                set
                {
                    _ToClearText = value;
                }
            }
    
            /// <summary>
            /// 清空界面值
            /// </summary>
            public void ClearText()
            {
                this.CurrentParameter.ClearText();
            }
    
            #endregion
        }
     

    5.CRC校验核心类

    作用:主要实现数据校验, 含ModbusCR标准校验

    /// <summary>
        /// CRC校验
        /// </summary>
        public class CRC
        {
    
            #region  CRC16
    
            public static byte[] CRC16(byte[] data)
            {
                int len = data.Length;
                if (len > 0)
                {
                    ushort crc = 0xFFFF;
    
                    for (int i = 0; i < len; i++)
                    {
                        crc = (ushort)(crc ^ (data[i]));
                        for (int j = 0; j < 8; j++)
                        {
                            crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                        }
                    }
                    byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                    byte lo = (byte)(crc & 0x00FF);         //低位置
    
                    return new byte[] { hi, lo };
                }
                return new byte[] { 0, 0 };
            }
            #endregion
    
            #region  ToCRC16
    
            public static string ToCRC16(string content)
            {
                return ToCRC16(content, Encoding.UTF8);
            }
    
            public static string ToCRC16(string content, bool isReverse)
            {
                return ToCRC16(content, Encoding.UTF8, isReverse);
            }
    
            public static string ToCRC16(string content, Encoding encoding)
            {
                return ByteToString(CRC16(encoding.GetBytes(content)), true);
            }
    
            public static string ToCRC16(string content, Encoding encoding, bool isReverse)
            {
                return ByteToString(CRC16(encoding.GetBytes(content)), isReverse);
            }
    
            public static string ToCRC16(byte[] data)
            {
                return ByteToString(CRC16(data), true);
            }
    
            public static string ToCRC16(byte[] data, bool isReverse)
            {
                return ByteToString(CRC16(data), isReverse);
            }
            #endregion
    
            #region  ToModbusCRC16
    
            public static string ToModbusCRC16(string s)
            {
                return ToModbusCRC16(s, true);
            }
    
            public static string ToModbusCRC16(string s, bool isReverse)
            {
                return ByteToString(CRC16(StringToHexByte(s)), isReverse);
            }
    
            public static string ToModbusCRC16(byte[] data)
            {
                return ToModbusCRC16(data, true);
            }
    
            public static string ToModbusCRC16(byte[] data, bool isReverse)
            {
                return ByteToString(CRC16(data), isReverse);
            }
            #endregion
    
            #region  ByteToString
    
            public static string ByteToString(byte[] arr, bool isReverse)
            {
                try
                {
                    byte hi = arr[0], lo = arr[1];
                    return Convert.ToString(isReverse ? hi + lo * 0x100 : hi * 0x100 + lo, 16).ToUpper().PadLeft(4, '0');
                }
                catch (Exception ex) { throw (ex); }
            }
    
            public static string ByteToString(byte[] arr)
            {
                try
                {
                    return ByteToString(arr, true);
                }
                catch (Exception ex) { throw (ex); }
            }
    
            #endregion
    
            #region  StringToHexString
    
            public static string StringToHexString(string str)
            {
                StringBuilder s = new StringBuilder();
                foreach (short c in str.ToCharArray())
                {
                    s.Append(c.ToString("X4"));
                }
                return s.ToString();
            }
    
            #endregion
    
            #region  StringToHexByte
    
            private static string ConvertChinese(string str)
            {
                StringBuilder s = new StringBuilder();
                foreach (short c in str.ToCharArray())
                {
                    if (c <= 0 || c >= 127)
                    {
                        s.Append(c.ToString("X4"));
                    }
                    else
                    {
                        s.Append((char)c);
                    }
                }
                return s.ToString();
            }
    
            private static string FilterChinese(string str)
            {
                StringBuilder s = new StringBuilder();
                foreach (short c in str.ToCharArray())
                {
                    if (c > 0 && c < 127)
                    {
                        s.Append((char)c);
                    }
                }
                return s.ToString();
            }
    
            /// <summary>
            /// 字符串转16进制字符数组
            /// </summary>
            /// <param name="hex"></param>
            /// <returns></returns>
            public static byte[] StringToHexByte(string str)
            {
                return StringToHexByte(str, false);
            }
    
            /// <summary>
            /// 字符串转16进制字符数组
            /// </summary>
            /// <param name="str"></param>
            /// <param name="isFilterChinese">是否过滤掉中文字符</param>
            /// <returns></returns>
            public static byte[] StringToHexByte(string str, bool isFilterChinese)
            {
                string hex = isFilterChinese ? FilterChinese(str) : ConvertChinese(str);
    
                //清除所有空格
                hex = hex.Replace(" ", "");
                //若字符个数为奇数,补一个0
                hex += hex.Length % 2 != 0 ? "0" : "";
    
                byte[] result = new byte[hex.Length / 2];
                for (int i = 0, c = result.Length; i < c; i++)
                {
                    result[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
                }
                return result;
            }
            #endregion
    
        }

    WPF技术点:

    1.自定义样式按钮

       <Style x:Key="CommonButtonBase" TargetType="{x:Type Button}">
                    <Setter Property="BorderBrush" Value="Transparent"/>
                    <Setter Property="BorderThickness" Value="0"/>
                    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
                    <Setter Property="VerticalContentAlignment" Value="Center"/>
                    <Setter Property="Padding" Value="1"/>
                    <Setter Property="Cursor" Value="Hand"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type Button}">
                                <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" 
                                                CornerRadius="4"
                                                BorderThickness="{TemplateBinding BorderThickness}"
                                                 Background="{TemplateBinding Background}"
                                                 SnapsToDevicePixels="true">
                                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                  Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" 
                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Background" Value="#000000"/>
                                        <Setter Property="Opacity" Value="0.1"/>
                                    </Trigger>
                                    <Trigger Property="IsMouseOver" Value="true">
                                        <Setter Property="Foreground" Value="#FFFF00"/>
                                    </Trigger>
                                    <Trigger Property="IsMouseOver" Value="false">
                                        <Setter Property="Foreground" Value="White"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            
                <Style TargetType="{x:Type Button}" x:Key="Btn0093EABase" BasedOn="{StaticResource CommonButtonBase}">
                    <Setter Property="Background" Value="#0093EA"/>
                    <Setter Property="Foreground" Value="White"/>
                    <Setter Property="FontSize" Value="22"/>
                    <Setter Property="Height" Value="40"/>
                    <Setter Property="Margin" Value="5"/>
                </Style>

    2.转换器用于绑定按钮

     public class FontConverters : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                if (value != null && bool.TryParse(value.ToString(), out bool result))
                {
                    if (result)
                    {
                        return "关闭串口";
                    }
                }
                return "打开串口";
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
        //用于绑定UI的颜色状态显示
    public
    class ColorConverters : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value != null && bool.TryParse(value.ToString(), out bool result)) { if (result) { return new SolidColorBrush((Color)System.Windows.Media.ColorConverter.ConvertFromString("#2E8B57")); } } return new SolidColorBrush((Color)System.Windows.Media.ColorConverter.ConvertFromString("#FF6347")); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }

    3.引用字体

       <TextBlock Text="&#xe6e4;" Margin="20 5 0 5" FontFamily="pack://application:,,,/Font/#iconfont" 
                                       Foreground="White" FontSize="30" VerticalAlignment="Center"/>

    4.绑定命令和元素

    <TextBlock Text="端     口:"  Style="{DynamicResource TxtComStyle}"/>
                            <ComboBox  Grid.Row="0" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.Port}"
                                      ItemsSource="{Binding ComParameterConfig.Port}"
                                       />
                            <TextBlock Text="波 特 率:" Style="{DynamicResource TxtComStyle}"/>
                            <ComboBox  Grid.Row="0" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" SelectedItem="{Binding CurrentParameter.BaudRdate}"
                                       ItemsSource="{Binding ComParameterConfig.BaudRate}"
                                       />
                            <TextBlock Text="数 据 位:" Style="{DynamicResource TxtComStyle}"/>
                            <ComboBox  Grid.Row="0" Grid.Column="2" Style="{StaticResource ComboBoxStyle}"  SelectedItem="{Binding CurrentParameter.DataBit}"
                                       ItemsSource="{Binding ComParameterConfig.DataBit}"
                                       />
                            <TextBlock Text="校 验 位:" Style="{DynamicResource TxtComStyle}"/>
                            <ComboBox  Grid.Row="0" Grid.Column="2" Style="{StaticResource ComboBoxStyle}"  SelectedItem="{Binding CurrentParameter.CheckMode}"
                                       ItemsSource="{Binding ComParameterConfig.CheckMode}"
                                       />
                            <TextBlock Text="停 止 位:" Style="{DynamicResource TxtComStyle}"/>
                            <ComboBox  Grid.Row="0" Grid.Column="2" Style="{StaticResource ComboBoxStyle}"  SelectedItem="{Binding CurrentParameter.StopBit}"
                                       ItemsSource="{Binding ComParameterConfig.StopBit}"
                                       />
                            <TextBlock Text="状     态:" Style="{DynamicResource TxtComStyle}"/>
                            <TextBlock Text="&#xe692;" Style="{DynamicResource TxtComStyle1}"  
    Foreground="{Binding CurrentParameter.IsOpen,Converter={StaticResource ColorConverters}}" />

    写在最后

    主项目的结构图 , 如下:

  • 相关阅读:
    hive笔记:复杂数据类型-array结构
    【数据库】数据库入门(四): SQL查询
    【数据库】数据库入门(三): SQL
    【数据库】数据库入门(二): 关系型数据库
    【数据库】数据库入门(一):基本概念
    mysql 多条数据中,分组获取值最大的数据记录
    log4j.properties配置详解与实例-全部测试通过
    spec开发思路以及理解
    运用 finereport 和 oracle 结合开发报表思路大总结
    Oracle的trim( )、ltrim( )、rtrim( )三个函数的用法及注意事项
  • 原文地址:https://www.cnblogs.com/zh7791/p/9317042.html
Copyright © 2011-2022 走看看