10分钟教你玩起来freemodbus

源码获取

本节教程源码资料获取方式:

1、公众号后台回复“modbus”

2、小飞哥gitee仓库自提

3、留言区获取资料链接

freemodbus是什么?

简介及应用场景

FreeMODBUS是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植。Modbus是一个工业制造环境中应用的一个通用协议。Modbus通信协议栈包括两层:Modbus应用层协议,该层定义了数据模式和功能;另外一层是网络层。

协议介绍

FreeMODBUS 提供了RTU/ASCII 传输模式及TCP协议支持。FreeModbus遵循BSD许可证,这意味着用户可以将FreeModbus应用于商业环境中。版本FreeModbus-V1.5提供如下的功能支持(本次也是基于V1.5移植的):

硬件需求

FreeModbus协议对硬件的需求非常少——基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。

1、 一个异步串行接口,能够支持接收缓冲区满和发送缓存区空中断。

2、 一个能够产生RTU传输所需要的t3.5字符超时定时器的时钟。

对于软件部分,仅仅需要一个简单的事件队列。在使用操作系统的处理器上,可通过单独定义一个任务完成Modbus时间的查询。小点的微控制器往往不允许使用操作系统,在那种情况下,可以使用一个全局变量来实现该事件队列(Atmel AVR 移植使用这种方式实现)。

实际的存储器需求决定于所使用的Modbus模块的多少。

下表列出了所支持的功能编译后所需要的存储器。ARM是使用GNUARM编译器3.4.4使用-O1选项得到的。AVR项数值是使用WinAVR编译器3.4.5使用-Os选项编译得到的。

代码移植

源码下载(gitee地址):

https://gitee.com/tao_ji_peng/freemodbus

或者这里下载:

https://sourceforge.net/projects/freemodbus.berlios/files/

或者直接下载小飞哥的工程,包含freemodbus源码

下载完成之后,看下都包含那些东东...

移植过程

1、新建工程

老规矩,咱们还是“懒人秘籍”,放cubemx,开淦...

非常简单,简单到,过程就略了哈,只需要一个定时器,一个串口即可:

生成工程之后,添加freemodbus源码到我们的工程中,各个文件夹下对应的文件,怎么分组,自己能分得清即可:

port.c\port.h,modbus.c\modbus.h是源码中没有的

那我们去哪里找呢,没错,这两个文件其实是自己写的,一个是modbus的一些功能码实现,一个是移植接口要用到,前面提到的demo,我们找一个就可以了,例如MSP430 demo中,建议大伙直接copy小飞哥的就好啦~

至此,我们的源码就全部添加进来了

接下来做什么?没错,包含头文件:

到这里我们就把文件目录结构搭建起来了...你以为结束了,不,才刚刚开始...

接口编写

这部分说难也不难,说不难也有点费劲,总之,不太难...

首先我们来关注几个文件,前面介绍了,freemodbus只需要一个串口、一个定时器即可,工业上再加个485传输

定时器配置:

串口配置:

接下来我们关注几个文件:

port.c也比较简单,是进入临界区,任务保护用的,以及一些平台移植的类型重定义,代码比较简单:

/* ----------------------- System includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/

/* ----------------------- Variables ----------------------------------------*/
int             VIC_Temp;
/* ----------------------- Start implementation -----------------------------*/
void
EnterCriticalSection(  )
{
// __SETPRIMASK();
 __disable_irq();

}

void
ExitCriticalSection(  )
{
// __RESETPRIMASK();
 __enable_irq();
}

port.h

#ifndef _PORT_H
#define _PORT_H

#include "stm32f1xx_hal.h"
#include <assert.h>
#include <inttypes.h>

#define INLINE
#define PR_BEGIN_EXTERN_C           extern "C" {
#define PR_END_EXTERN_C             }

#define ENTER_CRITICAL_SECTION( ) EnterCriticalSection( )
#define EXIT_CRITICAL_SECTION( )    ExitCriticalSection( )

#define UARTISR_EN    (  1 )
#define SLAVE_RS485_SEND_MODE     HAL_GPIO_WritePin(GPIOA, RS485_DE_Pin, GPIO_PIN_SET)
#define SLAVE_RS485_RECEIVE_MODE    HAL_GPIO_WritePin(GPIOA, RS485_DE_Pin, GPIO_PIN_RESET)

extern TIM_HandleTypeDef htim7;
extern UART_HandleTypeDef huart1; 


void EnterCriticalSection(void);
void ExitCriticalSection(void);
void prvvUARTTxReadyISR(void);
void prvvUARTRxISR(void);
void TIMERExpiredISR(void);

typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char    CHAR;

typedef uint16_t USHORT;
typedef int16_t SHORT;

typedef uint32_t ULONG;
typedef int32_t LONG;

#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

#endif

然后是串口配置相关的,主要是串口接收中断、发送相关的开关操作:

主要有4个关键函数:

串口接收、发送中断的使能:

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
 if(xRxEnable)
 {
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
 }
 else
 {
  __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
 }

 if(xTxEnable)
 {
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_TC);
 }
 else
 {
  __HAL_UART_DISABLE_IT(&huart1, UART_IT_TC);
 }
}

如果结合485,该怎么写呢?

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
 if(xRxEnable)
 {
  SLAVE_RS485_RECEIVE_MODE;
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
 }
 else
 {
  SLAVE_RS485_SEND_MODE;
  __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
 }

 if(xTxEnable)
 {
  __HAL_UART_ENABLE_IT(&huart1,UART_IT_TC);
 }
 else
 {
  __HAL_UART_DISABLE_IT(&huart1, UART_IT_TC);
 }
}

串口的初始化与关闭:

void
vMBPortClose( void )
{
 __HAL_UART_DISABLE_IT(&huart1, UART_IT_TC|UART_IT_RXNE);
 __HAL_UART_DISABLE(&huart1);
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{ 
 __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
 __HAL_UART_ENABLE(&huart1);
  return TRUE;
}

数据的发送与接收:

不加485:

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
  USART1->DR = (ucByte);
 while (__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)==RESET){;}
  return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
 *pucByte =(uint8_t)(USART1->DR & (uint16_t)0x01FF);
   return TRUE;
}

配合485:

BOOL xMBPortSerialPutByte(CHAR ucByte)
{
 HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);
 USART1->DR = (ucByte);
 while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET)
 {
  ;
 }
 HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);
 return TRUE;
}

BOOL xMBPortSerialGetByte(CHAR *pucByte)
{
 *pucByte = (uint8_t)(USART1->DR & (uint16_t)0x01FF);
 return TRUE;
}

接下来是定时器相关的接口,很朴实无华...

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
 __HAL_TIM_SET_AUTORELOAD(&htim7, 50 * usTim1Timerout50us-1);
  return TRUE;
}


void
vMBPortTimersEnable(  )
{ 
 __HAL_TIM_SET_COUNTER(&htim7,0);
 HAL_TIM_Base_Start_IT(&htim7);
}

void
vMBPortTimersDisable(  )
{
 __HAL_TIM_SET_COUNTER(&htim7,0);
 HAL_TIM_Base_Stop_IT(&htim7);
}


void
TIMERExpiredISR( void )
{
    (void)pxMBPortCBTimerExpired();
}

最后,我们只需要编写定时器回调及串口回调即可啦

/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
#if UARTISR_EN == 1
 if (__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET)
 {
  prvvUARTRxISR();    
 }
 if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TC) != RESET)
 {
  prvvUARTTxReadyISR();  
 }
#else 
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
#endif
  /* USER CODE END USART1_IRQn 1 */
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 TIMERExpiredISR();
}

至此,整个工程就基本接近尾声了,最最后一点就是初始化调用啦:

至于函数的内层含义,小飞哥后面再专门剖析,敬请关注小飞哥

modbus调试工具

给大家介绍个非常好用的modbus调试软件,MODBUS POLL,这个工具非常好用,小飞哥从开始用modbus就是用的这个工具

如何使用呢?

1、连接串口

2、选择功能码设置

接下来我们来验证几个功能码:

16:写多个寄存器:

06:写单个寄存器:

03:读保持寄存器

就不再一一介绍啦,今天的介绍就到这里啦,主要讲的是如何移植、使用,并没有讲解内部的实现逻辑,其实MODBUS是一个很不错的串口解析框架,下节小飞哥着重介绍下这部分

很晚啦,今天就到这里了

认识小飞哥和他的小伙伴们

freemodbus是一个开源的Modbus通信协议栈,用于在各种设备之间进行数据通信。它提供了一套功能丰富且易于使用的接口,使用户能够轻松地实现Modbus通信。 要将freemodbus移植到特定的设备上,首先需要了解该设备的硬件平台和操作系统。根据设备的处理器架构和系统支持的编程语言,选择合适的版本和构建工具。 在移植过程中,需要根据设备的硬件特性进行一些必要的修改和适配。可能需要调整串口驱动、定时器、中断处理等相关代码,以确保freemodbus能够与设备的硬件和操作系统进行正常的交互。 接下来,需要配置freemodbus的参数,包括Modbus通信协议的相关设置,如从站地址、波特率、数据位、停止位等,以及Modbus功能码和寄存器地址的映射关系。 在移植完成后,还需要编写应用程序代码来使用freemodbus接口进行数据通信。根据具体需求,可以实现Modbus从站或主站的功能,读取或写入寄存器数据。在应用程序中,还可以处理Modbus报文的接收和发送,以及处理异常情况和错误码。 最后,进行测试和调试,确保freemodbus在目标设备上能够正常运行。可以通过模拟Modbus从站或主站的形式进行测试,验证通信的可靠性和正确性。 总之,freemodbus的移植程包括了选择合适的版本和构建工具、适配硬件和操作系统、配置参数、编写应用程序和进行测试等多个步骤。通过按照这些步骤进行移植,可以在特定的设备上成功实现Modbus通信功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小飞哥玩嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值