工业视觉检测:用 C++ 和 Snap7 库快速读写西门子 S7-1200

在工业视觉检测、自动化设备开发中,Windows 端上位机与西门子 S7-1200 PLC 进行数据交互是非常常见的需求。本文基于 MFC + Snap7 开源库 ,从零实现一套稳定、可直接上线的 PLC 通信程序,包含自动连接、循环读取、线程安全写入、异常重连等完整功能,解决实际开发中最容易遇到的界面卡死、Job pending、读写冲突、断线不重连等痛点。

一、方案简介

  • 开发环境:Windows + MFC + C++
  • PLC 型号:西门子 S7‑1200
  • 通信库:Snap7(开源、轻量、跨平台、无驱动依赖)
  • 核心功能
    1. 后台线程循环读取 PLC 数据
    2. 50ms 稳定通信周期,适配相机联动场景
    3. 线程安全读写,避免多线程冲突
    4. 连接断开自动 1s 重连
    5. 界面实时显示连接状态与数据

二、关键结构设计

为保证界面不卡顿,PLC 通信必须放在独立子线程 中执行;为避免读写同时操作导致 Job pending 错误,使用 CCriticalSection 临界区 做线程互斥;界面更新不在线程内直接操作控件,而是通过 自定义消息 异步刷新。

整体流程:

启动线程 → 尝试连接 PLC → 连接成功循环读取 → 异常断开 → 1s 后自动重连

三、核心代码实现

1. PLC 通信线程(自动连接 + 循环读取)

cpp 复制代码
// PLC 数据读取线程(独立线程,不卡界面)
UINT ReadPLCThreadFunc(LPVOID lparam)
{
	Cs7client_testDlg *pDlg = (Cs7client_testDlg *)lparam;

	// 线程主循环
	while(pDlg->m_bRunThread)
	{
		// 连接 PLC:IP、机架0、槽号1
		int res = pDlg->client.ConnectTo("192.168.11.10", 0, 1);
		pDlg->m_nConnectStatus = res;
		pDlg->PostMessage(WM_USER_UPDATE_UI);

		// 连接成功后进入数据读取循环
		while(res == 0 && pDlg->m_bRunThread)
		{
			Sleep(50);  // 通信周期 50ms

			// 加锁:与写操作互斥
			pDlg->m_csPLC.Lock();
			{
				// 读取 DB1.DBB2288 4字节整数
				byte buf1[4] = {0};
				int ret1 = pDlg->client.ReadArea(S7AreaDB, 1, 2288, 4, S7WLByte, buf1);
				if(ret1 != 0) { res = -1; break; }
				pDlg->m_val1 = buf1[3] | (buf1[2] << 8) | (buf1[1] << 16) | (buf1[0] << 24);

				Sleep(5);  // 间隔避免连续读写报错

				// 读取 DB1.DBB2632 4字节整数
				byte buf2[4] = {0};
				int ret2 = pDlg->client.ReadArea(S7AreaDB, 1, 2632, 4, S7WLByte, buf2);
				if(ret2 != 0) { res = -1; break; }
				pDlg->m_val2 = buf2[3] | (buf2[2] << 8) | (buf2[1] << 16) | (buf2[0] << 24);
			}
			pDlg->m_csPLC.Unlock();

			pDlg->PostMessage(WM_USER_UPDATE_UI);
		}

		// 断开并重连
		pDlg->m_nConnectStatus = -1;
		pDlg->PostMessage(WM_USER_UPDATE_UI);
		pDlg->client.Disconnect();
		if(pDlg->m_bRunThread)
			Sleep(1000);
	}

	pDlg->client.Disconnect();
	return 0;
}

2. 线程安全写入(按钮下发数据)

cpp 复制代码
void Cs7client_testDlg::OnBnClickedButton1()
{
    CString sValue;
    m_ctrlEdit1.GetWindowText(sValue);
    int iValue = atoi(sValue);

    if(!client.Connected())
    {
        AfxMessageBox("未连接,请先连接!");
        return;
    }

    // 加锁保证读写不同时执行
    m_csPLC.Lock();
    {
        byte writeBuffer[4] = {0};
        // 大端字节序转换
        writeBuffer[0] = (iValue >> 24) & 0xFF;
        writeBuffer[1] = (iValue >> 16) & 0xFF;
        writeBuffer[2] = (iValue >> 8)  & 0xFF;
        writeBuffer[3] = iValue & 0xFF;

        int result = client.WriteArea(S7AreaDB, 1, 2632, 4, S7WLByte, writeBuffer);
        if(result != 0)
        {
            CString sMsg;
            sMsg.Format("Error writing data:%s ", CliErrorText(result));
            AfxMessageBox(sMsg);
        }
    }
    m_csPLC.Unlock();
}

