家庭能源网关开发历程(四)JSY-MK-163利用MODBUS协议读取和解析电能数据

         大家好,我是菜菜,在上一期我们主要介绍了关于JSY-MK-163串口中断发送和接收。通过上期我们实现的中断发送和接收,就利用到了本期读取电能数据啦,本期我们就来实现项目核心JSY-MK-163单相计量模块怎么利用MODBUS协议去读电能数据,并解析出来。首先,我们先看看怎么是MODBUS协议。

MODBUS定义

        MODBUS 协议在一根通讯线上采用主从应答方式的通讯连接方式。首先,主计算机的信号 寻址到一台唯一地址的终端设备(从机),然后,终端设备发出的应答信号以相反的方向传输 给主机,即:在一根单独的通讯线上信号沿着相反的两个方向传输所有的通讯数据流(半双工 的工作模式)

MODBUS报文格式

设备地址/从站地址: 1个字节 指定目标设备地址(从站地址)

功能码:1个字节 功能码在modbus协议用于表示信息帧的功能,例如读取线圈状态、读取寄存器等。

数据: N个字节 后面数据根据不同功能码不同。

CRC校验:循环冗余校验码,用于检测报文完整性和数据错误

MODBUS常用功能码

MODBUS读取数据

MODBUS校验

/******************************************************************
功能: CRC16校验
输入:
输出:
******************************************************************/
uint16_t  Modbus_CRC16(uint8_t *buf, uint8_t iBytesCount)
{
  	uint8_t wHi = 0;
	uint8_t wLo = 0;
	uint16_t wCRC = 0xFFFF;
	uint8_t i, j;
	uint8_t wCheck = 0;
	for (i = 0; i < iBytesCount; i++)
	{
		wCRC ^= buf[i];
		for (j = 0; j < 8; j++)
		{
			wCheck = wCRC & 1;
			wCRC = wCRC >> 1;
			wCRC = wCRC & 0x7fff;
			if (wCheck == 1)
				wCRC = wCRC ^ 0xa001;
			wCRC = wCRC & 0xffff;
		}
	}
	wHi = wCRC / 256;
	wLo = wCRC % 256;
	wCRC = (wHi << 8) | wLo;
	return wCRC;
}

 功能码03发送报文函数

//功能码03读单个或者多个寄存器
void modbus_03_function(void)
{
	modbus.tx_flag=0;
	modbus.tx_buff[0]=req_id;
	modbus.tx_buff[1]=0x03;
	modbus.tx_buff[2]=reg_start_addr/256;
	modbus.tx_buff[3]=reg_start_addr%256;
	modbus.tx_buff[4]=reg_len/256;
	modbus.tx_buff[5]=reg_len%256;
	calCRC = Modbus_CRC16(modbus.tx_buff,6);
	modbus.tx_buff[6]=calCRC/256;
	modbus.tx_buff[7]=calCRC%256;
	UART1_Sendarr(modbus.tx_buff,8);
}

功能码06写单个寄存器

//功能码06写单个寄存器报文发送
void modbus_06_function(uint16_t reg,uint16_t data)
{
	modbus.tx_flag=0;
	modbus.tx_buff[0]=req_id;
	modbus.tx_buff[1]=0x06;
	modbus.tx_buff[2]=reg/256;
	modbus.tx_buff[3]=reg%256;
	modbus.tx_buff[4]=data/256;
	modbus.tx_buff[5]=data%256;
	calCRC = Modbus_CRC16(modbus.tx_buff,6);
	modbus.tx_buff[6]=calCRC/256;
	modbus.tx_buff[7]=calCRC%256;
	UART1_Sendarr(modbus.tx_buff,8);
}

 功能码10写多个寄存器

