从github下载:https://github.com/cwalter-at/freemodbus

无法下载或者下载太慢可以用资源下载,无需积分。[freeModbus从机源码下载]

示例代码

一、使用资源需求

要实现FreeModbus的移植,必须在硬件上符合FreeModbus的使用要求。FreeModbus对硬件的需求很小,基本上任何具有支持接收与发送中断的串口,一个能够产生RTU传输所需的T35定时器,并且有能够容纳数据帧的RAM(实际的内存需求取决于所使用的地址的多少)的微控制器就足够了。而在软件上,对于实现FreeModbus,仅仅需要一个简单的事件队列。在使用操作系统情况下,可通过单独创建一个任务完成Modbus事件的轮询。

下面以STM32f103为例做移植参考。

二、单片机工程创建

使用STM32CubeMX创建一个新的工程,我们在这里主要使用的是串口2。

然后我这里配置使用了FREERTOS。具体配置就不详细描述,网上也有很多教程案例。或者可以下载我上传的资源进行对照。不关心这一步的也可以直接移植到裸机程序工程中。

资源中的CUBE文件

生成的MDK_V5工程

三、源代码移植

将FreeModbus从机源代码拷贝到工程中间件-第三方库-freemodbus源码库:即Middlewares\Third_Party\FreeModbusSlaver文件夹中。

将源码添加到工程中

头文件包含

3.1 源码接口完善

FreeModbus的移植主要包含:物理层接口的修改、应用层回调的修改。具体函数如下:

3.1.1 物理层接口:

(a) portserial.c
1)xMBPortSerialInit()
2)vMBPortSerialEnable()
3)xMBPortSerialPutByte()
4)xMBPortSerialGetByte()
(b) porttimer.c
1)xMBPortTimersInit()
2)vMBPortTimersEnable()
3)vMBPortTimersDisable()

3.1.2 应用层回调:

1)eMBRegCoilsCB()
2)eMBRegDiscreteCB()
3)eMBRegInputCB()
4)eMBRegHoldingCB()

3.1.3 具体修改代码

串口设置
1) void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)

此函数的功能为设置串口状态。有两个参数:xRxEnable及xTxEnable。当xRxEnable为真时,应使能串口接收;当xTxEnable为真时,应使能串口发送。

2) BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)

此函数的功能是初始化UART。有四个参数:ucPORT、ulBaudRate、ucDataBits及eParity。参数ucPORT可以忽略;参数ulBaudRate是波特率;参数ucDataBits为通讯时所使用的数据位宽,采用RTU模式,因此将其固化;eParity为校验方式,采用无校验方式,函数返回值务必为TRUE。
在这里我们如果采用的是STM32Cube生成的代码,无需在这了初始化,只需要直接返回TRUE即可,当然也可以将串口初始化移至此处。

3) BOOL xMBPortSerialPutByte(CHAR ucByte)与BOOL xMBPortSerialGetByte( CHAR * pucByte )

BOOL xMBPortSerialPutByte(CHAR ucByte)的功能为发送一字节数据。

/** FreeModbus Libary: ATMega168 Port* Copyright (C) 2006 Christian Walter <wolti@sil.at>*   - Initial version and ATmega168 support* Modfications Copyright (C) 2006 Tran Minh Hoang:*   - ATmega8, ATmega16, ATmega32 support*   - RS485 support for DS75176** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the Free Software* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA** File: $Id$*/#include "usart.h"#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"#define UART_BAUD_RATE          9600/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{if (xRxEnable) {__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);   }else{__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);}if (xTxEnable){__HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE);}else{__HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE);}
}BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{/*¿ÉÔÚ´Ë´¦Ìí¼Ó´®¿Ú³õʼ»¯º¯ÊýMX_USART2_UART_Init();Ò²¿ÉÒÔÔÚmainº¯Êý¼¯Öгõʼ»¯*/return TRUE;
}BOOL
xMBPortSerialPutByte( CHAR ucByte )
{/* Put a byte in the UARTs transmit buffer. This function is called* by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been* called. */if(HAL_UART_Transmit (&huart2 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )return FALSE ;elsereturn TRUE;
}BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{/* Return the byte in the UARTs receive buffer. This function is called* by the protocol stack after pxMBFrameCBByteReceived( ) has been called.*/if(HAL_UART_Receive (&huart2 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )return FALSE ;elsereturn TRUE;
}/* Create an interrupt handler for the transmit buffer empty interrupt* (or an equivalent) for your target processor. This function should then* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that* a new character can be sent. The protocol stack will then call * xMBPortSerialPutByte( ) to send the character.*/
void prvvUARTTxReadyISR( void )
{pxMBFrameCBTransmitterEmpty(  );
}/* Create an interrupt handler for the receive interrupt for your target* processor. This function should then call pxMBFrameCBByteReceived( ). The* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the* character.*/
void prvvUARTRxISR( void )
{pxMBFrameCBByteReceived(  );
}#ifdef RTS_ENABLE
SIGNAL( SIG_UART_TRANS )
{RTS_LOW;
}
#endif
定时器设置
5) BOOL xMBPortTimersInit( USHORT usTim1Timerout )

此函数功能为初始化定时器。参数为:usTim1Timerout。根据所使用的硬件初始化定时器,使之能产生中断时间为usTim1Timerout个时钟的中断。

6) void vMBPortTimersEnable()与void vMBPortTimersDisable()

