文章简介
本文带来一套完整工业流水线视觉分拣 WinForm 上位机 Demo,整套系统包含:Modbus‑RTU 接收 PLC 脉冲触发海康 GigE 相机软拍照、Halcon 实现 DataMatrix 二维码解码识别、JAKA 节卡机械臂完成 OK/NG 自动分拣、识别结果存入 Access 数据库保存记录。
业务流程:PLC 下发 Modbus 脉冲 → 相机软触发拍照 → Halcon 识别二维码 → 判断 OK/NG → 机械臂执行取料分拣,OK 放合格工位,NG 放不合格工位;识别数据写入 Access 数据库,可关闭流水线开关只做识别不运行机械臂,适合学习视觉 + 机器人联合工控项目。
一、开发环境与 NuGet / SDK 依赖
- IDE:Visual Studio2022
- 项目类型:Windows 窗体应用 (.NET Framework)
引用库与 SDK:
硬件清单:海康 GigE 工业相机、JAKA 节卡机器人、PLC(Modbus‑RTU)、电脑; 注意:Halcon 需要安装授权;Access 需要安装 AccessDatabaseEngine 驱动;节卡机器人 SDK 版本需要和控制柜固件匹配。
二、WinForm 界面控件说明
表格
| SearchCamera_Click | Button | 搜索局域网内 GigE 相机 |
| comboBox1 | ComboBox | 枚举搜索到的相机,切换选择相机 |
| OpenCamera_Click | Button | 打开相机,启动脉冲监听线程 |
| CloseCamera_Click | Button | 关闭相机,停止采集 |
| GetParameters_Click | Button | 读取相机曝光、增益参数回显到文本框 |
| SetParameter_Click | Button | 设置相机曝光时间、增益 |
| Connection_Click | Button | 连接节卡机械臂,上电上使能回原点 |
| Closing_Click | Button | 断开机械臂,下电销毁句柄 |
| Start_Click | Button | 启动流水线:识别后自动执行机械臂分拣 |
| Stop_Click | Button | 停止流水线:只做识别,机械臂不动作 |
| pictureBox1 | PictureBox | 显示拍照图片,绘制二维码识别框 |
| textBox1 | TextBox | 日志:模型加载、串口、数据库操作日志 |
| textBox2 | TextBox | 日志:相机、脉冲、拍照、识别结果日志 |
| textBox3 | TextBox | 填写相机曝光时间 |
| textBox4 | TextBox | 填写相机增益 |
| textBox5 | TextBox | 机械臂状态、调试日志输出 |
三、完整 Form1 代码
using HalconDotNet;
using jakaApi;
using jkType;
using MvCameraControl;
using NModbus;
using NModbus.Device;
using NModbus.Serial;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Drawing.Drawing2D;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace c_CombineJAKA_ZU
{
public partial class Form1 : Form
{
private readonly string SaveFolder = Path.Combine(Application.StartupPath, \”拍照缓存\”);
private HTuple Data2DCode;
//串口和Modbus
private SerialPort _SerialPort;
private IModbusSerialMaster _ModbusMaster;
//相机
private readonly DeviceTLayerType Type = DeviceTLayerType.MvGigEDevice;
private List<IDeviceInfo> deviceInfoList = new List<IDeviceInfo>();
private IDeviceInfo deviceInfo;
private IDevice device;
//相机采集
private bool isGrabbing = false;
//线程
private System.Threading.CancellationTokenSource _stop;
private System.Threading.Thread _checkThread;
private readonly object _lock = new object();
private readonly object _frameLock = new object();
private readonly object _robotLock = new object();
//机械臂
private const string RobotIP = \”192.168.1.101\”;
private int _robotHandle = 0;
private const double D2R = Math.PI / 180;
// 升降安全高度差值(模拟抓取下降距离,按需修改)
private const double DownOffset = 180;
#region 【4组基础点位 + 自动升降衍生点位】
// 流水线总开关:true=启动程序,识别后自动走机械分拣;false=仅识别不动作
private bool _workEnable = false;
// 流水线开关同步锁
private readonly object _workLock = new object();
private bool _robotConnected = false;
// 1. 待机原点(高位安全位置)
private JKTYPE.CartesianPose _posHome = new JKTYPE.CartesianPose()
{
tran = new JKTYPE.CartesianTran { x = 328.237, y = 400.956, z = 155.083 },
rpy = new JKTYPE.Rpy { rx = -179.765 * D2R, ry = 0.760 * D2R, rz = 89.876 * D2R }
};
// 2. 取料上方安全点位(高位不下压)
private JKTYPE.CartesianPose _posPickUp = new JKTYPE.CartesianPose()
{
tran = new JKTYPE.CartesianTran { x = 371.769, y = 215.102, z = 155.083 },
rpy = new JKTYPE.Rpy { rx = -179.765 * D2R, ry = 0.760 * D2R, rz = 89.876 * D2R }
};
// 2‑1 取料下压点位(_posPickUp Z轴 ‑80,模拟靠近零件)
private JKTYPE.CartesianPose _posPickDown;
// 3 OK传送带上方安全点位
private JKTYPE.CartesianPose _posOkUp = new JKTYPE.CartesianPose()
{
tran = new JKTYPE.CartesianTran { x = 330.253, y = 583.318, z = 155.083 },
rpy = new JKTYPE.Rpy { rx = -179.765 * D2R, ry = 0.760 * D2R, rz = 89.876 * D2R }
};
// 3‑1 OK放料下压点位
private JKTYPE.CartesianPose _posOkDown;
// 4 NG传送带上方安全点位
private JKTYPE.CartesianPose _posNgUp = new JKTYPE.CartesianPose()
{
tran = new JKTYPE.CartesianTran { x = 566.681, y = 298.829, z = 155.083 },
rpy = new JKTYPE.Rpy { rx = -179.765 * D2R, ry = 0.760 * D2R, rz = 89.876 * D2R }
};
// 4‑1 NG放料下压点位
private JKTYPE.CartesianPose _posNgDown;
#endregion
public Form1()
{
InitializeComponent();
CheckFolder();
LoadQrCodeModel();
ConnectSerialPort();
ConnectModbus();
SDKSystem.Initialize();
// 取料下压
_posPickDown = _posPickUp;
_posPickDown.tran.z -= DownOffset;
// OK下压
_posOkDown = _posOkUp;
_posOkDown.tran.z -= DownOffset;
// NG下压
_posNgDown = _posNgUp;
_posNgDown.tran.z -= DownOffset;
}
private void CheckFolder()
{
if (!Directory.Exists(SaveFolder))
Directory.CreateDirectory(SaveFolder);
}
//加载解二维码模型
private void LoadQrCodeModel()
{
try
{
HOperatorSet.CreateDataCode2dModel(\”Data Matrix ECC 200\”, \”default_parameters\”, \”maximum_recognition\”, out Data2DCode);
textBox1.AppendText($\”解二维码模型加载成功\\r\\n\”);
}
catch (Exception ex)
{
textBox1.AppendText($\”解二维码模型加载失败,错误:{ex.Message}\\r\\n\”);
}
}
//加载串口
private void ConnectSerialPort()
{
try
{
_SerialPort = new SerialPort(\”COM5\”, 9600, Parity.None, 8, StopBits.One)
{
ReadTimeout = 1000,
WriteTimeout = 1000
};
_SerialPort.Open();
textBox1.AppendText(\”串口连接成功,\”);
}
catch (Exception ex)
{
textBox1.AppendText($\”串口连接失败,错误:{ex.Message},\”);
}
}
//加载Modbus,用来接收脉冲
private void ConnectModbus()
{
try
{
ModbusFactory factory = new ModbusFactory();
_ModbusMaster = factory.CreateRtuMaster(_SerialPort);
textBox1.AppendText(\”Modbus连接成功\\r\\n\”);
网硕互联帮助中心





评论前必须登录!
注册