FreeMODBUS库的扩展与增强(1)- 移植到STM32单片机的基本流程

日期作者版本说明
2020.09.24TaoV0.0完成主体内容的撰写
2020.10.23TaoV0.1User_AssertFunc函数增加了weak属性
2020.10.24TaoV1.0修改了移植FreeModbus章节的内容与排版

特别说明

本文是笔者关于modbus长期开发经验的总结,分成几个阶段逐步完成,中间可能掺杂了英文。由于时间原因,全文也没有统一语言,还请读者谅解。本文介绍的移植方法、功能增强、扩展库都是笔者亲自编写设计并经过大量工程验证的,但其中依然可能存在漏洞与Bug,也不能保证在读者的项目或者平台中能够顺利实施。它山之石,可以攻玉,希望笔者的这篇文章对读者起到参考借鉴的作用。

FreeMODBUS简介

一般情况下,我们不会从底层开始去实现(也很难实现)完整的MODBUS协议栈,而会“站在巨人的肩膀上”,直接采用移植第三方的modbus库进行开发,而笔者今天要介绍的FreeMODBUS库的扩展与增强就是基于应用广泛的第三方库。网上关于FreeMODBUS的介绍与移植教程有很多,质量参差不齐。笔者推荐去FreeMODBUS的官网获取一手资料。以下是摘自官网的介绍:

FreeMODBUS 是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。在FreeMODBUS的当前版本中,提供了Modbus Application Protocol v1.1a 的实现并且支持在Modbus over serial line specification 1.0中定义的RTU/ASCII传输模式。从0.7版本开始,FreeModbus也支持在TCP defined in Modbus Messaging on TCP/IP Implementation Guide v1.0a中定义的TCP传输。Freemodbus遵循BSD,这意味着本协议栈的实现代码可以应用于商业用途。目前版本的FreeModbus支持如下的功能码:

  • 读输入寄存器 (0x04)
  • 读保持寄存器 (0x03)
  • 写单个寄存器 (0x06)
  • 写多个寄存器 (0x10)
  • 读/写多个寄存器 (0x17)
  • 读取线圈状态 (0x01)
  • 写单个线圈 (0x05)
  • 写多个线圈 (0x0F)
  • 读输入状态 (0x02)
  • 报告从机标识 (0x11)

本实现基于最新的标准并且与标准完全兼容。接收和传输Modbus RTU/ASCII数据帧是通过一个由硬件提取层的调用来驱动状态机来实现的。这就使得该协议非常容易移植到其他的平台之上。当收到一个完整的数据帧后,该数据帧被传入Modbus应用层,数据帧的内容在该层得到解析。为例方便增加新的Modbus功能,Freemodbus在应用层通提供了Hooks。
如果用到了Modbus TCP协议,那么当准备处理一个新数据帧的时候,移植层就必须首先向协议栈发送一个事件标志。然后,协议栈调用一个返回值为接收到的Modbus TCP数据帧的函数,并且开始处理这个数据帧。如果数据有效,则相应的Modbus反馈帧将提供给移植层生成反馈帧。最后,该反馈被发送到客户端。

一般来说FreeMODBUS主要应用在小型嵌入式系统与单片机中(不运行操作系统或者运行RTOS),而在通用操作系统平台上(Linux, MacOs, Windows),我们一般使用libmodbus库进行modbus通讯相关的开发。同样的,笔者推荐去它的官网libmodbus库的官网获取一手资料和最新源码。以下也给出一些摘自官网的介绍:

A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32

Libmodbus is a free software library to send/receive data according to the Modbus protocol. This library is written in C and supports RTU (serial) and TCP (Ethernet) communications.The license of libmodbus is LGPL v2.1+ and the licence of programs in the tests directory is BSD 3-clause.

官方源码

