日期

作者

版本

说明

2020.11.06

Tao

V0.0

完成主体内容的撰写


目录

  • 介绍
  • 源码
  • 头文件
  • 源文件
  • 使用指南


介绍

源码

需要特别说明的是void User_MB_InitPortParam(uint8_t portNum, uint32_t baudRate, eMBParity parity)函数。此函数并没有真正的配置通讯端口,而是初始化modbus通讯端口参数的几个全局变量。设计这些全局变量的主要原因是,在整个项目工程中还涉及其他地方需要用到这些参数,例如串口的中断服务函数中需要根据本串口有无被占用为modbus通讯端口而切换相应的服务内容,NVIC与外设配置也依赖这几个变量的值实现通讯端口外设的自动配置。Modbus的时基定时器一般默认设置为Timer2,通过宏定义FREEMODBUS_TIMER指定,这不是一个可调的参数(可以动态调整串口号与串口参数,而时基定时器在程序烧录的时候已经固定了)。

应当在程序初始化之前首先调用此函数,完成modbus通讯端口参数全局变量的初始化工作。modbus使用的串口与定时器外设将在执行eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )函数时完成初始化,无需额外再次配置一遍。但是用户需要在这之前调用void User_MB_ConfigNVIC()完成NVIC中断向量控制器中相关外设的初始化。

头文件

/*
 * mb_user.h
 *
 *  Created on: May 24, 2020
 *      Author: 81001
 */

#ifndef FREEMODBUS_USER_MB_USER_H_
#define FREEMODBUS_USER_MB_USER_H_

#include "mb_user.h"
#include "mb.h"
#include "mbplatform.h"
#include "mb_coilsreg.h"
#include "mb_discretereg.h"
#include "mb_inputreg.h"
#include "mb_holdingreg.h"

//是否启用FreeModbus模块
#define FREEMODBUS_ENABLE				1

//选择PORT类型是否为RS485模块
//#define FREEMODBUS_PORT_INTERFACE_RS485
#define FREEMODBUS_PORT_INTERFACE_RS232

//设置FreeModbus从机的地址
#define FREEMODBUS_DEV_ADDR	0x01

#define  FREEMODBUS_TIMER		2

extern uint8_t FreeModbus_PortNum;
extern USART_TypeDef *USART_Modbus;
extern uint32_t FreeModbus_PortBaudrate;
extern eMBParity Freemodbus_PortParity;

void User_MB_InitPortParam(uint8_t portNum, uint32_t baudRate, eMBParity parity);
void User_MB_Config();
void User_MB_ConfigNVIC();
void User_MB_InitRegs();

void User_RefreshInputRegister();
void User_RefreshCoilsRegister();
void User_RefreshDiscreteRegister();
void User_RefreshHoldingRegister();

#endif /* FREEMODBUS_USER_MB_USER_H_ */

源文件

/*
 * mb_user.c
 *
 *  Created on: May 24, 2020
 *      Author: 81001
 */

#include "mb_user.h"
#include "globalVariate.h"
#include "adg408.h"


uint8_t FreeModbus_PortNum = 1;
USART_TypeDef *USART_Modbus = USART1;
uint32_t FreeModbus_PortBaudrate = 115200;
eMBParity Freemodbus_PortParity = MB_PAR_NONE;

/**
 * @brief 当freemodbus中出错时,调用此函数进行错误处理
 * @param file: 一般情况下,填入出错所在的文件 __FILE__
 * @param line: 一般情况下,填入出错所在的行 __LINE__
 * @param function: 一般情况下,填入__ASSERT_FUNC
 * @param error: freemodbus库中传入出错参数(直接转化为字符串)
 */
void __attribute__((weak)) User_AssertFunc(const char *file, int line, const char *function, const char *error)
{
	/*not realize yet*/
}


/**
 * @brief 初始化modbus的通讯串口参数
 * 		此函数并没有真正的配置通讯端口,而是初始化modbus通讯端口参数的几个全局变量。
 * 		设计这些全局变量的主要原因是,在整个项目工程中还涉及其他地方需要用到这些参数,
 * 		例如串口的中断服务函数中需要根据本串口有无被占用为modbus通讯端口而切换相应的服务内容,
 * 		NVIC与外设配置也依赖这几个变量的值实现通讯端口外设的自动配置。
 * @param portNum: 串口号,1~5 对应 USART1~USART5外设
 * @param baudRate: 波特率
 * @param parity: 校验,枚举变量
 * 		@param MB_PAR_NONE: 无校验
 * 		@param MB_PAR_ODD: 奇校验
 * 		@param MB_PAR_EVEN: 偶校验
 */
