STM32F030R8移植freeModbus协议栈
环境
硬件
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协议栈相关推荐
- 手把手教你移植FreeModbus到STM32【看评论区引导,领取全套资料包】
为什么要移植freemodbus 大家好,近期由于一个小项目的需要,要用到Modbus协议进行通信.相信各位工作的小伙伴们,或多或少都要跟Modbus打交道吧.那么,Modbus协议的重要性我自不必多 ...
- 移植FreeModbus
freeemodbus百度百科(介绍比较详细) https://baike.baidu.com/item/freemodbus/7566841?fr=aladdin STMC2CubeMX | STM ...
- 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇
具体Freemodbus移植到STM32步骤参考: STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤 基于STM32HAL库移植FreeModbus FreeModbu ...
- 基于STM32F103移植canfestival协议栈(从站)CANopen
CAN open是一个基于CAN串行总线的网络传输系统的应用层协议,遵循ISO/OSI协议.CAN现场总线只是实现了OSI七层架构中的物理层和数据链路层,而canopen协议是基于他之上的一个应用层协 ...
- STM32F103/107 移植Freemodbus RTU
1.简介 FreeMODBUS一个奥地利人写的Modbus协议.它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植.Modbus是一个工业制造环境中应用的一个通用协议.Modbus通 ...
- 单片机移植freemodbus从机(STM32、GD32、瑞萨、国民技术等)
从github下载:https://github.com/cwalter-at/freemodbus 无法下载或者下载太慢可以用资源下载,无需积分.[freeModbus从机源码下载] 示例代码 一. ...
- STM32 移植FreeModbus详细过程
modbus是一个非常好的串口协议(当然也能用在网口上),它简洁.规范.强大.可以满足大部分的工业.嵌入式需求.我写的这个四个寄存器都可以用(输入寄存器.保持寄存器.线圈寄存器.离散寄存器).不像别的 ...
- STM32F103移植FreeModbus实现ModbusRTU和ModbusTCP同时工作
移植过程比较繁琐就不细说了,说一下注意点就行 一.共用函数 不管是TCP还是RTU有些函数是共用的,比如: eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBu ...
- FreeModbus的移植
FreeModbus V1.6 主机使用说明 一.简述 FreeModbus是一款开源的Modbus协议栈,但是只有从机开源,主机源码是需要收费的.同时网上也没有发现比较好的开源的Modbus主机协议 ...
- FreeModbus V1.6 主机
FreeModbus是一款开源的Modbus协议栈,但是只有从机开源,主机源码是需要收费的.同时网上也没有发现比较好的开源的Modbus主机协议栈,所以才开发这款支持主机模式的FreeModbus协议 ...
最新文章
- 什么是互联网大厂_仅限今天!大厂最热数据分析经典实战项目大公开!
- piwik的安装与配置
- 斐波那契问题的递归和动态规划
- 分享:我是怎么在github上找到优秀的仓库的?
- 驳《从团购网站看中国人的创新精神》
- MySQL——查询数据库、数据表总记录数
- 直接拿来用!GitHub 标星 5000+,学生党学编程有这份资料就够了
- 简单粗暴Tensorflow 2.0
- ftp搜索文件_CrossFTP for Mac(FTP客户端)
- 剑指offer-面试题37:序列化二叉树及二叉树的基本操作和测试
- 考研高等数学张宇30讲笔记——第九讲一元函数积分学的几何应用
- java itext 页边距_iText的用法
- 剑指offer第二版(C++实现)
- Maven项目中父子项目互相找不到的解决方法
- python修改图片尺寸
- HIVE厂牌艺人_北京音乐节-北京音乐节全攻略 - 马蜂窝
- Noteability和GoodNotes日常使用细节对比
- 语音识别软件_语音识别 软件_日语语音识别软件 - 云+社区 - 腾讯云
- 如何利用巨象指纹浏览器在twitter上找到精准客户
- VI 之快速查找定位