485轮询
■ 软件需求
运行WeStduio组态软件的智能显示屏做主机,通过两个485串口(com1, com2)各自连接64台从机,总共128台从机。主机轮询从机的状态(报警/正常) 并显示
串口通信协议如下:
主机巡查命令: 地址码 + 03 00 64 00 0D + 校验码(CRC-16 ModBus)
从机回应
02 03 1A 00 08 03 00 01 0A 03 00 01 0A 03 00 00 FD 03 00 00 F5 00 3C 00 3D 00
3E 00 3F 08 79
地址码 + 03 1A 00 08 +
第1路使能 + 第1路状态 + 第1路温度 +
第2路使能 + 第2路状态 + 第2路温度 +
第3路使能 + 第3路状态 + 第3路温度 +
第4路使能 + 第4路状态 + 第4路温度 +
00 + 第1路报警值 +
00 + 第2路报警值 +
00 + 第3路报警值 +
00 + 第4路报警值 + 校验码
■ 实现步骤
打开WeStudio软件,新建一工程
1. 首先设置串口控件com1, com2的协议为UserDefine, 波特率为9600,阈值为31 (从机回应的数据长度为31)
2. 删除主界面main, 添加4个界面(page1, page2, page3, page4)和一个底部导航界面(bottom)
3. 打开初始化脚本[工具菜单->初始化脚本],添加公共方法:
// 定义巡检地址变量,地址从1开始到64,在此初始化为0,调用checkGroupNextAddr会自动加1
var groupAddr = [0, 0];
/**计算 CRC-16校验值
* @param data: byte数组
* @param len: CRC检验计算的长度
*/
function getCRC(data, len) {
var j, k;
var crc = 0xffff;
k = len - 1;
while(len--) {
crc = crc ^ data[k - len];
for(j=0;j<8;j++) {
if(crc & 0x01 == 1) {
crc = (crc >> 1) ^ 0xA001;
}
else crc = crc >> 1;
}
}
var CRCH = (crc >> 8) & 0xff;
var CRCL = crc%0x100;
return [CRCL, CRCH];
}
/**获取主机巡检命令
* @param addr: 从机的地址码, 从地址1开始
*/
function getMasterCheckCmd(addr) {
// 地址码 + 03 00 64 00 0D + 校验码(CRC-16)
var cmd = [addr, 0x03, 0x00, 0x64, 0x00, 0x0D];
// 计算CRC检验值 CRCL, CRCH
var crc = getCRC(cmd, cmd.length);
var len = cmd.length;
cmd[len] = crc[0];
cmd[len+1] = crc[1];
return cmd;
};
/**解析从机状态
* @param data: 从串口接收到的从机返回数据(byte数组)
* @return: -1数据长度错误,-2数据格式错误,-3CRC校验错误,00正常,01报警,06断路,0A短路
*/
function parseSlaveState(data) {
// 02 03 1A 00 08 03 00 01 0A 03 00 01 0A 03 00 00
// FD 03 00 00 F5 00 3C 00 3D 00 3E 00 3F 08 79
if (data.length != 31) {
util.console.log('数据长度错误');
return -2;
}
// 地址码 + 03 1A 00 08
var magic = [0x03, 0x1A, 0x00, 0x08];
for (var i=0; i<magic.length; i++) {
if (data[i+1] != magic[i]) {
util.console.log('数据格式错误');
return -3;
}
}
// 计算CRC
var len = data.length;
var crc = getCRC(data, len-2);
if (crc[0] != data[len-2] || crc[1] != data[len-1]) {
util.console.log('CRC校验失败');
return -4;
}
var addr = data[0];
// 第1路使能的起始索引
var index = magic.length + 1;
var s_normal = 0;
var s_alarm = 0;
var s_open = 0;
var s_short = 0;
for (var ch=1; ch<=4; ch++) { // 第1路使能控制(1byte) + 第1路状态(1byte) + 第1路温度(2bytes)
//状态:00正常,01报警,06断路,0A短路
var state = data[index+1];
switch(state) {
case 0x00: // 00正常
s_normal++;
break;
case 0x01: // 01报警
s_alarm++;
break;
case 0x06: // 06断路
s_open++;
break;
case 0x0A: // 0A短路
s_short++;
break;
}
index += 4;
}
if (s_alarm > 0) { // 01报警
return 0x01;
} else if (s_short > 0) { // 0A短路
return 0x0A;
} else if (s_open > 0) { // 06断路
return 0x06;
} else if (s_normal == 4){ // 00正常
return 0x00;
}
}
/**巡检下一个地址
* @param index: 分组索引, 从0开始
*/
function checkGroupNextAddr(index)
{
var group = index + 1;
// 停止定时器
eval('ui.bottom.timer_group'+group+'.stop();');
// 巡检下一个地址
if (++groupAddr[index] > 64) {
groupAddr[index] = 1;
}
var data = getMasterCheckCmd(groupAddr[index]);
util.console.log('master check: ' + group + '_' + groupAddr[index]);
// 通过串口发送
eval('device.io.com'+group+'.write(data);');
// 启动定时器检查从机是否超时
eval('ui.bottom.timer_group'+group+'.start();');
}
4. 在导航页面拖入两个定时器控件(分别命名为timer_group1, timer_group2),设置定时器的间隔属性为100mS(从主机发送巡检命令到收到从机回应,平均时长为90mS)
在页面的onLoad方法中启动巡检
ui.bottom.onLoad = function() {
// 巡检命令
checkGroupNextAddr(0);
checkGroupNextAddr(1);
};
5. 在定时器onTimeout方法中检查从机回应是否超时,以timer_group1为例
ui.bottom.timer_group1.onTimeout = function(counter) {
// 从机返回超时
var index = 0;
updateState(index, groupAddr[index], -1);
// 巡检下一个地址
checkGroupNextAddr(index);
};
6. 在串口com1和com2的onReceive方法中处理接收的从机数据,以com1为例
device.io.com1.onReceive = function(count) {
if (device.io.com1.readableBytes() == 31) {
var index = 0;
// 1.立即巡检下一个地址
checkGroupNextAddr(index);
// 2.读取缓冲区数据,刷新界面状态
var data = device.io.com1.read(31);
var addr = data[0]; // 从机地址码
var state = parseSlaveState(data);
updateState(index, addr, state);
}
};
完整工程,请在WeStudio中打开例程“485轮询”