STM32 MODBUS协议-简介及接入 FreeMODBUS

简介

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

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

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

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




Modbus协议介绍

物理连接

Modbus的物理连接方式如下图所示:
在这里插入图片描述



软件架构

如果通过OSI七层网络模型来说的话,Modbus协议仅仅位于第二层:数据链路层。
在这里插入图片描述



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

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

废话少说,言归正传。看一下下面这张图:
在这里插入图片描述
上图表示的是:Modbus线上的信号是0/1的形式,Modbus协议会将多个0/1信号进行划分和组合,形成一个Modbus帧。一个Modbus帧就是一次Modbus请求或者Modbus回应

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

Modbus和http协议很像,一次请求,一次回应。
只能主机向从机发送一次Modbus请求,然后从机响应一次Modbus回应。
从机不能主动向主机发送任何信息。
在这里插入图片描述


寄存器

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上的移植。github地址:https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32

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



1.使用STM32CubeMX生成工程

  • 选择你的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*/
// <c1>Using Event
//  <i>Using Event
#define RT_USING_EVENT
// </c>

// <c1>Using Message Queue
//  <i>Using Message Queue
#define RT_USING_MESSAGEQUEUE
// </c>
// </h>

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

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

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

在这里插入图片描述






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

3.1 添加源文件:

先点击这里,添加一个Group
在这里插入图片描述按F2对Group重命名为MODBUS
在这里插入图片描述在这里插入图片描述往MODBUS组里面添加源文件
在这里插入图片描述由于这里我们仅仅需要实现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"

在这里插入图片描述

3.2 添加头文件:

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

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

在这里插入图片描述
在这里插入图片描述



3.3 移植部分代码:

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

/*
 * 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: 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 <em>Report Slave ID</em> function should be enabled. */
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED       (  1 )
/*! \brief If the <em>Read Input Registers</em> function should be enabled. */
#define MB_FUNC_READ_INPUT_ENABLED              (  1 )
/*! \brief If the <em>Read Holding Registers</em> function should be enabled. */
#define MB_FUNC_READ_HOLDING_ENABLED            (  0 )
/*! \brief If the <em>Write Single Register</em> function should be enabled. */
#define MB_FUNC_WRITE_HOLDING_ENABLED           (  0 )
/*! \brief If the <em>Write Multiple registers</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED  (  0 )
/*! \brief If the <em>Read Coils</em> function should be enabled. */
#define MB_FUNC_READ_COILS_ENABLED              (  0 )
/*! \brief If the <em>Write Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_COIL_ENABLED              (  0 )
/*! \brief If the <em>Write Multiple Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED    (  0 )
/*! \brief If the <em>Read Discrete Inputs</em> function should be enabled. */
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED    (  0 )
/*! \brief If the <em>Read/Write Multiple Registers</em> function should be enabled. */
#define MB_FUNC_READWRITE_HOLDING_ENABLED       (  0 )

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

//#include <stm32f10x_conf.h>
#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:
在这里插入图片描述





最后运行结果如下:

在这里插入图片描述
硬件连接如下在这里插入图片描述最终项目代码:https://github.com/jiladahe1997/CSDN_Modbus_port_demo



如果觉得这 篇文章有帮助,或者有什么地方有疑问,欢迎在下面留言,我每周都会查看留言。
  • 13
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: STM32F1_HAL库使用手册文件是STM32F1系列单片机的编程接口及其应用程序的软件开发包。该手册具有非常详细的介绍和说明,包括HAL库的功能、使用方法、配置及编译软件等多个方面。 首先,该手册详细讲解了STM32F1系列单片机的基本知识,如器件特性、体系结构和系统时钟等。其次,该手册介绍了HAL库的结构、API(应用程序接口)函数,文件和数据类型,并通过代码示例来演示如何使用HAL库进行应用程序的开发。此外,该手册还列出了各类功能实现的驱动库函数。 在使用HAL库进行开发时,手册中提供了充足的文献资料,可以帮助开发人员解决各种问题,例如在编写驱动程序时如何使用HAL中的定时计数器、串口转换器和DMA等。手册中还提供了各种应用示例,如PWM控制、定时器中断、SPI通讯和ADC采集等等,展示了HAL库在各种应用场景下的使用方法。 最后,该手册中还包含了硬件抽象层标准库的配置工具,如CubeMX和STM32工具箱,用于帮助开发人员更为快速地进行底层开发工作,降低了开发成本和时间成本。 综上所述,STM32F1系列单片机的HAL库使用手册文件是非常重要的开发工具。它深入浅出地解释了HAL库如何应用于STM32F1系列单片机开发。对于开发人员来说,熟练掌握该手册的内容,可以促进开发过程,提高开发效率。 ### 回答2: stm32f1_hal库是ST公司提供的一种硬件抽象层的库文件,支持对于STM32F1系列的微控制器进行控制并输出操作。这个编程库文件非常适合初学者或者是想要快速地进行STM32F1系列微控制器开发的程序员,因为它提供一种抽象的、高级的、更易于理解的方式来写代码。同时,stm32f1_hal库的使用手册文件非常重要,因为它是学习和使用stm32f1_hal库的关键,提供了详细的操作指南和样例代码。 stm32f1_hal库使用手册文件包含五个章节:库的概述、库的安装、库的使用、库的例子和库中的附录。第一个章节介绍了stm32f1_hal库的主要特性,这些特性包括高级的外设驱动、支持中斷实时和低功耗模式、易于使用和理解的API等等。 第二个章节讲解了如何在STM32F1系列微控制器中安装该库,此处需要注意的是不同的开发环境安装该库的方法可能不同。 第三个章节是重点所在,讲解了常见的库API及其使用方法,例如初始化外设、读取和写入数据。此处需要注意的是,代码中所调用的API需要根据不同的外设进行调整。 第四个章节列出了一些常见的例子,展示了如何使用stm32f1_hal库来实现不同的功能,例如LED、串口通信、时钟控制等等。 最后一个章节中提供了附录中的内容,为在实际开发中对库API的使用提供支持。总之,stm32f1_hal库使用手册文件是使用STM32F1微控制器开发的重要参考书,值得认真参考。 ### 回答3: stm32f1_hal库是STM32F1系列的外设驱动库。使用手册文件提供了完整的、详细的说明,包含了使用方法、函数及其参数的解释、编程范例等,是程序员使用stm32f1_hal库的重要参考资料。 手册文件主要介绍了STM32F1系列芯片的存储器、时钟、GPIO、中断、USART、SPI、I2C、DMA、ADC、DAC等各种外设的使用方法。通过手册,用户可以了解到如何对寄存器进行初始化,使外设工作正常。手册还提供了各种编程范例,程序员可以根据自己的需求进行调整和优化,大大提高了开发效率。 需要特别提醒的一点是,由于HAL库是由ST官方提供的驱动库,所以不同芯片的HAL库会有一些差别,用户在选择芯片型号后,务必下载对应的版本的手册。另外,由于HAL库是基于底层库的封装,对于一些特殊的需求,或者需要更高的性能的场合,程序员也可以直接使用底层库进行编程。 总之,stm32f1_hal库使用手册文件是STM32F1系列的外设驱动库的重要参考资料。对于初学者来说,掌握使用手册,能够快速地编写STM32F1的应用程序;对于有经验的开发者,可以通过更深入的阅读和理解手册,更好地优化应用程序,提高应用程序的稳定性和性能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值