找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1201|回复: 15
收起左侧

Modbus多机通信的疑问:regGroup[5]; //Modbus 寄存器组,地址为 0x00~0x04,包....

[复制链接]
ID:673647 发表于 2023-3-5 17:59 | 显示全部楼层 |阅读模式
1、regGroup[5]; //Modbus 寄存器组,地址为 0x00~0x04,这5个寄存器包含了什么?
2、功能码最高置1位,怎么不一样:A:buf[1] = 0x83; //功能码最高位置 1
                                                    B:buf[1] = 0x86; //功能码最高位置 1
                                                    C:buf[1] |= 0x80; //功能码最高位置 13、buf[0]~buf[5]是指什么?
单片机源代码如下:
#include <reg52.h>
sbit BUZZ = P1^6;
bit flagBuzzOn = 0; //蜂鸣器启动标志
unsigned char T0RH = 0; //T0 重载值的高字节
unsigned char T0RL = 0; //T0 重载值的低字节
unsigned char regGroup[5]; //Modbus 寄存器组,地址为 0x00~0x04
void ConfigTimer0(unsigned int ms);
extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);
extern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void main()
{
EA = 1; //开总中断
ConfigTimer0(1); //配置 T0 定时 1ms
ConfigUART(9600); //配置波特率为 9600
InitLcd1602(); //初始化液晶

while (1)
{
UartDriver(); //调用串口驱动
}
}
/* 串口动作函数,根据接收到的命令帧执行响应的动作
buf-接收到的命令帧指针,len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{
unsigned char i;
unsigned char cnt;
unsigned char str[4];
unsigned int crc;
unsigned char crch, crcl;

if (buf[0] != 0x01) //本例中的本机地址设定为 0x01,
{ //如数据帧中的地址字节与本机地址不符,
return; //则直接退出,即丢弃本帧数据不做任何处理
}
//地址相符时,再对本帧数据进行校验
crc = GetCRC16(buf, len-2); //计算 CRC 校验值
crch = crc >> 8;
crcl = crc & 0xFF;
if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
{
return; //如 CRC 校验不符时直接退出
}
//地址和校验字均相符后,解析功能码,执行相关操作
switch (buf[1])
{
case 0x03: //读取一个或连续的寄存器
if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持 0x0000~0x0005
{
if (buf[3] <= 0x04)
{
i = buf[3]; //提取寄存器地址
cnt = buf[5]; //提取待读取的寄存器数量
buf[2] = cnt*2; //读取数据的字节数,为寄存器数*2
len = 3; //帧前部已有地址、功能码、字节数共 3 个字节
while (cnt--)
{
buf[len++] = 0x00; //寄存器高字节补 0
buf[len++] = regGroup[i++]; //寄存器低字节
}
}
else //地址 0x05 为蜂鸣器状态
{
buf[2] = 2; //读取数据的字节数
buf[3] = 0x00;
buf[4] = flagBuzzOn;
len = 5;
}
break;
}
else //寄存器地址不被支持时,返回错误码
{
buf[1] = 0x83; //功能码最高位置 1
buf[2] = 0x02; //设置异常码为 02-无效地址
len = 3;
break;
}

case 0x06: //写入单个寄存器
if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持 0x0000~0x0005
{
if (buf[3] <= 0x04)
{
i = buf[3]; //提取寄存器地址
regGroup[ i] = buf[5]; //保存寄存器数据
cnt = regGroup[ i] >> 4; //显示到液晶上
if (cnt >= 0xA)
str[0] = cnt - 0xA + 'A';
else
str[0] = cnt + '0';
cnt = regGroup[ i] & 0x0F;
if (cnt >= 0xA)
str[1] = cnt - 0xA + 'A';
else
str[1] = cnt + '0';
str[2] = '\0';
LcdShowStr(i*3, 0, str);
}
else //地址 0x05 为蜂鸣器状态
{
flagBuzzOn = (bit)buf[5]; //寄存器值转为蜂鸣器的开关
}
len -= 2; //长度-2 以重新计算 CRC 并返回原帧
break;
}
else //寄存器地址不被支持时,返回错误码
{
buf[1] = 0x86; //功能码最高位置 1
buf[2] = 0x02; //设置异常码为 02-无效地址
len = 3;
break;
}

default: //其它不支持的功能码
buf[1] |= 0x80; //功能码最高位置 1
buf[2] = 0x01; //设置异常码为 01-无效功能
len = 3;
break;
}
crc = GetCRC16(buf, len); //计算返回帧的 CRC 校验值
buf[len++] = crc >> 8; //CRC 高字节
buf[len++] = crc & 0xFF; //CRC 低字节
UartWrite(buf, len); //发送返回帧
}
/* 配置并启动 T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量

tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 33; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = T0RH; //加载 T0 重载值
TL0 = T0RL;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
}
/* T0 中断服务函数,执行串口接收监控和蜂鸣器驱动 */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
if (flagBuzzOn) //执行蜂鸣器鸣叫或关闭
BUZZ = ~BUZZ;
else
BUZZ = 1;
UartRxMonitor(1); //串口接收监控
}




