This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/TM4C123GH6PM: MODBUS RTU implementation using FreeMODBUS on TM4C123G

Part Number: TM4C123GH6PM

Tool/software: Code Composer Studio

Hello everyone,

I'm trying to implement the Modbus protocol on the TM4C123G using the FreeMODBUS Stack. But the problem seems like happen on the hardware setup, here are the codes that I use

For the port.h:

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

#ifndef _PORT_H
#define _PORT_H

#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_sysctl.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"


#define	INLINE                      inline
#define PR_BEGIN_EXTERN_C           extern "C" {
#define	PR_END_EXTERN_C             }

#define ENTER_CRITICAL_SECTION( )   IntMasterDisable();
#define EXIT_CRITICAL_SECTION( )    IntMasterEnable();

typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char CHAR;

typedef uint16_t USHORT;
typedef int16_t SHORT;

typedef uint32_t ULONG;
typedef int32_t LONG;

#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

#endif

For the portserial.h

/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter <wolti@sil.at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* File: $Id$
*/

#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"
#include "driverlib/pin_map.h"
#include "driverlib/uart.h"

#include "port.h"

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

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

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if( xRxEnable )
{
UARTIntEnable(UART0_BASE, UART_INT_RX|UART_INT_RT);
UARTIntRegister(UART0_BASE,&prvvUARTRxISR);
}
else
UARTIntDisable(UART0_BASE,UART_INT_RX);

if( xTxEnable )
{
UARTIntEnable(UART0_BASE, UART_INT_TX);
UARTIntRegister(UART0_BASE,&prvvUARTTxReadyISR);
}
else
UARTIntDisable(UART0_BASE,UART_INT_TX);
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
(void)ucPORT;
uint32_t Parity,Bits,StopBits;
//
// Enable the GPIO Peripheral used by the UART.
//
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
//
// Enable UART0
//
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

//
// Configure GPIO Pins for UART mode.
//
GPIOPinConfigure(GPIO_PA0_U0RX);
GPIOPinConfigure(GPIO_PA1_U0TX);
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

//
// Select the length and Parity of UART
//

switch( eParity )
{
case MB_PAR_EVEN:
Parity = UART_CONFIG_PAR_EVEN;
StopBits = UART_CONFIG_STOP_ONE;
break;
case MB_PAR_ODD:
Parity = UART_CONFIG_PAR_ODD;
StopBits = UART_CONFIG_STOP_ONE;
break;
case MB_PAR_NONE:
Parity = UART_CONFIG_PAR_NONE;
StopBits = UART_CONFIG_STOP_TWO;//Ensure the 11 bits of RTU character format

break;
}

switch( ucDataBits )
{
case 8:
Bits = UART_CONFIG_WLEN_8;
break;
case 7:
Bits = UART_CONFIG_WLEN_7;
break;
}

//
// Initialize the UART for console I/O.
//
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), ulBaudRate,(Bits | StopBits | Parity));
//Disable the fifo in order to ensure the interrupt is only for a change in RX
UARTFIFODisable(UART0_BASE);
//
// Enable UART & Interrupts
//
UARTIntEnable(UART0_BASE, UART_INT_RX|UART_INT_RT);
UARTIntRegister(UART0_BASE,&prvvUARTRxISR);
UARTEnable(UART0_BASE);


return FALSE;
}

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. */
UARTCharPut(UART0_BASE, ucByte);
if(UARTCharsAvail(UART0_BASE))
{
UARTCharPut(UART0_BASE, UARTCharGet(UART0_BASE));
}
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.
*/
UARTIntClear(UART0_BASE,UARTIntStatus(UART0_BASE,true));
while (UARTCharsAvail(UART0_BASE))
{
*pucByte = UARTCharGetNonBlocking(UART0_BASE);
}
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( );
}

For porttimer.h

