| /**************************************************************************** |
| * apps/modbus/mb.c |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * SPDX-FileCopyrightText: 2006 Christian Walter <wolti@sil.at> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "port.h" |
| |
| #include "modbus/mb.h" |
| #include "modbus/mbframe.h" |
| #include "modbus/mbproto.h" |
| #include "modbus/mbfunc.h" |
| |
| #include "modbus/mbport.h" |
| |
| #ifdef CONFIG_MB_RTU_ENABLED |
| # include "mbrtu.h" |
| #endif |
| |
| #ifdef CONFIG_MB_ASCII_ENABLED |
| # include "mbascii.h" |
| #endif |
| |
| #ifdef CONFIG_MB_TCP_ENABLED |
| # include "mbtcp.h" |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static uint8_t ucMBAddress; |
| static eMBMode eMBCurrentMode; |
| |
| static enum |
| { |
| STATE_ENABLED, |
| STATE_DISABLED, |
| STATE_NOT_INITIALIZED |
| } eMBState = STATE_NOT_INITIALIZED; |
| |
| /* Functions pointer which are initialized in eMBInit(). Depending on the |
| * mode (RTU or ASCII) the are set to the correct implementations. |
| */ |
| |
| static peMBFrameSend peMBFrameSendCur; |
| static pvMBFrameStart pvMBFrameStartCur; |
| static pvMBFrameStop pvMBFrameStopCur; |
| static peMBFrameReceive peMBFrameReceiveCur; |
| static pvMBFrameClose pvMBFrameCloseCur; |
| |
| /* Callback functions required by the porting layer. They are called when |
| * an external event has happened which includes a timeout or the reception |
| * or transmission of a character. |
| */ |
| |
| bool(*pxMBFrameCBByteReceived)(void); |
| bool(*pxMBFrameCBTransmitterEmpty)(void); |
| bool(*pxMBPortCBTimerExpired)(void); |
| |
| bool(*pxMBFrameCBReceiveFSMCur)(void); |
| bool(*pxMBFrameCBTransmitFSMCur)(void); |
| |
| /* An array of Modbus functions handlers which associates Modbus function |
| * codes with implementing functions. |
| */ |
| |
| static xMBFunctionHandler xFuncHandlers[CONFIG_MB_FUNC_HANDLERS_MAX] = |
| { |
| #ifdef CONFIG_MB_FUNC_OTHER_REP_SLAVEID_ENABLED |
| {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_READ_INPUT_ENABLED |
| {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_READ_HOLDING_ENABLED |
| {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED |
| {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_WRITE_HOLDING_ENABLED |
| {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_READWRITE_HOLDING_ENABLED |
| {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_READ_COILS_ENABLED |
| {MB_FUNC_READ_COILS, eMBFuncReadCoils}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_WRITE_COIL_ENABLED |
| {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED |
| {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils}, |
| #endif |
| #ifdef CONFIG_MB_FUNC_READ_DISCRETE_INPUTS_ENABLED |
| {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs}, |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| eMBErrorCode eMBInit(eMBMode eMode, uint8_t ucSlaveAddress, uint8_t ucPort, |
| speed_t ulBaudRate, eMBParity eParity) |
| { |
| eMBErrorCode eStatus = MB_ENOERR; |
| |
| /* check preconditions */ |
| |
| if ((ucSlaveAddress == MB_ADDRESS_BROADCAST) || |
| (ucSlaveAddress < MB_ADDRESS_MIN) || (ucSlaveAddress > MB_ADDRESS_MAX)) |
| { |
| eStatus = MB_EINVAL; |
| } |
| else |
| { |
| ucMBAddress = ucSlaveAddress; |
| |
| switch (eMode) |
| { |
| #ifdef CONFIG_MB_RTU_ENABLED |
| case MB_RTU: |
| pvMBFrameStartCur = eMBRTUStart; |
| pvMBFrameStopCur = eMBRTUStop; |
| peMBFrameSendCur = eMBRTUSend; |
| peMBFrameReceiveCur = eMBRTUReceive; |
| pvMBFrameCloseCur = vMBPortClose; |
| pxMBFrameCBByteReceived = xMBRTUReceiveFSM; |
| pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; |
| pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; |
| |
| eStatus = eMBRTUInit(ucMBAddress, ucPort, ulBaudRate, eParity); |
| break; |
| #endif |
| #ifdef CONFIG_MB_ASCII_ENABLED |
| case MB_ASCII: |
| pvMBFrameStartCur = eMBASCIIStart; |
| pvMBFrameStopCur = eMBASCIIStop; |
| peMBFrameSendCur = eMBASCIISend; |
| peMBFrameReceiveCur = eMBASCIIReceive; |
| pvMBFrameCloseCur = vMBPortClose; |
| pxMBFrameCBByteReceived = xMBASCIIReceiveFSM; |
| pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM; |
| pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired; |
| |
| eStatus = eMBASCIIInit(ucMBAddress, ucPort, ulBaudRate, eParity); |
| break; |
| #endif |
| default: |
| eStatus = MB_EINVAL; |
| } |
| |
| if (eStatus == MB_ENOERR) |
| { |
| if (!xMBPortEventInit()) |
| { |
| /* port dependent event module initialization failed. */ |
| |
| eStatus = MB_EPORTERR; |
| } |
| else |
| { |
| eMBCurrentMode = eMode; |
| eMBState = STATE_DISABLED; |
| } |
| } |
| } |
| |
| return eStatus; |
| } |
| |
| #ifdef CONFIG_MB_TCP_ENABLED |
| eMBErrorCode eMBTCPInit(uint16_t ucTCPPort) |
| { |
| eMBErrorCode eStatus = MB_ENOERR; |
| |
| if ((eStatus = eMBTCPDoInit(ucTCPPort)) != MB_ENOERR) |
| { |
| eMBState = STATE_DISABLED; |
| } |
| else if (!xMBPortEventInit()) |
| { |
| /* Port dependent event module initialization failed. */ |
| |
| eStatus = MB_EPORTERR; |
| } |
| else |
| { |
| pvMBFrameStartCur = eMBTCPStart; |
| pvMBFrameStopCur = eMBTCPStop; |
| peMBFrameReceiveCur = eMBTCPReceive; |
| peMBFrameSendCur = eMBTCPSend; |
| #ifdef CONFIG_MB_HAVE_CLOSE |
| pvMBFrameCloseCur = vMBTCPPortClose; |
| #else |
| pvMBFrameCloseCur = NULL; |
| #endif |
| ucMBAddress = MB_TCP_PSEUDO_ADDRESS; |
| eMBCurrentMode = MB_TCP; |
| eMBState = STATE_DISABLED; |
| } |
| |
| return eStatus; |
| } |
| #endif |
| |
| eMBErrorCode eMBRegisterCB(uint8_t ucFunctionCode, pxMBFunctionHandler pxHandler) |
| { |
| eMBErrorCode eStatus; |
| int i; |
| |
| if ((0 < ucFunctionCode) && (ucFunctionCode <= 127)) |
| { |
| ENTER_CRITICAL_SECTION(); |
| if (pxHandler != NULL) |
| { |
| for (i = 0; i < CONFIG_MB_FUNC_HANDLERS_MAX; i++) |
| { |
| if ((xFuncHandlers[i].pxHandler == NULL) || |
| (xFuncHandlers[i].pxHandler == pxHandler)) |
| { |
| xFuncHandlers[i].ucFunctionCode = ucFunctionCode; |
| xFuncHandlers[i].pxHandler = pxHandler; |
| break; |
| } |
| } |
| eStatus = (i != CONFIG_MB_FUNC_HANDLERS_MAX) ? MB_ENOERR : MB_ENORES; |
| } |
| else |
| { |
| for (i = 0; i < CONFIG_MB_FUNC_HANDLERS_MAX; i++) |
| { |
| if (xFuncHandlers[i].ucFunctionCode == ucFunctionCode) |
| { |
| xFuncHandlers[i].ucFunctionCode = 0; |
| xFuncHandlers[i].pxHandler = NULL; |
| break; |
| } |
| } |
| |
| /* Remove can't fail. */ |
| |
| eStatus = MB_ENOERR; |
| } |
| |
| EXIT_CRITICAL_SECTION(); |
| } |
| else |
| { |
| eStatus = MB_EINVAL; |
| } |
| |
| return eStatus; |
| } |
| |
| eMBErrorCode eMBClose(void) |
| { |
| eMBErrorCode eStatus = MB_ENOERR; |
| |
| if (eMBState == STATE_DISABLED) |
| { |
| if (pvMBFrameCloseCur != NULL) |
| { |
| pvMBFrameCloseCur(); |
| } |
| } |
| else |
| { |
| eStatus = MB_EILLSTATE; |
| } |
| |
| return eStatus; |
| } |
| |
| eMBErrorCode eMBEnable(void) |
| { |
| eMBErrorCode eStatus = MB_ENOERR; |
| |
| if (eMBState == STATE_DISABLED) |
| { |
| /* Activate the protocol stack. */ |
| |
| pvMBFrameStartCur(); |
| eMBState = STATE_ENABLED; |
| } |
| else |
| { |
| eStatus = MB_EILLSTATE; |
| } |
| |
| return eStatus; |
| } |
| |
| eMBErrorCode eMBDisable(void) |
| { |
| eMBErrorCode eStatus; |
| |
| if (eMBState == STATE_ENABLED) |
| { |
| pvMBFrameStopCur(); |
| eMBState = STATE_DISABLED; |
| eStatus = MB_ENOERR; |
| } |
| else if (eMBState == STATE_DISABLED) |
| { |
| eStatus = MB_ENOERR; |
| } |
| else |
| { |
| eStatus = MB_EILLSTATE; |
| } |
| |
| return eStatus; |
| } |
| |
| eMBErrorCode eMBPoll(void) |
| { |
| static uint8_t *ucMBFrame; |
| static uint8_t ucRcvAddress; |
| static uint8_t ucFunctionCode; |
| static uint16_t usLength; |
| static eMBException eException; |
| |
| int i; |
| eMBErrorCode eStatus = MB_ENOERR; |
| eMBEventType eEvent; |
| |
| /* Check if the protocol stack is ready. */ |
| |
| if (eMBState != STATE_ENABLED) |
| { |
| return MB_EILLSTATE; |
| } |
| |
| /* Check if there is a event available. If not return control to caller. |
| * Otherwise we will handle the event. |
| */ |
| |
| if (xMBPortEventGet(&eEvent) == true) |
| { |
| switch (eEvent) |
| { |
| case EV_READY: |
| break; |
| |
| case EV_FRAME_RECEIVED: |
| eStatus = peMBFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength); |
| if (eStatus == MB_ENOERR) |
| { |
| /* Check if the frame is for us. If not ignore the frame. */ |
| |
| if ((ucRcvAddress == ucMBAddress) || (ucRcvAddress == MB_ADDRESS_BROADCAST)) |
| { |
| xMBPortEventPost(EV_EXECUTE); |
| } |
| } |
| break; |
| |
| case EV_EXECUTE: |
| ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; |
| eException = MB_EX_ILLEGAL_FUNCTION; |
| for( i = 0; i < CONFIG_MB_FUNC_HANDLERS_MAX; i++) |
| { |
| /* No more function handlers registered. Abort. */ |
| |
| if (xFuncHandlers[i].ucFunctionCode == 0) |
| { |
| break; |
| } |
| else if (xFuncHandlers[i].ucFunctionCode == ucFunctionCode) |
| { |
| eException = xFuncHandlers[i].pxHandler(ucMBFrame, &usLength); |
| break; |
| } |
| } |
| |
| /* If the request was not sent to the broadcast address we |
| * return a reply. |
| */ |
| |
| if (ucRcvAddress != MB_ADDRESS_BROADCAST) |
| { |
| if (eException != MB_EX_NONE) |
| { |
| /* An exception occurred. Build an error frame. */ |
| |
| usLength = 0; |
| ucMBFrame[usLength++] = (uint8_t)(ucFunctionCode | MB_FUNC_ERROR); |
| ucMBFrame[usLength++] = eException; |
| } |
| |
| #ifdef CONFIG_MB_ASCII_ENABLED |
| if ((eMBCurrentMode == MB_ASCII) && CONFIG_MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS) |
| { |
| vMBPortTimersDelay(CONFIG_MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS); |
| } |
| #endif |
| peMBFrameSendCur(ucMBAddress, ucMBFrame, usLength); |
| } |
| break; |
| |
| case EV_FRAME_SENT: |
| break; |
| } |
| } |
| |
| return MB_ENOERR; |
| } |