1. 程式人生 > 其它 >FreeMobus移植在FreeRTOS上的移植(從機)

FreeMobus移植在FreeRTOS上的移植(從機)

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 }
應用程式碼