AN0502: Connecting your GUI as a Modbus Slave

Tutorial

Required Reading

Before proceeding with this applications note, ensure you have reviewed the following: For more information on Modbus, consult this list of resources.

Binding the Protocol to the Hardware

The first step in connecting as a Modbus slave device is to create a link node in the Resources area. In SHIPTide, go to the Resources area and right click on the “links” section and add a new link node. This node will bind the Modbus slave protocol to a physical communications channel, so set up this new link node with properties like this:
Creating the new Modbus Slave link
Each channel in a platform may only be bound once to a single protocol. For example, UART0 can only be bound to a single protocol (e.g. MODBUS_SLAVE_RTU).

Creating One or More Virtual Slave Devices

Within a given link, there may be one or more linkset nodes. Each linkset encompasses all the traffic between two endpoints in a link. On the slave side of master/slave protocols (for example the MODBUS_SLAVE_RTU), the GUI may be responding as one or more Modbus slaves. Even though the protocol is running on a single physical communications channel (e.g. UART0), each linkset within the link can act as one of a number of independent virtual slaves. Adding two linksets to our prior example might look like this:
Example link with two linksets
In this example GUI, our SIM is acting as a Modbus slave attached to UART0, potentially one of many other Modbus slave devices on the physical network. This physical network (aka channel) could be, for instance, a multi-drop RS485 network with many slaves of which our SIM is only one. Our GUI, because it has the two linksets at ID #1 and #13 created, will respond as if it were two of those slaves on the network.

Adding Shared Link Variables

The SHIP GUI communicates with a remote network device using the concept of link variables. These are variables who’s values are shared, nearly automatically, across the communications link. One party must own the actual true value of the variable, other parties on the network merely need to get copies of the latest value in order to display, react, control, or otherwise act on that value. For example, if a device on a network has a motor RPM sensor, that device “owns” that value – perhaps represented as a 16-bit signed (for motor direction) RPM value. Other devices on the network that need to know that motor’s RPM need to somehow get access to that devices “RPM variable” periodically. A SHIP GUI, for example, may need to update a value on the screen showing the motor RPM every second or even more frequently. These shared network variables are represented in your SHIP GUI each as linkvar nodes describing these shared network variables within a given linkset. The linkvar properties available depend on the protocol selected. The following are normal properties of a linkvar:
Property Name Description
name A unique variable name within the linkset
datatype The data type of this link variable; may be limited depending on the protocols
address Most protocols require each variable to have a unique address/location/id number assigned
direction Most protocols need to understand if this is an input or output variable
enabled Defaults to true, but can be set false in SHIPTide. Only enabled variables participate in polling (if applicable).
The direction can be a bit confusing: it is always with respect to SHIPEngine and the GUI. So an output direction means the data is supplied by SHIPEngine to the device across the network on the remote end of the linkset, regardless of whether the linkset is a master or slave in a master/slave environment. Similarly, an input direction means that the data is coming into the variable from the remote end of the linkset. Remember that “in” and “out” are with respect to your GUI in SHIPEngine. The datatypes are protocol dependent, and, in the Modbus case, may be limited to the basic Modbus data types of Boolean and Short. Expanding the example above with four linkvars looks like this:
Example linkset with four link variables (linkvars)
Our example here is, perhaps, a hypothetical GUI controlling a pump. Our GUI (in a Sail script) could turn on the pump by setting the pumpOnRequest output Boolean linkvar to “true”. Since Modbus masters poll their slaves, the next time the master polls slave #1 address 0x4000 (“pumpOnRequest”, a Boolean), it will read a “true” at that shared variable location, indicating to the master (the pump) to turn on. It may also poll slave #1 address 0x2000 (“pumpRPMRequest”, a Short) to determine the requested RPM before turning the pump on. It may continue to poll these two locations to watch for a request to stop the pump or change the RPM. Also, as the pump turns on and off, and its actual RPM changes, it may send those values to slave#1 address 0x4001 (“pumpOn”, a Boolean) and 0x2002 (“pumpRPM”, a Short) respectively. Note that just because the GUI “requests” that a remote device performs some action does not mean the remote device actually does it. The GUI (in a Sail script) might request the pump turn on, but it may not happen for whatever reason (the pump is overheated, for example). Timeouts and other mechanisms can be done in the GUI to watch for these conditions. A good practice is to have visual indicators on the GUI reflect the actual remote state, rather than the requested state. For example, an RPM reading in the GUI should reflect the value of “pumpRPM”, not “pumpRPMRequest”.
Categories

