Porting for RTU/ASCII
The first steps should always be to create a new directory for the port. The recommended layout is to create a top level directory, e.g.demo/PLATFORM
which hold the application and project files. In addition a subdirectory port
should be created for the port specific files.
demo/PLATFORM/Makefile demo/PLATFORM/main.c demo/PLATFORM/port/portserial.c demo/PLATFORM/port/porttimer.c demo/PLATFORM/port/portother.c demo/PLATFORM/port/port.hYou can use
demo/BARE
as a starting point. Simply copy the directory and rename it to a name of your choice.
Platform specifics (port.h)
You should first check the fileport.h
and check the if the examples are already suitable for your platform. You must at least define the macros for enabling ENTER_CRITICAL_SECTION
and disabling EXIT_CRITICAL_SECTION
interrupts.
Implementation of the timer functions (porttimer.c)
The Modbus protocol stacks needs a timer to detect the end of the frame. The timers should have a resolution of half the time of a serial character. For example for 38400 baud the character time is approx. 280us assuming 11bits for a single character. The smallest timeout used by the protocol stack is 3.5 times the character timeout.You should start by implementing the function xMBPortTimersInit( USHORT usTim1Timerout50us )
and vMBPortTimersEnable( )
. Test the function with the following sample code:
xMBPortTimersInit( 20 ); vMBPortTimersEnable( ); for( ;; );Place a breakpoint or toggle an LED in the interrupt handler which calls
pxMBPortCBTimerExpired
. The ISR should occur approx. 1ms after the call to vMBPortTimersEnable()
. You should also check that vMBPortTimersDisable( )
works as expected.- Note:
- If you use Modbus ASCII the timers are in the range of seconds because the timeouts are much larger there. Make sure you can handle a value of 20000 for
usTim1Timerout50us
which corresponds to an one second timeout. Seembconfig.h
for the value of the timeout defined byMB_ASCII_TIMEOUT_SEC
.
Porting for RTU/ASCII
The serial porting layer must be capable of initializing the UART, disabling and enabling the receiver and transmitter components as well as performing callbacks if a character has been received or can be transmitted. You should start by implementingxMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
and vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
. In addition you need to create two interrupt service routines for you communication devices. It is usually simpler to start with the receive interrupt.
Create an interrupt handler for the receive interrupt, set a breakpoint there and check if xMBPortSerialGetByte( CHAR * pucByte )
correctly returns the character. This can be tested by the following code:
/* Initialize COM device 0 with 38400 baud, 8 data bits and no parity. */ if( xMBPortSerialInit( 0, 38400, 8, MB_PAR_NONE ) == FALSE ) { fprintf(stderr, "error: com init failed"); } else { /* Enable the receiver. */ vMBPortSerialEnable( TRUE, FALSE ); /* Now block. Any character received should cause an interrupt now. */ for( ;; ); }And your serial character received ISR should look like:
static void prvvUARTTxReadyISR( void ) { CHAR cByte; ( void )xMBPortSerialGetByte( &cByte ); /* Now cByte should contain the character received. */ }
Next you should check that the transmitter part is actually working as expected. Open a terminal program and simply call xMBPortSerialPutByte( 'a' )
in the transmit buffer empty ISR. If you use the sample code from below exactly 10 characters should be received.
/* Initialize COM device 0 with 38400 baud, 8 data bits and no parity. */ if( xMBPortSerialInit( 0, 38400, 8, MB_PAR_NONE ) == FALSE ) { fprintf(stderr, "error: com init failed"); } else { /* Enable the transmitter. */ vMBPortSerialEnable( FALSE, TRUE ); /* Now block. Any character received should cause an interrupt now. */ for( ;; ); }And you serial transmit buffer empty ISR should look like:
static unsigned int uiCnt = 0; void prvvUARTTxReadyISR( void ) { if( uiCnt++ < 10 ) { ( void )xMBPortSerialPutByte( 'a' ); } else { vMBPortSerialEnable( FALSE, FALSE ); } }
If you are sure everything works correctly change the interrupt routines back to the examples shown in portserial.c
Implementing the event queue (portevent.c)
If you are not using an operating system the port is already finished and the demo application should work as expected. If you in the luck of having an operating system usage of the FreeModbus protocol stack differs in the following way:
- Create another task at startup which calls eMBPoll( ) in a loop. This should look like:
for( ;; ) { ( void )eMBPoll( ); }
See the STR71x port for an FreeRTOS example.
- Change the function
xMBPortEventPost
to post an event to a queue. Note that this function will be called from an ISR so check your RTOS documentation for that.
- Change the
xMBPortEventGet
to retrieve an event from that queue. The functioneMBPoll
periodically calls it. The function should block until an event has been posted to the queue.
In addition the serial and timer interrupt function must be modified. Whenever the protocol handler callback functions pxMBFrameCBByteReceived
, pxMBFrameCBTransmitterEmpty
and pxMBPortCBTimerExpired
return TRUE
a context switch should be made after exiting the ISR because an event has been posted to the queue. Forgetting to do this will result in slow performance of the protocol stack.
Tips
This page provides some tips for using the FreeModbus protocol stack.
Reducing memory requirements
The memory requirements of FreeModbus can be tuned in the following way. These are basic tricks and can easily be done:
- Decided if you need RTU, ASCII and TCP at the same time. If not disable them in the file mbconfig.h by settings the respective options MB_RTU_ENABLED, MB_ASCII_ENABLED and MB_TCP_ENABLED to zero.
- If you don't need all Modbus functions disable them in the file mbconfig.h. This will reduce code requirements.
- Set the variable MB_FUNC_HANDLERS_MAX in mbconfig.h to the number of functions codes you want to support.
If you have stronger limits you can also try the following options. Note that this options have an impact on the features of the protocol stack.
- Use some compiler directive to put the mapping of function codes to handler functions into the flash memory of you CPU. You can find this table in the file mb.c at the top of the file. The static variable is named xFuncHandlers.
- Reduce the size of the RTU buffer. In this case longer frames will result in an error (Your device will drop all these frames). This is possible if you will never get read/write requests with that number of registers or your total amount of registers is small anyway.
- You could also remove some function pointers which make the protocol stack configurable and replace them by the functions itself. For example if you only want to use RTU remove the callback functions from the porting layer and fill in the appropriate calls. This will save the space for all function pointers.