Arduino UNO(从站)通过Modbus TCP向CodeSys传输YF-S201流量计数据问题排查
代码问题分析与修正
核心问题1:中断变量的非原子访问
pulse_freq是在中断服务函数中修改的volatile变量,主循环直接读取时可能因CPU中断触发导致字节拆分,出现错误读数。必须用原子操作包裹读取过程:
// 替换原循环中的pulse_freq读取逻辑 noInterrupts(); unsigned long temp_pulse = pulse_freq; pulse_freq = 0; // 重置计数需在原子操作内,避免中断修改导致数据丢失 interrupts(); double flowRate = temp_pulse / 7.5;
核心问题2:Modbus寄存器类型不匹配
andresarmento的ModbusIP库中,Hreg()操作的是16位无符号整数寄存器,你直接传入double类型的flowRate会导致精度丢失和类型转换错误。解决方案是将流量值转为整数存储(比如保留两位小数):
// 将流量值缩放为整数,适配16位寄存器 uint16_t flowRateScaled = static_cast<uint16_t>(flowRate * 100); mb.Hreg(FLOWMETER_HREG, flowRateScaled);
PLC端读取后再除以100即可还原真实流量值。
其他潜在问题
- IP网段一致性:确认PLC的IP与Arduino的
169.254.149.2处于同一链路本地网段(169.254.0.0/16),避免隐性连通性问题。 - 寄存器地址偏移:Modbus保持寄存器的地址在不同工具/PLC中存在偏移规则:
- 代码中
FLOWMETER_HREG = 100是0-based寄存器索引,对应标准Modbus地址为40101(保持寄存器起始地址为40001,索引0对应40001)。 - 若PLC支持直接输入索引,可填100;若需标准地址,需填40101。
- 代码中
FC3功能码适用性确认
FC3(读保持寄存器)完全适用于你的场景:
- 代码中
mb.addHreg()创建的是Modbus保持寄存器,正是FC3的读取对象。 - FC1(读线圈)对应离散输出寄存器,FC4(读输入寄存器)对应模拟输入寄存器,这两个功能码与你定义的寄存器类型不匹配,因此测试无效。
调试步骤建议
- 用Modbus调试工具验证Arduino端:使用Modbus Poll或类似工具,连接Arduino的IP和默认端口502,用FC3读取寄存器100(或40101),确认能获取正确的缩放后数值。
- 检查PLC配置:确保PLC中Modbus客户端的:
- 功能码设置为FC3
- 寄存器地址与Arduino端匹配(注意偏移规则)
- 数据类型设置为16位无符号整数(后续自行缩放还原)
- 排查网络问题:用ping命令确认PLC与Arduino的连通性,若丢包需检查以太网扩展板接线或供电。
修正后的完整代码:
#include <SPI.h> #include <Ethernet.h> #include <Modbus.h> #include <ModbusIP.h> // Flowmeter and Modbus settings const int FLOWMETER_SENSOR = 2; // Pin for flowmeter sensor const int FLOWMETER_HREG = 100; // Holding register for flowmeter data // ModbusIP object ModbusIP mb; // Flowmeter variables volatile unsigned long pulse_freq = 0; unsigned long lastTime; void pulseCounter() { pulse_freq++; } void setup() { // Modbus TCP and Ethernet settings byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; byte ip[] = { 169, 254, 149, 2 }; mb.config(mac, ip); // Flowmeter setup pinMode(FLOWMETER_SENSOR, INPUT); attachInterrupt(0, pulseCounter, RISING); mb.addHreg(FLOWMETER_HREG); lastTime = millis(); Serial.begin(9600); } void loop() { mb.task(); unsigned long currentTime = millis(); if (currentTime - lastTime > 1000) { // Every second lastTime = currentTime; // 原子操作读取并重置脉冲计数 noInterrupts(); unsigned long temp_pulse = pulse_freq; pulse_freq = 0; interrupts(); double flowRate = temp_pulse / 7.5; // Calculate flow rate uint16_t flowRateScaled = static_cast<uint16_t>(flowRate * 100); // 缩放为整数 mb.Hreg(FLOWMETER_HREG, flowRateScaled); // Update Modbus register Serial.print("Flow Rate: "); Serial.println(flowRate, 2); } }
内容的提问来源于stack exchange,提问作者Heroking18
相关产品推荐
相关产品推荐

