基于 C# WinForms 的 Windows 系统监控工具开发实战

前言

最近开发了一个轻量级的 Windows 系统监控工具,可以实时监控 CPU、内存、GPU、温度、网络流量等硬件信息,并通过曲线图表直观展示。整个项目基于 .NET 10.0 和 WinForms 框架,使用了 LibreHardwareMonitor 硬件监控库和 ScottPlot 图表库。

本文分享一下开发过程中的核心技术点和关键代码实现。

技术栈

  • .NET 10.0 - 最新的 .NET 平台
  • Windows Forms - 桌面 UI 框架
  • LibreHardwareMonitor 0.9.3 - 硬件传感器数据采集
  • ScottPlot 5.0 - 高性能实时图表绘制
  • PerformanceCounter - Windows 性能计数器

核心功能

  1. 实时监控 CPU 使用率、主频、温度
  2. 内存使用情况监控
  3. GPU 使用率和温度监控
  4. 网络上传/下载速度统计
  5. 磁盘 I/O 读写速度
  6. 电池电量和充电状态(笔记本)
  7. 历史数据曲线图表展示
  8. 任务栏悬浮小窗口(支持三种显示模式)
  9. 配置持久化存储

一、硬件监控实现

1.1 系统监控服务设计

系统监控服务是整个工具的核心,负责采集各类硬件数据。主要使用两种方式:

  • PerformanceCounter:采集 CPU 使用率、内存使用率、网络流量等
  • LibreHardwareMonitor:采集温度、频率、GPU 等硬件传感器数据

核心数据结构:

csharp 复制代码
public class MonitorData
{
    // CPU信息
    public float CpuUsage { get; set; }
    public float CpuFrequency { get; set; }  // CPU当前主频 (MHz)
    public float CpuTemperature { get; set; }
    public int CpuCoreCount { get; set; }
    public int CpuThreadCount { get; set; }
    public string CpuName { get; set; } = string.Empty;

    // 内存信息
    public float MemoryUsage { get; set; }
    public long TotalMemoryMB { get; set; }
    public long UsedMemoryMB { get; set; }
    public long AvailableMemoryMB { get; set; }

    // GPU信息
    public float GpuTemperature { get; set; }
    public float GpuUsage { get; set; }
    public string GpuName { get; set; } = string.Empty;

    // 网络信息
    public float NetworkUploadSpeed { get; set; }    // KB/s
    public float NetworkDownloadSpeed { get; set; }  // KB/s

    // 磁盘信息
    public float DiskUsage { get; set; }
    public long DiskReadSpeed { get; set; }
    public long DiskWriteSpeed { get; set; }

    // 电池信息
    public int BatteryLevel { get; set; }
    public bool IsCharging { get; set; }
}

1.2 性能计数器初始化

csharp 复制代码
public class SystemMonitor : IDisposable
{
    private readonly Computer _computer;
    private readonly PerformanceCounter _cpuCounter;
    private readonly PerformanceCounter _ramCounter;
    private PerformanceCounter? _networkSentCounter;
    private PerformanceCounter? _networkReceivedCounter;

    public SystemMonitor()
    {
        // 初始化性能计数器
        _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
        _ramCounter = new PerformanceCounter("Memory", "% Committed Bytes In Use");

        // 初始化网络计数器
        InitializeNetworkCounters();

        // 初始化 LibreHardwareMonitor
        _computer = new Computer
        {
            IsCpuEnabled = true,
            IsGpuEnabled = true,
            IsMemoryEnabled = true,
            IsBatteryEnabled = true,
            IsNetworkEnabled = true,
            IsStorageEnabled = true
        };
        _computer.Open();
    }
}

1.3 网络流量监控

网络流量监控需要注意的是 PerformanceCounter 返回的是累计值,需要计算两次采样之间的差值来得到速度:

csharp 复制代码
private void InitializeNetworkCounters()
{
    try
    {
        var category = new PerformanceCounterCategory("Network Interface");
        var instanceNames = category.GetInstanceNames();
        if (instanceNames.Length > 0)
        {
            string networkInterface = instanceNames[0];
            _networkSentCounter = new PerformanceCounter(
                "Network Interface", "Bytes Sent/sec", networkInterface);
            _networkReceivedCounter = new PerformanceCounter(
                "Network Interface", "Bytes Received/sec", networkInterface);
        }
    }
    catch { }
}

