首页 > 其他分享 >基于STM32的ModBus实现(一)移植FreeMODBUS RTU

基于STM32的ModBus实现(一)移植FreeMODBUS RTU

时间:2024-03-27 19:11:59浏览次数:41  
标签:STM32 USART MB void ModBus FreeMODBUS 寄存器 REG USART1

一、FreeMODBUS

  FreeModbus是一个开源的Modbus通信协议栈实现。它允许开发者在各种平台上轻松地实现Modbus通信功能,包括串口和以太网。FreeMODBUS提供了用于从设备和主站通信的功能,支持Modbus RTU和Modbus TCP协议。在工业控制和自动化领域广泛应用。

  FreeModBus可通过官方网站下载:FreeMODBUS

下载到的文件如下:

freemodbusfile

  • demo: 里面放置接口文件的模板和了一些示例代码
  • doc: FreeModBus的说明文档
  • modbus: modbus源码都在这个里面
  • tools: 这个里面是测试modbus工具,不过还需要去下载一般不用,一般使用Modbus Poll工具调试ModBus。

二、STM32 移植ModBus RTU

移植ModBusRTU 比较简单,我这里也参考了别人写的博客STM32 移植FreeModbus详细过程,移植ModBusRTU的话看这一篇就够了,我这里也总结一下。

1、准备一个STM32的工程模板

工程模板最好是实现了串口通信,移植比较方便,我这里使用的单片机为STM32F407ZGT6,相应的串口和定时器的初始化与其他单片机(如STM32F103)略有不同。

2、将FreeModBus需要使用到的源文件添加到工程

modbus源文件的内容如下:

02-freemodbussource.png

  • ascii: ModBus ASCII源文件
  • functions: ModBus源码函数文件
  • include: 相关的头文件
  • rtu: ModBus RTU文件
  • tcp: ModBus TCP文件
    移植ModBus RTU需要将ascii、functions、rtu和mb.c文件添加到工程的modbus文件夹下(在工程中新建一个modbus文件夹)

mod03-modbus.png

由于ascii、rtu中也有.h文件因此添加头文件路径时也要讲这两个文件夹加上

04-modbus_include.png

3、将接口文件的模板添加到工程并添加代码

接口文件模板使用demo\BARE\port文件夹中的接口模板,将port文件夹复制到工程中的modbus文件夹下并将其添加到工程

05-modbus_port.png

3.1 portserial.c文件

接下来就是向接口文件portserial.c文件中添加代码了,代码如下:

portserial.c

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <[email protected]>
 *
 * 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"
#include "stm32f4xx.h" 
#include "USART1/usart1.h"	
#include "stdio.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);  
	USART1_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);
  }
}

portserial.c文件主要封装了对串口的操作

vMBPortSerialEnable()   封装了接口(串口)的发送与接收使能\失能
xMBPortSerialInit()     接口(串口)的初始化
xMBPortSerialPutByte()  接口(串口)的发送函数
xMBPortSerialGetByte()  接口(串口)的接收函数
USART1_IRQHandler()     串口中断触发上面的接收与发送回调函数

调用的初始化函数如下

usart.c
#include "sys.h"
#include "USART1/usart1.h"	

