ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

C++ + Snap7 + Qt 实现 西门子PLC 通信

C++ + Snap7 + Qt 实现 西门子PLC 通信 一、前言最近做的项目需要上位机软件与西门子PLC进行数据交换。如控制电机启停、设置运行参数、读取传感器数据、获取设备运行状态。本文使用 C、Qt、Snap7、西门子 PLC 实现了一个简单的 PLC 通信测试程序。1.1 配置Cvisual studio 2022Qt5.12.10Snap71.4.2西门子 1200 PLC1.2 整体架构-------------------| Qt界面层 || MainWindow | 负责用户操作-------------------⬇---------------------| PLCController || 通信封装层 | 负责 PLC 通信---------------------⬇-------------------------| 西门子 1200 PLC |-------------------------1.3 环境配置Qt1. https://blog.csdn.net/qq_62888264/article/details/1326450542. https://blog.csdn.net/calmreason/article/details/132571344snap7https://blog.csdn.net/qq_34935373/article/details/107702089二、完整代码源码https://pan.baidu.com/s/1ZeHNoVZ-bziwj1dKWvJyGQ?pwduw65 提取码: uw65main.cpp#include QApplication #include mainwindow.h int main(int argc, char* argv[]) { QApplication app(argc, argv); MainWindow w; w.resize(400, 500); w.show(); return app.exec(); }mainwindow.h#pragma once #include QMainWindow #include QPushButton #include QLineEdit #include QLabel #include QTimer #include PLCController.h class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget* parent nullptr); private slots: void connectPLC(); void disconnectPLC(); void startMotor(); void stopMotor(); void setSpeed(); void updatePLC(); private: PLCController plc; QPushButton* btnConnect; QPushButton* btnDisconnect; QPushButton* btnStart; QPushButton* btnStop; QPushButton* btnSetSpeed; QLineEdit* speedInput; QLabel* statusLabel; QLabel* feedbackLabel; QLabel* beltLabel; QTimer* timer; };mainwindow.cpp#include mainwindow.h #include QVBoxLayout #include QHBoxLayout MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), plc(192.168.1.201, 0, 1) { QWidget* widget new QWidget; QVBoxLayout* mainLayout new QVBoxLayout; statusLabel new QLabel(PLC status: Disconnected); feedbackLabel new QLabel(Speed feedback: 0); beltLabel new QLabel(Belt speed: 0); btnConnect new QPushButton(Connect PLC); btnDisconnect new QPushButton(Disconnect PLC); btnStart new QPushButton(Start); btnStop new QPushButton(Stop); speedInput new QLineEdit(); speedInput-setPlaceholderText(Enter speed); btnSetSpeed new QPushButton(Set Speed); QHBoxLayout* connectLayout new QHBoxLayout; connectLayout-addWidget(btnConnect); connectLayout-addWidget(btnDisconnect); QHBoxLayout* motorLayout new QHBoxLayout; motorLayout-addWidget(btnStart); motorLayout-addWidget(btnStop); mainLayout-addWidget(statusLabel); mainLayout-addLayout(connectLayout); mainLayout-addLayout(motorLayout); mainLayout-addWidget(speedInput); mainLayout-addWidget(btnSetSpeed); mainLayout-addWidget(feedbackLabel); mainLayout-addWidget(beltLabel); widget-setLayout(mainLayout); setCentralWidget(widget); connect(btnConnect, QPushButton::clicked, this, MainWindow::connectPLC); connect(btnDisconnect, QPushButton::clicked, this, MainWindow::disconnectPLC); connect(btnStart, QPushButton::clicked, this, MainWindow::startMotor); connect(btnStop, QPushButton::clicked, this, MainWindow::stopMotor); connect(btnSetSpeed, QPushButton::clicked, this, MainWindow::setSpeed); timer new QTimer(this); connect(timer, QTimer::timeout, this, MainWindow::updatePLC); timer-start(500); } void MainWindow::connectPLC() { if (plc.connect()) { statusLabel-setText(PLC status: Connected); } } void MainWindow::disconnectPLC() { plc.disconnect(); statusLabel-setText(PLC status: Disconnected); } void MainWindow::startMotor() { plc.write_plc(M, 0, 114, 0, BOOL, 1); } void MainWindow::stopMotor() { plc.write_plc(M, 0, 114, 1, BOOL, 1); } void MainWindow::setSpeed() { int speed speedInput-text().toInt(); plc.write_plc(DB, 1, 4, 0, REAL, speed); } void MainWindow::updatePLC() { if (!plc.check_connection_status()) return; double feedback plc.read_plc(DB, 1, 0, 0, REAL); double belt plc.read_plc(DB, 1, 8, 0, REAL); feedbackLabel-setText(QString(Speed feedback: %1).arg(feedback)); beltLabel-setText(QString(Belt speed: %1 m/s).arg(belt)); }PLCController.h#pragma once #include snap7.h #include iostream #include vector #include string #include cstring class PLCController { public: PLCController(std::string ip 192.168.1.201, int rack 0, int slot 1, bool debug true); ~PLCController(); bool connect(); void disconnect(); bool check_connection_status(); bool write_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string data_type, double value); double read_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string data_type); bool batch_write_bits_snap7(const std::string area, int db_num, int start_byte, int start_bit, const std::vectorbool bits); private: std::string ip; int rack; int slot; bool enabled; bool debug; TS7Client* client; int area_code(const std::string area); int data_size(const std::string type); std::vectoruint8_t pack_value(double value, const std::string type, int bit); double unpack_value(const std::vectoruint8_t data, const std::string type, int bit); void debug_log(const std::string msg); };PLCController.cpp#include PLCController.h PLCController::PLCController(std::string ip, int rack, int slot, bool debug) { this-ip ip; this-rack rack; this-slot slot; this-debug debug; enabled true; client nullptr; } PLCController::~PLCController() { disconnect(); } void PLCController::debug_log(const std::string msg) { if (debug) std::cout msg std::endl; } bool PLCController::connect() { if (!enabled) return false; disconnect(); client new TS7Client(); int result client-ConnectTo(ip.c_str(), rack, slot); if (result 0) { debug_log(PLC connect successful IP: ip); return true; } debug_log(PLC connect failed); delete client; client nullptr; return false; } void PLCController::disconnect() { if (client) { client-Disconnect(); delete client; client nullptr; debug_log(PLC disconnected); } } bool PLCController::check_connection_status() { if (!client) return false; return client-Connected(); } int PLCController::area_code(const std::string area) { if (area I) return S7AreaPE; if (area Q) return S7AreaPA; if (area M) return S7AreaMK; if (area DB) return S7AreaDB; return -1; } int PLCController::data_size(const std::string type) { if (type BOOL) return 1; if (type BYTE) return 1; if (type WORD) return 2; if (type DWORD) return 4; if (type INT) return 2; if (type DINT) return 4; if (type REAL) return 4; return 0; } // 有修改 std::vectoruint8_t PLCController::pack_value(double value, const std::string type, int bit) { int size data_size(type); std::vectoruint8_t buf(size, 0); if (type BOOL) { if (value ! 0) buf[0] | (1 bit); } else if (type BYTE) { uint8_t v static_castuint8_t(value); buf[0] v; } else if (type WORD) { uint16_t v static_castuint16_t(value); buf[0] static_castuint8_t(v 8); buf[1] static_castuint8_t(v 0xff); } else if (type INT) { int16_t v static_castint16_t(value); uint16_t u static_castuint16_t(v); buf[0] static_castuint8_t(u 8); buf[1] static_castuint8_t(u 0xff); } else if (type DWORD) { uint32_t v static_castuint32_t(value); buf[0] static_castuint8_t(v 24); buf[1] static_castuint8_t(v 16); buf[2] static_castuint8_t(v 8); buf[3] static_castuint8_t(v); } else if (type DINT) { int32_t v static_castint32_t(value); uint32_t u static_castuint32_t(v); buf[0] static_castuint8_t(u 24); buf[1] static_castuint8_t(u 16); buf[2] static_castuint8_t(u 8); buf[3] static_castuint8_t(u); } else if (type REAL) { float v static_castfloat(value); uint32_t raw; memcpy(raw, v, sizeof(float)); buf[0] static_castuint8_t(raw 24); buf[1] static_castuint8_t(raw 16); buf[2] static_castuint8_t(raw 8); buf[3] static_castuint8_t(raw); } return buf; } /* area: 区域 I, Q, M, DB db_num : DB块编号(非 DB 区域时为 0) byte_offset : 字节偏移 bit_offset : 位偏移(非 BOOL 类型为 0) data_type : 数据类型 BOOL, BYTE, WORD, DWORD, INT, DINT, REAL value : 值 */ bool PLCController::write_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string type, double value) { if (!check_connection_status()) { debug_log(PLC not connected); return false; } auto data pack_value(value, type, bit_offset); int result; if (area DB) { result client-DBWrite(db_num, byte_offset, data.size(), data.data()); } else { result client-WriteArea(area_code(area), db_num, byte_offset, data.size(), S7WLByte, data.data()); } if (result 0) { debug_log(PLC write successful); return true; } debug_log(PLC write failed); return false; } double PLCController::unpack_value(const std::vectoruint8_t data, const std::string type, int bit) { if (type BOOL) { return (data[0] (1 bit)) ! 0; } else if (type BYTE) { return data[0]; } else if (type WORD) { uint16_t v ((uint16_t)data[0] 8) | ((uint16_t)data[1]); return v; } else if (type INT) { uint16_t u ((uint16_t)data[0] 8) | ((uint16_t)data[1]); int16_t v static_castint16_t(u); return v; } else if (type DWORD) { uint32_t v ((uint32_t)data[0] 24) | ((uint32_t)data[1] 16) | ((uint32_t)data[2] 8) | ((uint32_t)data[3]); return v; } else if (type DINT) { uint32_t u ((uint32_t)data[0] 24) | ((uint32_t)data[1] 16) | ((uint32_t)data[2] 8) | ((uint32_t)data[3]); int32_t v static_castint32_t(u); return v; } else if (type REAL) { uint32_t raw ((uint32_t)data[0] 24) | ((uint32_t)data[1] 16) | ((uint32_t)data[2] 8) | ((uint32_t)data[3]); float v; memcpy(v, raw, sizeof(float)); return v; } return 0; } double PLCController::read_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string type) { if (!check_connection_status()) return 0; int size data_size(type); std::vectoruint8_t data(size); int result; if (area DB) { result client-DBRead(db_num, byte_offset, size, data.data()); } else { result client-ReadArea(area_code(area), db_num, byte_offset, size, S7WLByte, data.data()); } if (result ! 0) { debug_log(PLC read failed); return 0; } return unpack_value(data, type, bit_offset); }三、代码说明3.1 PLCController 类PLCController主要完成PLC连接管理、PLC断开、数据读取、数据写入、数据类型转换、通信状态检测。3.1.1 核心业务接口connect() / disconnect()管理与PLC的连接状态。check_connection_status()检查当前连接是否有效。write_plc()向PLC的指定区域写入数据支持多种数据类型。read_plc()从PLC的指定区域读取数据返回解析后的数值。3.1.2 构造函数PLCController::PLCController(std::string ip, int rack, int slot, bool debug) { this-ip ip; this-rack rack; this-slot slot; this-debug debug; enabled true; client nullptr; }主要参数ipPLC的网络地址如192.168.1.201。rack / slot机架rack与槽slot对于S7-1200通常使用机架0槽1。3.1.3 PLC连接函数 connect()核心bool PLCController::connect() { ... int result client-ConnectTo( ip.c_str(), rack, slot ); ... }Snap7 中 ConnectTo() 返回 0 表示连接成功。3.1.4 PLC断开 disconnect()析构函数保证对象销毁时自动断开 PLC。PLCController::~PLCController() { disconnect(); }3.1.5 PLC写入 write_plc()/* area: 区域 I, Q, M, DB db_num : DB块编号(非 DB 区域时为 0) byte_offset : 字节偏移 bit_offset : 位偏移(非 BOOL 类型为 0) type : 数据类型 BOOL, BYTE, WORD, DWORD, INT, DINT, REAL value : 值 */ bool PLCController::write_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string type, double value) { if (!check_connection_status()) { debug_log(PLC not connected); return false; } auto data pack_value(value, type, bit_offset); int result; if (area DB) { result client-DBWrite(db_num, byte_offset, data.size(), data.data()); } else { result client-WriteArea(area_code(area), db_num, byte_offset, data.size(), S7WLByte, data.data()); } if (result 0) { debug_log(PLC write successful); return true; } debug_log(PLC write failed); return false; }参数area: 区域 I, Q, M, DBdb_num : DB块编号(非 DB 区域时为 0)byte_offset : 字节偏移bit_offset : 位偏移(非 BOOL 类型为 0)type : 数据类型 BOOL, BYTE, WORD, DWORD, INT, DINT, REALvalue : 值调用plc.write_plc(DB, 1, 4, 0, REAL, 100)表示向 西门子PLC 的 DB 块数据块写入一个REAL浮点数/实数值具体是将DB1.DBD4数据块1的第4个字节开始的4个字节设置为100.0。plc.write_plc(M, 0, 114, 0, BOOL, 1)表示向 西门子PLC 的M区位存储区写入一个BOOL布尔值具体是将M114.0第114个字节的第0位设置为1。流程write_plc() → 检查连接 → pack_value() → DBWrite() / WriteArea() → PLC核心if (area DB) { result client-DBWrite(db_num, byte_offset, data.size(), data.data()); } else { result client-WriteArea(area_code(area), db_num, byte_offset, data.size(), S7WLByte, data.data()); }其他说明参数 area 由area_code()函数转换为对应的区域ID供WriteArea()使用PLC常见区域如下PLC区域ID含义IS7AreaPE(0x81)输入QS7AreaPA(0x82)输出MS7AreaMK(0x83)辅助继电器DBS7AreaDB(0x84)数据块参数 type 由data_size()函数转换为需要读取的字节大小PLC中的变量类型如下类型长度BOOL1 bitBYTE1 byteWORD2 byteINT2 byteDWORD4 byteDINT4 byteREAL4 bytePLC通信不能直接传递 C 变量需要由pack_value()函数将 C变量转换为 PLC字节数据byte数组。例如给DB1.DBD4写入REAL 100.5最终发送42 C9 00 00。3.1.6 PLC读取 read_plc()double PLCController::read_plc(const std::string area, int db_num, int byte_offset, int bit_offset, const std::string type) { if (!check_connection_status()) return 0; int size data_size(type); std::vectoruint8_t data(size); int result; if (area DB) { result client-DBRead(db_num, byte_offset, size, data.data()); } else { result client-ReadArea(area_code(area), db_num, byte_offset, size, S7WLByte, data.data()); } if (result ! 0) { debug_log(PLC read failed); return 0; } return unpack_value(data, type, bit_offset); }参数area: 区域 I, Q, M, DBdb_num : DB块编号(非 DB 区域时为 0)byte_offset : 字节偏移bit_offset : 位偏移(非 BOOL 类型为 0)type : 数据类型 BOOL, BYTE, WORD, DWORD, INT, DINT, REAL调用double belt plc.read_plc(DB, 1, 8, 0, REAL)表示从西门子PLC的DB块数据块中读取一个REAL浮点数/实数值具体是从DB1.DBD8数据块1的第8个字节开始的4个字节读取一个32位浮点数并将其存储在C的double变量中。流程PLC → DBRead() → byte数组 → unpack_value() → doubleunpack_value()将从PLC读取的字节向量还原为double值。3.1.7 其他S7-1200 PLC采用大端Big-Endian字节序。在打包时我们需要将多字节数据的高位字节放在起始地址。例如对于REAL类型32位浮点数else if (type REAL) { float v static_castfloat(value); uint32_t raw; memcpy(raw, v, sizeof(float)); buf[0] static_castuint8_t(raw 24); buf[1] static_castuint8_t(raw 16); buf[2] static_castuint8_t(raw 8); buf[3] static_castuint8_t(raw); }四、Qt界面调用界面连接 启动设置速度停止 断开连接五、注意事项BOOL写入可能存在问题例如PLC 的 DB10.DBX0 10101010写 DBX0.1 1会导致DB10.DBX0 00000010其它 7 个 bit 全丢。当前的代码可以在我的项目中正常工作目前没有做进一步修改
返回列表