freemodbus下载地址:https://github.com/cwalter-at/freemodbus
说明:STM32CUBEMX5.6、freemodbus-v1.6,使用正点原子MiniSTM32-V2(STM32F103RBT6)开发板测试通过。
~~~~~~~~ 解压freemodbus文件后打开,我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c
、porttimer.c
、port.h
这三个文件。
~~~~~~~~ mobus文件夹就是完整的源码,包含rtu、ascii、tcp:
~~~~~~~~ 我为了移植时在keil添加源文件和头文件方便,就把modbus所有的头文件和源文件放到了一个文件夹下,并创建了一个port.c
文件,用于编写modbus所必需的回调处理函数:
时钟配置,设置主频工作在72MHz下:
配置串口1,这里随便配置就行,在modbus移植过程中还会对串口重新初始化:
配置定时器4,用于3.5个字符的定时检测,这里随便配置就行,在modbus移植过程中还会对定时器重新初始化:
中断配置,这里注意,串口的优先级是要比定时器优先高的:
取消掉自动生成中断服务程序,在移植过程中我们要自己编写串口和定时器的中断服务程序:
生成代码后将modbus放到工程目录下:
打开keil工程添加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个字符等待时间到
}
}
portserial.c
文件 ~~~~~~~~ 在该文件中实现串口1的中断服务程序,prvvUARTTxReadyISR
和prvvUARTRxISR
函数需要填写进中断服务程序,前者得到作用为通知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数据可以发松
}
}
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(); // 轮训查询
}
}
ends…