void vMBPortTimersEnable()的功能为使能超时定时器。需在此清零定时器计数值,并重新计数。
void vMBPortTimersDisable()的功能为关闭超时定时器。需在此函数中关闭定时器中断。

/** FreeModbus Libary: ATMega168 Port* Copyright (C) 2006 Christian Walter <wolti@sil.at>** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the Free Software* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA** File: $Id$*//* ----------------------- AVR includes -------------------------------------*/
#include "tim.h"/* ----------------------- Platform includes --------------------------------*/
#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{/* Calculate overflow counter an OCR values for Timer1. *//*¿ÉÔÚ´Ë´¦Ìí¼ÓTIM6³õʼ»¯º¯ÊýMX_TIM6_Init();Ò²¿ÉÒÔÔÚmainº¯Êý¼¯Öгõʼ»¯*/return TRUE;
}inline void
vMBPortTimersEnable(  )
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);__HAL_TIM_ENABLE_IT(&htim6,TIM_IT_UPDATE);__HAL_TIM_SET_COUNTER(&htim6,0);__HAL_TIM_ENABLE(&htim6);
}inline void
vMBPortTimersDisable(  )
{/* Disable any pending timers. */__HAL_TIM_DISABLE(&htim6);__HAL_TIM_SET_COUNTER(&htim6,0);__HAL_TIM_DISABLE_IT(&htim6,TIM_IT_UPDATE);__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
}/* Create an ISR which is called whenever the timer has expired. This function* must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that* the timer has expired.*/
void prvvTIMERExpiredISR( void )
{( void )pxMBPortCBTimerExpired(  );
}
回调函数修改

Modbus在应用层实现上,采用功能码的形式对不同数据类型进行操作,具体功能码定义如图

mbSlaverCB.h

在回调函数头文件中定义
输入寄存器 起始地址 寄存器数量
保持寄存器 起始地址 寄存器数量
线圈寄存器 起始地址 寄存器数量

#define MB_SLAVER_TEST   1/* REGADDRS AND NUMBER DEFINE */
#define REG_INPUT_START             5000
#define REG_INPUT_NREGS             5#define REG_HOLDING_START          5400
#define REG_HOLDING_NREGS           5#define REG_COIL_START             5900
#define REG_COIL_NREGS              16#define REG_DISC_START_STANDARD     5000
#define REG_DISC_NREGS_STANDARD     48#define REG_DISC_START_EXPAND         5100
#define REG_DISC_NREGS_EXPAND       96#define REG_DISC_START_INNER      5400
#define REG_DISC_NREGS_INNER        32
mbSlaverCB.c

定义测试使用的输入寄存器,并初始化其值
static uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x11, 0x22, 0x33, 0x44, 0x55};

#include "mbSlaverCB.h"static uint16_t usRegInputStart = REG_INPUT_START;
#if MB_SLAVER_TESTstatic uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x11, 0x22, 0x33, 0x44, 0x55};
#elsestatic uint16_t usRegInputBuf[REG_INPUT_NREGS];
#endif/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if( ( usAddress >= REG_INPUT_START )&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ){iRegIndex = ( int )( usAddress - usRegInputStart );while( usNRegs > 0 ){*pucRegBuffer++ =( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );*pucRegBuffer++ =( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;if( ( usAddress >= REG_HOLDING_START )&& ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) ){switch(eMode){case MB_REG_READ:{break;}case MB_REG_WRITE:{break;}default:break;}}else{eStatus = MB_ENOREG;}return eStatus;
}eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode )
{return MB_ENOREG;
}eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{return MB_ENOREG;
}

