我的上位机编程之路:从拧螺丝到写代码,这8年我踩过的那些坑

📅 2026/8/15 11:54:19
我的上位机编程之路:从拧螺丝到写代码,这8年我踩过的那些坑
零八年那会儿我还在厂里干维修电工每天拿着万用表在产线上晃悠。设备坏了就查线路、换继电器、拧紧松了的端子排。工资不高不低活儿不重不轻就是没啥盼头。有一天厂里来了个搞自动化的外协抱着笔记本往电控柜旁边一蹲噼里啪啦敲了几行代码我们那台老掉牙的PLC就活过来了屏幕上刷刷刷跳出来一串数据。当时我就站在他背后看的眼睛都直了。我就琢磨这玩意儿我得学。第一年摸着黑过河啥都不懂硬着头皮上第一步就卡住了。我连C#是啥都不知道问车间里那帮搞电气的兄弟没一个会的。上百度搜出来的东西五花八门VB、C、LabVIEW完全不知道该从哪儿下嘴。后来选C#是因为看了一篇帖子说WinForm拖控件就跟搭积木一样容易上手。我就去书店买了本《C#入门经典》那么厚一本跟砖头似的。刚开始那叫一个痛苦。照着书敲代码敲完了一运行满屏报错红字一片一片的。变量类型不匹配括号少一半分号用成中文的这些破事我干了个遍。一个简单的Hello World我折腾了三天才弹出来。但我这人有个毛病认准的事儿不回头。白天上班没空晚上回家从十点坐到凌晨两点笔记本屏幕亮着旁边泡着浓茶。看不懂的就搜搜不到的就硬猜猜错了再重来。就这么熬了两个月我写出了人生第一个能用的东西一个串口调试助手。界面丑得没法看按钮是灰的、字体是歪的但是能选COM口、能设波特率、能发数据能收数据。那天凌晨三点我看到自己发的Hello原封不动从虚拟串口那边回来的时候我直接从椅子上跳起来了。现在回想起来那会儿写的代码质量惨不忍睹。所有逻辑全塞在Form1.cs里面两千多行串口接收事件里面直接更新UI跨线程异常天天报我连Invoke是干啥的都不知道。程序动不动就卡死我就加Application.DoEvents()硬顶现在想想就是个定时炸弹。但不管怎么说这第一步我迈出去了。第三年第一次去现场被现实狠狠扇了一耳光学了两年多自我感觉良好了。在电脑上用虚拟串口和模拟器跑得顺顺当当以为天下无敌了。结果第一次跟着师傅去现场调试直接被打回原形。客户那边是一条锂电池组装线十几台设备联网每台设备里一个三菱FX系列的PLC全部走485总线连到一台工控机上。我的任务是把其中三台的数据读到上位机里。听起来简单对吧我打开笔记本开始干。串口一开数据刷刷刷就来了全是乱码。波特率不对我试了所有波特率还是乱码。数据位校验位挨个试了个遍不行。换了好几种编码格式ASCII、GB2312、UTF8来回切还不行。折腾了两个小时师傅过来瞅了一眼说你看看PLC那边的通信协议是不是三菱专用格式不是Modbus。我当场就傻了因为我在家只练过Modbus。那天晚上我在酒店翻了半夜的资料才知道三菱的FX系列走的是自己的协议报文格式跟Modbus完全两码事。第二天硬着头皮现场写解析代码照着说明书一个字一个字扣报文结构一直到晚上九点才把数据读上来。但这才是个开始。数据是读上来了可有个奇怪的问题程序跑个把小时就死一次界面卡住不动只能强行关闭重启。客户那边操作工骂骂咧咧的说还不如原来那个老系统。我排查了整整三天。开始怀疑是串口缓冲溢出了改了缓冲区大小没用。然后怀疑是内存泄漏用任务管理器盯着内存占用也没见涨。最后没办法打开日志一条一条看终于发现问题出在一个不起眼的地方。串口接收事件里我直接用了个循环读数据读到就丢给界面显示。但串口的数据是源源不断来的接收事件触发频率特别高界面更新根本跟不上消息队列堵死了UI线程就挂了。解决的办法是我在网上搜到的一个笨办法把收到的数据全部扔到一个队列里另外开一个线程从队列里取数据来更新界面。生产者消费者模式当时我还不知道这词儿就是照着人家的代码改的。改完之后稳了。连续跑了48小时没出过一次问题。从那次以后我学乖了不再觉得能在电脑上跑通就等于能干好活。现场那堆破事电压不稳、地线没接好、干扰严重、线接错了、设备手册写的全是错的这些才是一个上位机程序员真正的战场。第五年自己撸了一个协议库差点没把自己累死干了几年什么协议都遇见过。Modbus是家常便饭西门子的S7协议、三菱的MC协议、欧姆龙的FINS、松下、台达、还有各种国产小牌子PLC的自定义协议。我一开始的做法特别蠢每个项目都从头写通信代码。换个品牌就重新写一套换个协议就把原来的代码复制一份改吧改吧。后来出事儿了一个项目里同时要接西门子和三菱两种设备我那套代码里面既有S7的报文结构又有MC的解析逻辑改得乱七八糟最后整个工程全乱了离交货还有三天我急得嘴角起泡。这事儿之后我痛定思痛决定自己撸一个通用的通信框架。我把所有协议的共同点抽象出来无非就是打开连接、发送请求、接收响应、关闭连接。不同协议的差别在于报文怎么组、怎么拆、校验怎么算。所以我设计了一个底层接口上面挂不同的协议驱动换协议只需要换驱动上层代码一行不用改。这个框架我前前后后写了六个月周末全搭进去了。中间推翻重写了三遍第一遍设计得太死板换一种通信方式就要改基类。第二遍又太灵活接口一大堆自己都记不住怎么用。第三遍才找到一个平衡点。写完之后我就用这个框架接了好几个项目换协议确实方便多了。但说实话这六个月值不值得我一直没想明白。你要问我可能值得也可能不值得。值得是因为我通过这件事把面向对象的那套东西真正用明白了接口继承多态这些概念从书上的字变成了我手里的工具。不值得是因为这六个月要是拿来干别的可能产出更多。后来我发现了NModbus这个开源库看了人家的源码之后不得不服人家的设计比我那个框架优雅多了而且经过了那么多项目的检验健壮性根本不是一个量级的。从那以后我就不再自己造轮子了有现成的就用现成的但前提是我得能看懂人家的源码出了bug我能修。第七年跟GC干了一架差点没干过有个项目要求特别变态。甲方那边是搞精密测量的要求上位机每秒从设备读2000个数据点实时显示在曲线图上还要同时写入数据库而且延迟不能超过100毫秒。我第一版交上去直接被退回来了。程序运行不到五分钟就卡得跟幻灯片一样CPU占用飙到80%多。开始以为是UI渲染的问题。把Chart控件的刷新频率从每帧都画改成每50毫秒批量画一次稍微好了一点但还是卡。后来用性能分析工具一查发现问题根本不在UI在GC上。每秒2000个包每个包解析出来要new一堆对象字节数组转成float要new组包拆包要new临时数组把这些数据存到列表里又要new。GC回收线程频繁触发一触发所有的托管线程全得暂停等待卡顿就这么来的。解决的办法折腾了我两个星期。第一招用结构体代替类。值类型分配在栈上不触发GC。第二招预分配字节数组通信线程里所有的缓冲区全都提前new好循环使用不在运行时动态分配。第三招用ObjectPool对象池高频使用的对象用完放回去下次接着用减少new的次数。改完之后再测GC触发的频率降低了十几倍程序连续跑了四个小时稳如老狗。这一仗打完我对C#的理解深了一层知道这东西虽然方便但底层的内存管理你得心里有数不然早晚出事儿。第八年开始带人了踩过的坑一个一个讲给新人听现在我开始带新人了每次他们问我上位机怎么学我就跟他们说三句话第一句先写一个能跑的串口调试助手写完这个你就算入门了别管代码质量怎么样先跑起来再说。第二句写完第一个之后把它删了重新写一遍。第二遍你要把通信和界面分开了这比写第一遍重要得多。第三句用你重写的那个工具去接一个真实的设备便宜的传感器也行只要能发数据能收数据就行。这一关过了你就算真正入行了。我现在自己写代码的习惯也跟以前不一样了。日志从第一天就加上没有日志的代码调试起来就是瞎子摸象。异常处理一个不落通信超时、设备掉线、数据校验失败每种情况都写清楚处理逻辑不能把异常吞了不吭声。配置全都外置到JSON文件里硬编码等于自寻死路。还有最重要的一条每个版本都留备份改之前先打个tag。我有过一次血的教训一个功能改了三天没改好想退回三天前的版本发现没有备份只能凭记忆重新写那叫一个惨。总结几句掏心窝的话这八年走过来最大的体会就是上位机开发不是写代码是解决现场问题。你在代码里处理了99种异常现场冒出来的永远是第100种。你以为通信参数都配对了现场就偏偏收不到数据最后发现是485线的屏蔽层没接地。你以为超时重试写了三遍足够了现场偏偏第四遍才通因为那台老设备启动就需要那么久。这些事儿书上学不来网上也搜不到标准答案只能自己一次次栽进去再爬出来。但正是这些坑堆出来了一个经验。现在我去现场看一眼指示灯就能猜出八成的问题出在哪。操作工跟我说现象我不用去翻日志也能判断是硬件故障还是软件bug。这种手感不是靠看视频看出来的是靠在车间里蹲了成百上千个小时磨出来的。你要是问我后不后悔走这条路我不后悔。这行没有互联网那么光鲜赚钱也不快但有个好处是踏实。你写出来的软件让设备动起来了、让产线跑起来了、让工人不用盯着仪表盘看了那种实实在在的成就感办公室里敲纯代码的人体会不到。个人公司专接上位机软件外包项目✅工控上位机✅测试治具软件✅数据监控平台✅设备配套PC端支持C#/WPF/WinForms/Avalonia等技术栈熟悉Modbus、CAN、TCP/IP等工业协议。已交付多个项目有长期合作客户3家。新项目咨询请私信24小时内响应。