FreeMODBUS - Apresentação & Port

Aqui no portal Embarcados existem excelentes artigos sobre o protocolo de comunicação MODBUS, tais como os artigos do Carlos Márcio Freitas, que realizam a apresentação do protocolo de comunicação e o uso de ferramentas de diagnóstico e simuladores, vide “Protocolo Modbus: Fundamentos e Aplicações” e “Protocolo Modbus: Exemplos e Simuladores”.

 

FreeMODBUS

 

O FreeMODBUS é uma implementação gratuita e de código aberto do popular protocolo MODBUS, especialmente dedicado para sistemas embarcados. O MODBUS é um protocolo de rede popular no ambiente de produção industrial. Uma pilha de comunicação MODBUS requer duas camadas: O Protocolo de Aplicação MODBUS que define o modelo de dados e funções e uma camada de Rede. 

 

O FreeMODBUS suporta os modos de transmissão RTU/ASCII definidos na especificação de linha serial Modbus 1.0, também suporta MODBUS TCP definido no Modbus Messaging no TCP / IP. Ele está licenciado sob a licença BSD que permite seu uso em ambientes comerciais. A seguir temos as funções MODBUS que são atualmente suportadas:

  • Ler registro de entrada (0x04)
  • Read Holding Registers (0x03)
  • Escreva o registro único (0x06)
  • Gravar vários registros (0x10)
  • Ler / gravar vários registros (0x17)
  • Ler bobinas (0x01)
  • Escrever bobina única (0x05)
  • Escreva várias bobinas (0x0F)
  • Ler entradas discretas (0x02)
  • Informar ID do escravo (0x11)

 

O FreeMODBUS possui diversos “ports” e exemplos para diferentes microcontroladores, são eles:

  • Ports ASCII/RTU
    • FreeRTOS / STR71X
    • AVR ATMega8/16/32/128/168/169
    • Freescale MCF5235
    • TI MSP430
    • LPC214X
    • Z8 Encore! / Z8F6422
    • Win32
    • Linux / Cygwin
    • FreeRTOS / AT91SAM7X
    • HCS08
    • Atmel SAM3S
  • Ports TCP
    • Win32
    • lwIP/MCF5235
    • lwIP/STR71X

 

Requisitos de Hardware e Software para “Port”

 

Como visto no parágrafo anterior, no repositório do FreeMODBUS existe poucos microcontroladores suportados. Inclusive a família de microcontroladores Kinetis ARM Cortex-M0 que venho trabalhando. O FreeMODBUS possui em sua documentação explicando como deve ser feito o “port”. Antes de detalhar como realizar o “port” para um novo microcontrolador, é importante saber os seus requisitos. 

 

  • Aproximadamente 5 - 10 Kbytes de memória FLASH e 300 bytes de memória RAM.

Nota: O tamanho real depende das funções MODBUS configuradas. A função pode ser ativada / desativada usando o arquivo de cabeçalho mbconfig.h .

  • Uma UART com suporte para interrupção. Para conformidade padrão, 2 bits de parada são necessários para nenhum modo de paridade. 7 bits de dados são necessários para MODBUS ASCII
  • Um TIMER com uma resolução de pelo menos 750 microssegundos.
  • O FreeMODBUS permite trabalhar com ou sem um sistema operacional. 

 

Para os “ports” que venham ter um RTOS, se o RTOS contenha ou possua o recurso de Filas de eventos, tal recurso pode ser utilizado para evitar a espera ocupada na tarefa do MODBUS. 

 

Para sistemas rodando sem RTOS, uma abordagem baseada em pesquisa em combinação com uma variável global pode ser usada.

 

 

Realizando o “Port”

 

O “port” realizado foi o padrão de transmissão RTU para a Freedom Board KL25Z. O microcontrolador alvo presente na placa atende a todos requisitos descritos anteriormente.

 

Da maneira que organizando os arquivos dos projetos é bem simples realizar o “port” do FreeMODBUS, o mesmo contém camada de baixo nível onde é feito o acesso aos periféricos do microcontrolador (UART e Timer). Essa camada está presente no diretório port. Esse diretório possui quatro arquivos; port.h, portevent.c, portserial.c e porttimer.c

 

O arquivo port.h é onde contém as macros de uso geral/global para os arquivos que estão presente no diretório port.

 

Para “port” sem utilizar um sistema operacional não é necessário nenhuma alterações no arquivo portevent.c .

 

O arquivo portserial.c onde serão inseridos as funções de acesso a UART.

 

O arquivo porttimer.c é onde é configurado a funções de Timer do microcontrolador.

 

Diretório FreeMODBUS

 

O “port” feito para a Freedom Board KL25Z, foi feitos utilizando a Bibliotecas de Software para FRDM-KL25Z que venho apresentado aqui no Embarcados.

 

Para a funcionalidade de Timer, “port” implementado utiliza o periférico PIT - Periodic Interrupt Timer para FRDM-KL25Z, que possui um artigo detalhando o seu funcionamento. 

 

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "../include/mb.h"
#include "../include/mbport.h"

/* ----------------------- Drivers includes ----------------------------------*/
#include "../Library-FRDM-KL25Z/externs.h"