private void UpdateNetworkData(MonitorData data)
{
    try
    {
        if (_networkSentCounter != null && _networkReceivedCounter != null)
        {
            long bytesSent = (long)_networkSentCounter.NextValue();
            long bytesReceived = (long)_networkReceivedCounter.NextValue();

            var now = DateTime.Now;
            var timeDiff = (now - _lastNetworkUpdate).TotalSeconds;

            if (timeDiff > 0 && _lastBytesSent > 0)
            {
                // 计算速度 (KB/s)
                data.NetworkUploadSpeed =
                    (float)((bytesSent - _lastBytesSent) / timeDiff / 1024);
                data.NetworkDownloadSpeed =
                    (float)((bytesReceived - _lastBytesReceived) / timeDiff / 1024);
            }

            _lastBytesSent = bytesSent;
            _lastBytesReceived = bytesReceived;
            _lastNetworkUpdate = now;
        }
    }
    catch { }
}

1.4 温度和频率监控

使用 LibreHardwareMonitor 获取 CPU/GPU 温度和频率信息:

csharp 复制代码
private void UpdateData()
{
    var data = new MonitorData();

    // 获取 CPU 使用率
    data.CpuUsage = _cpuCounter.NextValue();

    // 更新硬件传感器信息
    foreach (var hardware in _computer.Hardware)
    {
        hardware.Update();

        // CPU 温度和频率
        if (hardware.HardwareType == HardwareType.Cpu)
        {
            foreach (var sensor in hardware.Sensors)
            {
                if (sensor.SensorType == SensorType.Temperature && sensor.Value.HasValue)
                {
                    data.CpuTemperature = Math.Max(data.CpuTemperature, sensor.Value.Value);
                }

                // 获取 CPU 主频
                if (sensor.SensorType == SensorType.Clock &&
                    sensor.Name.Contains("Core") && sensor.Value.HasValue)
                {
                    data.CpuFrequency = Math.Max(data.CpuFrequency, sensor.Value.Value);
                }
            }
        }

        // GPU 温度和使用率
        if (hardware.HardwareType == HardwareType.GpuNvidia ||
            hardware.HardwareType == HardwareType.GpuAmd ||
            hardware.HardwareType == HardwareType.GpuIntel)
        {
            foreach (var sensor in hardware.Sensors)
            {
                if (sensor.SensorType == SensorType.Temperature && sensor.Value.HasValue)
                {
                    data.GpuTemperature = Math.Max(data.GpuTemperature, sensor.Value.Value);
                }

                if (sensor.SensorType == SensorType.Load &&
                    sensor.Name.Contains("Core") && sensor.Value.HasValue)
                {
                    data.GpuUsage = Math.Max(data.GpuUsage, sensor.Value.Value);
                }
            }
        }
    }

    DataUpdated?.Invoke(this, data);
}

1.5 电池状态监控(Win11 兼容)

电池状态监控在 Win11 上需要特别处理,使用 SystemInformation.PowerStatus API:

csharp 复制代码
try
{
    var status = System.Windows.Forms.SystemInformation.PowerStatus;

    // 检查是否有电池
    if (status.BatteryLifePercent >= 0 && status.BatteryLifePercent <= 1 &&
        status.BatteryChargeStatus != System.Windows.Forms.BatteryChargeStatus.NoSystemBattery)
    {
        // 电池百分比
        data.BatteryLevel = (int)(status.BatteryLifePercent * 100);

        // 充电状态
        data.IsCharging = status.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Online ||
                         status.BatteryChargeStatus.HasFlag(System.Windows.Forms.BatteryChargeStatus.Charging);
    }
    else
    {
        // 台式机或没有电池的设备
        data.BatteryLevel = -1;  // 用 -1 表示无电池
        data.IsCharging = false;
    }
}
catch
{
    data.BatteryLevel = -1;
    data.IsCharging = false;
}

二、历史数据管理

为了绘制历史曲线,需要维护一个固定大小的数据队列:

csharp 复制代码
public class DataHistory
{
    private readonly int _maxDataPoints;
    private readonly Queue<float> _cpuHistory;
    private readonly Queue<float> _memoryHistory;
    private readonly Queue<float> _cpuTempHistory;
    private readonly Queue<float> _gpuTempHistory;
    // ... 其他数据队列

