尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C#上位机实战:海康相机与雷赛运动控制卡集成方案

C#上位机实战:海康相机与雷赛运动控制卡集成方案 简介本资源是一套基于C#开发的工业视觉与运动控制集成解决方案面向自动化产线开发工程师、机器视觉初学者及高校机电/计算机专业实践者解决芯片ID识别、精密定位抓取与数据闭环管理的实际工程问题。压缩包共205个文件含39个核心C#源码文件含相机采集、雷赛运动控制指令封装、OCR识别逻辑、59个数据库备份文件.dmp、37个界面图标资源.ico及6个动态链接库.dll整体22.98MB结构清晰模块划分明确——涵盖UI层、业务逻辑层、设备驱动层与数据持久层。已有101人学习下载资源提供完整VS解决方案.sln .csproj含运行日志.log、配置文件.config、本地化资源.resx/.resources及芯片识别专用位图.bmp可直接编译调试特别适合理解多设备协同通信、图像预处理与结构化数据上传等典型工业场景实现路径。 去年在产线上做了个C#上位机项目把海康工业相机、雷赛运动控制卡和数据库串到了一起实现产品ID识别、定位抓拍和数据上传追溯。这套东西在自动化行业很常见但真正从零搭起来才发现硬件选型、SDK调用、线程协调、数据库防重每个环节都有不少坑。这篇文章就把这个项目的完整思路、落地代码和踩坑记录整理出来给正在做类似中控系统的朋友一个参考。这类项目的典型场景是产品经过工位时运动控制卡带着机构运动到位触发海康相机拍照从图像里识别出产品上的二维码或条码ID然后把ID和当前时间、工位信息一起写入数据库方便后续追溯。整个过程要求稳定、快速、不能漏数据这就决定了软件架构不能只写能跑的代码还得考虑异常恢复、重复数据过滤和界面实时反馈。1. 项目整体设计与架构拆解1.1 核心需求解析这个项目的核心链路可以拆成四段运动到位、触发拍照、识别ID、上传数据库。每一段都有自己独立的逻辑但必须串成一条完整的自动化流程。运动到位这一步靠的是雷赛运动控制卡。不管是用DMC1000系列还是DMC3000系列本质都是通过脉冲输出来控制伺服或步进电机让机构精准走到指定位置。比如我的项目里有一个平移轴需要把产品推到相机正下方这个定位精度直接决定了后续相机视野里产品的位置偏差所以运动控制部分不能马虎。相机拍照和ID识别靠的是海康工业相机。海康的MVS SDK提供了完整的相机枚举、参数配置、抓图接口支持GigE和USB3.0接口。我这边用的是千兆网口相机通过触发线或者软触发方式抓图然后从图像中定位二维码并解析出ID字符串。识别这一步可以自己调第三方库也可以用海康的算法平台我这里用的是基于Halcon的二次识别后面会细说。数据库上传是把识别到的ID和附加信息写入数据库。数据库选型上如果是单机部署SQL Server Express或者MySQL都够用如果有多台设备需要汇总数据一般会单独部署一个数据库服务器。我在这个项目里用的是SQL Server主要考虑是Windows工控机集成方便连接字符串配置简单。1.2 为什么选C#做上位机很多老工程师习惯用C或者LabVIEW做上位机但C#在Windows平台上的开发效率和维护性确实有优势。尤其在这个项目里UI需要实时显示相机画面、运动位置、识别结果、数据库状态C#的WinForm或WPF做这类界面非常顺手用委托和事件处理异步回调也自然。还有一点很关键海康MVS SDK自带C#的封装库MvCameraControl.Net.dll直接用Visual Studio添加引用就行不用像C那样做复杂的类型映射。雷赛运动控制卡虽然提供的是C语言风格的动态库但通过DllImport一样可以很方便地在C#里调用。数据库这块更不用说了ADO.NET和Entity Framework都是C#的强项。如果你问我纯上位机开发C#基本是性价比最高的选择。但要注意C#调用C动态库时数据结构和调用约定必须对齐比如雷赛的动态库大多是stdcall调用约定声明DllImport时要记得指定CallingConvention。这个细节后面我会单独讲。1.3 系统整体架构我的系统架构大概是这样的物理层工控机Windows 10 64位 雷赛运动控制卡通过PCIe插槽 海康工业相机通过网口连接交换机 伺服驱动器/电机 光电传感器和限位开关。软件层C#解决方案里分了几个项目一个主程序WinForm一个硬件封装类库相机和运动卡的DLL调用一个数据库访问类再加上一个公共的型号定义。模块之间通过接口和事件解耦这样单独换相机型号或者换数据库类型时不需要把主程序全部推翻重写。数据流系统空闲时等待PLC或按钮触发到位后启动相机采图识别线程拿到ID后交给数据库线程写入同时在UI上刷新当前状态和数据库返回结果。所有硬件操作都放在后台线程避免阻塞UI。2. 硬件连接与SDK环境搭建2.1 海康相机SDK初始化海康的MVS SDK安装好之后在安装目录的Development\C#\文件夹下能找到MvCameraControl.Net.dll。项目里直接添加这个引用然后调用MvCameraControl.MvCamera类来操作相机。初始化流程分成三步枚举设备、创建句柄、打开设备。using MvCameraControl; // 枚举相机 ListIMVDeviceInfo devices MvCamera.EnumDevices(); if (devices.Count 0) { MessageBox.Show(未找到海康相机); return; } // 选择第一个设备并打开 IMVDevice camera MvCamera.CreateDevice(devices[0]); camera.Open();打开设备之后必须配置一下相机参数常用的有曝光时间、增益、触发模式、图像格式。这里有一个非常重要的点海康相机的软触发和硬触发是互斥的如果你希望用运动控制卡输出信号来触发相机就要设置TriggerMode为硬触发相反如果简单地从软件里发指令拍照就用软触发。我在项目里用的是硬触发因为产品在运动过程中速度比较快软触发会引入延迟导致拍到的产品位置偏移。硬触发的配置大概是这样camera.SetEnumValue(TriggerMode, 1); // 1表示硬触发0表示内触发 camera.SetEnumValue(TriggerSource, 0); // 0表示Line0按实际接的线设置 camera.SetEnumValue(TriggerActivation, 1); // 上升沿触发这里特别提醒海康相机的枚举名可能因为固件版本不同而略有差异比如有些相机里是TriggerMode有些新固件里改成了TriggerSelector和TriggerMode的组合配置前最好用MVS自带的MvDemo软件看一眼当前固件的参数列表。2.2 雷赛运动控制卡调用方式雷赛控制卡的型号很多我用的是DMC1000系列动态库是dmc1000.dll。C#里通过DllImport把需要用到的函数声明进来核心的就这么几个打开设备、设置脉冲模式、单轴点位运动、读取轴状态、回原点。[DllImport(dmc1000.dll, CallingConvention CallingConvention.StdCall)] public static extern short dmc_board_init(); [DllImport(dmc1000.dll, CallingConvention CallingConvention.StdCall)] public static extern short dmc_set_pulse_mode(int cardNo, int axis, int mode, int dir); [DllImport(dmc1000.dll, CallingConvention CallingConvention.StdCall)] public static extern short dmc_pmove(int cardNo, int axis, int pos, int posMode, int velocity, int acc, int dec); [DllImport(dmc1000.dll, CallingConvention CallingConvention.StdCall)] public static extern short dmc_check_done(int cardNo, int axis);初始化的时候先调用dmc_board_init()做板卡初始化然后对每个轴设置脉冲输出模式。这个模式要看驱动器是脉冲加方向PulseDir还是正反脉冲CW/CCW设置不对电机要么不动要么方向反了。short ret dmc_board_init(); if (ret ! 0) { // 打印错误码 } dmc_set_pulse_mode(0, 0, 0, 0); // 0号板、0轴、脉冲方向模式很多新手在这里容易犯错只初始化了板卡没有设置脉冲模式结果电机就是不走查了半天发现是这一步漏了。我建议把板卡初始化和轴参数配置写成独立的类比如MotionController这样主程序里只需要调用MotionController.Init()不用暴露一堆DllImport的细节。2.3 雷赛运动控制卡的同步触发配置雷赛控制卡除了发脉冲控制电机还带IO输入输出功能可以输出一个指定电平的信号。这个信号接到海康相机的触发线上就实现了运动到位后自动触发相机拍照。使用IO输出之前必须先配置IO方向。雷赛的DMC1000系列有专门的指令设置IO方向比如把OUT0配置为输出[DllImport(dmc1000.dll)] public static extern short dmc_set_io_mode(int cardNo, int ioMode); // ioMode里用bit位控制每个IO的方向1表示输出0表示输入 dmc_set_io_mode(0, 0x0001); // 将IO0设为输出然后运动到位后执行一次IO置高再延时置低就产生了一个上升沿触发相机dmc_write_io(0, 0, 1); // IO0输出高电平产生上升沿 Thread.Sleep(10); // 保持至少10ms确保相机可靠抓拍 dmc_write_io(0, 0, 0); // IO0拉低为下一次触发做准备注意触发电平的保持时间不能太短我之前用2ms时出现过相机偶尔漏触发的情况换成10ms后就很稳定了。3. 核心功能实现ID识别与数据库上传3.1 图像采集与二维码识别方案海康相机采图通常有两种方式回调模式和主动取流模式。回调模式适合连续采图的场景主动取流模式适合单位触发拍照的场景。这个项目是运动到位后触发一次拍一张所以用主动取流更简单。主动取流的核心代码// 开始取流 camera.StartGrabbing(); // 申请帧信息 MvFrame frame new MvFrame(); camera.GetFrame(ref frame, 3000); // 超时3秒 // 将原始数据转成Bitmap Bitmap bmp frame.ToBitmap();拿到Bitmap之后就需要做二维码识别了。这里我踩过几个坑第一个坑是识别算法在弱光照和模糊情况下容易失败。产线上产品都是快速移动相机曝光时间必须足够短才能把二维码拍清晰。如果发现识别率低先检查图像亮度是否正常、二维码是否锐利再考虑换算法。第二个坑是识别库的选择。可以用开源库ZXing.Net也可以用自己的算法库。ZXing.Net对DataMatrix和QRCode支持还可以但对工业条码支持一般。我的项目里因为要兼顾多种码制最后采用了Halcon的FindDataCode2d算子识别稳定性和速度都让人满意。用Halcon的话C#里可以通过HalconDotNet来调用大致流程是把Bitmap转成HObject然后执行识别算子HObject hoImage; Bitmap bmp frame.ToBitmap(); // Bitmap转HObject HOperatorSet.GenEmptyObj(out hoImage); HOperatorSet.ReadImage(out hoImage, bmp); // 具体转换方式有多种建议封装一个辅助类 HTuple hv_DataCodeHandle, hv_ResultHandle; HOperatorSet.CreateDataCode2dModel(DataMatrix ECC200, new HTuple(), new HTuple(), out hv_DataCodeHandle); HOperatorSet.FindDataCode2d(hoImage, hv_DataCodeHandle, stop_after_result_num, 1, out hv_ResultHandle, out _);如果不想引入Halcon用ZXing.Net也可以但一定要在程序里加一个识别失败的兜底逻辑允许重试、允许人机确认、允许跳过避免因为一次识别失败导致整条产线停机。3.2 数据库表设计与写入逻辑数据库这边我建了一张主表存放每次识别的产品ID常用字段字段名类型说明Idint 自增主键自增序号ProductIdvarchar(64)识别出来的ID内容WorkStationvarchar(32)工位编号Resultint识别结果1成功0失败CreateTimedatetime写入时间ImagePathvarchar(255)保存的图片路径便于追溯主表设计的几个原则ProductId必须有索引因为后续要按ID查追溯记录CreateTime默认值设为GETDATE()代码里不用专门赋值ImagePath建议保存相对路径方便整个数据目录迁移。写入数据库的代码用ADO.NET最简单using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql INSERT INTO dbo.ProductRecord (ProductId, WorkStation, Result, ImagePath) VALUES (pid, station, result, imagePath); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(pid, productId); cmd.Parameters.AddWithValue(station, stationName); cmd.Parameters.AddWithValue(result, resultFlag); cmd.Parameters.AddWithValue(imagePath, imagePath); cmd.ExecuteNonQuery(); } }记住一定要用参数化查询不要直接拼接字符串。一方面是为了防止SQL注入另一方面是参数化查询能提升执行计划复用率在长时间循环写入时差异非常明显。另外还要考虑重复数据。产线可能因为异常重复扫码如果同一个ID在短时间内重复出现可能是误判或者重工。我通常在写入前先查一遍SELECT COUNT(1) FROM ProductRecord WHERE ProductIdpid AND CreateTimeDATEADD(MINUTE,-1,GETDATE())如果计数大于0就认为是重复扫码可以选择跳过或者单独记录下来而不是直接插进主表。3.3 图片保存策略除了上传ID数据最好把每次识别的图片都保存下来。这样一旦后续追溯发现ID错误还能调出当时的原始图片重新分析。我项目里的做法是按日期建文件夹图片文件名用日期_时间_产品ID的方式命名例如20250614_153022_ABC123.png。保存图片的代码string dateDir Path.Combine(D:\ImageArchive, DateTime.Now.ToString(yyyyMMdd)); Directory.CreateDirectory(dateDir); string filePath Path.Combine(dateDir, ${DateTime.Now:HHmmssfff}_{productId}.png); bmp.Save(filePath, ImageFormat.Png);这里有个细节一定要先保存图片再写数据库而且数据库里记录的ImagePath要和实际保存路径完全一致。我项目里出现过一次数据库先写成功、图片还没来得及存盘结果程序崩溃导致数据库有记录但图片文件丢失的情况。所以顺序应该是保存图片、写数据库、更新界面。4. 实操过程与完整代码链路4.1 相机取流与识别主流程代码把上面的模块串起来一次完整的业务动作大概是这样的private void ProcessOneProduct() { // 1. 等待运动到位 if (!WaitMotionDone(axisPulseCount)) return; // 2. 触发相机抓拍 TriggerCamera(); // 3. 获取图像 MvFrame frame new MvFrame(); if (camera.GetFrame(ref frame, 3000) ! 0) { Log(取流超时); return; } // 4. 转Bitmap并保存 Bitmap bmp frame.ToBitmap(); string imagePath SaveImage(bmp); // 5. 识别ID string productId RecognizeBarcode(bmp); if (string.IsNullOrEmpty(productId)) { Log(识别失败); WriteRecord(productId, 0, imagePath); return; } // 6. 防重复 if (IsDuplicate(productId)) { Log(重复ID跳过); return; } // 7. 上传数据库 WriteRecord(productId, 1, imagePath); // 8. 更新界面 UpdateUI(productId); }这段代码是单线程串行的好处是逻辑简单。如果产线节拍快可以把取流和识别放到后台线程UI线程只做显示。我项目里因为节拍不快串行完全够用还省了一堆线程同步的麻烦。要特别注意的是GetFrame超时时间的设置。如果触发信号没接好或者相机没配触发模式GetFrame会一直等所以我设置了3000ms超时超时后走异常分支。这个超时时间不是越长越好太长会导致程序卡住太短又可能因为产品还没到位就拍照失败。建议根据实际产线节拍来定一般是节拍的1.5倍到2倍。4.2 运动控制卡定位与触发联动代码运动控制这块我用的是相对位移加绝对位移混合的方式。举个实际例子每次产品到位后Z轴要下压一下然后Y轴把产品推入相机视野。为了保证定位准确轴运动前先要判断原点是否已经回好否则直接运动可能撞机。private bool MoveAxisToPosition(int axis, int targetPos, int velocity) { // 检查原点信号 if (!IsHomeDone(axis)) { Log($轴{axis}未回原点); return false; } // 绝对位置运动 short ret dmc_pmove(0, axis, targetPos, 0, velocity, 1000, 1000); if (ret ! 0) { Log($轴{axis}运动指令发送失败错误码{ret}); return false; } // 等待运动完成 int timeoutMs 5000; while (timeoutMs 0) { short done 0; dmc_check_done(0, axis, ref done); if (done 1) return true; Thread.Sleep(10); timeoutMs - 10; } Log($轴{axis}运动超时); return false; }关于脉冲数换算必须清楚你用的伺服或步进驱动的电子齿轮比。比如电机转一圈需要10000个脉冲减速比是10:1丝杆导程是10mm那么1mm对应10000个脉冲。这个换算不搞清楚运动控制永远调不准。我这边常用的公式脉冲数 目标距离(mm) × 电机编码器分辨率 ÷ 电子齿轮比 ÷ 丝杆导程(mm)比如目标距离10mm编码器分辨率10000电子齿轮比设为1丝杆导程10mm那脉冲数就是10 × 10000 ÷ 1 ÷ 10 10000个脉冲然后在代码里把这个脉冲数算好传给dmc_pmove。4.3 数据库连接管理数据库的连接字符串我一般放到App.config里connectionStrings add nameMainDB connectionStringServerlocalhost;DatabaseProductTrace;User Idsa;Password******;Trusted_ConnectionFalse; providerNameSystem.Data.SqlClient / /connectionStrings但是注意不能用传统的写法每次new SqlConnection再Open。如果产线连续跑频繁打开关闭数据库连接会影响性能。最方便的做法是用using语句块配合连接池。ADO.NET默认是启用连接池的连接字符串一样时SqlConnection会自动复用池里的连接所以只要每个操作都用using释放连接即可。不过数据库服务器如果不在本机网络闪断会导致连接池里的连接全部失效下次GetConnection会抛异常。所以我在数据库操作外面包了一层重试机制private T ExecuteWithRetryT(FuncT action, int maxRetryCount 3) { int retry 0; while (retry maxRetryCount) { try { return action(); } catch (SqlException ex) { retry; Log($数据库操作失败第{retry}次重试错误{ex.Message}); Thread.Sleep(500 * retry); } } throw new Exception(数据库操作重试多次仍失败); }这样处理以后即使数据库临时重启或网络抖动程序也能自动恢复不用人工干预。4.4 UI状态刷新与日志记录上位机界面不用做得很花哨但状态要一目了然。我的主界面放了几个关键控件实时相机画面PictureBox、当前识别ID的Label、运动状态的Label、数据库状态的Label还有一个运行日志的RichTextBox。注意这些控件都只能在UI线程里更新后台线程里直接操作会抛异常。我封装了一个LogAndShow方法通过BeginInvoke让UI线程安全地更新private void LogAndShow(string message) { if (this.InvokeRequired) { this.BeginInvoke(new Actionstring(LogAndShow), message); return; } txtLog.AppendText(${DateTime.Now:HH:mm:ss.fff} {message}{Environment.NewLine}); }日志里建议把每次识别结果、数据库返回状态、运动完成时间都记录下来。尤其出问题时这些日志能帮你快速定位是相机问题、运动问题还是数据库问题。5. 常见问题与排查技巧实录5.1 相机连不上或掉线的排查工业相机在生产中最容易出现连不上、掉线、取流失败这几个问题。我把它们按概率从高到低排个序你在现场可以照着查第一网络IP没设置好。海康千兆网相机的IP和工控机网卡必须处于同一网段。比如相机默认IP是192.168.1.10那网卡的IP就要设置成192.168.1.x不能是192.168.2.x。MVS工具里可以直接查看相机当前IP也可以在网卡属性里临时改IP。第二网卡巨型帧Jumbo Frame设置不对。GigE相机分辨率高时网卡需要开启巨型帧否则数据量大时容易丢包导致取流不稳定。建议在网卡高级设置里把Jumbo Packet设为9KB。如果你的工控机网卡不支持巨型帧就把相机的分辨率降低或者减小PayloadSize也能缓解。第三相机的触发线接反了。硬触发模式下触发源线序接错相机一直处于等待触发状态GetFrame必定超时。排查方法是用万用表测触发线电平或者先改用软触发测试相机是否正常出图。MVS Debug信息里能看到相机SDK错误码。比如0x80000007是资源获取失败0x80000215是参数错误。遇到错误码先查官方错误码表比瞎猜快得多。5.2 二维码识别率低的优化识别率低的原因通常有几个光照问题。工业现场灯光往往不均匀二维码表面反光或者阴影都会导致识别失败。我项目里调整过光照角度增加了环形光源效果立竿见影。如果你不方便动硬件可以在图像处理里先做一次灰度化加直方图均衡化提高对比度后再识别。相机曝光时间太长导致运动模糊。产品只要在运动曝光时间一长就会拖影。我的做法是在硬触发模式下把曝光时间压到500微秒到1毫秒然后用光源补偿亮度。码制类型选错。有些产品上是QRCode有些是DataMatrixHalcon里创建DataCode2d模型时如果只指定了一种码制另一种就识别不出来。保险的做法是同时认定多个码制识别时依次尝试。识别成功之后一定要验证字符串长度。有一次客户产品上的二维码打印不全识别出来只有5个字符正常应该是12位。如果程序不做长度校验这条脏数据就直接进数据库了后期追溯会很麻烦。所以我在识别接口里加了正则校验if (!Regex.IsMatch(productId, ^[A-Z0-9]{4,20}$)) { Log($ID格式异常{productId}); return null; }5.3 运动控制卡常见报警与处理雷赛控制卡运动过程中最常见的异常一是电机不动二是走到一半报错三是回原点不准。电机不动大概率是脉冲模式设置错误或者使能信号没接。很多驱动器需要外部给出使能信号SON/EN后才会执行脉冲指令软件里发脉冲前必须先使能对应轴。回原点不准一般是因为回原点的速度和方向设置不对。DMC1000系列回原点时要设置回零速度如果速度太快机械冲量会触发限位开关后继续滑行导致每次原点位置都不一样。建议回零分两段先高速接近再低速靠近原点这样定位精度明显提升。运动到一半报错的常见原因是加速度过大导致伺服过载或者步进丢步。我一般把加速度设置成运动速度的三倍左右不能太激进。脉冲频率也不能超过控制器上限DMC1000系列单轴最高脉冲频率一般是1MHz到5MHz实际使用留30%余量。出现运动卡报警时可以用雷赛自带的调试工具读取轴报警码。根据报警码才能判断是过流、过压还是位置偏差过大。在现场排查时先看驱动器上的数码管显示再对照驱动器手册一般都能快速定位。5.4 数据库并发写入与连接池问题如果有多台设备同时往同一个库里写数据可能出现死锁或者主键冲突。解决思路有两个一个是把写入操作做成队列只有一个后台线程负责写库。其它线程只把数据放进ConcurrentQueue后台线程逐个取出写入。这样从源头避免并发冲突。另一个是数据库层面设置合适的索引和约束。ProductId字段建立索引CreateTime字段建议建立复合索引查询效率会高很多。同时给表加上必要的唯一约束比如某些场景下同一产品不允许重复入库就在ProductId上加唯一索引数据库帮你挡掉重复数据。连接池的问题在前面已经提过。补充一点SqlConnection用完一定要Dispose这个不用多解释但很多做上位机的朋友习惯用静态连接导致程序长时间运行后连接池泄露。用using包裹是最安全的。5.5 线程卡死和界面无响应上位机最怕的就是界面卡死。原因大多是后台线程直接操作了UI控件或者某个阻塞操作放在了UI线程。比如GetFrame超时3秒如果在UI线程里调用按下按钮后界面就冻住3秒非常影响操作。解决的思路很明确所有耗时操作都放后台线程。我的做法是按钮事件里用Task.Run把处理流程丢到线程池UI更新通过BeginInvoke回到UI线程private void btnStart_Click(object sender, EventArgs e) { Task.Run(() ProcessLoop()); }另外循环里不要用Thread.Sleep做长时间等待尽量用WaitHandle或Timer。如果一个循环里Sleep(1000)持续几分钟线程调度会变得不敏感自动恢复时的响应速度会变慢。如果出现死锁大多数情况是多个锁的获取顺序不一致导致的。写多线程代码时所有加锁顺序要全局一致。我项目里只有数据库操作和相机回调会加锁其它模块尽量无锁化死锁概率大大降低。6. 一些实用的小技巧写到这里再分享几个实际操作中的小技巧不是说多高深但关键时刻能省不少事。第一程序启动时先自检一遍。开机先把相机打开、控制卡初始化、数据库连接测试一次哪一项失败就在界面上红字提示不要等产线跑起来才发现相机关了。第二所有硬件操作都封装成独立类库哪怕现在项目里用不到那么多接口也建议把相机、运动卡、数据库分别放到不同的类里。后面产线扩机台、换设备只需要改对应的类主程序几乎不动。第三识别失败和上传失败的记录一定要单独保存成本地文本或独立数据表不要和主记录混在一起。我这边维护了一张ErrorLog表专门记录失败时间、失败阶段、失败原因和当时的图片路径排查问题非常有用。第四如果是长期运行的产线程序建议加一个看门狗或者心跳逻辑。比如每隔30秒自动检测一次相机和控制卡状态如果异常则预警。这个小功能不算复杂但能提前发现问题减少产线停机时间。我项目里就遇到过相机在晚上突然掉线的情况没有看门狗的话第二天早上才发现停了一整夜。最后再说一个细节程序的日志文件要按日期分文件并且定时清理。产线程序跑起来是7x24小时的日志如果不管理几个月就能把硬盘写满。我在程序里加了一个启动时的清理任务只保留最近30天的日志这个习惯救过我不止一次。本文还有配套的精品资源点击获取
返回列表