基于C#的电机监控上位机(串口通信+实时波形)

C# WinForms上位机程序,实现电机转速显示、实时波形绘制、串口通信、数据保存等功能。


一、项目结构

复制代码
MotorMonitor/
├── MotorMonitor.sln
├── MotorMonitor.csproj
├── FormMain.cs           # 主窗口
├── FormMain.Designer.cs
├── SerialPortManager.cs  # 串口管理
├── DataProcessor.cs     # 数据处理
├── WaveformChart.cs     # 波形图表
├── MotorData.cs         # 数据结构
├── Program.cs
└── 资源文件/

二、数据结构定义

MotorData.cs

csharp 复制代码
using System;
using System.Collections.Generic;

namespace MotorMonitor
{
    /// <summary>
    /// 电机数据结构
    /// </summary>
    public class MotorData
    {
        public DateTime TimeStamp { get; set; }
        public double RPM { get; set; }           // 转速(转/分钟)
        public double Current { get; set; }       // 电流(A)
        public double Voltage { get; set; }       // 电压(V)
        public double Temperature { get; set; }   // 温度(℃)
        public double Torque { get; set; }        // 扭矩(N·m)
        
        public MotorData()
        {
            TimeStamp = DateTime.Now;
        }
        
        public MotorData(double rpm, double current, double voltage, double temp, double torque)
        {
            TimeStamp = DateTime.Now;
            RPM = rpm;
            Current = current;
            Voltage = voltage;
            Temperature = temp;
            Torque = torque;
        }
    }
    
    /// <summary>
    /// 数据包协议
    /// </summary>
    public class DataProtocol
    {
        // 示例协议格式:$RPM,12.5,220,45,2.5# (转速,电流,电压,温度,扭矩)
        public const byte START_BYTE = 0x24;    // '$'
        public const byte END_BYTE = 0x23;      // '#'
        public const byte SEPARATOR = 0x2C;     // ','
        
        public static bool TryParseData(byte[] buffer, out MotorData data)
        {
            data = null;
            
            try
            {
                string dataStr = System.Text.Encoding.ASCII.GetString(buffer);
                
                if (dataStr.StartsWith("$") && dataStr.EndsWith("#"))
                {
                    string content = dataStr.Trim('$', '#');
                    string[] parts = content.Split(',');
                    
                    if (parts.Length >= 5)
                    {
                        data = new MotorData
                        {
                            RPM = double.Parse(parts[0]),
                            Current = double.Parse(parts[1]),
                            Voltage = double.Parse(parts[2]),
                            Temperature = double.Parse(parts[3]),
                            Torque = double.Parse(parts[4])
                        };
                        return true;
                    }
                }
            }
            catch
            {
                return false;
            }
            
            return false;
        }
        
        public static byte[] CreateDataPacket(MotorData data)
        {
            string packet = string.Format("${0:F1},{1:F2},{2:F1},{3:F1},{4:F2}#",
                data.RPM, data.Current, data.Voltage, data.Temperature, data.Torque);
            return System.Text.Encoding.ASCII.GetBytes(packet);
        }
    }
}

三、串口通信管理

SerialPortManager.cs

csharp 复制代码
using System;
using System.IO.Ports;
using System.Threading;

namespace MotorMonitor
{
    /// <summary>
    /// 串口通信管理器
    /// </summary>
    public class SerialPortManager : IDisposable
    {
        private SerialPort _serialPort;
        private Thread _receiveThread;
        private bool _isReceiving;
        private readonly object _lockObject = new object();
        
        // 事件定义
        public event EventHandler<string> LogMessage;
        public event EventHandler<MotorData> DataReceived;
        public event EventHandler<bool> ConnectionChanged;
        
        public bool IsConnected => _serialPort?.IsOpen ?? false;
        
        public SerialPortManager()
        {
            _serialPort = new SerialPort
            {
                ReadTimeout = 500,
                WriteTimeout = 500
            };
        }
        
        /// <summary>
        /// 获取可用串口列表
        /// </summary>
        public string[] GetAvailablePorts()
        {
            return SerialPort.GetPortNames();
        }
        
        /// <summary>
        /// 连接串口
        /// </summary>
        public bool Connect(string portName, int baudRate = 115200, 
                           Parity parity = Parity.None, int dataBits = 8, 
                           StopBits stopBits = StopBits.One)
        {
            lock (_lockObject)
            {
                if (IsConnected)
                {
                    Disconnect();
                }
                
                try
                {
                    _serialPort.PortName = portName;
                    _serialPort.BaudRate = baudRate;
                    _serialPort.Parity = parity;
                    _serialPort.DataBits = dataBits;
                    _serialPort.StopBits = stopBits;
                    _serialPort.Handshake = Handshake.None;
                    _serialPort.NewLine = "\r\n";
                    
                    _serialPort.Open();
                    
                    OnLogMessage($"串口 {portName} 已连接,波特率 {baudRate}");
                    
                    // 启动接收线程
                    _isReceiving = true;
                    _receiveThread = new Thread(ReceiveDataThread)
                    {
                        IsBackground = true,
                        Priority = ThreadPriority.AboveNormal
                    };
                    _receiveThread.Start();
                    
                    ConnectionChanged?.Invoke(this, true);
                    return true;
                }
                catch (Exception ex)
                {
                    OnLogMessage($"连接失败: {ex.Message}");
                    return false;
                }
            }
        }
        
        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            lock (_lockObject)
            {
                _isReceiving = false;
                
                if (_receiveThread != null && _receiveThread.IsAlive)
                {
                    _receiveThread.Join(1000);
                }
                
                if (_serialPort.IsOpen)
                {
                    _serialPort.Close();
                    OnLogMessage("串口已断开");
                }
                
                ConnectionChanged?.Invoke(this, false);
            }
        }
        