回复

使用道具 举报

ID:161164 发表于 2023-3-5 23:06 | 显示全部楼层
代码不全
串口中断函数呢?
UartDriver()函数呢?
回复

使用道具 举报

ID:94031 发表于 2023-3-6 08:59 | 显示全部楼层
大概看了一下,
1 Modbus 寄存器0x00放的是响应从机地址,寄存器0x01放的是功能码,寄存器0x02~0x04放的是参数。
2 功能不同,功能码肯定不同。   
回复

使用道具 举报

ID:673647 发表于 2023-3-6 09:19 | 显示全部楼层
lkc8210 发表于 2023-3-5 23:06
代码不全
串口中断函数呢?
UartDriver()函数呢?

谢谢回复!如下:
/****************************RS485.c 文件程序源代码*****************************/
#include <reg52.h>
#include <intrins.h>
sbit RS485_DIR = P1^7; //RS485 方向选择引脚
bit flagFrame = 0; //帧接收完成标志,即接收到一帧新数据
bit flagTxd = 0; //单字节发送完成标志,用来替代 TXD 中断标志位
unsigned char cntRxd = 0; //接收字节计数器
unsigned char pdata bufRxd[64]; //接收字节缓冲区
extern void UartAction(unsigned char *buf, unsigned char len);
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
RS485_DIR = 0; //RS485 设置为接收方向
SCON = 0x50; //配置串口为模式 1
TMOD &= 0x0F; //清零 T1 的控制位
TMOD |= 0x20; //配置 T1 为模式 2
TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
TL1 = TH1; //初值等于重载值
ET1 = 0; //禁止 T1 中断
ES = 1; //使能串口中断
TR1 = 1; //启动 T1
}
/* 软件延时函数,延时时间(t*10)us */
void DelayX10us(unsigned char t)
{
do {
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
} while (--t);
}
/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{
RS485_DIR = 1; //RS485 设置为发送
while (len--) //循环发送所有字节
{
flagTxd = 0; //清零发送标志
SBUF = *buf++; //发送一个字节数据
while (!flagTxd); //等待该字节发送完成
}
DelayX10us(5); //等待最后的停止位完成,延时时间由波特率决定
RS485_DIR = 0; //RS485 设置为接收
}
/* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
unsigned char UartRead(unsigned char *buf, unsigned char len)
{
unsigned char i;

if (len > cntRxd) //指定读取长度大于实际接收到的数据长度时,
{ //读取长度设置为实际接收到的数据长度
len = cntRxd;
}
for (i=0; i<len; i++) //拷贝接收到的数据到接收指针上
{
*buf++ = bufRxd;
}
cntRxd = 0; //接收计数器清零

return len; //返回实际读取长度
}
/* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
void UartRxMonitor(unsigned char ms)
{
static unsigned char cntbkp = 0;
static unsigned char idletmr = 0;
if (cntRxd > 0) //接收计数器大于零时,监控总线空闲时间
{
if (cntbkp != cntRxd) //接收计数器改变,即刚接收到数据时,清零空闲计时
{
cntbkp = cntRxd;
idletmr = 0;
}
else //接收计数器未改变,即总线空闲时,累积空闲时间
{
if (idletmr < 30) //空闲计时小于 30ms 时,持续累加
{
idletmr += ms;
if (idletmr >= 30) //空闲时间达到 30ms 时,即判定为一帧接收完毕
{
flagFrame = 1; //设置帧接收完成标志
}
}
}
}
else
{
cntbkp = 0;
}
}
/* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
void UartDriver()
{
unsigned char len;
unsigned char pdata buf[40];
if (flagFrame) //有命令到达时,读取处理该命令
{
flagFrame = 0;
len = UartRead(buf, sizeof(buf)-2); //将接收到的命令读取到缓冲区中
UartAction(buf, len); //传递数据帧,调用动作执行函数
}
}
/* 串口中断服务函数 */
void InterruptUART() interrupt 4
{
if (RI) //接收到新字节
{
RI = 0; //清零接收中断标志位
if (cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时,
{ //保存接收字节,并递增计数器
bufRxd[cntRxd++] = SBUF;
}
}
if (TI) //字节发送完毕
{
TI = 0; //清零发送中断标志位
flagTxd = 1; //设置字节发送完成标志
}
}
回复

使用道具 举报

ID:673647 发表于 2023-3-6 09:19 | 显示全部楼层
xuyaqi 发表于 2023-3-6 08:59
大概看了一下,
1 Modbus 寄存器0x00放的是响应从机地址,寄存器0x01放的是功能码,寄存器0x02~0x04放的是 ...

谢谢解答,能再说详细一点吗?
回复

使用道具 举报

ID:1034262 发表于 2023-3-6 10:05 | 显示全部楼层
寄存器,是人为规定的16位地址,给用户读写的,可以放任意的数据。
功能码最高置1位,一般是用于错误命令的返回。
回复

使用道具 举报

ID:673647 发表于 2023-3-6 10:10 | 显示全部楼层
coody_sz 发表于 2023-3-6 10:05
寄存器,是人为规定的16位地址,给用户读写的,可以放任意的数据。
功能码最高置1位,一般是用于错误命令 ...

谢谢,但搞不清楚程序里的buf[0]~buf[5]具体指的是什么?
回复

使用道具 举报

ID:161164 发表于 2023-3-6 11:17 | 显示全部楼层
cwb2038 发表于 2023-3-6 10:10
谢谢,但搞不清楚程序里的buf[0]~buf[5]具体指的是什么?

buf[0]~buf[5]指的是串口收到的Modbus命令
功能03:读取一个或连续的寄存器
从机地址(1字节)+功能(1字节)+数据首地址(2字节)+数据长度(2字节)+CRC(2字节)
即:Buf[0]+Buf[1]+Buf[2,3]+Buf[4,5]+Buf[6,7]

功能06:写入单个寄存器
从机地址(1字节)+功能(1字节)+数据首地址(2字节)+写入数据(2字节)+CRC(2字节)
即:Buf[0]+Buf[1]+Buf[2,3]+Buf[4,5]+Buf[6,7]
回复

使用道具 举报

ID:94031 发表于 2023-3-6 11:21 | 显示全部楼层
cwb2038 发表于 2023-3-6 09:19
谢谢解答,能再说详细一点吗?

你要提具体问题,别人时间有限。
回复

使用道具 举报

ID:673647 发表于 2023-3-6 12:30 | 显示全部楼层
lkc8210 发表于 2023-3-6 11:17
buf[0]~buf[5]指的是串口收到的Modbus命令
功能03:读取一个或连续的寄存器
从机地址(1字节)+功能(1字节 ...

谢谢!
回复

使用道具 举报

ID:673647 发表于 2023-3-6 12:42 | 显示全部楼层
xuyaqi 发表于 2023-3-6 11:21
你要提具体问题,别人时间有限。

谢谢提醒,其实就是对如下这一段不好理解:对buf[0]~buf[5]取值不理解!
void UartAction(unsigned char *buf, unsigned char len)
{
unsigned char i;
unsigned char cnt;
unsigned char str[4];
unsigned int crc;
unsigned char crch, crcl;

if (buf[0] != 0x01) //本例中的本机地址设定为 0x01,
{ //如数据帧中的地址字节与本机地址不符,
return; //则直接退出,即丢弃本帧数据不做任何处理
}
//地址相符时,再对本帧数据进行校验
crc = GetCRC16(buf, len-2); //计算 CRC 校验值
crch = crc >> 8;
crcl = crc & 0xFF;
if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
{
return; //如 CRC 校验不符时直接退出
}
//地址和校验字均相符后,解析功能码,执行相关操作
switch (buf[1])
{
case 0x03: //读取一个或连续的寄存器
if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持 0x0000~0x0005
{
if (buf[3] <= 0x04)
{
i = buf[3]; //提取寄存器地址
cnt = buf[5]; //提取待读取的寄存器数量
buf[2] = cnt*2; //读取数据的字节数,为寄存器数*2
len = 3; //帧前部已有地址、功能码、字节数共 3 个字节
while (cnt--)
{
buf[len++] = 0x00; //寄存器高字节补 0
buf[len++] = regGroup[i++]; //寄存器低字节
}
}
else //地址 0x05 为蜂鸣器状态
{
buf[2] = 2; //读取数据的字节数
buf[3] = 0x00;
buf[4] = flagBuzzOn;
len = 5;
}
break;
}
else //寄存器地址不被支持时,返回错误码
{
buf[1] = 0x83; //功能码最高位置 1
buf[2] = 0x02; //设置异常码为 02-无效地址
len = 3;
break;
}
回复

使用道具 举报

ID:123289 发表于 2023-3-6 14:09 | 显示全部楼层
关键点:先读通协议。
回复

使用道具 举报

ID:673647 发表于 2023-3-9 15:23 | 显示全部楼层
yzwzfyz 发表于 2023-3-6 14:09
关键点:先读通协议。

谢谢提醒,正在找协议看!
回复

使用道具 举报

ID:1038987 发表于 2023-4-9 21:05 | 显示全部楼层
cwb2038 发表于 2023-3-6 10:10
谢谢,但搞不清楚程序里的buf[0]~buf[5]具体指的是什么?

看起的名字应该是缓冲区。要是程序不是你自己写的就自己写写吧。那样会知道更多东西
回复

使用道具 举报

ID:1038987 发表于 2023-4-9 21:08 | 显示全部楼层
cwb2038 发表于 2023-3-6 10:10
谢谢,但搞不清楚程序里的buf[0]~buf[5]具体指的是什么?

刚才看了一眼,就是MODBUS判断,这个判断不一定好。指的就是传过来个数据的第几个数。看看他们是什么然后进行不到的判断。多了解下modbus协议吧
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表