|
| 1 | +:lang: en |
| 2 | + |
| 3 | +[[cha:mesa_modbusl]] |
| 4 | + |
| 5 | += Mesa Modbus = |
| 6 | + |
| 7 | +A framework to create custom, realtime, modbus drivers using the Mesa |
| 8 | +PktUART component. |
| 9 | + |
| 10 | +There are several existing ways to control Modbus devices with LinuxCNC, |
| 11 | +and those should also be considered. |
| 12 | +They include, but are not limited to: |
| 13 | + |
| 14 | +<<cha:mb2hal>> |
| 15 | + |
| 16 | +https://forum.linuxcnc.org/24-hal-components/38733-vfdmod-an-easy-vfd-control-over-modbus-rtu?start=0#162435[vfdmod] |
| 17 | + |
| 18 | +<<cha:cl-programming>> |
| 19 | + |
| 20 | +There are also several drivers for specific VFDs, for example |
| 21 | + |
| 22 | +<<cha:mitsub>> |
| 23 | + |
| 24 | +<<hy_gt_vfd>> |
| 25 | + |
| 26 | +<<hy_vfd>> |
| 27 | + |
| 28 | +<<gs2_vfd>> |
| 29 | + |
| 30 | +<<vfdb_vfd>> |
| 31 | + |
| 32 | +<<vfs11_vfd>> |
| 33 | + |
| 34 | +<<wj200_vfd>> |
| 35 | + |
| 36 | +For most applications (especially the "hy" (HuangYang) VFDs which do not |
| 37 | +actually conform to true modbus) it is likely to be less work to use |
| 38 | +these. |
| 39 | + |
| 40 | +Reasons to use this framework: |
| 41 | + |
| 42 | +1) It is realtime, commands and reads happen deterministically. This |
| 43 | +does not mean that they are fast, but that they happen at the same rate |
| 44 | +all of the time, independent of other activity on the PC. At 9600bps a |
| 45 | +typical transaction takes 18 servo-thread cycles, and the channels are |
| 46 | +serviced consecutively. At higher bit-rates things are better, but each |
| 47 | +read or write will always take at least three cycles, per register. To |
| 48 | +ameliorate this a little, writes to registers are only performed when |
| 49 | +necessary. Reads happen in turn, taking as long as they take. |
| 50 | + |
| 51 | +2) It uses ports on the Mesa card. If you already have a Mesa card then |
| 52 | +this means that no extra hardware is needed. A typical application would |
| 53 | +be to re-purpose the smart-serial port on the 7i96 as a modbus port to |
| 54 | +control a VFD. If you do not have a Mesa card then this is obviously no |
| 55 | +advantage at all. |
| 56 | + |
| 57 | +== Quick Start == |
| 58 | + |
| 59 | +Install the linuxcnc-dev package. (FIXME: modcompile and mesa_uart.c are |
| 60 | +not yet included in linuxcnc-dev) |
| 61 | + |
| 62 | +Create a new definition file similar to analogue.mod and relayboard.mod |
| 63 | +(the annotated mesa_uart.mod.sample file should help here) |
| 64 | + |
| 65 | +Run the command: |
| 66 | + |
| 67 | +---- |
| 68 | +./modcompile my_file.mod |
| 69 | +---- |
| 70 | + |
| 71 | +and the "modcompile" script will compile and install the module. As |
| 72 | +some realtime implementations of LinuxCNC realtime require that modules |
| 73 | +be kernel modules, and kernel modules have no access to files, the |
| 74 | +mesa_modbus framework compiles-in the modbus register structure and |
| 75 | +HAL pins into an immutable, loadable, module. If you need to change |
| 76 | +the register assigments or add/remove pins/registers then the module |
| 77 | +must be recompiled. |
| 78 | + |
| 79 | +Load and activate the module with HAL commands such as |
| 80 | + |
| 81 | +---- |
| 82 | +loadrt my_file ports=hm2_7i69.0.pktuart.0 |
| 83 | +addf my_file servo-thread |
| 84 | +---- |
| 85 | + |
| 86 | +== Modbus == |
| 87 | + |
| 88 | +Rather than document modbus here, the main details can be found at: |
| 89 | +https://en.wikipedia.org/wiki/Modbus |
| 90 | + |
| 91 | +The mesa_modbus system supports modbus commands 1,2,3,5,6,15,16. |
| 92 | + |
| 93 | +== HAL Interface == |
| 94 | + |
| 95 | +Modbus *write* commands will create HAL *input* pins, that take values |
| 96 | +from HAL and pass them to the Modbus device. |
| 97 | + |
| 98 | +Modbus *read* commands will read data from the device and pass that to |
| 99 | +HAL *output* pins for use by other components. |
| 100 | + |
| 101 | +HAL_BIT, HAL_U32 and HAL_S32 pins will present the (16 bit) Modbus |
| 102 | +registers exactly as the bits are delivered from the device and will |
| 103 | +send them to the device as 16 bit values according to standard C type |
| 104 | +conversion standards. HAL_FLOAT values are treated differently. When a |
| 105 | +float pin is specified then two extra HAL pins are created with the |
| 106 | +same name but suffixed with -scale and offset. The value from the |
| 107 | +modbus register will be interpreted as a sighed-16 bit number. It will |
| 108 | +then be multiplied by the scale value and then the offset will be |
| 109 | +added (ie the offset is in engineering units, and the scale converts 16 |
| 110 | +bit register values to engineering units) |
| 111 | + |
| 112 | +The reverse transformation is performed when writing to the device. |
| 113 | + |
| 114 | +All Modbus registers are 16 bits. It is possible that in some applications |
| 115 | +this might be used to represent encoder counts or some other number that |
| 116 | +will wrap-around. To circumvent this problem in the case of the HAL_S32 |
| 117 | +pin type the registers will be promoted to signed 64-bit internally then |
| 118 | +multiplied by the "scale" pin value and presented as a floating point |
| 119 | +value on a HAL pin with the suffix "scaled". |
| 120 | +For example mymodule.0.counts-0-scaled. |
| 121 | + |
| 122 | + |
| 123 | +In addition to the pins configured in the definition file, each |
| 124 | +module will create the following pins for each instance of the driver: |
| 125 | + |
| 126 | +*modname.address* (default 0x01) |
| 127 | + |
| 128 | +*modname.baudrate* (default 9600) |
| 129 | + |
| 130 | +*modname.parity* (default 0 (no parity) options are 1 for odd and 2 for even.) |
| 131 | + |
| 132 | +*modname.txdelay* (default 20 bit lengths. Generally should be larger than Rx Delay) |
| 133 | + |
| 134 | +*modname.rxdelay* (default 15) |
| 135 | + |
| 136 | +*modname.drive_delay* (default 0) |
| 137 | + |
| 138 | +•modname.update-hz* (default 0) |
| 139 | + |
| 140 | +*modname.fault* - indicates a fault with the device or comms |
| 141 | + |
| 142 | +*modname.last-error* - indicates the error code that set the fault |
| 143 | +output. |
| 144 | + |
| 145 | +These can be redefined at any time and will take effect the next time |
| 146 | +that a modbus packet is assembled. |
| 147 | + |
| 148 | +*modname.update-hz* is provided to slow down the transaction rate for |
| 149 | +modbus devices that become unstable if polled too frequently. If you see |
| 150 | +fault 11 then try setting this to 0.1Hz or even 1Hz. If set to zero the |
| 151 | +system runs as fast as it can. |
| 152 | + |
| 153 | +The fault codes returned in "last error" are |
| 154 | + |
| 155 | +|=== |
| 156 | +|Code|Fault |
| 157 | +|1|Illegal Function |
| 158 | +|2|Illegal Data Address |
| 159 | +|3|Illegal Data Value |
| 160 | +|4|Server Device Failure |
| 161 | +|5|Acknowledge |
| 162 | +|6|Server Device Busy |
| 163 | +|7|Negative Acknowledge |
| 164 | +|8|Memory Parity Error |
| 165 | +|9|Gateway Path Unavailable |
| 166 | +|10|Gateway Failed to Respond |
| 167 | +|11|Comm Timeout |
| 168 | +|=== |
| 169 | + |
| 170 | +Each module exports a single HAL function to be attached to a realtime |
| 171 | +thread. The function name is just the module name, with no distinction |
| 172 | +made between read and write cycles. |
| 173 | + |
| 174 | +All modules created by the framework require a hostmot2 pktuart instance |
| 175 | +to be given to the "ports" modparam on the "loadrt" file. See the |
| 176 | +example in the [Quick Start] section. |
| 177 | + |
| 178 | + |
| 179 | +== Configuration File == |
| 180 | + |
| 181 | +A Mesa_Modbus configuration file is actually a C header file and must |
| 182 | +conform to C syntax rules. An example file is included here: |
| 183 | + |
| 184 | +[source,C] |
| 185 | +---- |
| 186 | +/* |
| 187 | +The format of the channel descriptors is: |
| 188 | +
|
| 189 | +{TYPE, FUNC, ADDR, COUNT, pin_name} |
| 190 | +
|
| 191 | +TYPE is one of HAL_BIT, HAL_FLOAT, HAL_S32, HAL_U32 |
| 192 | +FUNC = 1, 2, 3, 4, 5, 6, 15, 16 - Modbus commands |
| 193 | +COUNT = number of coils/registers to read |
| 194 | +*/ |
| 195 | +
|
| 196 | +#define MAX_MSG_LEN 16 // may be increased if necessary to max 251 |
| 197 | +
|
| 198 | +static const hm2_modbus_chan_descriptor_t channels[] = { |
| 199 | +/* {TYPE, FUNC, ADDR, COUNT, pin_name} */ |
| 200 | +// Create 8 HAL bit pins coil-00 .. -07 supplying the values of coils at 0x0000 |
| 201 | + {HAL_BIT, 1, 0x0000, 8, "coil"}, |
| 202 | +// Create 8 HAL bit pins input-00 .. -07 supplying the values of inputs at 0x0000 |
| 203 | + {HAL_BIT, 2, 0x0000, 8, "input"}, |
| 204 | +// Create a HAL pin to set the coil at address 0x0010 |
| 205 | + {HAL_BIT, 5, 0x0010, 1, "coil-0"}, |
| 206 | +// Create 8 HAL pins to set the coils at 0x0020 |
| 207 | + {HAL_BIT, 15, 0x0020, 8, "more_coils"}, |
| 208 | +// Create a scaled floating point pin calculated from input register 0x0100 |
| 209 | + {HAL_FLOAT, 4, 0x0100, 1, "float"}, |
| 210 | +// Create 4 unsigned integer HAL pins from the holding registers at 0x0200-0x203 |
| 211 | + {HAL_S32, 3, 0x0003, 4, "holding"}, |
| 212 | +// Create a single signed int HAL pin to control the register at 0x0300 |
| 213 | + {HAL_S32, 6, 0x0300, 1, "relay-3"}, |
| 214 | +// Create 7 scaled FP HAL pins to control holfing registers at 0x400-0x406 |
| 215 | + {HAL_FLOAT, 16, 0x0300, 1, "more_floats"}, |
| 216 | +}; |
| 217 | +---- |
| 218 | + |
| 219 | +Typically the comments would not be included in a config file. |
| 220 | + |
| 221 | +MAX_MSG_LEN can be included as a #define if required, but will default |
| 222 | +to 16 bytes if this is omitted. The Modbus protocol forces a hard max |
| 223 | +limit of 251 bytes, but that would imply setting thousands of bits or |
| 224 | +hundreds of registers in a single transaction. |
| 225 | + |
| 226 | +An optional DEBUG parameter may be defined. This will default to |
| 227 | +RTAPI_MSG_ERR (1) which means that only error messages will be shown. |
| 228 | +include the line |
| 229 | +---- |
| 230 | +#define DEBUG 3 |
| 231 | +---- |
| 232 | +To see verbose data from the driver which can be useful for debugging. |
| 233 | +Be aware that this is a lot of data, and it should be turned back to |
| 234 | +1 when the driver is working. |
| 235 | + |
| 236 | +The text `static const hm2_modbus_chan_descriptor_t channels[] = {` |
| 237 | +must be left unchanged, and the concluding `};` is also very |
| 238 | +important. |
| 239 | + |
| 240 | +Between the start and end delimiters defined above there should be as |
| 241 | +many descriptors as necessary for the device being controlled. For a |
| 242 | +simple device (such as a single channel ADC) there might be only one |
| 243 | +line. For such a simple device the following minimal description file |
| 244 | +would suffice |
| 245 | + |
| 246 | +[source,C] |
| 247 | +---- |
| 248 | +static const hm2_modbus_chan_descriptor_t channels[] = { |
| 249 | +/* {TYPE, FUNC, ADDR, COUNT, pin_name} */ |
| 250 | + {HAL_FLOAT, 3, 0x0000, 1, "volts"}, |
| 251 | +}; |
| 252 | +---- |
| 253 | + |
| 254 | +The valid HAL pin types supported are HAL_BIT, HAL_FLOAT, HAL_U32 and |
| 255 | +HAL_S32. |
| 256 | + |
| 257 | +The supported Modbus command types are: |
| 258 | + |
| 259 | +[cols=description, code] |
| 260 | +|=== |
| 261 | +|Read Coils|1| |
| 262 | +|Read Discrete Inputs|2| |
| 263 | +|Read Multiple Holding Registers|3| |
| 264 | +|Read Input Registers|4| |
| 265 | +|Write Single Coil|5| |
| 266 | +|Write Single Holding Register|6| |
| 267 | +|Write Multiple Coils|15| |
| 268 | +|Write Multiple Holding Registers|16| |
| 269 | +|=== |
| 270 | + |
| 271 | +The Modbus address can be given in Hexadecimal, decimal (or even octal) |
| 272 | +as can the modbus command. Typically the modbus commands are given in |
| 273 | +decimal and the addresses in hex. |
| 274 | + |
| 275 | +If the number in the "count" column is >1 _and_ if the command given |
| 276 | +supports multiple reads/writes then a numbered sequence of HAL pins will |
| 277 | +be created using the root name from the definition with an appended 2 |
| 278 | +digit suffix, eg `volts-03`. For commands that do not support multiple |
| 279 | +values (5, 6) the count column is silently ignored (but must be numeric |
| 280 | +and not omitted) |
| 281 | + |
| 282 | +== Compiling == |
| 283 | + |
| 284 | +A simple script 'modcompile' is provided that will compile and install |
| 285 | +a new HAL module based on the mesa_modbus.c file and the pin definition |
| 286 | +file. The sample definition files use the .mod prefix but this is not |
| 287 | +necessary except in the special case of the 'modcompile all' command, |
| 288 | +which will compile and install all .mod files in the current directory. |
| 289 | + |
| 290 | +---- |
| 291 | +./modcompile my_file.mod |
| 292 | +---- |
| 293 | +or |
| 294 | +---- |
| 295 | +./modcompile all |
| 296 | +---- |
| 297 | + |
| 298 | +"modcompile" is provided by the "linuxcnc-dev" package. |
| 299 | + |
| 300 | +---- |
| 301 | +sudo apt-get install linuxcnc-uspace-dev |
| 302 | +---- |
| 303 | +or |
| 304 | +---- |
| 305 | +sudo apt-get install linuxcnc-dev |
| 306 | +---- |
| 307 | +if using RTAI kernel realtime. |
| 308 | + |
| 309 | +Alternatively the package should be installable with the Synaptic |
| 310 | +package manager. |
| 311 | + |
| 312 | +== Hardware Connection == |
| 313 | + |
| 314 | +The Mesa serial ports have separate pins for Tx and Tx pairs. For RS422 |
| 315 | +Modbus RTU communications these should be connected at the Mesa card |
| 316 | +Tx+ to Rx+ and Tx- to Rx-. |
| 317 | + |
| 318 | +Nore that there are differing naming standards for Modbus pins. |
| 319 | +Typically Rx+ and TX+ will connect to the B- pin on the modbus device |
| 320 | +and Rx- and Tx- will connect to the A+ pin. (ie, +/- will appear |
| 321 | +reversed. |
| 322 | + |
| 323 | + |
| 324 | +=== Ad-hoc Modbus device access === |
| 325 | + |
| 326 | +For experimentation and one-off configuration it is possible to send / |
| 327 | +recieve data through the FPGA serial port using the mesaflash utility |
| 328 | +in a script. A sample script follows. |
| 329 | + |
| 330 | +[source,bash] |
| 331 | +---- |
| 332 | +#! /bin/bash |
| 333 | +
|
| 334 | +# First setup the DDR and Alt Source regs for the 7I96 |
| 335 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1100=0x1F800 |
| 336 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1104=0x1C3FF |
| 337 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1200=0x1F800 |
| 338 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x1204=0x1C3FF |
| 339 | +# Next set the baud rate DDS's for 9600 baud |
| 340 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6300=0x65 |
| 341 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6700=0x65 |
| 342 | +# setup the TX and RX mode registers |
| 343 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6400=0x00000A20 |
| 344 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6800=0x3FC0140C |
| 345 | +# Reset the TX and RX UARTS |
| 346 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6400=0x80010000 |
| 347 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6800=0x80010000 |
| 348 | +# load two 8-byte modbus commands: |
| 349 | +# 01 05 00 00 5A 00 F7 6A and 01 01 00 00 00 01 FD CA |
| 350 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x00000501 |
| 351 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x6AF7005A |
| 352 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0x00000101 |
| 353 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6100=0xCAFD0100 |
| 354 | +
|
| 355 | +# Command the TX UART to send the two 8 byte packets |
| 356 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6200=0x08 |
| 357 | +mesaflash --device 7i96 --addr 10.10.10.10 --wpo 0x6200=0x08 |
| 358 | +sleep 1 |
| 359 | +# display TX Mode |
| 360 | +mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6400 |
| 361 | +# display the RX mode reg, RX count, and the data |
| 362 | +mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6800 |
| 363 | +mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6600 |
| 364 | +mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500 |
| 365 | +mesaflash --device 7i96 --addr 10.10.10.10 --rpo 0x6500 |
| 366 | +---- |
| 367 | + |
0 commit comments