经过前面几个夜晚的学习,此刻已经在FreeModbus中添加了主模式的代码,特此记录下添加心得体会。代码添加还是很容易,就是要保证数据准确的发送出去
文章目录
- 1. 添加前的思考
- 2. 添加工作模式(ASCII,RTU)接口
- 2.1 接口描述
- 2.2 接口注册过程
- 3. 添加数据打包过程
- 3.1 接口介绍
- 3.2 接口实现
- 4. 实验演示
1. 添加前的思考
在添加主模式代码前,深入的分析了FreeModbus源码,了解了其中软件协议才敢开始加代码。不过说来说去也就那么点东西,毕竟代码还是很简单。
- 掌握FreeModbus事件驱动的 "源动力"
针对于从模式设备,所有动作的执行,基本都来自于主机,即串口命令(中断)。有了数据和中断,各个状态机才能行动起来。 - 了解FreeModbus的接口
不管是RTU模式还是ASCII模式,他们接口都是固定的。都需要将他们的接口注册到modbus中,使用全局的函数指针管理起来。这样调用时直接使用函数指针,而不用关心具体接口的名字。 - 了解常用功能码的含义
这个才能更好的解读function目录下解析数据的流程。
2. 添加工作模式(ASCII,RTU)接口
2.1 接口描述
当主模式使用时,打包帧时需要获取到从机地址、PDU缓存、发送数据大小,所以需要添加几个接口获取。如下是RTU模式中添加的接口。
//下面是主机添加的接口,统一即可
typedef struct {
void (*pvGetPduBuffer)(UCHAR **ucPduBuffer);
USHORT (*pusGetPduSndBufCount)();
void (*pvSetPduSndBufCount)(USHORT len);
UCHAR (*pusGetDstAddr)();
void (*pvSetDstAddr)(UCHAR addr);
}mst_ops_t;
//====================================以下代码添加在mbrtu.c==========================
/*add by armwind for master send data,package by user.*/
static volatile USHORT usSndPduBufferCount = 0;
static volatile UCHAR usDstAddr = 0;
/*
*brief:get pdu buffer data
* <----------- MODBUS PDU (1') ---------------->
* +-----------+---------------+----------------------------+-------------+
* | Address | Function Code | Data | CRC/LRC |
* +-----------+---------------+----------------------------+-------------+
*/
static void vMBRTUGetPduBuffer(UCHAR **ucPduBuffer)
{
*ucPduBuffer =(UCHAR *) &ucRTUBuf[MB_SER_PDU_PDU_OFF];
}
static USHORT usMBRTUGetPduSndBufCount()
{
return usSndPduBufferCount;
}
static void vMBRTUSetPduSndBufCount(USHORT len)
{
usSndPduBufferCount = len;
}
static UCHAR usMBRTUGetDstAddr()
{
return usDstAddr;
}
static void vMBRTUSetDstAddr(UCHAR addr)
{
usDstAddr = addr;
}
mst_ops_t rtu_mst_ops = {
vMBRTUGetPduBuffer,
usMBRTUGetPduSndBufCount,
vMBRTUSetPduSndBufCount,
usMBRTUGetDstAddr,
vMBRTUSetDstAddr
};
- vMBRTUGetPduBuffer:获取PDU缓存地址,这个就是
volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX];
去掉设备地址的部分。用于打包数据时使用。 - usMBRTUGetPduSndBufCount:获取发送缓存数据长度,给FreeModbus发送接口传入数据长度
//实现在mb.c poll中
case EV_FRAME_SENT:
master_ops->pvGetPduBuffer(&ucMBFrame); //master_ops即为注册的rtu或者ascii接口。
peMBFrameSendCur(master_ops->pusGetDstAddr(), ucMBFrame, master_ops->pusGetPduSndBufCount());
// ( void )xMBPortEventPost( EV_EXECUTE );
break;
- vMBRTUSetPduSndBufCount:打包完数据后,需要将发送数据长度记录下,以便真正发送时使用。
- usMBRTUGetDstAddr:需要访问的子设备地址,在真正执行发送数据时,传入缓存中,如下发送数据函数第一个参数就是子设备地址。
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
eMBErrorCode eStatus = MB_ENOERR;
USHORT usCRC16;
ENTER_CRITICAL_SECTION( );
if( eRcvState == STATE_RX_IDLE )
{
/* First byte before the Modbus-PDU is the slave address. */
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;//这里保存了地址信息
usSndBufferCount += usLength;
//此处省略n行代码
return eStatus;
}
- vMBRTUSetDstAddr:提前保存子设备地址信息,方便后面使用。
此外上面添加了一个函数指针结构体mst_ops_t
,方便接口统一注册到modbus中。
2.2 接口注册过程
注册过程在eMBInit()相当的简单。接口注册过来后,用户无需关心关心是RTU模式还是ASCII,直接使用全局函数指针即可。
eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;
/* check preconditions */
if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) ||
( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) )
{
eStatus = MB_EINVAL;
}
else
{
ucMBAddress = ucSlaveAddress;
switch ( eMode )
{
#if MB_RTU_ENABLED > 0
case MB_RTU:
pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
//add by armwind for master mode.
extern mst_ops_t rtu_mst_ops;
master_ops = &rtu_mst_ops; //注册RTU接口
eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
break;
#endif
#if MB_ASCII_ENABLED > 0
case MB_ASCII:
pvMBFrameStartCur = eMBASCIIStart;
pvMBFrameStopCur = eMBASCIIStop;
peMBFrameSendCur = eMBASCIISend;
peMBFrameReceiveCur = eMBASCIIReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM;
pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired;
//add by armwind for master mode.
extern mst_ops_t ascii_mst_ops;
master_ops = &ascii_mst_ops;//注册ASCII接口
eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity );
break;
#endif
default:
eStatus = MB_EINVAL;
}
3. 添加数据打包过程
3.1 接口介绍
针对各种功能码读写数据的格式还有些出入,为了统一接口,定义如下的接口。
eMBErrorCode
eMBMasterRequestRWData(UCHAR ucSndAddr, /*dst address*/
USHORT usAddress, /*register address*/
USHORT usNRegs, /*register count*/
UCHAR nBytes, /*data count unit bytes,should nbytes = 2*usNRegs */
UCHAR *data, /*data to be write*/
eMBRegisterMode eMode) /* operation mode, read or write*/
- ucSndAddr:目的设备地址
- usAddress:当不是进行 “读写保持寄存器时”(读写同时进行),此寄存器为读寄存器地址,写寄存器存在了data指中。
- usNRegs:读写寄存器的数量,
- nBytes:读写寄存器的bytes字节数。
- data:写入的数据,当进行 “读写保持寄存器时”(读写同时进行),此data保存了写入的信息,其结构如下所示。
typedef struct {
USHORT usAddress; /*register address*/
USHORT usNRegs; /*register count*/
UCHAR nBytes; /*data count unit bytes,should nbytes = 2*usNRegs */
UCHAR *data; /*data write to usAddress*/
} rw_hold_dat_t;
- eMode:读写的工作模式,其枚举如下
typedef enum
{
MB_REG_READ, /*!< Read register values and pass to protocol stack. */
MB_REG_WRITE, /*!< Update register values. */
//add by armwind for master
MB_REG_WRITE_MULTIPLE, /*just write data*/
MB_REG_READWRITE, /*read/write data at same time*/
MB_REG_CMD_MAX_COUNT
} eMBRegisterMode;
3.2 接口实现
由于各个功能码数据打包的流程不一样,而且读写模式是有区别的,所以每个文件中都需要添加一个此接口。这些文件如下所示:
-rw-r--r-- 1 armwind 1049089 12912 2月 14 10:56 mbfunccoils.c
-rw-r--r-- 1 armwind 1049089 7070 2月 14 10:58 mbfuncdisc.c
-rw-r--r-- 1 armwind 1049089 16944 2月 14 11:01 mbfuncholding.c
-rw-r--r-- 1 armwind 1049089 6541 2月 14 10:56 mbfuncinput.c
这里为了方便,以mbfuncdisc.c
文件中添加的接口为例。由于 读离散输入状态这种情况主设备只有读的权限。
/**
brief:read and write short data.
**/
eMBErrorCode
eMBMasterRequestRWDisc(UCHAR ucSndAddr, /*dst address*/
USHORT usAddress, /*register address*/
USHORT usNRegs, /*register count*/
UCHAR nBytes, /*data count unit bytes,should nbytes = 2*usNRegs */
UCHAR *data, /*data to be write*/
eMBRegisterMode eMode) /* operation mode, read or write*/
{
UCHAR *ucMBPduBuf = NULL;
UCHAR sndLen = 0;
eMBErrorCode eStatus = MB_ENOERR;
master_ops->pvGetPduBuffer(&ucMBPduBuf); //获取pdu buffer的指针
master_ops->pvSetDstAddr(ucSndAddr); //保存子设备地址
/* Check if we have registers mapped at this block. */
if ((usAddress >= REG_DISC_START) && (usAddress + usNRegs <= REG_DISC_START + REG_DISC_SIZE)
&& (usNRegs < MB_PDU_FUNC_READ_DISCCNT_MAX)) {
switch (eMode) {
/* Read current values and pass to protocol stack. */
case MB_REG_READ:
ucMBPduBuf[MB_PDU_FUNC_OFF] = MB_FUNC_READ_DISCRETE_INPUTS; //读离散事件的功能码
ucMBPduBuf[MB_PDU_FUNC_READ_ADDR_OFF] = (UCHAR)(usAddress >> 8);
ucMBPduBuf[MB_PDU_FUNC_READ_ADDR_OFF + 1] = (UCHAR)usAddress; //寄存器
ucMBPduBuf[MB_PDU_FUNC_READ_DISCCNT_OFF] = (UCHAR)(usNRegs >> 8);
ucMBPduBuf[MB_PDU_FUNC_READ_DISCCNT_OFF + 1] = (UCHAR)usNRegs;//寄存器数量
sndLen = 5;
break;
}
}
else
{
//LOGE("dst_addr:0x%x,reg_addr:0x%x,usNregs:%d,nBytes:%d,data:0x%x,eMode:%d",ucSndAddr,usAddress,usNRegs,nBytes,data,eMode)
eStatus = MB_EINVAL;
}
master_ops->pvSetPduSndBufCount(sndLen); //保存发送数据长度
(void)xMBPortEventPost(EV_FRAME_SENT); //前面已经把数据打包好了,激发发送状态机,启动真正的串口发送流程。
return eStatus;
}
当然上面宗旨是要把数据打包好,打包准确,并最后由串口发送出去。
4. 实验演示
由于实例是在visual studio2015上模拟调试,物理设备上的调试需疫情过后了。下面这个例子能循环执行各种请求,查看打印的结果,可以判断发送数据的准确性。至于读取子设备响应数据,要在物理设备上调了。不过使用FreeModbus原生的接收流程,应该是没有问题的。
int main()
{
const UCHAR ucSlaveID[] = { 0xAA, 0xBB, 0xCC };
if (eMBInit(MB_RTU, 0x0A, 1, 38400, MB_PAR_EVEN) != MB_ENOERR)
{
printf("can't initialize modbus stack!\r\n");
}
else if (eMBSetSlaveID(0x34, TRUE, ucSlaveID, 3) != MB_ENOERR)
{
printf("can't set slave id!\r\n");
}
else
{
if( eMBEnable( ) == MB_ENOERR )
{
UCHAR dat[2] = {0xf8,0xf8};
UCHAR dat2[8] = {0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe};
UCHAR count = 0;
rw_hold_dat_t hld_dat = { 2100,4,8,dat2 };
while (1)
{
//printf("request write coils\n");
if (count == 0) {
printf("request read coils\n");
eMBMasterRequestRWCoils(0x3a, 1000, 1, 2, dat, MB_REG_READ);
}
else if (count == 1) {
printf("request write coils\n");
eMBMasterRequestRWCoils(0x3a, 1000, 1, 2, dat, MB_REG_WRITE);
}
else if (count == 2) {
printf("request write multiple coils\n");
eMBMasterRequestRWCoils(0x3a, 1006, 4, 8, dat2, MB_REG_WRITE_MULTIPLE);
}
else if (count == 3) {
printf("request read disc\n");
eMBMasterRequestRWDisc(0x3a, 1000, 1, 2, dat, MB_REG_READ);
}
else if (count == 4) {
printf("request read holding\n");
eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, dat, MB_REG_READ);
}
else if (count == 5) {
printf("request write holding\n");
eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, dat, MB_REG_WRITE);
}
else if (count == 6) {
printf("request write multiple holding\n");
eMBMasterRequestRWHolding(0x3a, 2000, 4, 8, dat2, MB_REG_WRITE_MULTIPLE);
}
else if (count == 7) {
printf("request read/write multiple holding\n");
eMBMasterRequestRWHolding(0x3a, 2000, 1, 2, (UCHAR*)&hld_dat, MB_REG_READWRITE);
}
else if (count == 8) {
printf("request read input\n");
eMBMasterRequestRWInput(0x3a, 1000, 1, 2, dat, MB_REG_READ);
}
else {
printf("-----------------------------------------------------------------------------------------\n");
count = 0;
continue;
}
if (eMBPoll() != MB_ENOERR)
break;
count++;
}
}
( void )eMBDisable( );
}
( void )eMBClose( );
return 0;
}
打印结果:
下面貌似有几个数据校验码算的有问题,可能逻辑上还有些问题,待稍后优化下吧。
-----------------------------------------------------------------------------------------
request read coils
0x3a,0x01,0x03,0xe8,0x00,0x01,0x79,0x31,
request write coils
0x3a,0x05,0x03,0xe8,0xf8,0xf8,0x79,0x31,
request write multiple coils
0x3a,0x0f,0x03,0xee,0x00,0x04,0x08,0xfe,
request read disc
0x3a,0x02,0x03,0xe8,0x00,0x01,0x08,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x81,0xa9,
request read holding
0x3a,0x02,0x03,0xe8,0x00,0x01,0x3d,0x31,
request write holding
0x3a,0x05,0x07,0xd0,0xf8,0xf8,0x3d,0x31,
request write multiple holding
0x3a,0x05,0x07,0xd0,0xf8,0xf8,0x8b,0x8e,
request read/write multiple holding
0x3a,0x17,0x07,0xd0,0x00,0x01,0x08,0x34,
request read input
0x3a,0x01,0x03,0xe8,0x00,0x01,0x08,0x34,0x00,0x04,0x08,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x1e,0x02,
-----------------------------------------------------------------------------------------