FreeModbus RTU 从机Hal库裸机移植避坑指南

首先说明 : FreeModbus 有很多个库!!!! 不同库的实现方法是略有不同的!!!
本次 FreeModbus RTU 移植 主要依据 这个网友分享的工程他人移植的库

你可能会在csdn看到他的文章, 但是完全跟着那个文章走很混乱 而且跟库的文件不一样. 故而 我重新整理了工程, 并写了一个详细的移植教程

1. 下载 FreeModbus RTU 库

FreeModbus RTU 库

2. STM32CubeMX 配置流程

我假设你已经学会使用stm32cubeMX点灯了;

2.1下载模式配置

下载模式配置

2.2 开启外部时钟

开启外部时钟

2.3 定时器配置

定时器配置

2.4 串口配置

串口配置

2.5 中断配置

开启定时器和串口的中断

2.6 配置中断函数(关闭自动生成)

中断函数关闭

2.7 配置时钟

时钟配置

3.库文件导入

3.1 .c文件汇总

_c文件汇总

3.2 .h文件汇总

_h文件索引汇总

3.3 demo文件选择

demo文件选择

4. 移植流程

ok 完成上述步骤后, 你就可以开始正式的移植工作了:

主要需要移植的地方为: portserial.c && porttimer.c && demo.c

4.1 portserial.c

vMBPortSerialEnable() 函数

这个函数要根据传入的参数进行串口接收和关闭中断使能

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_TXE);	
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
    }
}

xMBPortSerialInit() 函数

这个函数主要初始化串口,因为我们已经使用stm32cubeMX配置好串口,所以直接调用就好了, 这样子可以实现配置统一,方便阅读和修改

xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    MX_USART1_UART_Init();
		return TRUE;
}

xMBPortSerialPutByte () 函数

这个函数主要实现串口发送一个字节, 有点神奇的是我直接调用hal库的函数传输会有bug,通信失败. 具体原因我还没排查到.这里贴那个老哥是实现方法;

xMBPortSerialPutByte( CHAR ucByte )
{
    USART1->DR = ucByte;
    return TRUE;
}

xMBPortSerialGetByte() 函数

这个函数主要实现串口接收一个字节, 有点神奇的是我直接调用hal库的函数传输会有bug,通信失败. 具体原因我还没排查到.这里贴那个老哥的实现方法;

xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = (USART1->DR & (uint16_t)0x00FF);
    return TRUE;
}

手搓串口1中断函数USART1_IRQHandler()

这个中断函数要在portserial.c中手动添加, 主要是因为它调用了static void prvvUARTTxReadyISR( void ) 和 static void prvvUARTRxISR( void ); 再其他文件调用不了. 所以才要关闭hal库自动生成中断函数在这里手动添加;

void USART1_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);	
        prvvUARTRxISR();	
    }

    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))				
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);			
        prvvUARTTxReadyISR();
    }
}

4.2 porttimer.c

xMBPortTimersInit() 函数

这个函数主要初始化定时器, 因为我使用的是stm32cubeMX配置好定时器, 所以直接调用就好了, 这样子可以实现配置统一,方便阅读和修改

xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	MX_TIM4_Init();
	__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);      
    __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);					
    return TRUE;
}

vMBPortTimersEnable() 函数

使能定时器 和情况计数器数值

vMBPortTimersEnable(  )
{
   __HAL_TIM_SET_COUNTER(&htim4, 0);
   __HAL_TIM_ENABLE(&htim4);
}

vMBPortTimersDisable() 函数

关闭定时器

vMBPortTimersDisable(  )
{
   __HAL_TIM_DISABLE(&htim4);
}

手搓定时器4中断函数TIM4_IRQHandler()

这个中断函数要在porttimer.c中手动添加, 主要是因为它调用了static void prvvTIMERExpiredISR( void ); 再其他文件调用不了. 所以才要关闭hal库自动生成中断函数在这里手动添加;

void TIM4_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))
    {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
        prvvTIMERExpiredISR();
    }
}

4.3. demo.c

这个文件就是FreeModbus移植的关键位置了, 其通讯都是在调用这些信息;
以下直接贴验证的好代码, 想要自定义就修改前面那四个寄存器就可以;

#include "mb.h"
#include "mbport.h"


// 十路输入寄存器
#define REG_INPUT_SIZE  10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];


// 十路保持寄存器
#define REG_HOLD_SIZE   10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];


// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};


// 十路离散量
#define REG_DISC_SIZE  10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};


