Skip to content

Commit

Permalink
Merge branch 'github-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
JJL772 committed Sep 19, 2019
2 parents 762536a + d015fc0 commit 660bc20
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 32 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ This is a device support module for the EK9000, which couples EtherCAT terminals
Because it uses modbus, it's runnable on any IOC without needing a kernel module or other support software.

## Dependencies

This module depends on Mark River's modbus device support module/driver. You can grab that from here and make sure to grab it's dependencies too.

## Building

See the wiki page for instructions on building and a small example.

## Contributing
Expand All @@ -18,16 +20,19 @@ If you'd like to contribute to this, you can open a pull request with changes, a
If you have any questions or comments about the module, you can reach me at [email protected]

# Known Issues

The EK9000 doesn't really make figuring out a register map easy, so please keep these things in mind:

* If you install an unsupported terminal, the device support module may fail to correctly determine the register map, resulting in weirdness and stuff.
* If you place an unsupported analog ONLY terminal AFTER all other slaves on the EK9000, the register map will be correct. This is because the device maps all analog terminals in order of their position on the rail. Digital terminals are mapped to coils, and therefore, not affected by weirdness in the holding/input register space.
* If you place an unsupported digital ONLY terminal AFTER all other slaves on the EK9000, the register map will also be correct.
* If you place an unsupported digital ONLY terminal AFTER all other slaves on the EK9000, the register map will also be correct.
* If you change the Pdo mapping for analog input terminals to something other than standard, the analog IO AND register mapping will not work correctly.
To recap:
* Only supported terminals (EL10XX, EL11XX, EL20XX, EL30XX, EL31XX, EL40XX) should be placed on the rail first. Unsupported terminals can be placed after the ones you are trying to access through EPICS.
* Only supported terminals (EL10XX, EL11XX, EL20XX, EL30XX, EL31XX, EL40XX) should be placed on the rail first. Unsupported terminals can be placed after the ones you are trying to access through EPICS.
* Analog input Pdo mapping should ALWAYS be standard, or else the module will not work.

# Supported Terminals
## Supported Terminals

* EL10XX (EL1004, etc.)
* EL11XX (EL1124, etc.)
* EL20XX (EL2008, etc.)
Expand All @@ -36,5 +41,6 @@ To recap:
* EL31XX (EL3174, etc.)
* EL40XX (EL4004, etc.)
* EL41XX (EL4104, etc.)
Anything else is generally not supported.
Support for motor terminals (EL70XX) is in progress.

Any other terminals are not generally supported, but a few exceptions exist.
Support for motor terminals (EL70XX) is currently being developed.
70 changes: 43 additions & 27 deletions doc/manual.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,68 @@
# EPICS Device Support for the EK9000

This is the manual for the EK9000 device support module for EPICS 3.14 and later.

If you don't know already, the EK9000 is a low-cost Modbus to EtherCAT coupler. It allows you to control EtherCAT terminals (such as the EL3064 analog input) using a simple Modbus interface, which can be used on virtually any IOC with internet access.

## Building and Installing
This device support module was built and tested with EPICS R3.14, but should function fine in EPICS 7 or any earlier versions of EPICS.

This device support module was built and tested with EPICS R3.14, but should function fine in EPICS 7 or any earlier versions of EPICS.

This module depends on the EPICS modbus module (R3-0 or later) and subsequently requires the asyn driver.

Once you clone this module to your workspace, you'll need to change some of the configuration Makefiles to reflect your personal envrionment. Once done, building is pretty easy:
Once you clone this module to your workspace, you'll need to change some of the configuration Makefiles to reflect your personal environment. Once done, building is pretty easy:

```bash
make
make
```

You can run the test IOC with `./ek9000Test st.cmd` or with `./st.cmd` from the `iocBoot` directory.

## How It Works

Although the EK9000 is pretty easy to access with Modbus, it has some odd quirks and shortcomings that made making this module somewhat difficult, so I thought I'd include an explanation of how the module functions.

### Initialization

There are two steps to initialization: startup script functions and the actual record initialization.

In the startup script, the function `ek9000Configure(name, ip, port, slavecount)` is used to create an EK9000 device at the specified ip and port with the specified number of slaves.
Internally, it allocates memory for terminal structures and holds some important parameters.
In the startup script, the function `ek9000Configure(name, ip, port, slave_count)` is used to create an EK9000 device at the specified ip and port with the specified number of slaves.
Internally, it allocates memory for terminal structures and holds some important parameters.

