标准库不带操作系统移植FreeModbus到STM32

添加FreeModbus代码

首先准备一个空白的标准库项目。

下载FreeModbus源码。

将源码中的modbus文件夹复制到项目路径下,并把demo->BARE->port文件夹的内容也添加进来。
在这里插入图片描述

在这里插入图片描述

新建一个文件port.c备用。然后打开项目,将上述文件添加至项目,最好是按照文件夹建立不同分组。

完成后的项目结构如下:
在这里插入图片描述在这里插入图片描述
然后添加头文件路径,将modbus与port文件夹的内容包含。
在这里插入图片描述

修改代码

portserial.c

首先是串口文件portserial.c

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return FALSE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    return TRUE;
}

static void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

我们进行以下修改

在vMBPortSerialEnable函数进行串口中断的使能与失能,其实是切换发送或者接收。FreeModbus使用中断来进行数据的收发,但是由于Modbus协议特性,同时只能开启一种中断,即不能进行同时收发。

根据参数xRxEnable与xTxEnable的值,开启或关闭对应中断。这里发送中断选择TC、接收中断选择RXNE。

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
	if (xTxEnable)
	{
		USART_ITConfig(USART3,USART_IT_TC,ENABLE);
	}
	else
	{
		USART_ITConfig(USART3,USART_IT_TC,DISABLE);
	}
	if (xRxEnable)
	{
		USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
	}
	else
	{
		USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);
	}
}

xMBPortSerialInit函数进行串口初始化。传入的参数是串口、波特率、数据位与校验位。根据传入的参数对串口初始化,初始化成功返回TRUE,否则返回FALSE。

可以通过传入的参数进行灵活初始化,也可以不管参数,将初始化写死。这里使用UART3,波特率与参数一致,停止位1位,无校验位

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//打开串口3的GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//打开串口3时钟
	
	
	//配置USART3的RX,TX的GPIO口
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //将USART3_TX配置为复用推挽输出模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11; //将USART3_RX配置为浮空输入模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	//配置USART3
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_BaudRate = ulBaudRate;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	USART_Init(USART3,&USART_InitStruct);
	USART_Cmd(USART3,ENABLE);
	
	//配置中断
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;
	NVIC_Init(&NVIC_InitStruct);
  return TRUE;
}

xMBPortSerialPutByte与xMBPortSerialGetByte实现单字节收发

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    USART_SendData(USART3,ucByte);
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = USART_ReceiveData(USART3);
    return TRUE;
}

实现串口的中断函数。要求是发生发送与接收中断时,调用对应的函数。

void USART3_IRQHandler(void)
{
	if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
	{
		prvvUARTRxISR();  
		USART_ClearITPendingBit(USART3, USART_IT_RXNE);   
	}
	if(USART_GetITStatus(USART3, USART_IT_TC) == SET)
	{
		prvvUARTTxReadyISR();
		USART_ClearITPendingBit(USART3, USART_IT_TC);
	}
}

完整的portserial.c函数如下:

#include "port.h"
#include "stm32f10x.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 (xTxEnable)
	{
		USART_ITConfig(USART3,USART_IT_TC,ENABLE);
	}
	else
	{
		USART_ITConfig(USART3,USART_IT_TC,DISABLE);
	}
	if (xRxEnable)
	{
		USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
	}
	else
	{
		USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);
	}
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//打开串口3的GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//打开串口3时钟
	
	
	//配置USART3的RX,TX的GPIO口
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //将USART3_TX配置为复用推挽输出模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11; //将USART3_RX配置为浮空输入模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	//配置USART3
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_BaudRate = ulBaudRate;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	USART_Init(USART3,&USART_InitStruct);
	USART_Cmd(USART3,ENABLE);
	
	//配置中断
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;
	NVIC_Init(&NVIC_InitStruct);
  return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    USART_SendData(USART3,ucByte);
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = USART_ReceiveData(USART3);
    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 USART3_IRQHandler(void)
{
	if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
	{
		prvvUARTRxISR();  
		USART_ClearITPendingBit(USART3, USART_IT_RXNE);   
	}
	if(USART_GetITStatus(USART3, USART_IT_TC) == SET)
	{
		prvvUARTTxReadyISR();
		USART_ClearITPendingBit(USART3, USART_IT_TC);
	}
}

porttimer.c

本文件夹是初始化定时器,实现帧结束的截取。

