diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..e5ed6201c3770acb36f478ab4e1fb45dea2a049f --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +************************************************ +EPBFCat -- an EtherCAT master using EBPF and XDP +************************************************ + +EBPFCat is an EtherCAT master written entirely in Python, that uses EPBF and +XDP to achieve real-time response times. As a corollary, it contains a +Python based EBPF generator. + +.. toctree:: + ebpfcat/ethercat.rst + ebpfcat/ebpf.rst diff --git a/conf.py b/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..89c42895ff1bc7872561917050f45601e4d877a0 --- /dev/null +++ b/conf.py @@ -0,0 +1,22 @@ +extensions = [ + 'sphinx.ext.autodoc', +] + +templates_path = ['_templates'] +numfig = True +source_suffix = '.rst' +master_doc = 'README' + +project = 'EBPFCat' +copyright = '2020, Martin Teichmann' +author = 'Martin Teichmann' + +release = "0.1" +version = "0.1.0" +language = None +exclude_patterns = ['_build'] +pygments_style = 'sphinx' +todo_include_todos = False +html_theme = 'alabaster' +html_static_path = ['_static'] +htmlhelp_basename = 'EBPFCat' diff --git a/ebpfcat/ethercat.rst b/ebpfcat/ethercat.rst new file mode 100644 index 0000000000000000000000000000000000000000..c6c78bd110732768d718c3233cff68ce3d3d33fd --- /dev/null +++ b/ebpfcat/ethercat.rst @@ -0,0 +1,94 @@ +The EtherCAT master +=================== + +Getting started +--------------- + +Ethercat terminals are usually connected in a loop with the EtherCAT master. +The EtherCAT master has to know the order and function of these terminals. +The list of terminals then has to be given in correct order to the constructor +of the EtherCAT master object as follows:: + + from ebpfcat.ebpfcat import FastEtherCat + from ebpfcat.terminals import EL4104, Generic + + out = EL4104() + unknown = Generic() # use "Generic" for terminals of unknown type + + master = FastEtherCat("eth0", [out, unknown]) + +Once we have defined the order of devices, we can connect to the loop and +scan it to actually find all terminals. This takes time, so in a good +asyncronous fashion we need to use await, which can only be done in an +async function:: + + await master.connect() + await master.scan_bus() + +The terminals usually control some devices, where one terminal may control +several devices, or one device is controlled by several terminals. The devices +are represented by `Device` objects. Upon instantiation, they are connected to +the terminals:: + + from ebpfcat.devices import AnalogOutput + + ao = AnalogOutput(out.ch1_value) + +Devices are grouped into `SyncGroup`, which means that their terminals are +always read and written at the same time. A device can only belong to one +`SyncGroup`, but a terminal may be part of several devices or sync groups. +The sync group is also responsible to constantly transfer data to and from +the terminals such that they do not time out and go into a safe state:: + + from ebpfcat.ebpfcat import SyncGroup + + sg = SyncGroup(master, [ao]) # this sync group only contains one terminal + + sg.start() # start operating the terminals + +The `AnalogOutput` in the examples is a pretty boring device, it can only +output a value like so:: + + ao.value = 5 # set the value on the terminal + + +Writing a device +---------------- + +Equipment controlled via the EtherCAT terminals often requires that a dedicated +device is written for it. Devices inherit from `ebpfcat.Device`. They declare +which kind of data they want to communicate to the terminals as a `TerminalVar` +like so:: + + from ebpfcat.ebpfcat import Device + + class Motor(Device): + speed = TerminalVar() + position = TerminalVar() + +Before they can be used, their `TerminalVar`\ s need to be initialized:: + + motor = Motor() + motor.speed = outputTerminal.speed + motor.position = encoderTerminal.value + +whenever new data is read from the loop, the `update` method of the device is +called, in which one can evaluate the `TerminalVar`\ s:: + + def update(self): + """a idiotic speed controller""" + self.speed = (self.position - self.target) * self.pConst + +Three methods of control +------------------------ + +The communication with the terminals can happen in three different ways: + +- out-of-order: the communication happens ad-hoc whenever needed. This is + done during initialization and for reading and writing configuration data, + like CoE. +- slow: the data is sent, received and processed via Python. This is good + enough to around 100 Hz operation. +- fast: the data is sent, received and processed using XDP in the Linux + Kernel. Only very limited operations can be done, but the loop cycle + frequency exceeds 10 kHz.