/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter <wolti@sil.at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* File: $Id$
*/
#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_sysctl.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"

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

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

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
uint32_t a;
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
TimerConfigure(TIMER0_BASE, TIMER_CFG_ONE_SHOT);
a = usTim1Timerout50us*SysCtlClockGet()/20000-1;
TimerLoadSet(TIMER0_BASE, TIMER_A, a);
TimerIntRegister(TIMER0_BASE, TIMER_A, &prvvTIMERExpiredISR);
IntEnable(INT_TIMER0A);
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
IntMasterEnable();
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 4);

return FALSE;
}


inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */

TimerEnable(TIMER0_BASE, TIMER_A);

}

inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0);
}

/* 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( void )
{
( void )pxMBPortCBTimerExpired( );
}

For the main.c 

/*
 * FreeModbus Libary: BARE Demo Application
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 3000
#define REG_INPUT_NREGS 4

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

/* ----------------------- Start implementation -----------------------------*/
void Configuration()
{
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);// 16 MHz clk
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_3 | GPIO_PIN_2 );
    SysCtlDelay(100000);
    GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3 | GPIO_PIN_2  , 0);
}

int
main( void )
{
    eMBErrorCode    eStatus;

    Configuration();

    eStatus = eMBInit( MB_RTU, 0x0A, 0, 9600, MB_PAR_EVEN );

    /* Enable the Modbus Protocol Stack. */
    eStatus = eMBEnable();
//    eMBClose();

    for( ;; )
    {
        ( void )eMBPoll(  );

        /* Here we simply count the number of poll cycles. */
        usRegInputBuf[0]++;
        usRegInputBuf[1] = 5;
        usRegInputBuf[2] = 6;
        usRegInputBuf[3] = 33;

    }


}

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
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

After testing, I found out that the microcontroller still receive the data but don't process it. It may because of the timer setup but I'm not sure because there are no output to observe. I try using GPIO led to keep track on the data process but when it comes to the timer, the interrupt condition may block it.

