modbus从机的实现(STM32F1平台_RTU模式)

最近工作中需要用到modbus通信,在查阅了相关资料后在stm32f1中实现了符合要求的modbus协议。因为我的主机只需对保持寄存器(RW)进行单个或多个寄存器的读写,所以只需要实现对0x03(读寄存器)、0x06(写单个寄存器)、0x10(写多个寄存器)这三个功能码的响应。

我们首先要知道modbus的命令帧结构如下:

从机地址功能码DataCRC高字节CRC低字节
address8BitN*8Bit8Bit8Bit

然后分析下modbus的工作流程:
1、从机初始化
2、主机发送命令帧
3、从机开启定时器并解析收到的命令帧作出相应

将流程转化成如下代码:

rs485收发这两个宏根据需要自行更改
#define RS485_RX  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET)
#define RS485_TX  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET)
#define BUF_MAXSIZE  100 串口收发缓冲区大小
#define REG_MAXSIZE  32	 只读寄存器缓冲区大小

#define undefined_func_code   0x01  //未定义功能码,询问中接收到的功能码是不可允许的操作
#define illegal_reg_address   0x02  //非法的寄存器数据地址,接收到的数据地址是不可允许的地址
#define illegal_reg_data   0x03     //非法数据值,寄存器中被提交存储的数据项有一个应用程序期望之外的值
#define slave_device_busy  0x04     //从设备忙,正在处理上一个请求

modbus所处状态声明
typedef enum {
    mb_init_flag = 0,
    mb_idle_flag,
    mb_recv_busy_flag,
    mb_data_prasing_flag
}   mb_work_flag_t;  

typedef struct {
    uint8_t address;                //本机作为从机的地址
    mb_work_flag_t work_flag;       //工作状态
    uint8_t recvbuf[BUF_MAXSIZE];    //接收缓存区
    uint8_t sendbuf[BUF_MAXSIZE];    //发送缓存区
    uint32_t regbuf[REG_MAXSIZE];     //寄存器缓存区

    uint8_t  frame_timeout_flag;    //帧间隔超时状态 大于 3.5个字符 = frame_interval(us)
    uint32_t frame_interval;        //定时器溢出中断时间
	uint8_t  tim_cnt;               //tim_cnt*定时器溢出中断时间 = 3.5个字符时间

} modbus_t;
定义一个全局结构体modbus:
modbus_t modbus = { 0, mb_init_flag, {0}, {0}, {0}, 0, 0, 0 }; 
 从机初始化函数:slave_addr(从机地址)、huart(收发命令帧的串口)、tim(处理定时器)、p_mb(指向modbus_t的全局变量)
int modbus_init(uint8_t slave_addr, UART_HandleTypeDef * huart, TIM_HandleTypeDef * tim, modbus_t * p_mb)
{   
    if(p_mb->work_flag == mb_init_flag)
    {
        if(slave_addr < 1 || slave_addr > 247)
            return -1; 

        p_mb->address = slave_addr;
        p_mb->sendbuf[1] = slave_addr;

        p_mb->frame_interval = 38500000/(huart->Init.BaudRate)/(tim->Init.Period);  
        if(p_mb->frame_interval < 1)
            p_mb->frame_interval = 1;
        
        从机开启侦听,关闭发送
        RS485_RX;
        //__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC | UART_IT_RXNE);
        __HAL_UART_DISABLE_IT(huart, UART_IT_TXE|UART_IT_TC);
        __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
				
				__HAL_TIM_DISABLE(tim);
				__HAL_TIM_SET_COUNTER(tim, 0);
				__HAL_TIM_ENABLE_IT(tim, TIM_IT_UPDATE);
				
        p_mb->frame_timeout_flag = 1;
        p_mb->work_flag = mb_idle_flag;
        return 0;
    }

    return -1;
}
命令帧处理函数:(放在对应的串口中断中)
void mb_recv_handler(UART_HandleTypeDef * huart, TIM_HandleTypeDef * tim, modbus_t * p_mb)
{
    uint8_t u8temp;
    
    if(__HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE))
    {
        __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_RXNE);
        if(p_mb->work_flag == mb_idle_flag && p_mb->frame_timeout_flag == 1)
        {
            if(huart->Instance->DR == modbus.address) {
                p_mb->recvbuf[0] = 1;
                p_mb->recvbuf[p_mb->recvbuf[0]] = huart->Instance->DR;
                p_mb->work_flag = mb_recv_busy_flag;
                p_mb->frame_timeout_flag = 0;
							
                __HAL_TIM_DISABLE(tim);
                __HAL_TIM_SET_COUNTER(tim, 0);
                __HAL_TIM_ENABLE(tim);
							  
            }    
        }
        else if(p_mb->work_flag == mb_recv_busy_flag && p_mb->frame_timeout_flag == 0)
        {
            modbus.recvbuf[0]++;
            p_mb->recvbuf[p_mb->recvbuf[0]] = huart->Instance->DR;
			p_mb->tim_cnt = 0;
            __HAL_TIM_DISABLE(tim);
            __HAL_TIM_SET_COUNTER(tim, 0);
            __HAL_TIM_ENABLE(tim);
        }
        else if(p_mb->work_flag == mb_data_prasing_flag)
        {
            u8temp = huart->Instance->DR;
			UNUSED(u8temp);
        }
    }
    else if(__HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE))
    {
        huart->Instance->DR = (uint8_t)(*huart->pTxBuffPtr++ & 0x00FF);
        if(--huart->TxXferCount == 0) {
            __HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
            while (__HAL_UART_GET_FLAG(huart, UART_IT_TC) == SET);
            __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC);

            p_mb->work_flag = mb_idle_flag;
            RS485_RX;
			memset(p_mb->sendbuf, 0, BUF_MAXSIZE);
			__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
        }   
    }
}

