STM32 MODBUS协议-简介及接入 FreeMODBUS

简介

随着近年来物联网行业的迅速发展,工业物联网领域也成为了最大子领域之一。另外的领域包括运输业物联网、基础设施物联网、消费者物联网。

受制于体积、功耗、成本等因素,一部分设备无法直接接入物联网服务。对于这种设备,目前行业的解决方案通常是单独设置一个网关设备,无法直接接入网络的设备通过有线连接到网关,通过一定的协议将数据通过网关转发到上层网络。

这种连接方式和协议一起叫做现场总线(Field bus),现场总线的协议和一般的单片机常用UART、I2C、SPI、SDMMC等协议不同。现场总线协议要求更高的容错纠错率、抗干扰和易部署性,通常现场总线的长度都在几十米以上,普通的UART、I2C、SPI协议无法在这么长的长度上进行工作。

常用的线程总线协议有:
1.Modbus
2.CAN (常用于汽车)
3.Foundation Fieldbus




Modbus协议介绍

物理连接

Modbus的物理连接方式如下图所示:
STM32 MODBUS协议-简介及接入 FreeMODBUS_第1张图片



软件架构

如果通过OSI七层网络模型来说的话,Modbus协议仅仅位于第二层:数据链路层。
STM32 MODBUS协议-简介及接入 FreeMODBUS_第2张图片



因为处于的层次非常低,几乎不涉及到其他协议(实际上还涉及到串口UART协议,后面会讲),所以Modbus协议非常的单纯,几乎只是把物理层的电信号进行了一下封装。

当然这也不意味着Modbus协议就非常简单,实际上,如果你大学是学计算机专业的,一定学过一门叫做计算机网络的课,这门课通常的课时是50个小时左右,而这门课的内容大部分是在讲处于应用层的TCP/UDP协议。所以理论上来讲,学习Modbus协议至少也要20个小时的课时吧。(所以前两遍学不会也不要慌张,很正常O(∩_∩)O哈哈~)

废话少说,言归正传。看一下下面这张图:
STM32 MODBUS协议-简介及接入 FreeMODBUS_第3张图片
上图表示的是:Modbus线上的信号是0/1的形式,Modbus协议会将多个0/1信号进行划分和组合,形成一个Modbus帧。一个Modbus帧就是一次Modbus请求或者Modbus回应

实际上,Modbus还利用了串口UART协议,可以理解为Modbus是建立在UART协议之上的协议。但是由于其实在是不复杂,所以我个人认为还是把其定义为数据链路层比较OK。因此Modbus中也有波特率、数据位、校验位、停止位的概念,Modbus协议只取数据位作为数据。 由于串口协议数据位通常是8位,所以Modbus帧的数据划分也是以8位、16位这样进行划分的。

Modbus和http协议很像,一次请求,一次回应。
只能主机向从机发送一次Modbus请求,然后从机响应一次Modbus回应。
从机不能主动向主机发送任何信息。
STM32 MODBUS协议-简介及接入 FreeMODBUS_第4张图片


寄存器

Modbus协议还定义了寄存器的概念。Modbus主机向从机发送的查询请求帧的数据部分,并不能像tcp/http协议那样自定义数据内容,而是只能是固定的格式:16位寄存器起始地址➕16位寄存器数量。

同时从机也只会固定的按照这种格式去解析来自主机的请求。解析请求之后,一般会提供给开发者一个这样的回调函数:
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
开发者需要手动重写这个函数,来返回给主机期望的数据。

总而言之,可以理解Modbus请求还在应用层帮我们定义了一层寄存器的概念,所有Modbus数帧都必须按照寄存器的概念来发送/响应数据。这样加强了Modbus协议的通用性,同时我们在开发时也不用自己去想应用层的协议了。


举个实际的例子:

主机向一台设备地址为3的从机读取输入寄存器0~输入寄存器10的数据。波特率9600,数据位8,校验位无,停止位1:

  1. 首先主机会向从机发送一个起始信号,起始信号持续一定时间(3.5个字符周期)

  2. 然后开始发送数据,通过UART串口,依次发送以下电信号
    (注意串口是高位在前低位在后,最后的1代表1位停止位)
    11000001 (数据为0x03) // 代表设备地址为3
    00100001 (数据为0x04) // 代表功能码为0x04,读输入寄存器
    00000001 (数据为0x00) //
    00000001 (数据为0x00) // 和上一数据一起代表寄存器起始地址为0
    00000001 (数据为0x00) //
    01010001 (数据为0x0A) // 和上一数据一起代表寄存器数量为10
    111101111 (数据为0xEF) //
    100011101 (数据为0x71) // 和上一数据一起代表CRC校验码

    (转换为16进制顺序为:03 04 00 00 00 0A EF 71)
    你甚至可以通过串口直接发送 0x03 0x04 0x00 0x00 0x00 0x0A 0xEF 0x71,但是实际上你会发现不行,因为Modbus协议在各种地方还做了一些时序的限制,比如3.5字符周期的起始信号、1.5字符周期的数据间隔等。

3.从机收到之后,会对数据进行校验,校验的内容包括但不限于:设备地址是否是本机地址、CRC校验码是否正确等

4.校验如果通过,则会回复给主机相应的数据,数据格式和上面大同小异。这里就不累述了(写起太累啦)。







移植Modbus协议栈(主机部分)

看了我上面的介绍,你是不是会觉得好像自己手写一个Modbus请求也不是特别困难,百八十行代码就能解决。确实是这样,但是=你还需要考虑超时、接收数据CRC校验、总线冲突等一系列问题,所以再加上这些内容就不只几百行代码能搞定了,所以我们还是使用现成的Modbus协议栈一般来说Modbus协议都跑在RTOS操作系统之上。

目前有的Modbus协议栈有,在github上搜一搜:
https://github.com/search?l=C&o=desc&q=modbus&s=stars&type=Repositories

搜出来start多且是用C语言写的有如下几个

  • libmodbus: 使用c语言写的Modbus库。支持win、mac、linux平台,不支持arm平台。主要是由于在stm32使用的arm-none-eabi-gcc是阉割版本的gcc,部分内置函数、对象不支持。如果使用这个库的话,需要仔细阅读代码,手动替换不支持的部分代码。
  • FreeModbus:一个奥地利人写的Modbus库,从机模式免费,主机模式收费。官网https://www.embedded-experts.at/en/freemodbus/about/
  • FreeModbus_Slave-Master-RTT-STM32:也是C语言写的Modbus库,针对MCU平台。这个库是rtthread的主要作者armlink大神在FreeModbus免费的从机基础上添加了主机模式。中文文档,用户多,比较推荐。

接下来我们选择 FreeModbus_Slave-Master-RTT-STM32 进行开发。上面有说到,FreeModbus_Slave-Master-RTT-STM32 的作者是rtthread的主要作者,所以和rtthread有不潜的py关系,甚至直接提供了一套在rtthread上的移植。

使用的开发板为淘宝f103开发板
:淘宝链接



1.生成工程

  • 选择你的MCU,这里我用的是STM32F103RC
  • RCC中开启外部HSE时钟,外部时钟比HSI更稳定些。
  • Clock中时钟设置为最高主频72MHz。
  • SYS中开启DEBUG JTAG 4线(也就是SWD)
  • 打开串口1用于打印调试信息,波特率115200,校验位0,停止位1
  • 打开串口2用于Modbus协议,波特率9600,校验位0,停止位1
  • NVIC中,串口1和串口2中断都勾选Enabled
  • NVIC-Code generation中
    串口1勾选Generate IRQ handle,不勾选Call HAL Handle
    串口2不勾选Generate IRQ handle,不勾选Call HAL Handle
  • 接入RT-thread:参考官方文档 基于 CubeMX 移植 RT-Thread Nano 根据官方文档,需要1.取消生成HardFault_Handler、PendSV_Handler、SysTick_Handler三个中断函数
  • Project-Manager-Project 勾选Do not generate main()。
    主要是因为main函数需要自己写,不需要生成。
  • Project-Manager-Advanced Settings 所有函数勾选 Not Generate Function Call, 取消勾选 Visibility。
    主要是因为接入了rt-thread后,初始化工作需要在rt-thread初始化时进行,所以取消自动生成,并且把函数设置为non-static,全局可见。
  • Toolchain/IDE选择MDK-Keil,点击生成工程。