//功能码10写单个寄存器报文发送
void modbus_16_function(uint16_t reg,uint16_t len)
{
	modbus.tx_flag=0;
	modbus.tx_buff[0]=req_id;
	modbus.tx_buff[1]=0x10;
	modbus.tx_buff[2]=reg/256;
	modbus.tx_buff[3]=reg%256;
	modbus.tx_buff[4]=len/256;
	modbus.tx_buff[5]=len%256;
	calCRC = Modbus_CRC16(modbus.tx_buff,6);
	modbus.tx_buff[6]=calCRC/256;
	modbus.tx_buff[7]=calCRC%256;
	UART1_Sendarr(modbus.tx_buff,8);
}

功能码发送函数

//发送报文
void Modbus_Service_SEND(void)
{
	if(modbus.tx_flag==1)
	{
		order_act();

		if(send_06)
		{
			modbus_06_function(reg_06,reg_data_06);
			
		}
		else if(send_16)
		{
			modbus_16_function(2,3);
			send_16=0;
		}
		else
		{
			modbus_03_function();
		}
	}
}

 功能码03解析函数

Modbus功能码03处理程序///已验证程序OK
读保持寄存器
void Modbus_03_Solve(void)
{
	uint8_t i;	

	RegNum= (uint16_t)modbus.rx_buff[2];//获取寄存器数量
	
	for(i=0;i<RegNum;i+=2)	
	{
		Modbus_IO[reg_start_addr+i/2]=modbus.rx_buff[3+i]<<8|modbus.rx_buff[4+i];
	}
}

 功能码06解析函数

Modbus功能码06处理程序   //已验证程序OK
写单个保持寄存器
void Modbus_06_Solve(void)
{
	Modbus_IO[startRegAddr]=modbus.rx_buff[4]<<8;//高字节在前                    修改为高字节在前,低字节在后
	Modbus_IO[startRegAddr]|=((u16)modbus.rx_buff[5]);//低字节在后

	modbus.tx_buff[0]=modbus.rx_buff[0];
	modbus.tx_buff[1]=modbus.rx_buff[1];
	modbus.tx_buff[2]=modbus.rx_buff[2];
	modbus.tx_buff[3]=modbus.rx_buff[3];
	modbus.tx_buff[4]=modbus.rx_buff[4];
	modbus.tx_buff[5]=modbus.rx_buff[5];

	calCRC=Modbus_CRC16(modbus.tx_buff,6);
	modbus.tx_buff[6]=(calCRC>>8)&0xFF;
	modbus.tx_buff[7]=(calCRC)&0xFF;
	UART1_Sendarr(modbus.tx_buff,8);
}

功能码10解析函数

Modbus功能码16处理程序 /已验证程序OK
写多个保持寄存器
void Modbus_16_Solve(void)
{
	u8 i;
	RegNum= (((u16)modbus.rx_buff[4])<<8)|((modbus.rx_buff[5]));//获取寄存器数量
	if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
	{
		for(i=0;i<RegNum;i++)
		{
			Modbus_IO[startRegAddr+i]=modbus.rx_buff[7+i*2]<<8; //低字节在前                 /// 低字节在前,高字节在后正常
			Modbus_IO[startRegAddr+i]|=((u16)modbus.rx_buff[8+i*2]); //高字节在后
		}

		modbus.tx_buff[0]=modbus.rx_buff[0];
		modbus.tx_buff[1]=modbus.rx_buff[1];
		modbus.tx_buff[2]=modbus.rx_buff[2];
		modbus.tx_buff[3]=modbus.rx_buff[3];
		modbus.tx_buff[4]=modbus.rx_buff[4];
		modbus.tx_buff[5]=modbus.rx_buff[5];

		calCRC=Modbus_CRC16(modbus.tx_buff,6);
		modbus.tx_buff[6]=(calCRC>>8)&0xFF;
		modbus.tx_buff[7]=(calCRC)&0xFF;
		UART1_Sendarr(modbus.tx_buff,8);
	}
	else//寄存器地址+数量超出范围
	{
		modbus.tx_buff[0]=modbus.rx_buff[0];
		modbus.tx_buff[1]=modbus.rx_buff[1]|0x80;
		modbus.tx_buff[2]=0x02; //异常码
		UART1_Sendarr(modbus.tx_buff,3);
	}
}

 清空接收