xMBPortTimersInit初始化定时器,需要将计数间隔设定为50us(设时钟72MHz,这里将分频系数设置到3600-1,实现50us计时),定时周期按参数设置,并使能更新中断。这里我使用TIM1。

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1EN,ENABLE);
	TIM_TimeBaseInitTypeDef tbit;
	tbit.TIM_Prescaler = 3600-1;
	tbit.TIM_Period = usTim1Timerout50us;
	tbit.TIM_ClockDivision = TIM_CKD_DIV1;
	tbit.TIM_CounterMode = TIM_CounterMode_Up;
	tbit.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1,&tbit);
	TIM_ClearFlag(TIM1,TIM_IT_Update);
	
	NVIC_InitTypeDef nvic_inittypeddef;
	nvic_inittypeddef.NVIC_IRQChannel = TIM1_UP_IRQn;
	nvic_inittypeddef.NVIC_IRQChannelCmd = ENABLE;
	nvic_inittypeddef.NVIC_IRQChannelPreemptionPriority = 0;
	nvic_inittypeddef.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&nvic_inittypeddef);
 	return TRUE;
}

vMBPortTimersEnable与vMBPortTimersDisable函数进行定时器的使能与失能(去掉inline)。并写对应的中断函数,当中断触发时调用FreeModbus写好的处理函数。

void
vMBPortTimersEnable(  )
{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
		TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
		TIM_SetCounter(TIM1, 0);
		TIM_Cmd(TIM1, ENABLE);
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
}

void
vMBPortTimersDisable(  )
{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
		TIM_ITConfig(TIM1, TIM_IT_Update, DISABLE);
		TIM_SetCounter(TIM1, 0);
		TIM_Cmd(TIM1, DISABLE);
    /* Disable any pending timers. */
}

void TIM1_UP_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
	{
		prvvTIMERExpiredISR();
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
	}
}

以下是本文件全部内容:

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "stm32f10x.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1EN,ENABLE);
	TIM_TimeBaseInitTypeDef tbit;
	tbit.TIM_Prescaler = 3600-1;
	tbit.TIM_Period = usTim1Timerout50us;
	tbit.TIM_ClockDivision = TIM_CKD_DIV1;
	tbit.TIM_CounterMode = TIM_CounterMode_Up;
	tbit.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1,&tbit);
	TIM_ClearFlag(TIM1,TIM_IT_Update);
	
	NVIC_InitTypeDef nvic_inittypeddef;
	nvic_inittypeddef.NVIC_IRQChannel = TIM1_UP_IRQn;
	nvic_inittypeddef.NVIC_IRQChannelCmd = ENABLE;
	nvic_inittypeddef.NVIC_IRQChannelPreemptionPriority = 0;
	nvic_inittypeddef.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&nvic_inittypeddef);
 	return TRUE;
}


void
vMBPortTimersEnable(  )
{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
		TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
		TIM_SetCounter(TIM1, 0);
		TIM_Cmd(TIM1, ENABLE);
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
}

void
vMBPortTimersDisable(  )
{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
		TIM_ITConfig(TIM1, TIM_IT_Update, DISABLE);
		TIM_SetCounter(TIM1, 0);
		TIM_Cmd(TIM1, DISABLE);
    /* Disable any pending timers. */
}

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

void TIM1_UP_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
	{
		prvvTIMERExpiredISR();
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
	}
}

mbconfig.h

第49行将宏MB_ASCII_ENABLED失能,因为我们这里只使用RTU。
这个文件可以选择使能Modbus功能码函数,实现功能裁剪。

此时进行编译,会发现会报以下错误:
在这里插入图片描述四种数据类型(线圈、离散量、输入寄存器、保持寄存器)的操作函数与断言的定义没有实现,所以需要继续实现它们。

port.c

这个文件是自己新建的,我们在这个文件实现上述缺少的函数。

如何实现这些函数?同样可以参考Demo文件夹中的示例。例如Demo->MSP430->demo.c中的内容,这一部分可以复制到port.c中:

#include "mb.h"
#define REG_INPUT_START   0
#define REG_INPUT_NREGS   10
#define REG_HOLDING_START 0
#define REG_HOLDING_NREGS 10