        /// <summary>
        /// 发送数据
        /// </summary>
        public bool SendData(byte[] data)
        {
            lock (_lockObject)
            {
                if (!IsConnected) return false;
                
                try
                {
                    _serialPort.Write(data, 0, data.Length);
                    return true;
                }
                catch (Exception ex)
                {
                    OnLogMessage($"发送失败: {ex.Message}");
                    return false;
                }
            }
        }
        
        public bool SendCommand(string command)
        {
            byte[] data = System.Text.Encoding.ASCII.GetBytes(command + "\r\n");
            return SendData(data);
        }
        
        /// <summary>
        /// 接收数据线程
        /// </summary>
        private void ReceiveDataThread()
        {
            System.Text.StringBuilder buffer = new System.Text.StringBuilder();
            
            while (_isReceiving && _serialPort.IsOpen)
            {
                try
                {
                    // 读取可用数据
                    if (_serialPort.BytesToRead > 0)
                    {
                        string received = _serialPort.ReadExisting();
                        buffer.Append(received);
                        
                        // 处理完整数据包
                        ProcessBuffer(buffer);
                    }
                    else
                    {
                        Thread.Sleep(10);
                    }
                }
                catch (TimeoutException)
                {
                    // 正常超时,继续
                }
                catch (Exception ex)
                {
                    OnLogMessage($"接收错误: {ex.Message}");
                    Thread.Sleep(100);
                }
            }
        }
        
        /// <summary>
        /// 处理接收缓冲区
        /// </summary>
        private void ProcessBuffer(System.Text.StringBuilder buffer)
        {
            string dataStr = buffer.ToString();
            
            // 查找完整的数据包
            int startIndex = dataStr.IndexOf('$');
            int endIndex = dataStr.IndexOf('#');
            
            while (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
            {
                string packet = dataStr.Substring(startIndex, endIndex - startIndex + 1);
                
                // 解析数据包
                if (DataProtocol.TryParseData(System.Text.Encoding.ASCII.GetBytes(packet), 
                    out MotorData data))
                {
                    DataReceived?.Invoke(this, data);
                }
                
                // 移除已处理的数据
                buffer.Remove(startIndex, endIndex - startIndex + 1);
                dataStr = buffer.ToString();
                
                // 查找下一个数据包
                startIndex = dataStr.IndexOf('$');
                endIndex = dataStr.IndexOf('#');
            }
            
            // 如果缓冲区太大,清理
            if (buffer.Length > 1024)
            {
                buffer.Remove(0, buffer.Length - 512);
            }
        }
        
        private void OnLogMessage(string message)
        {
            LogMessage?.Invoke(this, message);
        }
        
        public void Dispose()
        {
            Disconnect();
            _serialPort?.Dispose();
        }
    }
}

四、波形图表控件

