FreeMODBUS
A portable MODBUS ASCII/RTU and TCP implementation
A portable MODBUS ASCII/RTU and TCP implementation
The example is based on the FreeRTOS/STR71X demo application simple2.c with some parts removed for presentation purposes. This example maps four 16 bit input registers are address 1000. Register 1000 counts the number of times the main polling loop was executed. The second and third register hold the RTOS tick counter. The fourth register holds a constant. Despite its simplicity this is all what is required for a basic slave which wants to export information over MODBUS.
/* ----------------------- Defines ------------------------------------------*/ #define REG_INPUT_START 1000 #define REG_INPUT_NREGS 4 /* ----------------------- Static variables ---------------------------------*/ static unsigned short usRegInputStart = REG_INPUT_START; static unsigned short usRegInputBuf[REG_INPUT_NREGS]; ... static void vModbusTask( void *pvParameters ) { portTickType xLastWakeTime; /* Select either ASCII or RTU Mode. */ ( void )eMBInit( MB_RTU, 0x0A, 38400, MB_PAR_EVEN ); /* Enable the Modbus Protocol Stack. */ ( void )eMBEnable( ); for( ;; ) { /* Call the main polling loop of the Modbus protocol stack. */ ( void )eMBPoll( ); /* Application specific actions. Count the number of poll cycles. */ usRegInputBuf[0]++; /* Hold the current FreeRTOS ticks. */ xLastWakeTime = xTaskGetTickCount( ); usRegInputBuf[1] = ( unsigned portSHORT )( xLastWakeTime >> 16UL ); usRegInputBuf[2] = ( unsigned portSHORT )( xLastWakeTime & 0xFFFFUL ); /* The constant value. */ usRegInputBuf[3] = 33; } }
Most of the work is performed within the main polling loop. Internally it works by waiting for an event posted to a queue (See footnote [1]). Such a event for example is the reception of a MODBUS frame. Events are posted to the event queue by the interrupt driven receiver and transmitter state machines. After a frame is received the FRAME_RECEIVED event is posted and the main loop is executed. Following the receive event an EXECUTION event is created internally which allows updating the registers values prior to sending the response. The end of transmission is signaled by the FRAME_SENT event.
Registers values for the MODBUS stack are provided within the respective callbacks. Whenever the stack requires the value of a register or a register is written the callback functions are executed. In the scope of this example input register values are provided by the function eMBRegInputCB
.
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) { iRegIndex = ( int )( usAddress - usRegInputStart ); while( usNRegs > 0 ) { *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); iRegIndex++; usNRegs--; } } else { eStatus = MB_ENOREG; } return eStatus; }
Using this code and a Modbus test utility we get the following output in a console window:
Z:homewoltidevelprivatarmfreemodbustools>modpoll.exe -m rtu -a 10 -r 10 00 -c 4 -t 3 -b 38400 -d 8 -p even COM4 modpoll - FieldTalk(tm) Modbus(R) Polling Utility Copyright (c) 2002-2004 FOCUS Software Engineering Pty Ltd Getopt Library Copyright (C) 1987-1997 Free Software Foundation, Inc. Protocol configuration: Modbus ASCII Slave configuration: Address = 10, start reference = 1000, count = 4 Serial port configuration: COM4, 9600, 7, 1, even Data type: 16-bit register, input register table Protocol opened successfully. Polling slave (Ctrl-C to stop) ... [1000]: 467 [1001]: 3 [1002]: 18547 [1003]: 33 Polling slave (Ctrl-C to stop) ... [1000]: 470 [1001]: 3 [1002]: 19544 [1003]: 33 Polling slave (Ctrl-C to stop) ...
[1]: Without an operating systems there are normally no queues available. In this case the queue sending and receiving functions are implemented using a static variable which is set to TRUE
when an event is posted. The receive function checks the variable and if TRUE
returns the previously stored event.