定时器处理函数(放在定时器中断中)
void mb_tim_handler(modbus_t * p_mb, variable_t * p_var, UART_HandleTypeDef * huart, TIM_HandleTypeDef * htim)
{
    //static uint8_t cnt = 0;
		__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
    if(p_mb->work_flag == mb_idle_flag)
    {   
        if(p_mb->frame_timeout_flag == 0)
            p_mb->tim_cnt++;
        if(p_mb->tim_cnt >= p_mb->frame_interval) {
            p_mb->frame_timeout_flag = 1;
            p_mb->tim_cnt = 0;
        }
    }
    else if(p_mb->work_flag == mb_recv_busy_flag)
    {
        if(p_mb->frame_timeout_flag == 0)
            p_mb->tim_cnt++;
        if(p_mb->tim_cnt >= p_mb->frame_interval) {
            p_mb->tim_cnt = 0;
            p_mb->frame_timeout_flag = 1;
            if(p_mb->recvbuf[0] >= 8)      
                mb_data_prase(p_mb, p_var, huart);
            else 
                error_handle(p_mb, illegal_reg_data, huart);
        }
    }
}

命令帧解析以及相应发送
void mb_data_prase(modbus_t * p_mb, variable_t * p_var, UART_HandleTypeDef * huart)
{
    
    p_mb->work_flag = mb_data_prasing_flag;

    
    switch (p_mb->recvbuf[2])
    {
    case 0x03:
        read_holdregister(p_mb, p_var, huart);
        break;
    case 0x06:
        write_single_register(p_mb, p_var, huart);
        break;
    case 0x10:
        write_multi_register(p_mb, p_var, huart);
        break;
    default:
        error_handle(p_mb, undefined_func_code, huart);
        break;
    }
}

源码:https://github.com/WigenBi/my_modbus

总结: 完整实现了要求,工作稳定。不足的是程序耦合度比较高,如果将流程转换成有限状态机,然后用表驱动的写法程序可移植性会好很多。

  • 2
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在STM32F103微控制器上运行Modbus RTU标准协议程序,需要以下步骤: 第一步,准备开发环境。确保已经安装了适当的集成开发环境(IDE)和编译器,例如Keil或者IAR。安装并配置好所需的STM32F103C8T6开发板,以便与计算机进行通信。 第二步,导入Modbus库文件。在IDE中创建一个新项目,并将适用于STM32F103的Modbus RTU库文件导入项目中。这些库文件包含Modbus通信所需的函数和定义,可以从开源代码库或者官方网站上获取。 第三步,编写主程序。根据具体的应用需求,编写主程序来控制STM32F103的输入输出和Modbus通信功能。程序应包含Modbus RTU通信的初始化、数据读取和写入的功能。 第四步,配置串口通信。为Modbus RTU通信配置STM32F103的串口。设置波特率、数据位、停止位和奇偶校验等参数,以确保与Modbus从机设备之间的数据传输正常进行。 第五步,编译和下载程序。使用IDE中的编译器将程序编译成可执行的二进制文件。将二进制文件通过串口或者其他适配器下载到STM32F103开发板中。 第六步,测试运行程序。将STM32F103开发板连接到Modbus从机设备,并通过串口或者其他适配器进行通信。验证程序是否可以正确地读取和写入Modbus寄存器,并根据需求对STM32F103的输入输出进行控制。 通过按照上述步骤执行,即可在STM32F103微控制器上下载并运行支持Modbus RTU标准协议的程序。这将使得STM32F103能够与其他Modbus设备进行通信和数据交换。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值