WaveformChart.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace MotorMonitor
{
    /// <summary>
    /// 实时波形图表控件
    /// </summary>
    public partial class WaveformChart : UserControl
    {
        private Chart chart;
        private Series seriesRPM;
        private Series seriesCurrent;
        private Series seriesVoltage;
        private Series seriesTemperature;
        
        private Queue<DataPoint> dataPointsRPM = new Queue<DataPoint>();
        private Queue<DataPoint> dataPointsCurrent = new Queue<DataPoint>();
        private Queue<DataPoint> dataPointsVoltage = new Queue<DataPoint>();
        private Queue<DataPoint> dataPointsTemperature = new Queue<DataPoint>();
        
        private int maxPoints = 1000;  // 最多显示点数
        private DateTime startTime = DateTime.Now;
        
        public WaveformChart()
        {
            InitializeChart();
        }
        
        private void InitializeChart()
        {
            chart = new Chart
            {
                Dock = DockStyle.Fill,
                BackColor = Color.FromArgb(240, 240, 240),
                BorderlineColor = Color.Gray,
                BorderlineDashStyle = ChartDashStyle.Solid,
                BorderlineWidth = 1
            };
            
            // 图表区域
            ChartArea chartArea = new ChartArea
            {
                Name = "ChartArea1",
                BackColor = Color.White,
                BorderColor = Color.Gray,
                BorderWidth = 1,
                BorderDashStyle = ChartDashStyle.Solid
            };
            
            // 坐标轴设置
            chartArea.AxisX.Title = "时间 (s)";
            chartArea.AxisX.TitleFont = new Font("Microsoft YaHei", 9, FontStyle.Regular);
            chartArea.AxisX.LabelStyle.Format = "F1";
            chartArea.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
            chartArea.AxisX.MajorGrid.LineColor = Color.LightGray;
            chartArea.AxisX.MinorGrid.Enabled = false;
            
            chartArea.AxisY.Title = "数值";
            chartArea.AxisY.TitleFont = new Font("Microsoft YaHei", 9, FontStyle.Regular);
            chartArea.AxisY.MajorGrid.LineColor = Color.LightGray;
            chartArea.AxisY.MinorGrid.Enabled = false;
            
            chartArea.AxisY2.Title = "温度 (℃) / 扭矩 (N·m)";
            chartArea.AxisY2.TitleFont = new Font("Microsoft YaHei", 9, FontStyle.Regular);
            chartArea.AxisY2.MajorGrid.Enabled = false;
            
            chart.ChartAreas.Add(chartArea);
            
            // 图例
            Legend legend = new Legend
            {
                Name = "Legend1",
                Docking = Docking.Top,
                Alignment = StringAlignment.Center,
                BackColor = Color.Transparent,
                Font = new Font("Microsoft YaHei", 9),
                IsTextAutoFit = false
            };
            chart.Legends.Add(legend);
            
            // 创建数据系列
            seriesRPM = new Series
            {
                Name = "转速 (RPM)",
                ChartType = SeriesChartType.Line,
                Color = Color.Red,
                BorderWidth = 2,
                XValueType = ChartValueType.DateTime,
                YValueType = ChartValueType.Double
            };
            
            seriesCurrent = new Series
            {
                Name = "电流 (A)",
                ChartType = SeriesChartType.Line,
                Color = Color.Blue,
                BorderWidth = 2,
                XValueType = ChartValueType.DateTime,
                YValueType = ChartValueType.Double
            };
            
            seriesVoltage = new Series
            {
                Name = "电压 (V)",
                ChartType = SeriesChartType.Line,
                Color = Color.Green,
                BorderWidth = 2,
                XValueType = ChartValueType.DateTime,
                YValueType = ChartValueType.Double
            };
            
            seriesTemperature = new Series
            {
                Name = "温度 (℃)",
                ChartType = SeriesChartType.Line,
                Color = Color.Orange,
                BorderWidth = 2,
                XValueType = ChartValueType.DateTime,
                YValueType = ChartValueType.Double,
                YAxisType = AxisType.Secondary
            };
            
            chart.Series.Add(seriesRPM);
            chart.Series.Add(seriesCurrent);
            chart.Series.Add(seriesVoltage);
            chart.Series.Add(seriesTemperature);
            
            // 添加到控件
            this.Controls.Add(chart);
        }
        
        /// <summary>
        /// 添加数据点
        /// </summary>
        public void AddDataPoint(MotorData data)
        {
            if (chart.InvokeRequired)
            {
                chart.Invoke(new Action<MotorData>(AddDataPoint), data);
                return;
            }
            
            try
            {
                double timeElapsed = (data.TimeStamp - startTime).TotalSeconds;
                
                // 添加数据点
                DataPoint pointRPM = new DataPoint(timeElapsed, data.RPM);
                DataPoint pointCurrent = new DataPoint(timeElapsed, data.Current);
                DataPoint pointVoltage = new DataPoint(timeElapsed, data.Voltage);
                DataPoint pointTemp = new DataPoint(timeElapsed, data.Temperature);
                
                dataPointsRPM.Enqueue(pointRPM);
                dataPointsCurrent.Enqueue(pointCurrent);
                dataPointsVoltage.Enqueue(pointVoltage);
                dataPointsTemperature.Enqueue(pointTemp);
                
                // 限制数据点数
                while (dataPointsRPM.Count > maxPoints)
                {
                    dataPointsRPM.Dequeue();
                    dataPointsCurrent.Dequeue();
                    dataPointsVoltage.Dequeue();
                    dataPointsTemperature.Dequeue();
                }
                
                // 更新图表
                UpdateChart();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"添加数据点错误: {ex.Message}");
            }
        }
        
        /// <summary>
        /// 更新图表显示
        /// </summary>
        private void UpdateChart()
        {
            try
            {
                // 清空系列
                seriesRPM.Points.Clear();
                seriesCurrent.Points.Clear();
                seriesVoltage.Points.Clear();
                seriesTemperature.Points.Clear();
                
                // 重新添加所有点
                foreach (var point in dataPointsRPM)
                {
                    seriesRPM.Points.Add(point);
                }
                
                foreach (var point in dataPointsCurrent)
                {
                    seriesCurrent.Points.Add(point);
                }
                
                foreach (var point in dataPointsVoltage)
                {
                    seriesVoltage.Points.Add(point);
                }
                
                foreach (var point in dataPointsTemperature)
                {
                    seriesTemperature.Points.Add(point);
                }
                
                // 自动调整Y轴范围
                if (seriesRPM.Points.Count > 0)
                {
                    double minRPM = double.MaxValue;
                    double maxRPM = double.MinValue;
                    
                    foreach (DataPoint point in seriesRPM.Points)
                    {
                        double y = point.YValues[0];
                        if (y < minRPM) minRPM = y;
                        if (y > maxRPM) maxRPM = y;
                    }
                    
                    chart.ChartAreas[0].AxisY.Minimum = Math.Floor(minRPM * 0.9);
                    chart.ChartAreas[0].AxisY.Maximum = Math.Ceiling(maxRPM * 1.1);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"更新图表错误: {ex.Message}");
            }
        }
        
        /// <summary>
        /// 清空图表
        /// </summary>
        public void Clear()
        {
            if (chart.InvokeRequired)
            {
                chart.Invoke(new Action(Clear));
                return;
            }
            
            dataPointsRPM.Clear();
            dataPointsCurrent.Clear();
            dataPointsVoltage.Clear();
            dataPointsTemperature.Clear();
            
            seriesRPM.Points.Clear();
            seriesCurrent.Points.Clear();
            seriesVoltage.Points.Clear();
            seriesTemperature.Points.Clear();
            
            startTime = DateTime.Now;
        }
        
        /// <summary>
        /// 保存图表为图片
        /// </summary>
        public void SaveImage(string filePath)
        {
            try
            {
                chart.SaveImage(filePath, ChartImageFormat.Png);
            }
            catch (Exception ex)
            {
                throw new Exception($"保存图片失败: {ex.Message}", ex);
            }
        }
        
        /// <summary>
        /// 设置最大显示点数
        /// </summary>
        public int MaxPoints
        {
            get { return maxPoints; }
            set
            {
                maxPoints = Math.Max(100, value);
                // 立即应用
                while (dataPointsRPM.Count > maxPoints)
                {
                    dataPointsRPM.Dequeue();
                    dataPointsCurrent.Dequeue();
                    dataPointsVoltage.Dequeue();
                    dataPointsTemperature.Dequeue();
                }
                UpdateChart();
            }
        }
    }
}