2.修改部分函数适配RT-thread

由于Modbus协议基于RT-thread,所以需要先稍稍修改一下RT-thread:

  1. board.c/rt_hw_board_init 函数中对MCU进行初始化,更改的后的rt_hw_board_init函数如下:
#include "main.h"
extern void SystemClock_Config(void);
extern void MX_GPIO_Init(void);
extern void MX_USART1_UART_Init(void);
extern UART_HandleTypeDef huart1;
/* 调试串口1接收数据的消息队列buffer */
static uint8_t consoleInputBuffer[256];
struct rt_messagequeue consoleInputMQ;

void rt_hw_board_init()
{
     
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
   
    /* 使用串口1作为调试串口,初始化一个消息队列保存串口1接收到的数据,并手动开启串口中断 */
    rt_err_t error = rt_mq_init(&consoleInputMQ,"consoleInputMQ",consoleInputBuffer,
                                1,sizeof(consoleInputBuffer),RT_IPC_FLAG_FIFO);
    RT_ASSERT(error == RT_EOK);
    SET_BIT(huart1.Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);
    
    /* System Clock Update */
    SystemCoreClockUpdate();
    /* System Tick Configuration */
    _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
    

    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

2.增加一个打印输出的函数 rt_hw_console_output ,位置随意,代码如下:

extern UART_HandleTypeDef huart1;
void rt_hw_console_output(const char *str)
{
     
    rt_size_t i = 0, size = 0;
    char a = '\r';

    __HAL_UNLOCK(&huart1);

    size = rt_strlen(str);
    for (i = 0; i < size; i++)
    {
     
        if (*(str + i) == '\n')
        {
     
            HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 50);
        }
        HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 50);
    }
}

3.增加一个接收输入的函数rt_hw_console_getchar,位置随意,代码如下:

char rt_hw_console_getchar(void){
     
    char ch = 0;
    rt_mq_recv(&consoleInputMQ, &ch, 1, 500);
    return ch;
}

4.串口1的中断函数中:

#include "rtthread.h"
extern struct rt_messagequeue consoleInputMQ;
void USART1_IRQHandler(void)
{
     
  /* USER CODE BEGIN USART1_IRQn 0 */
  if(huart1.Instance->SR & USART_SR_RXNE)
  {
     
    uint8_t data =  (uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF);
    rt_mq_send(&consoleInputMQ, &data, 1);
  }
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */
  /* USER CODE END USART1_IRQn 1 */
}

5.rtconfig.h中取消注释event和messagequeue:

/*rtconfig.h*/
// Using Event
//  Using Event
#define RT_USING_EVENT
// 

// Using Message Queue
//  Using Message Queue
#define RT_USING_MESSAGEQUEUE
// 
// 

6.在main.c中增加一个main函数:

int main(void) {
     
  while(1) {
     
    rt_thread_mdelay(500);
    __NOP();
  }
}

最后编译运行,在串口输出中应该能看到如下输出:

STM32 MODBUS协议-简介及接入 FreeMODBUS_第5张图片






3.将Modbus协议栈加入编译链,并移植代码

3.1 添加源文件:

先点击这里,添加一个Group
STM32 MODBUS协议-简介及接入 FreeMODBUS_第6张图片按F2对Group重命名为MODBUS
STM32 MODBUS协议-简介及接入 FreeMODBUS_第7张图片STM32 MODBUS协议-简介及接入 FreeMODBUS_第8张图片往MODBUS组里面添加源文件
STM32 MODBUS协议-简介及接入 FreeMODBUS_第9张图片由于这里我们仅仅需要实现MODBUS主机功能,所以只需要添加10个源文件:

 "./FreeModbus/modbus/mb_m.c"
 "./FreeModbus/modbus/rtu/mbcrc.c"
 "./FreeModbus/modbus/rtu/mbrtu_m.c"
 "./FreeModbus/modbus/functions/mbfuncother.c"
 "./FreeModbus/modbus/functions/mbfuncinput_m.c"
 "./FreeModbus/modbus/functions/mbutils.c"
 "./FreeModbus/port/rtt/port.c"
 "./FreeModbus/port/rtt/portevent_m.c"
 "./FreeModbus/port/rtt/portserial_m.c"
 "./FreeModbus/port/rtt/porttimer_m.c"