/*-------- Macro to convert a microsecond period to raw count value ---------*/
#define USEC_TO_COUNT(us, clockFreqInHz)(uint32_t)((uint64_t)us * clockFreqInHz / 1000000U)
#define CHANNEL_PIT_MODBUS	PIT_CH_0

/* ----------------------- static variable ---------------------------------*/
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( uint8_t ch );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    uint32_t time = 0;
    time = (uint32_t)(USEC_TO_COUNT((usTim1Timerout50us*49/*T:50us */),SystemCoreClock) );
    pit_Init( time, CHANNEL_PIT_MODBUS );
    pit_Add_Callback(prvvTIMERExpiredISR);
    return TRUE;
}

inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	//downcounter = timeout;
	pit_Start( CHANNEL_PIT_MODBUS );
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	pit_Stop( CHANNEL_PIT_MODBUS );
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
static void prvvTIMERExpiredISR( uint8_t ch )
{
	if(ch == CHANNEL_PIT_MODBUS)
	{
		( void )pxMBPortCBTimerExpired(  );
	}
}

 

A implementação da Serial é utilizado o UART1 - Universal Asynchronous Receiver / Transmitter.

 

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ---------------------------------*/
#include "../include/mb.h"
#include "../include/mbport.h"

/* ----------------------- Drivers includes ---------------------------------*/
#include "../Library-FRDM-KL25Z/externs.h"

/* -------------------------- Macros --------------------------------------- */
#define MDSERIAL 	UART1

/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );
static void prvvMBPortSerialISR( void );

/* ----------------------- Static variables -------------------------------- */
BOOL bTXEnabled;
BOOL bRXEnabled;

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /*
     * If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
    if( xRxEnable )
    {
        bRXEnabled = TRUE;
        MDSERIAL->C2 |= UART_C2_RIE_MASK;
    }
    else
    {
        bRXEnabled = FALSE;
        MDSERIAL->C2 &= ~UART_C2_RIE_MASK;
    }
    if( xTxEnable )
    {
        bTXEnabled = TRUE;
        MDSERIAL->C2 |= UART_C2_TIE_MASK;
    }
    else
    {
        bTXEnabled = FALSE;
        MDSERIAL->C2 &= ~UART_C2_TIE_MASK;
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	uart_Init(MDSERIAL, 2, (uint32_t)ulBaudRate);
	uart_add_callback(MDSERIAL,prvvMBPortSerialISR);
	uart_enable_irq(MDSERIAL);
    return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /*
     * Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called.
     */
	while(!(MDSERIAL->S1 & UART_S1_TDRE_MASK));
	MDSERIAL->D = (uint8_t)ucByte;
	MDSERIAL->C2 |= UART_C2_TIE_MASK ;
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /*
     * Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
	while(!(MDSERIAL->S1 & UART0_S1_TDRE_MASK));
	*pucByte = MDSERIAL->D;
	MDSERIAL->C2 |= UART_C2_RIE_MASK;
    return TRUE;
}

/*
 * Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call 
 * xMBPortSerialPutByte( ) to send the character.
 */
static void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/*
 * Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

/*
 * IRQ Uart
 */
void prvvMBPortSerialISR( void )
{
    if( bTXEnabled && ( MDSERIAL->S1 & UART_S1_TC_MASK ) )
    {
        ( void )pxMBFrameCBTransmitterEmpty(  );
    }
    if( bRXEnabled && ( MDSERIAL->S1 & UART_S1_RDRF_MASK ) )
    {
        ( void )pxMBFrameCBByteReceived(  );
    }
}

 

A aplicação de demonstração é baseada no exemplo de implementação “bare metal” que está presente do diretório do projeto (/demo/BARE/demo.c).

 

#include "MKL25Z4.h"
#include "../Library-FRDM-KL25Z/externs.h"
#include "../Middlewares/modbus/include/mb.h"
#include "../Middlewares/modbus/include/mbport.h"
#include "../Middlewares/modbus/include/mbutils.h"

#define REG_INPUT_START 		1000
#define REG_INPUT_NREGS 		8
#define REG_HOLDING_START           	2000
#define REG_HOLDING_NREGS           	50
#define REG_COILS_START			0
#define REG_COILS_NREGS			50
#define REG_DISCRETE_COILS_START	100
#define REG_DISCRETE_NREGS		50

static UCHAR usRegDiscreteCoilsStart 	= REG_DISCRETE_COILS_START;
static UCHAR usRegCoilsStart 		= REG_COILS_START;
static USHORT usRegHoldingStart 	= REG_HOLDING_START;
static USHORT usRegInputStart 		= REG_INPUT_START;

static UCHAR usRegDiscreteCoilsBuf[REG_DISCRETE_NREGS];
static UCHAR usRegCoilsBuf[REG_COILS_NREGS];
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
static USHORT usRegInputBuf[REG_INPUT_NREGS];

