环境

硬件

1、NUCLEO-F030R8,芯片为 STM32F030R8。该板子 RAM 为 8KB,FLASH 为 64KB,主频最高为48MHz。
2、一台 Win10 的机器,运行 Modbus Poll。
3、一个 USB 转 232 TTL 电平的小板。请特别注意,我是使用 RS232 进行通信的。如果是 RS485 或者 RS422 需要在对收发进行分别处理。

软件

IAR EWARM 8.22.1 + Stm32CubeMX 6.3 + HAL 1.11.3 + freeModbus 最新版

前言

本来是想用 libModbus 3.1.6,因为我的主站用的是这个。但是尝试移植到裸奔系统的时候才发现,libModbus 3.1.6 是在 Linux 下使用的。所以只好放弃,重新回到 freeModbus。

freeModbus 移植需求

freeModbus 是一个很小开源的 Modbus 协议栈,支持 ascii,rtu 和 tcp 模式。移植 freeModbus 需要硬件支持包括如下:
1、一个串口。
2、一个定时器。用于产生 3.5T 时间中断。
3、Modbus 相关的回调函数。

使用 CubeMX 生产基本代码

时钟

NUCLEO-F030R8 板子没有晶振,最高的时钟频率为 484848MHz,我这里配置为 404040MHz。其实这个频率可以任意配置,没有什么特别的要求。

串口

我是用了 usart1,波特率什么都可以随便设置,因为在一直 freeModbus 的时候都会重新配置。只需要硬件使能串口,并打开中断即可。下面是 CubeMX 的配置截图。

下图为串口中断配置。

主要目的是让 CubeMX 自动生成对应的代码。注意串口中断的优先级设置为最高。
你也可以不配置自动生成中断代码,如果这样配置,所有串口中断代码需要自己完成。因此我就选择配置自动生成中断代码。

定时器

任意选择一个 Timer 都可以。这里我选择了 TIM6,没有什么特别原因,就是它简单。配置如图。

也是随便配置一下。同样,在移植 freeModbus 的时候,会将 TIM6 重新配置的。
下图是 TIM6 中断配置。

同样注意 TIM6 的优先级设置为 222,比 USART1 的优先级低。
你也可以不配置自动生成中断代码,如果这样配置,所有串口中断代码需要自己完成。因此我就选择配置自动生成中断代码。

生成代码

这样配置完成后,生成代码即可。

freeModbus 移植

拷贝代码

我遵守 CubeMX 的方式,也就是增加了 Middlewares 目录。然后将 freeModbus-master 压缩包中的 modbus 目录拷贝过来即可。对应的目录结构和文件如下图。

port 目录

根据 freeModbus 官方文档定义,移植相关代码建议放在 port 目录下。建议参考 freeModbus-master/demo/bare 目录。
在该目录下有:文件 demo.c,该文件告诉你 main() 函数应该写什么,modbus 对应的回调函数应该如何实现。子目录 port 下有 444 个文件:
port.h
portevent.c 事件相关的移植文件
portserial.c 串口相关的移植文件
porttimer.c 定时器相关的移植文件
我就是将 bare 目录的 port 直接拷贝过来。具体的目录结构参考上图。

port.h

该文件不需要什么改动,只需要增加对应的板子头文件即可。我增加了如下代码:

#include "stm32f0xx_hal.h"

portevent.c

这个文件我没有做任何修改。

portserial.c

vMBPortSerialEnable

该函数为串口使能函数。根据自己的板子对串口中断进行使能即可。
我的代码如下:

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if(xRxEnable == TRUE)__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);else__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);if(xTxEnable == TRUE)__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);else__HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
}

xMBPortSerialInit

该函数为串口初始化函数。根据自己的板子对串口初始化即可。
我的代码如下:

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{HAL_UART_DeInit(&huart1);//DEINIT cubeMX中的初始化配置(void)ucPORT;huart1.Instance = USART1;huart1.Init.BaudRate = ulBaudRate;huart1.Init.StopBits = UART_STOPBITS_1;switch (eParity)//使用校验位,就需要将uart的数据位配置为9位{case MB_PAR_ODD:huart1.Init.WordLength = UART_WORDLENGTH_9B;huart1.Init.Parity = UART_PARITY_ODD;break;case MB_PAR_EVEN:huart1.Init.WordLength = UART_WORDLENGTH_9B;huart1.Init.Parity = UART_PARITY_EVEN;break;default:huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.Parity = UART_PARITY_NONE;break;}huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){return FALSE;}return TRUE;
}

注意:
1、我是用的是 USART1,所以对应为 huart1,参考 usart.c。
2、由于 CubeMX 会对串口进行初始化,所以要先 DeInit。

xMBPortSerialPutByte

发送一个字节。

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(&huart1,(uint8_t*)&ucByte,1,1) == HAL_OK)return TRUE;elsereturn FALSE;
}

xMBPortSerialGetByte