static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];
static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( (int16_t)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( ( (int16_t)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;
}

前面的数组usRegInputBuf与usRegHoldingBuf就是操作的输入寄存器与保持寄存器,而REG_INPUT_START与REG_HOLDING_START是这两类寄存器的起始地址。当从机收到特定的功能码时,会转为对这些数据变量的操作。

下面的eMBRegInputCB与eMBRegHoldingCB就是输入寄存器与保持寄存器对应的处理函数。在Modbus协议层面来讲就是实现了对应的功能码。虽然目前看不懂具体实现,但是只需要贴进来用即可。

下面打开源码Demo->STR71X->excolis.c与exdisc.c,线圈量与离散量的处理函数就在里面。与寄存器类似,将它们复制到port.c。

#include "mbutils.h"
#define REG_COILS_START     0
#define REG_COILS_SIZE      16
static unsigned char ucRegCoilsBuf[REG_COILS_SIZE / 8];


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iNCoils = ( int )usNCoils;
    unsigned short  usBitOffset;

    /* Check if we have registers mapped at this block. */
    if( ( (int16_t)usAddress >= REG_COILS_START ) &&
        ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
    {
        usBitOffset = ( unsigned short )( usAddress - REG_COILS_START );
        switch ( eMode )
        {
                /* Read current values and pass to protocol stack. */
            case MB_REG_READ:
                while( iNCoils > 0 )
                {
                    *pucRegBuffer++ =
                        xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                        ( unsigned char )( iNCoils >
                                                           8 ? 8 :
                                                           iNCoils ) );
                    iNCoils -= 8;
                    usBitOffset += 8;
                }
                break;

                /* Update current register values. */
            case MB_REG_WRITE:
                while( iNCoils > 0 )
                {
                    xMBUtilSetBits( ucRegCoilsBuf, usBitOffset, 
                                    ( unsigned char )( iNCoils > 8 ? 8 : iNCoils ),
                                    *pucRegBuffer++ );
                    iNCoils -= 8;
                    usBitOffset += 8;
                }
                break;
        }

    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}


#define REG_DISC_START     0
#define REG_DISC_SIZE      16
static unsigned char ucRegDiscBuf[REG_DISC_SIZE / 8] = { 0, 0 };

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    short           iNDiscrete = ( short )usNDiscrete;
    unsigned short  usBitOffset;

    /* Check if we have registers mapped at this block. */
    if( ( (int16_t)usAddress >= REG_DISC_START ) &&
        ( usAddress + usNDiscrete <= REG_DISC_START + REG_DISC_SIZE ) )
    {
        usBitOffset = ( unsigned short )( usAddress - REG_DISC_START );
        while( iNDiscrete > 0 )
        {
            *pucRegBuffer++ =
                xMBUtilGetBits( ucRegDiscBuf, usBitOffset,
                                ( unsigned char )( iNDiscrete >
                                                   8 ? 8 : iNDiscrete ) );
            iNDiscrete -= 8;
            usBitOffset += 8;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

注意,开关量与离散量都是位数据,因此数组长度会除以8。

然后再给断言函数加上,整个port.c就写好了。

void __aeabi_assert(const char * x1, const char * x2, int x3)
{
	
}

实现上述函数与数据,就实现了Modbus绝大多数功能码。

mbrtu.c的eMBRTUSend函数

第213行后面添加代码:

xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;  /* next byte in sendbuffer. */
usSndBufferCount--;

更新后的eMBRTUSend函数:

eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          usCRC16;

    ENTER_CRITICAL_SECTION(  );

    /* Check if the receiver is still in idle state. If not we where to
     * slow with processing the received frame and the master sent another
     * frame on the network. We have to abort sending the frame.
     */
    if( eRcvState == STATE_RX_IDLE )
    {
        /* First byte before the Modbus-PDU is the slave address. */
        pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
        usSndBufferCount = 1;

        /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
        pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
        usSndBufferCount += usLength;

        /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
        usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );

        /* Activate the transmitter. */
        eSndState = STATE_TX_XMIT;
			
				xMBPortSerialPutByte((CHAR)*pucSndBufferCur);
        pucSndBufferCur++;
        usSndBufferCount--;
			
        vMBPortSerialEnable( FALSE, TRUE );
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

mbfunccoils.c,mbfuncdisc.c,mbfuncholding.c,mbfuncinput.c

首先去掉所有的usRegAddress++,否则实际操作会比期望地址大一。

然后mbfuncholding.c第185行,添加一个或负号:

usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );

至此,代码修改完成,编译应该没有error了。

测试

main文件

#include "stm32f10x.h"
#include "mb.h"
int main(void){
	eMBInit(MB_RTU, 0X01, 3, 9600, MB_PAR_NONE);//初始化FreeModbus
    eMBEnable();//FreeModbus使能
    while (1)
    {
        eMBPoll();//在while (1)循环调用eMBPoll()
    }
}

eMBInit进行初始化,其中第一个参数表示协议,第二个参数是从机地址,后面三个是初始化串口那个函数的参数,可以跳转到那里进行对照(这里配置为串口3,波特率9600,不校验)

eMBEnable()启动FreeModbus后,不断调用eMBPoll()即可。

port.c

这里我们修改一下各个位数据与寄存器的初始值,方便观察结果。

static USHORT   usRegInputBuf[REG_INPUT_NREGS] = {0,1,2,3,4,5,6,7,8,9};
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS] = {10,11,12,13,14,15,16,17,18,19};
static unsigned char ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x12,0x34};
static unsigned char ucRegDiscBuf[REG_DISC_SIZE / 8] = {0x56,0x78};

使用ModbusPoll连接,看到可以正常读出数据:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/499209.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Sectigo多域名ssl证书1200元

多域名SSL证书是可以同时保护多个域名的域名型数字证书之一&#xff0c;为个人和企事业单位提供了多样化的数字证书方案。各个正规的CA认证机构所颁发的多域名费SSL证书产品中&#xff0c;Sectigo旗下的多域名SSL证书是使用范围比较广的一款。今天就随SSL盾小编了解Sectigo旗下…

2024三掌柜赠书活动第十九期:DevOps企业级CI/CD实战

目录 目录 前言 关于CI/CD 企业级CI/CD实战 关于《DevOps企业级CI/CD实战》 编辑推荐 内容简介 作者简介 图书目录 书中前言/序言 《DevOps企业级CI/CD实战》全书速览 结束语 前言 作为开发者&#xff0c;对于编程语言并不陌生&#xff0c;随着技术圈的不断进步和发…

EI、Scopus双检索 | 2024年第四届控制理论与应用国际会议

会议简介 Brief Introduction 2024年第四届控制理论与应用国际会议(ICoCTA 2024) 会议时间&#xff1a;2024年10月18 -20日 召开地点&#xff1a;中国杭州 大会官网&#xff1a;www.icocta.org 控制理论作为一门科学技术&#xff0c;已经广泛地运用于我们社会生活方方面面。随着…

java-pytorch 使用手动下载FashionMNIST数据集进行测试

java-pytorch 使用手动下载FashionMNIST数据集进行测试 先定义训练数据和测试数据的位置查看一下读取到的标签数据格式使用loc和iloc访问下数据&#xff0c;便于下面操作使用read_image函数查看下图片的数据大小开始写数据集使用DataLoader去加载我们自己的数据看下加载后的dat…

游戏陀螺首条报道(一)|看完GDC 2024,我找到了网易数智引领游戏AI技术的关键!

经过近几年的爆发式增长&#xff0c;AI技术在游戏行业的应用取得了显著的进步。从去年多为“AI生成文字、图片、代码”等工具型应用&#xff0c;发展到了如今可以深入至游戏研发、运营的各个环节&#xff0c;这也让今年的游戏开发者大会&#xff08;GDC&#xff09;显得格外的热…

面试官:你是如何解决跨域的?

在近期的面试中&#xff0c;面试官针对我的项目&#xff0c;问到了 如何解决跨域&#xff1f; 没答好&#xff0c;我想通过这篇文章&#xff0c;巩固一下这方面的知识&#xff0c;分享一下我对于这个问题的理解&#xff0c;希望也能对大家有所帮助。 我的回答 跨域我们需要从浏…

javaWeb项目-火车票订票信息系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Spring Boot框架 …

IP/TCP--解决为什么电脑连上了有线网就不能再连WIFI【转载】

文章目录 第一种情况&#xff1a;WIFI与有线网在同一网段下1、查看路由信息2、调整跃点数 第二种情况&#xff1a;WIFI与有线网不在同一网段下跃点数概念路由器设置入口 【注意适用情型&#xff1a;需要同时用到内网&#xff08;不能上公网的内部网络&#xff09;和互联网。】 …

Spring Cloud+Spring Alibaba笔记

Spring CloudSpring Alibaba 文章目录 Spring CloudSpring AlibabaNacos服务发现配置中心 OpenFeign超时机制开启httpclient5重试机制开启日志 SeataSentinel流量控制熔断降级热点控制规则持久化集成 OpenFeign集成 Gateway MicrometerZipKinGateway路由断言过滤器 Nacos 服务…

什么是广告可见性测量 如何测量广告可见性

广告可见性测量 & 如何测量广告可见性 --- 一起来来认识MOAT 现在是2024年&#xff0c;大家或许还记得大约8年前广告可见性&#xff08;Ad viewability&#xff09;成为数字媒体世界的一种货币以来&#xff0c;出版商一直处于不利地位。当广告商用不同的工具和技术武装自…

在微信上处理小程序用户反馈具体的方法

想必大家在开发小程序的时候&#xff0c;一定遇到这种情况&#xff1a; 为了用户有更好的体验&#xff0c;我们会定期登陆微信小程序来查看用户反馈并对用户的问题进行回复和处理&#xff0c;但是这种方法的缺点就是最多间隔48小时要进行登陆一次以及回复用户不及时。 这样的话…

2月运动鞋行业线上电商数据分析:国产品牌高歌猛进,占据热榜90%

随着天气回暖&#xff0c;踏青出行的人越来越多&#xff0c;运动鞋市场呈现出繁荣状态&#xff0c;尤其是国产品牌。 根据鲸参谋数据显示&#xff0c;2024年2月在京东平台的运动鞋销量表现强劲&#xff0c;累计约233万件&#xff0c;同比上个月涨幅了58%&#xff1b;销售额累计…

泛型的通配符及擦除机制详解

目录 一、通配符解决什么问题 二、通配符上界 三、通配符下界 ​编辑 四、泛型类的擦除机制 引言&#xff1a; 在这篇文章中&#xff0c;我主要介绍前一篇泛型没介绍完整的泛型通配符和泛型的擦除机制Java中泛型的详细介绍 ? 用于在泛型的使用&#xff0c;即为通配符 一、通…

Qt+OpenGL入门教程(二)——OpenGL渲染管线

渲染管线是图形学不可或缺的&#xff0c;在学习它之前&#xff0c;我们先了解一下什么是管线&#xff1f; 管线/流水线 当我们谈到管线时&#xff0c;我们指的是一个由多个阶段组成的过程&#xff0c;每个阶段都完成任务的一部分。在现实世界中&#xff0c;流水线的概念在许多…

小白了解Pinia第2集 · 三大核心状态Getters、Actions以及Plugins 插件

三大核心状态 state 第1集有详细讲解&#xff1a;https://blog.csdn.net/qq_51463650/article/details/137137080?spm1001.2014.3001.5501 getters Getter 完全等同于 Store 状态的 计算值。 它们可以用 defineStore() 中的 getters 属性定义。 他们接收“状态”作为第一个…

C++从入门到精通——缺省参数

缺省参数 前言一、缺省参数概念二、缺省参数分类位置参数的缺省参数全缺省参数半缺省参数 关键字参数的缺省参数函数指针的缺省参数lambda表达式 三、缺省参数的具体代码展示main.cpp 前言 缺省参数是在函数定义时指定的默认值&#xff0c;当调用函数时未提供该参数的值时&…

统信 UOS V20 一键安装 Oracle 12CR2(220118)单机版

Oracle 一键安装脚本&#xff0c;演示 统信 UOS V20 一键安装 Oracle 12CR2&#xff08;220118&#xff09;单机版过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;…

云主机8核16G配置租用优惠价格1198元1年、4688元三年

京东云8核16G租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 atengyun.com/go/jd 京东云8核16G租用优惠价格 京东云&#xff1a;轻量云主机CPU内存&#xff1a;8C16G公网带…

AIGC重塑金融 | 大模型在金融行业的应用场景和落地路径

作者&#xff1a;林建明 来源&#xff1a;IT阅读排行榜 本文摘编自《AIGC重塑金融&#xff1a;AI大模型驱动的金融变革与实践》&#xff0c;机械工业出版社出版 目录 01 大模型在金融领域的 5 个典型应用场景 02 大模型在金融领域应用所面临的风险及其防范 03 AIGC 技术的科…

职场沟通教训 程序汪改了一行代码,导致测试和开发大战

本文章有视频的&#xff0c;请到B站 我是程序汪 观看 程序汪改了一行代码&#xff0c;导致测试和开发大战&#xff0c;职场沟通教训 程序汪改了一行代码&#xff0c;导致测试和开发大战 鸡汤文 每个人都会在沟通上出问题 工作上沟通出问题可能让你郁闷一天、丢了客户、损失金…