Once the device is created, it will try to connect to the device. If it can't connect, the records and other startup script functions (such as `ek9000ConfigureTerminal`) will fail.

#### Startup script

After this, you can use `ek9000ConfigureTerminak(ek9000_name, record_name, type, slave_number)` to tell the driver to expect a terminal attached to the specified ek9000 with the given type, record name and slave position. The parameter `record_name` is the **base** name of the record that this terminal is supposed to be attached to.
Type is an integral value representing the terminal type, for example, 3064, which is found in the string EL3064.
`slave_number` simply represents the position on the rail: keep in mind, this is a 1-based index.
After this, you can use `ek9000ConfigureTerminal(ek9000_name, record_name, type, slave_number)` to tell the driver to expect a terminal attached to the specified ek9000 with the given type, record name and slave position. The parameter `record_name` is the **base** name of the record that this terminal is supposed to be attached to.
Type is an integral value representing the terminal type, for example, 3064, which is found in the string EL3064.
`slave_number` simply represents the position on the rail: keep in mind, this is a 1-based index.

It's important to make sure that the type of terminal that you specify actually matches what's on the rail.
It's important to make sure that the type of terminal that you specify actually matches what's on the rail.
The module **WILL** verify that they match, and throw an error if they don't.
This also means that you can't initialize terminals without a connection to the device.

After those functions are called, you can load a database or a substitutions file that contains the records you want to be used by terminals attached to that ek9000. It's important that you ONLY load these records after you've registerd all terminals to the device.
After those functions are called, you can load a database or a substitutions file that contains the records you want to be used by terminals attached to that ek9000. It's important that you ONLY load these records after you've registered all terminals to the device.

#### Record initialization

During record initialization, terminals with the `DTYP` field set to `EL10XX`, `EL11XX`, etc. will initiate a search for the terminal they're supposed to be attached to.
During record initialization, terminals with the `DTYP` field set to `EL10XX`, `EL11XX`, etc. will initiate a search for the terminal they're supposed to be attached to.
The name of the record usually will be suffixed with a `:x`, where x is an integer representing the channel that the record represents.
The string preceding the `:x` acts as the base record name, which you specified in your init script.
I decided to do this, because it made sense to me.. Comments on this design decision are welcome.

For example, if you use the template `EL1002.template`, and substitute all the stuff you want to, the records `MyTerminal1:1` and `MyTerminal1:2` will be created.
For example, if you use the template `EL1002.template`, and substitute all the stuff you want to, the records `MyTerminal1:1` and `MyTerminal1:2` will be created.
Given that you ran `ek9000ConfigureTerminal("EK9K1", "MyTerminal1", 1002, 1)` or something similar in your ioc init script, the records will initialize successfully.

Once the record initializes, it associates some private data with itself which contains a pointer to the descriptor of the terminal and some other stuff.

#### Final initialization

The final thing the module does is compute a register map for the terminals inside of the EK9000's register space.
The EK9000 will map the PDO (program data objects) from each slave into it's Modbus register space. Digital inputs will be mapped to discrete input coils and digital outputs will be mapped to output coils. Analog inputs get mapped to input registers and analog outputs are mapped to output registers.
The EK9000 will map the PDO (program data objects) from each slave into it's Modbus register space. Digital inputs will be mapped to discrete input coils and digital outputs will be mapped to output coils. Analog inputs get mapped to input registers and analog outputs are mapped to output registers.

Unfortunately, there is no way to ask the EK9000 for a register map, instead we have to use a register space (Starting at 0x6000) to figure out where each terminal is located on the rail.
This doesn't tell us anything about the size of the PDOs for each slave, nor does it tell us about the PDO types. Thus, all PDO sizes are hard-coded into the module (well, they're actually in an autogenerated header).
Devices with multiple PDO types, for example the EL3064 which has a compact and a standard PDO, can't really have their PDO sizes prediced, as they can be set to who knows what. In the next release of the module, a remedy for this is planned (it will actually involve a big restructure of the initialization commands). For all intents and purposes, the module will assume all devices have a PDO set to standard (if they have multiple PDO types).
This doesn't tell us anything about the size of the PDOs for each slave, nor does it tell us about the PDO types. Thus, all PDO sizes are hard-coded into the module (well, they're actually in an auto generated header).
Devices with multiple PDO types, for example the EL3064 which has a compact and a standard PDO, can't really have their PDO sizes predicted, as they can be set to who knows what. In the next release of the module, a remedy for this is planned (it will actually involve a big restructure of the initialization commands). For all intents and purposes, the module will assume all devices have a PDO set to standard (if they have multiple PDO types).

