源码获取

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

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

2、小飞哥gitee仓库自提

3、留言区获取资料链接

freemodbus是什么?

简介及应用场景

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

协议介绍

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

干货|10分钟教你玩转freemodbus_#include干货|10分钟教你玩转freemodbus_串口_02干货|10分钟教你玩转freemodbus_#include_03

硬件需求

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

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

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

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

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

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

干货|10分钟教你玩转freemodbus_#define_04

代码移植

源码下载(gitee地址):

​​

干货|10分钟教你玩转freemodbus_串口_05

或者这里下载:

​​

干货|10分钟教你玩转freemodbus_#define_06

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

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

干货|10分钟教你玩转freemodbus_串口_07干货|10分钟教你玩转freemodbus_#include_08干货|10分钟教你玩转freemodbus_#define_09

移植过程

1、新建工程

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

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

干货|10分钟教你玩转freemodbus_#include_10

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

干货|10分钟教你玩转freemodbus_串口_11干货|10分钟教你玩转freemodbus_#include_12干货|10分钟教你玩转freemodbus_#include_13干货|10分钟教你玩转freemodbus_串口_14干货|10分钟教你玩转freemodbus_#include_15

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

干货|10分钟教你玩转freemodbus_#include_16

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

至此,我们的源码就全部添加进来了干货|10分钟教你玩转freemodbus_#define_17

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

干货|10分钟教你玩转freemodbus_#define_18

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

接口编写

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

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

定时器配置:

干货|10分钟教你玩转freemodbus_#include_19串口配置:

干货|10分钟教你玩转freemodbus_串口_20

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

干货|10分钟教你玩转freemodbus_#include_21

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

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

干货|10分钟教你玩转freemodbus_#define_22主要有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;
}

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

干货|10分钟教你玩转freemodbus_#define_23

#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();
}

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

干货|10分钟教你玩转freemodbus_#include_24

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

modbus调试工具

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

干货|10分钟教你玩转freemodbus_#define_25

如何使用呢?

1、连接串口干货|10分钟教你玩转freemodbus_#include_26

2、选择功能码设置

干货|10分钟教你玩转freemodbus_串口_27

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

16:写多个寄存器:

干货|10分钟教你玩转freemodbus_串口_28

06:写单个寄存器:

干货|10分钟教你玩转freemodbus_串口_29

03:读保持寄存器

干货|10分钟教你玩转freemodbus_#include_30

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

很晚啦,今天就到这里了

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