参考与简介:
说在最前面,我是通过前面两位老哥的资料整合,站在了巨人的肩膀上完成了移植,以此说明一下。
链接:基于STM32CubeMX移植freeModbusRTU(从站)
链接:基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植
在移植之前准备了一下材料:
硬件:
正点原子STM32F407探索者开发板
下载器STlink
数据线Minusb转USB
上位机调试助手:
mbpoll(提取码:ns74)
软件:
STM32CubeMX
FreeModbus官方源码包
在1处输入自己的MUC型号,随后双击2处对应型号的芯片
外部时钟源RCC选项中,1表示禁用,2表示旁路时钟源,3表示石英/陶瓷 晶振,此处选择3石英/陶瓷 晶振
Debug选项由于是使用STlink下载所以选择Serial Wire
我们需要使能FreeRtos,所以此处将HAL库的1ms定时中断采用的定时器改为TIM10,因为Systick要用作FreeRTOS的时基定时
采用外部时钟源,主频配置了168MHz
已知在freemodbus中默认定义:当波特率大于19200时,判断一帧数据超时时间固定为1750us,当波特率小于19200时,超时时间为3.5个字符时间。这里移植的是115200,所以一帧数据超时时间为1750us
这里采用TIM2,TIM2挂载在ABP1上,主频为84MHz。我们此处取预分频系数84-1,可以得到对应的分频频率为1MHz,即0.000001s=0.001ms=1us,自动重载值设置为1750,得到超时时间1750us
采用异步通信方式,通讯参数此处为默认值,按需要更改。
在1处选择FreeRtos版本,有CMSIS_V1和CMSIS_V2两个版本可供选择
Tasks and Queues一栏中可创建任务函数,双击2处:
Task Name:任务名
Priority:任务优先级
Stack Size:分配堆栈大小
Entry Function:任务函数名
使能定时器2和串口1的中断
探索者开发板上PF9,PF10两个引脚控制LED灯,所以此处将PF9,PF10设置为输出引脚
此处工程文件名自定义,IDE选择MDK-ARM,注意工程路径中不要有中文,不然STM32CubeMX会在生成项目时会报错
到此处基础STM32Cubemx工程文件配置完成
在工程目录文件夹下新建一个HardWare文件夹用于存放外设的库文件
打开事先下载好的freemodbus-v1.6文件夹,需要此次移植需要用到的为上图中所标注出的两处。
在HardWare文件夹下新建一个STModBus文件夹用于存放freemodbus需要移植的文件
将freemodbus-v1.6文件夹下modbus文件夹整体复制到STModBus中
然后把freemodbus-v1.6\demo\BARE文件夹下的port、demo.c以及Makefile同样也复制到STModBus中
个人习惯,不打钩就可以节省不少编译时间,但是无法使用Go To Definition这个功能
由于STM32CubeMx生成的工程文件默认是不勾选Reset and Run,导致生成了代码,编译也通过了,0 Error(s),0 Waring(s) ,但是点击load的时候程序却不运行,需要手动复位一下。
点击此处,增加STModBus以及STModBus_Port这两个组
将Modbus_demo\HardWare\STModBus\modbus目录下所有.c文件以及Modbus_demo\HardWare\STModBus目录下demo.c文件导入STModBus组
将Modbus_demo\HardWare\STModBus\port目录下portevent.c、portserial.c、porttimer.c文件导入STModBus_Port组
添加完毕后如上图所示
将前面移植的文件夹添加到头文件路径中
修改demo.c的main函数名为host,然后编译,此时已经可以编译通过。
#include "port.h"
/* ----------------------- STM32 includes ----------------------------------*/
#include "stm32f4xx_hal.h"
#include "usart.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
首先引入所需要的头文件
/* ----------------------- static functions ---------------------------------*/
//static void prvvUARTTxReadyISR( void );
//static void prvvUARTRxISR( void );
将此处的静态函数声明注释掉
/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
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_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
}
}
将FreeModBus的串口收发底层函数与STM32串口收发相关联,此处使用是USART1,故为&huart1
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
return TRUE;
}
将原先的FALSE修改为TRUE,此函数是用于初始化串口,由于在生成工程文件时已经初始化完串口1了,所以此处直接返回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 (&huart1 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )
return FALSE ;
else
return TRUE;
}
添加在UARTs传输缓冲区中放置一个字节的代码
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,0x01) != HAL_OK )
return FALSE ;
else
return TRUE;
}
添加返回UARTs接收缓冲区中的字节的代码
//static
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
//static
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
把static修饰词注释,方便在串口中断使用
#include "port.h"
/* ----------------------- STM32 includes ----------------------------------*/
#include "stm32f4xx_hal.h"
#include "tim.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
同样首先引入所需要的头文件
/* ----------------------- static functions ---------------------------------*/
//static void prvvTIMERExpiredISR( void );
将此处的静态函数声明注释掉
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
return TRUE;
}
将原先的FALSE修改为TRUE,此函数是用于初始化定时器,由于在生成工程文件时已经初始化完定时器了,所以此处直接返回TRUE就行了
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_CLEAR_IT(&htim2,TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);
__HAL_TIM_SET_COUNTER(&htim2,0);
__HAL_TIM_ENABLE(&htim2);
}
使能定时器中断,此处使用的是TIM2,所以为&htim2
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
__HAL_TIM_DISABLE(&htim2);
__HAL_TIM_SET_COUNTER(&htim2,0);
__HAL_TIM_DISABLE_IT(&htim2,TIM_IT_UPDATE);
__HAL_TIM_CLEAR_IT(&htim2,TIM_IT_UPDATE);
}
禁用任何挂起的定时器
//static
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
把static修饰词注释,方便在中断内使用
#ifndef _PORT_H
#define _PORT_H
#include
#include
#include "stm32f4xx_hal.h"
#define INLINE inline
#define PR_BEGIN_EXTERN_C extern "C" {
#define PR_END_EXTERN_C }
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1)
#define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0)
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
同样首先引入所需头文件:
#include "stm32f4xx_hal.h"
然后添加开/关总中断代码:
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1)
#define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0)
/* USER CODE BEGIN 1 */
//在这BEGIN和END中间为规定的用户区!
/* USER CODE END 1 */
由于STM32CubeMX在每次生成代码后会将不按规范要求编写代码的区域对代码做删除处理,所以在对STM32CubeMX所生成的文件进行修改时,切记要在用户区内编写用户代码!切记要在用户区内编写用户代码!切记要在用户区内编写用户代码!重要的事说三遍!
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
/* USER CODE END PFP */
使用extern声明这两个外部函数,用于和modbus的串口底层代码相关联
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)!= RESET)
{
prvvUARTRxISR();//接收中断
}
if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)!= RESET)
{
prvvUARTTxReadyISR();//发送中断
}
HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
HAL_UART_IRQHandler(&huart1);
/* USER CODE END USART1_IRQn 1 */
}
添加将USART1收到的内容转移到freemodbus的功能函数进行处理
大致理一下整个demo.c文件:
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
此处没什么好说的,引入所需的头文件
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 10
这里定义了两个标识符表示的常量
#define REG_INPUT_START 0 输入寄存器的起始地址为0
#define REG_INPUT_NREGS 10 输入寄存器的个数为10
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
uint16_t usRegInputBuf[REG_INPUT_NREGS];
uint16_t InputBuff[5];
额外定义了两个保存输入寄存器数据的数组
//int
//host( void )
//{
// eMBErrorCode eStatus;
// eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );
// /* Enable the Modbus Protocol Stack. */
// eStatus = eMBEnable( );
// for( ;; )
// {
// ( void )eMBPoll( );
// /* Here we simply count the number of poll cycles. */
// usRegInputBuf[0]++;
// }
//}
将之前修改的host函数注释,此处并非是程序运行的main函数,后面将其移植到main.c中即可
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
int i;
InputBuff[0] = 0x11;
InputBuff[1] = 0x22;
InputBuff[2] = 0x33;
InputBuff[3] = 0x44;
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--;
// }
for(i=0;i>8;
pucRegBuffer++;
*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
pucRegBuffer++;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */
同样,首先引入所需的头文件
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */
extern void prvvTIMERExpiredISR( void );
/* USER CODE END PFP */
使用extern声明prvvTIMERExpiredISR外部函数,用于和modbus的定时器底层代码相关联
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);
eMBEnable( );
/* USER CODE END 2 */
在所需外设初始化完成之后的用户编辑区内初始化FreeModBus函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM10) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if(htim->Instance == TIM2)
{
prvvTIMERExpiredISR( );
}
/* USER CODE END Callback 1 */
}
添加定时器中断回调函数
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */
同样,首先引入所需的头文件
void LED_Task(void *argument)
{
/* USER CODE BEGIN LED_Task */
/* Infinite loop */
for(;;)
{
eMBPoll();
osDelay(10);
}
/* USER CODE END LED_Task */
}
在LED_Task任务中启动modbus侦听
前一段时间忙于工作没更新,实际上到这整体移植方案已经算完成了,接下来做一个简单的收尾。
程序下载后,开发板通过UAB线与电脑连接
电脑端打开Modbus Poll 软件-->连接设置-->连接,原则相应配置,确保与自己程序要能对应上,然后点击确认
点击软件上方的设置-->读写定义,如下配置
已经能正确接收到输入寄存器的值
然后依次进行如下操作
把数值显示改成16进制,这样看的会更加明显,
这样收尾就算完成了~
我这边主要使用保持寄存器,简单贴一下代码,有不清楚评论或私信联系
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;
// return MB_ENOREG;
}
以下 这是新增一个FreeRTOS任务,做了一些简单的读写。
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
UCHAR pucRegBuffer1[4] = {0xFF,0x01,0x02,0x03};
UCHAR pucRegBuffer2[4];
for(;;)
{
eMBRegHoldingCB(&pucRegBuffer1[0],1,1,MB_REG_WRITE);
eMBRegHoldingCB(&pucRegBuffer2[0],2,1,MB_REG_READ);
if( pucRegBuffer2[0] == 0xF1 && pucRegBuffer2[1] == 0x1F)
{
eMBRegHoldingCB(&pucRegBuffer1[2],3,1,MB_REG_WRITE);
}
osDelay(200);
}
/* USER CODE END StartTask02 */
}
实验效果为读写保持寄存器,
寄存器0值为0xFF01,
如果寄存器1值等于0xF11F,
则会向寄存器2写入0x0203;
这是在正点原子探索者开发板上测试没问题的程序,有需要的自己下载,提取码(qk6x)