//初始化IO 串口1 
//bound:波特率
void USART1_Config(uint16_t buad)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟

	//串口1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1

	//USART1端口配置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10

	//USART1 初始化设置
	USART_InitStructure.USART_BaudRate = buad;//波特率设置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	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_Init(USART1, &USART_InitStructure); //初始化串口1

	USART_Cmd(USART1, ENABLE);  //使能串口1 
	

}
void USART1_NVIC(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;

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

usart.h

#ifndef __USART1_H
#define __USART1_H
#include "stm32f4xx_conf.h"
#include "sys.h" 

void USART1_Config(uint16_t buad);
void USART1_NVIC(void);
#endif

3.2 porttimer.c

接下来修改porttimer.c文件,代码如下

porttimer.c
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <[email protected]>
 *
 * 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"
#include "timer.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

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

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


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

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	  TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	  TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
	  TIM_SetCounter(TIM3,0x0000); 
	  TIM_Cmd(TIM3, 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 TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
	{
		prvvTIMERExpiredISR();
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

porttimer.c文件主要是初始化定时器、使能、失能和中断后的时间更新,比较简单。

定时器初始化函数如下(也可以使用其他的定时器比如系统定时器):

timer.c
#include "timer.h"
#include "led.h"

//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Init(u16 period)
{
   TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
   
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  ///使能TIM3时钟
   
   TIM_TimeBaseInitStructure.TIM_Prescaler=period;  //定时器分频
   TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
   TIM_TimeBaseInitStructure.TIM_Period=(16800-1);   //自动重装载值
   TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
   
   TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
   
//	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
   TIM_Cmd(TIM3,ENABLE); //使能定时器3
   
   NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 5; //抢占优先级0
   NVIC_InitStructure.NVIC_IRQChannelSubPriority= 0; //子优先级15
   NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
   NVIC_Init(&NVIC_InitStructure);
   
}
timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"


void TIM3_Init(u16 period);
#endif

4、定义各模拟寄存器的参数以及补全相关的寄存器操作函数

4.1 各模拟寄存器的参数

这些只是测试的参数,实际项目需要根据相关的IO、传感器实时更新参数。

Register configuration
/* ----------------------- 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] = {0x0f,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x0f,0x02};

4.2 读取输入寄存器 eMBRegInputCB()

eMBRegInputCB()
/****************************************************************************
* 名	  称: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;
}

4.3 保持寄存器操作函数 eMBRegHoldingCB()

eMBRegHoldingCB()
/****************************************************************************
* 名	  称: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;
}

4.4 线圈操作函数 eMBRegCoilsCB()

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

4.5 离散寄存器操作函数 eMBRegDiscreteCB()

eMBRegDiscreteCB()
/****************************************************************************
* 名	  称: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;
}

5、FreeMODBUS源文件的一些修改

5.1、mbconfig.h文件

当前使用的为ModBus RTU 将默认的ModBus ASCII 取消

/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED                        (  0 )

/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED                          (  1 )

/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED                          (  0 )

/*! \brief The character timeout value for Modbus ASCII.
*
* The character timeout value is not fixed for Modbus ASCII and is therefore
* a configuration option. It should be set to the maximum expected delay
* time of the network.
*/
#define MB_ASCII_TIMEOUT_SEC                    (  0 )

5.2、mbrtu.c文件

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

5.3 usRegAddress++ 的地址自增

以下文件中的usRegAddress++会导致读写是的地址自增,可以将以下文件中的usRegAddress++注释掉

mbfunccoils.c
mbfuncdisc.c
mbfuncholding.c
mbfuncinput.c

6、main.c函数中初始化并启动ModBus

接下来结束在main.c函数中启动modbus RTU了。

eMBInit()       初始化modbus
eMBEnable()     使能modbus
eMbPoll()       查询事件状态
	printf("Err : %d\n",eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE));//初始化freemodbus 设置RTU模式和ID等
	eMBEnable();

    while(1)
	{
		eMBPoll();
		vTaskDelay(10);	
	}	

eMBInit()的原型为

eMBErrorCodeeMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )

eMode: modbus模式 MB_RTU、MB_ASCII
ucSlaveAddress: 从机地址
ucPort: 连接的端口号(如串口连接电脑的端口号,当前测试被忽略了无效)
ulBaudRate: 连接串口的波特率
eParity: 校验方式(MB_PAR_NONE、MB_PAR_ODD、MB_PAR_EVEN)

我的程序使用的FreeRTOS操作系统,将查询事件作为一个线程,还有一个modbus输入寄存器更新的线程,用于在测试时能看到寄存器数据的变化。
注:main.c文件中的lwip、dp83848的相关文件是用于之后移植modbus TCP使用的,这些文件对当前的modbus RTU不会造成影响。

完整main.c文件如下

main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lwip/timeouts.h"
#include "stm32f4x7_eth.h"
#include "DP83848.h"

#include "lwip_comm.h"

/*FreeModBus*/
#include "mb.h"
#include "mbutils.h"

#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h"
#include "task.h"
#endif /*SYSTEM_SUPPORT_OS*/

/* ----------------------- 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] = {0x0f,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x0f,0x02};



/*开始任务*/
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
/*LED任务*/
TaskHandle_t LED0Task_Handler;
void led0_task(void *pvParameters);

/**************************FreeModBus******************************/
/*ModBus任务*/
TaskHandle_t ModBus_TASK_STK_Handler;
void ModBus_task(void *pvParameters);
/*ModBus输入任务*/
TaskHandle_t ModBus_Input_TASK_Handler;
void ModBus_Input_task(void *pvParameters);
/******************************************************************/


#if SYSTEM_SUPPORT_OS
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 中断分组配置
    delay_init(168);                                // 初始化延时函数
	LED_Init();   
    uart_init();                              		// 初始化串口
	printf("Err : %d\n",eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE));//初始化freemodbus 设置RTU模式和ID等
	eMBEnable();

                                  // 初始化LED端口