void  __attribute__((weak)) User_MB_InitPortParam(uint8_t portNum, uint32_t baudRate, eMBParity parity)
{
	FreeModbus_PortNum = portNum;
	FreeModbus_PortBaudrate = baudRate;
	Freemodbus_PortParity = parity;

	switch(portNum)
	{
	case 1:
		USART_Modbus = USART1;
		break;
	case 2:
		USART_Modbus = USART2;
		break;
	case 3:
		USART_Modbus = USART3;
		break;
#if defined(STM32F10X_HD) || defined(STM32F10X_HD_VL) || defined(STM32F10X_XL) || defined(STM32F10X_CL)
	case 4:
		USART_Modbus = UART4;
		break;
	case 5:
		USART_Modbus = UART5;
		break;
#endif
	default:
		break;
	}
}
                                                                                                                                                                  
/**
 * @brief 提供了一个快速配置并启动Modbus功能的模板(采用默认配置)
 * 		需要注意的是为了使此函数尽可能的简洁易用,并未设置函数参数。
 * 		用户需要根据自己项目的实际情况修改函数中的各个配置参数。
 */
void User_MB_Config()
{
	//配置Modbus通讯串口的全局参数
	User_MB_InitPortParam(3, 115200, MB_PAR_NONE);
	//配置GPIO
	USART_ConfigGPIO('B', 10, 'B', 11);
	//配置NVIC
	User_MB_ConfigNVIC();
	//初始化Modbus四种寄存器
	User_MB_InitRegs();
	//初始化Modbus
	eMBInit( MB_RTU, FREEMODBUS_DEV_ADDR, FreeModbus_PortNum, FreeModbus_PortBaudrate, Freemodbus_PortParity);
	//使能Modbus功能
	eMBEnable();
}


/**
 * @brief 配置FreeModbus相关的中断
 * 		主要是配置时基定时器的中断与串口收发中断
 * 		默认选择NVIC_PriorityGroup_2中断优先级分组
 */
void __attribute__((weak)) User_MB_ConfigNVIC()
{
	NVIC_InitTypeDef NVIC_InitStructure;
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	switch(FreeModbus_PortNum)
	{
	case 1:
		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
		break;
	case 2:
		NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
		break;
	case 3:
		NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
		break;
#if defined(STM32F10X_HD) || defined(STM32F10X_HD_VL) || defined(STM32F10X_XL) || defined(STM32F10X_CL)
	case 4:
		NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;
		break;
	case 5:
		NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
		break;
#endif
	default:
		break;
	}

	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	switch(FREEMODBUS_TIMER)
	{
	case 1:
		NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
		break;
	case 2:
		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
		break;
	case 3:
		NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
		break;
	case 4:
		NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
		break;
	default:
		break;
	}

	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}


/**
 * @brief 完成各个寄存器的初始化
 */
void User_MB_InitRegs()
{
	RegHoldingBuf_Init();
	RegDiscreteBuf_Init();
	RegCoilsBuf_Init();
	RegInputBuf_Init();

	//Enable measurement
	CoilsReg_SetBit(0, 1);
	CoilsReg_SetBit(1, 1);

	//Auto Shift
	CoilsReg_SetBit(2, 1);
	CoilsReg_SetBit(3, 1);

	//Manual Shift
//	User_Measure1_SetShift(1);
//	User_Measure2_SetShift(3);
}


/**
 * @brief 刷新输入寄存器,将传感器数据赋值到输入寄存器
 * @param none
 * @retval none
 */
void User_MB_RefreshInputRegister()
{
	for (uint16_t i = 0; i < SENSOR_DATA_COUNT; i++)
	{
		InputRegReg_SetData(i, SensorData[i].value);
	}
}


/**
 * @brief 刷新线圈寄存器,根据线圈寄存器的值执行对应动作
 * @param none
 * @retval none
 */
void User_MB_RefreshCoilsRegister()
{
	//设置缓冲数组,提高轮询效率,只有缓冲数组中的数据发生变化的时候,才会实际寄存器的读写操作
	static uint8_t coilsReg_buffer[32] =
	{ 2 };

	(void) coilsReg_buffer;

	//Reboot
	if (CoilsReg_GetBit(16) != 0)
	{
		Reboot();
	}

	//Restore storable data and reboot
	if (CoilsReg_GetBit(17) != 0)
	{
		StorableData_SaveAllDefaultValue();
		Reboot();
	}
}