五、主窗口界面

FormMain.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Globalization;

namespace MotorMonitor
{
    public partial class FormMain : Form
    {
        private SerialPortManager serialManager;
        private List<MotorData> dataHistory = new List<MotorData>();
        private Timer updateTimer;
        private bool isRecording = false;
        private DateTime startTime;
        
        // 状态颜色
        private readonly Color COLOR_CONNECTED = Color.Green;
        private readonly Color COLOR_DISCONNECTED = Color.Red;
        private readonly Color COLOR_NORMAL = Color.Black;
        private readonly Color COLOR_WARNING = Color.Orange;
        private readonly Color COLOR_ALARM = Color.Red;
        
        // 报警阈值
        private double alarmRPM = 3000;
        private double alarmCurrent = 10.0;
        private double alarmTemperature = 80.0;
        
        public FormMain()
        {
            InitializeComponent();
            InitializeComponents();
        }
        
        private void InitializeComponents()
        {
            // 初始化串口管理器
            serialManager = new SerialPortManager();
            serialManager.LogMessage += SerialManager_LogMessage;
            serialManager.DataReceived += SerialManager_DataReceived;
            serialManager.ConnectionChanged += SerialManager_ConnectionChanged;
            
            // 初始化更新定时器
            updateTimer = new Timer
            {
                Interval = 100,  // 100ms更新一次
                Enabled = false
            };
            updateTimer.Tick += UpdateTimer_Tick;
            
            // 初始化波形图表
            waveformChart1.MaxPoints = 500;
            
            // 初始化下拉框
            InitializeComboBoxes();
            
            // 状态初始化
            UpdateConnectionStatus(false);
        }
        
        private void InitializeComboBoxes()
        {
            // 串口号
            cmbPort.Items.Clear();
            cmbPort.Items.AddRange(serialManager.GetAvailablePorts());
            if (cmbPort.Items.Count > 0)
                cmbPort.SelectedIndex = 0;
            
            // 波特率
            cmbBaudRate.Items.AddRange(new object[] { 9600, 19200, 38400, 57600, 115200, 230400 });
            cmbBaudRate.SelectedItem = 115200;
            
            // 数据位
            cmbDataBits.Items.AddRange(new object[] { 7, 8 });
            cmbDataBits.SelectedItem = 8;
            
            // 停止位
            cmbStopBits.Items.AddRange(new object[] { 1, 1.5, 2 });
            cmbStopBits.SelectedItem = 1;
            
            // 校验位
            cmbParity.Items.AddRange(Enum.GetNames(typeof(Parity)));
            cmbParity.SelectedItem = "None";
        }
        
        #region 事件处理
        
