该方法用来设置线圈的数据,贴上部分代码。
我们知道Modbus为数据为大端模式(高地址放在低位,低地址放在高位),这样可以按着我们的习惯来设置IO数据。如下为设置多个线圈帧。
今天就以这样的组合看modbus协议如何发送和解析的协议帧的。
下图是我的测试记录,可以看到差异差异就是倒数第3个字节
框选1:发送的报文Tx:008108-01 0F 0B B8 00 10 02 03 00 43 68
框选2:发送的报文Tx:008116-01 0F 0B B8 00 10 02 03 C0 43 38
下面先了解下 GD32单片机是大端还是小端模式
{
uint16_t data= 0x1122;
uint8_t *usdat = (uint8_t*)&data;
LOGE("0x%x,%x",(uint8_t*)&usdat[0],usdat[0]);
LOGE("0x%x,%x",(uint8_t*)&usdat[1],usdat[1]);
}
打印结果:
可以看到,针对GD32单片机来说,数据低位保存在低地址上,即“小弟弟”–>小低低模式。
接着上面的,目前我先把3000开始的2个线圈打开了,可以看到协议数据部分是03 00, 由于modbus是大端模式,单片机是小段模式,则低位的3000和3001寄存器对应的状态是0x03,即低位的2个灯亮了。
3000 | 3001 | ~~ | ~~ | 3015 |
---|
eMBErrorCode
eMBRegCoilsCB(MBObj_t *pMbObj, UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{
//return MB_ENOREG;
eMBErrorCode eStatus = MB_ENOERR;
short iNCoils = (short)usNCoils;
unsigned short usBitOffset;
//这里主要是MODBUS上层代码把地址+1了,很想I2C的读地址(+1操作),为此这里把地址进行还原
usAddress = usAddress -1;
LOGD("codi");
/* Check if we have registers mapped at this block. */
if ((usAddress >= REG_COILS_START) &&
(usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE))
{
usBitOffset = (unsigned short)(usAddress - REG_COILS_START);
switch (eMode)
{
/* Read current values and pass to protocol stack. */
case MB_REG_READ:
while (iNCoils > 0)
{
*pucRegBuffer++ =
xMBUtilGetBits(ucRegCoilsBuf[pMbObj->portId], usBitOffset,
(unsigned char)(iNCoils >
8 ? 8 :
iNCoils));
iNCoils -= 8;
usBitOffset += 8;
}
break;
/* Update current register values. */
case MB_REG_WRITE:
LOGD("ncoils:%d",iNCoils);
{
UCHAR *Psrc = ucRegCoilsBuf[pMbObj->portId];
while (iNCoils > 0)
{//这个地方为线圈写操作
//重点说的就是这里,这里我们设置16个bit,
//usBitOffset:首个线圈的偏移。3000-3000 = 0、iNCoils =16.接下来请看下面这个函数的具体实现
xMBUtilSetBits(Psrc++, usBitOffset,
(unsigned char)(iNCoils > 8 ? 8 : iNCoils),
*pucRegBuffer++);
iNCoils -= 8;
}
{
//add by armwind
UCHAR *PpduBUf = NULL;
master_ops->pvGetPduBuffer(&PpduBUf);
if (MB_FUNC_WRITE_SINGLE_COIL == PpduBUf[MB_PDU_FUNC_OFF]) {
//用户回调方法
} else if (MB_FUNC_WRITE_MULTIPLE_COILS == PpduBUf[MB_PDU_FUNC_OFF]) {
//用户回调方法
}
}
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
//注意这里,我们我们线圈寄存器基地址为3000,假设bitoffset为6,ucNBits为3,ucValue=0x03,
//即3006开始的3个bit
//ucByteBuf:寄存器首地址
//usBitOffset:寄存器偏移地址
//ucNBits:线圈数量
//线圈状态值
void
xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,
UCHAR ucValue )
{
USHORT usWordBuf;
USHORT usMask;
USHORT usByteOffset;
USHORT usNPreBits;
USHORT usValue = ucValue;
assert( ucNBits <= 8 );
assert( ( size_t )BITS_UCHAR == sizeof( UCHAR ) * 8 );
/* Calculate byte offset for first byte containing the bit values starting
* at usBitOffset. 偏移地址换算成几个字节,这里usByteOffset=0*/
usByteOffset = ( USHORT )( ( usBitOffset ) / BITS_UCHAR );
/* How many bits precede our bits to set. 多出来的bit数,usNPreBits=6*/
usNPreBits = ( USHORT )( usBitOffset - usByteOffset * BITS_UCHAR );
/* Move bit field into position over bits to set */
usValue <<= usNPreBits; //0x03<<6,数据对准窗口
/* Prepare a mask for setting the new bits. */
usMask = ( USHORT )( ( 1 << ( USHORT ) ucNBits ) - 1 );//计算数据bit掩码,1<<3-1,即0x07
usMask <<= usBitOffset - usByteOffset * BITS_UCHAR; //数据bit掩码,滑动到对应的数据区域
/* copy bits into temporary storage. */
usWordBuf = ucByteBuf[usByteOffset]; //获取低地址数据
usWordBuf |= ucByteBuf[usByteOffset + 1] << BITS_UCHAR; //获取高位地址数据
/* Zero out bit field bits and then or value bits into them. */
// 这一步先将数据区域清0,然后再与上赋予的数据
usWordBuf = ( USHORT )( ( usWordBuf & ( ~usMask ) ) | usValue );
/* move bits back into storage */
ucByteBuf[usByteOffset] = ( UCHAR )( usWordBuf & 0xFF );
ucByteBuf[usByteOffset + 1] = ( UCHAR )( usWordBuf >> BITS_UCHAR );
}
-
获取数据bit掩码
-
将数据滑动到指定偏移的窗口区
然后想根据掩码,与非后清空数据区,再按位或即可将数据更新进去