以输入寄存器回函数为例,在上位机使用0x04功能码读输入寄存器时,整个Modbus的状态图如图2所示,在正确解析支持的功能码后进入eMBRegInputCB()函数,此函数所做的操作即为图中起始地址usAddress及usAddress+ usNRegs合法判断,并在合法的情况下处理读输入寄存器请求。代码如下:

同理,其它3个函数针对不同的功能码及起始地址及数量合法判断结果进行对应的操作,函数处理过程大致相同。

工程文件修改
stmf1xxit.c(中断文件)
/*** @brief This function handles USART2 global interrupt.*/
void USART2_IRQHandler(void)
{/* USER CODE BEGIN USART2_IRQn 0 *//* USER CODE END USART2_IRQn 0 */
//HAL_UART_IRQHandler(&huart2);/* USER CODE BEGIN USART2_IRQn 1 */if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE)){uint16_t pucByte = (uint16_t)((&huart2)->Instance->DR & (uint16_t)0x01FF);__HAL_UART_CLEAR_OREFLAG(&huart2);}if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET) {prvvUARTRxISR();       //½ÓÊÕÖжÏ}if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET) {prvvUARTTxReadyISR();   //·¢ËÍÖжÏ}if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)){__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);}if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC)){__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC);}HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
//  HAL_UART_IRQHandler(&huart2);/* USER CODE END USART2_IRQn 1 */
}
/*** @brief This function handles TIM6 global interrupt.*/
void TIM6_IRQHandler(void)
{/* USER CODE BEGIN TIM6_IRQn 0 *//* USER CODE END TIM6_IRQn 0 */HAL_TIM_IRQHandler(&htim6);/* USER CODE BEGIN TIM6_IRQn 1 *//* NOTE : This function Should not be modified, when the callback is needed,the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file*/prvvTIMERExpiredISR( );/* USER CODE END TIM6_IRQn 1 */
}
freertos.c

生成的项目文件将任务初始化都生成再freertos.c文件中,配置时STM32Cube工程时,配置了一个Mb_Slaver_Thread的任务。修改任务代码如下:

/* USER CODE END Header_Mb_Slaver_Thread */
void Mb_Slaver_Thread(void *argument)
{/* USER CODE BEGIN Mb_Slaver_Thread */eMBInit( MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);//初始化MODBUSeMBEnable(  );//使能modbus/* Infinite loop */for(;;){( void )eMBPoll(  );//启动MODBUS侦听osDelay(1);}/* USER CODE END Mb_Slaver_Thread */
}

注意:实际使用中可以将任务代码放在单独的文件中。此外如果不使用操作系统,可以在裸机程序中初始化调用。

四、FreeModbus初始化及运行流程

FreeModbus是通过检测相应的消息来完成对应的功能。协议栈的初始化及运行流程如下:

  1. 调用eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )完成物理层设备的初始化,主要包括:BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )串口初始化;BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )定时器初始化,设定T35定时所需要的定时器常数。
  2. 调用eMBErrorCode eMBEnable(void)使能协议栈,主要包括:static pvMBFrameStart pvMBFrameStartCur(函数指针)协议栈开始,将eRcvState设为STATE_RX_INIT状态,调用void vMBPortSerialEnable(BOOL xRxEnable,BOOL xTxEnable)使能接收,调用void vMBPortTimersEnable()使能超时定时器。
  3. Modbus_App任务调用eMBPoll(void)轮询事件。
  4. 若发生串口接收中断,且eRcvState为STATE_RX_IDLE,则向接收缓存中存入接收到的字符,同时将eRcvState设为STATE_RX_RCV状态,并清零超时定时器。在下一个数据来到时,不断将数据存入接收缓存,并清零超时定时器。
  5. 如果没有接收完成,则不可能发生超时中断。发生超时中断,说明T35时间内未收到新的串口数据,根据Modbus协议的规定,这指示着一帧请求数据接收完成。接收完成后发送消息EV_FRAME_RECEIVED,等待处理此消息。
  6. eMBPoll(void)检测到EV_FRAME_RECEIVED,调用peMBFrameReceiveCur获得从机地址、数据及数据长度。
  7. 从数据帧中获取功能码,根据相应的功能码查找处理该功能码的函数指针来处理该功能。若不是广播消息,则调用peMBFrameSendCur发送回复消息。

五、FreeModbus验证

使用modbus上位机mbpoll轮训输入寄存器,并且能够修改其值,验证成功。

(此处提供工具示意,写文档时连接使用)

六、示例代码

STM32F103ZET6_TESTCODE

