之前分享的视频和文档:
BSP视频教程第23期:通过Modbus解析器,轻松掌握Modbus主从协议栈(2022-09-16)
https://www.armbbs.cn/forum.php?mod=viewthread&tid=115474
BSP视频教程第24期:应用实战,设计一款属于自己的Modbus RTU主从协议栈(2022-10-03)
https://www.armbbs.cn/forum.php?mod=viewthread&tid=115788
Modbus收发数据少的话,直接已经发布的例子方案即可,如果上百上千个寄存器处理,可以考虑map映射表的形式。
[C] 纯文本查看 复制代码 /*
*********************************************************************************************************
* 函 数 名: MODS_01H
* 功能说明: 读取线圈状态(对应远程开关D01/D02/D03)
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
/* 说明:这里用LED代替继电器,便于观察现象 */
typedef struct
{
uint8_t (*ModbusCbFuncRead)(uint8_t arg); /* Modbus回调处理函数,如果参数多,考虑将uint8_t改成结构体定义 */
void (*ModbusCbFuncWrite)(uint8_t arg); /* Modbus回调处理函数 */
uint16_t ModbusRegStart; /* 寄存器起始地址 */
uint16_t ModbusRegEnd; /* 寄存器结束地址 */
uint16_t Reserve1; /* 保留未使用,可以自己扩增 */
uint16_t Reserve2;
uint16_t Reserve3;
}MODBUS_T;
const MODBUS_T g_tModbus_01_MAP[] =
{
{bsp_IsLedOn, bsp_LedToggle, 0x0101, 0x0104, 0, 0, 0},
{NULL, NULL, 0x2101, 0x2104, 0, 0, 0},
{NULL, NULL, 0x3101, 0x3104, 0, 0, 0},
};
static void MODS_01H(void)
{
/*
举例:
主机发送:
11 从机地址
01 功能码
00 寄存器起始地址高字节
13 寄存器起始地址低字节
00 寄存器数量高字节
25 寄存器数量低字节
0E CRC校验高字节
84 CRC校验低字节
从机应答: 1代表ON,0代表OFF。若返回的线圈数不为8的倍数,则在最后数据字节未尾使用0代替. BIT0对应第1个
11 从机地址
01 功能码
05 返回字节数
CD 数据1(线圈0013H-线圈001AH)
6B 数据2(线圈001BH-线圈0022H)
B2 数据3(线圈0023H-线圈002AH)
0E 数据4(线圈0032H-线圈002BH)
1B 数据5(线圈0037H-线圈0033H)
45 CRC校验高字节
E6 CRC校验低字节
例子:
01 01 10 01 00 03 29 0B --- 查询D01开始的3个继电器状态
01 01 10 03 00 01 09 0A --- 查询D03继电器的状态
*/
uint32_t n = 0;
uint16_t reg;
uint16_t num;
uint16_t i;
uint16_t m;
uint8_t status[10];
g_tModS.RspCode = RSP_OK;
/** 第1步: 判断接到指定个数数据 ===============================================================*/
/* 没有外部继电器,直接应答错误
地址(8bit)+指令(8bit)+寄存器起始地址高低字节(16bit)+寄存器个数(16bit)+ CRC16
*/
if (g_tModS.RxCount != 8)
{
g_tModS.RspCode = RSP_ERR_VALUE; /* 数据值域错误 */
return;
}
/** 第2步: 数据解析 ===========================================================================*/
/* 数据是大端,要转换为小端 */
reg = BEBufToUint16(&g_tModS.RxBuf[2]); /* 寄存器号 */
num = BEBufToUint16(&g_tModS.RxBuf[4]); /* 寄存器个数 */
/* 不足字节整数倍,补齐 */
m = (num + 7) / 8;
/* 寄存器处理 */
for(n = 0; n < (sizeof(g_tModbus_01_MAP)/sizeof(g_tModbus_01_MAP[0])); n++)
{
/* 解析主机命令要读取的状态 */
if ((reg >= g_tModbus_01_MAP[n].ModbusRegStart) && (num > 0) && (reg + num <= g_tModbus_01_MAP[n].ModbusRegEnd + 1))
{
for (i = 0; i < m; i++)
{
status[i] = 0;
}
for (i = 0; i < num; i++)
{
if(g_tModbus_01_MAP[n].ModbusCbFuncRead != NULL)
{
if (g_tModbus_01_MAP[n].ModbusCbFuncRead(i + 1 + reg - REG_D01)) /* 读LED的状态,写入状态寄存器的每一位 */
{
status[i / 8] |= (1 << (i % 8));
}
}
}
}
else
{
g_tModS.RspCode = RSP_ERR_REG_ADDR; /* 寄存器地址错误 */
}
}
/** 第3步: 应答回复 =========================================================================*/
if (g_tModS.RspCode == RSP_OK) /* 正确应答 */
{
g_tModS.TxCount = 0;
g_tModS.TxBuf[g_tModS.TxCount++] = g_tModS.RxBuf[0]; /* 返回从机地址 */
g_tModS.TxBuf[g_tModS.TxCount++] = g_tModS.RxBuf[1]; /* 返回从机指令 */
g_tModS.TxBuf[g_tModS.TxCount++] = m; /* 返回字节数 */
for (i = 0; i < m; i++)
{
g_tModS.TxBuf[g_tModS.TxCount++] = status[i]; /* 返回继电器状态 */
}
MODS_SendWithCRC(g_tModS.TxBuf, g_tModS.TxCount);
}
else
{
MODS_SendAckErr(g_tModS.RspCode); /* 告诉主机命令错误 */
}
}
|