STM32 MODBUS协议-简介及接入 FreeMODBUS_第10张图片

3.2 添加头文件:

在option-include Paths中添加三个路径:

  "-IFreeModbus/port",
  "-IFreeModbus/modbus/include",
  "-IFreeModbus/modbus/rtu"

STM32 MODBUS协议-简介及接入 FreeMODBUS_第11张图片
STM32 MODBUS协议-简介及接入 FreeMODBUS_第12张图片



3.3 移植部分代码:

由于官方对于rtthread的移植默认采用的是rtthread的bsp框架,bsp框架接入比较复杂,暂且不讨论,这里我们需要把bsp的函数替换成我们的函数,位置在 portserial_m.c 中,
主要是把rtthread bsp相关的代码注释掉,替换成手动调用HAL库,整个文件改动比较大,注释部分为原有代码,见下面:

/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink 
 *
 * 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_m.c,v 1.60 2013/08/13 15:07:05 Armink add Master Functions $
 */

#include "port.h"

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

#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0
/* ----------------------- Static variables ---------------------------------*/
ALIGN(RT_ALIGN_SIZE)
/* software simulation serial transmit IRQ handler thread stack */
static rt_uint8_t serial_soft_trans_irq_stack[512];
/* software simulation serial transmit IRQ handler thread */
static struct rt_thread thread_serial_soft_trans_irq;
/* serial event */
static struct rt_event event_serial;
/* modbus master serial device */
// static rt_serial_t *serial;
/**/
extern UART_HandleTypeDef huart2;
static uint8_t uartRecvBuffer[64];
static uint8_t * uartRecvBufferPtr = uartRecvBuffer;

/* ----------------------- Defines ------------------------------------------*/
/* serial transmit event */
#define EVENT_SERIAL_TRANS_START    (1<<0)

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
static void prvvUARTRxISR(void);
static rt_err_t serial_rx_ind(rt_size_t size);
static void serial_soft_trans_irq(void* parameter);
extern void MX_USART2_UART_Init(void);
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
        eMBParity eParity)
{
     
//     /**
//      * set 485 mode receive and transmit control IO
//      * @note MODBUS_MASTER_RT_CONTROL_PIN_INDEX need be defined by user
//      */
//     rt_pin_mode(MODBUS_MASTER_RT_CONTROL_PIN_INDEX, PIN_MODE_OUTPUT);

//     /* set serial name */
//     if (ucPORT == 1) {
     
// #if defined(RT_USING_UART1) || defined(RT_USING_REMAP_UART1)
//         extern struct rt_serial_device serial1;
//         serial = &serial1;
// #endif
//     } else if (ucPORT == 2) {
     
// #if defined(RT_USING_UART2)
//         extern struct rt_serial_device serial2;
//         serial = &serial2;
// #endif
//     } else if (ucPORT == 3) {
     
// #if defined(RT_USING_UART3)
//         extern struct rt_serial_device serial3;
//         serial = &serial3;
// #endif
//     }
//     /* set serial configure parameter */
//     serial->config.baud_rate = ulBaudRate;
//     serial->config.stop_bits = STOP_BITS_1;
//     switch(eParity){
     
//     case MB_PAR_NONE: {
     
//         serial->config.data_bits = DATA_BITS_8;
//         serial->config.parity = PARITY_NONE;
//         break;
//     }
//     case MB_PAR_ODD: {
     
//         serial->config.data_bits = DATA_BITS_9;
//         serial->config.parity = PARITY_ODD;
//         break;
//     }
//     case MB_PAR_EVEN: {
     
//         serial->config.data_bits = DATA_BITS_9;
//         serial->config.parity = PARITY_EVEN;
//         break;
//     }
//     }
//     /* set serial configure */
//     serial->ops->configure(serial, &(serial->config));

//     /* open serial device */
//     if (!serial->parent.open(&serial->parent,
//             RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX )) {
     
//         serial->parent.rx_indicate = serial_rx_ind;
//     } else {
     
//         return FALSE;
//     }

	/* 手动调用HAL库 初始化串口,并开启串口中断 */
    MX_USART2_UART_Init();
    SET_BIT(huart2.Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);

    /* software initialize */
    rt_event_init(&event_serial, "master event", RT_IPC_FLAG_PRIO);
    rt_thread_init(&thread_serial_soft_trans_irq,
                   "master trans",
                   serial_soft_trans_irq,
                   RT_NULL,
                   serial_soft_trans_irq_stack,
                   sizeof(serial_soft_trans_irq_stack),
                   10, 5);
    rt_thread_startup(&thread_serial_soft_trans_irq);

    return TRUE;
}

