基于STM32CubeMX移植freeModbus RTU(从站)-避坑篇

基于STM32CubeMX移植freeModbus RTU(从站)-避坑篇


FreeModbus源码结构分析

原文链接:https://blog.csdn.net/qq_36072849/article/details/124085920

目录结构
【demo】-各种平台的例程
【doc】-文档资料
【modbus】-核心源码
【tools】-相关工具

FreeModus的移植主要用到…\modbus目录和 …\demo\BARE目录下的内容

【modbus】文件夹直接导入到项目,基本不需要修改

【demo\BARE】文件夹下的demo.c是一个最基本的后台演示,主要实现协议栈的初始化、激活和运行,根据实际项目需求,实现对每个modbus功能码的处理逻辑。

port目录下是实现modbus协议栈的底层驱动支持
portevent.c 事件驱动
portserial.c 串口驱动
porttimer.c t3.5定时器驱动
其中portevent.c不用修改;portserial.c实现对应MCU下串口相关配置,包括端口初始化和中断配置,中断处理函数;porttimer.c 实现t3.5定时器初始化和相应的中断处理函数

协议栈内容主要参考解读mb.c,以RTU模式为例:
协议栈主要功能函数通过函数指针实现定义:

        case MB_RTU:
            pvMBFrameStartCur = eMBRTUStart;  //开启串口接收中断,开启t3.5
            pvMBFrameStopCur = eMBRTUStop;	//关闭串口中断,关闭t3.5
            peMBFrameSendCur = eMBRTUSend;  //发送数据,激活串口发送状态机
            peMBFrameReceiveCur = eMBRTUReceive; //处理接收事件
            pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
            pxMBFrameCBByteReceived = xMBRTUReceiveFSM; //串口接收状态机
            pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;//串口发送状态机
            pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; //t3.5超时处理

            eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
            break;

freemodbus基本是通过状态机+事件机制的巧妙结合实现协议栈的稳定运行。
在这里插入图片描述
协议栈主要运行流程
在这里插入图片描述
在这里插入图片描述
t3.5定时器在整个协议栈运行过程中扮演者重要的角色
在这里插入图片描述

[keil] Error: L6218E: Undefined symbol __aeabi_assert (referred from xxx.o).

原文链接:https://blog.csdn.net/qq_29246181/article/details/128777718

一、原因:引用#include <assert.h> 断言功能缺失
未定义的符号__aeabi_assert,原因是keil没有添加依赖项,请按图添加即可。
Compiler–I/O–STDERR
在这里插入图片描述

二、 官方解答
适用于:Arm编译器6,Keil MDK
保密性:客户非机密性
本知识库文章中的信息适用于:
Keil MDK 5
Arm编译器5
Arm编译器6
Arm MicroLIB C库

(1) 症状
使用Arm Compiler 6(或Arm Compiler 5)构建项目会产生以下链接错误:

Error: L6218E: Undefined symbol __aeabi_assert (referred from *.o)

(2)导致
如果在Target选项卡上的Project -> Options for Target ->下选择Use MicroLIB,可能会出现“Error: L6218E: Undefined symbol __aeabi_assert…”。

在这里插入图片描述
Microlib是一个独立的、比ARM标准C库更小的库。

为了节省大小,Arm MicroLIB C库不支持或实现几乎所有与操作系统交互的函数,例如abort()、exit()或assert()等函数。

(3)解决
有几个选项可以解决这个问题:

  1. 使用µVision中的Keil::ARM_Compiler Pack:
    打开“管理运行时环境”对话框,展开“编译器-> I/O”。
    在这里插入图片描述

  2. 在STDERR旁边,选中复选框。在变体列中,选择ITM。
    单击OK关闭对话框。

  3. 启用该软件组件包括Project Window的Compiler部分中的retarget_io.c文件。retarget_io.c文件包含一个__aeabi_assert()函数,
    如下所示:

__attribute__((weak,noreturn))
void __aeabi_assert (const char *expr, const char *file, int line) {
  char str[12], *p;

  fputs("*** assertion failed: ", stderr);
  fputs(expr, stderr);
  fputs(", file ", stderr);
  fputs(file, stderr);
  fputs(", line ", stderr);

  p = str + sizeof(str);
  *--p = '\0';
  *--p = '\n';
  while (line > 0) {
    *--p = '0' + (line % 10);
    line /= 10;
  }
  fputs(p, stderr);

  abort();
}

__attribute__((weak))
void abort(void) {
  for (;;);
}

或者在main.c中定义

void __aeabi_assert(const char *x1,const char *x2,int x3)
{
}
#endif

(重点)Chapter0 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇

原文链接:https://blog.csdn.net/xq1998/article/details/123707792

具体Freemodbus移植到STM32步骤参考:

STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤

基于STM32HAL库移植FreeModbus

FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)

移植过程各路网站上都比较多,具体可参考以上链接内容。把思路和步骤记录一下:
①下载复制Freemodbus库;
② 使用CubeMX生成工程,需要注意配置串口、timer,NVIC优先级串口高于timer,需要用到中断,但不需要生成IRQHandle(移植中有手动添加)
③添加Freemodbus中相关.c.h文件到工程,设置中配置c++ include目录
④移植的核心在于port.c,portserial.c,porttimer.c,portevent.c 四个文件的修改与配置;其中配置serial与time是关键,需修改portserial.c与porttimer.c,操作时,只需要修改为需要的串口和timer即可,其他内容不需要动。
⑤如果用到485收发芯片,需要添加收发控制端代码(具体可以参考下边问题1种内容)
⑥根据需要配置port.c文件,如果初步测试,不用修改
⑦编译下载,然后就可以测试了

整个移植过程其实不是太复杂(站在前人基础上),但还是容易出现一些问题,在此仅个人移植存在的问题,踩过的坑在这里记录一下

(1)Freemodbus移植到TTL的USART1可行,但改为485的USART2不行

具体和端口没关系,主要问题是端口使用到485芯片,需要有收发控制端的配置,并在适当时候打开即可;
这个问题具体操作是修改portserial.c文件中 vMBPortSerialEnable函数内容,添加打开接收或者打开发送控制端,具体需要根据芯片及接法有关。

/* ------------- Start implementation -------------------------*/
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    if(xRxEnable)
    {
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);// 使能接收非空中断
		HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);// 禁能接收非空中断	
    }
    
    if(xTxEnable)
    {
		HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
		__HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE);			// 使能发送为空中断	
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE);		// 禁能发送为空中断
    }
}

(2)485-Modbus 通讯 Timeout,数据发送不全或者结尾总是0xFF

这个问题的在不使用485芯片上一般不出现,在使用到485芯片的时候就比较明显,根源在于收发过程切换。仔细调试发现,MCU已经将数据传输出去,TTL口数据已经正常,但到485芯片后就出错,主要是MCU从发送到接收的转换太快,数据还没发送完全就将485的收发端口从发送转换为接收,造成最后一个字节数据没有从485芯片传输出去,造成出错。修改也比较简单,在切换之前进行适当延时,确保数据发送完成后在进行切换收发使能端口状态,即可解决。通过多方测试,感觉加到 cBOOL xMBRTUTransmitFSM( void )比较合适:增加语句【while(i<RS485_swtict_delay) i++; 】即可,具体位置看代码吧,其实加到相应的串口中断中也可以,不过可能影响效率。

关于RS485_swtict_delay 设置多大合适呢
经测试的波特率115200时,1000左右即可;波特率9600时,13000左右;这个延时时间与芯片延时有关系,具体以测试为准。

BOOL xMBRTUTransmitFSM( void )
{
    BOOL            xNeedPoll = FALSE;
	uint16_t        i = 0;
    assert( eRcvState == STATE_RX_IDLE );

    switch ( eSndState )
    {
    /* We should not get a transmitter event if the transmitter is in
     * idle state.  */
    case STATE_TX_IDLE:
        /* enable receiver/disable transmitter. */
        vMBPortSerialEnable( TRUE, FALSE );
        break;

    case STATE_TX_XMIT:
        /* check if we are finished. */
        if( usSndBufferCount != 0 )
        {
            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
            pucSndBufferCur++;  /* next byte in sendbuffer. */
            usSndBufferCount--;
        }
        else
        {
            xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
            /* Disable transmitter. This prevents another transmit buffer
             * empty interrupt. */
             * 
			//  ********看这里***** ↓ 边是重点
			
			while(i<RS485_swtict_delay) i++; //补充延时
			
			//  ********看这里***** ↑ 边是重点
			
            vMBPortSerialEnable( TRUE, FALSE );
            eSndState = STATE_TX_IDLE;
        }
        break;
    }

    return xNeedPoll;
}

