Modbus是一种串行通信协议,最早应用于PLC通信,现已经成为工业领域通信协议的业界标准。Freemodbus是针对嵌入式应用的通用Modbus协议移植。本文将介绍使用标准外设STD库实现 FreeMODBUS 在 STM32 上的移植。
导入项目
从 FreeMODBUS 官方网站下载库文件。在 demo
> BARE
中导入下图中的文件及头文件至项目中。其中 modbus.c为新建文件,用于存储示例程序中 main.c 的内容。
项目配置
须在 Options for target > Target > Code Generation 中勾选 Use MicroLIB 选项。否则程序可能会出现卡死,汇编卡在
0x8xxxxxxx BEAB BKPT 0xAB
在 Options for target > C/C++ > Preprocessor Symbols > Define 中增加 NDEBUG,注意各项间用 ,
连接。否则编译过程会报错
.\Objects\project.axf: Error: L6218E: Undefined symbol __aeabi_assert (referred from mbrtu.o).
串口配置
串口配置相关函数在 portserial.c
中。vMBPortSerialEnable
、xMBPortSerialInit
、xMBPortSerialPutByte
、xMBPortSerialGetByte
分别是在串口进行使能、初始化、发送字节、接受字节的调用函数,在相关行为触发时调用。我们需要根据实际情况加入配置程序。
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
// enable rx interrupt
if(xRxEnable == TRUE) USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
else USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
// enable tx interrupt
if(xTxEnable == TRUE) USART_ITConfig(USART1, USART_IT_TC, ENABLE);
else USART_ITConfig(USART1, USART_IT_TC, DISABLE);
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
usart1_init((int)ulBaudRate);
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. */
USART_SendData(USART1, ucByte);
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.
*/
*pucByte = USART_ReceiveData(USART1);
return TRUE;
}
上面程序中除了串口初始化中调用的是自己的函数,其他的几个函数调用的都是库函数,可以直接使用。
定时器配置
定时器相关函数位于 porttimer.c
文件中,结构和串口配置函数类似。不同的是定时器必须配置中断。
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
timer2_init(usTim1Timerout50us);
return TRUE;
}
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_SetCounter(TIM2, 0x0000);
TIM_Cmd(TIM2, ENABLE);
}
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
TIM_SetCounter(TIM2, 0x0000);
TIM_Cmd(TIM2, DISABLE);
}
/* 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( );
}
同样,除了初始化函数外,都直接调用的库函数,代码可直接使用。
此外,在定时器中断还需要调用 prvvTIMERExpiredISR
// modbus timer
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
prvvTIMERExpiredISR();
}
}
寄存器配置
寄存器配置函数在官方例程中是直接写在 main.c 中,我们可以根据自己的情况放置。我专门新建了一个文件 modbus.c ,将寄存器配置相关函数放置其中。
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "modbus.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4
#define REG_HOLDING_START 1000
#define REG_HOLDING_NREGS 130
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
/* ----------------------- Start implementation -----------------------------*/
void modbus_init( void )
{
/* Initialize Protocol Stack. */
eMBInit( MB_RTU, 0x0A, 0, 9600, MB_PAR_NONE );
eMBEnable( );
return;
}
void modbus_run( void )
{
( void )eMBPoll( );
return;
}
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;
}
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
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
modbus_init 是FreeMODBUS 相关初始化程序;modbus_run 为运行函数,在程序空闲时调用;
排故
程序可能会出现卡死,汇编码停在 0x8xxxxxxx BEAB BKPT 0xAB
须在 Options for target > Target > Code Generation 中勾选 Use MicroLIB 选项。
编译报错 Undefined symbol __aeabi_assert
完整错误信息
.\Objects\project.axf: Error: L6218E: Undefined symbol __aeabi_assert (referred from mbrtu.o).
在 Options for target > C/C++ > Preprocessor Symbols > Define 中增加 NDEBUG,注意各项间用 ,
连接。
仅回复一包数据,之后不再响应
在 mbrtu.c 中 eMBRTUSend 函数
/* Activate the transmitter. */
eSndState = STATE_TX_XMIT;
之后添加
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;
usSndBufferCount--;