接收一个字节。

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(&huart1,(uint8_t*)pucByte,1,1) == HAL_OK)return TRUE;elsereturn FALSE;
}

prvvUARTTxReadyISR

Tx Ready ISR。

void prvvUARTTxReadyISR( void )
{pxMBFrameCBTransmitterEmpty(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。

prvvUARTRxISR

Rx ISR。

void prvvUARTRxISR( void )
{pxMBFrameCBByteReceived(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。

porttimmer.c

xMBPortTimersInit

定时器初始化函数。

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{HAL_TIM_Base_DeInit(&htim6);TIM_MasterConfigTypeDef sMasterConfig = {0};/* USER CODE BEGIN TIM6_Init 1 *//* USER CODE END TIM6_Init 1 */htim6.Instance = TIM6;htim6.Init.Prescaler = 4499;//50us分频,这里使用的timer PCLK频率是40Mhzhtim6.Init.CounterMode = TIM_COUNTERMODE_UP;htim6.Init.Period = usTim1Timerout50us-1;//modbus 规定的TIMEOUT时间htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim6) != HAL_OK){return FALSE;}sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK){return FALSE;}return TRUE;
}

感觉这个定时器不需要特别的准确。随便给一个时间就可以了。

vMBPortTimersEnable

使能定时器中断。

inline void
vMBPortTimersEnable(  )
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);__HAL_TIM_SetCounter(&htim6,0);//这里一定要清零计数器HAL_TIM_Base_Start_IT(&htim6);
}

vMBPortTimersDisable

inline void
vMBPortTimersDisable(  )
{/* Disable any pending timers. */HAL_TIM_Base_Stop_IT(&htim6);__HAL_TIM_SetCounter(&htim6,0);__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
}

prvvTIMERExpiredISR

定时器结束中断。

void prvvTIMERExpiredISR( void )
{( void )pxMBPortCBTimerExpired(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 TIM 中断中调用了本函数。

中断函数

在stm32f0_xx_it.c 中。增加对应的代码。

串口中断

CubeMX 生成的函数为 USART1_IRQHandler。增加的代码如下:

  /* USER CODE BEGIN USART1_IRQn 0 */uint8_t tmp;if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_PE))//奇偶校验位判断{HAL_UART_Receive(&huart1,&tmp,1,1);}else if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_RXNE)){prvvUARTRxISR();}if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_TXE)){prvvUARTTxReadyISR();}/* USER CODE END USART1_IRQn 0 */

以上的代码是处理串口接收数据。

定时器中断

CubeMX 生成的函数为 TIM6_IRQHandler。增加的代码如下:

  /* USER CODE BEGIN TIM6_IRQn 1 */prvvTIMERExpiredISR();/* USER CODE END TIM6_IRQn 1 */

回调函数

由于 freeModbus 采用了回调函数的方法来实现具体的功能。所以我们必须实现对应的协议回调函数。
这部分代码的实现,可以参考 freeModbus-master/demo 目录中任意一个实现。

头文件

必须包含 mb.h。

#include "mb.h"

初始化

主要是定义开始地址,数据缓存区之类。

#define REG_INPUT_START     0x0001U //寻址地址是从1开始的
#define REG_INPUT_NREGS 4#define REG_HOLDING_START               ( 1 )
#define REG_HOLDING_NREGS               ( 32 )
/* ----------------------- Static variables ---------------------------------*/
static uint16_t   usRegInputStart = REG_INPUT_START;
static uint16_t   usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];

eMBRegInputCB

Read Input Register。响应功能码 0x04。

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;
}

eMBRegHoldingCB

Write Holding Register。响应功能码 0x06。

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if( ( usAddress >= REG_HOLDING_START ) && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) ){iRegIndex = ( int )( usAddress - usRegHoldingStart );switch ( eMode ){case MB_REG_READ:while( usNRegs > 0 ){*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}break;case MB_REG_WRITE:while( usNRegs > 0 ){usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}}}else{eStatus = MB_ENOREG;}return eStatus;
}

eMBRegCoilsCB

Read Coils。响应功能码 0x01。

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{return MB_ENOREG;
}

也就是说我暂时没有实现。

eMBRegDiscreteCB

Read Discrete Inputs。响应功能码 0x02。

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{return MB_ENOREG;
}

其他相关功能码

其参考 mb.c。代码如下:

static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0{MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0{MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0{MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0{MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0{MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0{MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0{MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0{MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};

freeModbus 支持的功能码

参考 mbproto.h。支持的功能码如下:

#define MB_FUNC_READ_COILS                    (  1 )
#define MB_FUNC_READ_DISCRETE_INPUTS          (  2 )
#define MB_FUNC_WRITE_SINGLE_COIL             (  5 )
#define MB_FUNC_WRITE_MULTIPLE_COILS          ( 15 )
#define MB_FUNC_READ_HOLDING_REGISTER         (  3 )
#define MB_FUNC_READ_INPUT_REGISTER           (  4 )
#define MB_FUNC_WRITE_REGISTER                (  6 )
#define MB_FUNC_WRITE_MULTIPLE_REGISTERS      ( 16 )
#define MB_FUNC_READWRITE_MULTIPLE_REGISTERS  ( 23 )
#define MB_FUNC_DIAG_READ_EXCEPTION           (  7 )
#define MB_FUNC_DIAG_DIAGNOSTIC               (  8 )
#define MB_FUNC_DIAG_GET_COM_EVENT_CNT        ( 11 )
#define MB_FUNC_DIAG_GET_COM_EVENT_LOG        ( 12 )
#define MB_FUNC_OTHER_REPORT_SLAVEID          ( 17 )

代码运行效果

04 命令测试


注意上面代码中

static uint16_t   usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值

06 命令测试

03 命令测试

其实代码收到 03 命令就直接返回了。但是从协议的角度。报文时正常的。

STM32F030R8移植freeModbus协议栈相关推荐

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

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

  2. 移植FreeModbus

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

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

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

  4. 基于STM32F103移植canfestival协议栈(从站)CANopen

    CAN open是一个基于CAN串行总线的网络传输系统的应用层协议,遵循ISO/OSI协议.CAN现场总线只是实现了OSI七层架构中的物理层和数据链路层,而canopen协议是基于他之上的一个应用层协 ...

  5. STM32F103/107 移植Freemodbus RTU

    1.简介 FreeMODBUS一个奥地利人写的Modbus协议.它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植.Modbus是一个工业制造环境中应用的一个通用协议.Modbus通 ...

  6. 单片机移植freemodbus从机(STM32、GD32、瑞萨、国民技术等)

    从github下载:https://github.com/cwalter-at/freemodbus 无法下载或者下载太慢可以用资源下载,无需积分.[freeModbus从机源码下载] 示例代码 一. ...

  7. STM32 移植FreeModbus详细过程

    modbus是一个非常好的串口协议(当然也能用在网口上),它简洁.规范.强大.可以满足大部分的工业.嵌入式需求.我写的这个四个寄存器都可以用(输入寄存器.保持寄存器.线圈寄存器.离散寄存器).不像别的 ...

  8. STM32F103移植FreeModbus实现ModbusRTU和ModbusTCP同时工作

    移植过程比较繁琐就不细说了,说一下注意点就行 一.共用函数 不管是TCP还是RTU有些函数是共用的,比如: eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBu ...

  9. FreeModbus的移植

    FreeModbus V1.6 主机使用说明 一.简述 FreeModbus是一款开源的Modbus协议栈,但是只有从机开源,主机源码是需要收费的.同时网上也没有发现比较好的开源的Modbus主机协议 ...

  10. FreeModbus V1.6 主机

    FreeModbus是一款开源的Modbus协议栈,但是只有从机开源,主机源码是需要收费的.同时网上也没有发现比较好的开源的Modbus主机协议栈,所以才开发这款支持主机模式的FreeModbus协议 ...

最新文章

  1. 什么是互联网大厂_仅限今天!大厂最热数据分析经典实战项目大公开!
  2. piwik的安装与配置
  3. 斐波那契问题的递归和动态规划
  4. 分享:我是怎么在github上找到优秀的仓库的?
  5. 驳《从团购网站看中国人的创新精神》
  6. MySQL——查询数据库、数据表总记录数
  7. 直接拿来用!GitHub 标星 5000+,学生党学编程有这份资料就够了
  8. 简单粗暴Tensorflow 2.0
  9. ftp搜索文件_CrossFTP for Mac(FTP客户端)
  10. 剑指offer-面试题37:序列化二叉树及二叉树的基本操作和测试
  11. 考研高等数学张宇30讲笔记——第九讲一元函数积分学的几何应用
  12. java itext 页边距_iText的用法
  13. 剑指offer第二版(C++实现)
  14. Maven项目中父子项目互相找不到的解决方法
  15. python修改图片尺寸
  16. HIVE厂牌艺人_北京音乐节-北京音乐节全攻略 - 马蜂窝
  17. Noteability和GoodNotes日常使用细节对比
  18. 语音识别软件_语音识别 软件_日语语音识别软件 - 云+社区 - 腾讯云
  19. 如何利用巨象指纹浏览器在twitter上找到精准客户
  20. VI 之快速查找定位

热门文章

  1. PRCS-1007 : Server pool egapdb already exists
  2. Win10正式版历代记
  3. AIX平台安装python
  4. python 下载mp4视频 实例
  5. 电容式计算机键盘的按键下,计算机键盘开关
  6. 微信小程序注册流程图文详解
  7. LAMP 技术简介(1)
  8. 半次元热门图片,各种好看的cosplay小姐姐,统统爬取收藏
  9. 算法-枚举法-已知xyz + yzz = 532,其中x、y、z都是数字(0~9),编写一个程序求出x、y、z分别代表什么数字。
  10. LVS之ipvsadm命令