(重点)Chapter1 基于STM32CubeMX移植freeModbus RTU(从站)

原文链接:https://blog.csdn.net/ASWaterbenben/article/details/105549750

困惑了将近一年多的ModbusRTU在我昨天穷极无聊给自己定目标的情况下搞出来了,以前移植不出来主要原因就是基本功不扎实,没有进一步理解串口和定时器配置的原理,一通操作,移植完之后就Timeout,接下来就分享一下我是怎么从0开始移植这个协议的。

项目已上传码云,文章底部有链接!

1.需要的材料

STM32开发板一块,不限型号
freeModbus包可进入后方链接下载(Modbus官方源码包)
STM32CubeMX

2.操作步骤

操作之前先讲两个主要问题

1.串口设置问题

MoubusRTU移植到stm32平台通信是通过串口进行通信,主要是需要串口进行收发,所以发送中断时必须的,在波特率设置问题上是和定时器相关联,在mbrtu.c文件的eMBRTUInit函数里具体说明了串口波特率和定时器设置的关系

eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    ULONG           usTimerT35_50us;

    ( void )ucSlaveAddress;
    ENTER_CRITICAL_SECTION(  );

    /* Modbus RTU uses 8 Databits. */
    if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
    {
        eStatus = MB_EPORTERR;
    }
    else
    {
        /* If baudrate > 19200 then we should use the fixed timer values
         * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
         */
        if( ulBaudRate > 19200 )
        {
            usTimerT35_50us = 35;       /* 1750us. */
        }
        else
        {
            /* The timer reload value for a character is given by:
             *
             * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
             *             = 11 * Ticks_per_1s / Baudrate
             *             = 220000 / Baudrate
             * The reload for t3.5 is 1.5 times this value and similary
             * for t3.5.
             */
            usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
        }
        if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
        {
            eStatus = MB_EPORTERR;
        }
    }
    EXIT_CRITICAL_SECTION(  );

    return eStatus;
}

从上面代码的注释中可以看出,当波特率大于19200时,超时时间固定位为1750us,当波特率小于19200时,超时时间为3.5个字符时间,具体计算公式在代码注释里已经有了,这里我就不多赘述。本人波特率使用115200,所以按照1750us来。

2.定时器设置问题

ModbusRTU是通过定时器和串口配合来实现Modbus通信的,所以定时器是决定有没有超时的一大关键问题,由串口设置部分可知,定时器设置是要配合串口设置的波特率食用比较香,所以根据我使用的115200波特率可以得到我定时器设置。首先是APB1的主频率获取到,modbus要求通过预分配后得到的周期为50us,对应频率为20KHz。根据rtu初始化代码得到自动重载值设置为35。

具体操作:

熟悉stm32cubemx的老司机可以直接从15步看起

1.选择MCU型号

2.使能时钟源RCC为外部时钟

3.配置时钟树,记录APB1频率

4.使能定时器4,预分频系数为3600-1,对应的分频频率为20KHz,不懂的回到上面去看定时器设置解析,自动重载值设置为35,得到超时时间1750us。

在这里插入图片描述

5.使能定时器中断

在这里插入图片描述

6.配置串口2,选择异步通信后参数设置为115200,8,NONE,1

在这里插入图片描述

7.使能串口中断

在这里插入图片描述

8.配置中断优先级,定时器中断优先级低于串口中断即可

在这里插入图片描述

9.配置项目参数并分离头文件和c文件后生成代码。

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

10.打开freeModbus代码包的demo文件夹,新建一个名为STM32MB的文件夹,之后将BARE文件夹内所有内容复制到STM32MB文件夹下,复制完成如图

在这里插入图片描述

11.回到freeModbus代码包,复制整个modbus文件夹也粘贴到STM32MB文件夹内,完成效果如图

在这里插入图片描述

12.将STM32MB文件夹移动到stm32cubeMX生成的工程目录下,如图

在这里插入图片描述

13.打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,

打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,MB内添加STM32MB文件夹下modbus文件夹内所有c文件以及根目录的demo.c文件,MB_Port内添加STM32MB文件夹下port文件夹内所有c文件,如图所示
在这里插入图片描述
在这里插入图片描述

14.修改demo.c文件夹的main函数名为host,编译不报错即可开始修改,如图所示

在这里插入图片描述
以下为正式修改Modbus代码,上面比较繁琐,熟悉stm32cubemx的老司机可以直接从15步看起

15.修改MB_Port下的portserial.c文件(串口设置)

我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注

#include "port.h"
#include "stm32f7xx_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)															//将串口收发中断和modbus联系起来,下面的串口改为自己使能的串口
			{
				__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);	//我用的是串口2,故为&huart2
			}
		else
			{
				__HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE);
			}
		if (xTxEnable)
			{
				__HAL_UART_ENABLE_IT(&huart2,UART_IT_TXE);
			}
		else
			{
				__HAL_UART_DISABLE_IT(&huart2,UART_IT_TXE);
			}	
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return TRUE;				//改为TURE,串口初始化在usart.c定义,mian函数已完成
}

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 (&huart2 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )	//添加发送一位代码
			return FALSE ;
		else
			return TRUE;
}

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 (&huart2 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )//添加接收一位代码
			return FALSE ;
	else
    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 )		//删去前面的static,方便在串口中断使用
{
    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 )				//删去前面的static,方便在串口中断使用
{
    pxMBFrameCBByteReceived(  );
}


16.修改MB_Port下的porttimer.c文件(定时器设置)

我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注

#include "port.h"
#include "stm32f7xx_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 )		//定时器初始化直接返回TRUE,已经在mian函数初始化过
{
    return TRUE;
}


inline void
vMBPortTimersEnable(  )		//使能定时器中断,我用的是定时器4,所以为&htim4
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
		__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);
		__HAL_TIM_ENABLE_IT(&htim4,TIM_IT_UPDATE);
		__HAL_TIM_SET_COUNTER(&htim4,0);
		__HAL_TIM_ENABLE(&htim4);
}

inline void
vMBPortTimersDisable(  )	//取消定时器中断
{
    /* Disable any pending timers. */
			__HAL_TIM_DISABLE(&htim4);
			__HAL_TIM_SET_COUNTER(&htim4,0);
			__HAL_TIM_DISABLE_IT(&htim4,TIM_IT_UPDATE);
			__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
//static 
void prvvTIMERExpiredISR( void )	//modbus定时器动作,需要在中断内使用
{
    ( void )pxMBPortCBTimerExpired(  );
}

17.修改完Modbus与stm32的接口文件之后要在port.h文件内定义总中断

位置在port.h文件的32行和33行,修改为如下所示,并在port.h前包含上stm32的hal库,如图所示

#define ENTER_CRITICAL_SECTION( )   __set_PRIMASK(1) 	 //关总中断
#define EXIT_CRITICAL_SECTION( )    __set_PRIMASK(0)   //开总中断

#include "stm32f7xx_hal.h"

在这里插入图片描述
modbus端口函数到此修改完成,接下来是中断函数

18.串口及定时器中断修改

打开工程内的中断文件,是在Application/User–>stm32f7xx_it.c
根据板子不同而不同,区别是stm32f后面的数字。知道是中断管理文件就行
在/* USER CODE BEGIN PFP */后添加以下代码,用于和modbus的串口和定时器功能代码联系

extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );

找到自己设置的串口中断处理函数,添加如下代码,用于将串口收到的内容移动到modbus功能函数进行处理

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
	if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET) 
		{
			prvvUARTRxISR();//接收中断
		}

	if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET) 
		{
			prvvUARTTxReadyISR();//发送中断
		}
	
  HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE END USART2_IRQn 1 */
}

在Application/User–>stm32f7xx_it.c末尾的/* USER CODE BEGIN 1 */添加定时器中断回调函数如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)	//定时器中断回调函数,用于连接porttimer.c文件的函数
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
  	prvvTIMERExpiredISR( );
}