int main(void)
{
     // inicializa modbus
     eMBInit( MB_RTU, 1, 0, 19200, MB_PAR_NONE );
     eMBEnable();

     // Inicializa PTB18 - LED Vermelho
     gpio_Init(GPIOB,18,OUTPUT,NO_PULL_RESISTOR);

     for (;;)
    {
	eMBPoll();
        gpio_Toggle(GPIOB,18);
    }
    /* Never leave main */
    return 0;
}
/****************************************************************************************
*
*****************************************************************************************/
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;
}
/****************************************************************************************
*
*****************************************************************************************/
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    //return MB_ENOREG;
	eMBErrorCode eStatus = MB_ENOERR;
	int iRegIndex;

	if((usAddress >= REG_HOLDING_START) && (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS))
	{
		iRegIndex = (int)(usAddress - usRegHoldingStart);
		switch(eMode)
		{
		/* Pass current register values to the protocol stack. */
		case MB_REG_READ:
			while(usNRegs > 0)
			{
				*pucRegBuffer++ = (unsigned char)(usRegHoldingBuf[iRegIndex] >> 8);
				*pucRegBuffer++ = (unsigned char)(usRegHoldingBuf[iRegIndex] & 0xFF);
				iRegIndex++;
				usNRegs--;
			}
			break;

			/* Update current register values with new values from the
			 * protocol stack. */
		case MB_REG_WRITE:
			while(usNRegs > 0)
			{
				usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
				usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
				iRegIndex++;
				usNRegs--;
			}
		}
	}
	else
	{
		eStatus = MB_ENOREG;
	}
	return eStatus;
}
/****************************************************************************************
*
*****************************************************************************************/
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
   // return MB_ENOREG;
    eMBErrorCode eStatus = MB_ENOERR;
    int iRegIndex;

    if((usAddress >= REG_COILS_START) && (usAddress + usNCoils <= REG_COILS_START + REG_COILS_NREGS))
    {
        iRegIndex = (int)(usAddress - usRegCoilsStart);

        switch(eMode)
        {
            case MB_REG_READ:
            {
                while(usNCoils > 0)
                {
                    UCHAR ucResult = xMBUtilGetBits(usRegCoilsBuf, iRegIndex, 1);
                    xMBUtilSetBits(pucRegBuffer, iRegIndex - (usAddress - usRegCoilsStart), 1, ucResult);
                    iRegIndex++;
                    usNCoils--;
                }
                break;
            }
            case MB_REG_WRITE:
            {
                while ( usNCoils > 0 )
                {
                    UCHAR ucResult = xMBUtilGetBits(pucRegBuffer, iRegIndex - (usAddress - usRegCoilsStart), 1);
                    xMBUtilSetBits(usRegCoilsBuf, iRegIndex, 1, ucResult );
                    iRegIndex++;
                    usNCoils--;
                }
                break;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}
/****************************************************************************************
*
*****************************************************************************************/
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    //return MB_ENOREG;
	eMBErrorCode eStatus = MB_ENOERR;
	int  iRegIndex;

	if((usAddress >= REG_DISCRETE_COILS_START) && (usAddress + usNDiscrete <= REG_DISCRETE_COILS_START + REG_DISCRETE_NREGS))
	{
		iRegIndex = (int)(usAddress - usRegDiscreteCoilsStart);
		while(usNDiscrete > 0)
		{
			UCHAR ucResult = xMBUtilGetBits(usRegDiscreteCoilsBuf, iRegIndex, 1);
			xMBUtilSetBits(pucRegBuffer, iRegIndex - (usAddress - usRegDiscreteCoilsStart), 1, ucResult);
			iRegIndex++;
			usNDiscrete--;
		}
	}
	else
	{
		eStatus = MB_ENOREG;
	}
	return eStatus;
}
/****************************************************************************************
*
*****************************************************************************************/

 

 

Conclusão 

 

O FreeMODBUS é uma ótima alternativa de implementação para o MODBUS para ser empregado em sistemas embarcados. O mesmo oferece suporte aos modos de transmissão RTU, ASCII e TCP / IP.

 

O “port” do FreeMODBUS implementado para a Freedom Board KL25Z está no GitHub.

 

O que você achou? Você trabalha ou já trabalhou com FreeMODBUS? Deixe o seu comentário a abaixo.

 

 

Referência 

 

https://www.embedded-solutions.at/en/freemodbus/ 

https://github.com/evandro-teixeira/FreeModbus_KL25Z 

https://github.com/nucleron/freemodbus-v1.5.0

https://www.embedded-solutions.at/en/freemodbus/api-documentation/ 

Imagem de destaque: https://wiki.teltonika.lt/wiki/images/thumb/4/4d/Configuration_examples_modbus_logo.png/800px-Configuration_examples_modbus_logo.png

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Evandro Teixeira
Desenvolvedor de Sistemas Embarcados. Sou formado Técnico em Instrumentação e Automação Industrial/Mecatrônica pelo Colégio Salesiano Dom Bosco de Americana-SP, cursei o Engenharia Elétrica com Ênfase em Eletrônica pela UNISAL Centro Universitário Salesiano de São Paulo e atualmente estou cursando Superior de Tecnologia em Análise e Desenvolvimento de Sistemas pela UNIP Universidade Paulista.

Deixe um comentário

avatar
 
  Notificações  
Notificar