    public DataHistory(int maxDataPoints = 60)
    {
        _maxDataPoints = maxDataPoints;
        _cpuHistory = new Queue<float>(maxDataPoints);
        _memoryHistory = new Queue<float>(maxDataPoints);
        // 初始化其他队列...
    }

    public void AddData(MonitorData data)
    {
        AddToQueue(_cpuHistory, data.CpuUsage);
        AddToQueue(_memoryHistory, data.MemoryUsage);
        AddToQueue(_cpuTempHistory, data.CpuTemperature);
        // 添加其他数据...
    }

    private void AddToQueue(Queue<float> queue, float value)
    {
        if (queue.Count >= _maxDataPoints)
        {
            queue.Dequeue();
        }
        queue.Enqueue(value);
    }

    public double[] GetCpuHistory() => _cpuHistory.Select(x => (double)x).ToArray();
    public double[] GetMemoryHistory() => _memoryHistory.Select(x => (double)x).ToArray();
    // 其他获取方法...
}

三、ScottPlot 图表绘制

3.1 图表初始化

ScottPlot 5.0 提供了强大的实时图表绘制能力:

csharp 复制代码
private void SetupPlot(FormsPlot plot, string title, Color lineColor, double yMin = 0, double yMax = 100)
{
    plot.Plot.Title(title);
    plot.Plot.Axes.Title.Label.ForeColor = ScottColor.FromColor(Color.White);
    plot.Plot.FigureBackground.Color = ScottColor.FromColor(Color.FromArgb(40, 40, 40));
    plot.Plot.DataBackground.Color = ScottColor.FromColor(Color.FromArgb(30, 30, 30));
    plot.Plot.Axes.Color(ScottColor.FromColor(Color.Gray));
    plot.Plot.Grid.MajorLineColor = ScottColor.FromColor(Color.FromArgb(60, 60, 60));

    // 设置 Y 轴范围
    plot.Plot.Axes.SetLimitsY(yMin, yMax);
}

3.2 单线图表更新

csharp 复制代码
private void UpdatePlot(FormsPlot? plot, double[] data, Color lineColor)
{
    if (plot == null || data.Length == 0) return;

    plot.Plot.Clear();

    var signal = plot.Plot.Add.Signal(data);
    signal.Color = ScottColor.FromColor(lineColor);
    signal.LineWidth = 2;

    plot.Plot.Axes.SetLimitsX(0, data.Length);
    plot.Plot.Axes.SetLimitsY(0, 100);

    plot.Refresh();
}

3.3 双线图表(温度/网络)

温度图表同时显示 CPU 和 GPU 温度,网络图表同时显示上传和下载速度:

csharp 复制代码
private void UpdateDualLinePlot(FormsPlot? plot, double[] data1, double[] data2,
    string legend1, string legend2, Color color1, Color color2)
{
    if (plot == null) return;

    plot.Plot.Clear();

    if (data1.Length > 0)
    {
        var signal1 = plot.Plot.Add.Signal(data1);
        signal1.Color = ScottColor.FromColor(color1);
        signal1.LineWidth = 2;
        signal1.LegendText = legend1;
    }

    if (data2.Length > 0)
    {
        var signal2 = plot.Plot.Add.Signal(data2);
        signal2.Color = ScottColor.FromColor(color2);
        signal2.LineWidth = 2;
        signal2.LegendText = legend2;
    }

    plot.Plot.ShowLegend();
    plot.Plot.Axes.SetLimitsX(0, Math.Max(data1.Length, data2.Length));

    // 自动调整 Y 轴范围
    if (data1.Length > 0 || data2.Length > 0)
    {
        double maxValue = 0;
        if (data1.Length > 0) maxValue = Math.Max(maxValue, data1.Max());
        if (data2.Length > 0) maxValue = Math.Max(maxValue, data2.Max());

        plot.Plot.Axes.SetLimitsY(0, Math.Max(10, maxValue * 1.2));
    }

    plot.Refresh();
}

四、任务栏悬浮窗口

4.1 窗口基本设置

任务栏小窗口使用无边框窗体,置顶显示,不在任务栏显示:

csharp 复制代码
private void InitializeComponent()
{
    this.SuspendLayout();

    this.AutoScaleDimensions = new SizeF(7F, 17F);
    this.AutoScaleMode = AutoScaleMode.Font;
    this.ClientSize = new Size(400, 135);
    this.FormBorderStyle = FormBorderStyle.None;
    this.Name = "TaskbarWindow";
    this.TopMost = true;
    this.ShowInTaskbar = false;
    this.StartPosition = FormStartPosition.Manual;
    this.BackColor = Color.FromArgb(30, 30, 30);

    this.ResumeLayout(false);
}

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW - 不显示在 Alt+Tab 中
        return cp;
    }
}

4.2 三种显示模式

小窗口支持三种显示模式,通过右键菜单切换:

模式 0:CPU + 内存图表

csharp 复制代码
private void SetupMode0_CpuMemory()
{
    _cpuPlot = new FormsPlot
    {
        Location = new Point(5, 30),
        Size = new Size(190, 100),
        BackColor = Color.FromArgb(40, 40, 40)
    };
    SetupPlot(_cpuPlot, "CPU", Color.FromArgb(0, 174, 219));
    this.Controls.Add(_cpuPlot);

    _memoryPlot = new FormsPlot
    {
        Location = new Point(205, 30),
        Size = new Size(190, 100),
        BackColor = Color.FromArgb(40, 40, 40)
    };
    SetupPlot(_memoryPlot, "内存", Color.FromArgb(142, 68, 173));
    this.Controls.Add(_memoryPlot);
}

模式 1:温度 + 网络图表

csharp 复制代码
private void SetupMode1_TempNetwork()
{
    _tempPlot = new FormsPlot
    {
        Location = new Point(5, 30),
        Size = new Size(190, 100),
        BackColor = Color.FromArgb(40, 40, 40)
    };
    SetupPlot(_tempPlot, "温度", Color.FromArgb(231, 76, 60));
    this.Controls.Add(_tempPlot);

    _networkPlot = new FormsPlot
    {
        Location = new Point(205, 30),
        Size = new Size(190, 100),
        BackColor = Color.FromArgb(40, 40, 40)
    };
    SetupPlot(_networkPlot, "网络", Color.FromArgb(52, 152, 219));
    this.Controls.Add(_networkPlot);
}

模式 2:详细信息列表

csharp 复制代码
private void SetupMode2_DetailedInfo()
{
    int yPos = 30;
    int labelHeight = 13;

    var labels = new[]
    {
        ("CPU使用率", "0%"),
        ("CPU频率", "0 MHz"),
        ("CPU温度", "0°C"),
        ("GPU使用率", "0%"),
        ("GPU温度", "0°C"),
        ("内存使用率", "0%"),
        ("网络上传", "0 KB/s"),
        ("网络下载", "0 KB/s")
    };

    foreach (var (name, value) in labels)
    {
        var nameLabel = new Label
        {
            Text = name + ":",
            Location = new Point(10, yPos),
            Size = new Size(100, labelHeight),
            ForeColor = Color.FromArgb(200, 200, 200),
            Font = new Font("Microsoft YaHei UI", 8F),
            TextAlign = ContentAlignment.MiddleLeft
        };
        this.Controls.Add(nameLabel);

        var valueLabel = new Label
        {
            Text = value,
            Location = new Point(120, yPos),
            Size = new Size(270, labelHeight),
            ForeColor = Color.White,
            Font = new Font("Microsoft YaHei UI", 8F, FontStyle.Bold),
            TextAlign = ContentAlignment.MiddleLeft,
            Tag = name // 用于在更新时识别标签
        };
        this.Controls.Add(valueLabel);

        yPos += labelHeight;
    }
}

4.3 窗口拖拽功能

实现窗口拖拽移动:

csharp 复制代码
private Point _dragStartPoint;
private bool _isDragging = false;

private void TaskbarWindow_MouseDown(object? sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        _isDragging = true;
        _dragStartPoint = e.Location;
    }
}

private void TaskbarWindow_MouseMove(object? sender, MouseEventArgs e)
{
    if (_isDragging)
    {
        Point newLocation = this.Location;
        newLocation.X += e.X - _dragStartPoint.X;
        newLocation.Y += e.Y - _dragStartPoint.Y;
        this.Location = newLocation;
    }

    if (e.Button == MouseButtons.None)
    {
        _isDragging = false;
    }
}