Please help me with this. I did look up for Modbus implementation on TM4C but there is no solution that help, so I don't know what to do next.

  • Hi,
    You are using third party code that we can't support. I will suggest you put a breakpoint in your timer interrupt ISR and debug what is going on. Do you see the Timer interrupt occurs?
  • Thank you for your reply.  

    I put the breakpoint in the interrupt ISR but didn't see Timer interrupt occurs. At first, I thought it may because I didn't restart the timer properly. I try to do by using this 

    BOOL
    xMBPortTimersInit( USHORT usTim1Timerout50us )
    {
        SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
        TimerConfigure(TIMER0_BASE, TIMER_CFG_ONE_SHOT);
        a = ((usTim1Timerout50us*SysCtlClockGet())/20000)-1;
        TimerLoadSet(TIMER0_BASE, TIMER_A, a);
        TimerIntRegister(TIMER0_BASE, TIMER_A, &prvvTIMERExpiredISR);
        IntEnable(INT_TIMER0A);
        TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
        TimerEnable(TIMER0_BASE, TIMER_A);
    
        return FALSE;
    }
    
    
    inline void
    vMBPortTimersEnable( )
    {
        /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
        TimerLoadSet(TIMER0_BASE, TIMER_A, a);
        TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
        TimerEnable(TIMER0_BASE, TIMER_A);
    }
    
    inline void
    vMBPortTimersDisable( )
    {
        /* Disable any pending timers. */
        TimerDisable(TIMER0_BASE,TIMER_A);
        TimerIntDisable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
        TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    }
    
    /* 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( void )
    {
        TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
        ( void )pxMBPortCBTimerExpired( );
    }

    But it still doesn't work. I don't know why the interrupt doesn't occur meanwhile it still occurs when I run a separate program (I mean a program for Timer interrupt, not the one above). Please help me out with this. Thank you!

  • Try TimerIntRegister(TIMER0_BASE, TIMER_A, prvvTIMERExpiredISR), not TimerIntRegister(TIMER0_BASE, TIMER_A, &prvvTIMERExpiredISR).
  • I did try it but the break point only works when I put it in the vMBPortTimersEnable( ), and when it doesn't work when I put break point in the ISR (I only put 1 break point at a time to avoid conflict).

    And about the code, can you tell me the different between prvvTIMERExpiredISR and &prvvTIMERExpiredISR? Because I thought "&A" is reference of "A", which suits for the function extern void TimerIntRegister(uint32_t ui32Base, uint32_t ui32Timer,void (*pfnHandler)(void)) - has a pointer in pfnHandler.

    Regards,
  • I'm not clear what you meant. Are you saying if you put a breakpoint in vMBPortTimersEnable then you will see the interrupt happen for the timer? When the interrupt does not come, then check if the timer interrupt status register, i.e. GPTMRIS and GPTMMIS registers and see if any flags set. Also check GPTMIMR to see if you have enable interrupts.

    The prvvTIMERExpiredISR is already a function pointer. You don't need the &.
  • About the breakpoint, when I put the breakpoint in vMBPortTimersEnable, the process was stopped at the TimerEnable function. But when I put the breakpoint in the timer interrupt function, the process didn't stop. So my assumption is the function hasn't been called in the process. 

    And about checking status register, I haven't done anything like that before. So I will try and response you ASAP. 

    Thank you for your advises.

  • Dear Charles,

    I found out that the interrupt haven't been called due to some unsuitable functions. So I decided to rewrite it all myself and the program works as expected (thanks for your advises).

    But currently, I have some problems with the addresses of coils, registers, etc. The Modbus protocols require reading or reading/writing at some registers.

    For example:

    • Coils (R/W): 0x00000
    • Discrete Input  (RO): 0x10000
    • Input Register (RO): 0x30000
    • Holding Register (R/W): 0x40000

    I found out that the address of data can be assigned using HWREG or directly using pointer. But I'm not sure that if I do that, will the program be conflict or not. I've read that the data in that range is for the Flash ROM, but I cannot find more specific memory map. I was worried if TM4C123GH6PM uses the same kind of memory map or not; because at first, when I debugged the program, I found out that the Discrete Inputs (Read Only, at 0x10000 due to the standard Modbus guide) are all 1. (The picture below)

    I tried to look up for the memory setup for other MCUs and found out that, for the STM32 and Arduino, they have Register Bank to put the data to the desired addresses, but I haven't found any similar function in TM4C123GH6PM. Can you help me out with this? Thank you.

    Regards,

  • Hi,
    I think you are confused between the ModBUS addresses with the internal flash memory address. The Modbus slave addresses are for the Modbus device, not for the internal flash memory. If you are implementing the ModBUS based on RS232 then you need to use the UART module. Please refer to the UART module in the datasheet for the registers.

    I think you may find the below post helpful.
    e2e.ti.com/.../1466567
  • I'm sorry that I haven't make it clear from the beginning, I'm trying to imply Modbus protocol on TM4C123GH6PM as a Slave and using my C# program on my laptop as a master. And I doing it on RS485, which I have used a converter from RS232 and UART module.
    So now that I want to program TM4C123GH6PM as a slave, will the memory (not the address) be saved in internal flash? Or if not, where will it be stored?
  • Why are you using the MCU as a Modbus slave? The TM4C123 MCU does not natively support MODBUS. You will need to somehow model it as a MODBUS device. You will need to map the ModBUS addresses to RAM area.
  • So you mean I need to put the Modbus data into RAM area by calling arrays in the main program right? I read that the RAM address is from 0x20000000, so when I debug, the Memory Browser looks like this (The picture below), am I right?

    Regards,

  • Hi,
    Yes, your understanding is correct. If you get it to work, would you please share your project with the community as I think many people looking for the same solution will benefit from you.
  • I'd love to share my project, but right now I only write few functions (not fully) for the Modbus protocol due to my application and my codes are little bit complicated. However, I considered to rewrite a full program right after I finish my current research.
    I appreciate your help Charles, wish you all the bests!
    Regards,