📌 前言
在工业自动化领域,上位机与PLC的通信是不可或缺的环节。汇川Easy 521作为一款高性能小型PLC,广泛应用于各类自动化设备中。本文将详细介绍如何使用Python通过Modbus TCP协议与汇川Easy 521 PLC进行通信,实现X、Y、M、D等区域的读写操作。
🎯 为什么选择Modbus TCP?
Modbus TCP是一种开放、简单、可靠的工业通信协议,具有以下优势:
-
跨平台性:支持Windows、Linux、嵌入式系统
-
易于实现:协议简单,开发周期短
-
网络化:基于以太网,支持远程控制
-
实时性:响应速度快,适合工业控制场景
🛠️ 环境准备
硬件准备
-
汇川Easy 521 PLC(带以太网口)
-
网线(直连或通过交换机)
-
上位机(Windows/Linux均可)
软件环境
python
# 安装pymodbus库
pip install pymodbus
PLC端配置(使用AutoShop软件)
-
设置PLC的IP地址(如:192.168.1.88)
-
确保Modbus TCP服务器使能(默认端口502)
-
确认防火墙允许502端口通信
📊 地址映射关系
汇川Easy系列PLC的元件地址与Modbus地址有固定的映射关系:
| PLC元件 | Modbus类型 | 起始地址(Hex) | 起始地址(Dec) | 功能码 | 说明 |
|---|---|---|---|---|---|
| Y0-Y1777 | 线圈(Coil) | 0xFC00 | 64512 | 01/05/15 | 输出继电器,八进制地址 |
| X0-X1777 | 线圈(Coil) | 0xF800 | 63488 | 01/05/15 | 输入继电器,八进制地址 |
| M0-M7999 | 线圈(Coil) | 0x0000 | 0 | 01/05/15 | 辅助继电器 |
| D0-D7999 | 保持寄存器 | 0x0000 | 0 | 03/06/16 | 16位数据寄存器 |
⚠️ 重要提示:X和Y元件的地址是八进制表示法,例如Y10(八进制)实际对应十进制地址8。
💻 完整代码实现
python
from pymodbus.client import ModbusTcpClient
# ==================== 配置区 ====================
PLC_IP = "192.168.1.88" # 汇川521 PLC的IP地址
PLC_PORT = 502 # Modbus TCP默认端口
# ==================== 地址映射表 ====================
ADDR_OFFSET = {
'Y': 0xFC00, # Y区起始地址: 64512 (0xFC00)
'X': 0xF800, # X区起始地址: 63488 (0xF800)
'M': 0x0000, # M区起始地址: 0
'D': 0x0000, # D寄存器起始地址: 0
}
# ==================== 建立连接 ====================
def connect_plc():
"""建立Modbus TCP连接"""
client = ModbusTcpClient(PLC_IP, port=PLC_PORT)
if client.connect():
print(f"[成功] 已连接到 {PLC_IP}:{PLC_PORT}")
return client
else:
print(f"[失败] 无法连接到 {PLC_IP}:{PLC_PORT}")
return None
# ==================== 线圈操作(Y, X, M区域)====================
def write_coil(client, area, address, value):
"""写入单个线圈"""
if area.upper() not in ADDR_OFFSET:
print(f"[错误] 不支持的区域: {area}")
return False
modbus_addr = ADDR_OFFSET[area.upper()] + address
result = client.write_coil(modbus_addr, value)
if result.isError():
print(f"[失败] 写入 {area}{address} = {value}")
return False
else:
print(f"[成功] 写入 {area}{address} = {value}")
return True
def write_coils(client, area, start_address, values):
"""批量写入线圈"""
if area.upper() not in ADDR_OFFSET:
print(f"[错误] 不支持的区域: {area}")
return False
modbus_addr = ADDR_OFFSET[area.upper()] + start_address
result = client.write_coils(modbus_addr, values)
if result.isError():
print(f"[失败] 批量写入失败")
return False
else:
print(f"[成功] 批量写入 {area}{start_address}开始: {values}")
return True
def read_coil(client, area, address):
"""读取单个线圈"""
if area.upper() not in ADDR_OFFSET:
print(f"[错误] 不支持的区域: {area}")
return None
modbus_addr = ADDR_OFFSET[area.upper()] + address
result = client.read_coils(address=modbus_addr, count=1)
if result.isError():
print(f"[失败] 读取 {area}{address} 失败")
return None
else:
value = result.bits[0]
print(f"[读取] {area}{address} = {value}")
return value
def read_coils(client, area, start_address, count):
"""读取多个线圈"""
if area.upper() not in ADDR_OFFSET:
print(f"[错误] 不支持的区域: {area}")
return None
modbus_addr = ADDR_OFFSET[area.upper()] + start_address
result = client.read_coils(address=modbus_addr, count=count)
if result.isError():
print(f"[失败] 批量读取失败")
return None
else:
values = result.bits[:count]
print(f"[读取] {area}{start_address}-{start_address+count-1}: {values}")
return values
# ==================== 寄存器操作(D区域)====================
def write_register(client, address, value):
"""写入单个D寄存器(16位)"""
modbus_addr = ADDR_OFFSET['D'] + address
result = client.write_register(modbus_addr, value)
if result.isError():
print(f"[失败] 写入 D{address} = {value}")
return False
else:
print(f"[成功] 写入 D{address} = {value}")
return True
def read_register(client, address):
"""读取单个D寄存器"""
modbus_addr = ADDR_OFFSET['D'] + address
result = client.read_holding_registers(address=modbus_addr, count=1)
if result.isError():
print(f"[失败] 读取 D{address} 失败")
return None
else:
value = result.registers[0]
print(f"[读取] D{address} = {value}")
return value
def read_registers(client, start_address, count):
"""读取多个D寄存器"""
modbus_addr = ADDR_OFFSET['D'] + start_address
result = client.read_holding_registers(address=modbus_addr, count=count)
if result.isError():
print(f"[失败] 批量读取失败")
return None
else:
values = result.registers[:count]
print(f"[读取] D{start_address}-D{start_address+count-1}: {values}")
return values
def read_32bit_int(client, start_address):
"""读取32位整数(占用D和D+1两个寄存器)"""
modbus_addr = ADDR_OFFSET['D'] + start_address
result = client.read_holding_registers(address=modbus_addr, count=2)
if result.isError():
print(f"[失败] 读取32位整数失败")
return None
else:
low_word = result.registers[0]
high_word = result.registers[1]
value = (high_word << 16) | low_word
# 处理负数
if value & 0x80000000:
value = value - 0x100000000
print(f"[读取] D{start_address}组合32位整数 = {value}")
return value
# ==================== 主程序示例 ====================
if __name__ == "__main__":
# 建立连接
client = connect_plc()
if client is None:
exit(1)
print("\n" + "=" * 50)
print("开始执行读写示例")
print("=" * 50 + "\n")
# 示例1: 写入Y0输出点
write_coil(client, 'Y', 0, True)
# 示例2: 写入M100辅助继电器
write_coil(client, 'M', 100, True)
# 示例3: 写入D100数据寄存器
write_register(client, 100, 250)
# 示例4: 验证读取
print("\n" + "-" * 50)
print("验证读取")
print("-" * 50)
read_coil(client, 'Y', 0)
read_coil(client, 'M', 100)
read_register(client, 100)
# 示例5: 批量写入操作
print("\n" + "-" * 50)
print("批量写入")
print("-" * 50)
write_coils(client, 'M', 0, [True, False, True])
read_coils(client, 'M', 0, 3)
# 关闭连接
client.close()
print("\n[完成] 连接已关闭")
📖 代码详解
1. 连接管理
connect_plc()函数创建Modbus TCP客户端并建立连接。建议在程序结束时显式关闭连接,释放资源。
2. 线圈操作(位元件)
-
写入 :
write_coil()控制单个Y、X、M点的通断 -
批量写入 :
write_coils()可一次性控制多个点,提高通信效率 -
读取 :
read_coil()/read_coils()获取位元件的当前状态
3. 寄存器操作(字元件)
-
16位数据 :
write_register()/read_register()处理0-65535的整数 -
32位数据 :
read_32bit_int()读取占用两个连续寄存器的32位整数 -
批量操作 :
read_registers()一次性读取多个连续寄存器