4.4 右键菜单切换模式

csharp 复制代码
private void SetupContextMenu()
{
    var contextMenu = new ContextMenuStrip();
    var modeMenu = new ToolStripMenuItem("切换显示模式");

    var mode0 = new ToolStripMenuItem("CPU + 内存图表")
    {
        Checked = _displayMode == 0
    };
    mode0.Click += (s, e) => SwitchDisplayMode(0);

    var mode1 = new ToolStripMenuItem("温度 + 网络图表")
    {
        Checked = _displayMode == 1
    };
    mode1.Click += (s, e) => SwitchDisplayMode(1);

    var mode2 = new ToolStripMenuItem("详细信息列表")
    {
        Checked = _displayMode == 2
    };
    mode2.Click += (s, e) => SwitchDisplayMode(2);

    modeMenu.DropDownItems.Add(mode0);
    modeMenu.DropDownItems.Add(mode1);
    modeMenu.DropDownItems.Add(mode2);

    contextMenu.Items.Add(modeMenu);
    contextMenu.Items.Add(new ToolStripSeparator());
    contextMenu.Items.Add("关闭", null, (s, e) => this.Close());

    this.ContextMenuStrip = contextMenu;
}

private void SwitchDisplayMode(int mode)
{
    _displayMode = mode;
    _settings.TaskbarWindowDisplayMode = mode;

    // 保存配置
    Utils.SettingsManager.Save(_settings);

    // 重新创建界面
    SetupWindow();
}

五、配置持久化

5.1 配置数据结构

csharp 复制代码
public class AppSettings
{
    public bool AutoStart { get; set; } = false;
    public int RefreshInterval { get; set; } = 1000;
    public bool ShowTaskbarWindow { get; set; } = true;
    public int TaskbarWindowX { get; set; } = -1;
    public int TaskbarWindowY { get; set; } = -1;
    public int HistoryDuration { get; set; } = 120;
    public bool StartMinimized { get; set; } = false;
    public bool EnableTemperatureMonitoring { get; set; } = true;
    public int MainWindowWidth { get; set; } = 900;
    public int MainWindowHeight { get; set; } = 600;
    public int TaskbarWindowDisplayMode { get; set; } = 0;
}

5.2 JSON 序列化存储

csharp 复制代码
public static class SettingsManager
{
    private static readonly string SettingsPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "WindowsMonitor",
        "settings.json"
    );

    public static AppSettings Load()
    {
        try
        {
            if (File.Exists(SettingsPath))
            {
                string json = File.ReadAllText(SettingsPath);
                return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
            }
        }
        catch { }

        return new AppSettings();
    }

    public static void Save(AppSettings settings)
    {
        try
        {
            string directory = Path.GetDirectoryName(SettingsPath)!;
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            var options = new JsonSerializerOptions { WriteIndented = true };
            string json = JsonSerializer.Serialize(settings, options);
            File.WriteAllText(SettingsPath, json);
        }
        catch { }
    }

    public static string GetSettingsPath() => SettingsPath;
}

六、开机自启动

6.1 注册表方式实现

csharp 复制代码
public static class AutoStartManager
{
    private const string RegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
    private const string AppName = "WindowsMonitor";

    public static void SetAutoStart(bool enable)
    {
        try
        {
            using var key = Registry.CurrentUser.OpenSubKey(RegistryKey, true);
            if (key == null) return;

            if (enable)
            {
                string exePath = Application.ExecutablePath;
                key.SetValue(AppName, $"\"{exePath}\"");
            }
            else
            {
                key.DeleteValue(AppName, false);
            }
        }
        catch { }
    }

    public static bool IsAutoStartEnabled()
    {
        try
        {
            using var key = Registry.CurrentUser.OpenSubKey(RegistryKey, false);
            return key?.GetValue(AppName) != null;
        }
        catch
        {
            return false;
        }
    }
}

七、UI 布局优化

7.1 避免控件重叠

在 WinForms 中,控件重叠是常见问题。需要精确计算每个控件的位置和大小:

csharp 复制代码
// 使用固定列位置和行位置
int col1X = 15, col2X = 250, col3X = 485, col4X = 720;
int row1Y = 10, row2Y = 40, row3Y = 70, row4Y = 100;

