尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
从零开发上位机:通信协议、串口通信与固件升级实战
前两篇把Status Deck的机械结构、电源、主控板和固件流程聊完了手里这块小面板已经能通电、能显示、能跑本地逻辑。可真正把它从“一个会亮屏的摆件”变成“一台能配置、能监控、能远程更新的桌面设备”中间还差了最关键的一环——PC端的上位机。这一篇就专门拆解桌面端上位机程序是怎么从零写出来的。我会按实际开发顺序走一遍先讲整机逻辑里上位机到底负责什么再讲我踩了三次坑才定下来的通信协议然后给出发送接收、UI刷新、数据可视化、固件升级这些核心模块的可落地方案最后把调试期遇到的一堆“文档里查不到”的问题整理成速查表。如果你也在做一个“硬件PC软件”的全栈小项目这篇应该能帮你省下至少一周的试错时间。1. 整体设计拆解先把上位机的职责和边界画清楚1.1 Status Deck 上位机到底管什么动手写代码之前我先在一张白纸上把“上位机该干什么、不该干什么”列了出来。很多人一听到上位机第一反应就是“打开串口、读数据、画曲线”如果只做到这个程度那它充其量是个调试助手不是产品的一部分。在Status Deck这个项目里设备本身是一个带屏幕、带传感器的小盒子固件负责渲染、按键、传感器采集和本地显示。但它有几个天然短板一是配置特别痛苦显示主题、采集周期、联网参数都写死在固件里每次改一点就得重新烧录二是缺少全局视角设备端只能看到自己的传感器数据PC的CPU占用、网速这些状态它拿不到三是固件出了问题没法远程补救只能拆壳接烧录器。上位机就是来解决这三件事的。我把它拆成四个职责配置管理把设备参数下发并持久化在PC端、数据中转采集PC侧状态按协议推给设备同时接收设备上报的传感器数据、可视化监控把设备状态展示成曲线、卡片、日志能回溯、固件升级作为设备更新的入口承担文件传输与校验。这四个职责决定了技术选型。如果只是“读串口画曲线”那几乎任何语言都能干但要同时做配置持久化、界面交互、文件传输、异常恢复还得让非技术用户也能顺手用桌面端原生应用依然是性价比最高的选择。1.2 技术选型这次我为什么用 C# WinForms上位机的主流方案就那么几类我列了个对比表把每条路都盘了一遍方案开发效率跨平台部署体积串口/系统API支持复杂UI表现力C# WinForms高仅Windows小自包含40MB左右极好够用C# WPF中高仅Windows小极好强QtC/PyQt/PySide中好中好强Python Tkinter/PySide高好中需打包解释器好一般Electron Web技术中好大100MB需走Node集成强最后选了C# WinForms理由很现实串口支持是System.IO.Ports原生提供的API简单稳定WinForms的UI模型直白做一个“设备列表参数面板实时曲线日志框”的主界面不需要引入重量级MVVM框架发布时用.NET 8自包含单文件扔到没有开发环境的Windows机器上照样跑。WPF我考虑过动画和样式确实更现代但Status Deck的界面核心是实时数据和状态展示不是复杂动态交互WinForms加一点自绘就完全够了。Qt我也考虑过如果这台上位机未来要跑在Linux工控机上我大概率会换PySide6但眼下项目的固定部署环境就是一台日常用的Windows PC没必要一上来就把跨平台成本付掉。顺带说一句如果你是从零开始且C#不熟Python PySide6其实也很好上手通信协议部分的设计思路完全通用只是实现语言不同。1.3 分层架构通信层、协议层与 UI 解耦个人项目最容易犯的错误就是把所有代码堆在窗体文件里一个Button_Click里既写串口读写又写协议解析还直接改控件文本。前期爽后期改一个功能能牵扯出一堆副作用。我这次强制自己按四层来组织工程通信层负责串口扫描、打开关闭、字节收发、热插拔监测。对外只暴露Send(bytes)和Receive事件不关心字节内容是什么。协议层负责组帧、校验、解析、会话管理。输入是通信层抛出的原始字节输出是结构化的业务消息。业务层负责具体场景比如“采集PC性能数据并推送给设备”“收到传感器上报后更新最新值缓存”“触发固件升级流程”。UI层只做展示和用户操作转发不直接碰串口。工程目录大致是这样的StatusDeckStudio/ ├─ Comms/ // 通信层 │ ├─ SerialTransport.cs │ └─ DeviceDiscovery.cs ├─ Protocol/ // 协议层 │ ├─ FrameBuilder.cs │ ├─ FrameParser.cs │ └─ Commands.cs ├─ Services/ // 业务层 │ ├─ DeviceSession.cs │ ├─ TelemetryService.cs │ └─ FirmwareUpdater.cs ├─ UI/ // 界面 │ ├─ MainForm.cs │ └─ Controls/ └─ Program.cs分层带来的最大收益是协议解析这套逻辑我既可以用在上位机里也可以在写测试工具时直接复用以后如果设备改成WiFi/TCP通信只需新增一个TcpTransport实现通信层接口协议层以上完全不动。提示不要为了分层而分层。四层是合理的下限但这个项目没必要再引入依赖注入容器、仓储模式之类的重型结构过度设计在小项目里同样是坑。2. 通信协议先行先把数据帧“合同”定下来2.1 帧格式帧头、命令字、长度和 CRC16写上位机最容易犯的顺序错误就是一上来先写界面写完了才发现跟设备端对不上数据格式。我的建议是打开IDE的第一件事不是建窗体和拖控件而是把通信协议文档以注释形式写进代码顶部。Status Deck的协议帧格式我定得比较传统属于串口通信里最常见、也最不容易出错的一类字段长度说明帧头2字节固定 0xA5 0x5A命令字1字节区分消息类型数据长度2字节小端序表示数据段字节数数据段N字节具体业务数据CRC162字节对“命令字长度数据段”做CRC16-MODBUS帧头为什么用0xA5 0x5A而不是0xAA 0x55因为0xA5、0x5A这两个字节在普通ASCII报文里出现概率低而且二进制形态分别是10100101和01011010视觉上容易辨认抓包时一眼能看到帧边界。长度字段是解决“粘包”和“半包”的关键。设备端可能一次发来多帧也可能一帧分两三次才发完没有长度字段就只能靠猜有了长度字段解析器就知道“凑够这些字节才算一帧”。CRC16我直接用MODBUS参数集多项式0x8005、初值0xFFFF、输出异或0x0000。它计算量小、嵌入式端几行C代码就能实现C#端也到处有现成实现。有些方案会直接省掉校验或者用简单的累加和实测下来在USB转串口环境里一般问题不大但一旦线缆质量差或者干扰强偶发单字节错误会让整帧数据变成脏数据而且很难排查。加上校验后解析器可以直接丢弃错误帧并计数可靠性完全不一样。举个例子设备上报一条环境温度数据十六进制大概是A5 5A 10 03 00 01 D2 15 F5 9B其中0x10是“上报传感器数据”命令字0x03 0x00表示数据段长度3字节数据段依次是传感器类型01、温度高字节D2、低字节15也就是0x15D26610除以100就是26.10℃最后两个字节F5 9B是CRC16。2.2 命令字表与双向数据流通信协议最忌讳“想到哪儿加到哪儿”。我在文档里把命令字分成三组主机主动请求、设备主动上报、双方向应答。方向命令字名称数据段说明下行0x01查询设备信息无下行0x02心跳请求4字节随机数上行0x81设备信息应答设备型号、固件版本、设备序列号上行0x82心跳应答4字节随机数原样返回上行0x10传感器数据上报传感器类型数值下行0x20下发显示配置JSON配置文本下行0x30开始固件升级固件总大小、总包数下行0x31固件数据包包序号128字节固件数据下行0x32升级结束固件CRC32总校验值这里面有一个细节值得说明0x02/0x82的心跳交互带随机数是为了防止历史帧被意外重放。这个问题在串口环境不太常见但设备可能因为看门狗重启留下半截响应带随机数校验后上位机能确认这帧心跳就是“针对我刚才那次请求”的而不是一条滞后的旧消息。另一个细节是版本协商。0x81应答里带固件版本号上位机拿到后要先判断版本再决定是否下发0x20配置、是否允许进入0x30升级流程。老固件可能不认识新配置字段直接下发会导致设备解析异常。协议有版本意识比盲目兼容稳妥得多。2.3 解析器核心用一个状态机搞定粘包和半包协议解析这块新手最容易写成一坨“找帧头-截字符串-硬切分”的临时逻辑遇到半包就崩。我这次直接用了一个简单的状态机这也是串口解析里最标准的做法。状态流转简单说就是等待帧头1 → 等待帧头2 → 等待命令字 → 等待长度低字节 → 等待长度高字节 → 等待数据段 → 等待CRC低字节 → 等待CRC高字节 → 完成。每收到一个字节就推进一次状态任何一步校验失败立刻回到等待帧头1。public class FrameParser { private enum State { WaitHeader1, WaitHeader2, WaitCmd, WaitLenLo, WaitLenHi, WaitData, WaitCrcLo, WaitCrcHi } private State _state State.WaitHeader1; private byte[] _payload new byte[0]; private int _payloadIndex; private ushort _payloadLength; private ushort _crc; private int _crcIndex; public event ActionProtocolFrame FrameReceived; public void Push(byte[] buffer, int offset, int count) { for (int i offset; i offset count; i) { byte b buffer[i]; switch (_state) { case State.WaitHeader1: if (b 0xA5) _state State.WaitHeader2; break; case State.WaitHeader2: _state b 0x5A ? State.WaitCmd : State.WaitHeader1; break; case State.WaitCmd: _cmd b; _state State.WaitLenLo; break; case State.WaitLenLo: _payloadLength b; _state State.WaitLenHi; break; case State.WaitLenHi: _payloadLength | (ushort)(b 8); _payload new byte[_payloadLength]; _payloadIndex 0; _crc Crc16Modbus.InitialValue; _crcIndex 0; _state _payloadLength 0 ? State.WaitData : State.WaitCrcLo; break; case State.WaitData: _payload[_payloadIndex] b; if (_payloadIndex _payloadLength) _state State.WaitCrcLo; break; case State.WaitCrcLo: _crc (ushort)(b 0xFF); _state State.WaitCrcHi; break; case State.WaitCrcHi: _crc | (ushort)((b 8) 0xFF00); ushort calc Crc16Modibus.Compute(_cmd, _payload); if (calc _crc) FrameReceived?.Invoke(new ProtocolFrame(_cmd, _payload)); Reset(); break; } } } }这个状态机的价值在于不管对方是每次发一个字节还是一个字节一个字节地挤出来只要字节流按顺序到达它都能正确拼出一整帧长度异常时自动丢弃还原不会把后续帧的字节吃掉。我还会在解析器里加一个最大帧长保护比如超过2KB直接重置。因为设备如果跑飞了可能不停地吐0xFF没有上限保护的话解析器会一直缓存垃圾字节直到内存被撑爆。3. 串口模块实操收发、热插拔与跨线程 UI 刷新3.1 串口参数与打开逻辑基本配置不是玄学串口参数这块很多人直接照抄“9600,8,N,1”但Status Deck的数据量不大我最后把波特率定在115200。这个速率下理论约11.5KB/s一帧温度上报最多几十字节余量很足而且现在所有USB转串口芯片都稳定支持115200没必要更低。var sp new SerialPort(portName, 115200, Parity.None, 8, StopBits.One) { ReadTimeout 500, WriteTimeout 500 }; sp.DataReceived OnDataReceived; sp.Open();打开串口有两点需要特别小心。第一Open()会抛异常常见原因包括端口被占用、设备被拔出、权限不足。上位机启动时不能让异常直接弹崩溃我会在设备面板上把可用端口枚举出来用户选择后再打开打开失败只提示原因并保持面板可用。第二串口句柄是稀缺资源关闭时必须Dispose。如果程序退出时没释放端口其他工具就暂时打不开这个COM口。我一般把SerialPort放在DeviceSession里统一管理Dispose时先关事件再关端口。热插拔检测我用的是轮询法500ms一次枚举SerialPort.GetPortNames()跟当前列表对比新增就刷进下拉框减少就清除状态。更精细的做法是监听Windows的WM_DEVICECHANGE但轮询委托简单可靠对个人项目已经足够。3.2 接收数据处理不要在 DataReceived 里干重活SerialPort的DataReceived事件是在后台线程触发的这意味着你永远不应该在这个事件里直接改UI也不应该在里面做耗时解析。正确做法是快速把系统缓冲区的字节读出来塞进自己的接收缓冲区然后统一交给协议层处理。private readonly object _rxLock new object(); private readonly Listbyte _rxBuffer new Listbyte(); private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) { var sp (SerialPort)sender; int available sp.BytesToRead; byte[] chunk new byte[available]; int read sp.Read(chunk, 0, available); lock (_rxLock) { _rxBuffer.AddRange(chunk); while (TryExtractOneFrame(out var frame)) // 协议层解析 { // 交给业务层处理 _session.OnFrameReceived(frame); } } }这个while循环很关键串口一次事件可能带过来多帧数据只解析一帧会把剩下的帧留在缓冲区里下一轮又跟新数据混在一起。循环解析直到缓冲区里凑不出完整帧为止剩下留到下次事件再处理。3.3 跨线程刷新 UIInvoke 与聚合刷新WinForms的控件只能由创建它的线程操作子线程直接改Text百分百抛InvalidOperationException。我刚上手时第一次跨线程刷新UI就吃了这个亏当时的想法很朴素“不就是加个锁吗”后来才理解线程亲和性这个概念。最简单的做法是Control.Invokethis.Invoke(new Action(() labelTemp.Text ${temp:F1} ℃));Invoke是同步的会阻塞后台线程等待UI线程执行完BeginInvoke是异步的不会阻塞。但这两个方法还有个隐形问题如果设备以高频上报每条数据都Invoke一次UI线程的消息队列会被刷爆界面卡成幻灯片。所以我采用了“定时器聚合刷新”方案。UI线程挂一个100ms的System.Windows.Forms.Timer每次Tick去拿业务层缓存的最新状态批量刷新标签和曲线串口事件线程只负责把数据写进缓存完全不碰控件。private void uiTimer_Tick(object sender, EventArgs e) { var snapshot _session.GetSnapshot(); // 线程安全缓存 lblCpu.Text snapshot.CpuLoad.ToString(F1) %; lblTemp.Text snapshot.Temperature.ToString(F1) ℃; chart.PushPoint(snapshot.Temperature); }100ms的刷新间隔对桌面仪表盘来说肉眼已经足够顺滑而且UI线程压力极小。这个方案的通用性很强不管底层是串口、TCP还是别的什么高频数据源只要数据先落缓存再由UI定时器统一取界面就永远稳。3.4 数据可视化滚动曲线和状态卡片的落地做法Status Deck上位机主要展示两类信息实时数值温度、湿度、CPU占用和变化趋势传感器历史曲线。实时数值我用最简单的Label摆成状态卡片值变化时通过颜色和前景图标提示高低这块没什么技术含量关键在于色彩规范正常绿色、警告橙色、异常红色这套规则我在数据进入UI层之前就用枚举定义好避免UI里散落一堆裸的色值判断。趋势曲线这块有两条路。一是用现成库比如ScottPlotWinForms下集成非常顺滑性能也能扛几千个点的实时滚动二是自己做轻量自绘在Panel的Paint事件里用Graphics画线。我最后选了ScottPlot因为它自带双缓冲和坐标轴缩放拖拽都不用自己造轮子。如果你不想引第三方依赖自绘方案也可行但必须记得把画线控件的DoubleBuffered设为true否则滚动刷新时会闪得怀疑人生。自绘滚动曲线的核心思路并不复杂保留最近N个数据点横轴按时间均匀排布每次Paint重新计算坐标并画折线超过N个点就丢弃最早的。4. 配置、日志与固件升级让上位机具备交付级品质4.1 配置持久化JSON 文件优于注册表很多“电子工程师风格”的上位机喜欢把配置写进注册表跨机器部署麻烦备份也麻烦。我的做法是所有配置集中到一个JSON配置文件放在程序目录下程序启动时读取没有就生成默认配置。{ serial: { port: COM3, baudRate: 115200, reconnectOnOpen: true }, device: { expectedSerial: SD-2025-001, pollIntervalMs: 1000 }, features: { showCpuLoad: true, showNetworkSpeed: true, tempAlertHigh: 32.0 } }这里有一个我踩过的坑配置文件路径不能用Environment.CurrentDirectory因为这个值会随着用户从哪个目录启动程序而变化。必须用AppContext.BaseDirectory它永远指向exe所在目录稳定可靠。JSON序列化在.NET里直接System.Text.Json就够用不需要引入第三方库。加载时要做容错文件损坏就备份一份.bad然后生成默认配置绝不能一崩溃就白屏。4.2 日志系统调试期和交付期都需要上位机开发中日志是最容易被忽略的直到你遇上一个“偶尔发生、但抓不住现场”的Bug。我的做法是写一个极简的日志工具按天滚动文件单文件超过5MB就重命名成.log.1再新建。日志分四级Debug、Info、Warn、Error。日常运行只写Info以上调试模式才开Debug。特别要提的是“协议帧日志”——我把串口收发的原始十六进制帧也落进日志但只在调试开关打开时记录。这个功能在接线不稳、设备跑飞、协议对不上的时候堪称救命稻草。帧日志的格式大概是[2025-06-01 21:03:12.458] RX: A5 5A 10 03 00 01 D2 15 F5 9B [2025-06-01 21:03:13.012] TX: A5 5A 02 04 00 12 34 56 78 C4 2A出现问题时不用用户在设备上测半天直接把日志发回来一个十六进制序列就能定位是设备没发、发错、还是上位机解析错。4.3 固件升级上位机承担设备更新入口把固件升级放进上位机是Status Deck从“开发板”走向“产品”的一步。用户不需要拆壳烧录打开软件点一下就能更新固件。我的升级流程参考了XMODEM的思路简化成五步上位机发送0x30开始升级命令数据段包含固件总字节数和总包数。设备端应答后进入Bootloader模式关闭业务逻辑、擦写应用区。上位机按128字节分包循环发送0x31数据包每包带包序号。设备每收一包回一个ACK收包出错回NAK上位机收到NAK或超时未收到ACK就重发连续3次失败则终止升级。全部发完后发送0x32结束命令附带全文件CRC32设备校验通过后重启进入新固件。这里有几个非常关键的细节。第一升级期间用户不能拔串口或断电否则设备变砖的概率很高因此我做完开始升级后会在界面上锁定关闭按钮。第二上位机发送每个数据包间隔要有短暂延时避免设备端Flash擦写来不及处理流控做得不好会引发一连串NAK。第三升级开始前把当前设备配置读取出来并保存一份副本升级完成后自动重连并恢复配置体验会好很多。固件文件本身用拖拽方式放进上位机我在拖放区域做了文件类型过滤只接受.bin读取后立即校验文件头魔数防止用户选错文件。5. 真实调试实录常见问题和避坑清单5.1 粘包、半包与设备启动垃圾数据第一次联调时上位机一打开就疯狂收到乱码帧程序直接卡死。查了半天发现罪魁祸首是设备上电瞬间串口芯片和主控还在初始化会吐出一串无意义的0xFF和中断字符。这个问题的解决靠两件事一是状态机本来就天然过滤非法字节非帧头一律忽略二是解析器对“最大帧长度”做了上限超过就丢弃并重置。从那以后我再也不怕设备上电抽风随便它先吐多少垃圾。粘包问题则出现在设备连续上报两条传感器数据时上位机偶尔只显示最新一条偶尔显示一条错误数据。检查日志才发现接收事件一次读回了多帧旧代码只取了一帧就返回剩下的字节留在缓冲区跟下一条数据混在一起。改成while循环解析后问题消失。5.2 COM 口漂移与权限问题USB转串口的COM口漂移是所有串口工具的痛点。你把设备插在USB3.0口和USB2.0口上Windows分配的COM号可能不一样重启一次键盘上的某个设备占了COM3你的设备就变COM4了。做上位机时我给“选择端口”这步加了一个辅助提示不仅列出COM编号还显示设备描述符通过Win32 API读取比如“USB-SERIAL CH340 (COM5)”。同时设备应答的0x81信息里带了设备序列号上位机连接后会校验这个序列号不对就警告“端口选择可能错误”。这样即使COM口换了用户也能快速找到正确的那个。权限问题也有一次典型踩坑设备串口被另一个程序占着或者用户用非管理员账号运行但串口驱动有权限限制Open()会直接拒绝。我的处理是把所有串口异常统一捕获并让主界面保持可用而不是弹出未处理异常框。5.3 高 DPI 与界面显示模糊WinForms在高DPI显示器上默认会模糊这在开发机上不明显换到用户的4K屏上就非常明显。解决办法在app.manifest里声明PerMonitorV2并在程序入口调用SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)。[DllImport(user32.dll)] private static extern bool SetProcessDpiAwarenessContext(IntPtr value); SetProcessDpiAwarenessContext((IntPtr)(-4)); // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2加完之后还有一个连带问题表格、分组框的字体大小在不同DPI下可能错位我在UI布局时强制统一使用固定的布局边距并给关键控件设置了AutoSize实测下来缩放基本正常。5.4 断线重连与异常恢复策略设备在运行过程中被拔掉USB线或者设备端看门狗重启上位机的表现决定了这工具“像不像正经产品”。我的处理分三层。第一设备端每500ms发一次心跳超过3个周期没收到就判定设备离线第二上位机发现离线后自动进入重连循环每2秒尝试重新打开串口同时UI显示“设备离线正在重连”状态不弹模态框打扰用户第三重连成功后自动重新下发一次配置快照让设备恢复到断开前的显示状态。这个重连策略在稳定性测试里跑了几千次只有一个问题需要注意重试打开串口的操作不能放在UI线程否则每2秒卡一次界面我是用System.Threading.Timer做后台重试。5.5 常见问题速查表问题现象可能原因排查/解决方法串口打开失败端口被占用、驱动异常、权限不足关闭占用程序检查设备管理器以管理员身份运行收不到设备数据波特率不一致、接错引脚、线缆不良用串口工具确认基础通信核对波特率和校验位数据乱码波特率不一致、地线接触不良固定115200检查线缆接线偶发解析错误校验缺失、半包处理不当协议层启用CRC16解析器状态机完整实现界面卡顿高频Invoke刷新UI改为定时器聚合刷新100ms间隔界面模糊未做DPI适配manifest声明PerMonitorV2并调用APICOM口漂移USB口变更、驱动程序分配变化显示设备描述符连接后校验设备序列号升级失败中断供电不稳、串口被拔、包序错乱禁止升级中断电与拔线每包ACK超时重传调试期我还有一个很顺手的技巧用虚拟串口工具成对创建COM口上位机连一端另一端跑一个模拟设备脚本自动化造帧压测解析和重连逻辑。这样不等硬件到位就能把上位机软件测到八成稳定真正联调时省了很多时间。Status Deck这个项目的上位机部分我从开工到稳定运行最大的感受是“协议先行比编码先行重要得多”。协议定了上位机和固件就是两条并行线各自照着同一份“合同”开发最后接起来几乎不用改。第二个感受是日志必须从一开始就写尤其是帧日志很多问题没有日志根本无从下手排查。第三个感受是UI一定不要跟数据收发纠缠在一起缓存加定时器刷新这套模式看着笨但确实怎么压都不出问题。如果你也在写自己的上位机我建议你也先花两天时间把帧格式、命令字、异常流程写在纸上再碰IDE。后面即使换语言、换平台这套思路都能原样搬过去。
RELATED