3. 界面异步更新(避免线程崩溃)

cpp 复制代码
LRESULT Cs7client_testDlg::OnUpdateUI(WPARAM wParam, LPARAM lParam)
{
    // 更新连接状态
    if(m_nConnectStatus == 0)
        m_ctrlStaticNet.SetWindowText("连接成功");
    else
        m_ctrlStaticNet.SetWindowText("连接失败");

    // 更新数值显示
    CString s;
    s.Format("%d", m_val1);
    m_ctrlStatic1.SetWindowText(s);

    s.Format("%d", m_val2);
    m_ctrlStatic3.SetWindowText(s);

    return 0;
}

4. 单次读写测试函数

用于调试、校验地址是否正确:

cpp 复制代码
void Cs7client_testDlg::OnBnClickedButton2()
{
	TS7Client client;
	if(client.ConnectTo("192.168.11.10", 0, 1) == 0)
	{
		AfxMessageBox("Connected to PLC !");
		byte buffer[4];
		int result = client.ReadArea(S7AreaDB, 1, 2320, 4, S7WLByte, buffer);
		if(result == 0)
		{
			int iValue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
			CString sMsg;
			sMsg.Format("Read data:%d,%d,%d,%d  %d",buffer[0],buffer[1],buffer[2],buffer[3],iValue);
			AfxMessageBox(sMsg);
		}

		// 写入测试
		int iValue = 2352;
		byte writeBuffer[4];
		writeBuffer[0] = (iValue >> 24) & 0xFF;
		writeBuffer[1] = (iValue >> 16) & 0xFF;
		writeBuffer[2] = (iValue >> 8) & 0xFF;
		writeBuffer[3] = iValue & 0xFF;
		result = client.WriteArea(S7AreaDB, 1, 2352, 4, S7WLByte, writeBuffer);

		client.Disconnect();
	}
}

四、重点问题与解决方案

  1. 界面卡死将通信逻辑放到子线程,不阻塞主线程。

  2. Job pending 错误连续读写之间加 5ms 间隔,并使用临界区锁保证串行执行。

  3. 断线后不重连读取失败标记断开,外层循环 1s 自动重试。

  4. 线程操作控件崩溃使用 PostMessage 异步更新 UI,不在线程内操作控件。

  5. S7 大端字节序高低字节重新组合,保证 int 数据正确。

五、总结

本文基于 Snap7 库实现了一套工业级稳定 的 S7‑1200 通信程序,代码结构清晰、注释完整、可直接移植到项目中使用。整套方案支持自动重连、线程安全、实时读写,非常适合视觉相机、自动化产线、数据采集等场景。

相比于西门子官方 OPC 或 S7‑NET,Snap7 体积小、部署简单、无版权问题,是 C++ 上位机开发的首选通信方案。

相关推荐
咸鱼翻身小阿橙2 小时前
Qt页面小项目
开发语言·qt·计算机视觉
橙子也要努力变强2 小时前
信号捕捉的底层机制-内核态和用户态初识
linux·服务器·c++
hipolymers2 小时前
C语言是什么
c语言·嵌入式开发·编程范式·高效性·系统级编程
j_xxx404_2 小时前
Linux C 语言编译链接全解析:静态库与动态库从原理到实战
linux·运维·服务器·c语言·编辑器
Daydream.V2 小时前
github基础入门及git安装配置
git·github·git学习·github学习
澈2072 小时前
内存四区模型详解(栈、堆、全局、常量)
c++·面试·职场和发展
Lazionr2 小时前
【链表经典OJ-中】
c语言·数据结构·链表
橙子也要努力变强2 小时前
信号捕捉底层机制-进程与OS
linux·服务器·c++
青瓦梦滋2 小时前
Linux线程
linux·运维·c++