到此,串口和定时器的问题已经处理完毕,接下来是modbus的配置

19.modbus功能处理

硬件接口方面结束之后就可以开始写功能了,在MB–>demo.c中有功能示例,我们根据功能示例来修改对应的功能并使能modbus,这里我只说输入寄存器功能,其它的一次类推,就不多赘述。
这里也是直接贴代码,大概说一下,就是自己设置一个数组,将数据放到数组内,并在被读取时根据数据位置将数据返回去

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

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 5

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

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 );
				for(i=0;i<usNRegs;i++)
				{
					*pucRegBuffer=InputBuff[i+usAddress-1]>>8;
					pucRegBuffer++;
					*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
					pucRegBuffer++;
				}
    }
    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;
}


20.modbus启动

启动modbus需要在main函数进行初始化、开启侦听操作,需要添加以下代码,对应位置可在mian函数找到

/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */

  /* USER CODE BEGIN 2 */
	eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。
	eMBEnable(  );//使能modbus
  /* USER CODE END 2 */

  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		( void )eMBPoll(  );//启动modbus侦听
  }
  /* USER CODE END 3 */

至此修改完毕,编译下载之后即可使用modbus poll进行连接测试。

21.modbus测试

将上述代码编译下载到板子,用TTL转USB接入PC,找到在PC的对应端口即可打开ModbusPoll进行通信测试
代码下载成功后打开ModbusPoll,打开读写定义并设置为从站地址1,功能04读输入寄存器,起始地址0,长度为4,如图所示
在这里插入图片描述
在这里插入图片描述
按F3进行连接,连接设置如图,串口所在位置会显示TTL转串口的芯片型号,按照如下设定后确定。
在这里插入图片描述
即可得到下图,由于我们输入寄存器存放的是16进制数,所以要将ModbusPoll显示模式改为16进制才能显示相同内容
在这里插入图片描述
在这里插入图片描述
最终效果如下图,ModbusPoll读取的值与STM32内寄存器内的值一致,读取成功!
在这里插入图片描述
至此,freeModbusRTU移植成功!,具体代码已上传,详见我的码云F7_ModbusRTU

Chapter2 基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植

原文链接:https://blog.csdn.net/qq_41252980/article/details/116709983

Chapter3 基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植

原文链接:https://blog.csdn.net/yiyimufeng/article/details/115966004

Chapter4 STM32CubeMX | STM32 HAL库移植FreeModbus详细步骤

原文链接:https://blog.csdn.net/qq153471503/article/details/104840279

Chapter5 STM32 移植FreeModbus详细过程

原文链接:https://blog.csdn.net/qq_40305944/article/details/107447042

3. 完善portserial.c文件

该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择usart1,波特率9600.

第一次打开这个文件,内容如下:

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return FALSE;
}

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. */
    return TRUE;
}

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.
     */
    return TRUE;
}

认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节等等。

完善后代码如下,也可以直接复制整个内容替换文件内容:


/*
 * 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: portserial.c,v 1.1 2006/08/22 21:35:13 wolti Exp $
 */

#include "port.h"
#include "stm32f10x.h"
#include "bsp_usart1.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.
     */
  //STM32串口接收中断使能
	if(xRxEnable == TRUE)
	{
		//UART中断使能
		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	}
	else
	{
	  //禁止接收和接收中断
		USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
	}
  //STM32串口发送中断使能
	if(xTxEnable == TRUE)
	{
	  //使能发送中断
		USART_ITConfig(USART1, USART_IT_TC, ENABLE);
	}
	else
	{
    //禁止发送中断
		USART_ITConfig(USART1, USART_IT_TC, DISABLE);
	}
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	//串口初始化
  USART1_Config((uint16_t)ulBaudRate);  
	USART_NVIC();
	return 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. */
	//串口发送函数
		USART_SendData(USART1, ucByte);
    return TRUE;
}

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.
     */
	//串口接收函数
  *pucByte = USART_ReceiveData(USART1); 
	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(  );
}

/**
  * @brief  This function handles usart1 Handler.
  * @param  None
  * @retval None
  */
//串口中断函数
void USART1_IRQHandler(void)
{
  //发生接收中断
  if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
  {
    prvvUARTRxISR(); //串口接收中断调用函数
    //清除中断标志位    
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);   
  }
	
	if(USART_GetITStatus(USART1, USART_IT_ORE) == SET)
  {  
    USART_ClearITPendingBit(USART1, USART_IT_ORE);
		prvvUARTRxISR(); 	//串口发送中断调用函数
  }
  
  //发生完成中断
  if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
  {
    prvvUARTTxReadyISR();
    //清除中断标志
    USART_ClearITPendingBit(USART1, USART_IT_TC);
  }
}

其中USART1_Config((uint16_t)ulBaudRate);和 USART_NVIC();是串口初始化的代码,我专门弄了一个.c和.h文件来放置这个文件,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,具体代码如下:

bsp_usart1.c


#include "bsp_usart1.h"

uint8_t SendBuff[SENDBUFF_SIZE];

/**
  * @brief  USART1 GPIO 配置,工作模式配置。9600 8-N-1
  * @param  无
  * @retval 无
  */
void USART1_Config(uint16_t buad)
{
		GPIO_InitTypeDef GPIO_InitStructure;
		USART_InitTypeDef USART_InitStructure;
		
		/* config USART1 clock */
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
		
		/* USART1 GPIO config */
		/* Configure USART1 Tx (PA.09) as alternate function push-pull */
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOA, &GPIO_InitStructure);    
		/* Configure USART1 Rx (PA.10) as input floating */
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
		GPIO_Init(GPIOA, &GPIO_InitStructure);
			
		/* USART1 mode config */
		USART_InitStructure.USART_BaudRate = buad;
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
		USART_InitStructure.USART_StopBits = USART_StopBits_1;
		USART_InitStructure.USART_Parity = USART_Parity_No ;
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
		//USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
		USART_Init(USART1, &USART_InitStructure); 
		USART_Cmd(USART1, ENABLE);
}

/**
  * @brief  USART1 中断 配置
  * @param  无
  * @retval 无
  */
void USART_NVIC(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* Configure one bit for preemption priority */
	//NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
  
  /* 配置中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

/// 重定向c库函数printf到USART1
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到USART1 */
		USART_SendData(USART1, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);		
	
		return (ch);
}

/// 重定向c库函数scanf到USART1
int fgetc(FILE *f)
{
		/* 等待串口1输入数据 */
		while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(USART1);
}
/*********************************************END OF FILE**********************/


bsp_usart1.h

#ifndef __USART1_H
#define	__USART1_H

#include "stm32f10x.h"
#include <stdio.h>

#define USART1_DR_Base  0x40013804		// 0x40013800 + 0x04 = 0x40013804
#define SENDBUFF_SIZE   5000

void USART1_Config(uint16_t buad);
void USART1_DMA_Config(void);
void USART_NVIC(void);

#endif /* __USART1_H */


4. 完善porttimer.c文件

modbus工作时需要一个定时器,所以这里配置一个定时器。定时器时基是50us,周期做为参数输入。这里注意我们这里面的inline void vMBPortTimersEnable( )以及inline void vMBPortTimersDisable( )函数需要去掉前面的inline,具体改好的代码如下:

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

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

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
  timer2_init(usTim1Timerout50us);
	timer2_nvic();
	return TRUE;
}


void vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  TIM_SetCounter(TIM2,0x0000); 
	TIM_Cmd(TIM2, ENABLE);
}

void vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
  TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
  TIM_SetCounter(TIM2,0x0000); 
  TIM_Cmd(TIM2, DISABLE);
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
		prvvTIMERExpiredISR();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

其中 timer2_init(usTim1Timerout50us) 和 timer2_nvic() 是timer2初始化函数,我也同样建立了一个.c和一个.h文件保存,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,内容如下:

bsp_timer2.c


#include "bsp_timer2.h"

void timer2_init(uint16_t period)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  TIM_DeInit(TIM2);
	TIM_TimeBaseStructure.TIM_Period = period;
  TIM_TimeBaseStructure.TIM_Prescaler = (1800 - 1);	
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	TIM_Cmd(TIM2, ENABLE);

}

void timer2_nvic(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	
	NVIC_Init(&NVIC_InitStructure);	
}


bsp_timer2.h

#ifndef __TIMER2_H
#define	__TIMER2_H