        private void SerialManager_LogMessage(object sender, string message)
        {
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new Action<string>(AddLogMessage), message);
            }
            else
            {
                AddLogMessage(message);
            }
        }
        
        private void SerialManager_DataReceived(object sender, MotorData data)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<MotorData>(ProcessReceivedData), data);
            }
            else
            {
                ProcessReceivedData(data);
            }
        }
        
        private void SerialManager_ConnectionChanged(object sender, bool isConnected)
        {
            if (btnConnect.InvokeRequired)
            {
                btnConnect.Invoke(new Action<bool>(UpdateConnectionStatus), isConnected);
            }
            else
            {
                UpdateConnectionStatus(isConnected);
            }
        }
        
        private void ProcessReceivedData(MotorData data)
        {
            // 添加到历史数据
            dataHistory.Add(data);
            
            // 更新波形
            waveformChart1.AddDataPoint(data);
            
            // 更新数字显示
            UpdateDigitalDisplays(data);
            
            // 检查报警
            CheckAlarms(data);
            
            // 如果正在记录,保存到文件
            if (isRecording)
            {
                AppendDataToFile(data);
            }
        }
        
        private void UpdateDigitalDisplays(MotorData data)
        {
            lblRPM.Text = $"{data.RPM:F0}";
            lblCurrent.Text = $"{data.Current:F2} A";
            lblVoltage.Text = $"{data.Voltage:F1} V";
            lblTemperature.Text = $"{data.Temperature:F1} ℃";
            lblTorque.Text = $"{data.Torque:F2} N·m";
            lblPower.Text = $"{data.Torque * data.RPM / 9549:F2} kW";  // 功率 = 扭矩 * 转速 / 9549
        }
        
        private void CheckAlarms(MotorData data)
        {
            // 检查转速报警
            if (data.RPM > alarmRPM)
            {
                lblRPM.ForeColor = COLOR_ALARM;
                AddLogMessage($"警告: 转速过高 {data.RPM:F0} RPM");
            }
            else if (data.RPM > alarmRPM * 0.9)
            {
                lblRPM.ForeColor = COLOR_WARNING;
            }
            else
            {
                lblRPM.ForeColor = COLOR_NORMAL;
            }
            
            // 检查电流报警
            if (data.Current > alarmCurrent)
            {
                lblCurrent.ForeColor = COLOR_ALARM;
                AddLogMessage($"警告: 电流过大 {data.Current:F2} A");
            }
            else
            {
                lblCurrent.ForeColor = COLOR_NORMAL;
            }
            
            // 检查温度报警
            if (data.Temperature > alarmTemperature)
            {
                lblTemperature.ForeColor = COLOR_ALARM;
                AddLogMessage($"警告: 温度过高 {data.Temperature:F1} ℃");
            }
            else if (data.Temperature > alarmTemperature * 0.9)
            {
                lblTemperature.ForeColor = COLOR_WARNING;
            }
            else
            {
                lblTemperature.ForeColor = COLOR_NORMAL;
            }
        }
        
        private void UpdateConnectionStatus(bool isConnected)
        {
            btnConnect.Text = isConnected ? "断开连接" : "连接";
            btnConnect.BackColor = isConnected ? COLOR_CONNECTED : SystemColors.Control;
            
            lblStatus.Text = isConnected ? "已连接" : "未连接";
            lblStatus.ForeColor = isConnected ? COLOR_CONNECTED : COLOR_DISCONNECTED;
            
            // 更新按钮状态
            cmbPort.Enabled = !isConnected;
            cmbBaudRate.Enabled = !isConnected;
            cmbDataBits.Enabled = !isConnected;
            cmbStopBits.Enabled = !isConnected;
            cmbParity.Enabled = !isConnected;
            
            // 启动/停止定时器
            updateTimer.Enabled = isConnected;
            
            if (!isConnected)
            {
                // 清空数据显示
                lblRPM.Text = "---";
                lblCurrent.Text = "---";
                lblVoltage.Text = "---";
                lblTemperature.Text = "---";
                lblTorque.Text = "---";
                lblPower.Text = "---";
            }
        }
        
        private void UpdateTimer_Tick(object sender, EventArgs e)
        {
            // 更新运行时间显示
            if (serialManager.IsConnected)
            {
                TimeSpan runTime = DateTime.Now - startTime;
                lblRunTime.Text = $"运行时间: {runTime:hh\\:mm\\:ss}";
            }
        }
        
        private void AddLogMessage(string message)
        {
            if (txtLog.TextLength > 10000)
            {
                txtLog.Clear();
            }
            
            txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");
            txtLog.ScrollToCaret();
        }
        
        #endregion
        
        #region 控件事件
        
        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (serialManager.IsConnected)
            {
                serialManager.Disconnect();
            }
            else
            {
                if (cmbPort.SelectedItem == null)
                {
                    MessageBox.Show("请选择串口号", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                
                try
                {
                    string portName = cmbPort.SelectedItem.ToString();
                    int baudRate = (int)cmbBaudRate.SelectedItem;
                    int dataBits = (int)cmbDataBits.SelectedItem;
                    float stopBits = (float)cmbStopBits.SelectedItem;
                    Parity parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.SelectedItem.ToString());
                    
                    bool success = serialManager.Connect(portName, baudRate, parity, dataBits, 
                        stopBits == 1.5f ? StopBits.OnePointFive : (stopBits == 2 ? StopBits.Two : StopBits.One));
                    
                    if (success)
                    {
                        startTime = DateTime.Now;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"连接失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
        
        private void btnRefreshPorts_Click(object sender, EventArgs e)
        {
            string currentPort = cmbPort.SelectedItem?.ToString();
            cmbPort.Items.Clear();
            cmbPort.Items.AddRange(serialManager.GetAvailablePorts());
            
            if (!string.IsNullOrEmpty(currentPort) && cmbPort.Items.Contains(currentPort))
            {
                cmbPort.SelectedItem = currentPort;
            }
            else if (cmbPort.Items.Count > 0)
            {
                cmbPort.SelectedIndex = 0;
            }
        }
        
        private void btnStartRecord_Click(object sender, EventArgs e)
        {
            if (isRecording)
            {
                // 停止记录
                isRecording = false;
                btnStartRecord.Text = "开始记录";
                btnStartRecord.BackColor = SystemColors.Control;
                AddLogMessage("数据记录已停止");
            }
            else
            {
                // 开始记录
                SaveFileDialog saveDialog = new SaveFileDialog
                {
                    Filter = "CSV文件|*.csv|文本文件|*.txt|所有文件|*.*",
                    FileName = $"MotorData_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
                    Title = "保存数据文件"
                };
                
                if (saveDialog.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        // 创建CSV文件头
                        using (StreamWriter writer = new StreamWriter(saveDialog.FileName, false, System.Text.Encoding.UTF8))
                        {
                            writer.WriteLine("时间,转速(RPM),电流(A),电压(V),温度(℃),扭矩(N·m),功率(kW)");
                        }
                        
                        isRecording = true;
                        btnStartRecord.Text = "停止记录";
                        btnStartRecord.BackColor = Color.LightGreen;
                        AddLogMessage($"开始记录数据到: {saveDialog.FileName}");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"创建文件失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }
        
        private void AppendDataToFile(MotorData data)
        {
            try
            {
                // 查找最新的文件
                string[] csvFiles = Directory.GetFiles(Application.StartupPath, "MotorData_*.csv");
                if (csvFiles.Length == 0) return;
                
                string latestFile = csvFiles[0];
                foreach (string file in csvFiles)
                {
                    if (File.GetLastWriteTime(file) > File.GetLastWriteTime(latestFile))
                    {
                        latestFile = file;
                    }
                }
                
                using (StreamWriter writer = new StreamWriter(latestFile, true, System.Text.Encoding.UTF8))
                {
                    double power = data.Torque * data.RPM / 9549;
                    writer.WriteLine($"{data.TimeStamp:HH:mm:ss.fff},{data.RPM:F1},{data.Current:F2},{data.Voltage:F1},{data.Temperature:F1},{data.Torque:F2},{power:F2}");
                }
            }
            catch (Exception ex)
            {
                AddLogMessage($"保存数据错误: {ex.Message}");
            }
        }
        
        private void btnClearChart_Click(object sender, EventArgs e)
        {
            waveformChart1.Clear();
            dataHistory.Clear();
            AddLogMessage("图表已清空");
        }
        
        private void btnSaveImage_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveDialog = new SaveFileDialog
            {
                Filter = "PNG图片|*.png|JPEG图片|*.jpg|BMP图片|*.bmp",
                FileName = $"Waveform_{DateTime.Now:yyyyMMdd_HHmmss}.png",
                Title = "保存波形图片"
            };
            
            if (saveDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    waveformChart1.SaveImage(saveDialog.FileName);
                    AddLogMessage($"波形图已保存: {saveDialog.FileName}");
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
        
        private void btnSendCommand_Click(object sender, EventArgs e)
        {
            if (!serialManager.IsConnected)
            {
                MessageBox.Show("请先连接串口", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            string command = txtCommand.Text.Trim();
            if (!string.IsNullOrEmpty(command))
            {
                bool success = serialManager.SendCommand(command);
                if (success)
                {
                    AddLogMessage($"发送命令: {command}");
                    txtCommand.Clear();
                }
            }
        }
        
        private void btnSetAlarm_Click(object sender, EventArgs e)
        {
            using (FormAlarmSetting form = new FormAlarmSetting(alarmRPM, alarmCurrent, alarmTemperature))
            {
                if (form.ShowDialog() == DialogResult.OK)
                {
                    alarmRPM = form.AlarmRPM;
                    alarmCurrent = form.AlarmCurrent;
                    alarmTemperature = form.AlarmTemperature;
                    
                    AddLogMessage($"报警阈值已更新: 转速>{alarmRPM}RPM, 电流>{alarmCurrent}A, 温度>{alarmTemperature}℃");
                }
            }
        }
        
        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            serialManager?.Dispose();
        }
        
        #endregion
    }
}

六、报警设置窗口

FormAlarmSetting.cs

csharp 复制代码
using System;
using System.Windows.Forms;

namespace MotorMonitor
{
    public partial class FormAlarmSetting : Form
    {
        public double AlarmRPM { get; private set; }
        public double AlarmCurrent { get; private set; }
        public double AlarmTemperature { get; private set; }
        
        public FormAlarmSetting(double rpm, double current, double temp)
        {
            InitializeComponent();
            
            // 初始化控件
            numRPM.Value = (decimal)rpm;
            numCurrent.Value = (decimal)current;
            numTemperature.Value = (decimal)temp;
        }
        
        private void btnOK_Click(object sender, EventArgs e)
        {
            AlarmRPM = (double)numRPM.Value;
            AlarmCurrent = (double)numCurrent.Value;
            AlarmTemperature = (double)numTemperature.Value;
            
            DialogResult = DialogResult.OK;
            Close();
        }
        
        private void btnCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            Close();
        }
    }
}

七、界面设计(FormMain.Designer.cs 部分)

csharp 复制代码
partial class FormMain
{
    private System.ComponentModel.IContainer components = null;
    
    // 控件定义
    private Panel panelTop;
    private GroupBox gbConnection;
    private ComboBox cmbPort;
    private ComboBox cmbBaudRate;
    private ComboBox cmbDataBits;
    private ComboBox cmbStopBits;
    private ComboBox cmbParity;
    private Button btnRefreshPorts;
    private Button btnConnect;
    private Label lblStatus;
    
    private GroupBox gbDataDisplay;
    private Label lblRPM;
    private Label lblCurrent;
    private Label lblVoltage;
    private Label lblTemperature;
    private Label lblTorque;
    private Label lblPower;
    private Label lblRunTime;
    
    private Panel panelMiddle;
    private WaveformChart waveformChart1;
    
    private Panel panelBottom;
    private GroupBox gbControl;
    private Button btnStartRecord;
    private Button btnClearChart;
    private Button btnSaveImage;
    private Button btnSetAlarm;
    
    private GroupBox gbCommand;
    private TextBox txtCommand;
    private Button btnSendCommand;
    
    private GroupBox gbLog;
    private TextBox txtLog;
    
    private Label label1, label2, label3, label4, label5, label6, label7, label8, label9, label10;
    
    private void InitializeComponent()
    {
        this.components = new System.ComponentModel.Container();
        System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
        
        // 主窗口设置
        this.Text = "电机监控系统";
        this.Size = new System.Drawing.Size(1200, 800);
        this.StartPosition = FormStartPosition.CenterScreen;
        this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
        
        // 顶部面板
        panelTop = new Panel
        {
            Dock = DockStyle.Top,
            Height = 120,
            BorderStyle = BorderStyle.FixedSingle
        };
        
        // 连接设置组
        gbConnection = new GroupBox
        {
            Text = "串口设置",
            Location = new Point(10, 10),
            Size = new Size(400, 100)
        };
        
        // 串口控件
        label1 = new Label { Text = "端口:", Location = new Point(10, 25), Size = new Size(40, 20) };
        cmbPort = new ComboBox { Location = new Point(50, 22), Size = new Size(80, 21) };
        
        label2 = new Label { Text = "波特率:", Location = new Point(140, 25), Size = new Size(50, 20) };
        cmbBaudRate = new ComboBox { Location = new Point(190, 22), Size = new Size(80, 21) };
        
        label3 = new Label { Text = "数据位:", Location = new Point(280, 25), Size = new Size(50, 20) };
        cmbDataBits = new ComboBox { Location = new Point(330, 22), Size = new Size(50, 21) };
        
        label4 = new Label { Text = "停止位:", Location = new Point(10, 55), Size = new Size(50, 20) };
        cmbStopBits = new ComboBox { Location = new Point(60, 52), Size = new Size(50, 21) };
        
        label5 = new Label { Text = "校验位:", Location = new Point(120, 55), Size = new Size(50, 20) };
        cmbParity = new ComboBox { Location = new Point(170, 52), Size = new Size(80, 21) };
        
        btnRefreshPorts = new Button { Text = "刷新", Location = new Point(260, 50), Size = new Size(60, 25) };
        btnConnect = new Button { Text = "连接", Location = new Point(330, 50), Size = new Size(60, 25) };
        
        gbConnection.Controls.AddRange(new Control[] { label1, cmbPort, label2, cmbBaudRate, label3, cmbDataBits,
            label4, cmbStopBits, label5, cmbParity, btnRefreshPorts, btnConnect });
        
        // 数据显示组
        gbDataDisplay = new GroupBox
        {
            Text = "电机数据",
            Location = new Point(420, 10),
            Size = new Size(350, 100)
        };
        
        label6 = new Label { Text = "转速:", Location = new Point(10, 25), Size = new Size(40, 20) };
        lblRPM = new Label { Text = "---", Location = new Point(50, 25), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 10, FontStyle.Bold), ForeColor = Color.Red };
        
        label7 = new Label { Text = "电流:", Location = new Point(120, 25), Size = new Size(40, 20) };
        lblCurrent = new Label { Text = "---", Location = new Point(160, 25), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 9, FontStyle.Bold) };
        
        label8 = new Label { Text = "电压:", Location = new Point(230, 25), Size = new Size(40, 20) };
        lblVoltage = new Label { Text = "---", Location = new Point(270, 25), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 9, FontStyle.Bold) };
        
        label9 = new Label { Text = "温度:", Location = new Point(10, 55), Size = new Size(40, 20) };
        lblTemperature = new Label { Text = "---", Location = new Point(50, 55), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 9, FontStyle.Bold) };
        
        label10 = new Label { Text = "扭矩:", Location = new Point(120, 55), Size = new Size(40, 20) };
        lblTorque = new Label { Text = "---", Location = new Point(160, 55), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 9, FontStyle.Bold) };
        
        Label label11 = new Label { Text = "功率:", Location = new Point(230, 55), Size = new Size(40, 20) };
        lblPower = new Label { Text = "---", Location = new Point(270, 55), Size = new Size(60, 20), 
            Font = new Font("Microsoft YaHei", 9, FontStyle.Bold) };
        
        gbDataDisplay.Controls.AddRange(new Control[] { label6, lblRPM, label7, lblCurrent, label8, lblVoltage,
            label9, lblTemperature, label10, lblTorque, label11, lblPower });
        
        // 状态显示
        lblStatus = new Label
        {
            Text = "未连接",
            ForeColor = Color.Red,
            Font = new Font("Microsoft YaHei", 10, FontStyle.Bold),
            Location = new Point(780, 20),
            Size = new Size(100, 30)
        };
        
        lblRunTime = new Label
        {
            Text = "运行时间: 00:00:00",
            Location = new Point(780, 60),
            Size = new Size(150, 20)
        };
        
        panelTop.Controls.AddRange(new Control[] { gbConnection, gbDataDisplay, lblStatus, lblRunTime });
        
        // 中间面板 - 波形图
        panelMiddle = new Panel
        {
            Dock = DockStyle.Fill,
            BorderStyle = BorderStyle.FixedSingle
        };
        
        waveformChart1 = new WaveformChart
        {
            Dock = DockStyle.Fill
        };
        panelMiddle.Controls.Add(waveformChart1);
        
        // 底部面板
        panelBottom = new Panel
        {
            Dock = DockStyle.Bottom,
            Height = 180,
            BorderStyle = BorderStyle.FixedSingle
        };
        
        // 控制组
        gbControl = new GroupBox
        {
            Text = "控制",
            Location = new Point(10, 10),
            Size = new Size(200, 150)
        };
        
        btnStartRecord = new Button { Text = "开始记录", Location = new Point(20, 25), Size = new Size(160, 30) };
        btnClearChart = new Button { Text = "清空图表", Location = new Point(20, 65), Size = new Size(160, 30) };
        btnSaveImage = new Button { Text = "保存图片", Location = new Point(20, 105), Size = new Size(160, 30) };
        btnSetAlarm = new Button { Text = "报警设置", Location = new Point(20, 145), Size = new Size(160, 30) };
        
        gbControl.Controls.AddRange(new Control[] { btnStartRecord, btnClearChart, btnSaveImage, btnSetAlarm });
        
        // 命令组
        gbCommand = new GroupBox
        {
            Text = "命令发送",
            Location = new Point(220, 10),
            Size = new Size(300, 150)
        };
        
        txtCommand = new TextBox
        {
            Location = new Point(10, 25),
            Size = new Size(280, 80),
            Multiline = true,
            ScrollBars = ScrollBars.Vertical
        };
        
        btnSendCommand = new Button
        {
            Text = "发送",
            Location = new Point(220, 110),
            Size = new Size(70, 30)
        };
        
        gbCommand.Controls.AddRange(new Control[] { txtCommand, btnSendCommand });
        
        // 日志组
        gbLog = new GroupBox
        {
            Text = "系统日志",
            Location = new Point(530, 10),
            Size = new Size(640, 150)
        };
        
        txtLog = new TextBox
        {
            Dock = DockStyle.Fill,
            Multiline = true,
            ReadOnly = true,
            ScrollBars = ScrollBars.Vertical,
            BackColor = Color.Black,
            ForeColor = Color.Lime,
            Font = new Font("Consolas", 9)
        };
        
        gbLog.Controls.Add(txtLog);
        
        panelBottom.Controls.AddRange(new Control[] { gbControl, gbCommand, gbLog });
        
        // 添加所有面板到主窗口
        this.Controls.AddRange(new Control[] { panelTop, panelMiddle, panelBottom });
        
        // 绑定事件
        btnConnect.Click += btnConnect_Click;
        btnRefreshPorts.Click += btnRefreshPorts_Click;
        btnStartRecord.Click += btnStartRecord_Click;
        btnClearChart.Click += btnClearChart_Click;
        btnSaveImage.Click += btnSaveImage_Click;
        btnSendCommand.Click += btnSendCommand_Click;
        btnSetAlarm.Click += btnSetAlarm_Click;
        
        this.FormClosing += FormMain_FormClosing;
    }
}

参考代码 上位机显示电机转速和波形 www.youwenfan.com/contentcsv/111859.html

八、安装和使用说明

1. NuGet包依赖

在项目中添加以下NuGet包:

xml 复制代码
<!-- 在.csproj文件中 -->
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
<PackageReference Include="System.Windows.Forms.DataVisualization" Version="1.0.0" />

或通过NuGet管理器安装:

  • System.IO.Ports
  • System.Windows.Forms.DataVisualization

2. 运行要求

  • .NET Framework 4.7.2 或更高版本
  • Windows 7/8/10/11
  • 串口通信支持

3. 使用步骤

  1. 连接设备

    • 选择正确的串口号
    • 设置波特率(通常115200)
    • 点击"连接"按钮
  2. 数据显示

    • 实时显示转速、电流、电压、温度、扭矩
    • 波形图显示历史数据
    • 数字会根据阈值变化颜色(正常→黄色→红色)
  3. 数据记录

    • 点击"开始记录"保存数据到CSV文件
    • 点击"保存图片"保存波形图
    • 点击"清空图表"清除历史数据
  4. 命令发送

    • 在下方的文本框中输入命令
    • 点击"发送"按钮发送到下位机

4. 下位机数据格式

下位机应发送以下格式的数据包:

复制代码
$转速,电流,电压,温度,扭矩#

例如:$1500,5.2,24.5,45.2,3.8#


九、扩展功能建议

1. 添加网络通信支持

csharp 复制代码
public class NetworkManager
{
    // 支持TCP/UDP通信
    public bool ConnectTCP(string ip, int port) { ... }
    public bool ConnectUDP(int localPort) { ... }
}

2. 添加数据回放功能

csharp 复制代码
public class DataReplayer
{
    public void LoadDataFromFile(string filePath) { ... }
    public void Play() { ... }
    public void Pause() { ... }
    public void Seek(TimeSpan time) { ... }
}

3. 添加FFT频谱分析

csharp 复制代码
public class SpectrumAnalyzer
{
    public double[] ComputeFFT(double[] timeDomain) { ... }
    public void DisplaySpectrum(Chart chart) { ... }
    public double FindDominantFrequency() { ... }
}

4. 添加数据库支持

csharp 复制代码
public class DatabaseManager
{
    public void SaveToSQLite(MotorData data) { ... }
    public List<MotorData> LoadHistory(DateTime start, DateTime end) { ... }
    public void ExportToExcel(string filePath) { ... }
}

十、故障排除

问题 可能原因 解决方案
无法连接串口 端口被占用 关闭其他占用端口的程序
无数据显示 波特率不匹配 检查下位机波特率设置
波形图卡顿 数据点太多 减少最大显示点数
数据乱码 协议格式错误 检查下位机数据格式
保存失败 文件被占用 关闭已打开的数据文件
相关推荐
星栈独行1 小时前
Makepad、egui、Dioxus、Tauri:Rust GUI 到底怎么选
开发语言·后端·程序人生·ui·rust
△曉風殘月〆1 小时前
C#如何Hook托管函数
c#·hook
兰令水1 小时前
leecodecode【回溯组合】【2026.6.5打卡-java版本】
java·开发语言
zyl837211 小时前
Python 线性代数:矩阵与向量
开发语言·python·机器学习
雪豹阿伟1 小时前
18.C# —— 三层结构 + 接口架构实战(UI+Model+DAL+IDAL)
c#·上位机
AC赳赳老秦1 小时前
OpenClaw+MySQL 深度应用:自动生成建表语句、索引优化建议与数据迁移脚本
开发语言·数据库·人工智能·python·mysql·算法·openclaw
yangyongdehao301 小时前
桌面宠物开发记:从Rust到Tauri的探索之旅
开发语言·rust·宠物
想取一个与众不同的名字好难2 小时前
安卓设置亮度的时候,系统会在100%与0%反复横跳
android·java·开发语言
郝学胜-神的一滴2 小时前
Qt 高级开发 025:打造优雅界面的艺术与高效重构之道
开发语言·c++·qt·程序人生·重构·软件构建·用户界面