在这里插入图片描述

一、准备工作

1.1获取freemodbus v1.6源码

点击下载

1.2准备一个GD32F30x已经移植好RT_Thread的基础工程

移植参考链接
直接下载基础工程

二、FreeModbus文件移植

FreeModbus文件移植
解压freemodbus-v1.6,将源码目录的以下两个文件夹复制到工程的文件夹中

  • freemodbus-v1.6/modbus
  • freemodbus-v1.6/demo/BARE

在这里插入图片描述
将源码添加到工程中
在这里插入图片描述

这里仅展示RTU的实现

在这里插入图片描述

添加.h文件路径
在这里插入图片描述

进行编译,这里第一次编译会有错误,没有关系
在这里插入图片描述

三、修改FreeModbus相关文件

3.1 port.h

  • 主机和从机通用该文件;
  • 内联inline可能编译器不支持,我的会报错,如果有函数报未定义,可以检查是否内联,删去即可;或开启C99 mode
    在这里插入图片描述
/*
 * FreeModbus Libary: BARE 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$
 */

#ifndef _PORT_H
#define _PORT_H

//#include <assert.h>
#include <inttypes.h>
#include <gd32f30x.h>

#define	INLINE                      inline
#define PR_BEGIN_EXTERN_C           extern "C" {
#define	PR_END_EXTERN_C             }

#include <rtthread.h>
#include <rthw.h>  // 包含硬件中断相关函数声明

/* 保存中断状态的变量 */
static rt_base_t critical_level;

/* 进入临界区:关闭中断和线程调度 */
#define ENTER_CRITICAL_SECTION() do { \
    critical_level = rt_hw_interrupt_disable(); \
    rt_enter_critical(); \
} while (0)

/* 退出临界区:恢复中断和线程调度 */
#define EXIT_CRITICAL_SECTION() do { \
    rt_exit_critical(); \
    rt_hw_interrupt_enable(critical_level); \
} while (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;


/* 如果未定义 assert_param 宏,则进行定义 */
#define assert(condition) do{ }while(0);
    
#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

#endif


3.2 portserial.c

/*
 * FreeModbus Libary: GD32 Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * 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: portserial.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
static void prvvUARTRxISR(void);
/* ----------------------- Start implementation -----------------------------*/
uint8_t isr_flag;

#define SLAVE_PORT USART0
#define SLVAE_RX_PORT GPIOB 
#define SLAVE_RX_PIN GPIO_PIN_7

#define SLVAE_TX_PORT GPIOB 
#define SLAVE_TX_PIN GPIO_PIN_6

#define SLAVE_DIR_PORT GPIOB
#define SLAVE_DIR_PIN GPIO_PIN_8

#define SLAVE_RS485_RECEIVE_MODE gpio_bit_reset(SLAVE_DIR_PORT, SLAVE_DIR_PIN)
#define SLAVE_RS485_SEND_MODE gpio_bit_set(SLAVE_DIR_PORT, SLAVE_DIR_PIN)

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
  if (xRxEnable)
  {
    /* 485通信时,等待串口移位寄存器中的数据发送完成后,再去使能485的接收、使能485的发送*/
    while (!usart_flag_get(SLAVE_PORT, USART_FLAG_TC))
      ;
    SLAVE_RS485_RECEIVE_MODE;
    usart_interrupt_enable(SLAVE_PORT, USART_INT_RBNE);
  }
  else
  {
    SLAVE_RS485_SEND_MODE;
    usart_interrupt_disable(SLAVE_PORT, USART_INT_RBNE);
  }
  if (xTxEnable)
  {
    usart_interrupt_enable(SLAVE_PORT, USART_INT_TBE);
  }
  else
  {
    usart_interrupt_disable(SLAVE_PORT, USART_INT_TBE);
  }
}