#include "stm32f10x.h"

void timer2_init(uint16_t period);
void timer2_nvic(void);

#endif /* __TIMER2_H */


这样,两个文件就补充好了,接下来需要我们将bsp_usart1.h和bsp_timer2.h的文件路径加入我们的工程,如下图:
在这里插入图片描述
此时编译会提示我们没有加入官方库函数,我们现在导入
在这里插入图片描述
我的提示官方标准库里面的一些关于tim函数没找到,代表我们没加入的stm32f10x_tim.c文件,加入就好了,你们的还可能提示关于usart的文件没找到,加入stm32f10x_usart.c就好,这个我就不写教程了,不会的话建议重新学stm32,加入后如图:
在这里插入图片描述
接下来在编译,会发现就只报4个错误了,提示4个文件未定义,如下图:
在这里插入图片描述
跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。

5、在main.c文件中定义各个模拟寄存器的地址和大小。

将下面的宏定义放到我们main.c 声明头文件结束之后

/* ----------------------- Defines ------------------------------------------*/
//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8

//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16

//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16

/* Private variables ---------------------------------------------------------*/
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;

//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;

//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};

6.补全输入寄存器操作函数、保持寄存器操作函数、线圈操作函数、离散寄存器函数

在main.c文件里面更改int main()函数对modbus功能进行初始化,设置地址和波特率。这部分内容可以参考官方资料里的例程,也可以直接复制别人写好的。这是我写好的代码:

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
		//初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验 	
		eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
		eMBEnable();   
		for(;;)
		{
			(void)eMBPoll();
		}
}

/*********************************************END OF FILE**********************/

/****************************************************************************
* 名	  称:eMBRegInputCB 
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读取的寄存器个数
* 出口参数:
* 注	  意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*								+StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*								+LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*								+CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*							3 区
****************************************************************************/
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++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegHoldingCB 
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister 
*													16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*													03 读保持寄存器 eMBFuncReadHoldingRegister
*													23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读写的寄存器个数
*						eMode: 功能码
* 出口参数:
* 注	  意:4 区
****************************************************************************/
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://读 MB_REG_READ = 0
        while(usNRegs > 0)
				{
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); 
          iRegIndex++;
          usNRegs--;					
				}                            
        break;
			case MB_REG_WRITE://写 MB_REG_WRITE = 0
				while(usNRegs > 0)
				{         
					usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
          usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
          iRegIndex++;
          usNRegs--;
        }				
			}
	}
	else//错误
	{
		eStatus = MB_ENOREG;
	}	
	
	return eStatus;
}

extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,
                UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );
/****************************************************************************
* 名	  称:eMBRegCoilsCB 
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*													05 写线圈 eMBFuncWriteCoil
*													15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 线圈地址
*						usNCoils: 要读写的线圈个数
*						eMode: 功能码
* 出口参数:
* 注	  意:如继电器 
*						0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
              eMBRegisterMode eMode )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //寄存器个数
  int16_t iNCoils = ( int16_t )usNCoils;
  //寄存器偏移量
  int16_t usBitOffset;
  
  //检查寄存器是否在指定范围内
  if( ( (int16_t)usAddress >= REG_COILS_START ) &&
     ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
  {
    //计算寄存器偏移量
    usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
    switch ( eMode )
    {
      //读操作
    case MB_REG_READ:
      while( iNCoils > 0 )
      {
        *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                         ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
        iNCoils -= 8;
        usBitOffset += 8;
      }
      break;
      
      //写操作
    case MB_REG_WRITE:
      while( iNCoils > 0 )
      {
        xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
                       ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
                       *pucRegBuffer++ );
        iNCoils -= 8;
      }
      break;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegDiscreteCB 
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注	  意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //操作寄存器个数
  int16_t iNDiscrete = ( int16_t )usNDiscrete;
  //偏移量
  uint16_t usBitOffset;
  
  //判断寄存器时候再制定范围内
  if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
     ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
  {
    //获得偏移量
    usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
    
    while( iNDiscrete > 0 )
    {
      *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
                                       ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
      iNDiscrete -= 8;
      usBitOffset += 8;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}


同时这里需要在main.c文件里面加入如下两个头文件

#include "mb.h"
#include "mbutils.h"

完整的main.c文件内容如下:

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "mb.h"
#include "mbutils.h"

/* ----------------------- Defines ------------------------------------------*/
//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8

//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16

//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16

/* Private variables ---------------------------------------------------------*/
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;

//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;

//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
		//初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验 	
		eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
		eMBEnable();   
		for(;;)
		{
			(void)eMBPoll();
		}
}

/*********************************************END OF FILE**********************/

/****************************************************************************
* 名	  称:eMBRegInputCB 
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读取的寄存器个数
* 出口参数:
* 注	  意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*								+StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*								+LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*								+CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*							3 区
****************************************************************************/
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++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegHoldingCB 
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister 
*													16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*													03 读保持寄存器 eMBFuncReadHoldingRegister
*													23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNRegs: 要读写的寄存器个数
*						eMode: 功能码
* 出口参数:
* 注	  意:4 区
****************************************************************************/
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://读 MB_REG_READ = 0
        while(usNRegs > 0)
				{
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); 
          iRegIndex++;
          usNRegs--;					
				}                            
        break;
			case MB_REG_WRITE://写 MB_REG_WRITE = 0
				while(usNRegs > 0)
				{         
					usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
          usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
          iRegIndex++;
          usNRegs--;
        }				
			}
	}
	else//错误
	{
		eStatus = MB_ENOREG;
	}	
	
	return eStatus;
}

extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,
                UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );
/****************************************************************************
* 名	  称:eMBRegCoilsCB 
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*													05 写线圈 eMBFuncWriteCoil
*													15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 线圈地址
*						usNCoils: 要读写的线圈个数
*						eMode: 功能码
* 出口参数:
* 注	  意:如继电器 
*						0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
              eMBRegisterMode eMode )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //寄存器个数
  int16_t iNCoils = ( int16_t )usNCoils;
  //寄存器偏移量
  int16_t usBitOffset;
  
  //检查寄存器是否在指定范围内
  if( ( (int16_t)usAddress >= REG_COILS_START ) &&
     ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
  {
    //计算寄存器偏移量
    usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
    switch ( eMode )
    {
      //读操作
    case MB_REG_READ:
      while( iNCoils > 0 )
      {
        *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                         ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
        iNCoils -= 8;
        usBitOffset += 8;
      }
      break;
      
      //写操作
    case MB_REG_WRITE:
      while( iNCoils > 0 )
      {
        xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
                       ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
                       *pucRegBuffer++ );
        iNCoils -= 8;
      }
      break;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegDiscreteCB 
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注	  意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //操作寄存器个数
  int16_t iNDiscrete = ( int16_t )usNDiscrete;
  //偏移量
  uint16_t usBitOffset;
  
  //判断寄存器时候再制定范围内
  if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
     ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
  {
    //获得偏移量
    usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
    
    while( iNDiscrete > 0 )
    {
      *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
                                       ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
      iNDiscrete -= 8;
      usBitOffset += 8;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}


如果你没有这些头文件:

#include "led.h"
#include "delay.h"
#include "sys.h"

就将其换成

#include "stm32f10x.h"

7. 修改mbrtu.c文件

否则modbus从机收到命令后,只会返回一次数据。在函数“eMBRTUSend”中,函数在mbrtu.c文件里面。修改后的代码如下:

eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          usCRC16;

    ENTER_CRITICAL_SECTION(  );

    /* Check if the receiver is still in idle state. If not we where to
     * slow with processing the received frame and the master sent another
     * frame on the network. We have to abort sending the frame.
     */
    if( eRcvState == STATE_RX_IDLE )
    {
        /* First byte before the Modbus-PDU is the slave address. */
        pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
        usSndBufferCount = 1;

        /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
        pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
        usSndBufferCount += usLength;

        /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
        usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );

        /* Activate the transmitter. */
        eSndState = STATE_TX_XMIT;

		//启动第一次发送,这样才可以进入发送完成中断
        xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
        pucSndBufferCur++;  /* next byte in sendbuffer. */
        usSndBufferCount--;
        
        //修改了从这里往下
        //使能发送状态,禁止接收状态
        vMBPortSerialEnable( FALSE, TRUE );
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

8. 修改mbconfig.h文件

