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.
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
Imagem de destaque: https://wiki.teltonika.lt/wiki/images/thumb/4/4d/Configuration_examples_modbus_logo.png/800px-Configuration_examples_modbus_logo.png





