工业视觉检测:用 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++ 上位机开发的首选通信方案。

相关推荐
小短腿的代码世界34 分钟前
Qt OpenGL 架构与自定义着色器:源码级解析高性能图形渲染
qt·架构·着色器
蜡笔小马1 小时前
07.C++设计模式-组合模式
c++·设计模式·组合模式
liulilittle1 小时前
TCP UCP v1.0:BBR 的非破坏性约束层
网络·c++·网络协议·tcp/ip·算法·c·通信
每天回答3个问题1 小时前
leetcodeHot100 | 104.二叉树的最大深度
c++·面试·
坚果派·白晓明1 小时前
【鸿蒙PC三方库移植适配框架解读系列】第五篇:完整流程图与角色职责
c语言·c++·华为·harmonyos·鸿蒙
xiao_li_ya2 小时前
C++学习日记1(`*`的理解、const关键词)
开发语言·c++
聆风吟º2 小时前
【C标准库】深入理解C语言 isalpha 函数详解:判断字符是否为字母
c语言·开发语言·库函数·isalpha
郝学胜-神的一滴4 小时前
Qt 入门 01-02: 开发环境搭建指南
开发语言·c++·qt·客户端
Languorous.4 小时前
C++数据结构高阶|布隆过滤器(Bloom Filter)深度解析:从原理到手写实现,面试高频考点全覆盖
数据结构·c++·面试
SurpriseDPD4 小时前
Linux 内核基础知识:READ_ONCE、内存屏障与指令重排
linux·系统架构