[--------------点击下面,进入总目录索引--------------]
STM32系列精品Demo集 - 总目录
图示:MosbusRtu通信效果演示
一、方案设计
- 1.1、Uart接收方案
Modbus通信采用主发从收的方式进行通信,所以本示例Demo采用[主发从收->阻塞式->数据定长]
的方案,方案实现细节参考3. STM32裸机串口接收方案
-
1.2、通信接口方案设计
本Demo大的方向上有两个接口,一是数据封包接口,二是数据解析接口,为了使接口更加清晰明了,每个功能都有各自独立的数据封包接口,所有的数据解析公用一个接口去做处理。
//数据封包接口 int ModbusRtuPacket01(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegNum, unsigned char *pucReqString, unsigned int *piNum); int ModbusRtuPacket02(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegNum, unsigned char *pucReqString, unsigned int *piNum); int ModbusRtuPacket03(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegNum, unsigned char *pucReqString, unsigned int *piNum); int ModbusRtuPacket04(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegNum, unsigned char *pucReqString, unsigned int *piNum); int ModbusRtuPacket05(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned char ucWriteValue,unsigned char *pucReqString, unsigned int *piNum); int ModbusRtuPacket06(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegValue,unsigned char *pucReqString, unsigned int *piNum); //数据解析接口 int ModbusRtuResponse(char cModbusMode, int iReadDataLen, unsigned int uiTimeOutMs,unsigned char *pucSlaveId, unsigned char *pucFunction, unsigned char *pucData, int *iDataLen);
二、通信接口详解
2.1、CRC校验接口
static const unsigned char g_aucCrcLow[] =
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
/* Table of CRC values for low-order byte */
static const unsigned char g_aucCrcHigh[] =
{
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
static int CrcError(unsigned char *pucBuf, int iLen)
{
int i;
unsigned char ucCrcHigh, ucCrcLow;
unsigned char ucIndex = 0;
if (NULL == pucBuf || iLen < 0)
{
ModbusUartPrintf("Wrong Parameter!");
return DRIVER_ERROR_PARAMETER;
}
ucCrcHigh = 0xff;
ucCrcLow = 0xff;
for (i = 0; i < iLen - 2; i++)
{
ucIndex = ucCrcLow ^ pucBuf[i];
ucCrcLow = ucCrcHigh ^ g_aucCrcLow[ucIndex];
ucCrcHigh = g_aucCrcHigh[ucIndex];
}
if ((ucCrcLow != pucBuf[iLen - 2]) ||
(ucCrcHigh != pucBuf[iLen - 1]))
{
ModbusUartPrintf("CRC Error!");
return DRIVER_ERROR_MODBUS_CRC;
}
return DRIVER_SUCCESS;
}
2.2、数据组包接口示例(03)
/***********************************************************************
* 函数名称: ModbusRtuPacket03
* 功能描述: 组包读保持存器(A0)点的协议帧
* 输入参数:
* 输出参数:
* 返 回 值:
iRspLen :Modbus从机应当响应的字节个数
* 其 它:
***********************************************************************/
int ModbusRtuPacket03(unsigned char ucSlave, char cModbusMode, unsigned short usStartAddr, unsigned short usRegNum,
unsigned char *pucReqString, unsigned int *piNum)
{
int iNum;
int iRspLen = 0;
if (NULL == pucReqString || NULL == piNum)
{
ModbusUartPrintf("Wrong Parameter!");
return DRIVER_ERROR_PARAMETER;
}
if ((cModbusMode != MODBUS_RTU)) // only support RTU.
{
ModbusUartPrintf("Wrong Parameter!");
return DRIVER_ERROR_MODBUS_MODE;
}
if (MODBUS_RTU == cModbusMode)
{
iNum = 0;
pucReqString[iNum++] = ucSlave;
pucReqString[iNum++] = READ_MUL_REG;
pucReqString[iNum++] = usStartAddr >> 8; //register start address (MSB)
pucReqString[iNum++] = usStartAddr & 0xFF; //(LSB)
pucReqString[iNum++] = usRegNum >> 8; //register number: number =usPointNum
pucReqString[iNum++] = usRegNum & 0xFF;
//insert CRC check code
CrcInsert(pucReqString, iNum);
iNum += 2;
}
*piNum = iNum; //the packeted data length
iRspLen = usRegNum * 2;
return iRspLen + READ_PKG_RSP_LEN;
}
2.3、数据解析接口示例
int ModbusRtuResponse(char cModbusMode, int iReadDataLen, unsigned int uiTimeOutMs,
unsigned char *pucSlaveId, unsigned char *pucFunction, unsigned char *pucData, int *iDataLen)
{
unsigned char *pucTmp = NULL;
int iLen = 0, iByteCount, iData, iVal, iTmp;
unsigned char aucBuffer[MODBUS_MAX_RESPONSE_LENGTH] = {0};
int iSlave, iFunction;
unsigned char ucSlave, ucFunction;
unsigned char *pucStartBuf = NULL;
// check parameter.
if (NULL == pucSlaveId || NULL == pucFunction || NULL == pucData || NULL == iDataLen)
{
ModbusUartPrintf("Wrong Parameter!");
return DRIVER_ERROR_PARAMETER;
}
if ((cModbusMode != MODBUS_ASCII) && (cModbusMode != MODBUS_RTU))
{
ModbusUartPrintf("Wrong Parameter!");
return DRIVER_ERROR_MODBUS_MODE;
}
// init data.
*iDataLen = 0;
//read from device
iTmp = ModbusUartDataRead(aucBuffer, iReadDataLen, uiTimeOutMs);
if (iTmp < 0)
{
ModbusUartPrintf("DrivCommDeviceDataRead failed. and return=%d", iTmp);
ControlUartIrqEnable(3, DISABLE);
ModbusUartCleanCache();
ControlUartIrqEnable(3, ENABLE);
return iTmp;
}
aucBuffer[iTmp] = '\0'; //end the request string
ModbusUartPrintf("Read(%d):", iTmp);
//此处将modbus回过来的数据进行了打印。
for (iLen = 0; iLen < iTmp; iLen++)
{
DataPrintf("0x%02X ", aucBuffer[iLen]);
}
DataPrintf("\r\n");
if (MODBUS_ASCII == cModbusMode)
{
pucTmp = (unsigned char *) strchr((const char *) aucBuffer, ':');
if (NULL == pucTmp)
{
ModbusUartPrintf("[Modbus]Modbus Wrong Data Format, not find ':'.");
return DRIVER_ERROR_MODBUS_WRONG_DATA_FORMAT;
}
pucStartBuf = pucTmp;
iLen = 0;
iLen++;
sscanf((char *) &pucTmp[iLen], "%02X", &iSlave);
iLen += 2;
sscanf((char *) &pucTmp[iLen], "%02X", &iFunction);
iLen += 2;
ModbusUartPrintf("[Modbus]iSlave:%d, iFunction:%d.", iSlave, iFunction);
*pucSlaveId = (unsigned char)iSlave;
*pucFunction = (unsigned char)iFunction;
switch (iFunction)
{
case 2:
case 3: // response :data-length h-data l-date ...
case 4:
sscanf((char *) &pucTmp[iLen], "%02X", &iByteCount);
iLen += 2;
ModbusUartPrintf("[Modbus]iByteCount:%d.", iByteCount);
for (iData = 0; iData < iByteCount; iData++)
{
sscanf((const char *) &pucTmp[iLen], "%02X", &iVal);
iLen += 2;
pucData[iData] = iVal;
}
iTmp = LrcError((char *)pucStartBuf, iLen); //lrc check
if (iTmp < 0)
{
ModbusUartPrintf("[Modbus]LrcError check failed(%d).", iTmp);
return DRIVER_ERROR_MODBUS_LRC;
}
*iDataLen = iByteCount;
break;
case 5: // for MX28A and AO4A.
case 6: //function code :0x06 response :h-addr l-addr h-data l-date
case 16: //0x10: response :h-addr l-addr h-register numner l-register number
for (iData = 0; iData < 4; iData++)
{
sscanf((const char *) &pucTmp[iLen], "%02X", &iVal);
iLen += 2;
pucData[iData] = iVal;
}
iTmp = LrcError((char *)pucStartBuf, iLen); //lrc check
if (iTmp < 0)
{
ModbusUartPrintf("[Modbus]LrcError check failed(%d).", iTmp);
/* [add by jiangfeng.zhang, 2021-07-01] :数据接收出错,清缓存*/
#if 1
ControlUartIrqEnable(3, DISABLE);
ModbusUartCleanCache();
ControlUartIrqEnable(3, ENABLE);
#endif
return DRIVER_ERROR_MODBUS_LRC;
}
*iDataLen = iByteCount;
break;
default:
ModbusUartPrintf("[Modbus]Unsupport function(%d).", iFunction);
return DRIVER_ERROR_MODBUS_UNSUPPORT_FUNCTION;
}
}
else if (MODBUS_RTU == cModbusMode)
{
iLen = 0;
ucSlave = aucBuffer[iLen++];
ucFunction = aucBuffer[iLen++];
*pucSlaveId = ucSlave;
*pucFunction = ucFunction;
switch (ucFunction)
{
//这块区分,当时读PLC,功能码为03 4 254: 数据处理分支。
// 回的数据不包含设备地址,仅仅包含读取到的数据字节个数。
case 1:
case 2:
case 3:
case 4:
case 254: // 0xFE
iByteCount = aucBuffer[iLen++];
memcpy(pucData, &aucBuffer[iLen], iByteCount); //ByteCount
iLen += (iByteCount + 2); // with 2 bytes crc lens
iTmp = CrcError(aucBuffer, iLen); //crc check
if (iTmp < 0)
{
ModbusUartPrintf("[Modbus]LrcError check failed(%d).", iTmp);
return DRIVER_ERROR_MODBUS_LRC;
}
*iDataLen = iByteCount; // only return data lens
break;
//这块是写PLC,功能码为5 6 16,PLC回执的数据,回执数据里面包含了寄存器地址
case 5:
case 6: //function code :0x06 response :h-addr l-addr h-data l-date
case 16: //0x10: response :h-addr l-addr h-register numner l-register number
memcpy(pucData, &aucBuffer[iLen], 4);
iLen += (4 + 2); // (data length)+2(crc)
iTmp = CrcError(aucBuffer, 8); //crc check
if (iTmp < 0)
{
ModbusUartPrintf("[Modbus]LrcError check failed(%d).", iTmp);
return DRIVER_ERROR_MODBUS_LRC;
}
*iDataLen = 4;
break;
case 0xB8:
memcpy(pucData, &aucBuffer[iLen], 9); //ByteCount + 2(CRC)
iTmp = CrcError(aucBuffer, 11); //crc check
if (iTmp < 0)
{
ModbusUartPrintf("[Modbus]LrcError check failed(%d).", iTmp);
return DRIVER_ERROR_MODBUS_LRC;
}
*iDataLen = 7;
break;
default:
ModbusUartPrintf("[Modbus]Unsupport function(%d).", iFunction);
return DRIVER_ERROR_MODBUS_UNSUPPORT_FUNCTION;
}
}
return DRIVER_SUCCESS;
}
2.4、通信测试
/***********************************************************************
* 函数名称: TestModbusRtu
* 功能描述: Modbus Uart测试代码
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
int TestModbusRtu(void)
{
int iRet = 0;
int iRspLen = 0;
int iLen;
#define TEST_DEV_ID 0X01
unsigned char ucSlaveId;
unsigned char ucFunction;
//发送数组
unsigned char aucSendBuf[20];
unsigned int uiReqMsgLen = 0;
//接收数据
unsigned char aucRecvBuf[20];
unsigned int iRspMsgLen = 0;
iRspLen = ModbusRtuPacket03(TEST_DEV_ID, MODBUS_RTU, 0X00, 1, aucSendBuf, &uiReqMsgLen);
iRet = ModbusUartDataWrite(3, aucSendBuf, uiReqMsgLen);
iRet = ModbusRtuResponse(
MODBUS_RTU,
iRspLen, //设备地址+ 功能码+字节数目+数据+crc = 1+1+1+2+2 = 7
UART_READ_TIME_OUT,
&ucSlaveId,
&ucFunction,
aucRecvBuf,
&iRspMsgLen // 数据域中除数据地址外数据位有几个字节。
);
for (iLen = 0; iLen < iRspMsgLen; iLen++)
{
DataPrintf("0x%02X ", aucRecvBuf[iLen]);
}
DataPrintf("\r\n");
}
三、注册LetterShell测试命令
3.1、注册OEM命令
/******************************************************************************
* 注册Modbus RTU测试指令
******************************************************************************/
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus01, TestModbusFun01, modbus01 [slave id](1) address(2) regnum(2));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus02, TestModbusFun02, modbus02 [slave id](1) address(2) regnum(2));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus03, TestModbusFun03, modbus03 [slave id](1) address(2) regnum(2));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus04, TestModbusFun04, modbus04 [slave id](1) address(2) regnum(2));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus05, TestModbusFun05, modbus05 [slave id](1) address(2) writeValue(1));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), modbus06, TestModbusFun06, modbus06 [slave id](1) address(2) writeValue(2));
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), unitmodbustest, TestModbusRtu,unitmodbustest in while(1));
3.2、LetterShll测试命令的回调接口
/***********************************************************************
* 函数名称: TestModbusFun03
* 功能描述: 读AO
* 输入参数: 命令字(1) + slaveid(1)+start address(2)+regnum(2) = 6
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
int TestModbusFun03(int argc, char *agrv[])
{
int iRet = 0;
int iRspLen = 0;
int iLen;
unsigned char ucSlaveId;
unsigned char ucFunction;
//发送数组
unsigned char aucSendBuf[20];
unsigned int uiReqMsgLen = 0;
//接收数据
unsigned char aucRecvBuf[20];
unsigned int iRspMsgLen = 0;
unsigned short usStartAddress = 0;
unsigned short usRegNum = 0;
unsigned char ucTemData_H = 0;
unsigned char ucTemData_L = 0;
if (argc != 6)
{
ModuleShellPrintf("[user read ao]:\r\n modbus03 + slaveid(1) + start address(2) + regnum(2).\r\n");
ModuleShellPrintf("[exzample]:\r\n [modbus03] [01] [00 00] [00 01].\r\n\r\n\r\n");
return -1;
}
else
{
iRet = ZipStrToHex(agrv[1], strlen(agrv[1]), &ucSlaveId);
if (iRet < 0)
{
return -1;
}
iRet = ZipStrToHex(agrv[2], strlen(agrv[2]), &ucTemData_H);
if (iRet < 0)
{
return -1;
}
iRet = ZipStrToHex(agrv[3], strlen(agrv[3]), &ucTemData_L);
if (iRet < 0)
{
return -1;
}
usStartAddress = ((ucTemData_H << 8) & 0XFF00) | (ucTemData_L);
iRet = ZipStrToHex(agrv[4], strlen(agrv[4]), &ucTemData_H);
if (iRet < 0)
{
return -1;
}
iRet = ZipStrToHex(agrv[5], strlen(agrv[5]), &ucTemData_L);
if (iRet < 0)
{
return -1;
}
usRegNum = ((ucTemData_H << 8) & 0XFF00) | (ucTemData_L);
ModuleShellPrintf("[read ao][slaveid]:0x%x [start address]:0x%x [regnum]:0x%x.\r\n\r\n", ucSlaveId, usStartAddress, usRegNum);
}
/***********************************************************************************************/
iRspLen = ModbusRtuPacket03(ucSlaveId, MODBUS_RTU, usStartAddress, usRegNum, aucSendBuf, &uiReqMsgLen);
iRet = ModbusUartDataWrite(3, aucSendBuf, uiReqMsgLen);
iRet = ModbusRtuResponse(
MODBUS_RTU,
iRspLen, //设备地址+ 功能码+字节数目+数据+crc = 1+1+1+2+2 = 7
UART_READ_TIME_OUT,
&ucSlaveId,
&ucFunction,
aucRecvBuf,
&iRspMsgLen // 数据域中除数据地址外数据位有几个字节。
);
for (iLen = 0; iLen < iRspMsgLen; iLen++)
{
ModuleShellPrintf("0x%02X ", aucRecvBuf[iLen]);
}
if (iRspMsgLen <= 0)
{
ModuleShellPrintf("[ERROR] no response \r\n");
}
ModuleShellPrintf("\r\n\r\n\r\n");
/***********************************************************************************************/
return 0;
}