序号名称说明/描述
1ascii这个文件夹包含Modbus-ASCII协议的实现代码
2functions这个文件夹主要包括一些功能码对应的处理函数
3include里面主要是Modbus协议需要使用的一些头文件和配置文件
4rtu这个文件夹包含Modbus-RTU协议的实现代码
5tcp这个文件夹包含Modbus-TCP协议的实现代码
6mb.c这个是MODBUS协议栈的主文件,这个文件夹只是一个框架,与具体的协议无关

因为modbus有三种具体的协议,分别为RTU、ASCII和TCP,具体的实现在1、4、5文件夹中,而mb.c在初始化的时候会根据使用情况将指针指向具体的处理函数。

移植FreeMODBUS

以移植到STM32F103为例,笔者介绍一下移植过程中需要注意的几个关键问题。

移植的时候需要重点关注

  1. Add freemodbus/modbus, freemodbus/platform, freemodbus/registers and freemodbus/user folders to your own stm32 library. Among the above four folders:

    • freemodbus/modbus is the source code folder of the FreeMODBUS, which is downloaded from the internet.
    • The remaining three folders freemodbus/platform, freemodbus/registers and freemodbus/user are created by the users.
  2. In freemodbus/modbus folder, only rtu/mbrtu.c need some modification if you configuration the USART_IT_TC interruption, which will be detailed in step 5.

  3. Modify portserial.c and porttimer.c in freemodbus/platform (modifications for stm32f103 platform have been completed already). (Follow the steps described in this blog)

  4. Rewrite IRQ handler of modbus serial port and timer in other places of your project. For example, if USART1 is chosen as modbus serial port, you should define:

移植时基定时器

库内文件修改

freemodbus/platform/porttimer.c文件中移植定时器。需要重新实现一下三个函数:

  • 定时器初始化函数BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
  • 定时器启动函数inline void vMBPortTimersEnable()
  • 定时器关闭函数inline void vMBPortTimersDisable()

示例代码如下:

BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
#if FREEMODBUS_TIMER == 1

#elif  FREEMODBUS_TIMER == 2

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	TIM_DeInit(TIM2);

	TIM_TimeBaseInitStructure.TIM_Prescaler = 3600 - 1;		//1MHz, 1us
	TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us;		//50us
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	TIM_ARRPreloadConfig(TIM2, ENABLE);
	TIM_Cmd(TIM2, DISABLE);

#elif  FREEMODBUS_TIMER == 3

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	TIM_DeInit(TIM3);

	TIM_TimeBaseInitStructure.TIM_Prescaler = 3600 - 1;		//1MHz, 1us
	TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us;		//50us
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	TIM_ARRPreloadConfig(TIM3, ENABLE);
	TIM_Cmd(TIM3, DISABLE);

#elif  FREEMODBUS_TIMER == 4

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	TIM_DeInit(TIM4);

	TIM_TimeBaseInitStructure.TIM_Prescaler = 3600 - 1;		//1MHz, 1us
	TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us;		//50us
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);

	TIM_ClearFlag(TIM4, TIM_FLAG_Update);
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	TIM_ARRPreloadConfig(TIM4, ENABLE);
	TIM_Cmd(TIM4, DISABLE);

#else

#endif

	return TRUE;
}
//static void prvvTIMERExpiredISR( void );

inline void vMBPortTimersEnable()
{
#if FREEMODBUS_TIMER == 1

#elif  FREEMODBUS_TIMER == 2

	TIM_SetCounter(TIM2, 0x0000);
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM2, ENABLE);

#elif  FREEMODBUS_TIMER == 3

	TIM_SetCounter(TIM3, 0x0000);
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM3, ENABLE);

#elif  FREEMODBUS_TIMER == 4

	TIM_SetCounter(TIM4, 0x0000);
	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM4, ENABLE);

#else

#endif

	/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
}

inline void vMBPortTimersDisable()
{
#if FREEMODBUS_TIMER == 1

#elif  FREEMODBUS_TIMER == 2

	TIM_Cmd(TIM2, DISABLE);
	TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	TIM_SetCounter(TIM2, 0x0000);

#elif   FREEMODBUS_TIMER == 3

	TIM_Cmd(TIM3, DISABLE);
	TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	TIM_SetCounter(TIM3, 0x0000);

#elif  FREEMODBUS_TIMER == 4

	TIM_Cmd(TIM4, DISABLE);
	TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);
	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	TIM_SetCounter(TIM4, 0x0000);

#else

#endif

	/* Disable any pending timers. */
}

配置时钟中断

需要在合适的位置设置定时器的更新中断(以Timer2为例)

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
	{
#if FREEMODBUS_ENABLE==1 && FREEMODBUS_TIMER == 2
		pxMBPortCBTimerExpired();
#else
		Timer2Updated();
#endif

		TIM_ClearFlag(TIM2,TIM_FLAG_Update);
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

移植串口功能

库内文件修改1

freemodbus/platform/portserial.c文件中移植串口。需要重新实现一下四个函数:

  • 串口初始化函数BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
  • 串口收发控制函数void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
  • 串口数据接收函数BOOL xMBPortSerialGetByte(CHAR * pucByte)
  • 串口数据发送函数BOOL xMBPortSerialPutByte(CHAR ucByte)

示例代码如下:

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
	/* If xRXEnable enable serial receive interrupts. If xTxENable enable
	 * transmitter empty interrupts.
	 */
	if (xRxEnable)
	{
		//使能接收和接收中断
		USART_ITConfig(USART_MODBUS, USART_IT_RXNE, ENABLE);

#ifdef FREEMODBUS_PORT_INTERFACE_RS485
		//MAX485操作 低电平为接收模式
		RS485_T_CTRL = 0;
#endif
	}
	else
	{
		USART_ITConfig(USART_MODBUS, USART_IT_RXNE, DISABLE);

#ifdef FREEMODBUS_PORT_INTERFACE_RS485
		//MAX485操作 高电平为发送模式
			RS485_T_CTRL = 1;
#endif
	}

	if (xTxEnable)
	{
		//使能发送完成中断
		USART_ITConfig(USART_MODBUS, USART_IT_TC, ENABLE);

#ifdef FREEMODBUS_PORT_INTERFACE_RS485
		//MAX485操作 高电平为发送模式
			RS485_T_CTRL = 1;
#endif
	}
	else
	{
		//禁止发送完成中断
		USART_ITConfig(USART_MODBUS, USART_IT_TC, DISABLE);

#ifdef FREEMODBUS_PORT_INTERFACE_RS485
		//MAX485操作 低电平为接收模式
		RS485_T_CTRL = 0;
#endif
	}
}

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

	USART_InitTypeDef USART_InitStructure;

	USART_TypeDef* usart_mb;

	switch (ucPORT)
	{
	case 1:
		usart_mb = USART1;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
		break;
	case 2:
		usart_mb = USART2;
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
		break;
	case 3:
		usart_mb = USART3;
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
		break;
	case 4:
		usart_mb = UART4;
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
		break;
	case 5:
		usart_mb = UART5;
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE);
		break;

	default:
		return FALSE;
	}

	USART_InitStructure.USART_BaudRate = ulBaudRate;

//	if (ucDataBits == 8)
//	{
//		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//	}
//	else if (ucDataBits == 9)
//	{
//		USART_InitStructure.USART_WordLength = USART_WordLength_9b;
//	}
//	else
//	{
//		return FALSE;
//	}

	USART_InitStructure.USART_StopBits = USART_StopBits_1;

	if (eParity == MB_PAR_NONE)
	{
		USART_InitStructure.USART_Parity = USART_Parity_No;
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	}
	else if (eParity == MB_PAR_EVEN)
	{
		USART_InitStructure.USART_Parity = USART_Parity_Even;
		USART_InitStructure.USART_WordLength = USART_WordLength_9b;
	}
	else if (eParity == MB_PAR_ODD)
	{
		USART_InitStructure.USART_Parity = USART_Parity_Odd;
		USART_InitStructure.USART_WordLength = USART_WordLength_9b;
	}
	else
	{
		return FALSE;
	}

	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(usart_mb, &USART_InitStructure);
	USART_Cmd(usart_mb, ENABLE);

