Getting started

If you're new to MicroTBX-Modbus, one of the first questions will be: How I do setup a Modbus server with it? The goal of this section is to answer exactly that question. As a starting point we'll take an empty embedded software application:

#include "board.h"

void main(void)
{
  /* Initialize the clock, enable peripherals and configure GPIO pins. */
  BoardInit();

  /* Enter the program's infinite loop. */  
  for(;;)
  {

  } 
}

We'll create a Modbus server step-by-step with the following properties:

  • Communication using serial communication in RTU mode:
    • Baudrate 19200 bits/second.
    • 8 data-bits
    • even parity
    • 1 stop-bit
  • Node address 10.
  • 1 coil at address 0 (element number 1), representing an LED.

Construct the transport layer object

The first step is always the construction of a transport layer object. It's the object that handles the actual transmission and reception of communication packets:

/* Construct a Modbus RTU transport layer object. */
tTbxMbTp modbusTp = TbxMbRtuCreate(10U, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
                                   TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);   

Construct the server channel object

With the transport layer object created, we continue with constructing a server channel object and attaching the transport layer object to it:

/* Construct a Modbus server object. */
tTbxMbServer modbusServer = TbxMbServerCreate(modbusTp);  

Call the task function for event processing

An event task function drives the MicroTBX-Modbus stack. We just need to continuously call it in the program's infinite superloop:

/* Continuously call the Modbus stack event task function. */
TbxMbEventTask();

Configure the callback for handling coil writes

Our example Modbus server should enable a Modbus client to change the state of an LED, whenever it receives a coil write request. For this we'll implement a callback function, with a name of our choosing, and then register this callback function for coil write requests:

tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
{
  tTbxMbServerResult result = TBX_MB_SERVER_OK;

  /* Request to write the coil at address 0? */
  if (addr == 0U)
  {
     if (value == TBX_ON)
     {
       BoardLedOn();    
     }
     else
     {
       BoardLedOff();  
     }
  }
  /* Unsupported coil address. */
  else
  {
    result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;
  }
  return result;
}

/* Set the callback for accessing the coils in the Modbus data table. */
TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);

Assembling it all together into our initial empty application results in this:

#include <microtbx.h>
#include <microtbxmodbus.h>
#include "board.h"

tTbxMbTp modbusTp;
tTbxMbServer modbusServer;

tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value);

void main(void)
{
  /* Initialize the clock, enable peripherals and configure GPIO pins. */
  BoardInit();
  /* Construct a Modbus RTU transport layer object. */
  modbusTp = TbxMbRtuCreate(10, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
                            TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);   
  /* Construct a Modbus server object. */
  modbusServer = TbxMbServerCreate(modbusTp); 
  /* Set the callback for accessing the coils in the Modbus data table. */
  TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);

  /* Enter the program's infinite loop. */  
  for(;;)
  {
    /* Continuously call the Modbus stack event task function. */
    TbxMbEventTask();
  } 
}

tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
{
  tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;

  /* Request to write the coil at address 0? */
  if (addr == 0U)
  {
     if (value == TBX_ON)
     {
       BoardLedOn();    
     }
     else
     {
       BoardLedOff();  
     }
     result = TBX_MB_SERVER_OK;      
  }
  return result;
}

And voilĂ , you now have a fully functional Modbus server. You can extend it by adding support for:

  • Writing holding registers.
  • Reading discrete inputs.
  • Reading input registers.

The process is the same: You implement the callback function and then register it with the channel using a TbxMbServerSetCallbackXxx() API function. Refer to the API reference for more details.

Using FreeRTOS instead of a superloop

The previous example assumed a traditional superloop type application. Thanks to the ever increasing processing power and available RAM and ROM memory on modern microcontrollers, the use of a real-time operating system (RTOS) is more common.

For this reason, MicroTBX-Modbus ships with an operating system abstraction layer (OSAL). In this section, we'll upgrade the previous superloop example to use FreeRTOS instead of a traditional superloop.

Select the correct OSAL source file

As a first step, re-configure your project to compile and link the correct OSAL source file:

  • Remove source/osal/tbxmb_superloop.c from your project.
  • Add source/osal/tbxmb_freertos.c to your project.

In case you use CMake to manage your project's build system, update its target_link_libraries():

  • Remove microtbx-modbus-osal-superloop.
  • Add microtbx-modbus-osal-freertos.

Create a new RTOS task for event handling

Instead of continuously calling TbxMbEventTask() in the superloop, create a new RTOS task and call TbxMbEventTask() in the task's infinite loop. You can assign it a priority of your liking that fits your application. Note that the MicroTBX-Modbus FreeRTOS OSAL source file automatically places the RTOS task in the waiting state, when no events are pending:

void AppModbusTask(void * pvParameters)
{
  /* Enter infinite task loop. */
  for (;;)
  {
    /* Continuously call the Modbus stack event task function. */
    TbxMbEventTask();
  }
}

/* Create the Modbus task. */
xTaskCreate(AppModbusTask, "ModbusTask", configMINIMAL_STACK_SIZE, NULL, 4U, NULL);

Here follows to previous example application, upgraded for FreeRTOS:

#include <microtbx.h>
#include <microtbxmodbus.h>
#include "board.h"
#include "FreeRTOS.h"
#include "task.h"

tTbxMbTp modbusTp;
tTbxMbServer modbusServer;

void AppModbusTask(void * pvParameters);
tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value);

void main(void)
{
  /* Initialize the clock, enable peripherals and configure GPIO pins. */
  BoardInit();
  /* Construct a Modbus RTU transport layer object. */
  modbusTp = TbxMbRtuCreate(10, TBX_MB_UART_PORT1, TBX_MB_UART_19200BPS,
                            TBX_MB_UART_1_STOPBITS, TBX_MB_EVEN_PARITY);   
  /* Construct a Modbus server object. */
  modbusServer = TbxMbServerCreate(modbusTp); 
  /* Set the callback for accessing the coils in the Modbus data table. */
  TbxMbServerSetCallbackWriteCoil(modbusServer, AppWriteCoil);
  /* Create the Modbus task. */
  xTaskCreate(AppModbusTask, "ModbusTask", configMINIMAL_STACK_SIZE, NULL, 4U, NULL);    
  /* Start the RTOS scheduler. Note that this function does not return. */
  vTaskStartScheduler();
}

void AppModbusTask(void * pvParameters)
{
  /* Enter infinite task loop. */
  for (;;)
  {
    /* Continuously call the Modbus stack event task function. */
    TbxMbEventTask();
  }
}

tTbxMbServerResult AppWriteCoil(tTbxMbServer channel, uint16_t addr, uint8_t value)
{
  tTbxMbServerResult result = TBX_MB_SERVER_ERR_ILLEGAL_DATA_ADDR;

  /* Request to write the coil at address 0? */
  if (addr == 0U)
  {
     if (value == TBX_ON)
     {
       BoardLedOn();    
     }
     else
     {
       BoardLedOff();  
     }
     result = TBX_MB_SERVER_OK;      
  }
  return result;
}

Next steps

After reading through this getting started section, you now have a basic understanding of how to set up a Modbus server. For ready-to-run examples, refer to the demo programs in the separate repository. It also includes example on how to set up a Modbus client, instead of a server:

For more in-depth details on the API functions offered by MicroTBX-Modbus, head over to the API reference in this user manual. When your itchy to start adding MicroTBX-Modbus to your own embedded software program, continue with the integration section of this user manual.