void vMBPortClose(void)
{
  usart_interrupt_disable(SLAVE_PORT, USART_INT_RBNE);
  usart_interrupt_disable(SLAVE_PORT, USART_INT_TBE);
  usart_disable(SLAVE_PORT);
}
// 默认一个从机 串口0 波特率可设置  奇偶检验可设置
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
             eMBParity eParity)
{
  // 直接使用函数配置 NVIC,不使用结构体
  // 设置NVIC优先级分组为Group2:0-3抢占式优先级,0-3的响应式优先级
  nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
  //======================时钟初始化=======================================
  rcu_periph_clock_enable(RCU_GPIOB);
  rcu_periph_clock_enable(RCU_USART0);
  // 重映射
  rcu_periph_clock_enable(RCU_AF);
  gpio_pin_remap_config(GPIO_USART0_REMAP, ENABLE); 
  //======================IO初始化=======================================
  // SLAVE_PORT_TX
  gpio_init(SLVAE_RX_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SLAVE_TX_PIN);
  // SLAVE_PORT_RX
  gpio_init(SLVAE_RX_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, SLAVE_RX_PIN);
  // 配置485发送和接收模式
  gpio_init(SLAVE_DIR_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SLAVE_DIR_PIN);
  //======================串口初始化=======================================
  usart_deinit(SLAVE_PORT);
  usart_baudrate_set(SLAVE_PORT, ulBaudRate);
  // 设置校验模式
  switch (eParity)
  {
  case MB_PAR_NONE: // 无校验
    usart_parity_config(SLAVE_PORT, USART_PM_NONE);
    usart_word_length_set(SLAVE_PORT, USART_WL_8BIT);
    break;
  case MB_PAR_ODD: // 奇校验
    usart_parity_config(SLAVE_PORT, USART_PM_ODD);
    usart_word_length_set(SLAVE_PORT, USART_WL_9BIT);
    break;
  case MB_PAR_EVEN: // 偶校验
    usart_parity_config(SLAVE_PORT, USART_PM_EVEN);
    usart_word_length_set(SLAVE_PORT, USART_WL_9BIT);
    break;
  default:
    return FALSE;
  }

  usart_stop_bit_set(SLAVE_PORT, USART_STB_1BIT);
  usart_hardware_flow_rts_config(SLAVE_PORT, USART_RTS_DISABLE);
  usart_hardware_flow_cts_config(SLAVE_PORT, USART_CTS_DISABLE);
  usart_receive_config(SLAVE_PORT, USART_RECEIVE_ENABLE);
  usart_transmit_config(SLAVE_PORT, USART_TRANSMIT_ENABLE);
  /* SLAVE_PORT */
  usart_enable(SLAVE_PORT);
  //=====================中断初始化======================================
  nvic_irq_enable(USART0_IRQn, 0, 0);

  SLAVE_RS485_RECEIVE_MODE;
  EXIT_CRITICAL_SECTION(); // 开全局中断

  return TRUE;
}

BOOL xMBPortSerialPutByte(CHAR ucByte)
{
  usart_data_transmit(SLAVE_PORT, (uint8_t)ucByte);
  return TRUE;
}

BOOL xMBPortSerialGetByte(CHAR *pucByte)
{
  *pucByte = (CHAR)usart_data_receive(SLAVE_PORT);
  return 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.
 */
static 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.
 */
static void prvvUARTRxISR(void)
{
  pxMBFrameCBByteReceived();
}
/*******************************************************************************
 * Function Name  : USART0_IRQHandler
 * Description    : This function handles USART0 global interrupt request.
 * Input          : None
 * Output         : None
 * Return         : None
 *******************************************************************************/
void USART0_IRQHandler(void)
{
  rt_interrupt_enter();
  // 溢出错误
  if (usart_flag_get(SLAVE_PORT, USART_FLAG_ORERR) == SET)
  {
    usart_flag_clear(SLAVE_PORT, USART_FLAG_ORERR);
    prvvUARTRxISR();
  }
  // 接收中断
  if (usart_interrupt_flag_get(SLAVE_PORT, USART_INT_FLAG_RBNE) == SET)
  {
    usart_interrupt_flag_clear(SLAVE_PORT, USART_INT_FLAG_RBNE);
    prvvUARTRxISR();
  }
  // 发送中断
  if (usart_interrupt_flag_get(SLAVE_PORT, USART_INT_FLAG_TBE) == SET)
  {
    prvvUARTTxReadyISR();
  }
  rt_interrupt_leave();
}

3.3 pottimer.c

/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * 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: porttimer.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

#define MBS_SOFT_TIMER  (FALSE)
#define USE_UART_DMA (FALSE)

/* ----------------------- static functions ---------------------------------*/
static struct rt_timer timer;
static void prvvTIMERExpiredISR(void);
static void timer_timeout_ind(void* parameter);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
    rt_timer_init(&timer, "slave timer",
                   timer_timeout_ind, /* bind timeout callback function */
                   RT_NULL,
                   (50 * usTim1Timerout50us) / (1000 * 1000 / RT_TICK_PER_SECOND) + 1,
                   RT_TIMER_FLAG_ONE_SHOT); /* one shot */
    return TRUE;
}

void vMBPortTimersEnable()
{
    rt_timer_start(&timer);
}

void vMBPortTimersDisable()
{
    rt_timer_stop(&timer);
}

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

static void timer_timeout_ind(void* parameter)
{
    prvvTIMERExpiredISR();
}

/**
 * 销毁Modbus从机定时器
 * 
 * @return 成功返回TRUE,失败返回FALSE
 */
BOOL xMBPortTimersDestroy(void)
{
    rt_err_t ret;
    
    // 1. 先停止定时器(确保定时器处于非运行状态)
    ret = rt_timer_stop(&timer);
    if (ret != RT_EOK && ret != -RT_ERROR)  // 允许返回-RT_ERROR(定时器已停止)
    {
        rt_kprintf("Modbus timer stop failed: %d\n", ret);
        return FALSE;
    }
    
    // 2. 销毁定时器(释放相关资源)
    ret = rt_timer_detach(&timer);
    if (ret != RT_EOK)
    {
        rt_kprintf("Modbus timer destroy failed: %d\n", ret);
        return FALSE;
    }
    
    // 3. 可选:清除定时器结构体(对于静态定时器非必需,但增强安全性)
    rt_memset(&timer, 0, sizeof(struct rt_timer));
    
    return TRUE;
}

