[TUT][SOFT] How to use MODBUS protocol on LUFA USB

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

This short tutorial describes how to configure a MODBUS server (FREEMODBUS more exactly) to run on USB instead than a standard serial port.
I suppose only a few persons will find this document useful, but why not to share nonetheless? :wink:

Note: this is not a FMB or LUFA tutorial. So I suppose you are already able to setup both.

Goal: implement a MODBUS server that can be accessed via USB
Software needed:
- FREEMODBUS (FMB) for the MODBUS server
- LUFA for the USB driver, configured as CDC device
- Part Pack from Atmel if you use the latest XMega with USB

Problem: FMB expects an ISR to signal when there is a new character in the RX buffer, It also expects another ISR that tells when the TX buffer is empty, to start sending next char.
With USB you don't have these ISRs, and you have to find a different strategy.

As FMB documentation suggests, start from the BARE implementation and change these files:
- portserial.c for serial initialisation and interface
- portevent.c for the inter-function event exchange

Step 1: set portserial.c

a) the first function to implement is

vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )

The function takes two parameters to enable/disable RX and TX respectively. FMB enables TX only when it really needs to transmit, so when the function is called to enable TX, we also "simulate" the TX buffer empty signal.

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if (xTxEnable)
  pxMBFrameCBTransmitterEmpty(  );
}

b) now implement the callback function that FMB calls to put a byte in the serial port.

BOOL xMBPortSerialPutByte( CHAR ucByte )
{
CDC_Device_SendByte(&VirtualSerial_CDC_Interface,ucByte);
xMBPortEventPost(EV_FRAME_SENT);
return TRUE;
}

Note that, after putting a byte in the USB buffer, we also send a FRAME_SENT event. This is used, again, to "simulate" the TX buffer empty signal. We will use it later.

c) finally, define the function called to get a byte from the serial port:

BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
	if (CDC_Device_BytesReceived(&VirtualSerial_CDC_Interface) > 0)
	{
		int16_t data = CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
		if (data >= 0)
	    *pucByte = (uint8_t)(data);
	  return TRUE;
	}
	return FALSE;
}

Not much to say here. Instead of reading from serial we read from USB.

Step 2: changes to portevent.c

The file defined in the BARE port is almost ok. There is only one change to do. You remember that, after sending a byte to USB, we also put a FRAME_SENT event in the FMB queue. It's now time to handle this event:

BOOL xMBPortEventGet( eMBEventType * eEvent )
{
    BOOL            xEventHappened = FALSE;

    if( xEventInQueue )
    {
        *eEvent = eQueuedEvent;
        xEventInQueue = FALSE;
        xEventHappened = TRUE;
			// add these two lines
			if (eQueuedEvent == EV_FRAME_SENT)
			  pxMBFrameCBTransmitterEmpty(  );
    }
    return xEventHappened;
}

This will retrigger another fake "TX buffer empty" event and force FMB to send a new byte, if available.

Step 3: implement USB task

You need to periodically call LUFA functions CDC_Device_USBTask() and USB_USBTask() from inside a task or a timer ISR. Short before/after these functions are invoked, check if new bytes are received and in case, inform FMB:

	if (CDC_Device_BytesReceived(&VirtualSerial_CDC_Interface) > 0)
        pxMBFrameCBByteReceived( );
    CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
    USB_USBTask();

That's all. When you call eMBInit(), you can pass any value for serial speed.

These modifications were tested with ASCII MODBUS on a MEGA16U4 and are working well. As soon as the new hardware is ready, I will test on XMEGA64A3U but no big surprises are expected.

If you find mistakes or parts that are not clear, please let me know.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Goal: implement a MODBUS server that can be accessed via USB
So how do you connect, say 32, MODBUS devices on the same USB line? :-)

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
Quote:
Goal: implement a MODBUS server that can be accessed via USB
So how do you connect, say 32, MODBUS devices on the same USB line? :-)

1) put all USB ports in parallel

2) turn power on

