跳到主要内容

以太网 CH9121 TCP Server 通信

一、本例程实现功能

  1. Core 初始化 CH9121 以太网模块为 TCP Server 角色, 静态 IP 地址为:192.168.0.35
  2. 和 TCP Client 建立通信连接
  3. 当收到 TCP Client 发来的 Json 字符串 {"Dev":"TH"}后,开始读取 RS485 Modbus 温湿度传感器的数值
  4. 并将温湿度传感器值以 Json 字符串形式 {"Dev":"TH","Data":{"Humi":79.7,"Temp":26.8},"Err":null}返回给 TCP Client

二、 CH9121模块 (SB-C18)

product0

product1

三、接线图

layout

四、材料清单

名称推荐购买链接(或者您可以自己制作)模块/芯片硬件资料下载
CH9121 以太网模块SB-C18模块,如需获取该以太网模块请联系我们。
RS485 模块SB-C15模块,如需获取该以太网模块请联系我们。
RS485温湿度传感器RS485温湿度传感器,在某宝有很多,请自行购买
路由器
RJ45网线
电脑运行网络调试助手软件(作为 TCP Client 角色)

五、完整代码

----------------------配置信息------------------------
--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地址,DHCP=0x00时有效
GATEWAY = {192,168,0,1}--网关地址,DHCP=0x00时有效
SUBNET = {255,255,255,0} --子网掩码,DHCP=0x00时有效

DESPORT = {0,0} --目的端口号,例如1000=0x03e8,注意:仅当设置为TCP/UDP Client时才有效
DESIP = {0,0,0,0} --目的IP地址,注意:仅当设置为TCP/UDP Client时才有效

--Part2: 系统配置
SysSensorHeatTime = 5 --传感器上电后的预热时间,单位:秒,请查阅相应厂家的传感器说明书

--外围设备GPIO分配
CH9121_RSTI = "D6"
CH9121_CFG = "D5" --DTU01电路板没有连接CFG引脚,所以不能用CFG引脚方式配置以太网模块,必须要用协商方式(该方式需要提前用Wch厂家提供的上位机开启)

-------------------全局变量(可变值)-------------------
RedLedTimeMsCnt = 0
GreenLedTimeMsCnt = 0
DelayTimeMsCnt = 0
EthLostContactTimeMsCnt = 0 --以太网故障计时器

--示例:读取某厂家的一款RS485温湿度传感器
--获取到的Json数据形如:{"Humi":79.7,"Temp":26.8}
function ModbusToJson()
--配置RS485 Modbus转Json功能,并设置将要生成的Json字符串最大长度为150字节
LIB_ModbusToJsonConfig(150)--必须小于255
--湿度值寄存器地址0x0000,类型为16bit带符号AB型,小数点个数1个,Json Key名称"Humi"
LIB_ModbusToJsonAdd(0x0000,"S_AB",1,"Humi")
--温度值寄存器地址0x0001,类型为16bit带符号AB型,小数点个数1个,Json Key名称"Temp"
LIB_ModbusToJsonAdd(0x0001,"S_AB",1,"Temp")

--参数设置(4800,无校验,1位停止位,功能码"03",从机地址0x01,应答最长等待1000ms,包与包最短间隔100ms)
--注意:应答最长等待时间取决于通信数据量以及传感器,请查阅传感器说明书,通信失败时请尝试增加该值
--注意:包与包最短间隔时间取决于传感器,请查阅传感器说明书,通信失败时请尝试增加该值
LIB_ModbusToJsonSend("BAUDRATE_4800","NoneParity","StopBit_1","03",0x01,1000,100)
end

--等待RS485通信结束,并将合成好的Json数据通过以太网ETH发送给服务器
function WaitAndEthSend(dev)
--等待RS485通信结束
while true do
--读取传感器结果和错误信息(如果没有错误Err=null)
Finish,Data,Err = LIB_ModbusToJsonFinishCheck()
if Finish == 1 then
--如果获取到了传感器的Json数据
if Data ~= "null" then
RedLedTimeMsCnt = 100 --红色LED闪烁100毫秒(0.1秒)
end
--MbToJsonStr形如:{"Dev":"TH","Data":{"Humi":79.7,"Temp":26.8},"Err":null}
MbToJsonStr = string.format("{\"Dev\":\"%s\",\"Data\":%s,\"Err\":%s}", dev, Data, Err)
--将MbToJsonStr通过Uart0串口透传发送给以太网模块
LIB_Uart0Send(LIB_StrToTab(MbToJsonStr))
break --跳出while循环
end
end
end

--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 EthProcess()
--查询是否收到以太网数据,例如:{"Dev":"TH"}
u0_flag,u0_tab = LIB_Uart0Recv()
if u0_flag == 1 then
--绿灯闪0.1秒
GreenLedTimeMsCnt = 100
--清零Eth故障判断计时器
EthLostContactTimeMsCnt = 0
--判断数据包的首和尾分别是"{"和"}"
if u0_tab[1] == 0x7B and u0_tab[#u0_tab] == 0x7D then
JsStr = LIB_TabToStr(u0_tab)
--Json解析"Dev"的值
dev = LIB_JsonParse(JsStr, "$.Dev", "String")
--如果"Dev"="TH",执行读取RS485温湿度传感器的操作,形成Json应答字符串通过以太网发送给查询者
if dev == "TH" then
ModbusToJson()
--回复消息形如:{"Dev":"TH","Data":{"Humi":79.7,"Temp":26.8},"Err":null}
WaitAndEthSend("TH")
end
else
print("Eth recv data format error!")
end
end
end

--该函数用来判断以太网模块是不是很久没收到消息了,有可能是以太网模块故障了
--如果是故障就重启执行以太网模块的初始化流程
function EthKeepAlive()
--超过30分钟没有以太网通信行为,认为以太网模块出问题
if EthLostContactTimeMsCnt >= 1800000 then
LIB_SystemReset() --重启整个系统
end
end

--延时N秒函数
function DelayS(N)
DelayTimeMsCnt = 0
target_ms = N*1000
while DelayTimeMsCnt <= target_ms do
GC(1)
end
end

--定义10毫秒定时器的回调函数,函数名字必须是LIB_10msTimerCallback
function LIB_10msTimerCallback()
DelayTimeMsCnt = DelayTimeMsCnt + 10
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作为485接口,初始默认波特率4800,并且D8作为自动收发切换引脚
LIB_Uart1Rs485Config("BAUDRATE_4800","D8")
--使能系统10毫秒定时器开始工作
LIB_10msTimerConfig("ENABLE")
end

--硬件初始化
Init()
--传感器预热
DelayS(SysSensorHeatTime)
--开始大循环
while(GC(1) == true)
do
--以太网收发处理,Modbus读取
EthProcess()
--EthKeepAlive()用来进行判断以太网是否正常,也可考虑去掉该函数,原因如下:
--因为如果30分钟收不到以太网数据,该函数会让以太网模块重启,导致TCP断开连接,Client端需要再次重新执行TCP连接
--但如果能够保证30分钟内必然会收到Client端发来的消息,则可保留此函数,因为这样可保证以太网长期工作的稳定性
--综上所述,为了保证长期工作的稳定性,Client端需要在检测到本Server端断连后,再次重新执行TCP连接
EthKeepAlive()
end
如果感兴趣,上面代码中出现的LIB开头的库函数可以在 API文档 中通过Ctrl+F查询。