void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
     
    rt_uint32_t recved_event;
    if (xRxEnable)
    {
     
        /* enable RX interrupt */
        // serial->ops->control(serial, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_FLAG_INT_RX);
        // /* switch 485 to receive mode */
        // rt_pin_write(MODBUS_MASTER_RT_CONTROL_PIN_INDEX, PIN_LOW);
        HAL_NVIC_EnableIRQ(USART2_IRQn);
    }
    else
    {
     
        /* switch 485 to transmit mode */
        // rt_pin_write(MODBUS_MASTER_RT_CONTROL_PIN_INDEX, PIN_HIGH);
        // /* disable RX interrupt */
        // serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *)RT_DEVICE_FLAG_INT_RX);
        HAL_NVIC_DisableIRQ(USART2_IRQn);
    }
    if (xTxEnable)
    {
     
        /* start serial transmit */
        rt_event_send(&event_serial, EVENT_SERIAL_TRANS_START);
    }
    else
    {
     
        /* stop serial transmit */
        rt_event_recv(&event_serial, EVENT_SERIAL_TRANS_START,
                RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, 0,
                &recved_event);
    }
}

void vMBMasterPortClose(void)
{
     
    // serial->parent.close(&(serial->parent));
}

BOOL xMBMasterPortSerialPutByte(CHAR ucByte)
{
     
    // serial->parent.write(&(serial->parent), 0, &ucByte, 1);
    HAL_UART_Transmit(&huart2, (uint8_t*)&ucByte, 1, 1);
    return TRUE;
}

