Difference between revisions of "Modbus notes"
m |
|||
(82 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
+ | == OVERVIEW == | ||
+ | Notes on Modbus protocol, and references to one or more Modbus protocol libraries, written in C and usable in Unix and Linux envorinments. | ||
+ | |||
+ | <!-- 2024 ENTRY BEGIN --> | ||
+ | |||
+ | The following web site does one of the better jobs of describing Modbus protocol clearly, and with multiple examples. | ||
+ | |||
+ | * https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/ | ||
+ | |||
+ | This IPC2U site combined with code snippets from a Github gist authored and shared by J Grossholtz is sufficient to write a C program which can make a PC act as a Modbus enabled device. While compiling an first test program using this library, encountered need to include library path to gcc. | ||
+ | |||
+ | How to specify preference of library path to C compiler and linker: | ||
+ | |||
+ | * https://stackoverflow.com/questions/2726993/how-to-specify-preference-of-library-path | ||
+ | |||
+ | |||
+ | |||
+ | Stackoverflow post showing libmodbus based sender and listener code snippets: | ||
+ | |||
+ | * https://stackoverflow.com/questions/29602698/create-a-simple-client-server-using-modbus-in-c | ||
+ | |||
+ | J. Grossholtz Github gist with modbus server and client sample C programs: | ||
+ | |||
+ | * https://gist.github.com/JGrossholtz/d1d02a87d535d7772139 | ||
+ | |||
+ | On systems supporting standard C and its libraries, `libmodbus` may be used to create programs which act as Modbus controllers (sending commands) and or peripherals (responding to commamds). | ||
+ | |||
+ | * https://stackoverflow.com/questions/2726993/how-to-specify-preference-of-library-path | ||
+ | |||
+ | <!-- 2024 ENTRY END --> | ||
+ | |||
+ | == OVERVIEW CERCA 2019 == | ||
+ | |||
+ | This wiki article contains some notes and references on FreeModbus, an opensource C library to support a Modbus protocol stack in stand-alone applications as well as operating system and scheduler based systems. This article also contains a number of links to more general C language references, which discuss function pointers in C, and the type defining of function pointers. | ||
+ | |||
+ | A few links: | ||
* https://en.wikipedia.org/wiki/Modbus | * https://en.wikipedia.org/wiki/Modbus | ||
Line 6: | Line 42: | ||
* https://libmodbus.org/ . . . Ubuntu package is 'libmodbus5' and 'libmodbus-dev' | * https://libmodbus.org/ . . . Ubuntu package is 'libmodbus5' and 'libmodbus-dev' | ||
+ | |||
+ | * http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf | ||
+ | |||
+ | Table of contents for local Modbus notes: | ||
+ | |||
+ | __TOC__ | ||
+ | |||
+ | <!-- comentario --> | ||
+ | |||
+ | == [[#top|^]] Modbus and Zephyr RTOS == | ||
+ | |||
+ | "zephyr/samples/subsys/modbus/rtu_client/app.overlay" 15L, 269C | ||
+ | |||
+ | UART hardware driver types in Zephyr RTOS project: | ||
+ | "doc/hardware/peripherals/uart.rst" 77L, 1979C | ||
+ | |||
+ | <!-- comentario --> | ||
+ | |||
+ | == [[#top|^]] Freemodbus Notes from 2019 == | ||
+ | |||
+ | Note: FreeModbus project uses a variable and routine naming scheme where many names begin with one or two lower-case characters. From context in the file ./modbus/mb.c, it looks like 'uc' may stand for 'unsigned char' data type. The starting lower case 'e' stands for 'enumeration': | ||
+ | |||
+ | 62 static UCHAR ucMBAddress; | ||
+ | 63 static eMBMode eMBCurrentMode; | ||
+ | |||
- 2019-11-08 FRI - | - 2019-11-08 FRI - | ||
− | When working with FreeModbus, be sure to pay attention to the versions of protocol available, and likely, we only want to enable one of these. See Modbus/include/mbconfig.h. | + | When working with FreeModbus, be sure to pay attention to the versions of protocol available, and likely, we only want to enable one of these. See Modbus/include/mbconfig.h. See also Modbus/include/mb.h for instructions on how to initialize and begin to use an instance of FreeModbus stack in given program. Also mention that eMBPoll() routine may be set up as a task in an RTOS, to realize the periodic polling which is part of the stack's runtime manifestation . . . |
+ | |||
+ | Looks like also this function to register callbacks for end-user-device-specific Modbus registers will be important: | ||
+ | |||
+ | eMBErrorCode eMBRegisterCB ( UCHAR ucFunctionCode, | ||
+ | pxMBFunctionHandler pxHandler | ||
+ | ) | ||
+ | |||
+ | This function signature part of documentation found at https://www.embedded-solutions.at/en/freemodbus/api-documentation/, which gives Doxygen generated API help in an embedded pane in the browser window on which it is visited. | ||
+ | |||
+ | |||
+ | 2019-11-12 Tuesday - trying to figure out how pxMBFunctionHandler is defined . . . from http://www.teamfdi.com/uez/docs/mbproto_8h.html: | ||
+ | |||
+ | <pre> | ||
+ | Typedefs | ||
+ | typedef eMBException(* pxMBFunctionHandler )(UCHAR *pucFrame, USHORT *pusLength) | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | ...from this typedef looks like Modbus routines implemented by the end user/end designer must have parameter lists which match "unsigned char pointer, unsigned integer pointer". | ||
+ | |||
+ | |||
+ | 2019-11-13 | ||
+ | Further search for FreeModbus callback registering code" | ||
+ | |||
+ | <pre> | ||
+ | downloads/freemodbus/freemodbus-v1.5.0$ grep -nr pxMBFunctionHandler ./* | ||
+ | ./modbus/mb.c:226:eMBRegisterCB( UCHAR ucFunctionCode, pxMBFunctionHandler pxHandler ) | ||
+ | ./modbus/include/mbproto.h:72:typedef eMBException( *pxMBFunctionHandler ) ( UCHAR * pucFrame, USHORT * pusLength ); | ||
+ | ./modbus/include/mbproto.h:77: pxMBFunctionHandler pxHandler; | ||
+ | ./modbus/include/mb.h:266: pxMBFunctionHandler pxHandler ) | ||
+ | </pre> | ||
+ | |||
+ | And the entire section of typedefs from mbproto.h: | ||
+ | |||
+ | <pre> | ||
+ | /* ----------------------- Type definitions ---------------------------------*/ | ||
+ | typedef enum | ||
+ | { | ||
+ | MB_EX_NONE = 0x00, | ||
+ | MB_EX_ILLEGAL_FUNCTION = 0x01, | ||
+ | MB_EX_ILLEGAL_DATA_ADDRESS = 0x02, | ||
+ | MB_EX_ILLEGAL_DATA_VALUE = 0x03, | ||
+ | MB_EX_SLAVE_DEVICE_FAILURE = 0x04, | ||
+ | MB_EX_ACKNOWLEDGE = 0x05, | ||
+ | MB_EX_SLAVE_BUSY = 0x06, | ||
+ | MB_EX_MEMORY_PARITY_ERROR = 0x08, | ||
+ | MB_EX_GATEWAY_PATH_FAILED = 0x0A, | ||
+ | MB_EX_GATEWAY_TGT_FAILED = 0x0B | ||
+ | } eMBException; | ||
+ | |||
+ | typedef eMBException( *pxMBFunctionHandler ) ( UCHAR * pucFrame, USHORT * pusLength ); | ||
+ | |||
+ | typedef struct | ||
+ | { | ||
+ | UCHAR ucFunctionCode; | ||
+ | pxMBFunctionHandler pxHandler; | ||
+ | } xMBFunctionHandler; | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] floating point byte order == | ||
+ | |||
+ | <ul> | ||
+ | * https://store.chipkin.com/articles/how-real-floating-point-and-32-bit-data-is-encoded-in-modbus-rtu-messages | ||
+ | </ul> | ||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Obscurely defined FreeModbus variables == | ||
+ | |||
+ | |||
+ | Where is the following <code>demo/LPC214X/port/port.c</code> variable defined? Does not appear defined in any file of FreeModbus 1.5.0 download. Note however that this may be an LPC microcontroller register name: | ||
+ | |||
+ | VICIntEnable | ||
+ | |||
+ | See http://www.keil.com/dd/vtr/3880/9794.htm for a brief mention of this term. - TMH | ||
+ | |||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Modbus frames and frame handling == | ||
+ | |||
+ | This search result may not be directly useful to getting a first modbus routine running and testable: | ||
+ | |||
+ | <pre> | ||
+ | ./modbus/include/mbport.h:109:extern BOOL( *pxMBFrameCBByteReceived ) ( void ); | ||
+ | Binary file ./modbus/include/.mbport.h.swp matches | ||
+ | ./modbus/mb.c:85:BOOL( *pxMBFrameCBByteReceived ) ( void ); | ||
+ | ./modbus/mb.c:153: pxMBFrameCBByteReceived = xMBRTUReceiveFSM; | ||
+ | ./modbus/mb.c:167: pxMBFrameCBByteReceived = xMBASCIIReceiveFSM; | ||
+ | </pre> | ||
+ | |||
+ | We notice, however, that in <code>[project]/Modbus/mb.c</code> in routine eMBPoll() there is a switch statement which selects code per events of a state machine. The enumerated event <code>eEvent</code> can take on the value 'EV_FRAME_RECEIVED'. When this occurs, this part of the FreeModbus library calls a function pointer, which is set to one of two functions based on Modbus protocol version RTU or ASCII. The function pointer is named <code>peMBFrameReceiveCur</code> and by assigned in routine <code>eMBPoll()</code> points to <code>eMBRTUReceive</code>. | ||
+ | |||
+ | Search for the Modbus RTU receive frame function: | ||
+ | |||
+ | <pre> | ||
+ | $ grep -nr eMBRTUReceive ./* | ||
+ | ./mb.c:167: peMBFrameReceiveCur = eMBRTUReceive; | ||
+ | ./rtu/mbrtu.c:153:eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) | ||
+ | ./rtu/mbrtu.h:40:eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ); | ||
+ | </pre> | ||
+ | |||
+ | This function eMBRTUReceive is defined as follows: | ||
+ | |||
+ | <pre> | ||
+ | eMBErrorCode | ||
+ | eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) | ||
+ | { | ||
+ | BOOL xFrameReceived = FALSE; | ||
+ | eMBErrorCode eStatus = MB_ENOERR; | ||
+ | |||
+ | ENTER_CRITICAL_SECTION( ); | ||
+ | assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); | ||
+ | |||
+ | /* Length and CRC check */ | ||
+ | if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) | ||
+ | && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) | ||
+ | { | ||
+ | /* Save the address field. All frames are passed to the upper layed | ||
+ | * and the decision if a frame is used is done there. | ||
+ | */ | ||
+ | *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; | ||
+ | |||
+ | /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus | ||
+ | * size of address field and CRC checksum. | ||
+ | */ | ||
+ | *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); | ||
+ | |||
+ | /* Return the start of the Modbus PDU to the caller. */ | ||
+ | *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; | ||
+ | xFrameReceived = TRUE; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | eStatus = MB_EIO; | ||
+ | } | ||
+ | |||
+ | EXIT_CRITICAL_SECTION( ); | ||
+ | return eStatus; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | So there is also an eMBRTUInit() function, | ||
+ | |||
+ | eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) | ||
+ | |||
+ | which itself calls a function named <code> xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity )</code>, but is this actually executing when we start our firmware running? | ||
+ | |||
+ | === [[#top|^]] edit point === | ||
+ | |||
+ | QUESTION: is this actually executing when we start our firmware running? | ||
+ | |||
+ | ANSWER: according to IAR Workbench yes. | ||
+ | |||
+ | QUESTION: what causes the event 'EV_FRAME_RECEIVED'? | ||
+ | |||
+ | ANSWER: From the following excerpt of pattern matching results, it looks like there is just one place this event gets posted, and this occurs by means of a line of code in <code>mbrtu.c</code>: | ||
+ | |||
+ | <pre> | ||
+ | Binary file ./Modbus/.mb.c.swp matches | ||
+ | ./Modbus/ascii/mbascii.c:294: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); | ||
+ | ./Modbus/include/mbport.h:42: EV_FRAME_RECEIVED, /*!< Frame received. */ | ||
+ | ./Modbus/mb.c:411: case EV_FRAME_RECEIVED: | ||
+ | Binary file ./Modbus/rtu/.mbrtu.c.swp matches | ||
+ | ./Modbus/rtu/mbrtu.c:340: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); | ||
+ | </pre> | ||
+ | |||
+ | The routine where this happens is: | ||
+ | |||
+ | <pre> | ||
+ | BOOL | ||
+ | xMBRTUTimerT35Expired( void ) | ||
+ | { | ||
+ | BOOL xNeedPoll = FALSE; | ||
+ | |||
+ | switch ( eRcvState ) | ||
+ | { | ||
+ | /* Timer t35 expired. Startup phase is finished. */ | ||
+ | case STATE_RX_INIT: | ||
+ | xNeedPoll = xMBPortEventPost( EV_READY ); | ||
+ | break; | ||
+ | |||
+ | /* A frame was received and t35 expired. Notify the listener that | ||
+ | * a new frame was received. */ | ||
+ | case STATE_RX_RCV: | ||
+ | xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); | ||
+ | break; | ||
+ | |||
+ | /* An error occured while receiving the frame. */ | ||
+ | case STATE_RX_ERROR: | ||
+ | break; | ||
+ | |||
+ | /* Function called in an illegal state. */ | ||
+ | default: | ||
+ | assert( ( eRcvState == STATE_RX_INIT ) || | ||
+ | ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); | ||
+ | } | ||
+ | |||
+ | vMBPortTimersDisable( ); | ||
+ | eRcvState = STATE_RX_IDLE; | ||
+ | |||
+ | return xNeedPoll; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | === [[#top|^]] edit point - routine xMBRTUTimerT35Expired called? === | ||
+ | |||
+ | QUESTION 1 OF 3: Is this timer routine ever running? | ||
+ | |||
+ | ANSWER 1 OF 3: not running per IAR breakpoint at first switch statement! | ||
+ | |||
+ | QUESTION 2 OF 3: What must we implement or at least uncomment in <code>mbportserial.c</code> routine: | ||
+ | <pre> | ||
+ | BOOL | ||
+ | xMBPortSerialGetByte( CHAR * pucByte ) | ||
+ | { | ||
+ | #if 0 | ||
+ | while( !( U1LSR & 0x01 ) ) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | /* Receive Byte */ | ||
+ | *pucByte = U1RBR; | ||
+ | #endif | ||
+ | return TRUE; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | QUESTION 3 OF 3: where among FreeModbus demos appear <code>xMBPortSerialGetByte</code> and <code>xMBRTUTimerT35Expired</code>? | ||
+ | |||
+ | ANSWER 3 OF 3: <code>xMBRTUTimerT35Expired</code> appears in no demos. <code>xMBPortSerialGetByte</code> appears in all or nearly all the demos. We note also at this time that the register name U1RBR appears to be LPC21xx specific, as this does not appear in the LPC11U6x datasheet. Looking now to the serial based Command Line Interface (CLI) code, what may we find for reading single bytes from a UART input buffer there? . . . | ||
+ | |||
+ | QUESTION: what is the "xMBPortSerialGetByte" routine in L S command line interface? | ||
+ | |||
+ | ANSWER: | ||
+ | |||
+ | To find the "get byte" equivalent in CLI code, we follow that uart.c routine ___ calls ./lpc_chip/chip_11u6x/uart_0_11u6x.c routine <code>Chip_UART0_ReadRB()</code>, defined: | ||
+ | |||
+ | <pre> | ||
+ | 206 /* Copy data from a receive ring buffer */ | ||
+ | 207 int Chip_UART0_ReadRB(LPC_USART0_T *pUART, RINGBUFF_T *pRB, void *data, int bytes) | ||
+ | 208 { | ||
+ | 209 (void) pUART; | ||
+ | 210 | ||
+ | 211 return RingBuffer_PopMult(pRB, (uint8_t *) data, bytes); | ||
+ | 212 } | ||
+ | </pre> | ||
+ | |||
+ | The routine RingBuffer_PopMult() does not reveal any details about a receive byte register or buffer name for LPC11U6X UART0. But returning to an earlier question about how to link or associate an LPC timer event with an interrupt service routine, we note that in the older, custom modbus.c there is a routine named SilenceTimerCallback(), which may be used for recognizing the 3.5 character width timing delays between Modbus PDUs or data packets. And in uart.c, there is a reference to this function on line 353: | ||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | === [[#top|^]] edit point - init_uart3 routine calls xTimerCreate === | ||
+ | |||
+ | <pre> | ||
+ | void init_uart3(void) | ||
+ | { | ||
+ | /* Shut down for now */ | ||
+ | NVIC_DisableIRQ(USART2_3_IRQn); | ||
+ | |||
+ | /* Configure pins */ | ||
+ | Chip_IOCON_PinMuxSet(LPC_IOCON, PORT(UART3RX), PIN(UART3RX), IOCON_FUNC1 | IOCON_DIGMODE_EN); | ||
+ | Chip_IOCON_PinMuxSet(LPC_IOCON, PORT(UART3TX), PIN(UART3TX), IOCON_FUNC1 | IOCON_DIGMODE_EN); | ||
+ | |||
+ | /* Enable UART clock */ | ||
+ | LPC_SYSCTL->SYSAHBCLKCTRL |= UART3_CLK_EN; | ||
+ | LPC_SYSCTL->FRGCLKDIV = 0x1; /* divided by 1 */ | ||
+ | |||
+ | LPC_SYSCTL->PRESETCTRL |= UART3_CLR_RST; /* clear reset on UART3 */ | ||
+ | |||
+ | LPC_USART3->BRG = (CLOCK_FREQUENCY/(16 * MODBUS_RATE)) - 1; /* set baud rate */ | ||
+ | |||
+ | /* Enable UART | 8N1 parity */ | ||
+ | LPC_USART3->CFG |= UARTN_CFG_ENABLE | UARTN_CFG_DATALEN_8 | UARTN_CFG_PARITY_NONE | UARTN_CFG_STOPLEN_1; | ||
+ | |||
+ | /* Enable UART interrupt bits */ | ||
+ | LPC_USART3->INTENSET = INTENSET_RXRDYEN; // | INTENSET_TXRDYEN | INTENSET_TXIDLEEN; | ||
+ | |||
+ | /* Enable the UART Interrupt */ | ||
+ | NVIC_EnableIRQ(USART2_3_IRQn); | ||
+ | |||
+ | // 2019-01-21 - Ted removing at pre-compile step some legacy Modbus code: | ||
+ | #ifndef USING_FREEMODBUS_REL_1_6_0_OR_SIMILAR | ||
+ | //initialize the ring buffers | ||
+ | RingBuffer_Init(&Modbus_TX_Buffer, (void*)&modbus_tx_buff[0], 1, MODBUS_BUFSIZE_RX); | ||
+ | RingBuffer_Init(&Modbus_RX_Buffer, (void*)&modbus_rx_buff[0], 1, MODBUS_BUFSIZE_TX); | ||
+ | |||
+ | //initialize silence timer | ||
+ | if(ModbusSilenceTimer == 0) | ||
+ | { | ||
+ | ModbusSilenceTimer = xTimerCreate("ModbusSilenceTimer", | ||
+ | 10*3*10*1000/MODBUS_RATE, pdFALSE, 0, &SilenceTimerCallback); | ||
+ | } | ||
+ | #endif | ||
+ | |||
+ | return; | ||
+ | } | ||
+ | |||
+ | </pre> | ||
+ | |||
<!-- comment --> | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Modbus register input callback routine == | ||
+ | |||
+ | Here is a search history, some searches run in the STR71X demo. These variables and routines look more promising in terms of telling us how to implement a routine of our own which properly ties in with FreeModbus library code. What these variables and routines lead us to examine, is how the code developers, us, using FreeModbus can return data from a device to a meaningful place in memory, which FreeModbus then reads and send out along the physical link layer to a given Modbus master. | ||
+ | |||
+ | From the following searches, | ||
+ | |||
+ | <pre> | ||
+ | 1004 grep -nr usRegInputBuf ./* | ||
+ | 1005 grep -n pucRegBuffer ./* | ||
+ | 1006 # eMBRegInputCB, third token for which to search | ||
+ | </pre> | ||
+ | |||
+ | We quickly come to the routine named eMBRegInputCB(). It turns out this routine is implemented in various FreeModbus demos, which is to say it is not a FreeModbus library routine. But part of FreeModbus library source calls or needs to call a routine by this name. So it looks like as users of FreeModbus, we are bound to define a routine by this name and with its enumerated return type and parameter list as defined in <code>freemodbus-v1.5.0/modbus/include/mb.h</code>. | ||
+ | |||
+ | Variable <code>usRegInputBuf</code> is in source file simple2.c a static array of bytes, which hold values that a Modbus function returns, | ||
+ | |||
+ | <pre> | ||
+ | <./simple2.c:42:static unsigned short usRegInputBuf[REG_INPUT_NREGS]; | ||
+ | ./simple2.c:76: usRegInputBuf[0]++; | ||
+ | ./simple2.c:79: usRegInputBuf[1] = ( unsigned portSHORT )( xLastWakeTime >> 16UL ); | ||
+ | ./simple2.c:80: usRegInputBuf[2] = ( unsigned portSHORT )( xLastWakeTime & 0xFFFFUL ); | ||
+ | ./simple2.c:82: usRegInputBuf[3] = 33; | ||
+ | ./simple2.c:99: ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); | ||
+ | ./simple2.c:101: ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); | ||
+ | </pre> | ||
+ | |||
+ | We look then at <code>pucRegBuffer</code> because of the following assignments in a callback routine of this demo: | ||
+ | |||
+ | <pre> | ||
+ | 86 eMBErrorCode | ||
+ | 87 eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | 88 { | ||
+ | 89 eMBErrorCode eStatus = MB_ENOERR; | ||
+ | 90 int iRegIndex; | ||
+ | 91 | ||
+ | 92 if( ( usAddress >= REG_INPUT_START ) | ||
+ | 93 && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) | ||
+ | 94 { | ||
+ | 95 iRegIndex = ( int )( usAddress - usRegInputStart ); | ||
+ | 96 while( usNRegs > 0 ) | ||
+ | 97 { | ||
+ | 98 *pucRegBuffer++ = | ||
+ | 99 ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); | ||
+ | 100 *pucRegBuffer++ = | ||
+ | 101 ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); | ||
+ | 102 iRegIndex++; | ||
+ | 103 usNRegs--; | ||
+ | 104 } | ||
+ | 105 } | ||
+ | 106 else | ||
+ | 107 { | ||
+ | 108 eStatus = MB_ENOREG; | ||
+ | 109 } | ||
+ | 110 | ||
+ | 111 return eStatus; | ||
+ | 112 } | ||
+ | </pre> | ||
+ | |||
+ | Now the question is, which code calls the routine <code>eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )</code>? This routine is not called by any code in this specific demo: | ||
+ | |||
+ | <pre> | ||
+ | $ grep -nr -A 1 eMBRegInputCB ./* | ||
+ | ./excoils.c:130:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./excoils.c-131-{ | ||
+ | -- | ||
+ | ./exdisc.c:118:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./exdisc.c-119-{ | ||
+ | -- | ||
+ | ./exholding.c:86:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./exholding.c-87-{ | ||
+ | -- | ||
+ | ./simple2.c:87:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./simple2.c-88-{ | ||
+ | -- | ||
+ | ./simple.c:94:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./simple.c-95-{ | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | Looking now a level or two up and at the FreeModbus library sources . . . there is a longer function in file <code>./modbus/functions/mbfuncinput.c </code> but worth noting here, as it parses the modbus PDU to figure out what type of PDU read versus write it is, and how many bytes are expected to follow or are being requested. Worth noting the function definition in full here: | ||
+ | |||
+ | <pre> | ||
+ | /* ----------------------- Start implementation -----------------------------*/ | ||
+ | #if MB_FUNC_READ_INPUT_ENABLED > 0 | ||
+ | |||
+ | eMBException | ||
+ | eMBFuncReadInputRegister( UCHAR * pucFrame, USHORT * usLen ) | ||
+ | { | ||
+ | USHORT usRegAddress; | ||
+ | USHORT usRegCount; | ||
+ | UCHAR *pucFrameCur; | ||
+ | |||
+ | eMBException eStatus = MB_EX_NONE; | ||
+ | eMBErrorCode eRegStatus; | ||
+ | |||
+ | if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) ) | ||
+ | { | ||
+ | usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); | ||
+ | usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); | ||
+ | usRegAddress++; | ||
+ | |||
+ | usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 ); | ||
+ | usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] ); | ||
+ | |||
+ | /* Check if the number of registers to read is valid. If not | ||
+ | * return Modbus illegal data value exception. | ||
+ | */ | ||
+ | if( ( usRegCount >= 1 ) | ||
+ | && ( usRegCount < MB_PDU_FUNC_READ_REGCNT_MAX ) ) | ||
+ | { | ||
+ | /* Set the current PDU data pointer to the beginning. */ | ||
+ | pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; | ||
+ | *usLen = MB_PDU_FUNC_OFF; | ||
+ | |||
+ | /* First byte contains the function code. */ | ||
+ | *pucFrameCur++ = MB_FUNC_READ_INPUT_REGISTER; | ||
+ | *usLen += 1; | ||
+ | |||
+ | /* Second byte in the response contain the number of bytes. */ | ||
+ | *pucFrameCur++ = ( UCHAR )( usRegCount * 2 ); | ||
+ | *usLen += 1; | ||
+ | |||
+ | eRegStatus = | ||
+ | eMBRegInputCB( pucFrameCur, usRegAddress, usRegCount ); | ||
+ | |||
+ | /* If an error occured convert it into a Modbus exception. */ | ||
+ | if( eRegStatus != MB_ENOERR ) | ||
+ | { | ||
+ | eStatus = prveMBError2Exception( eRegStatus ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | *usLen += usRegCount * 2; | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | eStatus = MB_EX_ILLEGAL_DATA_VALUE; | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | /* Can't be a valid read input register request because the length | ||
+ | * is incorrect. */ | ||
+ | eStatus = MB_EX_ILLEGAL_DATA_VALUE; | ||
+ | } | ||
+ | return eStatus; | ||
+ | } | ||
+ | |||
+ | #endif | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | |||
+ | <pre> | ||
+ | user@~/projects/freemodbus-v1.5.0$ grep -nr eMBRegInputCB ./* | ||
+ | |||
+ | ./demo/WIN32/demo.cpp:221:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71XGCC/demo.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/ATSAM3S_FREERTOS/demo.c:168:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/BARE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71X/exdisc.c:118:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71X/excoils.c:130:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71X/simple2.c:87:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | Binary file ./demo/STR71X/.simple2.c.swp matches | ||
+ | ./demo/STR71X/exholding.c:86:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71X/simple.c:94:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/LPC214X/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/LINUX/demo.c:264:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/LINUXTCP/demo.c:219:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/MSP430/demo.c:81:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/HCS08/demo.c:149:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/MCF5235TCP/demo.c:129:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/WIN32TCP/demo.cpp:214:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/AVR/demo.c:63:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/AVR/excoils.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/MCF5235CW/demo.c:92:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/MCF5235/demo.c:71:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/STR71XTCP/demo.c:243:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/ATSAM3S/demo.c:111:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/Z8ENCORE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./demo/AT91SAM7X_ROWLEY/demo.c:98:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | ./modbus/include/mb.h:101: * eMBRegInputCB( ). | ||
+ | ./modbus/include/mb.h:312:eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, | ||
+ | ./modbus/functions/mbfuncinput.c:96: eMBRegInputCB( pucFrameCur, usRegAddress, usRegCount ); | ||
+ | |||
+ | user@~/projects/freemodbus-v1.5.0$ | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] C function pointers and further references == | ||
+ | |||
+ | <ul> | ||
+ | * https://stackoverflow.com/questions/40239270/freemodbus-embregcoilscb-function-body-example | ||
+ | </ul> | ||
+ | |||
+ | What do we need to pass to eMBRegisterCB()? . . . | ||
+ | <ul> | ||
+ | * https://os.mbed.com/users/giryan/code/Modbus/docs/tip/group__modbus.html | ||
+ | |||
+ | C function pointer syntax: | ||
+ | * https://stackoverflow.com/questions/1591361/understanding-typedefs-for-function-pointers-in-c | ||
+ | * https://www.zentut.com/c-tutorial/c-function-pointer/ | ||
+ | </ul> | ||
+ | |||
+ | 2019-11-14 Thursday: | ||
+ | <ul> | ||
+ | * https://cs.nyu.edu/courses/spring12/CSCI-GA.3033-014/Assignment1/function_pointers.html | ||
+ | </ul> | ||
+ | |||
+ | |||
+ | A highlighting test . . . | ||
+ | |||
+ | <font color="magenta">ted@kalaru</font><font color="cyan">:</font>/var/local/ted/projects/lsa/freemodbus-v1.5.0$ grep -nr <font color="red">eMBRegInputCB</font> ./* | ||
+ | <font color="magenta">./demo/WIN32/demo.cpp</font><font color="cyan">:</font>221<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71XGCC/demo.c</font><font color="cyan">:</font>101<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/ATSAM3S_FREERTOS/demo.c</font><font color="cyan">:</font>168<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/BARE/demo.c</font><font color="cyan">:</font>55<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71X/exdisc.c</font><font color="cyan">:</font>118<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71X/excoils.c</font><font color="cyan">:</font>130<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71X/simple2.c</font><font color="cyan">:</font>87<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">Binary file ./demo/STR71X/.simple2.c.swp matches | ||
+ | </font> <font color="magenta">./demo/STR71X/exholding.c</font><font color="cyan">:</font>86<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71X/simple.c</font><font color="cyan">:</font>94<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/LPC214X/demo.c</font><font color="cyan">:</font>55<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/LINUX/demo.c</font><font color="cyan">:</font>264<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/LINUXTCP/demo.c</font><font color="cyan">:</font>219<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/MSP430/demo.c</font><font color="cyan">:</font>81<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/HCS08/demo.c</font><font color="cyan">:</font>149<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/MCF5235TCP/demo.c</font><font color="cyan">:</font>129<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/WIN32TCP/demo.cpp</font><font color="cyan">:</font>214<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/AVR/demo.c</font><font color="cyan">:</font>63<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/AVR/excoils.c</font><font color="cyan">:</font>101<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/MCF5235CW/demo.c</font><font color="cyan">:</font>92<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/MCF5235/demo.c</font><font color="cyan">:</font>71<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/STR71XTCP/demo.c</font><font color="cyan">:</font>243<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/ATSAM3S/demo.c</font><font color="cyan">:</font>111<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/Z8ENCORE/demo.c</font><font color="cyan">:</font>55<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./demo/AT91SAM7X_ROWLEY/demo.c</font><font color="cyan">:</font>98<font color="cyan">:</font><font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) | ||
+ | <font color="magenta">./modbus/include/mb.h</font><font color="cyan">:</font>101<font color="cyan">:</font> * <font color="red">eMBRegInputCB</font>( ). | ||
+ | <font color="magenta">./modbus/include/mb.h</font><font color="cyan">:</font>312<font color="cyan">:</font>eMBErrorCode <font color="red">eMBRegInputCB</font>( UCHAR * pucRegBuffer, USHORT usAddress, | ||
+ | <font color="magenta">./modbus/functions/mbfuncinput.c</font><font color="cyan">:</font>96<font color="cyan">:</font> <font color="red">eMBRegInputCB</font>( pucFrameCur, usRegAddress, usRegCount ); | ||
+ | |||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | |||
+ | == [[#top|^]] xFunctionHandlers == | ||
+ | |||
+ | This looks important: | ||
+ | |||
+ | ./modbus/mb.c:95:static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = { | ||
+ | |||
+ | The full declaration is: | ||
+ | |||
+ | <pre> | ||
+ | static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = { | ||
+ | #if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0 | ||
+ | {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID}, | ||
+ | #endif | ||
+ | #if MB_FUNC_READ_INPUT_ENABLED > 0 | ||
+ | {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister}, | ||
+ | #endif | ||
+ | #if MB_FUNC_READ_HOLDING_ENABLED > 0 | ||
+ | {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister}, | ||
+ | #endif | ||
+ | #if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 | ||
+ | {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister}, | ||
+ | #endif | ||
+ | #if MB_FUNC_WRITE_HOLDING_ENABLED > 0 | ||
+ | {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister}, | ||
+ | #endif | ||
+ | #if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 | ||
+ | {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister}, | ||
+ | #endif | ||
+ | #if MB_FUNC_READ_COILS_ENABLED > 0 | ||
+ | {MB_FUNC_READ_COILS, eMBFuncReadCoils}, | ||
+ | #endif | ||
+ | #if MB_FUNC_WRITE_COIL_ENABLED > 0 | ||
+ | {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil}, | ||
+ | #endif | ||
+ | #if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0 | ||
+ | {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils}, | ||
+ | #endif | ||
+ | #if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0 | ||
+ | {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs}, | ||
+ | #endif | ||
+ | }; | ||
+ | </pre> | ||
+ | |||
+ | FreeModbus presently supports sixteen function handlers in this array: | ||
+ | |||
+ | ./include/mbconfig.h:86:#define MB_FUNC_HANDLERS_MAX ( 16 ) | ||
+ | |||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Modbus RTU == | ||
+ | |||
+ | |||
+ | 2019-11-20 Question: how does FreeModbus RTU protocol version get to the state of "received frame"? | ||
+ | |||
+ | <pre> | ||
+ | $ grep -nr STATE_RX_RCV ./* | grep -v '^Binary' | ||
+ | ./Modbus/ascii/mbascii.c:61: STATE_RX_RCV, /*!< Frame is beeing received. */ | ||
+ | ./Modbus/ascii/mbascii.c:237: case STATE_RX_RCV: | ||
+ | ./Modbus/ascii/mbascii.c:301: eRcvState = STATE_RX_RCV; | ||
+ | ./Modbus/ascii/mbascii.c:321: eRcvState = STATE_RX_RCV; | ||
+ | ./Modbus/ascii/mbascii.c:417: case STATE_RX_RCV: | ||
+ | ./Modbus/ascii/mbascii.c:423: assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) ); | ||
+ | ./Modbus/rtu/mbrtu.c:58: STATE_RX_RCV, /*!< Frame is beeing received. */ | ||
+ | ./Modbus/rtu/mbrtu.c:261: eRcvState = STATE_RX_RCV; | ||
+ | ./Modbus/rtu/mbrtu.c:272: case STATE_RX_RCV: | ||
+ | ./Modbus/rtu/mbrtu.c:339: case STATE_RX_RCV: | ||
+ | ./Modbus/rtu/mbrtu.c:350: ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | In following paragraph, FreeModbus source files are ./mb.c, ./rtu/mbrtu.c, and ./port/mv_portserial.c. | ||
+ | |||
+ | In our case, FreeModbus routine <code>eMBInit()</code> calls routine <code>eMBRTUInit()</code>. Routine eMBRTUInit() seems to ignore the caller's passed Modbus slave device address . . . strange. This routine in turn calls <code>xMBPortSerialInit()</code> with parameters: ucPort, ulBaudRate, 8, eParity. . . . | ||
+ | |||
+ | |||
+ | |||
+ | Aside question: in the Kargs BACNet stack what does the following line of code carry out? | ||
+ | |||
+ | # ./BACnet/src/mstp.c:307: mstp_port->SilenceTimerReset((void *) mstp_port); | ||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Implementing eMBRegHoldingCB and eMBRegCoilsCB == | ||
+ | |||
+ | With routine <code>eMBRegHoldingCB()</code> implemented for a given project, can we use this as an example to implement <code>eMBRegCoilsCB()</code>? . . . | ||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] LPC Stuff == | ||
+ | |||
+ | How is an LPC11u6x software project able to specify system clock source, and clock speed? | ||
+ | |||
+ | In one project there is, in <code>int main()</code> a first line which calls routine <code>SystemCoreClockUpdate()</code>. There are two files in <code>[project dir | parent to project dir]/Common/lpc_chip/chip_11u6x/</code> which define this routine. In file [project]/Common/lpc_chip/chip_11u6x/chip_11u6x.c: | ||
+ | |||
+ | <pre> | ||
+ | 288 /* Return main clock rate */ | ||
+ | 289 uint32_t Chip_Clock_GetMainClockRate(void) | ||
+ | 290 { | ||
+ | 291 uint32_t clkRate = 0; | ||
+ | 292 | ||
+ | 293 switch ((CHIP_SYSCTL_MAINCLKSRC_T) (LPC_SYSCTL->MAINCLKSEL & 0x3)) { | ||
+ | 294 case SYSCTL_MAINCLKSRC_IRC: | ||
+ | 295 clkRate = Chip_Clock_GetIntOscRate(); | ||
+ | 296 break; | ||
+ | 297 | ||
+ | 298 case SYSCTL_MAINCLKSRC_PLLIN: | ||
+ | 299 clkRate = Chip_Clock_GetSystemPLLInClockRate(); | ||
+ | 300 break; | ||
+ | 301 | ||
+ | 302 case SYSCTL_MAINCLKSRC_WDTOSC: | ||
+ | 303 clkRate = Chip_Clock_GetWDTOSCRate(); | ||
+ | 304 break; | ||
+ | 305 | ||
+ | 306 case SYSCTL_MAINCLKSRC_PLLOUT: | ||
+ | 307 clkRate = Chip_Clock_GetSystemPLLOutClockRate(); | ||
+ | 308 break; | ||
+ | 309 } | ||
+ | 310 | ||
+ | 311 return clkRate; | ||
+ | 312 } | ||
+ | 313 | ||
+ | 314 /* Return system clock rate */ | ||
+ | 315 uint32_t Chip_Clock_GetSystemClockRate(void) | ||
+ | 316 { | ||
+ | 317 /* No point in checking for divide by 0 */ | ||
+ | 318 return Chip_Clock_GetMainClockRate() / LPC_SYSCTL->SYSAHBCLKDIV; | ||
+ | 319 } | ||
+ | clock_11u6x.c [dos] (09:59 30/10/2019) | ||
+ | </pre> | ||
+ | |||
+ | In file [project]/Common/lpc_chip/chip_11u6x/system_LPC11Uxx.c: | ||
+ | |||
+ | <pre> | ||
+ | |||
+ | 296 | ||
+ | 297 /*---------------------------------------------------------------------------- | ||
+ | 298 Clock Variable definitions | ||
+ | 299 *----------------------------------------------------------------------------*/ | ||
+ | 300 uint32_t SystemCoreClock = __SYSTEM_CLOCK;/*!< System Clock Frequency (Core Clock)*/ | ||
+ | 301 | ||
+ | 302 | ||
+ | 303 /*---------------------------------------------------------------------------- | ||
+ | 304 Clock functions | ||
+ | 305 *----------------------------------------------------------------------------*/ | ||
+ | 306 void SystemCoreClockUpdate (void) /* Get Core Clock Frequency */ | ||
+ | 307 { | ||
+ | 308 uint32_t wdt_osc = 0; | ||
+ | 309 | ||
+ | 310 /* Determine clock frequency according to clock register values */ | ||
+ | 311 switch ((LPC_SYSCTL->WDTOSCCTRL >> 5) & 0x0F) { | ||
+ | 312 case 0: wdt_osc = 0; break; | ||
+ | 313 case 1: wdt_osc = 500000; break; | ||
+ | 314 case 2: wdt_osc = 800000; break; | ||
+ | 315 case 3: wdt_osc = 1100000; break; | ||
+ | 316 case 4: wdt_osc = 1400000; break; | ||
+ | 317 case 5: wdt_osc = 1600000; break; | ||
+ | 318 case 6: wdt_osc = 1800000; break; | ||
+ | 319 case 7: wdt_osc = 2000000; break; | ||
+ | 320 case 8: wdt_osc = 2200000; break; | ||
+ | 321 case 9: wdt_osc = 2400000; break; | ||
+ | 322 case 10: wdt_osc = 2600000; break; | ||
+ | 323 case 11: wdt_osc = 2700000; break; | ||
+ | 324 case 12: wdt_osc = 2900000; break; | ||
+ | 325 case 13: wdt_osc = 3100000; break; | ||
+ | 326 case 14: wdt_osc = 3200000; break; | ||
+ | 327 case 15: wdt_osc = 3400000; break; | ||
+ | 328 } | ||
+ | 329 wdt_osc /= ((LPC_SYSCTL->WDTOSCCTRL & 0x1F) << 1) + 2; | ||
+ | 330 | ||
+ | 331 switch (LPC_SYSCTL->MAINCLKSEL & 0x03) { | ||
+ | 332 case 0: /* Internal RC oscillator */ | ||
+ | 333 SystemCoreClock = __IRC_OSC_CLK; | ||
+ | |||
+ | 334 break; | ||
+ | 335 case 1: /* Input Clock to System PLL */ | ||
+ | 336 switch (LPC_SYSCTL->SYSPLLCLKSEL & 0x03) { | ||
+ | 337 case 0: /* Internal RC oscillator */ | ||
+ | 338 SystemCoreClock = __IRC_OSC_CLK; | ||
+ | 339 break; | ||
+ | 340 case 1: /* System oscillator */ | ||
+ | 341 SystemCoreClock = __SYS_OSC_CLK; | ||
+ | 342 break; | ||
+ | 343 case 2: /* Reserved */ | ||
+ | 344 case 3: /* Reserved */ | ||
+ | 345 SystemCoreClock = 0; | ||
+ | 346 break; | ||
+ | 347 } | ||
+ | 348 break; | ||
+ | 349 case 2: /* WDT Oscillator */ | ||
+ | 350 SystemCoreClock = wdt_osc; | ||
+ | 351 break; | ||
+ | 352 case 3: /* System PLL Clock Out */ | ||
+ | 353 switch (LPC_SYSCTL->SYSPLLCLKSEL & 0x03) { | ||
+ | 354 case 0: /* Internal RC oscillator */ | ||
+ | 355 if (LPC_SYSCTL->SYSPLLCTRL & 0x180) { | ||
+ | 356 SystemCoreClock = __IRC_OSC_CLK; | ||
+ | 357 } else { | ||
+ | 358 SystemCoreClock = __IRC_OSC_CLK * ((LPC_SYSCTL->SYSPLLCTRL & 0x01F) + 1); | ||
+ | 359 } | ||
+ | 360 break; | ||
+ | 361 case 1: /* System oscillator */ | ||
+ | 362 if (LPC_SYSCTL->SYSPLLCTRL & 0x180) { | ||
+ | 363 SystemCoreClock = __SYS_OSC_CLK; | ||
+ | 364 } else { | ||
+ | 365 SystemCoreClock = __SYS_OSC_CLK * ((LPC_SYSCTL->SYSPLLCTRL & 0x01F) + 1); | ||
+ | 366 } | ||
+ | 367 break; | ||
+ | 368 case 2: /* Reserved */ | ||
+ | 369 case 3: /* Reserved */ | ||
+ | 370 SystemCoreClock = 0; | ||
+ | 371 break; | ||
+ | 372 } | ||
+ | 373 break; | ||
+ | 374 } | ||
+ | 375 | ||
+ | 376 SystemCoreClock /= LPC_SYSCTL->SYSAHBCLKDIV; | ||
+ | 377 | ||
+ | 378 } | ||
+ | </pre> | ||
+ | |||
+ | These routines are completely different. What does a trace in IAR reveal about which project source files' code is executing during start up? . . . it is the second source file mentioned here, file <code>system_LPC11Uxx.c</code>. | ||
+ | |||
+ | |||
+ | |||
+ | <!-- comment --> | ||
+ | |||
+ | == [[#top|^]] Related Tools, Resources and References == | ||
+ | |||
+ | Modbus data packet structure: | ||
+ | <ul> | ||
+ | * https://www.modbustools.com/modbus.html#function16 Modbus write multiple registers data packet message and response | ||
+ | * http://helpdesk.servelec-technologies.com/knowledgebase/article/view/700/156 | ||
+ | </ul> | ||
+ | |||
+ | |||
+ | <ul> | ||
+ | * https://github.com/ed-chemnitz/qmodbus/ | ||
+ | </ul> | ||
+ | |||
+ | Search string "FreeModbus configure UART lpc microcontroller": | ||
+ | <ul> | ||
+ | * https://community.nxp.com/thread/444068 | ||
+ | </ul> | ||
+ | |||
+ | Good explanation of Modbus coils and discrete inputs, syntax for reading n coil values: | ||
+ | <ul> | ||
+ | * https://www.csimn.com/CSI_pages/Modbus101.html | ||
+ | </ul> | ||
+ | |||
+ | |||
+ | <center> | ||
+ | - - - [[#top|top of page]] - - - | ||
+ | </center> |
Latest revision as of 17:04, 2 July 2024
OVERVIEW
Notes on Modbus protocol, and references to one or more Modbus protocol libraries, written in C and usable in Unix and Linux envorinments.
The following web site does one of the better jobs of describing Modbus protocol clearly, and with multiple examples.
This IPC2U site combined with code snippets from a Github gist authored and shared by J Grossholtz is sufficient to write a C program which can make a PC act as a Modbus enabled device. While compiling an first test program using this library, encountered need to include library path to gcc.
How to specify preference of library path to C compiler and linker:
Stackoverflow post showing libmodbus based sender and listener code snippets:
J. Grossholtz Github gist with modbus server and client sample C programs:
On systems supporting standard C and its libraries, `libmodbus` may be used to create programs which act as Modbus controllers (sending commands) and or peripherals (responding to commamds).
OVERVIEW CERCA 2019
This wiki article contains some notes and references on FreeModbus, an opensource C library to support a Modbus protocol stack in stand-alone applications as well as operating system and scheduler based systems. This article also contains a number of links to more general C language references, which discuss function pointers in C, and the type defining of function pointers.
A few links:
- https://libmodbus.org/ . . . Ubuntu package is 'libmodbus5' and 'libmodbus-dev'
Table of contents for local Modbus notes:
Contents
- 1 OVERVIEW
- 2 OVERVIEW CERCA 2019
- 3 ^ Modbus and Zephyr RTOS
- 4 ^ Freemodbus Notes from 2019
- 5 ^ floating point byte order
- 6 ^ Obscurely defined FreeModbus variables
- 7 ^ Modbus frames and frame handling
- 8 ^ Modbus register input callback routine
- 9 ^ C function pointers and further references
- 10 ^ xFunctionHandlers
- 11 ^ Modbus RTU
- 12 ^ Implementing eMBRegHoldingCB and eMBRegCoilsCB
- 13 ^ LPC Stuff
- 14 ^ Related Tools, Resources and References
^ Modbus and Zephyr RTOS
"zephyr/samples/subsys/modbus/rtu_client/app.overlay" 15L, 269C
UART hardware driver types in Zephyr RTOS project: "doc/hardware/peripherals/uart.rst" 77L, 1979C
^ Freemodbus Notes from 2019
Note: FreeModbus project uses a variable and routine naming scheme where many names begin with one or two lower-case characters. From context in the file ./modbus/mb.c, it looks like 'uc' may stand for 'unsigned char' data type. The starting lower case 'e' stands for 'enumeration':
62 static UCHAR ucMBAddress; 63 static eMBMode eMBCurrentMode;
- 2019-11-08 FRI -
When working with FreeModbus, be sure to pay attention to the versions of protocol available, and likely, we only want to enable one of these. See Modbus/include/mbconfig.h. See also Modbus/include/mb.h for instructions on how to initialize and begin to use an instance of FreeModbus stack in given program. Also mention that eMBPoll() routine may be set up as a task in an RTOS, to realize the periodic polling which is part of the stack's runtime manifestation . . .
Looks like also this function to register callbacks for end-user-device-specific Modbus registers will be important:
eMBErrorCode eMBRegisterCB ( UCHAR ucFunctionCode, pxMBFunctionHandler pxHandler )
This function signature part of documentation found at https://www.embedded-solutions.at/en/freemodbus/api-documentation/, which gives Doxygen generated API help in an embedded pane in the browser window on which it is visited.
2019-11-12 Tuesday - trying to figure out how pxMBFunctionHandler is defined . . . from http://www.teamfdi.com/uez/docs/mbproto_8h.html:
Typedefs typedef eMBException(* pxMBFunctionHandler )(UCHAR *pucFrame, USHORT *pusLength)
...from this typedef looks like Modbus routines implemented by the end user/end designer must have parameter lists which match "unsigned char pointer, unsigned integer pointer".
2019-11-13
Further search for FreeModbus callback registering code"
downloads/freemodbus/freemodbus-v1.5.0$ grep -nr pxMBFunctionHandler ./* ./modbus/mb.c:226:eMBRegisterCB( UCHAR ucFunctionCode, pxMBFunctionHandler pxHandler ) ./modbus/include/mbproto.h:72:typedef eMBException( *pxMBFunctionHandler ) ( UCHAR * pucFrame, USHORT * pusLength ); ./modbus/include/mbproto.h:77: pxMBFunctionHandler pxHandler; ./modbus/include/mb.h:266: pxMBFunctionHandler pxHandler )
And the entire section of typedefs from mbproto.h:
/* ----------------------- Type definitions ---------------------------------*/ typedef enum { MB_EX_NONE = 0x00, MB_EX_ILLEGAL_FUNCTION = 0x01, MB_EX_ILLEGAL_DATA_ADDRESS = 0x02, MB_EX_ILLEGAL_DATA_VALUE = 0x03, MB_EX_SLAVE_DEVICE_FAILURE = 0x04, MB_EX_ACKNOWLEDGE = 0x05, MB_EX_SLAVE_BUSY = 0x06, MB_EX_MEMORY_PARITY_ERROR = 0x08, MB_EX_GATEWAY_PATH_FAILED = 0x0A, MB_EX_GATEWAY_TGT_FAILED = 0x0B } eMBException; typedef eMBException( *pxMBFunctionHandler ) ( UCHAR * pucFrame, USHORT * pusLength ); typedef struct { UCHAR ucFunctionCode; pxMBFunctionHandler pxHandler; } xMBFunctionHandler;
^ floating point byte order
^ Obscurely defined FreeModbus variables
Where is the following demo/LPC214X/port/port.c
variable defined? Does not appear defined in any file of FreeModbus 1.5.0 download. Note however that this may be an LPC microcontroller register name:
VICIntEnable
See http://www.keil.com/dd/vtr/3880/9794.htm for a brief mention of this term. - TMH
^ Modbus frames and frame handling
This search result may not be directly useful to getting a first modbus routine running and testable:
./modbus/include/mbport.h:109:extern BOOL( *pxMBFrameCBByteReceived ) ( void ); Binary file ./modbus/include/.mbport.h.swp matches ./modbus/mb.c:85:BOOL( *pxMBFrameCBByteReceived ) ( void ); ./modbus/mb.c:153: pxMBFrameCBByteReceived = xMBRTUReceiveFSM; ./modbus/mb.c:167: pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
We notice, however, that in [project]/Modbus/mb.c
in routine eMBPoll() there is a switch statement which selects code per events of a state machine. The enumerated event eEvent
can take on the value 'EV_FRAME_RECEIVED'. When this occurs, this part of the FreeModbus library calls a function pointer, which is set to one of two functions based on Modbus protocol version RTU or ASCII. The function pointer is named peMBFrameReceiveCur
and by assigned in routine eMBPoll()
points to eMBRTUReceive
.
Search for the Modbus RTU receive frame function:
$ grep -nr eMBRTUReceive ./* ./mb.c:167: peMBFrameReceiveCur = eMBRTUReceive; ./rtu/mbrtu.c:153:eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) ./rtu/mbrtu.h:40:eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength );
This function eMBRTUReceive is defined as follows:
eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { BOOL xFrameReceived = FALSE; eMBErrorCode eStatus = MB_ENOERR; ENTER_CRITICAL_SECTION( ); assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); /* Length and CRC check */ if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed * and the decision if a frame is used is done there. */ *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus * size of address field and CRC checksum. */ *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); /* Return the start of the Modbus PDU to the caller. */ *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; xFrameReceived = TRUE; } else { eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; }
So there is also an eMBRTUInit() function,
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
which itself calls a function named xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity )
, but is this actually executing when we start our firmware running?
^ edit point
QUESTION: is this actually executing when we start our firmware running?
ANSWER: according to IAR Workbench yes.
QUESTION: what causes the event 'EV_FRAME_RECEIVED'?
ANSWER: From the following excerpt of pattern matching results, it looks like there is just one place this event gets posted, and this occurs by means of a line of code in mbrtu.c
:
Binary file ./Modbus/.mb.c.swp matches ./Modbus/ascii/mbascii.c:294: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); ./Modbus/include/mbport.h:42: EV_FRAME_RECEIVED, /*!< Frame received. */ ./Modbus/mb.c:411: case EV_FRAME_RECEIVED: Binary file ./Modbus/rtu/.mbrtu.c.swp matches ./Modbus/rtu/mbrtu.c:340: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
The routine where this happens is:
BOOL xMBRTUTimerT35Expired( void ) { BOOL xNeedPoll = FALSE; switch ( eRcvState ) { /* Timer t35 expired. Startup phase is finished. */ case STATE_RX_INIT: xNeedPoll = xMBPortEventPost( EV_READY ); break; /* A frame was received and t35 expired. Notify the listener that * a new frame was received. */ case STATE_RX_RCV: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); break; /* An error occured while receiving the frame. */ case STATE_RX_ERROR: break; /* Function called in an illegal state. */ default: assert( ( eRcvState == STATE_RX_INIT ) || ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); } vMBPortTimersDisable( ); eRcvState = STATE_RX_IDLE; return xNeedPoll; }
^ edit point - routine xMBRTUTimerT35Expired called?
QUESTION 1 OF 3: Is this timer routine ever running?
ANSWER 1 OF 3: not running per IAR breakpoint at first switch statement!
QUESTION 2 OF 3: What must we implement or at least uncomment in mbportserial.c
routine:
BOOL xMBPortSerialGetByte( CHAR * pucByte ) { #if 0 while( !( U1LSR & 0x01 ) ) { } /* Receive Byte */ *pucByte = U1RBR; #endif return TRUE; }
QUESTION 3 OF 3: where among FreeModbus demos appear xMBPortSerialGetByte
and xMBRTUTimerT35Expired
?
ANSWER 3 OF 3: xMBRTUTimerT35Expired
appears in no demos. xMBPortSerialGetByte
appears in all or nearly all the demos. We note also at this time that the register name U1RBR appears to be LPC21xx specific, as this does not appear in the LPC11U6x datasheet. Looking now to the serial based Command Line Interface (CLI) code, what may we find for reading single bytes from a UART input buffer there? . . .
QUESTION: what is the "xMBPortSerialGetByte" routine in L S command line interface?
ANSWER:
To find the "get byte" equivalent in CLI code, we follow that uart.c routine ___ calls ./lpc_chip/chip_11u6x/uart_0_11u6x.c routine Chip_UART0_ReadRB()
, defined:
206 /* Copy data from a receive ring buffer */ 207 int Chip_UART0_ReadRB(LPC_USART0_T *pUART, RINGBUFF_T *pRB, void *data, int bytes) 208 { 209 (void) pUART; 210 211 return RingBuffer_PopMult(pRB, (uint8_t *) data, bytes); 212 }
The routine RingBuffer_PopMult() does not reveal any details about a receive byte register or buffer name for LPC11U6X UART0. But returning to an earlier question about how to link or associate an LPC timer event with an interrupt service routine, we note that in the older, custom modbus.c there is a routine named SilenceTimerCallback(), which may be used for recognizing the 3.5 character width timing delays between Modbus PDUs or data packets. And in uart.c, there is a reference to this function on line 353:
^ edit point - init_uart3 routine calls xTimerCreate
void init_uart3(void) { /* Shut down for now */ NVIC_DisableIRQ(USART2_3_IRQn); /* Configure pins */ Chip_IOCON_PinMuxSet(LPC_IOCON, PORT(UART3RX), PIN(UART3RX), IOCON_FUNC1 | IOCON_DIGMODE_EN); Chip_IOCON_PinMuxSet(LPC_IOCON, PORT(UART3TX), PIN(UART3TX), IOCON_FUNC1 | IOCON_DIGMODE_EN); /* Enable UART clock */ LPC_SYSCTL->SYSAHBCLKCTRL |= UART3_CLK_EN; LPC_SYSCTL->FRGCLKDIV = 0x1; /* divided by 1 */ LPC_SYSCTL->PRESETCTRL |= UART3_CLR_RST; /* clear reset on UART3 */ LPC_USART3->BRG = (CLOCK_FREQUENCY/(16 * MODBUS_RATE)) - 1; /* set baud rate */ /* Enable UART | 8N1 parity */ LPC_USART3->CFG |= UARTN_CFG_ENABLE | UARTN_CFG_DATALEN_8 | UARTN_CFG_PARITY_NONE | UARTN_CFG_STOPLEN_1; /* Enable UART interrupt bits */ LPC_USART3->INTENSET = INTENSET_RXRDYEN; // | INTENSET_TXRDYEN | INTENSET_TXIDLEEN; /* Enable the UART Interrupt */ NVIC_EnableIRQ(USART2_3_IRQn); // 2019-01-21 - Ted removing at pre-compile step some legacy Modbus code: #ifndef USING_FREEMODBUS_REL_1_6_0_OR_SIMILAR //initialize the ring buffers RingBuffer_Init(&Modbus_TX_Buffer, (void*)&modbus_tx_buff[0], 1, MODBUS_BUFSIZE_RX); RingBuffer_Init(&Modbus_RX_Buffer, (void*)&modbus_rx_buff[0], 1, MODBUS_BUFSIZE_TX); //initialize silence timer if(ModbusSilenceTimer == 0) { ModbusSilenceTimer = xTimerCreate("ModbusSilenceTimer", 10*3*10*1000/MODBUS_RATE, pdFALSE, 0, &SilenceTimerCallback); } #endif return; }
^ Modbus register input callback routine
Here is a search history, some searches run in the STR71X demo. These variables and routines look more promising in terms of telling us how to implement a routine of our own which properly ties in with FreeModbus library code. What these variables and routines lead us to examine, is how the code developers, us, using FreeModbus can return data from a device to a meaningful place in memory, which FreeModbus then reads and send out along the physical link layer to a given Modbus master.
From the following searches,
1004 grep -nr usRegInputBuf ./* 1005 grep -n pucRegBuffer ./* 1006 # eMBRegInputCB, third token for which to search
We quickly come to the routine named eMBRegInputCB(). It turns out this routine is implemented in various FreeModbus demos, which is to say it is not a FreeModbus library routine. But part of FreeModbus library source calls or needs to call a routine by this name. So it looks like as users of FreeModbus, we are bound to define a routine by this name and with its enumerated return type and parameter list as defined in freemodbus-v1.5.0/modbus/include/mb.h
.
Variable usRegInputBuf
is in source file simple2.c a static array of bytes, which hold values that a Modbus function returns,
<./simple2.c:42:static unsigned short usRegInputBuf[REG_INPUT_NREGS]; ./simple2.c:76: usRegInputBuf[0]++; ./simple2.c:79: usRegInputBuf[1] = ( unsigned portSHORT )( xLastWakeTime >> 16UL ); ./simple2.c:80: usRegInputBuf[2] = ( unsigned portSHORT )( xLastWakeTime & 0xFFFFUL ); ./simple2.c:82: usRegInputBuf[3] = 33; ./simple2.c:99: ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); ./simple2.c:101: ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
We look then at pucRegBuffer
because of the following assignments in a callback routine of this demo:
86 eMBErrorCode 87 eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) 88 { 89 eMBErrorCode eStatus = MB_ENOERR; 90 int iRegIndex; 91 92 if( ( usAddress >= REG_INPUT_START ) 93 && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) 94 { 95 iRegIndex = ( int )( usAddress - usRegInputStart ); 96 while( usNRegs > 0 ) 97 { 98 *pucRegBuffer++ = 99 ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); 100 *pucRegBuffer++ = 101 ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); 102 iRegIndex++; 103 usNRegs--; 104 } 105 } 106 else 107 { 108 eStatus = MB_ENOREG; 109 } 110 111 return eStatus; 112 }
Now the question is, which code calls the routine eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
? This routine is not called by any code in this specific demo:
$ grep -nr -A 1 eMBRegInputCB ./* ./excoils.c:130:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./excoils.c-131-{ -- ./exdisc.c:118:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./exdisc.c-119-{ -- ./exholding.c:86:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./exholding.c-87-{ -- ./simple2.c:87:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./simple2.c-88-{ -- ./simple.c:94:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./simple.c-95-{
Looking now a level or two up and at the FreeModbus library sources . . . there is a longer function in file ./modbus/functions/mbfuncinput.c
but worth noting here, as it parses the modbus PDU to figure out what type of PDU read versus write it is, and how many bytes are expected to follow or are being requested. Worth noting the function definition in full here:
/* ----------------------- Start implementation -----------------------------*/ #if MB_FUNC_READ_INPUT_ENABLED > 0 eMBException eMBFuncReadInputRegister( UCHAR * pucFrame, USHORT * usLen ) { USHORT usRegAddress; USHORT usRegCount; UCHAR *pucFrameCur; eMBException eStatus = MB_EX_NONE; eMBErrorCode eRegStatus; if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) ) { usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); usRegAddress++; usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 ); usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] ); /* Check if the number of registers to read is valid. If not * return Modbus illegal data value exception. */ if( ( usRegCount >= 1 ) && ( usRegCount < MB_PDU_FUNC_READ_REGCNT_MAX ) ) { /* Set the current PDU data pointer to the beginning. */ pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; *usLen = MB_PDU_FUNC_OFF; /* First byte contains the function code. */ *pucFrameCur++ = MB_FUNC_READ_INPUT_REGISTER; *usLen += 1; /* Second byte in the response contain the number of bytes. */ *pucFrameCur++ = ( UCHAR )( usRegCount * 2 ); *usLen += 1; eRegStatus = eMBRegInputCB( pucFrameCur, usRegAddress, usRegCount ); /* If an error occured convert it into a Modbus exception. */ if( eRegStatus != MB_ENOERR ) { eStatus = prveMBError2Exception( eRegStatus ); } else { *usLen += usRegCount * 2; } } else { eStatus = MB_EX_ILLEGAL_DATA_VALUE; } } else { /* Can't be a valid read input register request because the length * is incorrect. */ eStatus = MB_EX_ILLEGAL_DATA_VALUE; } return eStatus; } #endif
user@~/projects/freemodbus-v1.5.0$ grep -nr eMBRegInputCB ./* ./demo/WIN32/demo.cpp:221:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71XGCC/demo.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/ATSAM3S_FREERTOS/demo.c:168:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/BARE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/exdisc.c:118:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/excoils.c:130:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/simple2.c:87:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) Binary file ./demo/STR71X/.simple2.c.swp matches ./demo/STR71X/exholding.c:86:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/simple.c:94:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LPC214X/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LINUX/demo.c:264:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LINUXTCP/demo.c:219:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MSP430/demo.c:81:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/HCS08/demo.c:149:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235TCP/demo.c:129:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/WIN32TCP/demo.cpp:214:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AVR/demo.c:63:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AVR/excoils.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235CW/demo.c:92:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235/demo.c:71:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71XTCP/demo.c:243:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/ATSAM3S/demo.c:111:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/Z8ENCORE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AT91SAM7X_ROWLEY/demo.c:98:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./modbus/include/mb.h:101: * eMBRegInputCB( ). ./modbus/include/mb.h:312:eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, ./modbus/functions/mbfuncinput.c:96: eMBRegInputCB( pucFrameCur, usRegAddress, usRegCount ); user@~/projects/freemodbus-v1.5.0$
^ C function pointers and further references
What do we need to pass to eMBRegisterCB()? . . .
-
C function pointer syntax:
2019-11-14 Thursday:
A highlighting test . . .
ted@kalaru:/var/local/ted/projects/lsa/freemodbus-v1.5.0$ grep -nr eMBRegInputCB ./*
./demo/WIN32/demo.cpp:221:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71XGCC/demo.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/ATSAM3S_FREERTOS/demo.c:168:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/BARE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/exdisc.c:118:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/excoils.c:130:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71X/simple2.c:87:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) Binary file ./demo/STR71X/.simple2.c.swp matches
./demo/STR71X/exholding.c:86:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
./demo/STR71X/simple.c:94:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LPC214X/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LINUX/demo.c:264:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/LINUXTCP/demo.c:219:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MSP430/demo.c:81:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/HCS08/demo.c:149:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235TCP/demo.c:129:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/WIN32TCP/demo.cpp:214:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AVR/demo.c:63:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AVR/excoils.c:101:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235CW/demo.c:92:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/MCF5235/demo.c:71:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/STR71XTCP/demo.c:243:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/ATSAM3S/demo.c:111:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/Z8ENCORE/demo.c:55:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./demo/AT91SAM7X_ROWLEY/demo.c:98:eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) ./modbus/include/mb.h:101: * eMBRegInputCB( ). ./modbus/include/mb.h:312:eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, ./modbus/functions/mbfuncinput.c:96: eMBRegInputCB( pucFrameCur, usRegAddress, usRegCount );
^ xFunctionHandlers
This looks important:
./modbus/mb.c:95:static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
The full declaration is:
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = { #if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0 {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID}, #endif #if MB_FUNC_READ_INPUT_ENABLED > 0 {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister}, #endif #if MB_FUNC_READ_HOLDING_ENABLED > 0 {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister}, #endif #if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister}, #endif #if MB_FUNC_WRITE_HOLDING_ENABLED > 0 {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister}, #endif #if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister}, #endif #if MB_FUNC_READ_COILS_ENABLED > 0 {MB_FUNC_READ_COILS, eMBFuncReadCoils}, #endif #if MB_FUNC_WRITE_COIL_ENABLED > 0 {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil}, #endif #if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0 {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils}, #endif #if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0 {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs}, #endif };
FreeModbus presently supports sixteen function handlers in this array:
./include/mbconfig.h:86:#define MB_FUNC_HANDLERS_MAX ( 16 )
^ Modbus RTU
2019-11-20 Question: how does FreeModbus RTU protocol version get to the state of "received frame"?
$ grep -nr STATE_RX_RCV ./* | grep -v '^Binary' ./Modbus/ascii/mbascii.c:61: STATE_RX_RCV, /*!< Frame is beeing received. */ ./Modbus/ascii/mbascii.c:237: case STATE_RX_RCV: ./Modbus/ascii/mbascii.c:301: eRcvState = STATE_RX_RCV; ./Modbus/ascii/mbascii.c:321: eRcvState = STATE_RX_RCV; ./Modbus/ascii/mbascii.c:417: case STATE_RX_RCV: ./Modbus/ascii/mbascii.c:423: assert( ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_WAIT_EOF ) ); ./Modbus/rtu/mbrtu.c:58: STATE_RX_RCV, /*!< Frame is beeing received. */ ./Modbus/rtu/mbrtu.c:261: eRcvState = STATE_RX_RCV; ./Modbus/rtu/mbrtu.c:272: case STATE_RX_RCV: ./Modbus/rtu/mbrtu.c:339: case STATE_RX_RCV: ./Modbus/rtu/mbrtu.c:350: ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) );
In following paragraph, FreeModbus source files are ./mb.c, ./rtu/mbrtu.c, and ./port/mv_portserial.c.
In our case, FreeModbus routine eMBInit()
calls routine eMBRTUInit()
. Routine eMBRTUInit() seems to ignore the caller's passed Modbus slave device address . . . strange. This routine in turn calls xMBPortSerialInit()
with parameters: ucPort, ulBaudRate, 8, eParity. . . .
Aside question: in the Kargs BACNet stack what does the following line of code carry out?
# ./BACnet/src/mstp.c:307: mstp_port->SilenceTimerReset((void *) mstp_port);
^ Implementing eMBRegHoldingCB and eMBRegCoilsCB
With routine eMBRegHoldingCB()
implemented for a given project, can we use this as an example to implement eMBRegCoilsCB()
? . . .
^ LPC Stuff
How is an LPC11u6x software project able to specify system clock source, and clock speed?
In one project there is, in int main()
a first line which calls routine SystemCoreClockUpdate()
. There are two files in [project dir | parent to project dir]/Common/lpc_chip/chip_11u6x/
which define this routine. In file [project]/Common/lpc_chip/chip_11u6x/chip_11u6x.c:
288 /* Return main clock rate */ 289 uint32_t Chip_Clock_GetMainClockRate(void) 290 { 291 uint32_t clkRate = 0; 292 293 switch ((CHIP_SYSCTL_MAINCLKSRC_T) (LPC_SYSCTL->MAINCLKSEL & 0x3)) { 294 case SYSCTL_MAINCLKSRC_IRC: 295 clkRate = Chip_Clock_GetIntOscRate(); 296 break; 297 298 case SYSCTL_MAINCLKSRC_PLLIN: 299 clkRate = Chip_Clock_GetSystemPLLInClockRate(); 300 break; 301 302 case SYSCTL_MAINCLKSRC_WDTOSC: 303 clkRate = Chip_Clock_GetWDTOSCRate(); 304 break; 305 306 case SYSCTL_MAINCLKSRC_PLLOUT: 307 clkRate = Chip_Clock_GetSystemPLLOutClockRate(); 308 break; 309 } 310 311 return clkRate; 312 } 313 314 /* Return system clock rate */ 315 uint32_t Chip_Clock_GetSystemClockRate(void) 316 { 317 /* No point in checking for divide by 0 */ 318 return Chip_Clock_GetMainClockRate() / LPC_SYSCTL->SYSAHBCLKDIV; 319 } clock_11u6x.c [dos] (09:59 30/10/2019)
In file [project]/Common/lpc_chip/chip_11u6x/system_LPC11Uxx.c:
296 297 /*---------------------------------------------------------------------------- 298 Clock Variable definitions 299 *----------------------------------------------------------------------------*/ 300 uint32_t SystemCoreClock = __SYSTEM_CLOCK;/*!< System Clock Frequency (Core Clock)*/ 301 302 303 /*---------------------------------------------------------------------------- 304 Clock functions 305 *----------------------------------------------------------------------------*/ 306 void SystemCoreClockUpdate (void) /* Get Core Clock Frequency */ 307 { 308 uint32_t wdt_osc = 0; 309 310 /* Determine clock frequency according to clock register values */ 311 switch ((LPC_SYSCTL->WDTOSCCTRL >> 5) & 0x0F) { 312 case 0: wdt_osc = 0; break; 313 case 1: wdt_osc = 500000; break; 314 case 2: wdt_osc = 800000; break; 315 case 3: wdt_osc = 1100000; break; 316 case 4: wdt_osc = 1400000; break; 317 case 5: wdt_osc = 1600000; break; 318 case 6: wdt_osc = 1800000; break; 319 case 7: wdt_osc = 2000000; break; 320 case 8: wdt_osc = 2200000; break; 321 case 9: wdt_osc = 2400000; break; 322 case 10: wdt_osc = 2600000; break; 323 case 11: wdt_osc = 2700000; break; 324 case 12: wdt_osc = 2900000; break; 325 case 13: wdt_osc = 3100000; break; 326 case 14: wdt_osc = 3200000; break; 327 case 15: wdt_osc = 3400000; break; 328 } 329 wdt_osc /= ((LPC_SYSCTL->WDTOSCCTRL & 0x1F) << 1) + 2; 330 331 switch (LPC_SYSCTL->MAINCLKSEL & 0x03) { 332 case 0: /* Internal RC oscillator */ 333 SystemCoreClock = __IRC_OSC_CLK; 334 break; 335 case 1: /* Input Clock to System PLL */ 336 switch (LPC_SYSCTL->SYSPLLCLKSEL & 0x03) { 337 case 0: /* Internal RC oscillator */ 338 SystemCoreClock = __IRC_OSC_CLK; 339 break; 340 case 1: /* System oscillator */ 341 SystemCoreClock = __SYS_OSC_CLK; 342 break; 343 case 2: /* Reserved */ 344 case 3: /* Reserved */ 345 SystemCoreClock = 0; 346 break; 347 } 348 break; 349 case 2: /* WDT Oscillator */ 350 SystemCoreClock = wdt_osc; 351 break; 352 case 3: /* System PLL Clock Out */ 353 switch (LPC_SYSCTL->SYSPLLCLKSEL & 0x03) { 354 case 0: /* Internal RC oscillator */ 355 if (LPC_SYSCTL->SYSPLLCTRL & 0x180) { 356 SystemCoreClock = __IRC_OSC_CLK; 357 } else { 358 SystemCoreClock = __IRC_OSC_CLK * ((LPC_SYSCTL->SYSPLLCTRL & 0x01F) + 1); 359 } 360 break; 361 case 1: /* System oscillator */ 362 if (LPC_SYSCTL->SYSPLLCTRL & 0x180) { 363 SystemCoreClock = __SYS_OSC_CLK; 364 } else { 365 SystemCoreClock = __SYS_OSC_CLK * ((LPC_SYSCTL->SYSPLLCTRL & 0x01F) + 1); 366 } 367 break; 368 case 2: /* Reserved */ 369 case 3: /* Reserved */ 370 SystemCoreClock = 0; 371 break; 372 } 373 break; 374 } 375 376 SystemCoreClock /= LPC_SYSCTL->SYSAHBCLKDIV; 377 378 }
These routines are completely different. What does a trace in IAR reveal about which project source files' code is executing during start up? . . . it is the second source file mentioned here, file system_LPC11Uxx.c
.
^ Related Tools, Resources and References
Modbus data packet structure:
- https://www.modbustools.com/modbus.html#function16 Modbus write multiple registers data packet message and response
- http://helpdesk.servelec-technologies.com/knowledgebase/article/view/700/156
Search string "FreeModbus configure UART lpc microcontroller":
Good explanation of Modbus coils and discrete inputs, syntax for reading n coil values:
- - - top of page - - -