//	TIM3_Int_Init(999, 839);
	/*创建开始任务*/
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )256,       			//任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )1,       				//任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄   
	/*开启任务调度*/
	vTaskStartScheduler();
    while (1)
    {
//		sys_check_timeouts();
    }
}

/*开始任务任务函数*/
void start_task(void *pvParameters)
{
	lwip_comm_init();
    taskENTER_CRITICAL();           //进入临界区
    //创建LED0任务
    xTaskCreate((TaskFunction_t )led0_task,     	
                (const char*    )"led0_task",   	
                (uint16_t       )64, 
                (void*          )NULL,				
                (UBaseType_t    )2,	
                (TaskHandle_t*  )&LED0Task_Handler);      
	//ModBus任务
    xTaskCreate((TaskFunction_t )ModBus_task,     	
                (const char*    )"modbus_task",   	
                (uint16_t       )256, 
                (void*          )NULL,				
                (UBaseType_t    )3,	
                (TaskHandle_t*  )&ModBus_TASK_STK_Handler);  
	//ModBus输入任务
    xTaskCreate((TaskFunction_t )ModBus_Input_task,     	
                (const char*    )"modbus_input_task",   	
                (uint16_t       )256, 
                (void*          )NULL,				
                (UBaseType_t    )4,	
                (TaskHandle_t*  )&ModBus_Input_TASK_Handler);  
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/*LED0任务函数*/
void led0_task(void *pvParameters)
{
    while(1)
    {
        LED0=~LED0;
        vTaskDelay(500);
    }
}

//ModBus任务
void ModBus_task(void *pdata)
{
	while(1)
	{
		eMBPoll();
		vTaskDelay(10);
		
	}	
}
//ModBus输入寄存器任务
void ModBus_Input_task(void *pdata)
{
	while(1)
	{
		if(usRegInputBuf[0] > 0x1050)
		{
			usRegInputBuf[0] = 0x1000;
		}
		else
			usRegInputBuf[0] = usRegInputBuf[0] + (u16)1;
		vTaskDelay(1000);
		
	}	
}


/****************************************************************************
* 名	  称: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;
}

6、连接电脑使用ModBus Poll 软件进行测试

在程序编译没问题后使用ModBus Poll软件进行ModBus 通讯测试。

使用串口转usb模块将串口连接到电脑,打开ModBus Poll软件,点击connection配置如下:

Connection : Serial Port
Serial Setting : COMx(当前连接的端口)
Baud : 9600 (Modbus初始化时的参数)
其他如下图,配置完成后OK

06-modbus_Poll_conn.png

选择Setup-Read/Write Definition

SlaveID : 初始化时的从机地址(本例配置为 0x01)
Function : 04 Read Input Registers (读取输入寄存器,我们添加了一个更新输入寄存器的线程,读取时会看到相关数据的变化)
Address : 起始地址,代码中配置为0x0000
Quantity : 读取的寄存器数量(不能超过程序中配置的数量8)
其他参数如下图

07-modbus_Poll_conn.png

配置完成后会自动读取相关寄存器的值如下

08-modbus_Poll_conn.png

7、ending

本节完成了STM32 移植ModBus RTU,会在下一章实现 STM32 + Lwip2.1.2 + DP83848 + FreeRTOS + ModBus TCP的移植。

标签:STM32,USART,MB,void,ModBus,FreeMODBUS,寄存器,REG,USART1
From: https://www.cnblogs.com/fuyunxiansen/p/18099998

相关文章

  • Profinet转ModbusTCP:从站设备转换与集成案例
    本案例旨在探讨如何将ModbusTCP设备数据成功地接入到西门子PROFINET网络中。为了实现这一目标,我们将使用西门子S7-1200型PLC以及Profinet转ModbusTCP网关作为关键设备。为了模拟Modbus从站,我们将使用电脑安装modbuspoll软件。首先需要了解Profinet和ModbusTCP这两种协议的基本概......
  • Proteus8.0仿真应用设计(十七)基于FreeRTOS、STM32F103C8、HAL库、DHT11、LCD12864的温
    一、简介:        DHT11是一款湿、温度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个NTC测温元件。DHT11与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。传感器内部......
  • stm32串口使用dma接收数据全为0发送正常
    cubemx版本:keil版本:当使用cubeMX生成代码时,需要调整dma初始化和串口初始化的顺序,在3处那里调整,不然串口接收的数据全是0,未知原因,只找到办法......
  • Profinet转Modbus网关的调试与故障排除教程
    Profinet转Modbus网关(XD-MDPN100)带有网口和串口很大限度地解决了设备接口不统一的问题,支持485和232,可以实现从Modbus通信协议到Profinet通信协议的无缝转换,为不同协议之间的互联互通提供了便利。Profinet转Modbus网关(XD-MDPN100)的调试与故障排除教程通常涉及一系列步骤来确保网......
  • 01-【HAL库】STM32实现串口打印
    一、什么是串口串口通讯(SerialCommunication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片......
  • 基于STM32微控制器的智能快递箱设计
    标题:基于STM32微控制器的智能快递箱设计摘要:随着电子商务的快速发展,快递业务日益繁荣,智能快递箱作为解决“最后一公里”配送难题的有效手段,受到了广泛关注。本文设计了一种基于STM32微控制器的智能快递箱系统,通过对其硬件架构、电路设计、软件编程以及功能实现等方面的详细阐......
  • 基于STM32的个人健康助手的设计
    基于STM32的个人健康助手的设计一、摘要随着科技的进步和人们生活水平的提高,个人健康管理变得越来越重要。本文介绍了一种基于STM32的个人健康助手的设计,该设计结合了嵌入式系统、传感器技术和数据分析等先进技术,旨在为用户提供便捷、个性化的健康管理服务。本文首先介绍了项......
  • EtherCAT主站SOEM -- 23 -- 基于STM32F767IGT6搭建自己的SOEM-EtherCAT主站
    EtherCAT主站SOEM--23--SOEM之基于STM32F7搭建自己的EtherCAT主站0QT-SOEM视频预览及源代码下载:0.1QT-SOEM视频预览0.2QT-SOEM源代码下载一准备win系统和keil软件及SOEM:二Soem搭建大概步骤:2.1.使用正点原子提供的案例:2.2下载SOEM库将SOEM添加到......
  • 用Clion进行STM32开发(3、面板配置文件、board、cfg文件,JLink)
    打开OpenOCD安装目录\share\openocd\scripts目录,新建对应芯片型号cfg文件。输入source[findinterface/jlink.cfg]transportselectswdsource[findtarget/stm32f1x.cfg]在运行设置里配置为这个文件参考讲解烧录程序&在线调试这段:https://blog......
  • 用Clion进行STM32开发(2、创建项目,点亮LED灯)
    选择嵌入式->STM32CubeMX选择面板配置文件,选择对应的芯片配置文件,或者点击跳过(有的芯片不一定有,后面会说明任何创建及引用此配置文件)项目生成后默认生成STM32F030F4Px芯片的代码(删除除.ioc文件和.idea文件夹外的所有文件及文件夹,只保留.ioc文件和.idea文件夹),需要手......