STM32 HAL库移植FreeModbus详细步骤

STM32 HAL库移植freemodbus-v1.6详细步骤


freemodbus下载地址:https://github.com/cwalter-at/freemodbus
说明:STM32CUBEMX5.6、freemodbus-v1.6,使用正点原子MiniSTM32-V2(STM32F103RBT6)开发板测试通过。


FreeModbus文件说明

         ~~~~~~~~         解压freemodbus文件后打开,我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.cporttimer.cport.h这三个文件。
STM32 HAL库移植FreeModbus详细步骤_第1张图片
         ~~~~~~~~         mobus文件夹就是完整的源码,包含rtu、ascii、tcp:
STM32 HAL库移植FreeModbus详细步骤_第2张图片
         ~~~~~~~~         我为了移植时在keil添加源文件和头文件方便,就把modbus所有的头文件和源文件放到了一个文件夹下,并创建了一个port.c文件,用于编写modbus所必需的回调处理函数:
STM32 HAL库移植FreeModbus详细步骤_第3张图片


STM32CUBEMX配置

时钟配置,设置主频工作在72MHz下:
STM32 HAL库移植FreeModbus详细步骤_第4张图片
配置串口1,这里随便配置就行,在modbus移植过程中还会对串口重新初始化:
STM32 HAL库移植FreeModbus详细步骤_第5张图片
配置定时器4,用于3.5个字符的定时检测,这里随便配置就行,在modbus移植过程中还会对定时器重新初始化:
STM32 HAL库移植FreeModbus详细步骤_第6张图片
中断配置,这里注意,串口的优先级是要比定时器优先高的:
STM32 HAL库移植FreeModbus详细步骤_第7张图片
取消掉自动生成中断服务程序,在移植过程中我们要自己编写串口和定时器的中断服务程序:
STM32 HAL库移植FreeModbus详细步骤_第8张图片

移植代码修改

生成代码后将modbus放到工程目录下:
STM32 HAL库移植FreeModbus详细步骤_第9张图片
打开keil工程添加modbus源码:
STM32 HAL库移植FreeModbus详细步骤_第10张图片
添加包含头文件:
STM32 HAL库移植FreeModbus详细步骤_第11张图片

修改modbus定时器初始化源代码porttimer.c文件

         ~~~~~~~~         定时器的修改比较容易,将定时器设置为每50us的时长记一个数,传入的usTim1Timerout50us变量给自动装载即可,prvvTIMERExpiredISR函数需要在定时器中断服务函数中调用,它的作用是用于通知modbus协议栈3.5个字符的等待时间已经到达;由于我们在STM32CUBEMX中取消掉了定时器和串口的中断服务函数程序,所以我们在该文件中添加定时器的中断服务程序,修改后的代码如下:

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    htim4.Instance = TIM4;
    htim4.Init.Prescaler = 3599;								// 50us记一次数
    htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim4.Init.Period = usTim1Timerout50us - 1;					// usTim1Timerout50us * 50即为定时器溢出时间
    htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
    {
        return FALSE;
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
    {
        return FALSE;
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
    {
        return FALSE;
    }

    __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);					// 使能定时器更新中断
    return TRUE;
}

inline void
vMBPortTimersEnable(  )
{
    __HAL_TIM_SET_COUNTER(&htim4, 0);		// 清空计数器
    __HAL_TIM_ENABLE(&htim4);				// 使能定时器
}

inline void
vMBPortTimersDisable(  )
{
    __HAL_TIM_DISABLE(&htim4);				// 禁能定时器
}

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

/// 定时器4中断服务程序
void TIM4_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))			// 更新中断标记被置位
    {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);		// 清除中断标记
        prvvTIMERExpiredISR();								// 通知modbus3.5个字符等待时间到
    }
}


修改modbus串口初始化源代码portserial.c文件

         ~~~~~~~~         在该文件中实现串口1的中断服务程序,prvvUARTTxReadyISRprvvUARTRxISR函数需要填写进中断服务程序,前者得到作用为通知modbus协议栈串口已经空闲可以发送数据了,后者的作用为通知modbus串口1有数据到达,修改后的代码如下:

/*
 * 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$
 */

