RS485 Modbus 从机通信
简介:
下文介绍了如何用 ShineBlink C1 或 C2 开发板作为设备端(Modbus从机)来和上位机 (Modbus 主机) 来通信,并在 ShineBlink 设备端实现了 0x03 功能码(读取多个保持寄存器)和 0x05 功能码(写单个线圈)的程序代码。
一、实现环境
ShineBlink C1 或 C2 开发板设备作为Modbus从机通过RS485总线和上位机通信,我们在电脑上运行知名的Modbus Poll调试软件作为上位机来模拟Modbus主机,Modbus Poll软件可以到其官网上下载。
二、接线图
三、Modbus从机设备介绍
ShineBlink C1 或 C2 开发板设备作为Modbus网络中的其中一个节点有如下特性:
**串口属性:**19200、N、8、1
**设备地址:**21(0x15)
设备支持的Modbus功能码:
-
0x05 写单个线圈
-
0x03 读取多个保持寄存器
功能介绍:
-
0x05,上位机通过向设备发送0x05功能码,对线圈地址为0x0000的线圈写入值0xFF00时,设备开始运行,
对线圈地址为0x0000的线圈写入值0x0000时,设备停止运行。
-
0x03,上位机通过向设备发送0x03功能码,读取保持寄存器起始地址为0x0000的9个保持寄存器(每个保持寄存器值为16bit无符号数据),每个寄存器对应的数据如下:
寄存器1 寄存器2 寄存器3 寄存器4 寄存器5 寄存器6 寄存器7 寄存器8 寄存器9 Pm25 Hcho Tvoc Mesh Temp1 Temp2 Wind Run Fault 三、Modbus通信实现代码实例
以下代码不仅实现了03和05功能码,并实现了将各种异常情况回复给Modbus主机。
--程序中用到的全局变量定义
Pm25Percent = 0
HchoPercent = 0
TvocPercent = 0
MeshPercent = 0.0
Temprature1 = 0.00
Temprature2 = 0.00
Wind485DisSpeed = 0
DevIsRunning = 0 --控制设备运行或停止
FaultCode = 0 --故障代码
--ModBus通信函数定义
function ModbusProcess()
local sdata = {}
--查询是否收到Modbus主机发来的消息
flag, data = LIB_Uart1Recv()
if flag == 1 then
--判断消息是不是发给本机,是本机的才理会
if data[1] == PI[2] then --PI[2], Modbus本机地址(1-247)
--判断Modbus功能码
if data[2] == 0x05 then -- 0x05 写单个线圈
--这里定义线圈地址为0x0000的线圈为开机/关机控制信号
if data[3] == 0x00 and data[4] == 0x00 then
if data[5] == 0xff and data[6] == 0x00 then --ON
DevIsRunning = 1 --置1开机全局变量
elseif data[5] == 0x00 and data[6] == 0x00 then --OFF
DevIsRunning = 0 --置0开机全局变量
else
--这里需回复非法数据03异常消息(非法数据值),读者可自行完成
end
--回复OK,把收到的数据原封不动回传
LIB_Uart1BlockSend(data)
else
--回复异常消息(非法数据地址)
sdata[1] = data[1] --本机地址
sdata[2] = data[2]+0x80 --异常的时候功能码加0x80
sdata[3] = 0x02 --异常码0x02表示设备不支持此数据地址
CRC = LIB_CrcCalculate("CRC16_MODBUS", sdata)
sdata[4] = CRC & 0x00ff --低位在前
sdata[5] = CRC >> 8 --高位在后
LIB_Uart1BlockSend(sdata)
end
elseif data[2] == 0x03 then --0x03 读多个保持寄存器
--这里定义起始地址为0x0000的这些寄存器存放传感器数据,且读取的寄存器个数必须是9个
if data[3] == 0x00 and data[4] == 0x00 and data[5] == 0x00 and data[6] == 0x09 then
sdata[1] = data[1] --本机地址
sdata[2] = data[2] --功能码
sdata[3] = 18 --数据域字节数: 9个寄存器一共18字节
sdata[4] = Pm25Percent >> 8
sdata[5] = Pm25Percent & 0x00ff
sdata[6] = HchoPercent >> 8
sdata[7] = HchoPercent & 0x00ff
sdata[8] = TvocPercent >> 8
sdata[9] = TvocPercent & 0x00ff
sdata[10] = math.floor(MeshPercent) >> 8
sdata[11] = math.floor(MeshPercent) & 0x00ff
sdata[12] = math.floor(Temprature1) >> 8
sdata[13] = math.floor(Temprature1) & 0x00ff
sdata[14] = math.floor(Temprature2) >> 8
sdata[15] = math.floor(Temprature2) & 0x00ff
sdata[16] = Wind485DisSpeed >> 8
sdata[17] = Wind485DisSpeed & 0x00ff
sdata[18] = DevIsRunning >> 8
sdata[19] = DevIsRunning & 0x00ff
sdata[20] = FaultCode >> 8
sdata[21] = FaultCode & 0x00ff
CRC = LIB_CrcCalculate("CRC16_MODBUS", sdata)
sdata[22] = CRC & 0x00ff --低位在前
sdata[23] = CRC >> 8 --高位在后
--回复传感器数据
LIB_Uart1BlockSend(sdata)
else
--回复异常消息(非法数据地址)
sdata[1] = data[1] --本机地址
sdata[2] = data[2]+0x80 --异常的时候功能码加0x80
sdata[3] = 0x02 --异常码0x02表示设备不支持此数据地址
CRC = LIB_CrcCalculate("CRC16_MODBUS", sdata)
sdata[4] = CRC & 0x00ff --低位在前
sdata[5] = CRC >> 8 --高位在后
LIB_Uart1BlockSend(sdata)
end
else
--回复异常消息(非法功能码)
sdata[1] = data[1] --本机地址
sdata[2] = data[2]+0x80 --异常的时候功能码加0x80
sdata[3] = 0x01 --异常码0x01表示设备不支持此功能码
CRC = LIB_CrcCalculate("CRC16_MODBUS", sdata)
sdata[4] = CRC & 0x00ff --低位在前
sdata[5] = CRC >> 8 --高位在后
LIB_Uart1BlockSend(sdata)
end
end
end
end
--配置Uart1作为485接口,初始默认波特率19200,并且D6作为自动收发切换引脚
LIB_Uart1Rs485Config("BAUDRATE_19200","D6")
--开始大循环
while(GC(1) == true)
do
--Modbus通信处理
ModbusProcess()
end