// 创建信息标签时使用固定大小
private Label CreateInfoLabel(string text, Point location, Panel parent)
{
    var label = new Label
    {
        Text = text,
        Location = location,
        Size = new Size(210, 25),  // 固定宽度避免显示不全
        AutoSize = false,
        Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular),
        AutoEllipsis = true  // 文字太长时显示省略号
    };
    parent.Controls.Add(label);
    return label;
}

7.2 响应式布局

使用 Anchor 属性实现窗口大小改变时控件自适应:

csharp 复制代码
var dataPanel = new Panel
{
    Location = new Point(10, 10),
    Size = new Size(this.ClientSize.Width - 40, 140),
    BackColor = Color.White,
    BorderStyle = BorderStyle.FixedSingle,
    Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};

八、系统托盘功能

8.1 托盘图标和菜单

csharp 复制代码
private void SetupNotifyIcon()
{
    _notifyIcon = new NotifyIcon
    {
        Icon = SystemIcons.Application,
        Visible = true,
        Text = "Windows 系统监控"
    };

    var contextMenu = new ContextMenuStrip();
    contextMenu.Items.Add("显示主窗口", null, (s, e) => ShowMainWindow());
    contextMenu.Items.Add("任务栏窗口", null, (s, e) => ToggleTaskbarWindow());
    contextMenu.Items.Add(new ToolStripSeparator());
    contextMenu.Items.Add("退出", null, (s, e) => Application.Exit());

    _notifyIcon.ContextMenuStrip = contextMenu;
    _notifyIcon.DoubleClick += (s, e) => ShowMainWindow();
}

8.2 窗口最小化到托盘

csharp 复制代码
private void MainWindow_Resize(object? sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        this.Hide();
    }
}

private void MainWindow_FormClosing(object? sender, FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.UserClosing)
    {
        e.Cancel = true;
        this.WindowState = FormWindowState.Minimized;
        this.Hide();
    }
}

总结

这个系统监控工具的开发涉及了以下几个关键技术点:

  1. 硬件数据采集:结合 PerformanceCounter 和 LibreHardwareMonitor 实现全面的硬件监控
  2. 数据可视化:使用 ScottPlot 实现高性能的实时曲线绘制
  3. UI 设计:WinForms 布局优化,避免控件重叠
  4. 数据管理:使用队列管理历史数据,控制内存占用
  5. 配置持久化:JSON 序列化存储用户配置
  6. 系统集成:托盘图标、开机自启、悬浮窗口等系统功能

整个项目代码结构清晰,模块化设计良好,后续可以方便地扩展更多监控项和功能。

运行截图

主窗口展示了多个实时曲线图表,任务栏小窗口可以自由拖拽并切换显示模式,整体界面简洁美观,性能开销低。


开发环境要求

  • Visual Studio 2022 或更高版本
  • .NET 10.0 SDK
  • Windows 10/11 操作系统

依赖包

xml 复制代码
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.3" />
<PackageReference Include="ScottPlot.WinForms" Version="5.0.47" />

如有问题欢迎留言讨论。

相关推荐
@good_good_study2 小时前
STM32 通用定时器基础中断配置函数及实验
stm32·单片机
wotaifuzao2 小时前
STM32最新的CubeMx v6-16-1下载及安装-包含固件库下载教程(免费提供下多版本载链接)(二)
stm32·单片机·嵌入式硬件·cubemx·stm32cubemx·stm32f407
bleach-2 小时前
应急响应之入侵检测排查——Windows篇—,Windows日志介绍分析
windows·安全·web安全·网络安全·系统安全
youcans_2 小时前
【STM32-MBD】(1b)Matlab2025b 安装 STM32 硬件支持包
stm32·单片机·嵌入式硬件·matlab·simulink
anghost1502 小时前
基于 STC89C52 单片机的自动窗帘系统设计
单片机·嵌入式硬件
元气满满-樱2 小时前
安装Windows Server 2008
windows·云计算
c#上位机2 小时前
halcon计算仿射变换矩阵的逆矩阵
计算机视觉·矩阵·c#
IT方大同3 小时前
SYSTICK、RCC应用
单片机·嵌入式硬件
v先v关v住v获v取3 小时前
汽车起重机大扭矩回转减速器设计“cad32张+三维图+设计说明书
科技·单片机·51单片机