取消对ASCII的支持。

#define MB_ASCII_ENABLED                        (  0 )

#define MB_ASCII_TIMEOUT_SEC                    (  0 )

这里再编译就会发现没有错误了,有4个警告,四个警告都是说与0比较无意义,因为我们四个寄存器的起始地址都为0x0000,所以这里不用管它,等哪天公司要求改地址啥的方好直接改宏定义就好了。
在这里插入图片描述
跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。

9. 修改一些细节,让我们读取写入的时候地址不会自动加一,使读写的地址准确:

需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c

直接去对应的文件搜索:usRegAddress++;
然后屏蔽掉。
mbfunccoils.c里面有三处
mbfuncdisc.c里面有一处
mbfuncholding.c里面有三处
mbfuncinput.c里面有一处
修改后就可以使读写地址不会自动加1了,如果疑问心比较强的同学也可以不屏蔽测测。

10. modbus通信格式以及寄存器功能码。

在这里插入图片描述

12. 广播地址回复设置

这里我们发送广播地址是没有回复的,想要有回复的话在mb.c文件里面的函数eMBPoll( void )里面屏蔽掉一个if条件,下面是我屏蔽好的。这个根据自己的需求来

eMBErrorCode
eMBPoll( void )
{
    static UCHAR   *ucMBFrame;
    static UCHAR    ucRcvAddress;
    static UCHAR    ucFunctionCode;
    static USHORT   usLength;
    static eMBException eException;

    int             i;
    eMBErrorCode    eStatus = MB_ENOERR;
    eMBEventType    eEvent;

    /* Check if the protocol stack is ready. */
    if( eMBState != STATE_ENABLED )
    {
        return MB_EILLSTATE;
    }

    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if( xMBPortEventGet( &eEvent ) == TRUE )
    {
        switch ( eEvent )
        {
        case EV_READY:
            break;

        case EV_FRAME_RECEIVED:
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
            if( eStatus == MB_ENOERR )
            {
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
                {
                    ( void )xMBPortEventPost( EV_EXECUTE );
                }
            }
            break;

        case EV_EXECUTE:
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
            eException = MB_EX_ILLEGAL_FUNCTION;
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
            {
                /* No more function handlers registered. Abort. */
                if( xFuncHandlers[i].ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
                {
                    eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
                    break;
                }
            }

            /*这里的代码含义,如果请求没有发送到广播地址我们返回一个回复。*/ 
            /*我屏蔽了,发送广播地址也要求回复*/
//            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
//            {
                if( eException != MB_EX_NONE )
                {
                    /* An exception occured. Build an error frame. */
                    usLength = 0;
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
                    ucMBFrame[usLength++] = eException;
                }
                if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
                {
                    vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
                }                
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
//            }
            break;

        case EV_FRAME_SENT:
            break;
        }
    }
    return MB_ENOERR;
}

(重点)Chapter6 【freeModbus】STM32之HAL库移植笔记(避坑主要内容之一)

原文链接:https://blog.csdn.net/peng19106/article/details/132052766

工作主要是传感器相关,常与之打交道的协议,莫过于MODBUS了。之前一直都是手撸相关功能码,所以也就没了解过类似freeModbus之类的,现在需要使用HAL库开发,且配置Modbus从机协议为全栈,最近趁着空余时间,学习一番。

(网上说好的移植简单快捷,结果照着各种教程配置,磕磕碰碰了小一周才搞定,在此记录下详细教程)

一、下载压缩包

官网下载地址:About - Embedded Experts (embedded-experts.at)

注:下拉页面,然后点击右下角的Downloads,然后点击红框选中,下载;

在这里插入图片描述

二、移植准备

解压后,我们会看到几个文件夹,但是对我们当前移植来说,有用的是modbus,以及demo下的BARE,我们新建一个工程文件夹,并在该工程目录下新建文件夹freemodbus,将上面所说的两个文件夹复制到这里,如下图:
在这里插入图片描述
图2 freemodbus解压缩后目录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图3 新建工程内的freemodbus文件内容

注:modbus与port文件没有进行内容增删,usModbus文件夹放置的是BARE文件夹下的demo.c,将其改名,并新增usModbus.h文件(为了后期其他项目方便移植使用);

三、新建工程

Modbus协议,我们需要知道的一个关键点是:超时时间;所以,我们需要为其配置一个定时器TIM,以及一个串口USART

定时器配置:
在这里插入图片描述

定时器的配置是比较关键的,freeModbus中,计时步长为50us,且当波特率大于19200时,固定超时时间为1750us(35*50us),19200及其以下的波特率则还按照波特率计算3.5字节的超时时间,我使用的定时器的时钟为84M,串口波特率为115200,所以配置如上;
在这里插入图片描述
图 超时时间设置来源

串口配置:
在这里插入图片描述
串口配置无须多言,选择Asynchronous后,因为使用115200波特率,所以其他的使用默认即可,不需要多余配置;

重点!重点!!重点!!!

不要开启选择的定时器以及串口中断啊,纯纯的都是血泪史,之前就是看教程没有这一步,浪费了好多时间;

在这里插入图片描述
到此为此,我们已经完成了工程配置,然后生成工程即可;

四、文件添加

(1)点击魔术棒右边的品字图形,打开Manage Project items,然后添加三个组,FreeModbus,modbus_port,usModbus,三个组下的文件分别来源于工程文件下freemodbus内的modbus,port,usmodbus
在这里插入图片描述
添加文件如上,其中usmodbus.c是由demo.c改名而来;

(2)点击魔术棒,然后在C/C++中,将包含了.h文件的路径都包含进去;
在这里插入图片描述

五、内容修改

战前准备:【main.h】
在这里插入图片描述
方便我们后面修改内容时调用;

1.首先我们修改的是串口配置文件[portserial.c]

这其中只有六个函数,如下:

/* 串口中断使能函数 */
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );
 
/* 串口初始化函数 */
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity );
 
/* 发送函数:将一个数据推至串口发送缓存 */
BOOL   
xMBPortSerialPutByte( CHAR ucByte );
 
/* 接收函数 */
BOOL
xMBPortSerialGetByte( CHAR * pucByte );
 
/* 串口中断发送处理函数 */
void prvvUARTTxReadyISR( void );
 
/* 串口中断接收函数 */
void prvvUARTRxISR( void );

我们首先在开头添加头文件[main.h]

#include "mb.h"
#include "mbport.h"
 
#include "main.h"       /* 添加,方便调用usart函数以及句柄 */

1.1 【vMBPortSerialEnable(BOOL , BOOL)】

这个函数主要是用于使能接收/发送中断,根据形参BOOL可知,仅由TRUE与FALSE两值;

在这里有个关键点,在于发送标志用的是UART_IT_TXE还是UART_IT_TC,特别是Modbus协议是通过RS485去通讯的;

UART_IT_TC:发送数据完成;

UART_IT_TXE:发送寄存器空,但是不代表已经将数据发送出去,所以将RS485的发送/接收使能引脚跳变为接收前,需要添加一个小延迟,否则会导致个别数据包丢失最后一个字节;

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
	if(xRxEnable)
	{
        RS485_Receive_ENABLE();
		__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
	}
	
	
	if(xTxEnable)
	{
        RS485_Transmit_ENABLE();
		__HAL_UART_ENABLE_IT(&huart1,UART_IT_TC);
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_TC);
	}
}

1.2【xMBPortSerialInit】

串口初始化函数,因为我们在cubeMX中配置好工程后,已经自动进行了串口引脚及参数的配置,所以在这里我们不需要多余配置,只需要把NVIC打开即可(很重要);

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    /**
      *  \note    我们在配置时关闭了中断,是因为我们需要手动的去开始关闭
      ***/
	HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
	
	__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
	__HAL_UART_DISABLE_IT(&huart1,UART_IT_TC);
	
    return TRUE;
}

1.3【xMBPortSerialPutByte】

将一个字节推至串口发送缓冲中

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. */
	
		USART1->DR = ucByte;
		return TRUE;
}

1.4【xMBPortSerialGetByte】

从串口接收缓冲中读出一个字节

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.
     */
		
		
		*pucByte = (USART1->DR & (uint16_t)0x00ff);
    return TRUE;
}

1.5 接收/发送中断处理函数