单片机移植freemodbus从机(STM32、GD32、瑞萨、国民技术等)相关推荐

  1. 瑞萨单片机CAN口复用唤醒

    瑞萨单片机CAN口复用唤醒 瑞萨单片机睡眠模式 CAN总线唤醒的原理 外部GPIO口唤醒步骤 注意事项 目前笔者在公司实习接触到了瑞萨单片机 经过了一个月的踩坑已经对瑞萨单片机的开发有了一定的了解,现 ...

  2. 基于STM32单片机的差分升级(增量升级)算法移植手册V.3 STM32+BSDiff+LZ77

    基于STM32单片机的差分升级算法移植手册V.3 STM32+BSDiff+LZ77 更新时间:2022-03-10 版本V1.3 同步更新如下: 基于STM32单片机的差分升级(增量升级)算法V1. ...

  3. 手把手教你移植FreeModbus到STM32【看评论区引导,领取全套资料包】

    为什么要移植freemodbus 大家好,近期由于一个小项目的需要,要用到Modbus协议进行通信.相信各位工作的小伙伴们,或多或少都要跟Modbus打交道吧.那么,Modbus协议的重要性我自不必多 ...

  4. 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇

    具体Freemodbus移植到STM32步骤参考: STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤 基于STM32HAL库移植FreeModbus FreeModbu ...

  5. STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用)

    STM32CubeMX | Modbus RTU 主机协议栈实现 目录 1.前言 2.协议栈API介绍 2.1 控制结构 2.2 主机读线圈状态(CMD1) 2.2 主机读离散量输入(CMD2) 2. ...

  6. 移植FreeModbus

    freeemodbus百度百科(介绍比较详细) https://baike.baidu.com/item/freemodbus/7566841?fr=aladdin STMC2CubeMX | STM ...

  7. 瑞萨e2studio(17)----IIC,12864OLED移植

    瑞萨e2studio.17--IIC,12864OLED移植 概述 视频教学 csdn课程 样品申请 完整代码下载 硬件准备 新建工程 工程模板 保存工程路径 芯片配置 工程模板选择 时钟配置 IIC ...

  8. 如何从一款单片机移植到另一款单片机

    如何从一款单片机移植到另一款单片机 目录 如何从一款单片机移植到另一款单片机 前言 1 硬件移植 2 软件移植 2.1 移植原因 2.2 移植原理 2.3 固件库之间的移植方法 2.3 非固件库之间的 ...

  9. 主题:瑞萨16 位R8C/TINY 系列单片机

    主题:瑞萨16 位R8C/TINY 系列单片机– 小型,少脚数,高速,高效能,含闪存的MCU 在线问答: [问:winalltech] WDT通过软件来开关,有可能不可靠吗?会因为干扰而造成其突然关闭 ...

最新文章

  1. 32位浮点数在威纶触摸屏显示_MCGS触摸屏与与西门子 S7-1200 PLC以太网通讯
  2. C# 获取字符串长度 获取字符串字节长度
  3. 1088 最长回文子串
  4. 男生的哪个“不要”是真的不要?
  5. python 读写文件 另存为_python读写文件(五)
  6. Spring框架----AOP的概念及术语
  7. 如何装系统,如何装kali linux系统
  8. Nodejs抓取非utf8字符编码的页面
  9. 如何在旧 Mac 或 MacBook 上安装 Chrome 操作系统?
  10. lempel ziv matlab,精讲Lempel-Ziv压缩算法
  11. USB虚拟串口(CDC)极限速度测试
  12. GIS-测绘学概论(5)
  13. JPA 查询 未查询到时返回的list的size为0 但是引用并不为空
  14. 记录docker自定义easyswoole镜像
  15. 天津大学大学计算机基础成绩查询,天津大学《大学计算机基础1》课程教学大纲.PDF...
  16. 安卓 android
  17. ext4 笔记一(与ext3比较)
  18. 自我健康检测及产品应用方案
  19. GEA 3.1 重温C++以及实践
  20. HCIP(华为高级网络安全工程师)(第五天)(OSPF协议1)

热门文章

  1. 弘辽科技:淘宝关键词权重该如何提升?商家要记住的4大必要因素
  2. Mathjax语法总结
  3. android混淆保留内部类,混淆jar包总结
  4. 「建议收藏」Pycharm使用教程(非常详细,非常实用)
  5. java几大特性_关于java三大特性的总结
  6. Linux常用命令——startx命令
  7. mysql 下载安装教程以及密码初始化
  8. CPU load 高占用率低问题的彻底排查
  9. JS图片轮播切换效果实现
  10. JavaScript—动画