本文旨在提供将Free Modbus移植到Mini-F5265-OB开发板的开发测试纪实,首先确定使用的硬件,其次对相关源代码进行修改,最终完成功能的实现,测试了开发板的性能,并分享需要注意的内容和存在的疑问,与网上各路大神一起探讨学习,请大家多多提出意见。
一、硬件分析
通过查看原理框图,Mini-F5265-OB开发板对应的虚拟串口使用了MM32F5265E7PV的UART3,移植Free\_Modbus是也需要使用对应串口。
二、Free Modbus简介
Free Modbus 是一款开源的、免费的 Modbus 协议栈,是一种应用层协议,用于在不同设备之间进行通信,主要用于工业自动化领域,实现控制器与其他设备如传感器、执行器等之间的数据交互。通常以 C 语言编写,这使得它具有很好的可移植性,可以方便地在不同的硬件平台和操作系统上运行。支持 Modbus 协议的多种功能码,如读取线圈状态、读取寄存器、写入线圈、写入寄存器等常见操作,能够满足大多数工业应用场景下的数据交互需求。经过了大量实际项目的验证和测试,具有较高的稳定性和可靠性,在工业环境中能够长时间稳定运行,保证数据通信的准确性和及时性。提供了简单清晰的 API 接口,开发者只需要按照规定的接口函数进行调用,就可以轻松实现 Modbus 通信功能,降低了开发难度,提高了开发效率。
三、程序文件及开发环境准备
本次移植使用的是Free Modbus开源代码1.6版本,利用MM32的UART\_Polling例程(..\LibSamples\_MM32F5260\_V0.11.3\Samples\LibSamples\UART\UART\_Polling)进行修改,开发环境MDK。
首先将驱动文件(..\LibSamples\_MM32F5260\_V0.11.3\Device)和free modbus源代码(文件夹名称命名为mm32mb)复制到工程文件夹,并修改编译器头文件路径如下
....\UART\_Polling;..\Device\MM32F5260\HAL\_Lib\Inc;..\Device\MM32F5260\Include;..\Device\CMSIS\Core\Include;..\mm32mb\modbus\include;..\mm32mb\port;..\mm32mb\modbus\rtu;修改分散加载文件路径如下..\Device\MM32F5260\Source\MM32F5260.sct
至于如何添加C文件到工程里这里就不赘述了。
四、源码修改
(一)定时器初始化
/* ----------------------- Platform includes --------------------------------*/
#include <mb.h>
#include <mbport.h>
#include <port.h>
#include "mm32f5260.h"
#include "platform.h"
/* ----------------------- Modbus includes ----------------------------------*/
/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
NVIC_InitTypeDef NVIC_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
TIM_TimeBaseStruct.TIM_Prescaler = (TIM_GetTIMxClock(TIM7) / 10000 - 1);
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = (5000 - 1);
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_Div1;
TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStruct);
TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM7, ENABLE);
return TRUE;
}
void vMBPortTimersEnable()
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
TIM_ClearITPendingBit(TIM7,TIM_IT_Update);
TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE);
TIM_SetCounter(TIM7,0);
TIM_Cmd(TIM7, ENABLE);
}
void vMBPortTimersDisable()
{
/* Disable any pending timers. */
TIM_Cmd(TIM7, DISABLE);
TIM_SetCounter(TIM7,0);
TIM_ITConfig(TIM7,TIM_IT_Update,DISABLE);
TIM_ClearITPendingBit(TIM7,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( );
}
这里需要完善定时器中断函数:
void TIM7_IRQHandler(void)
{
if (RESET != TIM_GetITStatus(TIM7, TIM_IT_Update))
{
prvvTIMERExpiredISR( );
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
}
}
(二)串口初始化及发送接收函数修改
#include <mb.h>
#include <mbport.h>
#include <port.h>
#include "mm32f5260.h"
#include "platform.h"
/* ----------------------- Modbus includes ----------------------------------*/
/* ----------------------- 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)
{
UART_ITConfig(UART3, UART_IT_RX, ENABLE); //开发用的是串口3,故为&huart3
}
else
{
UART_ITConfig(UART3, UART_IT_RX, DISABLE);
}
if (xTxEnable)
{
UART_ITConfig(UART3, UART_IT_TXC, ENABLE);
}
else
{
UART_ITConfig(UART3, UART_IT_TXC, DISABLE);
}
}
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
eMBParity eParity)
{
/* 串口初始化*/
GPIO_InitTypeDef GPIO_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
UART_InitTypeDef UART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
UART_StructInit(&UART_InitStruct);
UART_InitStruct.BaudRate = ulBaudRate;
UART_InitStruct.WordLength = UART_WordLength_8b;
UART_InitStruct.StopBits = UART_StopBits_1;
UART_InitStruct.Parity = UART_Parity_No;
UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
UART_InitStruct.Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART3, &UART_InitStruct);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_7);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_7);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOC, &GPIO_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = UART3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
UART_Cmd(UART3, ENABLE);
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 (SET == UART_GetFlagStatus(UART3, UART_FLAG_RXAVL))
{
ucByte = UART_ReceiveData(UART3);//串口接收函数
}
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.
*/
UART_SendData(UART3, (uint8_t)pucByte);
while (RESET == UART_GetFlagStatus(UART3, UART_FLAG_TXC))//串口发送函数
{
}
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)
{
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)
{
pxMBFrameCBByteReceived();
}
不要忘记完成中断函数:
void UART1_IRQHandler(void)
{
uint8_t RxData = 0;
if (SET == UART_GetITStatus(UART1, UART_IT_RX))
{
prvvUARTRxISR();//接收中断
UART_ClearITPendingBit(UART1, UART_IT_RX);
}
if (SET == UART_GetITStatus(UART1, UART_IT_TX))
{
UART_ClearITPendingBit(UART1, UART_IT_TX);
prvvUARTTxReadyISR();//发送中断
}
}
(三)功能码对应的程序
利用网上大神的程序即可,就不在重复讲了。
五、注意事项
一要设置好工程文件相关路径,如果有缺漏一般编译时会有提示,二是代码文件要包含相关头文件,同样可以根据编译提示进行更正,三是各类时钟和定时器的配置要协同起来,避免出现与要求不一致的定时触发信号,四是初始化要注意顺序,完成初始化后再执行相关函数。
六、待补充内容
在手册中,未找到和STM32一样的总中断开关的寄存器位,待后续研究是否为遗漏还是对所以中断信号只给予了单独控制的寄存器位