或者使用硬件定时器

/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * 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: porttimer.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

#define MBS_SOFT_TIMER  (FALSE)
#define USE_UART_DMA (FALSE)
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
    uint16_t prescaler_value = 0;
    // GD32 V2.x中定时器初始化结构体
    timer_parameter_struct timer_initpara;

    // 1. 使能定时器时钟(与串口代码保持一致的RCU使能方式)
    rcu_periph_clock_enable(RCU_TIMER3);

    // 2. 计算预分频值(目标:50us周期,即20kHz计数频率)
    // 假设APB1时钟为36MHz,定时器时钟 = APB1时钟 × 2(当APB1预分频>1时)
    prescaler_value = (uint16_t)(SystemCoreClock / 20000) - 1;

    // 3. 定时器复位与初始化(V2.x风格)
    timer_deinit(TIMER3);
    timer_initpara.prescaler         = prescaler_value;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = (uint16_t)usTim1Timerout50us;  // 超时值(单位:50us)
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER3, &timer_initpara);

    // 使能自动重装载影子寄存器
    timer_auto_reload_shadow_enable(TIMER3);

    // 4. 配置NVIC中断(与串口代码一致的优先级配置方式)
    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);  // 与串口共用同一优先级分组
    nvic_irq_enable(TIMER3_IRQn, 1, 1);  // 抢占优先级1,子优先级1(高于串口中断)

    // 5. 初始化时禁用定时器和中断
    timer_flag_clear(TIMER3, TIMER_FLAG_UP);
    timer_interrupt_disable(TIMER3, TIMER_INT_UP);
    timer_disable(TIMER3);

    return TRUE;
}

void vMBPortTimersEnable()
{
    // 清除标志→使能中断→重置计数器→启动定时器(V2.x函数风格)
    timer_flag_clear(TIMER3, TIMER_FLAG_UP);
    timer_interrupt_enable(TIMER3, TIMER_INT_UP);
    timer_counter_value_config(TIMER3, 0);  // V2.x中计数器设置函数
    timer_enable(TIMER3);
}

void vMBPortTimersDisable()
{
    // 停止定时器→禁用中断→清除标志→重置计数器
    timer_disable(TIMER3);
    timer_interrupt_disable(TIMER3, TIMER_FLAG_UP);
    timer_flag_clear(TIMER3, TIMER_INT_UP);
    timer_counter_value_config(TIMER3, 0);
}

void prvvTIMERExpiredISR(void)
{
    (void)pxMBPortCBTimerExpired();  // 调用Modbus超时回调
}

/* 定时器3中断服务程序(与串口中断处理风格一致) */
void TIMER3_IRQHandler(void)
{
    rt_interrupt_enter();  // 与串口中断保持一致的RT-Thread中断上下文处理

    // 检查更新中断标志(V2.x中断标志判断方式)
    if (timer_interrupt_flag_get(TIMER3, TIMER_FLAG_UP) == SET)
    {
        timer_interrupt_flag_clear(TIMER3, TIMER_INT_UP);  // 清除中断标志
        prvvTIMERExpiredISR();  // 处理超时事件
    }

    rt_interrupt_leave();
}

四、运行modbus

demo.c

/*
 * FreeModbus Libary: BARE Demo Application
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

// 静态定义线程控制块和线程栈

#define MBSLAVE_THREAD_STACK_SIZE 1024
#define MBSLAVE_THREAD_PRIORITY 8
#define MBSLAVE_THREAD_TIMESLICE 5
static struct rt_thread mbslave_thread;
static rt_uint8_t mbslave_thread_stack[MBSLAVE_THREAD_STACK_SIZE];

/* ----------------------- Start implementation -----------------------------*/
static void mbslave_thread_entry(void *parameter)
{
    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]++;
        rt_thread_dela(10);
    }
}

/* 初始化函数 */
int thread_mbslave_init(void)
{
    // 静态初始化线程
    rt_err_t result = rt_thread_init(&mbslave_thread,
                                     "mbslave",
                                     mbslave_thread_entry,
                                     RT_NULL,
                                     &mbslave_thread_stack[0],
                                     sizeof(mbslave_thread_stack),
                                     MBSLAVE_THREAD_PRIORITY,
                                     MBSLAVE_THREAD_TIMESLICE);

    if (result == RT_EOK)
    {
        rt_thread_startup(&mbslave_thread);
        rt_kprintf("mbslave thread started\n");
        return 0;
    }
    else
    {
        rt_kprintf("Failed to initialize mbslave thread!\n");
        return -1;
    }
}

INIT_APP_EXPORT(thread_mbslave_init); /* 系统启动时自动初始化 */

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 )
{
    return MB_ENOREG;
}


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

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

至此,freemodbus v1.6移植成功

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