prvvUARTTxReadyISR(),prvvUARTRxISR()两个函数不需要改动,只需要将[static]关键字屏蔽即可;

/* 函数声明:在文件的开头,记得将static关键字屏蔽 */
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );
 
 
 
void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}
 
void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

然后将prvvUARTTxReadyISR(),prvvUARTRxISR()两个函数添加至main.h中;

1.6 中断调用

freeModbus的接收和发送都是在中断中完成的,所以当我们配置好串口的各个函数后,也需要去完成串口中断函数,如下:

void USART1_IRQHandler(void)
{
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET)
	{
		prvvUARTRxISR();
		__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
	}
	
	
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC) != RESET)
	{
		prvvUARTTxReadyISR();
		__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_TC);
	}
}

2.接下来我们需要配置的是定时器,即【porttimer.c】文件

熟悉的环节,我们需要了解这个文件中包含了哪些函数

/* 定时器初始化 */
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us );
 
/* 定时器使能 */
void vMBPortTimersEnable(  );         //inline关键字屏蔽
 
/* 定时器失能 */
void vMBPortTimersDisable(  );        //inline关键字屏蔽
 
/* 超时处理函数 */
void prvvTIMERExpiredISR( void );

然后,在开头将[main.h]头文件包含进去

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

2.1定时器初始化【xMBPortTimersInit】

定时器的初始化不需要多余的配置,只需要将中断打开即可,并且返回true。

注:串口和定时器初始化中最重要的,就是配置并打开中断,否则程序将无法正常运行;

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
		
	__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
	__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
	
    return TRUE;
}

2.2定时器使能/失能【vMBPortTimersEnable】

接下来是配置定时器的使能/失能函数,配置完后,记得将inline关键字屏蔽;

/* 定时器使能 */
void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	
		__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_SetCounter(&htim3,0);
		__HAL_TIM_ENABLE(&htim3);
	
}
 
/* 定时器失能 */
void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
__HAL_TIM_DISABLE(&htim3);
//		__HAL_TIM_ENABLE(&htim3);
//		__HAL_TIM_SetCounter(&htim3,0);
//		__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
//		__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);
}

2.3超时处理函数【prvvTIMERExpiredISR】

超时处理函数是放置在定时器中断函数中,这个函数中我们不需要增删内容,只需要将static关键字屏蔽即可,方便我们后面在中断函数中调用,然后将该函数放置于main.h函数中;

/* 函数声明,放置在程序牵头 */
void prvvTIMERExpiredISR( void );
 
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

至此,porttimer.c的修改就完成了,接下来就是定时器中断函数配置;

2.4中断调用

在cubemx配置时,已经将定时器配置为1750us(35*50us)了,所以在定时器中断中,我们不需要再去计算时间,当TIM_FLAG_UPDATE标志置位时,我们直接调用prvvTIMERExpiredISR()即可;

void TIM3_IRQHandler(void)
{
	if(__HAL_TIM_GET_FLAG(&htim3,TIM_FLAG_UPDATE) != RESET)
	{
		
		prvvTIMERExpiredISR();
		
		__HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
	}
}

3.从机地址修改

freeModbus的函数中,对从机地址进行了一次usRegAddress++;在这里,我们不需要这个机制,所以,crtl+F,在find in files中搜索[usRegAddress++],然后全部屏蔽;
在这里插入图片描述
在这里插入图片描述

4.Modbus ASCII

这次我们只是需要移植RTU功能,不需要用到Modbus ascii功能,所以直接关闭即可,随便找一个文件,将头文件包含进去,即#include “mbconfig.h”,然后鼠标右键跳转至该文件中,将line 49的MB_ASCII_ENABLED设置为0,
在这里插入图片描述

5.发送中断修复

打开[mbrtu.c]文件,然后下拉到line 213,即eMBRTUSend()函数之中,然后插入图中红框内容,插入内容的作用是将一个带发送字节的数据推入串口数据寄存器中,触发串口的发送中断,实现从机对主机指令的响应;
在这里插入图片描述

6.点开魔术棒,勾选Use MicroLIB,很重要的一步!!!

在这里插入图片描述

7.[demo.c]

打开demo.c文件,删除该文件中的main函数,保留其他函数。小小修改以下方便测试,

起始地址为0x01;

寄存器数量为0x0004;

寄存器保存值为0x01,0x02,0x03,0x04
在这里插入图片描述

8.[main.h]

main.h中新增一个头文件

#include "mb.h"

六、测试

至此,我们已经完成了freemodbus移植文件的所有修改,剩下的,即是初始化以及调用了;
在这里插入图片描述
我们进入main()函数,在while(1)之前调用freemodbus的初始化函数eMBInit()与使能函数eMBEnable();

/**
  * 第一个参数:(eMode)MB_RTU,表示freeModbus初始化模式为Modbus RTU
  * 第二个参数:(ucSlaveAddress)0x01,表示从机地址为0x01;
  * 第三个参数:(ucPort)0x01,使用串口1;
  * 第四个参数:(ulBaudRate)115200,表示波特率为115200;
  * 第五个参数:(eParity)MB_PAR_NONE,表示无校验码;
  **/
eMBInit(MB_RTU,0X01,0X01,115200,MB_PAR_NONE);
eMBEnable();    /* freeModbus 使能 */

在while(1)中,然后使用eMBPoll()函数轮询;

使用Modbus Poll工具来测试,测试结果如下:
在这里插入图片描述
至此,freemodbus的移植以及测试已经完成;

Chapter7 【stm32】FreeModbus 介绍 + 移植stm32f103 HAl库

原文链接:https://blog.csdn.net/tao475824827/article/details/126188867

1. 背景

最近朋友让帮忙做个小东西,需要用到modbus协议。RTU的协议之前用过,也总结过相关的知识点,具体见: Modbus RTU协议各知识点入门 + 实例.
之前项目用的命令很固定,就自己写了一个实现。现在这边要实现完整的协议栈,果断去找一些轮子来装。

2. FreeModbus介绍

Modbus相关的协议栈有不少轮子,比较有名是 LibModbus 和 FreeModbus。
LibModbus主要用在linux环境,而FreeModbus主要用在mcu环境,资源占用会小一些。
官方的FreeModbus只到v1.5,且只支持从机免费,做主机的功能要收费。
cwalter - FreeModbus v1.5
后来有兄弟基于这个开发了自己的v1.6,主从机都免费。
armlink- FreeModbus v1.6
我这边的话,因为只做从机,就下载了v1.5来用的。

3. 移植步骤

移植基于的原子的stm32f103zet6 精英板,串口2,用的开发库是HAL库。

3.1 下载代码 & 文件夹格式

从官网或者Github下载到源码, 进入文件夹,扫一下所有文件:
主要文件夹4个,

在这里插入图片描述
Demo中主要放了不同平台下的移植文件,以bare文件夹为例

BARE
	| - port 
	|    | - portevent.c
	|    | - portserial.c
	|    | - porttimer.c
	| - demo.c
	| - Makefile

这些就是移植层需要我们适配的接口了。其中 event基本不改。主要就是适配串口接口 和 定时器 接口。
然后选择demo.c 里面的

tools:工具
modbus:主要的协议栈实现,实际上不怎么需要修改
doc:文档

3.2 移植代码

主要我们需要修改的就是 串口的driver支持,定时器的driver支持,以及对应寄存器的设置。
网上的代码实在太多了。具体可以参见我的参考链接2。
这里我也把我的HAL库移植过的程序放上来,供大家参考。

3.2.1 串口
串口是我们使用modbus RTU的物理接口。
需要在portserial.c 中进行适配。
主要是实现:

串口初始化
发一个字节
收一个字节
中断实现,直接写中断函数或者用回调函数
代码如下:

void USART2_RS485_Init(u32 bound)
{
    //GPIO端口设置
    // PA2 TX
    // PA3 RX
    // PD7 DE
	GPIO_InitTypeDef GPIO_Initure;
	
	__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟
	__HAL_RCC_GPIOD_CLK_ENABLE();			//使能GPIOD时钟
	__HAL_RCC_USART2_CLK_ENABLE();			//使能USART2时钟
	
	GPIO_Initure.Pin=GPIO_PIN_2; 			//PA2
	GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出
	GPIO_Initure.Pull=GPIO_PULLUP;			//上拉
	GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
	HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA2
	
	GPIO_Initure.Pin=GPIO_PIN_3; 			//PA3
	GPIO_Initure.Mode=GPIO_MODE_AF_INPUT;	//复用输入
	HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA3
	
	//PD7推挽输出,485模式控制  
    GPIO_Initure.Pin=GPIO_PIN_7; 			//PD7
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOD,&GPIO_Initure);
    
    //USART 初始化设置
	G_USART2_RS485Handler.Instance=USART2;			        //USART2
	G_USART2_RS485Handler.Init.BaudRate=bound;		        //波特率
	G_USART2_RS485Handler.Init.WordLength=UART_WORDLENGTH_8B;	//字长为8位数据格式
	G_USART2_RS485Handler.Init.StopBits=UART_STOPBITS_1;		//一个停止位
	G_USART2_RS485Handler.Init.Parity=UART_PARITY_NONE;		//无奇偶校验位
	G_USART2_RS485Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;	//无硬件流控
	G_USART2_RS485Handler.Init.Mode=UART_MODE_TX_RX;		    //收发模式
	HAL_UART_Init(&G_USART2_RS485Handler);			        //HAL_UART_Init()会使能USART2
    
  __HAL_UART_DISABLE_IT(&G_USART2_RS485Handler,UART_IT_TC);

	__HAL_UART_ENABLE_IT(&G_USART2_RS485Handler,UART_IT_RXNE);//开启接收中断
	HAL_NVIC_EnableIRQ(USART2_IRQn);				        //使能USART1中断
	HAL_NVIC_SetPriority(USART2_IRQn,3,3);			        //抢占优先级3,子优先级3

	USART2_RS485_TX_EN=0;											//默认为接收模式		
}

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


extern UART_HandleTypeDef G_USART2_RS485Handler;
#define	MODBUS_UART	G_USART2_RS485Handler


/* ----------------------- 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(&MODBUS_UART, UART_IT_RXNE);		 //使能接收寄存器非空中断
        //HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
		USART2_RS485_TX_EN = 0;
		}
    else
    {
        __HAL_UART_DISABLE_IT(&MODBUS_UART, UART_IT_RXNE);		//禁能接收寄存器非空中断		
        //HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
    	USART2_RS485_TX_EN = 1;

	}
    
    if (TRUE == xTxEnable)
    {
        //HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
		USART2_RS485_TX_EN = 1;
		__HAL_UART_ENABLE_IT(&MODBUS_UART, UART_IT_TC);      //使能发送完成中断
    }
    else
    {
        USART2_RS485_TX_EN = 0;
        __HAL_UART_DISABLE_IT(&MODBUS_UART, UART_IT_TC);   //禁能发送完成中断
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{

		BOOL ret = TRUE;
	
		USART2_RS485_Init(ulBaudRate);
		
		return ret;
}

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. */

    //HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
  	USART2_RS485_TX_EN = 1;
  
#if 0
		HAL_UART_Transmit_IT(&MODBUS_UART, (uint8_t *)&ucByte, 1);
#else
    USART2->DR = ucByte;
#endif
    return TRUE;
}

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.
     */
  
	// HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
  USART2_RS485_TX_EN = 0;
  
	*pucByte = (USART2->DR & (uint16_t)0x00FF); 

	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(  );
}

void USART2_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&MODBUS_UART, UART_FLAG_RXNE))			// 接收非空中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&MODBUS_UART, UART_FLAG_RXNE);			// 清除中断标记
        prvvUARTRxISR();										// 通知modbus有数据到达
    }

    if(__HAL_UART_GET_FLAG(&MODBUS_UART, UART_FLAG_TXE))				// 发送为空中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&MODBUS_UART, UART_FLAG_TXE);			// 清除中断标记
        prvvUARTTxReadyISR();									// 通知modbus数据可以发松
    }
}

3.2.2 定时器
定时器主要是用于计算3.5位宽的一个时长,每次接受到一个字节的时候,会把定时器的count值清零,重新计时。
连续3.5位宽的时间内

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{

	  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
		
    TIM3_Handler.Instance = TIM3;
    TIM3_Handler.Init.Prescaler = 3600 - 1;		                                // 72M 时钟,72m/3600 = 10k 频率,  50us记一次数
    TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM3_Handler.Init.Period = usTim1Timerout50us - 1;											// usTim1Timerout50us * 50即为定时器溢出时间
    TIM3_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;		
    if (HAL_TIM_Base_Init(&TIM3_Handler) != HAL_OK)
    {
        return FALSE;
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&TIM3_Handler, &sClockSourceConfig) != HAL_OK)
    {
        return FALSE;
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&TIM3_Handler, &sMasterConfig) != HAL_OK)
    {
        return FALSE;
    }
    __HAL_TIM_CLEAR_FLAG(&TIM3_Handler, TIM_FLAG_UPDATE);              // 先清除一下定时器的中断标记,防止使能中断后直接触发中断
    __HAL_TIM_ENABLE_IT(&TIM3_Handler, TIM_IT_UPDATE);	                    // 使能定时器更新中断
    HAL_TIM_Base_Start_IT(&TIM3_Handler);		
	


    return TRUE;

}

/* HAL_TIM_Base_Init 里会调用这个 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();            
		HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    
		HAL_NVIC_EnableIRQ(TIM3_IRQn);          
	}
}

 void vMBPortTimersEnable()
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	__HAL_TIM_CLEAR_IT(&TIM3_Handler,TIM_IT_UPDATE);
	__HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);
	__HAL_TIM_SET_COUNTER(&TIM3_Handler, 0);	// 清空计数器
    __HAL_TIM_ENABLE(&TIM3_Handler);     // 使能定时器	
}

 void vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	__HAL_TIM_DISABLE(&TIM3_Handler);	// 禁能定时器
	__HAL_TIM_SET_COUNTER(&TIM3_Handler,0);
	__HAL_TIM_DISABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);
	__HAL_TIM_CLEAR_IT(&TIM3_Handler,TIM_IT_UPDATE);	
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );			/* 指向 xMBRTUTimerT35Expired */
}

// 定时器5中断服务程序
//void TIM5_IRQHandler(void)
void TIM3_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&TIM3_Handler, TIM_FLAG_UPDATE)) // 更新中断标记被置位
    {
        __HAL_TIM_CLEAR_FLAG(&TIM3_Handler, TIM_FLAG_UPDATE);// 清除中断标记
    	prvvTIMERExpiredISR();	// 通知modbus3.5个字符等待时间
    }
}

一个要注意的点就是记得把定时器的时钟 和 中断打开。
网上一些移植程序这里没写,应该是用cudeIDE直接生成的代码里默认打开了。
如果是自己写的记得开一下,不然进不去中断。
就是加上这个函数HAL_TIM_Base_MspInit, 会在 HAL_TIM_Base_Init 的 时候自动调用。

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();            
		HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    
		HAL_NVIC_EnableIRQ(TIM3_IRQn);          
	}
}

Chapter8 特别注意

原文链接:https://blog.csdn.net/weixin_43724208/article/details/108331049

这里使能了串口的“发送完成中断”,而不是官方代码建议的“发送为空中断”。由于单片机复位后,发送完成标志位TC置0,或者由于上一次发送最后一个字节时,发送中断中清除了TC标志位,所以无法进入USART2_IRQHandler()函数中启动发送。因此,在eMBPoll()函数中插入如下代码,发送一帧数据中的第一个字节,触发发送完成中断,随后就会自动完成一帧数据的发送。

