标准库不带操作系统移植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);//配置USART3USART_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);//配置USART3USART_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 10static 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);//初始化FreeModbuseMBEnable();//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连接,看到可以正常读出数据:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简述双亲委派机制

双亲委派机制是Java虚拟机&#xff08;JVM&#xff09;在加载类时遵循的一种类加载器层次结构与协作规则。它定义了当一个类加载器接收到类加载请求时&#xff0c;首先将加载任务委托给其父类加载器&#xff0c;直至请求到达最顶级的类加载器。只有当父类加载器无法完成加载时&…

php phar反序列化POC编写笔记

具体结构不细究&#xff0c;主要方便写poc代码&#xff0c;比如有如下文件内容&#xff1a; 文件内容如下&#xff1a; file base64.b64decode("PD9waHAgX19IQUxUX0NPTVBJTEVSKCk7ID8DQp9AQAAAQAAABEAAAABAAAAAABHAQAATzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZG…

Leetcode 27. 移除元素

给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面…

Kepler无代码:让应用开发0门槛,普通人秒变“大神”!

在数字化时代&#xff0c;软件开发应用已成为我们生活和工作中不可或缺的一部分。 然而&#xff0c;对于大多数人来说&#xff0c;编写代码、开发应用似乎是一个遥不可及的技能。幸运的是&#xff0c;随着无代码平台的兴起&#xff0c;普通人也能轻松成为应用开发者&#xff0…

实战篇:商品API接口在跨平台销售中的有效运用与案例解析

引言 随着电子商务的蓬勃发展&#xff0c;企业为了扩大市场覆盖面&#xff0c;经常需要在多个在线平台上展示和销售产品。然而&#xff0c;手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要&#xff0c;它能够帮助企业在不…

Node.js-------初识Node.js与内置模块

能够知道什么是 Node.js能够知道 Node.js 可以做什么能够说出 Node.js 中的 JavaScript 的组成部分能够使用 fs 模块读写操作文件能够使用 path 模块处理路径能够使用 http 模块写一个基本的 web 服务器 一.初识Node.js 1.浏览器中的 JavaScript 的组成部分 2.Node.js 简介 …

nut-ui中的menu 菜单组件的二次封装

这个菜单组件 一般可以直接用到项目里 如果复用性不强的话 直接使用 但是有一个问题 如果很多地方都需要用到这个组件 我们可以把这个组件二次封装一下 <template><div class"cinema-search-filter-component"><nut-menu><template #icon>&…

Debian12 安装Python2.7

源码安装Python2.7.18 。是目前2020年后发布的新Linux发行版的唯一途径。并且安装好了之后只能运行基本的python2功能。不能作为共享库使用&#xff0c;即 ./configure --enable-shared 会导致编译通不过。也不能优化&#xff0c;即 ./configure --enable-optimizations 会导致…

【PyTorch][chapter 25][李宏毅深度学习][ CycleGAN]【实战】

前言&#xff1a; 论文中直接提供了GitHub 的代码下载地址 GitHub - junyanz/pytorch-CycleGAN-and-pix2pix: Image-to-Image Translation in PyTorch 这里面简单的解读一下. 目录&#xff1a; 1. 模型参数配置 2&#xff1a; 生成器模型 3&#xff1a; 鉴别器模型 4&#…

TCP服务端主动向客户端发送数据

C TCP 服务端和客户端通信的例子 在此基础上&#xff0c;要修改服务端代码&#xff0c;使其能够每秒向客户端发送当前时间&#xff0c;你需要添加一个循环&#xff0c;每次循环发送当前时间给客户端。同时&#xff0c;你需要在客户端代码中添加接收服务端发送的数据的逻辑。 …

基于PHP的校园招聘管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的校园招聘管理系统 一 介绍 此校园招聘管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为个人用户&#xff0c;企业和管理员三种。 技术栈&#xff1a;phpmysqlbootstrapphpstudyvscode 二…

蓝桥杯刷题第六天(昨天忘记发了)

今天想从不一样的角度来解题&#xff1a;从时间紧张暴力求解到思路阔达直接通过所有案例 暴力方法&#xff1a; 思路第一眼看到这个问题我就想到了第一个思路就是先用两个数组一个存石子数一个存颜色状态&#xff0c;每次遍历一遍看看有没有相邻石子颜色一样且为和最小的。 im…

C语言算法学习记录:不创建第三个参数进行交换

int a 90;int b 20;printf("交换之前 a %d b %d\n", a, b);a a ^ b;b a ^ b;a a ^ b;printf("交换之后 a %d b %d\n", a, b);

深度学习理论基础(五)卷积神经网络CNN

目录 一、卷积神经网络基础1.卷积层&#xff08;1&#xff09;内部参数&#xff1a;卷积核权重&#xff08;2&#xff09;内部参数&#xff1a;偏置&#xff08;3&#xff09;外部参数&#xff1a;填充padding&#xff08;默认不填充&#xff09;&#xff08;3&#xff09;外部…

分月饼 java题解

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in); int m sc.nextInt(); // 读取员工数量mint n sc.nextInt(); // 读取月饼数量n// 调用distribute方法并打印返回的分配方法总数//先默认每人分一个…

--headless=new,pythom,爬虫

相信大家在看很多爬虫资料的时候&#xff0c;都知道&#xff0c;有一个无头模式。也就是不显示浏览器窗口。 但是实际上&#xff0c;无头模式&#xff0c;在很多地方会被很多网站检测。比如cloundfront, 亚马逊的DNS保护就会拦截导致403 错误。 实际上呢。无头模式&#xff0…

2024年华为OD机试真题-亲子游戏-Java-OD统一考试(C卷)

题目描述: 宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N)的格子地图上,宝宝和妈妈抽签决定各自的位置,地图上每个格子有不同的糖果数量,部分格子有障碍物。 游戏规则是妈妈必须在最短的时间(每个单位时间只能走一步)到达宝宝的位置,路上的所有糖果都可以拿走,不能走障…

如何冷迁移Oracle RAC到单机(非RMAN)

1、起因 群友说有套数据库要进行迁移&#xff0c;源端是套跑了十年的RAC&#xff0c;目标段是个新的单机&#xff08;都是同一架构平台&#xff09;&#xff0c;数据量约3T左右。 目前DATA目录存储和归档放在一起&#xff0c;整个磁盘组只剩下了20G空间&#xff0c;每间隔1小…

Google DeepMind 大语言模型中的长形态事实性

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 论文标题&#xff1a;Long-form factuality in large language models 论文链接&#xff1a;https://arxiv.org/abs/2403.18802 论文的关键信息总结如下&#xff1a; 研究问题是什么&#xff1f;论文…

如何优化Flutter应用以通过iOS应用商店的审核流程

本文探讨了使用Flutter开发的iOS应用能否上架&#xff0c;以及上架的具体流程。苹果提供了App Store作为正式上架渠道&#xff0c;同时也有TestFlight供开发者进行内测。合规并通过审核后&#xff0c;Flutter应用可以顺利上架。但上架过程可能存在一些挑战&#xff0c;因此可能…