基于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) { ... }
}

十、故障排除

问题 可能原因 解决方案
无法连接串口 端口被占用 关闭其他占用端口的程序
无数据显示 波特率不匹配 检查下位机波特率设置
波形图卡顿 数据点太多 减少最大显示点数
数据乱码 协议格式错误 检查下位机数据格式
保存失败 文件被占用 关闭已打开的数据文件
相关推荐
小大宇21 分钟前
python flask框架 SSE流式返回、跨域、报错
开发语言·python·flask
柒星栈1 小时前
PHP 源码怎么加密防破解?三套方案实战指南
开发语言·php·android studio
勉灬之1 小时前
Next.js + Prisma 跨平台部署踩坑记
开发语言·javascript·ecmascript
这是个栗子2 小时前
前端开发中的常用工具函数(九)
开发语言·javascript·ecmascript·at
蓝创工坊Blue Foundry2 小时前
图片文字提取到 Excel:批量任务如何先定义要交付的字段
运维·服务器·开发语言·数据库·自动化·ocr·excel
会飞的小新2 小时前
C 标准库之 <fenv.h> 详解与深度解析
c语言·开发语言·microsoft
雪的季节2 小时前
dir (obj) 与dir()
开发语言
暗暗别做白日梦3 小时前
Pulsar 消息同步机制
c#·linq
大不点wow3 小时前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
阿里嘎多学长3 小时前
2026-07-22 GitHub 热点项目精选
开发语言·程序员·github·代码托管