2) open the window so the smoke coming from your boards/PCs can go out. If you see fire, call for professional help :twisted:

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, I know this discussion has been a while. I wonder if these three parts were the only places you changed in the FreeModbus port? Did you also change other API such as eMBInit (xMBPortSerialInit)? Currently I'm trying to implement the USB-Modbus protocol on my STM32 chip. Although the mcu is different, this article is still a good starting point for me. Thanks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow, the post is already two years old! Time passes...

I am quite sure that you only need to modify those three points.
As a further suggestion, I recommend you to download the modbuspoll application (for PC), very useful to check if your porting works.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, I also used ModbusPoll to test my ModbusRTU-UART code previously and now I'm transferring to ModbusRTU-USB.

I still have some questions and hope you could help. :)

1. In the vMBPortSerialEnable(), you only consider TxEnable to simulate the TX buffer empty signal, instead of RxEnable. Shouldn't I also implement a pxMBFrameCBByteReceived to simulate RX buffer receive signal?

2. I don't fully understand the 3rd step you pointed out. So in the main function, how would the code structure look like? Before for UART it should be something like this:

int main() 
{
   eMBInit(MB_RTU, .......);
   eMBEnable();
   while(1) 
   {
      eMBPoll();
   }

}

Where did you place the pxMBFrameCBByteReceived() and USB stack functions?

I'm still trying to figure out how to implement the USB port on my STM32L mcu. Thanks a lot. :D:D:D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

1. No, the function pxMBFrameCBByteReceived() is already implemented. In the mb.c file you can find this assignment:

pxMBFrameCBByteReceived = xMBRTUReceiveFSM;

where xMBRTUReceiveFSM() is implemented in mbrtu.c

So you don't need to deal with this function.

2. I no longer use LUFA. I switched to the Atmel ASF implementation, but most concepts should be similar.
ASF allows me to define a callback function invoked when the USB receives something.

void uart_rx_notify(void)
{
	while (udi_cdc_is_rx_ready() )
		pxMBFrameCBByteReceived();
}

Then in the main()


// initialise MODBUS interface
// (note: baudrate is not influent here)
eMBInit(MB_RTU,1,1,115200,MB_PAR_NONE);
eMBEnable();

// Enable USB before entering the main loop
udc_start();

while (true)
{
	eMBPoll();
}

That should be all! :-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your reply!

I was working on other stuff so forgot to follow up your message. After several try, somehow I could not get it work still.

Hope you could clarify my confusion if you still remember it. :P

In my understanding, the xMBPortSerialGetByte and xMBPortSerialPutByte functions only process ONE byte at each function call.

So I have another question here. In your USB stack, does the RX function (i.e. CDC_Device_ReceiveByte) takes only ONE byte at each callback function?

I found that in STM32 USB API, the CDC_Receive_DATA() reads an array of data in the RX callback function, which is different from yours.

For example, if ModbusPoll send a "01 04 00 00 00 07 B1 C8", the xMBPortSerialGetByte function is supposed to store 01 into a ucbyte, and then 04, and then 00, 00, 00, ..., but my CDC function store all of them into a buffer, which makes me unable to assign the value directly.

I suspect it might be because of this problem, but I'd like to make sure if I understand it right.

Thanks a lot!!! :D

P.S. It's really a nightmare that I couldn't print out variable values while testing the modbus-usb stack in ModbusPoll software. If you could suggest any debugging method, that would be awesome! :P

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

yushanh wrote:

So I have another question here. In your USB stack, does the RX function (i.e. CDC_Device_ReceiveByte) takes only ONE byte at each callback function?

I found that in STM32 USB API, the CDC_Receive_DATA() reads an array of data in the RX callback function, which is different from yours.

My USB stack has functions to read both one byte and an array of bytes. Indeed in xMBPortSerialGetByte I call

  *pucByte = (uint8_t)(udi_cdc_getc());

If your stack only returns arrays of bytes, then you should modify xMBPortSerialGetByte to read from an array instead than from the USB. In order to ensure that all received bytes are processed, simply call pxMBFrameCBByteReceived() as many times as the bytes to read.

Quote:
P.S. It's really a nightmare that I couldn't print out variable values while testing the modbus-usb stack in ModbusPoll software.

My only suggestion could be to use a different port, e.g. a serial port, if you have one available on your hardware