DIY一个以太网转RS485 Modbus网关
1 实现功能
基于 FlexLua DTU01万能采集器实现 以太网 通信和 RS485 通信之间的数据透传,以太网接入服务器的方式为 Tcp Server。
- 在 以太网 通信链路上,DTU01 和 远端服务器通信数据格式为 HEX 字节流
- 在 485 通信链路上,DTU01 和 RS485 节点通信数据格式为 HEX 字节流
下行通道举例:例如当远端的 TCP 服务器向 DTU 发送 0x08 0x09 0x0A,则 DTU 在收到这些数据后会原封不动通过 485总线 将 0x08 0x09 0x0A 转发给RS485 节点。
上行通道举例:例如当 RS485 节点向 DTU 发送 0x01 0x02 0x03,则 DTU 在收到这些数据后会原封不动通过 以太网 将 0x01 0x02 0x03 转发给远端的服务器。
建议:由于 DTU01 是 Tcp Server ,远端服务器是 Tcp Client,所以整个系统不适合双向随机透传数据。建议由远端服务器发起数据传输,进而等待RS485节点回复应答数据。
2 实现方法
(1)硬件
由于 DTU01 的硬件接口是可配置的,所以需要确认一下您手上的 DTU01 采集器是否满足要求,如果确认已满足就无需再做任何操作,否则请按《DTU01硬件使用指导书》完成下面的硬件配置:
- DTU01 电路板上的 ‘A’ 和 ‘B’ 引脚需配置成 485 接口的 ‘A’ 和 ‘B’,即 S3 选择
- DTU01 电路板正面焊接上 SB-C18 以太网通信模块
- DTU01 电路板上的 ‘+’ 和 ‘-’ 电源输出引脚配置成 12v 输出用来接12v的传感器/仪表/设备(非必须)
《DTU01硬件使用指导书》可在 {FlexLua开源网盘}->{Hardware}->{DTU01万能采集器}->{DTU01硬件使用指导书} 文件夹中找到。
(2)软件
只需将本文章末尾提供的源码拷贝覆盖至设备上的 main.lua 文件中,并根据实际情况修改源码开头部分的配置信息,即可实现大部分场景的需求。
操作方法:首先用TypeC USB数据线将设备和电脑连接,然后在电脑上会自动出现一个1.6MB大小的U盘(如果U盘大小是0MB,则需用FAT32快速格式化该盘),找到 main.lua 代码文件(如果没有请自行创建一个 main.lua文件),然后将本文的源码拷贝覆盖至 main.lua 文件中然后Ctrl+S保存即可,代码更新后在下次重新上电后其会自行编译并运行。
3 源代码
下面贴出完整的源代码,但是可能在网页上展示格式比较混乱,看源代码的话可以在 FlexLua 官网的 C004A 文档中找到源码。
----------------------配置信息开始------------------------
--Part1: 以太网参数配置:TCP Server 静态IP示例(本机IP=192.168.0.35,本机端口号=40001)
ROLE = {0x00} --0x00:TCP Server, 0x01:TCP Client, 0x02:UDP Server, 0x03:UDP Client
SRCPORT = {0x41,0x9c} --源端口号,例如40001=0x9c41
DHCP = {0x00} --0x00关闭DHCP, 0x01使能DHCP
SRCIP = {192,168,0,35} --源IP地址
GATEWAY = {192,168,0,1}--网关地址
SUBNET = {255,255,255,0} --子网掩码
DESPORT = {0,0} --目的端口号,无需配置
DESIP = {0,0,0,0} --目的IP地址,无需配置
--Part2:RS485 通信配置
Rs485BaudRate = "BAUDRATE_9600"
--串口校验设置
Rs485ParitySet = "NoneParity" --"NoneParity","EvenParity","OddParity"
--串口停止位设置
Rs485StopBitSet = "StopBit_1" --"StopBit_1","StopBit_1_5","StopBit_2"
----------------------配置信息结束------------------------
--外围设备GPIO分配
CH9121_RSTI = "D6"
CH9121_CFG = "D5" --DTU01电路板没有连接CFG引脚,所以不能用CFG引脚方式配置以太网模块,必须要用协商方式(该方式需要提前用Wch厂家提供的上位机开启)
-------------------全局变量(可变值)-------------------
RedLedTimeMsCnt = 0
GreenLedTimeMsCnt = 0
EthLostContactTimeMsCnt = 0 --以太网故障计时器
--CH9121以太网模块写命令和数据
function Ch9121Write(cmd, data)
LIB_GpioToggle("D11") --喂硬件看门狗
SendData = {0x57,0xab}
SendData[#SendData+1] = cmd
for i=1,#data do
SendData[#SendData+1] = data[i]
end
--发送数据包
LIB_Uart0Send(SendData)
LIB_DelayMs(100)--延时不能低于100ms,不然会有个别指令收不到回复
--查询Uart0是否收到0xaa应答
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 and u0_tab[1] == 0xaa then
print(string.format("Ch9121 0x%02x wr ok, Send=",cmd)..LIB_HexTabToHexStr(SendData).."\r\n")
else
print(string.format("Ch9121 0x%02x wr fail, Send=",cmd)..LIB_HexTabToHexStr(SendData).."\r\n")
end
end
--CH9121以太网模块读命令和数据
function Ch9121Read(cmd)
LIB_GpioToggle("D11") --喂硬件看门狗
SendData = {0x57,0xab}
SendData[#SendData+1] = cmd
--发送数据包
LIB_Uart0Send(SendData)
LIB_DelayMs(100)--延时不能低于100ms,不然会有个别指令收不到回复
--查询Uart0是否收到应答
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 then
print(string.format("Ch9121 0x%02x rd ok, Recv=",cmd)..LIB_HexTabToHexStr(u0_tab).."\r\n")
else
print(string.format("Ch9121 0x%02x rd fail, Send=",cmd)..LIB_HexTabToHexStr(SendData).."\r\n")
end
end
--CH9121初始化
function Ch9121Init()
LIB_GpioOutputConfig(CH9121_RSTI,"STANDARD")
LIB_GpioOutputConfig(CH9121_CFG,"STANDARD")
--CH9121复位
LIB_GpioWrite(CH9121_RSTI,0)
LIB_DelayMs(100)
LIB_GpioWrite(CH9121_RSTI,1)
LIB_DelayMs(200)--这里需要延时
LIB_GpioToggle("D11") --喂硬件看门狗
--CH9121进入配置模式(CFG引脚方式)
LIB_GpioWrite(CH9121_CFG,0)--DTU01没接CFG,此种配置方式无效!
LIB_DelayMs(100)
--CH9121进入配置模式(串口协商方式)
LIB_DelayMs(500)
SendData = {0x55,0xaa,0x5a}
LIB_Uart0Send(SendData)--发送 55 aa 5a
cnt = 0
while cnt < 500 do --500ms内等待ch9121回复a5
LIB_DelayMs(1)
LIB_GpioToggle("D11") --喂硬件看门狗
cnt = cnt + 1
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 and u0_tab[1] == 0xa5 then
break
end
end
if cnt >= 500 then
print("Ch9121 init fail, system reset!")
LIB_GpioWrite("D3",0) --蓝灯亮3秒后重启
LIB_GpioToggle("D11") --喂硬件看门狗
LIB_DelayMs(3000)
LIB_SystemReset()
end
SendData = {0xa5}
LIB_Uart0Send(SendData)--发送 a5
cnt = 0
while cnt < 500 do --500ms内等待ch9121回复a5
LIB_DelayMs(1)
LIB_GpioToggle("D11") --喂硬件看门狗
cnt = cnt + 1
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 and u0_tab[1] == 0xa5 then
break
end
end
if cnt >= 500 then
print("Ch9121 init fail, system reset!")
LIB_GpioWrite("D3",0) --蓝灯亮3秒后重启
LIB_GpioToggle("D11") --喂硬件看门狗
LIB_DelayMs(3000)
LIB_SystemReset()
end
LIB_DelayMs(20)
--读取芯片MAC地址
mac_ok,mac = Ch9121Read(0x81)
if mac_ok == 1 and #mac == 6 then
print("Mac:"..LIB_HexTabToHexStr(mac))
end
Ch9121Write(0x10,ROLE) --0x00:TCP Server, 0x01:TCP Client, 0x02:UDP Server, 0x03:UDP Client
--配置网络信息(目的IP和端口号配置),仅当Client时有效
if ROLE[1] == 0x01 or ROLE[1] == 0x03then
Ch9121Write(0x15,DESIP)--目的IP
Ch9121Write(0x16,DESPORT)--目的端口号
end
--配置网络信息(静态IP配置)
if DHCP[1] ~= 0x01 then
Ch9121Write(0x11,SRCIP)--源IP
Ch9121Write(0x12,SUBNET)--子网掩码
Ch9121Write(0x13,GATEWAY) --网关
end
Ch9121Write(0x14,SRCPORT)--本地源端口PORT
Ch9121Write(0x23,{0x01,0x00,0x00,0x00})--RX接收打包超时时间1*5ms
Ch9121Write(0x24,{0x00})--网线断开不断网
Ch9121Write(0x25,{0x00,0x02,0x00,0x00})--RX接收打包数据个数512字节
Ch9121Write(0x26,{0x01})--网络连接时清空串口数据
Ch9121Write(0x33,DHCP)--DHCP or Static
Ch9121Write(0x0d,{})--更新配置参数至EEPROM
Ch9121Write(0x0e,{})--执行配置,复位CH9121
LIB_GpioToggle("D11") --喂硬件看门狗
LIB_DelayMs(200)
--CH9121退出配置模式
LIB_GpioWrite(CH9121_CFG,1)--DTU01没接CFG,此种配置方式无效!
--CH9121退出配置模式(串口协商方式)
Ch9121Write(0x5e,{})
LIB_DelayMs(50)
end
--该函数用来判断以太网模块是不是很久没收到消息了,有可能是以太网模块故障了
--如果是故障就重启执行以太网模块的初始化流程
function EthKeepAlive()
--超过30分钟没有以太网通信行为,认为以太网模块出问题
if EthLostContactTimeMsCnt >= 1800000 then
LIB_SystemReset() --重启整个系统
end
end
--定义10毫秒定时器的回调函数,函数名字必须是LIB_10msTimerCallback
function LIB_10msTimerCallback()
EthLostContactTimeMsCnt = EthLostContactTimeMsCnt + 10
LIB_GpioToggle("D11") --喂硬件看门狗
--绿色LED灯闪烁处理程序
if GreenLedTimeMsCnt >= 10 then
GreenLedTimeMsCnt = GreenLedTimeMsCnt - 10
LIB_GpioWrite("D1",0) --绿灯亮
else
LIB_GpioWrite("D1",1) --绿灯灭
end
--红色LED灯闪烁处理程序
if RedLedTimeMsCnt > 10 then
RedLedTimeMsCnt = RedLedTimeMsCnt - 10
LIB_GpioWrite("D0",0) --红灯亮
else
LIB_GpioWrite("D0",1) --红灯灭
end
end
--硬件初始化函数
function Init()
--使能系统日志,不用的时候关闭
--LIB_SystemLogEnable()
--配置Uart0波特率为9600,和CH9121以太网模块通信
LIB_Uart0Config("BAUDRATE_9600")
--配置D0,D1,D3为普通GPIO输出,控制LED_R,LED_G,LED_B
LIB_GpioOutputConfig("D0","STANDARD")
LIB_GpioOutputConfig("D1","STANDARD")
LIB_GpioOutputConfig("D3","STANDARD")
LIB_GpioWrite("D0",1) --红灯灭
LIB_GpioWrite("D1",1) --绿灯灭
LIB_GpioWrite("D3",1) --蓝灯灭
--配置D11为普通输出,控制看门狗
LIB_GpioOutputConfig("D11","STANDARD")
--初始化CH9121以太网模块
Ch9121Init()
--配置Uart1串口工作,D8自动控制收发电平
LIB_Uart1Rs485Config(Rs485BaudRate,"D8",Rs485ParitySet,Rs485StopBitSet)
--使能系统10毫秒定时器开始工作
LIB_10msTimerConfig("ENABLE")
end
--硬件初始化
Init()
--开始大循环
while(GC(1) == true)
do
--查询是否收到以太网数据,例如:0x01 0x02 0x03
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 then
--绿灯闪0.1秒
GreenLedTimeMsCnt = 100
--清零Eth故障判断计时器
EthLostContactTimeMsCnt = 0
--将收到的数据原封不动转发给485端口
LIB_Uart1BlockSend(u0_tab)
end
--查询是否收到485数据,例如:0x01 0x02 0x03
flag,tab = LIB_Uart1Recv()
if flag == 1 then
--红灯闪0.1秒
RedLedTimeMsCnt = 100
--将收到的数据原封不动转发给以太网
LIB_Uart0Send(tab)
end
--EthKeepAlive()用来进行判断以太网是否正常,也可考虑去掉该函数,原因如下:
--因为如果30分钟收不到以太网数据,该函数会让以太网模块重启,导致TCP断开连接,Client端需要再次重新执行TCP连接
--但如果能够保证30分钟内必然会收到Client端发来的消息,则可保留此函数,因为这样可保证以太网长期工作的稳定性
--综上所述,为了保证长期工作的稳定性,Client端需要在检测到本Server端断连后,再次重新执行TCP连接
EthKeepAlive()
end