源码获取
1、小飞哥gitee仓库自提
2、留言区获取资料链接
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是一个很不错的串口解析框架,下节小飞哥着重介绍下这部分
很晚啦,今天就到这里了