AN0500: Using Modbus in SHIP – A Step-by-Step Tutorial

Tutorial

First Steps: SHIP Communications Concept

You’ll want to review the Communications in SHIP Overview first to get acquainted with the how communications work via link, linksets, and linkvars within SHIPTide projects. These same concepts are used for the various protocols supported in SHIP, including Modbus.

You’ll also want to review the Modbus protocol itself and some more specifics about how it’s used in SHIP.

Function Codes

As described in the Modbus protocol, each “command” issued by the master to the slave (and each response from the slave) has a command (or “function”) code: a single byte that uniquely identifies the command (or response to the command) type.

The function code used for messages by SHIPEngine depends on the configuration of each individual link and how its linkvar’s are setup. Here are two tables showing how the datatype/direction used when defining a linkvar maps to the Modbus protocol data concepts and function codes used depending on if the SIM is acting as a master or slave.

Standard Function Codes

When your GUI configures a link as a Modbus Master link (or as a Slave and receiving Master commands), the following standard Master function codes are used by the underlying communications engine in SHIPEngine to issue (or recognize) Master commands:

SHIP Modbus Master Used Function Code Table
SHIP DatatypeDirectionReadRead MultipleWriteWrite MultipleModbus Type
BooleanInput0x02N/AN/AN/ADiscrete Input
BooleanOutput0x01N/A0x05N/ACoil
ShortInput0x04N/AN/AN/AInput Register
ShortOutput0x03N/A0x06N/AHolding Register

When your GUI configures a link as a Modbus Slave, (or as a Master and expecting a Slave response), the following standard function codes are used by the underlying communications engine in SHIPEngine to issue (or recognize) Slave commands:

SHIP Modbus Slave Response Function Code Table
SHIP DatatypeDirectionReadRead MultipleWriteWrite MultipleModbus Type
BooleanInput0x020x02N/AN/ADiscrete Input(s)
BooleanOutput0x010x010x050x0FCoil(s)
ShortInput0x040x04N/AN/AInput Register(s)
ShortOutput0x030x030x060x10Holding Register(s)

Extended Function Codes

While the above only deals with standard Modbus datatypes, SHIP also supports the transfer of Integer and Float types via both (a) a standard approach using the write/read multiple functions and (b) the Daniel/Enron modbus extensions approach using those functions in a different way. For more information, see:

SHIP also support String and even generic Buffer transfers via custom function codes and payload formats

 

Setting up the SIM as a Modbus Master or Slave

On the GUI side, you set a Modbus link in SHIPTide, the GUI Development tool.

To create a Modbus link you create a link node in the resources area and “bind” that link to a communications port (e.g. UART0 or USB_CDC). The link node also has all the properties to configure the Modbus protocol specifics, including choosing Master or Slave mode, ASCII or RTU, and which (if any) extensions are enabled.

There are two application notes that deal specifically with the basics of setting up and using the SIM as a Modbus Master or Slave:

 

Example Projects

There are two example projects in the .zip file attached to this Application Note: one for ASCII Master and one for ASCII slave. Download the .zip and unpack it; install the project of your choice to a directory on your PC. Launch SHIPTide and open the project.

Defaults in the Examples

The SubProtocol: ASCII

The projects come configured for Modbus ASCII. It is easy to modify them for RTU mode by changing the link node property from ASCII to RTU in SHIPTide, but ASCII mode can be very helpful in debugging. It is also better “framed” than RTU mode, as every ASCII message starts with a “:” and synchronization is easier.

The Comms Link: USB CDC

The example projects are also set up to use USB CDC as the communications link, making it easy to connect the SIM to a PC and (using a terminal program) see the traffic coming out of the SIM. You can even use the Modbus simulators such as at [1] for standard mode commands to simulate the device side on your PC. You can easily change the binding of the link to (say) a UART by changing the link node properties.

The Platform

Check the platform the example project is configured for using the Project->Change Platform/Variant menu and set the example to the platform you are using.

The Projects in Detail