//清空数组
void Rec_Buf_clean(u8 *point,u8 len ) 
{
	int p;
	for (p=0;p<len;p++)
	{
		point[p]=0;
	}
}

 中断接收数据组包

//用于串口接收中断,进行组包
void Modbus_pack(u8 Res)
{
	if(modbus.rx_cnt<100)
	{
		modbus.rx_buff[modbus.rx_cnt++]=Res;
		modbus.rx_end = 0;
	}
	else
	{
		Rec_Buf_clean(modbus.rx_buff,100);
		modbus.rx_cnt = 0;
	}
}

MODBUS解析

         首先我们先通过手册去看电能数据的起始地址,在结合我们的功能码读取相应数据,并通过手册的解析公式转换后得到最后的电能数据

 功能码03读一个或多个寄存器解析

        注意:以产品手册为例

 请求01 03 00 48 00 0A 45 DB (读 0048 开始的 10 个寄存器)

第1位 01 设备地址

第2位 03 功能码

第3,4位00 48起始地址

第5,6位 00 0A查询寄存器长度,查询10个寄存器

第7,8位45 DB是CRC校验码

响应:01 03 14 5F C0 01 8F 03 D5 00 00 07 C0 03 E8 00 00 01 E6 6E 5E 13 86 F9 F1

前2位设备地址和功能码,同请求发送的报文

第3位14是后面数据位的长度,每一个寄存器长度为2个字节,所以总长20(0x14)

第4位开始,20位都是数据位

对照我们电能数据表红色数据

第4,5位 5F C0 对应 0048 寄存器,即为电压:0x5FC0=24512,根据公式除以 100 为 245.12V

第6,7位01 8F电流:0x018F:根据公式除以 100 为 3.99A

第8,9位03 D5有功功率:0x03D5:根据公式981W

第10,11,  12,13位 00 00 07 C0电能:0x000007C0:根据公式除以 3200为 62KWh

第14,15位03 E8功率因素:0x018F:根据公式除以 1000 为1

第16,17,18,19位00 00 01 E6二氧化氮排放量:0x000001E6:根据公式除以 1000 为 486Kg

第17,18位6E 5E温度:0x018F:根据公式除以 1000 为 28.254度

第19,20位13 86频率:0x1386:根据公式除以 100 为 49.98Hz

如果我们要更改模块的ID和通信速率,就需要功能码06和10去更改。如果是想一次一次改就用功能码06分别去改ID和波特率,功能码10就可以一次性改。首先我们先根据手册去看模块默认的ID和波特率。如下图:

功能码06写单个寄存器解析

请求:01 06 00 04 00 37 89 DD

向地址为4的寄存器写入55。

第1位 01 设备地址

第2位 06 功能码

第3,4位 00 04写入的起始地址

第5,6位 00 37写入寄存器的值

第7,8位89 DD位CRC校验码

响应:01 06 00 04 00 37 89 DD

写入成功,响应报文与写入报文一致

功能码10写多个寄存器解析

        改ID和波特率

请求02 10 00 04 00 01 02 01 06 32 B6

第1位 02 设备地址

第2位 10 功能码

第3,4位 00 04写入的起始地址

第5,6位 00 01寄存器数量

第7位02字节计数

第8位01表示改的ID

第9位 06表示改的波特率

第10,11位 32 B6表示校验码

响应01 10 00 04 00 01 40 08

第1位 01 设备地址

第2位 10功能码

第3,4位 00 04写入的起始地址

第5,6位 00 01寄存器数量

第7,8位40 08位CRC校验码

结果如下图:

总结

        以上就是我们利用MODBUS协议通过JSY-MK-163单相计量模块去读电能数据的全过程啦,到这里我们也结束了本专栏家庭能源网关的开发。很多小伙伴问我JSY-MK-163单相计量模块从哪里买的,当然是万能的淘宝,要是有兴趣的小伙伴可以点击JSY-MK-163单相计量模块  查看详情购买。下面是我们本期用到读取电能数据的模块图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值