大家好,我是菜菜,在上一期我们主要介绍了关于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单相计量模块 查看详情购买。下面是我们本期用到读取电能数据的模块图: