在前面几篇文章中,对FreeModbus文件的源码进行了分析,还剩下与平台相关的接口部分,在这里通过对FreeModbus在STM32上的移植过程为例来介绍FreeModbus的接口部分。
移植FreeModbus之前需要准备好FreeModbus源码,关于源码的获取方式,参考我之前的文章:
https://blog.csdn.net/u014100102/article/details/90453930
STM32的工程,这个在此不做说明,只要之前做过STM32的对STM32工程的建立都比较熟悉了。
总结:
移植前需要准备:
1、FreeModbus源码
2、STM32基础工程
1、在工程中新建FreeModbus文件夹,将Modbus文件夹中的所有文件及文件夹复制进来。
2、将FreeModbus-v1.6->demo->AVR->port文件夹复制到STM32工程中的FreeModbus文件夹中。并将文件添加到工程中。
3、文件添加好之后,来开始看port文件夹下的文件。里面的文件需要修改,移植到STM32平台上,就可以使用了。
port.h文件主要定义了FreeModbus使用到的数据类型定义,这里和平台相关的有一个,就是进出临界区的定义,不同的MCU的定义不同,STM32修改如下:
#define ENTER_CRITICAL_SECTION() __set_PRIMASK(1) //关总中断
#define EXIT_CRITICAL_SECTION() __set_PRIMASK(0) //开总中断
此外,头文件也需要修改,包含stm32的头文件
#include "stm32f10x.h"
port serial.c文件夹中是实现底层串口的相关函数。这里先贴出代码。
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/**控制串口发送和接收中断**/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if(xRxEnable == TRUE)
{
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //接收非空中断
}
else
{
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
}
if(xTxEnable == TRUE)
{
USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //发送中断空
}
else
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* prevent compiler warning. */
(void)ucPORT;
//USART1端口配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
USART_InitStructure.USART_BaudRate = ulBaudRate;
if(ucDataBits == 9)
USART_InitStructure.USART_WordLength = USART_WordLength_9b; // 9位数据 ;
else
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据 ;
if(eParity == MB_PAR_ODD)
USART_InitStructure.USART_Parity = USART_Parity_Odd; // 奇校验;
else if(eParity == MB_PAR_EVEN)
USART_InitStructure.USART_Parity = USART_Parity_Even; // 偶校验;
else
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验位;
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 在帧结尾传输1个停止位 ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制失能 ;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 接收发送使能 ;
USART_Init(USART1, &USART_InitStructure);
// USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE); //
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
return TRUE;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
USART_SendData(USART1, ucByte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);/*等待发送完成*/
return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = USART_ReceiveData(USART1);
return TRUE;
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
OSIntEnter();
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
pxMBFrameCBByteReceived();
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
pxMBFrameCBTransmitterEmpty();
USART_ClearITPendingBit(USART1, USART_IT_TXE);
}
OSIntExit(); //退出中断
}
vMBPortSerialEnable()
函数该函数实现STM32串口发送中断和接收中断的使能。
xMBPortSerialInit()
函数该函数对UART串口进行初始化,由eMBRTUInit函数进行调用。
xMBPortSerialPutByte()
函数串口发送函数,将STM32串口发送函数进行封装,供协议栈使用
xMBPortSerialGetByte()
函数串口接收函数,将STM32串口接收函数进行封装,供协议栈使用
USART1_IRQHandler()
函数串口中断处理函数,包含发送中断和接收中断,都是调用之前mbrtu.c中的中断函数进行处理,关于mbrtu.c,请参考之前的文章:
https://blog.csdn.net/u014100102/article/details/90543437
该文件主要包含定时器处理相关函数,话不多说,直接贴代码
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter
*
* 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"
/* ----------------------- static functions ---------------------------------*/
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us;
TIM_TimeBaseInitStructure.TIM_Prescaler=16800-1; //定时器分频 50us
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=9; //子优先级8
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
//TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器更新中断
TIM_Cmd(TIM2,ENABLE); //使能定时器
return TRUE;
}
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_SetCounter(TIM2, 0);
//TIM_Cmd(TIM2, ENABLE);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
TIM_SetCounter(TIM2, 0);
//TIM_Cmd(TIM2, DISABLE);
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
}
void TIM2_IRQHandler(void)
{
OSIntEnter(); //进入中断
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断
{
pxMBPortCBTimerExpired();
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
OSIntExit();
}
xMBPortTimersInit()
函数定时器初始化函数,初始化定时器50us一次中断。
vMBPortTimersEnable()
函数使能定时器中断
vMBPortTimersDisable()
函数禁止定时器中断
TIM2_IRQHandler()
函数定时器中断处理函数,调用mbrtu.c中的定时器中断函数,关于mbrtu.c,请参考之前的文章:
https://blog.csdn.net/u014100102/article/details/90543437
该文件主要包含事件处理相关函数,话不多说,直接贴代码
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue;
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
xEventInQueue = FALSE;
return TRUE;
}
BOOL
xMBPortEventPost( eMBEventType eEvent )
{
xEventInQueue = TRUE;
eQueuedEvent = eEvent;
return TRUE;
}
BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
if( xEventInQueue )
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
xMBPortEventInit()
函数初始化事件队列
xMBPortEventPost()
函数发送一个事件
xMBPortEventGet()
函数读取一个事件
上面把接口文件都移植好了,现在看一下main函数怎么使用
#define REG_HOLDING_START 0x2000
#define REG_HOLDING_NREGS 4
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS] = {23, 45, 88, 32};
int main(void)
{
delay_init(); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置
eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); //初始化freemodbus 设置RTU模式和ID等
eMBEnable();
while(1)
{
eMBPoll();
delay_ms(30);
}
}
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 )
{
/* Pass current register values to the protocol stack. */
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ =
( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ =
( unsigned char )( usRegHoldingBuf[iRegIndex] &
0xFF );
iRegIndex++;
usNRegs--;
}
break;
/* Update current register values with new values from the
* protocol stack. */
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/**
* @功能
* @参数
* @返回值
*/
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;
}
这里主函数主要是进行协议栈的初始化、使能和轮循,之前的博客已经介绍过,再次不做介绍。可以看之前的文章。下面两个函数实现具体的逻辑,就是读保持寄存器的值和寄存器输入。这两个函数都由freemodbus中具体的功能码处理函数调用。
FreeModbus代码中有一处有问题,代码如下:
eMBException
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
USHORT usRegAddress;
USHORT usRegCount;
UCHAR *pucFrameCur;
eMBException eStatus = MB_EX_NONE;
eMBErrorCode eRegStatus;
if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) )
{
usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 );
usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] );
//usRegAddress++;
usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 );
usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if( ( usRegCount >= 1 ) && ( usRegCount <= MB_PDU_FUNC_READ_REGCNT_MAX ) )
{
/* Set the current PDU data pointer to the beginning. */
pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF];
*usLen = MB_PDU_FUNC_OFF;
/* First byte contains the function code. */
*pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER;
*usLen += 1;
/* Second byte in the response contain the number of bytes. */
*pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 );
*usLen += 1;
/* Make callback to fill the buffer. */
eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ );
/* If an error occured convert it into a Modbus exception. */
if( eRegStatus != MB_ENOERR )
{
eStatus = prveMBError2Exception( eRegStatus );
}
else
{
*usLen += usRegCount * 2;
}
}
else
{
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
}
else
{
/* Can't be a valid request because the length is incorrect. */
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
return eStatus;
}
将以下这句话注释掉
//usRegAddress++;
本人移植的代码如下
说明:本人移植的代码包含UCOSIII操作系统且在GNU编译器下移植。
链接如下:
https://download.csdn.net/download/u014100102/11203636
其他FreeModbus代码请参考我其他文章。