相关推荐

残障人士安全设备设计指南:跌倒检测与定位通信实战

残障人士安全设备设计指南:跌倒检测与定位通信实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/4 11:04:18
基于ADS122U04与PIC18的多通道高精度测温系统设计

基于ADS122U04与PIC18的多通道高精度测温系统设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/4 11:04:18
STM32F407+FreeRTOS多传感器环境监测系统实战

STM32F407+FreeRTOS多传感器环境监测系统实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/4 11:04:18
MORE NEWS

更多资讯

📰

完整上手 Trae Agent:从克隆仓库到看懂它如何执行任务

完整上手 Trae Agent:从克隆仓库到看懂它如何执行任务 【免费下载链接】trae-agent Trae Agent is an LLM-based agent for general purpose software engineering tasks. 项目地址: https://gitcode.com/gh_mirrors/tr/trae-agent Trae Agent 是一个基于 LL…

📰

Tiled 实操教程:3 步做出首张带碰撞、可导出的地图

Tiled 实操教程:3 步做出首张带碰撞、可导出的地图 【免费下载链接】tiled Flexible level editor 项目地址: https://gitcode.com/gh_mirrors/ti/tiled 不到 1 小时,你会得到一张 4030 格、带 3 组平台碰撞框和 5 个出生点的 Tiled 地图 map_01.…