#ifdef FREEMODBUS_PORT_INTERFACE_RS485
		//MAX485操作 低电平为接收模式
		RS485_T_CTRL = 0;
#endif

	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_SendByte(USART_MODBUS, ucByte);
//	USART_SendData(USART_MODBUS,(const uint8_t*) &ucByte, 1);
	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_ReceiveByte(USART_MODBUS);
	return TRUE;
}

库内文件修改2

由于使用的是串口发送完成中断(USART_IT_TC),想要进入该中断服务函数,需要发送一个字节的数据并启动串口发送中断,代码还需要少许修改。在rtu/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++;
           usSndBufferCount--;
   
           vMBPortSerialEnable( FALSE, TRUE );
       }
       else
       {
           eStatus = MB_EIO;
       }
       EXIT_CRITICAL_SECTION(  );
       return eStatus;
   }

配置串口中断

void USART1_IRQHandler(void)
{
	/**
	 * 如果使能串口接收中断,那么ORE为1时也会产生中断。
	 * 在应用中对ORE标志进行处理,当判断发生ORE中断的时候,
	 * 我们再读一次USART_DR的值,
	 * 这样如果没有新的Overrun 溢出事件发生的时候,ORE会被清除,
	 * 然后程序就不会因为ORE未被清除而一直不断的进入串口中断
	 */
	if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
	{
		USART_ReceiveByte(USART1);
	}

#if FREEMODBUS_ENABLE==1 && FREEMODBUS_PORT_NUM == 1
	if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		pxMBFrameCBByteReceived();
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
	else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
	{
		pxMBFrameCBTransmitterEmpty();
		USART_ClearITPendingBit(USART1, USART_IT_TC);
	}
	else
	{

	}
#else
	if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		/*省略了与移植无关代码*/
	}
#endif
}

重写assert函数

Rewrite assert function.
Freemodbus use assert function to report errors in the process of communication, which was declared in assert.h:

# define assert(__e) ((__e) ? (void)0 : __assert_func (__FILE__, __LINE__, __ASSERT_FUNC, #__e))

This function is may not be suitable for our own project. As a result, it is better to re-implement the error reporting function. There are 6 places that need to be modified:
需要修改的地方

  • Change macro assertto assert_mb, such as:
