基于STM32实现MODBUS-RTU从机协议
准备材料
STM32CubeMX软件(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
合宙DAP下载器 DAPLink使用手册 - LuatOS 文档
Modbus Poll Modbus Master Simulator (modbustools.com)
STM32F103
任务
实现Modbus-RTU从机协议
配置工程
使用STM32CubeMX软件配置工程
串口1
Stop Bits 设置为 2
定时器 TIM2
代码
/**
* @brief 解析数据包. 轮流调用
* @param None
* @retval None
*/
void MODS_Poll(void)
{
uint16_t addr;
uint16_t crc1;
// 超过3.5个字符时间后执行MODH_RxTimeOut()函数。
// 全局变量 g_rtu_timeout = 1; 通知主程序开始解码
if (g_mods_timeout == 0)
{
/* 没有超时,继续接收。 */
return;
}
/* 清标志 */
g_mods_timeout = 0;
/* 接收到的数据小于4个字节就认为错误,地址(8bit)+指令(8bit)+操作寄存器(16bit) */
if (g_tModS.RxCount < 4)
{
goto err_ret;
}
/* 计算CRC校验 */
crc1 = CRC16_Modbus(g_tModS.RxBuf, g_tModS.RxCount);
if (crc1 != 0)
{
printf("%c", 'c');
goto err_ret;
}
/* 从站地址 (1字节) */
/* 第1字节 站号 */
addr = g_tModS.RxBuf[0];
/* 判断主机发送的命令地址是否符合 */
if (addr != SADDR485)
{
goto err_ret;
}
/* 分析应用层协议 */
MODS_AnalyzeApp();
err_ret:
{
g_tModS.RxCount = 0;
}
}
/**
* @brief 中断接收数据
* @param _byte 接收的数据
* @retval None
*/
void MODS_ReciveNew(uint8_t _byte)
{
uint8_t i;
/* 根据波特率,获取需要延迟的时间 */
for(i = 0; i < (sizeof(ModbusBaudRate)/sizeof(ModbusBaudRate[0])); i++)
{
if(SBAUD485 == ModbusBaudRate[i].Bps)
{
break;
}
}
g_mods_timeout = 0;
/* 硬件定时中断,3.5个字符的时间间隔*/
__HAL_TIM_SET_AUTORELOAD(&htim2, ModbusBaudRate[i].usTimeOut-1);
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
HAL_TIM_Base_Start_IT(&htim2);
if (g_tModS.RxCount < S_RX_BUF_SIZE)
{
g_tModS.RxBuf[g_tModS.RxCount++] = _byte;
}
}
/**
* @brief 接收数据帧超时
* @param _byte 接收的数据
* @retval None
*/
void MODS_RxTimeOut()
{
g_mods_timeout = 1;
}
/**
* @brief 发送数据, 自动追加2字节CRC
* @param _pBuf 待发送的数据包
* @param _ucLen待发送的数据包长度
* @retval None
*/
static void MODS_SendWithCRC(uint8_t *_pBuf, uint8_t _ucLen)
{
uint16_t crc;
uint8_t buf[S_TX_BUF_SIZE];
memcpy(buf, _pBuf, _ucLen);
crc = CRC16_Modbus(_pBuf, _ucLen);
buf[_ucLen++] = crc >> 8;
buf[_ucLen++] = crc;
HAL_UART_Transmit(&huart1, buf, _ucLen, 100);
}
/**
* @brief 发送错误应答
* @param _ucErrCode 错误码
* @retval None
*/
static void MODS_SendAckErr(uint8_t _ucErrCode)
{
uint8_t txbuf[3];
txbuf[0] = g_tModS.RxBuf[0]; /* 485地址 */
txbuf[1] = g_tModS.RxBuf[1] | 0x80; /* 异常的功能码 */
txbuf[2] = _ucErrCode; /* 错误代码(01,02,03,04) */
MODS_SendWithCRC(txbuf, 3);
}
/**
* @brief 发送正确的应答
* @param None
* @retval None
*/
static void MODS_SendAckOk(void)
{
uint8_t txbuf[6];
uint8_t i;
for (i = 0; i < 6; i++)
{
txbuf[i] = g_tModS.RxBuf[i];
}
MODS_SendWithCRC(txbuf, 6);
}
/**
* @brief 分析应用层协议
* @param None
* @retval None
*/
static void MODS_AnalyzeApp(void)
{
switch (g_tModS.RxBuf[1]) /* 第2个字节 功能码 */
{
case 0x01: /* 读取线圈状态 */
MODS_01H();
break;
case 0x02: /* 读取输入状态 */
MODS_02H();
break;
case 0x03: /* 读取保持寄存器 */
MODS_03H();
break;
case 0x04: /* 读取输入寄存器 */
MODS_04H();
break;
case 0x05: /* 强制单线圈 */
MODS_05H();
break;
case 0x06: /* 写单个保存寄存器 */
MODS_06H();
break;
case 0x10: /* 写多个保存寄存器 */
MODS_10H();
break;
default:
g_tModS.RspCode = RSP_ERR_CMD;
MODS_SendAckErr(g_tModS.RspCode); /* 告诉主机命令错误 */
break;
}
}
一些代码不想展示😏
评论区