📰

dsh-purge 安装指南:3 步跑通 DeepSeek 红队评测插件 + 完整排坑清单

dsh-purge 安装指南:3 步跑通 DeepSeek 红队评测插件 完整排坑清单 【免费下载链接】dsh-purge DeepSeek Harness 破甲:让所有模型都能破甲,不同模型可换不同提示词;默认提示词面向国模「小码酱」。Jailbreak for every model —…

📰

如何免费下载高清小红书作品:XHS-Downloader 单链接 3 分钟到 300 条批量的完整实操

如何免费下载高清小红书作品:XHS-Downloader 单链接 3 分钟到 300 条批量的完整实操 【免费下载链接】XHS-Downloader 小红书(XiaoHongShu、RedNote)链接提取/作品采集工具 项目地址: https://gitcode.com/gh_mirrors/xh/XHS-Downloader …

📰

免费 JAV 马赛克修复工具 Jasna:GPU 一键去码完整指南,1080p 只要 34 秒

免费 JAV 马赛克修复工具 Jasna:GPU 一键去码完整指南,1080p 只要 34 秒 【免费下载链接】jasna Video restoration tool 项目地址: https://gitcode.com/GitHub_Trending/ja/jasna Jasna 是一款免费的 JAV 马赛克修复(去码&#xff0…

📰

一文读懂 Go 夜读:源码分享与参与路径全解

一文读懂 Go 夜读:源码分享与参与路径全解 【免费下载链接】night Weekly Go Online Meetup via Bilibili|Go 夜读|通过 bilibili 在线直播的方式分享 Go 相关的技术话题,每天大家在微信/telegram/Slack 上及时沟通交流编程技术话…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