assert_mb( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
  • Define macro assert_mband declare function User_MbAssertFuncin mbplatform.h:
# define assert_mb(__e) ((__e) ? (void)0 : User_MbAssertFunc(__FILE__, __LINE__, __ASSERT_FUNC, #__e))

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

注意事项

  • 中断优先级配置

为了保证modbus通讯的顺畅(尤其是在负载较大或者通讯速率较高的情况下),通讯串口与时基定时器的中断优先级需要配置为最高抢占优先级。其他中断的抢占优先级应该低于通讯串口与时基定时器的中断优先级

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

	switch(FREEMODBUS_PORT_NUM)
	{
	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 = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}
  • 外设配置

FreeModbus会占用系统的一个串口与一个定时器,在初始化modbus的时候会自动配置这两个外设,因此不需要在工程的其他位置再重新配置。

  1. freemodbus/registers is the collection of operation functions of four types of registers. There are eight files in the folder: mb_coilsreg.c, mb_coilsreg.h, mb_discretereg.c, mb_discretereg.h, mb_holdingreg.c, mb_holdingreg.h, mb_inputreg.c, mb_inputreg.h. All these files belongs to the extended content of the freemodbus.

  2. freemodbus/user is the collection of project related functions of four types of registers. This folder is the most frequently modified location based on the project. There are two files in the folder: mb_user.c, mb_user.h. All these files belongs to the extended content of the freemodbus.

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 好的,我可以回答这个问题。首先,您需要下载FreeRTOS和freemodbus的源代码,然后根据STM32F103的硬件特性进行适当的修改和配置。最后,将修改后的代码编译并烧录到STM32F103上即可。具体的步骤可以参考相关的开发文档和教程。 ### 回答2: 要将FreeRTOS freemodbus移植STM32F103上,首先需要了解STM32F103的硬件特性和FreeRTOS、freemodbus的工作原理。然后按照以下步骤进行移植: 1. 首先,将STM32F103的开发环境搭建好,包括安装相关的开发工具和驱动程序。 2. 从FreeRTOS官方网站下载最新版本的FreeRTOS源代码,并解压缩到工程目录下。 3. 从freemodbus官方网站下载最新版本的freemodbus源代码,并解压缩到工程目录下。 4. 打开STM32F103的开发环境,创建一个新的工程。 5. 将FreeRTOS的源代码添加到工程中,包括核心代码、任务调度器、内存管理等。 6. 根据STM32F103的硬件特性,配置相关的寄存器和引脚,确保与FreeRTOS的功能相匹配。 7. 将freemodbus的源代码添加到工程中,包括主站、从站和串口通信等模块。 8. 通过配置文件或修改源代码,设置freemodbus的参数,如波特率、数据位、停止位等。 9. 在工程中创建一个新的任务,用于处理freemodbus的通信逻辑。 10. 编译并下载程序到STM32F103。 11. 运行程序,通过串口或其他方式检测和验证freemodbus的通信功能。 12. 根据实际需求,对移植后的FreeRTOS freemodbus进行优化和调试。 以上是将FreeRTOS freemodbus移植STM32F103的大致步骤,具体的实施过程需要根据具体的环境和需求进行调整。在移植过程中可能会遇到一些问题和挑战,需要仔细阅读相关文档和参考资料,并进行适当的学习和研究。 ### 回答3: 要将FreeRTOS freemodbus移植STM32F103上,需要按照以下步骤进行操作: 1. 首先,需要确保你熟悉STM32F103的操作和编程。了解该微控制器的寄存器和外设配置,以及相应的开发工具和环境设置。熟悉使用STM32CubeMX来进行初始化和配置。 2. 下载FreeRTOS和freemodbus的最新版本。可以从官方网站下载并解压这两个。 3. 在STM32CubeMX中,创建一个新的项目,并选择适合你的STM32F103型号的芯片。 4. 在配置时,确保使能FreeRTOS和freemodbus。这些应该是预编译好的二进制文件,所以不需要进行额外的编译工作。 5. 在配置文件中,设置FreeRTOS的任务和堆栈大小,根据你的需求进行适当的调整。还要根据freemodbus的要求,配置相应的寄存器和外设。 6. 在生成代码后,你将得到一个基本FreeRTOS和freemodbus的项目框架。在这个框架中,你可以添加自定义任务和功能,以满足你的应用需求。例如,你可以添加具体的modbus从站和主站的实现。 7. 通过FreeRTOS的任务和队列机制,你可以实现多任务并发和通信。在这个框架中,你可以创建多个modbus任务,以处理不同的modbus请求。 8. 配置UART或其他串行通信外设来与modbus通信。这取决于你的具体需求和硬件配置。 9. 最后,编译和下载固件到STM32F103上,然后运行你的应用程序。确保程序正常工作并能够实现预期的功能。 总结起来,移植FreeRTOS freemodbusSTM32F103的过程需要熟悉STM32F103的编程和操作,配置合适的开发环境和工具,同时充分理解和使用FreeRTOS和freemodbus的功能和特性。通过合理的任务分配和通信机制,你可以实现一个高效可靠的modbus通信应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全能骑士涛锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值