环境
硬件
NUCLEO-F030R8,芯片为 STM32F030R8。该板子 RAM 为 8KB,FLASH 为 64KB,主频最高为48MHz。
软件
IAR EWARM 8.22.1 + Stm32CubeMX 6.3 + HAL 1.11.3 + freeModbus 最新版
前言
本来是想用 libModbus 3.1.6,因为我的主站用的是这个。但是尝试移植到裸奔系统的时候才发现,libModbus 3.1.6 是在 Linux 下使用的。所以只好放弃,重新回到 freeModbus。
freeModbus 移植需求
freeModbus 是一个很小开源的 Modbus 协议栈,支持 ascii,rtu 和 tcp 模式。移植 freeModbus 需要硬件支持包括如下: 1、一个串口。 2、一个定时器。用于产生 3.5T 时间中断。 3、Modbus 相关的回调函数。
使用 CubeMX 生产基本代码
时钟
NUCLEO-F030R8 板子没有晶振,最高的时钟频率为
48
48
48MHz,我这里配置为
40
40
40MHz。其实这个频率可以任意配置,没有什么特别的要求。
串口
我是用了 usart1,波特率什么都可以随便设置,因为在一直 freeModbus 的时候都会重新配置。只需要硬件使能串口,并打开中断即可。下面是 CubeMX 的配置截图。 下图为串口中断配置。 主要目的是让 CubeMX 自动生成对应的代码。注意串口中断的优先级设置为最高。
定时器
任意选择一个 Timer 都可以。这里我选择了 TIM6,没有什么特别原因,就是它简单。配置如图。 也是随便配置一下。同样,在移植 freeModbus 的时候,会将 TIM6 重新配置的。 下图是 TIM6 中断配置。 同样注意 TIM6 的优先级设置为
2
2
2,比 USART1 的优先级低。
生成代码
这样配置完成后,生成代码即可。
freeModbus 移植
拷贝代码
我遵守 CubeMX 的方式,也就是增加了 Middlewares 目录。然后将 freeModbus-master 压缩包中的 modbus 目录拷贝过来即可。对应的目录结构和文件如下图。
port 目录
根据 freeModbus 官方文档定义,移植相关代码建议放在 port 目录下。建议参考 freeModbus-master/demo/bare 目录。 在该目录下有:文件 demo.c,该文件告诉你 main() 函数应该写什么,modbus 对应的回调函数应该如何实现。子目录 port 下有
4
4
4 个文件: port.h portevent.c 事件相关的移植文件 portserial.c 串口相关的移植文件 porttimer.c 定时器相关的移植文件 我就是将 bare 目录的 port 直接拷贝过来。具体的目录结构参考上图。
port.h
该文件不需要什么改动,只需要增加对应的板子头文件即可。我增加了如下代码:
#include "stm32f0xx_hal.h"
portevent.c
这个文件我没有做任何修改。
portserial.c
vMBPortSerialEnable
该函数为串口使能函数。根据自己的板子对串口中断进行使能即可。 我的代码如下:
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if(xRxEnable == TRUE)
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
else
__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
if(xTxEnable == TRUE)
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
else
__HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
}
xMBPortSerialInit
该函数为串口初始化函数。根据自己的板子对串口初始化即可。 我的代码如下:
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
HAL_UART_DeInit(&huart1);
(void)ucPORT;
huart1.Instance = USART1;
huart1.Init.BaudRate = ulBaudRate;
huart1.Init.StopBits = UART_STOPBITS_1;
switch (eParity)
{
case MB_PAR_ODD:
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.Parity = UART_PARITY_ODD;
break;
case MB_PAR_EVEN:
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.Parity = UART_PARITY_EVEN;
break;
default:
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.Parity = UART_PARITY_NONE;
break;
}
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
注意: 1、我是用的是 USART1,所以对应为 huart1,参考 usart.c。 2、由于 CubeMX 会对串口进行初始化,所以要先 DeInit。
xMBPortSerialPutByte
发送一个字节。
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
if(HAL_UART_Transmit(&huart1,(uint8_t*)&ucByte,1,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
xMBPortSerialGetByte
接收一个字节。
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
if(HAL_UART_Receive(&huart1,(uint8_t*)pucByte,1,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
prvvUARTTxReadyISR
Tx Ready ISR。
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。
prvvUARTRxISR
Rx ISR。
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。
porttimmer.c
xMBPortTimersInit
定时器初始化函数。
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
HAL_TIM_Base_DeInit(&htim6);
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 4499;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = usTim1Timerout50us-1;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
return FALSE;
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
感觉这个定时器不需要特别的准确。随便给一个时间就可以了。
vMBPortTimersEnable
使能定时器中断。
inline void
vMBPortTimersEnable( )
{
__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
__HAL_TIM_SetCounter(&htim6,0);
HAL_TIM_Base_Start_IT(&htim6);
}
vMBPortTimersDisable
inline void
vMBPortTimersDisable( )
{
HAL_TIM_Base_Stop_IT(&htim6);
__HAL_TIM_SetCounter(&htim6,0);
__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
}
prvvTIMERExpiredISR
定时器结束中断。
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
注意:原来这个函数是一个静态函数,需要将 static 删除。我在 TIM 中断中调用了本函数。
中断函数
道歉这个部分忘记加了。节后补上。
回调函数
由于 freeModbus 采用了回调函数的方法来实现具体的功能。所以我们必须实现对应的协议回调函数。 这部分代码的实现,可以参考 freeModbus-master/demo 目录中任意一个实现。
头文件
必须包含 mb.h。
#include "mb.h"
初始化
主要是定义开始地址,数据缓存区之类。
#define REG_INPUT_START 0x0001U
#define REG_INPUT_NREGS 4
#define REG_HOLDING_START ( 1 )
#define REG_HOLDING_NREGS ( 32 )
static uint16_t usRegInputStart = REG_INPUT_START;
static uint16_t usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
eMBRegInputCB
Read Input Register。响应功能码 0x04。
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;
}
eMBRegHoldingCB
Write Holding Register。响应功能码 0x06。
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 )
{
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBRegCoilsCB
Read Coils。响应功能码 0x01。
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
也就是说我暂时没有实现。
eMBRegDiscreteCB
Read Discrete Inputs。响应功能码 0x02。
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
其他相关功能码
其参考 mb.c。代码如下:
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
{MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
{MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
{MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
{MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
{MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
{MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};
freeModbus 支持的功能码
参考 mbproto.h。支持的功能码如下:
#define MB_FUNC_READ_COILS ( 1 )
#define MB_FUNC_READ_DISCRETE_INPUTS ( 2 )
#define MB_FUNC_WRITE_SINGLE_COIL ( 5 )
#define MB_FUNC_WRITE_MULTIPLE_COILS ( 15 )
#define MB_FUNC_READ_HOLDING_REGISTER ( 3 )
#define MB_FUNC_READ_INPUT_REGISTER ( 4 )
#define MB_FUNC_WRITE_REGISTER ( 6 )
#define MB_FUNC_WRITE_MULTIPLE_REGISTERS ( 16 )
#define MB_FUNC_READWRITE_MULTIPLE_REGISTERS ( 23 )
#define MB_FUNC_DIAG_READ_EXCEPTION ( 7 )
#define MB_FUNC_DIAG_DIAGNOSTIC ( 8 )
#define MB_FUNC_DIAG_GET_COM_EVENT_CNT ( 11 )
#define MB_FUNC_DIAG_GET_COM_EVENT_LOG ( 12 )
#define MB_FUNC_OTHER_REPORT_SLAVEID ( 17 )
代码运行效果
04 命令测试
注意上面代码中
static uint16_t usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};
06 命令测试
03 命令测试
其实代码收到 03 命令就直接返回了。但是从协议的角度。报文时正常的。
|