1、FreeModbus的源码下载地址,Modbus协议的文档
下载地址:https://sourceforge.net/projects/freemodbus.berlios/。
FreeMdbus的V1.6版本源码和调试工具,以及中文版的Modbus协议文档在下面百度云连接里。
链接:https://pan.baidu.com/s/1W6iQsXbmZb8QY3khkLGwFQ
提取码:h217
FreeModbus是对Modbus协议封装,所以要想使用它需要先熟悉Modbus协议。Modbus协议本身也不是特别难,所以有时间的话可以自己先实现这个协议,然后再移植FreeModbus。
2、FreeModbus的移植
(1)源码文件结构
下载下来的源码的目录结构如下图所示,重要的代码部分在modbus文件夹下。移植的时候我们需要modbus文件夹下所有文件以及demo文件夹下的对应芯片的demo文件。
我们这里使用的是STM32芯片,所以使用demo文件夹下的BARE里面的工程文件。modbus文件夹里面是FreeModbus的主要源码,而demo文件夹下就是开发者根据不同的平台写的相关示例,我们根据自己的使用情况选择对应的示例完成移植。
(2)创建工程,开始移植。项目工程使用STM32CubeMX生成模板工程
这里有很多博客都写过了,我移植的时候参考的几个博客放到这里,自己就不在重复的写一遍了。
STM32 HAL库移植FreeModbus详细步骤:https://blog.csdn.net/qq153471503/article/details/104840279
STM32 移植FreeModbus详细过程:http://www.mcublog.cn/software/2020_03/stm32-freemodbus-yizhi/
利用STM32CubeMx创建好工程后,最后项目的目录结构如下所示:
(2)创建好工程后,我们开始移植,完善底层代码,实现应用层代码,完成移植。
一、串口部分
①首先是串口部分,这是实现modbus的基础,一般实现的485功能都是通过串口实现的,如果您有其他方式,请根据实际情况修改。
串口部分在FreeModbus中是在 portserial.c文件中。主要有下面几个函数需要我们实现。下面是源码中关于串口需要我们完善的几个函数。
portserial.c的源码
② xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )函数
1 /*ucPORT: 可以用来选择是哪一个串口(这里默认串口1,没有使用这个参数) 2 ulBaudRate:波特率 3 ucDataBits:数据长度(没有用到) 4 eParity:校验模式 5 */ 6 BOOL 7 xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) 8 { 9 huart1.Instance = USART1; 10 huart1.Init.BaudRate = ulBaudRate; 11 huart1.Init.StopBits = UART_STOPBITS_1; 12 huart1.Init.Mode = UART_MODE_TX_RX; 13 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 14 huart1.Init.OverSampling = UART_OVERSAMPLING_16; 15 16 switch(eParity) 17 { 18 // 奇校验 19 case MB_PAR_ODD: 20 huart1.Init.Parity = UART_PARITY_ODD; 21 huart1.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits 22 break; 23 24 // 偶校验 25 case MB_PAR_EVEN: 26 huart1.Init.Parity = UART_PARITY_EVEN; 27 huart1.Init.WordLength = UART_WORDLENGTH_9B; // 带奇偶校验数据位为9bits 28 break; 29 30 // 无校验 31 default: 32 huart1.Init.Parity = UART_PARITY_NONE; 33 huart1.Init.WordLength = UART_WORDLENGTH_8B; // 无奇偶校验数据位为8bits 34 break; 35 } 36 return HAL_UART_Init(&huart1) == HAL_OK ? TRUE : FALSE; 37 }xMBPortSerialInit完善代码
注意:如果使用的是HAL库初始化串口的话,最后会调用HAL_UART_MspInit(huart);函数去初始化芯片的硬件,这个时候如果我们生成时使用的串口和初始化的时候不一致,我们需要在HAL_UART_MspInit()函数中加判断从而确保我们的串口是初始化成功的。此外HAL库的串口初始化后我们需要调用一下 HAL_UART_Receive_IT( ); 函数,原因就是因为HAL库中并初始化时并没有使能串口中断我们需要用这个接收函数间接的使能串口的中断。使用下面这个函数是一样的 __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
③vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )函数
1 /* ----------------------- Start implementation -----------------------------*/ 2 void 3 vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) 4 { 5 /* If xRXEnable enable serial receive interrupts. If xTxENable enable 6 * transmitter empty interrupts. 7 */ 8 if(xRxEnable == TRUE) 9 { 10 MONITOR_485_DE_DISENABLE; //使能485接收 11 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); 12 } 13 else 14 { 15 __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE); 16 } 17 18 if(xTxEnable == TRUE) 19 { 20 MONITOR_485_DE_ENABLE; //485发送 21 __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE); 22 } 23 else 24 { 25 __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE); 26 } 27 }vMBPortSerialEnable()串口使能函数完善
这里 MONITOR_485_DE_DISENABLE; 是个宏定义 #define MONITOR_485_DE_DISENABLE HAL_GPIO_WritePin(MONITOR_485_DE_GPIO_Port, MONITOR_485_DE_Pin, GPIO_PIN_RESET)
需要根据自己的硬件环境的不同进行定义。
④xMBPortSerialPutByte( CHAR ucByte ) , xMBPortSerialGetByte( CHAR * pucByte ),串口中断函数 void USART1_IRQHandler(void)
1 BOOL 2 xMBPortSerialPutByte( CHAR ucByte ) 3 { 4 /* Put a byte in the UARTs transmit buffer. This function is called 5 * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been 6 * called. */ 7 USART1->DR = ucByte; 8 return TRUE; 9 } 10 11 BOOL 12 xMBPortSerialGetByte( CHAR * pucByte ) 13 { 14 /* Return the byte in the UARTs receive buffer. This function is called 15 * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. 16 */ 17 *pucByte = (USART1->DR & (uint16_t)0x00FF); 18 return TRUE; 19 } 20 void USART1_IRQHandler(void) 21 { 22 if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) // 接收非空中断标记被置位 23 { 24 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); // 清除中断标记 25 prvvUARTRxISR(); // 通知modbus有数据到达 26 } 27 28 if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)) // 发送为空中断标记被置位 29 { 30 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE); // 清除中断标记 31 prvvUARTTxReadyISR(); // 通知modbus数据可以发松 32 } 33 }串口接收,发送处理,中断函数
接收和发送都是通过寄存器实现的,中断函数可以不放到这里,可以放到HAL库的放中断函数的位置,这样我们需要将 prvvUARTRxISR静态函数再封装一层,在其他.C文件内调用我们封装好的函数即可。向下面这样。
void freeModbusSlavePrvvUARTRxISR(void )
{
prvvUARTRxISR();
}
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) // 接收非空中断标记被置位
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); // 清除中断标记
freeModbusSlavePrvvUARTRxISR(); // 替换成我们再次封装好的函数即可
}
}
二、定时器部分
①在FreeModbus中定时器的作用是为了精准实现modbus中3.5字符的超时时间。这里我们可以使用任何一个定时器,源码中的时间基数是50us,然后在根据波特率算出溢出数,我们在移植的时候需要看一下自己的定时器的时钟频率一次实现50us的定时。这里不是很精准也没关系,我自己试的是差别不是很大也不影响数据的接收。在源码中主要是porttimer.c文件中。下面是需要完善的源码:
定时器部分需要完善的源码1 /* ----------------------- static functions ---------------------------------*/ 2 static void prvvTIMERExpiredISR( void ); 3 4 /* ----------------------- Start implementation -----------------------------*/ 5 BOOL 6 xMBPortTimersInit( USHORT usTim1Timerout50us ) 7 { 8 //定时器初始化 9 return FALSE; 10 } 11 12 13 inline void 14 vMBPortTimersEnable( ) 15 { 16 //使能定时器 17 /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ 18 } 19 20 inline void 21 vMBPortTimersDisable( ) 22 { 23 //取消使能 24 /* Disable any pending timers. */ 25 } 26 27 /* Create an ISR which is called whenever the timer has expired. This function 28 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that 29 * the timer has expired. 30 */ 31 static void prvvTIMERExpiredISR( void ) 32 { 33 //定时器中断调用的回调函数 34 ( void )pxMBPortCBTimerExpired( ); 35 }
②xMBPortTimersInit( USHORT usTim1Timerout50us )函数,这是定时器的初始化函数
1 BOOL 2 xMBPortTimersInit( USHORT usTim1Timerout50us ) 3 { 4 BOOL TimersInitState; 5 TimersInitState = MX_TIM2_Init(usTim1Timerout50us); 6 return TimersInitState; 7 } 8 /* TIM2 init function */ 9 uint8_t MX_TIM2_Init(uint16_t timeCount) 10 { 11 /* USER CODE BEGIN TIM2_Init 0 */ 12 /* USER CODE END TIM2_Init 0 */ 13 TIM_ClockConfigTypeDef sClockSourceConfig = {0}; 14 TIM_MasterConfigTypeDef sMasterConfig = {0}; 15 /* USER CODE BEGIN TIM2_Init 1 */ 16 /* USER CODE END TIM2_Init 1 */ 17 htim2.Instance = TIM2; 18 htim2.Init.Prescaler = 5999; 19 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; 20 htim2.Init.Period = timeCount-1; 21 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 22 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; 23 if (HAL_TIM_Base_Init(&htim2) != HAL_OK) 24 { 25 return 0; 26 } 27 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; 28 if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) 29 { 30 return 0; 31 } 32 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; 33 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; 34 if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) 35 { 36 return 0; 37 } 38 /* USER CODE BEGIN TIM2_Init 2 */ 39 //清理TIM开启时的中断标识 40 __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); 41 //使能TIM中断 42 HAL_TIM_Base_Start_IT(&htim2); 43 /* USER CODE END TIM2_Init 2 */ 44 return 1; 45 }定时器初始化
这里我使用的是STM32CubeMx生成的定时器初始化,稍微修改了一下,得到了一个返回值。
③定时器使能,失能,中断函数
定时器使能,失能,中断函数源码完善后这里比较和串口的一样,要是不想把定时器的中断函数放到这里,封装一下static void prvvTIMERExpiredISR( void )这个函数即可,我不确定prvvTIMERExpiredISR函数去掉静态修饰是否可行,所以又封装了一次。
三、串口、定时器初始化函数的调用
这两个的部分的初始化在哪一个地方调用的呢,他们是在eMBRTUInit()函数中被调用,当然这是RTU模式下的。
1 /* ----------------------- Start implementation -----------------------------*/ 2 eMBErrorCode 3 eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 4 { 5 eMBErrorCode eStatus = MB_ENOERR; 6 ULONG usTimerT35_50us; 7 8 ( void )ucSlaveAddress; 9 ENTER_CRITICAL_SECTION( ); 10 11 /* Modbus RTU uses 8 Databits. */ 12 if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE ) 13 { 14 eStatus = MB_EPORTERR; 15 } 16 else 17 { 18 /* If baudrate > 19200 then we should use the fixed timer values 19 * t35 = 1750us. Otherwise t35 must be 3.5 times the character time. 20 */ 21 if( ulBaudRate > 19200 ) 22 { 23 usTimerT35_50us = 35; /* 1800us. */ 24 } 25 else 26 { 27 /* The timer reload value for a character is given by: 28 * 29 * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 ) 30 * = 11 * Ticks_per_1s / Baudrate 31 * = 220000 / Baudrate 32 * The reload for t3.5 is 1.5 times this value and similary 33 * for t3.5. 34 */ 35 usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate ); 36 } 37 if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE ) 38 { 39 eStatus = MB_EPORTERR; 40 } 41 } 42 EXIT_CRITICAL_SECTION( ); 43 44 return eStatus; 45 }eMBRTUInit
从这个源码中可以看出它是根据波特率和晶振频率确定的定时器的溢出值。这是调用我们刚才完善串口和定时器初始化部分的代码,然后这不是我们最后使用函数,我们最后使用的是下面这个函数
1 /* ----------------------- Start implementation -----------------------------*/ 2 eMBErrorCode 3 eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 4 { 5 eMBErrorCode eStatus = MB_ENOERR; 6 7 /* check preconditions */ 8 if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) || 9 ( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) ) 10 { 11 eStatus = MB_EINVAL; 12 } 13 else 14 { 15 ucMBAddress = ucSlaveAddress; 16 switch ( eMode ) 17 { 18 #if MB_RTU_ENABLED > 0 19 case MB_RTU: 20 pvMBFrameStartCur = eMBRTUStart; 21 pvMBFrameStopCur = eMBRTUStop; 22 peMBFrameSendCur = eMBRTUSend; 23 peMBFrameReceiveCur = eMBRTUReceive; 24 pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; 25 pxMBFrameCBByteReceived = xMBRTUReceiveFSM; 26 pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; 27 pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; 28 eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity ); 29 break; 30 #endif 31 #if MB_ASCII_ENABLED > 0 32 case MB_ASCII: 33 pvMBFrameStartCur = eMBASCIIStart; 34 pvMBFrameStopCur = eMBASCIIStop; 35 peMBFrameSendCur = eMBASCIISend; 36 peMBFrameReceiveCur = eMBASCIIReceive; 37 pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; 38 pxMBFrameCBByteReceived = xMBASCIIReceiveFSM; 39 pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM; 40 pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired; 41 42 eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity ); 43 break; 44 #endif 45 default: 46 eStatus = MB_EINVAL; 47 } 48 49 if( eStatus == MB_ENOERR ) 50 { 51 if( !xMBPortEventInit( ) ) 52 { 53 /* port dependent event module initalization failed. */ 54 eStatus = MB_EPORTERR; 55 } 56 else 57 { 58 eMBCurrentMode = eMode; 59 eMBState = STATE_DISABLED; 60 } 61 } 62 } 63 return eStatus; 64 }eMBInit()
eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); // 初始化modbus为RTU方式,波特率115200
eMBEnable(); // 使能modbus协议栈
最后我们在主函数前像这样调用eMBInit()函数,就可以了实现FreeModbus函数的初始化了。
(3)应用层代码
①初始化和使能 FreeModbus从机的函数
1 eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); // 初始化modbus为RTU方式,波特率115200 2 eMBEnable(); // 使能modbus协议栈
这两个函数的功能就是初始化和使能FreeModbus,上述所有初始化都是由这个函数调用的,在初始化部分里面有对源码中使用的函数进行重定向,所以如果使用了实时操作系统,最好在调度器开启前使用这两个函数,否则可能会导致系统出现错误。下面的代码是对源码中的一些函数进行的重定向。
1 #if MB_RTU_ENABLED > 0 2 case MB_RTU: 3 pvMBFrameStartCur = eMBRTUStart; 4 pvMBFrameStopCur = eMBRTUStop; 5 peMBFrameSendCur = eMBRTUSend; 6 peMBFrameReceiveCur = eMBRTUReceive; 7 pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; 8 pxMBFrameCBByteReceived = xMBRTUReceiveFSM; 9 pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; 10 pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; 11 eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity ); 12 break; 13 #endif
②应用代码
下面是使用FreeModbus实现的从机的回应和填充发送的内容,根据命令的不同分为了四个部分。
1 #include "mb.h" 2 #include "mbport.h" 3 4 5 // 十路输入寄存器 6 #define REG_INPUT_SIZE 10 7 uint16_t REG_INPUT_BUF[REG_INPUT_SIZE]; 8 9 10 // 十路保持寄存器 11 #define REG_HOLD_SIZE 10 12 uint16_t REG_HOLD_BUF[REG_HOLD_SIZE]; 13 14 15 // 十路线圈 16 #define REG_COILS_SIZE 10 17 uint8_t REG_COILS_BUF[REG_COILS_SIZE]; 18 19 20 // 十路离散量 21 #define REG_DISC_SIZE 10 22 uint8_t REG_DISC_BUF[10]; 23 24 /// CMD4 25 eMBErrorCode 26 eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) 27 { 28 USHORT usRegIndex = usAddress - 1; 29 30 // 非法检测 31 if((usRegIndex + usNRegs) > REG_INPUT_SIZE) 32 { 33 return MB_ENOREG; 34 } 35 36 // 循环读取 37 while( usNRegs > 0 ) 38 { 39 *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 ); 40 *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF ); 41 usRegIndex++; 42 usNRegs--; 43 } 44 45 // 模拟输入寄存器被改变 46 for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++) 47 { 48 REG_INPUT_BUF[usRegIndex]++; 49 } 50 51 return MB_ENOERR; 52 } 53 54 /// CMD6、3、16 55 eMBErrorCode 56 eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) 57 { 58 USHORT usRegIndex = usAddress - 1; 59 60 // 非法检测 61 if((usRegIndex + usNRegs) > REG_HOLD_SIZE) 62 { 63 return MB_ENOREG; 64 } 65 66 // 写寄存器 67 if(eMode == MB_REG_WRITE) 68 { 69 while( usNRegs > 0 ) 70 { 71 REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1]; 72 pucRegBuffer += 2; 73 usRegIndex++; 74 usNRegs--; 75 } 76 } 77 78 // 读寄存器 79 else 80 { 81 while( usNRegs > 0 ) 82 { 83 *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 ); 84 *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF ); 85 usRegIndex++; 86 usNRegs--; 87 } 88 } 89 90 return MB_ENOERR; 91 } 92 93 /// CMD1、5、15 94 eMBErrorCode 95 eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) 96 { 97 USHORT usRegIndex = usAddress - 1; 98 USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1); 99 UCHAR ucStatus = 0; 100 UCHAR ucBits = 0; 101 UCHAR ucDisp = 0; 102 103 // 非法检测 104 if((usRegIndex + usNCoils) > REG_COILS_SIZE) 105 { 106 return MB_ENOREG; 107 } 108 109 // 写线圈 110 if(eMode == MB_REG_WRITE) 111 { 112 while(usCoilGroups--) 113 { 114 ucStatus = *pucRegBuffer++; 115 ucBits = 8; 116 117 while((usNCoils--) != 0 && (ucBits--) != 0) 118 { 119 REG_COILS_BUF[usRegIndex++] = ucStatus & 0X01; 120 ucStatus >>= 1; 121 } 122 } 123 } 124 125 // 读线圈 126 else 127 { 128 while(usCoilGroups--) 129 { 130 ucDisp = 0; 131 ucBits = 8; 132 133 while((usNCoils--) != 0 && (ucBits--) != 0) 134 { 135 ucStatus |= (REG_COILS_BUF[usRegIndex++] << (ucDisp++)); 136 } 137 138 *pucRegBuffer++ = ucStatus; 139 } 140 } 141 142 return MB_ENOERR; 143 } 144 145 146 /// CMD4 147 eMBErrorCode 148 eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) 149 { 150 USHORT usRegIndex = usAddress - 1; 151 USHORT usCoilGroups = ((usNDiscrete - 1) / 8 + 1); 152 UCHAR ucStatus = 0; 153 UCHAR ucBits = 0; 154 UCHAR ucDisp = 0; 155 156 // 非法检测 157 if((usRegIndex + usNDiscrete) > REG_DISC_SIZE) 158 { 159 return MB_ENOREG; 160 } 161 162 // 读离散输入 163 while(usCoilGroups--) 164 { 165 ucDisp = 0; 166 ucBits = 8; 167 168 while((usNDiscrete--) != 0 && (ucBits--) != 0) 169 { 170 if(REG_DISC_BUF[usRegIndex]) 171 { 172 ucStatus |= (1 << ucDisp); 173 } 174 175 ucDisp++; 176 } 177 178 *pucRegBuffer++ = ucStatus; 179 } 180 181 // 模拟改变 182 for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++) 183 { 184 REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex]; 185 } 186 187 return MB_ENOERR; 188 }应用代码
然后我们在一个任务函数中轮训调用 eMBPoll(); 函数从而改变状态完成整个协议。协议使用的是状态机实现的协议实现的过程,状态机的过程是可以使用系统的队列实现的,这样系统实时性会更加好,下篇讲解源码的实现过程和使用队列替代状态机。
1 void Monitor485Task(void *argument) 2 { 3 /* USER CODE BEGIN StartDefaultTask */ 4 for(;;) 5 { 6 eMBPoll(); // 轮巡查询 7 } 8 /* USER CODE END StartDefaultTask */ 9 }