eMBErrorCode
eMBPoll( void )
{
    static UCHAR   *ucMBFrame;
    static UCHAR    ucRcvAddress;
    static UCHAR    ucFunctionCode;
    static USHORT   usLength;
    static eMBException eException;
    int             i;
    eMBErrorCode    eStatus = MB_ENOERR;
    eMBEventType    eEvent;
    /* Check if the protocol stack is ready. */
    if( eMBState != STATE_ENABLED )
    {
        return MB_EILLSTATE;
    }
    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if( xMBPortEventGet( &eEvent ) == TRUE )
    {
        switch ( eEvent )
        {
        case EV_READY:
            break;
        case EV_FRAME_RECEIVED:
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
            if( eStatus == MB_ENOERR )
            {
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
                {
                    ( void )xMBPortEventPost( EV_EXECUTE );
                }
            }
            break;
        case EV_EXECUTE:
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
            eException = MB_EX_ILLEGAL_FUNCTION;
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
            {
                /* No more function handlers registered. Abort. */
                if( xFuncHandlers[i].ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
                {
                    eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
                    break;
                }
            }
            /* If the request was not sent to the broadcast address we
             * return a reply. */
            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
            {
                if( eException != MB_EX_NONE )
                {
                    /* An exception occured. Build an error frame. */
                    usLength = 0;
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
                    ucMBFrame[usLength++] = eException;
                }
                if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
                {
                    vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
                }                
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
                //插入代码 begin *******************************************************
                if( eStatus == MB_ENOERR ) //no error
                 {
                   xMBRTUTransmitFSM();  //发送一帧数据中的第一个字节,触发发送完成中断
                 }
				//插入代码 end *******************************************************
            }
            break;
        case EV_FRAME_SENT:
            break;
        }
    }
    return MB_ENOERR;
}

Chapter9 个人移植总结

在这里插入图片描述

portserial.c



/*
 * 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$
 */
#include "port.h"

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

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR(void);
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) //将串口收发中断和modbus联系起来,下面的串口改为自己使能的串口
		{
		__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //我用的是串口1,故为&huart1
		}
	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);
		}

}


BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{
	HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
	HAL_NVIC_EnableIRQ(USART1_IRQn);

	__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
	__HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);

	return 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;

}


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;

}


/* 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) //删去前面的static,方便在串口中断使用
{
	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.
 */
void prvvUARTRxISR(void) //删去前面的static,方便在串口中断使用
{
	pxMBFrameCBByteReceived();
}

porttimer.c

/*
 * 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$
 */

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

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

/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0);
		HAL_NVIC_EnableIRQ(TIM3_IRQn);
			
		__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
		
		return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_SetCounter(&htim3,0);
		__HAL_TIM_ENABLE(&htim3);

}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
		__HAL_TIM_DISABLE(&htim3);
		__HAL_TIM_SetCounter(&htim3,0);
		__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
		__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);

}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

port.h

/*
 * 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 <stdio.h>
//#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#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

usmodbus.h

/*
 * 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$
 */

#ifndef _USMODBUS_H
#define _USMODBUS_H

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

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 	0x0001
#define REG_INPUT_NREGS 	0x0004

#define REG_HOLDING_START 1
#define REG_HOLDING_NREGS 100


/* ----------------------- Static variables ---------------------------------*/
extern USHORT   usRegInputBuf[REG_INPUT_NREGS];
extern USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];


#endif

demo.c(usmodbus.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"
#include "usmodbus.h"

/* ----------------------- Defines ------------------------------------------*/
//#define REG_INPUT_START 	0x0001
//#define REG_INPUT_NREGS 	0x0004
//
//#define REG_HOLDING_START 1
//#define REG_HOLDING_NREGS 100


/* ----------------------- Static variables ---------------------------------*/
USHORT   usRegInputStart = REG_INPUT_START;
USHORT   usRegInputBuf[REG_INPUT_NREGS] = {0x01, 0x02, 0x03, 0x04};

USHORT   usRegHoldingStart = REG_HOLDING_START;
USHORT   usRegHoldingBuf[REG_HOLDING_NREGS]={0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00};

/* ----------------------- Start implementation -----------------------------*/
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]++;
    }
}

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


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

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

stm32f4xx_it.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    stm32f4xx_it.c
  * @brief   Interrupt Service Routines.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );


/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */

/* USER CODE END TD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/
extern TIM_HandleTypeDef htim2;
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M4 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
  while (1)
  {
  }
  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles Hard fault interrupt.
  */
void HardFault_Handler(void)
{
  /* USER CODE BEGIN HardFault_IRQn 0 */

  /* USER CODE END HardFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_HardFault_IRQn 0 */
    /* USER CODE END W1_HardFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Pre-fetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles System service call via SWI instruction.
  */
void SVC_Handler(void)
{
  /* USER CODE BEGIN SVCall_IRQn 0 */

  /* USER CODE END SVCall_IRQn 0 */
  /* USER CODE BEGIN SVCall_IRQn 1 */

  /* USER CODE END SVCall_IRQn 1 */
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/**
  * @brief This function handles Pendable request for system service.
  */
void PendSV_Handler(void)
{
  /* USER CODE BEGIN PendSV_IRQn 0 */

  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
}

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles TIM2 global interrupt.
  */
void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */

  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */

  /* USER CODE END TIM2_IRQn 1 */
}

/* USER CODE BEGIN 1 */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_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 USART2_IRQn 1 */
}

void TIM3_IRQHandler(void)
{

	  if(__HAL_TIM_GET_FLAG(&htim3,TIM_FLAG_UPDATE) != RESET)
	  {
		  
		  prvvTIMERExpiredISR();
		  
		  __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
	  }

}


/* USER CODE END 1 */

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
#include "usmodbus.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint16_t led_ctrl = 0x0000;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
//	__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
//	__HAL_UART_DISABLE(&huart1);
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
	HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);

	eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。
	eMBEnable(	);//使能modbus


  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	( void )eMBPoll(  );//启动modbus侦听
	led_ctrl = usRegHoldingBuf[99];
	if(led_ctrl & 0x0001)
		HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
	if(led_ctrl & 0x0008)
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);

	
//	HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
//  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
//  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

//  /** Configure the main internal regulator output voltage
//  */
//  __HAL_RCC_PWR_CLK_ENABLE();
//  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
//  /** Initializes the RCC Oscillators according to the specified parameters
//  * in the RCC_OscInitTypeDef structure.
//  */
//  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
//  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
//  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
//  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
//  RCC_OscInitStruct.PLL.PLLM = 4;
//  RCC_OscInitStruct.PLL.PLLN = 168;
//  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
//  RCC_OscInitStruct.PLL.PLLQ = 4;
//  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
//  {
//    Error_Handler();
//  }
//  /** Initializes the CPU, AHB and APB buses clocks
//  */
//  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
//                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
//  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
//  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
//  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
//  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV4;

//  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
//  {
//    Error_Handler();
//  }
	RCC_OscInitTypeDef RCC_OscInitStruct;
	RCC_ClkInitTypeDef RCC_ClkInitStruct;

	__HAL_RCC_PWR_CLK_ENABLE(); 					//使能PWR时钟

	__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); //设置调压器输出电压级别1

	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 外部晶振,8MHz
	RCC_OscInitStruct.HSEState = RCC_HSE_ON;		//打开HSE 
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;	//打开PLL
	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL时钟源选择HSE
	RCC_OscInitStruct.PLL.PLLM = 8; 				//8分频MHz
	RCC_OscInitStruct.PLL.PLLN = 336;				//336倍频
	RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; 	//2分频,得到168MHz主时钟
	RCC_OscInitStruct.PLL.PLLQ = 7; 				//USB/SDIO/随机数产生器等的主PLL分频系数
	HAL_RCC_OscConfig(&RCC_OscInitStruct);

	RCC_ClkInitStruct.ClockType =
		 RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟:168MHz
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟: 168MHz
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // APB1时钟:42MHz
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // APB2时钟:84MHz
	HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

	HAL_RCC_EnableCSS();							// 使能CSS功能,优先使用外部晶振,内部时钟源为备用

	// HAL_RCC_GetHCLKFreq()/1000	 1ms中断一次
	// HAL_RCC_GetHCLKFreq()/100000	 10us中断一次
	// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
	HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); // 配置并启动系统滴答定时器

	/* 系统滴答定时器时钟源 */
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

	/* 系统滴答定时器中断优先级配置 */
	HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */


/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

FreeModbus V1.6 主机使用说明

http://www.openedv.com/thread-289725-1-1.html

FreeModbus是一款开源的Modbus协议栈,但是只有从机开源,主机源码是需要收费的。同时网上也没有发现比较好的开源的Modbus主机协议栈,所以才开发这款支持主机模式的FreeModbus协议栈。本版FreeModbus版本号更改为V1.6,特性如下:

新增加的主机源码与原有从机的风格及接口保持一致;
支持主机与从机在同一协议栈运行;
支持实时操作系统及裸机移植;
为应用提供多种请求模式,用户可以选择阻塞还是非阻塞模式,自定义超时时间等,方便应用层灵活调用;
支持所有常用的Modbus方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值