BOOL xMBMasterPortSerialGetByte(CHAR * pucByte)
{
     
    // serial->parent.read(&(serial->parent), 0, pucByte, 1);
    *pucByte = *uartRecvBufferPtr--;
    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.
 */
void prvvUARTTxReadyISR(void)
{
     
    pxMBMasterFrameCBTransmitterEmpty();
}

/* 
 * 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.
 */
void prvvUARTRxISR(void)
{
     
    pxMBMasterFrameCBByteReceived();
}

/**
 * Software simulation serial transmit IRQ handler.
 *
 * @param parameter parameter
 */
static void serial_soft_trans_irq(void* parameter) {
     
    rt_uint32_t recved_event;
    while (1)
    {
     
        /* waiting for serial transmit start */
        rt_event_recv(&event_serial, EVENT_SERIAL_TRANS_START, RT_EVENT_FLAG_OR,
                RT_WAITING_FOREVER, &recved_event);
        /* execute modbus callback */
        prvvUARTTxReadyISR();
    }
}

/**
 * This function is serial receive callback function
 *
 * @param dev the device of serial
 * @param size the data size that receive
 *
 * @return return RT_EOK
 */
static rt_err_t serial_rx_ind(rt_size_t size) {
     
    prvvUARTRxISR();
    return RT_EOK;
}

/* 手动声明串口中断函数 */
void USART2_IRQHandler(void) {
     
  uint32_t isrflags   = READ_REG(huart2.Instance->SR);
  if((isrflags & USART_SR_RXNE) != RESET ) {
     
    *++uartRecvBufferPtr = huart2.Instance->DR;
    serial_rx_ind(0);
  }
}
#endif


还需要在 mbconfig.h 中关闭没有用到的功能函数,这里我们只用到了input功能。所以把input后面的宏定义值都改为0.

#define MB_FUNC_OTHER_REP_SLAVEID_BUF           ( 32 )
/*! \brief If the Report Slave ID function should be enabled. */
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED       (  1 )
/*! \brief If the Read Input Registers function should be enabled. */
#define MB_FUNC_READ_INPUT_ENABLED              (  1 )
/*! \brief If the Read Holding Registers function should be enabled. */
#define MB_FUNC_READ_HOLDING_ENABLED            (  0 )
/*! \brief If the Write Single Register function should be enabled. */
#define MB_FUNC_WRITE_HOLDING_ENABLED           (  0 )
/*! \brief If the Write Multiple registers function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED  (  0 )
/*! \brief If the Read Coils function should be enabled. */
#define MB_FUNC_READ_COILS_ENABLED              (  0 )
/*! \brief If the Write Coils function should be enabled. */
#define MB_FUNC_WRITE_COIL_ENABLED              (  0 )
/*! \brief If the Write Multiple Coils function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED    (  0 )
/*! \brief If the Read Discrete Inputs function should be enabled. */
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED    (  0 )
/*! \brief If the Read/Write Multiple Registers function should be enabled. */
#define MB_FUNC_READWRITE_HOLDING_ENABLED       (  0 )

最后还需要在 port.h 中修改一下:

//#include 
#include "main.h"



4.运行测试

main.c中添加测试代码:

/* 启动两个线程,一个线程用于MODBUS轮询 */
#include "mb.h"
#include "mb_m.h"
#include "user_mb_app.h"

/* MODBUS需要额外使用一个线程*/
static uint8_t modbusStatck[256] = {
     0};
static struct rt_thread modbusThread;
USHORT   usMRegInStart                              = M_REG_INPUT_START;
USHORT   usMRegInBuf[MB_MASTER_TOTAL_SLAVE_NUM][M_REG_INPUT_NREGS];

void modbusSend(void* param) {
     
  while(1) {
     
     eMBMasterPoll();
     rt_thread_mdelay(500);
  }
}
int main(void) {
     
  eMBMasterInit(MB_RTU, 1, 9600, 0);
  eMBMasterEnable();
  rt_err_t ret = rt_thread_init(&modbusThread, "modbus", modbusSend, NULL, modbusStatck, 
                                sizeof(modbusStatck), 9, 10);
  if(ret!=RT_EOK) {
     
    Error_Handler();
  }
  rt_thread_startup(&modbusThread);
  while(1) {
     
    rt_thread_mdelay(3000);
    eMBMasterReqReadInputRegister(1, 0, 10, 1000);
	rt_kprintf("MODBUS read input regitster 0 value:%d",  usMRegInBuf[0][0]);
  }
}

/* MODBUS读取回调函数 */
eMBErrorCode eMBMasterRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
     
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT *        pusRegInputBuf;
    USHORT          REG_INPUT_START;
    USHORT          REG_INPUT_NREGS;
    USHORT          usRegInStart;

    pusRegInputBuf = usMRegInBuf[ucMBMasterGetDestAddress() - 1];
    REG_INPUT_START = M_REG_INPUT_START;
    REG_INPUT_NREGS = M_REG_INPUT_NREGS;
    usRegInStart = usMRegInStart;

    /* it already plus one in modbus function method. */
    usAddress--;

    if ((usAddress >= REG_INPUT_START)
            && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS))
    {
     
        iRegIndex = usAddress - usRegInStart;
        while (usNRegs > 0)
        {
     
            pusRegInputBuf[iRegIndex] = *pucRegBuffer++ << 8;
            pusRegInputBuf[iRegIndex] |= *pucRegBuffer++;
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
     
        eStatus = MB_ENOREG;
    }

    return eStatus;
}


然后找个MODBUS从机模拟器:个人推荐这个 https://github.com/ClassicDIY/ModbusTool

直接下载安装后,设置寄存器0的值为11:
STM32 MODBUS协议-简介及接入 FreeMODBUS_第13张图片





最后运行结果如下:

STM32 MODBUS协议-简介及接入 FreeMODBUS_第14张图片
硬件连接如下STM32 MODBUS协议-简介及接入 FreeMODBUS_第15张图片最终项目代码:https://github.com/jiladahe1997/CSDN_Modbus_port_demo



如果觉得这 篇文章有帮助,或者有什么地方有疑问,欢迎在下面留言,我每周都会查看留言。

你可能感兴趣的:(嵌入式,STM32,嵌入式,物联网,stm32,modbus)