modbus绑定变量,并发送8位数据的办法
简单介绍一下modbus,在输入寄存器和保持寄存器的操作里,这里只能存储16位数据。如图:
需求一:
1.如果已经定义了一些变量,或者结构体了,那么我该如何去通过modbus,来维护这些变量呢?比如一个是开关变量,已经在项目中使用了。此时我添加modbus,我该如何去维护这个变量的变化呢?
方法一、写一个定时扫描的函数,来比对数组和变量的值,用来检查是否发生变化。
这个方法本人认为,首先占用时间,其次对于数据的维护不安全,万一扫描的间隔中,数据发生变化,并且需要读取了,那么此刻读取变量了,那么变量的值就没有发生及时变化。
方法二、绑定变量,在接受时,直接修改变量。
这个方法就是我采用的方法。
2.如果我需要发送8位数据,又不想影响数组和地址之间对应的关系,该怎么办?
方法一、所有的数据都以16位存放。超过16位的分成两个或更多。
问题在于,如果这些变量已经定义好了呢?比如有些数据是bool类型,它已经定义好了,不能去修改。那么这时候该怎么办呢?
方法二、告知类型,根据类型,分类管理。
这里就是我使用的方法。
分析函数:
我主要就是解决上述两个需求。
首先我们看modbus的数据处理函数,以eMBRegHoldingCB为例:
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode eStatus = MB_ENOERR;int iRegIndex;/* it already plus one in modbus function method. */usAddress--;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);switch(eMode){ case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U8){*pucRegBuffer++ = 0x00;*pucRegBuffer++ = (*(uint8_t*)usRegHoldingBuf8[iRegIndex].pData);}else if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U16){*pucRegBuffer++ = (uint8_t)((*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) >> 8); *pucRegBuffer++ = (uint8_t)((*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) & 0xFF); }iRegIndex++;usNRegs--; } break;case MB_REG_WRITE://写 MB_REG_WRITE = 0while(usNRegs > 0){ if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U8){uint8_t high = *pucRegBuffer++; // 高字节(丢弃)uint8_t low = *pucRegBuffer++; // 低字节(有效数据)(void)high; // 避免编译器警告(*(uint8_t*)usRegHoldingBuf8[iRegIndex].pData) = low;}else if (usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U16){(*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) = *pucRegBuffer++ << 8;(*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData)|= *pucRegBuffer++;}iRegIndex++;usNRegs--;} }}else//错误{eStatus = MB_ENOREG;} return eStatus;
}
我们可以看到,这里它是先判断了我们读取的数据是否在地址范围内。接着通过
iRegIndex = (int)(usAddress - usRegHoldingStart);
将起始地址找到。同时这个序号也是维护的数组的下标。这里我们就知道了,对于维护的数组。不管数据的大小,必须一个空间存放一个16位数据。
接着看,发现后边是判断读取的模式和数据收发的方法。那么这里就是我们需要做修改的地方!
修改方法:
首先:我们要绑定数据,那么一定是通过指针来找到数据。那么这就需要我们来修改存放的数组了。需要将数组变成存放指针的指针数组。同时,对于数据的不同大小(16位,32位无所谓,本身modbus对于这个处理就是正常的),尤其是8位数据,我们需要指明类型。ok这样我们就知道了修改的办法:
首先我们定义一个结构体如下:
typedef enum {REG_TYPE_U8,REG_TYPE_U16,
} RegType;typedef struct {RegType type;void *pData;
} RegMap;
上边的枚举定义了数据的类型。这里不能有32或64位。因为正常的modbus,本身就是一个数组存放一个16位数据。我们这里主要处理的是8位和非8位数据的区别。因为两者需要不同的收发函数。
紧接着修改数组类型
//输入寄存器内容 只读
//uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
RegMap usRegInputBuf8[REG_INPUT_NREGS];
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;//保持寄存器内容 可读可写
//uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
RegMap usRegHoldingBuf8[REG_HOLDING_NREGS];
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;
我只用这两个,所以只对这两个做修改,同理其他的也是一样的。线圈部分则是会调用函数,将位一个个取出,重新变成8位数据发送而已。(注释的就是之前的样子,下边的就是现在修改后的样子。)
接下来就是初始化这些变量了。因为我们数据是需要绑定的,还有类型也需要指定好。
简单的举个例子:
typedef struct{uint8_t data1;uint16_t data2;float data3; }tempdata;tempdata temp;
void initdata()
{
uint16_t* pTemP16;usRegInputBuf8[0].pData = (uint8_t*)&temp.data1;usRegInputBuf8[0].type = REG_TYPE_U8;usRegInputBuf8[1].pData = (uint16_t*)&temp.data2;usRegInputBuf8[1].type = REG_TYPE_U16;pTemP16 = (uint16_t*)&temp.data3;usRegInputBuf8[2].pData = &pTemP16[0];usRegInputBuf8[2].type = REG_TYPE_U16;usRegInputBuf8[3].pData = &pTemP16[1];usRegInputBuf8[3].type = REG_TYPE_U16;
}
原理很简单,对于8位数据,我们类型指定8位,对于16位,存16位。但是对于32位,我们拆成两个,分别存储前16位和后16位。那么以此类推,64位也是。
接下来就是收发函数的修改:
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode eStatus = MB_ENOERR;int iRegIndex;/* it already plus one in modbus function method. */usAddress--;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);switch(eMode){ case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U8){*pucRegBuffer++ = 0x00;*pucRegBuffer++ = (*(uint8_t*)usRegHoldingBuf8[iRegIndex].pData);}else if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U16){*pucRegBuffer++ = (uint8_t)((*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) >> 8); *pucRegBuffer++ = (uint8_t)((*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) & 0xFF); }iRegIndex++;usNRegs--; } break;case MB_REG_WRITE://写 MB_REG_WRITE = 0while(usNRegs > 0){ if(usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U8){uint8_t high = *pucRegBuffer++; // 高字节(丢弃)uint8_t low = *pucRegBuffer++; // 低字节(有效数据)(void)high; // 避免编译器警告(*(uint8_t*)usRegHoldingBuf8[iRegIndex].pData) = low;}else if (usRegHoldingBuf8[iRegIndex].type == REG_TYPE_U16){(*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData) = *pucRegBuffer++ << 8;(*(uint16_t*)usRegHoldingBuf8[iRegIndex].pData)|= *pucRegBuffer++;}iRegIndex++;usNRegs--;} }}else//错误{eStatus = MB_ENOREG;} return eStatus;
}
首先时在里边进行判断,如果时8位数据,那么我们就先写入0,后再写入8位数据。如果是16位数据,那么我们就写入高字节数据,再写入低字节数据。在这个之后,再将iRegIndex增加。这样就做到了一个数组内部,每一个对应一个数据。
(提一嘴,之前想的是把数组指针定uint8_t*来索引数据,也就是每一个数组来存8位数据,发送时候一次性发送两个,凑成一个16位数据。但是我发现这样还是有问题,与接收方是不对应的。同时你想要写数据的时候,如果是两个uint8_t数据,这时候,你一写,因为你写的必须给16位数据。那么就会将两个全部写掉。但是实际上你需要的只是写一个。这就导致了数据的不稳定。)
所以现在这个样子大体算是最好的方法了。
如果有好的方法,也可以分享。其他的也是照样子改,至于线圈部分,我就不做修改了。但是原理相同,我也修改过,但是感觉没必要。