The ASCII Master demo has 4 buttons to interact with. The buttons and their descriptions are listed below. • “SEND COIL” : Sends out a Modbus RTU set coil packet to slave ID 0x01 at address 0x0003. Value sent flips from 0 to 1 repeatedly. • “SEND HOLDING REGISTER” : Sends out a Modbus RTU set holding register packet to slave ID 0x01 at address 0x0000. Value sent increments with each press. • “MANUAL SLAVE POLL” : Triggers a single polling sequence on addresses 0x0000, 0x0001, 0x0003, 0x0004 at slave ID 0x01. • “AUTO POLLING ENABLED”/ “AUTO POLLING DISABLED” : Enables auto polling of addresses 0x0000, 0x0001, 0x0003, 0x0004 at slave ID 0x01 every 1/10th of a second.

Additionally, there are various fields in the table below the buttons showing what data (not full packet contents) had last been sent or received as a result of a polling/sends.

The ASCII Slave demo is basically setup to be driven and polled by the ASCII Master demo and has a few buttons to interact with polled values and display values sent to it.

When either are loaded onto your SIM, you should be able (assuming drivers installed etc) to plug it in via USB to your PC and verify you see a device labelled “Serious CDC/ACM Device (COM#)” in the “Ports (COM & LPT)” section of your device manager.

If you connect to that COM port via a terminal application like Tera Term VT you can then receive and send packets manually via ASCII. Same could be done with literal bytes if switched to RTU, but ASCII is far easier.

Our SIMs connect as a composite device with VID 0x25D8, PID 0x0051 where the first interface is a vendor class interface that purely is used for SHIPBridge communications and the second interface is CDC/ACM which is what you’ll be targeting just like the terminal application.

What About on the Other End of the Wire? Free Modbus Stacks

If you’re looking for a free adaptable Modbus stack, the FreeModbus.org site includes several off-the-shelf examples in C you can easily port to your specific MCU. Even if you don’t start with that code, Modbus is a very simple protocol so writing a small bit of code to handle the particular function codes you want to use in the end is not very challenging, especially if you implement a Modbus Slave on your side of the wire. The Master is a bit trickier, so generally you’ll want to use the Master mode in the GUI if you can.

Alternatives to Modbus

Modbus is easy, cheap, and straightforward. It also allows multiple slaves. But Modbus is polled, and not all that rich when it comes to data types and communications flexibility.

See Protocols for a list of other protocols supported in SHIP.

Categories

AN0319: SHIPTide – Implementing Calibration for Resistive Touch SIMs

Calibrating Resistive Touch Screens — the Concepts

Resistive touch screens are made of two planes of resistive gradient plastic, aligned 90 degrees from each other, separated by a tiny gap with microscopic beads to keep them apart. When you press down, these layers connect and the resistance gradient can be measured in both XY directions by an analog to digital converter (ADC).

These ADC XY readings must be offset/scaled to overlay the pixel coordinates of the screen in order to extract the XY location of the touch in pixels (rather than ADC values).

The “calibration” of a resistive touch screen, therefore, is all about getting the X and Y offsets+scaling values in order to translate ADC values to XY pixel values.

The most straight forward way to do this is to ask the user to sequentially touch two opposing corners of the screen. Knowing you expect a touch (for example) in the upper left at pixel XY [0,0], if you see (for example) ADC XY values of [20,103], then you know your offset is [20,103] to translate from ADC values to XY pixel values. Once you have the opposite corner (for example, the ADC coordinates on the bottom right of the screen press at pixel XY [glass width – 1, glass height – 1], you now can calculate the XY scaling factor.

However, it is critical that the calibration process be as accurate as possible, so several additional steps can be added:

  • sequentially touching each of the 4 corners, so as to get redundant ADC readings for each edge
  • asking for multiple touches in the same location and averaging the result

Once all the calibration ADC values have been obtained, the result must be either (a) committed to non-volatile storage for future use or (b) abandoned to revert to prior values if it is determined the calibration was not very accurate.

The demo project uses several of these techniques.

Calibration “Commands”

Assigning a 32-bit value to the touch port’s port variable calibrate delivers calibration commands to the touch controller. On most SIMs, this is the TOUCH.calibrate port variable.

The 32-bit value has a high 16-bit portion and a low 16-bit portion, and there are 4 types of calibration commands accepted by the touch controller:

CommandDescription
(X<<16)|(Y)enter/continue calibration mode and expect this pixel coordinate as next touch down
0x80000001process the calibration sequence and start using; do not yet save in NV memory
0x80000000cancel calibration sequence and revert to prior values
0x80000002save in NV memory the new calibration values

Once the calibration mode has been entered, you will not receive TOUCH.DN/TOUCH.MV/TOUCH.UP events on TOUCH0.event.e. You will receive TOUCH.CALIBRATE events only on the press down. This allows your GUI to index to the next touch point. After the 0x80000001 (“process”) command, the TOUCH0.event.e resumes functioning as normal.

Entering Calibration Mode and Acquiring Calibration Points

The first step in calibration is to acquire a sequence of physical touches associated with known pixel locations in the 4 corners of the screen.

The demo project sequences through a simple software-driven state machine. Each corner is handled in a different state of the state machine. For example, the first corner is the left top (LT) corner and the GUI drives this sequence of events for this corner:

  1. The GUI makes the target visible and moves it so that its center is in the top left corner (location 0,0) on the LCD
  2. The GUI then send assigns TOUCH0.calibrate = (0<<16)|0;, which sends the location 0,0 to the touch driver’s special port variable. This assignment tells the touch driver to enter calibration mode (if not already there), and expect future touches at this coordinate
  3. The GUI displays a message on the screen telling the user to press the target
  4. The user now presses the screen, hopefully somewhere in the general region of that target!
  5. The touch driver, instead of sending the normal TOUCH.DN event to the GUI, instead (since it is in calibration mode) sends a special TOUCH.CALIBRATE event to the GUI
  6. The GUI’s touch listener “hears” that event, and uses that event to advance to the next corner

The next corner in the demo, for example, is the right top, which is at location [GLASS0.width-1,0], so makes the assignment TOUCH0.calibrate = ((GLASS0.width-1)<<16)|0;.

You can optionally ask for repeat touches in each corner by re-sending the same coordinate command and the touch controller will expect the next touch to be again in that same location. This will cause the two ADC readings to be averaged.

Exiting Calibration Mode

When all four corners have been acquired, the demo sends 0x80000001 to the touch driver calibration system (via the script assignment TOUCH0.calibrate = 0x80000001;), telling the touch driver to process all the ADC to Pixel relationships and generate the new offset/scaling values. The “calibration mode” is exited and the new calibration is in force, albeit temporarily.

The new calibration at this point has not been committed to NV memory. There is no assurance at this point that the user did a good job and that the calibration is accurate. At this point, the GUI needs to test for accuracy; only with some assurance of accuracy should the result be committed. Otherwise, the calibration sequence must be rejected and revert to the original values prior to the calibration cycle.

Committing/Cancelling Calibration Data

At this point in the process, the new calibration data (scaling+offset) is in force and is driving the conversion of touch ADC values to pixel values. Assuming the sequence was done well, the readings in the low level touch driver, translated by the scaling+offset, should yield pixel values very close to the correct location.

If this is true, the user should be able to easily hit a small icon on the screen.

If, however, the calibration data is inaccurate, the touch ADC values with scaling and offset will not line up with the pixels on the screen, and therefore even if a user thinks they are pressing an icon on the screen, the touch driver is calculating a very different XY pixel location which the GUI will map far away from that icon.

Therefore, the decision to “commit to non-volatile memory” the calibration data should be made after getting confirmation by a button hit that the calibration data is, indeed, resulting in a good mapping between the ADC driver values and the visible pixel locations.

The way this is normally done is two touch listeners. The first is assigned to the whole page, looking for any touch anywhere on the screen. This will trap all touches, regardless of good or bad calibration data. The second touch listener, further down in the layout, should be mapped to a button on the screen. Since SHIP uses a “last-best-touch” algorithm, the second touch listener will only get the event if the user touches on the button and the ADC calibration aligns that touch with the visual button on the screen. That should drive the commit event. However, if the user presses the button (or anywhere else) and the calibration data is misaligned, the first touch listener will get the event and should issue the cancel command which will discard all the new calibration data and revert back to the old calibration.

Example Project

Unpack the attached project to your local drive and open the project in SHIPTide. It’s written for the SIM231, but easily adaptable to other SIMs.