/// CMD4命令处理回调函数
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    USHORT usRegIndex = usAddress - 1;

    // 非法检测
    if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
    {
        return MB_ENOREG;
    }

    // 循环读取
    while( usNRegs > 0 )
    {
        *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
        *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
        usRegIndex++;
        usNRegs--;
    }

    // 模拟输入寄存器被改变
    for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
    {
        REG_INPUT_BUF[usRegIndex]++;
    }

    return MB_ENOERR;
}

/// CMD6、3、16命令处理回调函数
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    USHORT usRegIndex = usAddress - 1;

    // 非法检测
    if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
    {
        return MB_ENOREG;
    }

    // 写寄存器
    if(eMode == MB_REG_WRITE)
    {
        while( usNRegs > 0 )
        {
            REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
            pucRegBuffer += 2;
            usRegIndex++;
            usNRegs--;
        }
    }

    // 读寄存器
    else
    {
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
            *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
            usRegIndex++;
            usNRegs--;
        }
    }

    return MB_ENOERR;
}

/// CMD1、5、15命令处理回调函数
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    USHORT usRegIndex   = usAddress - 1;
    UCHAR  ucBits       = 0;
    UCHAR  ucState      = 0;
    UCHAR  ucLoops      = 0;

    // 非法检测
    if((usRegIndex + usNCoils) > REG_COILS_SIZE)
    {
        return MB_ENOREG;
    }

    if(eMode == MB_REG_WRITE)
    {
        ucLoops = (usNCoils - 1) / 8 + 1;
        while(ucLoops != 0)
        {
            ucState = *pucRegBuffer++;
            ucBits  = 0;
            while(usNCoils != 0 && ucBits < 8)
            {
                REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;
                usNCoils--;
                ucBits++;
            }
            ucLoops--;
        }
    }
    else
    {
        ucLoops = (usNCoils - 1) / 8 + 1;
        while(ucLoops != 0)
        {
            ucState = 0;
            ucBits  = 0;
            while(usNCoils != 0 && ucBits < 8)
            {
                if(REG_COILS_BUF[usRegIndex])
                {
                    ucState |= (1 << ucBits);
                }
                usNCoils--;
                usRegIndex++;
                ucBits++;
            }
            *pucRegBuffer++ = ucState;
            ucLoops--;
        }
    }

    return MB_ENOERR;
}

/// CMD2命令处理回调函数
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    USHORT usRegIndex   = usAddress - 1;
    UCHAR  ucBits       = 0;
    UCHAR  ucState      = 0;
    UCHAR  ucLoops      = 0;

    // 非法检测
    if((usRegIndex + usNDiscrete) > REG_DISC_SIZE)
    {
        return MB_ENOREG;
    }

    ucLoops = (usNDiscrete - 1) / 8 + 1;
    while(ucLoops != 0)
    {
        ucState = 0;
        ucBits  = 0;
        while(usNDiscrete != 0 && ucBits < 8)
        {
            if(REG_DISC_BUF[usRegIndex])
            {
                ucState |= (1 << ucBits);
            }
            usNDiscrete--;
            usRegIndex++;
            ucBits++;
        }
        *pucRegBuffer++ = ucState;
        ucLoops--;
    }

    // 模拟离散量输入被改变
    for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++)
    {
        REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
    }

    return MB_ENOERR;
}

5.移植最后一步 main.c 编写

ok 至此你已经完成90%的移植工作了;
现在只需要调用 对应初始化的函数 还有那四个数组就可以了;
贴一个简单版的main.c关键部分

#include "mb.h"
#include "mbport.h"

void SystemClock_Config(void);
extern uint16_t REG_HOLD_BUF[10];

int main(void)
{
  HAL_Init();
	eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); 
	eMBEnable();

  SystemClock_Config();

  REG_HOLD_BUF[0] = 0Xff00;
  MX_GPIO_Init();
  MX_TIM4_Init();
  MX_USART1_UART_Init();

  while (1)
  {
		eMBPoll();	
  }
}

6.剩下10%的坑

如果你操作完上面的步骤发现还没能使用, 恭喜你 还有几个坑得改一改:

  1. 使用 MicroLIB 库 且 编译优化等级选择 Level 3(-O3) 重编译;
    如下:

  2. 把你的modbus助手(如 QModBus) 关闭再开启 然后就可以使用了
posted @ 2024-11-13 18:18  Atul-8  阅读(177)  评论(1编辑  收藏  举报