With this in mind, the module will loop through all registered terminals and determine the register map in the same way the EK9000 does.
It will store this info with a terminal's descriptor, which will then be used by the reading and writing functions.
Expand All @@ -71,14 +76,15 @@ Reading from a digital input terminal is done in a similar way, except we read f
### Reading/Writing to analog terminals

Once again, using the channel that is determined from the record name, the module computes which registers need to be read with the following equation:
```

```c
register = base_register + ((channel-1) * channel_size)
```
In the above example, `channel_size` refers to the size of each channel in modbus registers. Since modbus registers are 16-bits wide, `channel_size` will equal 2, since each channel is 4 bytes wide by default


In the above example, `channel_size` refers to the size of each channel in modbus registers. Since modbus registers are 16-bits wide, `channel_size` will equal 2, since each channel is 4 bytes wide by default

## Supported Terminals

This module supports most digital and analog I/O terminals, but a few are not supported. You can use the following terminals normally with your EK9000:

* EL10XX and EL11XX digital input terminals
Expand All @@ -99,21 +105,22 @@ If you have an unsupported terminal, but want to use it on the same rail as othe
* When placing an unsupported terminal on the same rail as other terminals to be accessed by an EPICS IOC, place it last on the rail.

Here's a visual example of these rules:
| Slave Number | Terminal Type | Accessable by EPICS IOC |
| Slave Number | Terminal Type | Accessible by EPICS IOC |
|---|---|---|
| 1 | EL3064 | Yes |
| 2 | EL2008 | Yes |
| 3 | EL7047 | No |
| 4 | EL3064 | No |
| 4 | EL3064 | No |
| 5 | EL2008 | No |

In the above table, all terminals that are placed after the **unsupported** slave (number 3), cannot be _properly_ accessed by the IOC.
In the above table, all terminals that are placed after the **unsupported** slave (number 3), cannot be _properly_ accessed by the IOC.
In the above example, slave 4, which is placed after the EL7047, will accidentally get mapped to the address space of slave 3, the unsupported terminal, since the module doesn't know the PDO sizes of the EL7047.

## Startup Script Example

Here's an example of how a rail and IOC might be set up.

Lets assume the rail looks something like this:
Lets assume the rail looks something like this:
| Slave number | Terminal Type |
|---|---|
| 1 | EL3064 |
Expand All @@ -123,6 +130,7 @@ Lets assume the rail looks something like this:
| 5 | EL1008 |

The initialization script might look something like this:

```bash
...
ek9000Configure("EK9K1", "192.168.1.3", 502, 5)
Expand All @@ -137,24 +145,28 @@ dbLoadTemplate("MySubs.substitutions")
```

Lets assume that `MySubs.substitutions` contains the following records, which are created from the proper template file:

* MyTerminal1 (Created from EL3064.template)
* MyTerminal2 (Created from EL2008.template)
* MyTerminal3 (Created from EL3154.template)
* MyTerminal4 (Created from EL1004.template)
* MyTerminal5 (Created from EL1008.template)

Given this, you can write to the first digital output on slave 2 using:
```

```bash
caput MyTerminal1:1 1
```

You can also read from the 3rd analog input on slave 3 using:
```

```bash
caget MyTerminal3.RVAL
```

## IOCsh Function Documentation

```
```c
----------------------------------------------------------
ek9000Configure(ek9k, ip, port, num_slaves)

Expand Down Expand Up @@ -208,15 +220,19 @@ Params:
```
## Contributing
If you'd like to contribute to this project, feed free to open a pull request at any time, and I'll hopefully get around to reviewing and merging it quickly.
If you'd like to contribute to this project, feel free to open a pull request at any time!
## Bugs
If you find any bugs, you can open an issue or send me an email, and I'll hopefully get around to fixing it fairly quickly.
## Suggestions or Requests
If you have any suggestions or feature requests, you can either open an issue or email me at [email protected]
If you have any suggestions or feature requests, you can either open an issue on the github repository or email me at [email protected]
Usually I'll respond within a day or two.
## Developers
* Jeremy Lorelli ([email protected])

0 comments on commit 660bc20

Please sign in to comment.