/**
 * @brief Refresh discrete registers
 */
void User_MB_RefreshDiscreteRegister()
{
	//设置缓冲数组,提高轮询效率,只有缓冲数组中的数据发生变化的时候,才会实际寄存器的读写操作
	static uint8_t discreteReg_buffer[10] = { 99 };
	(void) discreteReg_buffer;
}


/**
 * @brief Refresh holding registers
 */
void User_MB_RefreshHoldingRegister()
{

}

使用指南

以下简述将独立的FreeModbus库添加到STM32工程中,使用modbus功能需要执行的一般流程:

  1. 在初始化MCU其他外设之前,首先初始化modbus的端口参数(一般在主函数最前面),因为后面的初始化函数中可能会涉及依赖这些变量完成一些外设的初始化。
/**
	 * 初始化modbus端口参数
	 */
#if FREEMODBUS_ENABLE == 1
	User_MB_InitPortParam(3, 115200, MB_PAR_NONE);
#endif
  1. 初始化Modbus通讯串口使用到的GPIO,可以直接调用串口扩展库中的配置函数快速配置。之所以没有在modbus扩展库中设置串口使用GPIO端口的自动配置函数是因为USART外设对应的GPIO不是唯一的,可以通过重映射切换到2~3组GPIO上,因此Modbus通讯串口使用到的GPIO采用手动配置的方式更加便捷。
/**
	 * Serial Port Interface
	 */
	USART_ConfigGPIO('A',9,'A',10);
	USART_ConfigGPIO('B',10,'B',11);
  1. 在中断控制器中配置Modbus使用的串口与定时器(由于modbus通讯对时序与间隔要求什么严格,因此为了保证通讯的稳定性,一般将它们配置为最高与次高级抢占优先级)。根据项目的不同,可能会设置不同的NVIC中断优先级分组,此时针对Modbus使用的串口与定时器中断配置可能会有细微差别。
#if FREEMODBUS_ENABLE != 0
	 User_MB_ConfigNVIC();
#endif
  1. 初始化Modbus四种寄存器、初始化Modbus、使能Modbus功能
User_MB_InitRegs();
	eMBInit( MB_RTU, FREEMODBUS_DEV_ADDR, FreeModbus_PortNum, FreeModbus_PortBaudrate, Freemodbus_PortParity);
	eMBEnable();

为了简化上述配置的流程,提供了一个快速配置并启动Modbus功能的模板(“All in one”, 采用默认配置),直接调用void User_MB_Config()函数即可完成上述四个步骤的配置,但是应当注意的是此函数并为提供参数列表,用户需要根据自己项目的实际情况修改函数中的各个配置参数。

  1. 在合适的地方轮询Modbus。如果没有采用实时操作系统,则一般在主循环中轮询Modbus。如果移植了实时操作系统,则一般在一个任务中轮询Modbus
  • 在主循环中轮询Modbus:
while (1)
	{
		( void )eMBPoll();
	}
  • 在任务中轮询Modbus:
void ModbusPoll_task(void *pvParameters)
{
   //	User_MB_InitPortParam(3, 115200, MB_PAR_NONE);
   User_MB_InitRegs();
   eMBInit( MB_RTU, FREEMODBUS_DEV_ADDR, FREEMODBUS_PORT_NUM, FREEMODBUS_PORT_BAUDRATE, FREEMODBUS_PORT_PARITY);
   eMBEnable(  );
   
   TickType_t ticks = xTaskGetTickCount();
   while (1)
   {
   	eMBPoll();
   	
   	//1ms 一个处理周期
   	vTaskDelayUntil( &ticks, 1 );
   }
}
  1. 刷新Modbus寄存器数据。根据业务需要和寄存器类型,或是将传感器数据写入到特定寄存器,或者读取特定寄存器的值作为业务逻辑的控制参数。同样的,根据是否应用实时操作系统,可以选择不同的刷新方法。如果没有采用实时操作系统,则一般在硬件定时器的中断服务函数或者软件定时器的timeout服务函数中轮询刷新Modbus寄存器。如果移植了实时操作系统,则一般在一个任务中轮询刷新Modbus寄存器
//刷新四种寄存器
	User_MB_RefreshInputRegister();
	User_MB_RefreshCoilsRegister();
	User_MB_RefreshDiscreteRegister();
	User_MB_RefreshHoldingRegister();