#include "port.h"
#include "usart.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)
    {
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);		// 使能接收非空中断
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);		// 禁能接收非空中断
    }

    if(xTxEnable)
    {
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);			// 使能发送为空中断
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);		// 禁能发送为空中断
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;

    switch(eParity)
    {
	// 奇校验
    case MB_PAR_ODD:
        huart1.Init.Parity = UART_PARITY_ODD;
        huart1.Init.WordLength = UART_WORDLENGTH_9B;			// 带奇偶校验数据位为9bits
        break;
	
	// 偶校验
    case MB_PAR_EVEN:
        huart1.Init.Parity = UART_PARITY_EVEN;
        huart1.Init.WordLength = UART_WORDLENGTH_9B;			// 带奇偶校验数据位为9bits
        break;
	
	// 无校验
    default:
        huart1.Init.Parity = UART_PARITY_NONE;
        huart1.Init.WordLength = UART_WORDLENGTH_8B;			// 无奇偶校验数据位为8bits
        break;
    }
    return HAL_UART_Init(&huart1) == HAL_OK ? TRUE : FALSE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    USART1->DR = ucByte;
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = (USART1->DR & (uint16_t)0x00FF);
	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(  );
}

void USART1_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))			// 接收非空中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);			// 清除中断标记
        prvvUARTRxISR();										// 通知modbus有数据到达
    }

    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))				// 发送为空中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);			// 清除中断标记
        prvvUARTTxReadyISR();									// 通知modbus数据可以发松
    }
}

编写modbus命令处理回调函数port.c文件

本例程只实现了读取输入寄存器和保持寄存器的功能,详细代码如下:

#include "mb.h"
#include "mbport.h"

#define NB_REG_INPUT_SIZE  10						///< 输入寄存器大小
uint16_t MB_REG_INPUT_BUF[NB_REG_INPUT_SIZE];		///< 输入寄存器

#define NB_REG_HOLD_SIZE  10						///< 保持寄存器大小
uint16_t MB_REG_HOLD_BUF[NB_REG_HOLD_SIZE];			///< 保持寄存器

/**
 * CMD3回调函数
 * @param  pucRegBuffer 存放读取到的输入寄存器的值
 * @param  usAddress    要读取的输入寄存器起始地址
 * @param  usNRegs      要读取的输入寄存器数量
 * @return              MB_ENOERR:成功  other:失败
 */
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    int iRegIndex = usAddress - 1;
	
	// 非法检测
	if((iRegIndex + usNRegs) > NB_REG_INPUT_SIZE)
	{
		return MB_ENOREG;
	}
	
	// 循环读取
	while( usNRegs > 0 )
	{
		*pucRegBuffer++ =
			( unsigned char )( MB_REG_INPUT_BUF[iRegIndex] >> 8 );
		*pucRegBuffer++ =
			( unsigned char )( MB_REG_INPUT_BUF[iRegIndex] & 0xFF );
		iRegIndex++;
		usNRegs--;
	}
	
	// 模拟输入寄存器被改变
	for(iRegIndex = 0; iRegIndex < NB_REG_INPUT_SIZE; iRegIndex++)
	{
		MB_REG_INPUT_BUF[iRegIndex]++;
	}
	
    return MB_ENOERR;
}

/**
 * CMD3回调函数
 * @param  pucRegBuffer 存放读取到的保持寄存器值
 * @param  usAddress    要读取的保持寄存器地址
 * @param  usNRegs      要读取的保持寄存器数量
 * @param  eMode        模式,读取或者写入,本例程只用了读取
 * @return              MB_ENOERR:成功  other:失败
 */
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    int iRegIndex = usAddress - 1;
	
	// 非法检测
	if((iRegIndex + usNRegs) > NB_REG_HOLD_SIZE)
	{
		return MB_ENOREG;
	}
	
	// 循环读取
	while( usNRegs > 0 )
	{
		*pucRegBuffer++ =
			( unsigned char )( MB_REG_HOLD_BUF[iRegIndex] >> 8 );
		*pucRegBuffer++ =
			( unsigned char )( MB_REG_HOLD_BUF[iRegIndex] & 0xFF );
		iRegIndex++;
		usNRegs--;
	}
	
	// 模拟保持寄存器被改变
	for(iRegIndex = 0; iRegIndex < NB_REG_HOLD_SIZE; iRegIndex++)
	{
		MB_REG_HOLD_BUF[iRegIndex]++;
	}
	
    return MB_ENOERR;
}

/// 未使用
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

/// 未使用
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

主函数

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_ODD);		// 初始化modbus为RTU方式,波特率9600,奇校验
    eMBEnable();									// 使能modbus协议栈

    for( ;; )
    {
        eMBPoll();									// 轮训查询
    }
}

移植测试

STM32 HAL库移植FreeModbus详细步骤_第12张图片


ends…

你可能感兴趣的:(STM32,freemodbus,hal,stm32,移植freemodbus)