Skip to content
Snippets Groups Projects
Commit 4c968882 authored by Martin Teichmann's avatar Martin Teichmann
Browse files

first working version with fmmu

this is tested with read hardware, the tests obviously don't work,
we have to grandfather them.
parent aac6aa75
No related branches found
No related tags found
No related merge requests found
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
"""The high-level API for EtherCAT loops""" """The high-level API for EtherCAT loops"""
from asyncio import ensure_future, gather, sleep, wait_for, TimeoutError from asyncio import ensure_future, gather, sleep, wait_for, TimeoutError
from collections import defaultdict from collections import defaultdict
from contextlib import asynccontextmanager, contextmanager from contextlib import asynccontextmanager, AsyncExitStack, contextmanager
import os import os
from struct import pack, unpack, calcsize, pack_into, unpack_from from struct import pack, unpack, calcsize, pack_into, unpack_from
from time import time from time import time
...@@ -238,6 +238,7 @@ class Device(SubProgram): ...@@ -238,6 +238,7 @@ class Device(SubProgram):
class EBPFTerminal(Terminal): class EBPFTerminal(Terminal):
compatibility = None compatibility = None
position_offset = {2: 0, 3: 0} position_offset = {2: 0, 3: 0}
use_fmmu = True
async def apply_eeprom(self): async def apply_eeprom(self):
await super().apply_eeprom() await super().apply_eeprom()
...@@ -255,17 +256,28 @@ class EBPFTerminal(Terminal): ...@@ -255,17 +256,28 @@ class EBPFTerminal(Terminal):
def allocate(self, packet, readwrite): def allocate(self, packet, readwrite):
"""allocate space in packet for the pdos of this terminal """allocate space in packet for the pdos of this terminal
return a dict that contains the starting offset for each return a dict that contains the datagram number and
sync manager""" starting offset therein for each sync manager.
Negative datagram numbers are for the future FMMU
datagrams."""
bases = {} bases = {}
if self.pdo_in_sz: if self.use_fmmu:
bases[3] = packet.size + packet.DATAGRAM_HEADER if self.pdo_in_sz:
packet.append(ECCmd.FPRD, b"\0" * self.pdo_in_sz, 0, bases[3] = (1, packet.fmmu_in_size)
self.position, self.pdo_in_off) packet.fmmu_in_size += self.pdo_in_sz
if readwrite and self.pdo_out_sz: if readwrite and self.pdo_out_sz:
bases[2] = packet.size + packet.DATAGRAM_HEADER bases[2] = (2, packet.fmmu_out_size)
packet.append_writer(ECCmd.FPWR, b"\0" * self.pdo_out_sz, 0, packet.fmmu_out_size += self.pdo_out_sz
self.position, self.pdo_out_off) else:
if self.pdo_in_sz:
bases[3] = (0, packet.size)
packet.append(ECCmd.FPRD, b"\0" * self.pdo_in_sz, 0,
self.position, self.pdo_in_off)
if readwrite and self.pdo_out_sz:
bases[2] = (0, packet.size)
packet.append_writer(ECCmd.FPWR, b"\0" * self.pdo_out_sz, 0,
self.position, self.pdo_out_off)
return bases return bases
def update(self, data): def update(self, data):
...@@ -377,9 +389,13 @@ class FastEtherCat(SimpleEtherCat): ...@@ -377,9 +389,13 @@ class FastEtherCat(SimpleEtherCat):
class SterilePacket(Packet): class SterilePacket(Packet):
"""a sterile packet has all its sets exchanges by NOPs""" """a sterile packet has all its sets exchanges by NOPs"""
next_logical_addr = 0 # global for all packets
logical_addr_inc = 0x800
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.on_the_fly = [] # list of sterilized positions self.on_the_fly = [] # list of sterilized positions
self.fmmu_out_size = self.fmmu_in_size = 0
def append_writer(self, cmd, *args): def append_writer(self, cmd, *args):
self.on_the_fly.append((self.size, cmd)) self.on_the_fly.append((self.size, cmd))
...@@ -391,6 +407,19 @@ class SterilePacket(Packet): ...@@ -391,6 +407,19 @@ class SterilePacket(Packet):
ret[pos] = ECCmd.NOP.value ret[pos] = ECCmd.NOP.value
return ret return ret
def append_fmmu(self):
SterilePacket.next_logical_addr += 2 * self.logical_addr_inc
fmmu_in_pos = self.size
if self.fmmu_in_size:
self.append(ECCmd.LRD, b"\0" * self.fmmu_in_size, 0,
self.next_logical_addr)
fmmu_out_pos = self.size
if self.fmmu_out_size:
self.append_writer(ECCmd.LWR, b"\0" * self.fmmu_out_size, 0,
self.next_logical_addr + self.logical_addr_inc)
return (fmmu_in_pos, fmmu_out_pos, self.next_logical_addr,
self.next_logical_addr + self.logical_addr_inc)
def activate(self, ebpf): def activate(self, ebpf):
for pos, cmd in self.on_the_fly: for pos, cmd in self.on_the_fly:
ebpf.pB[pos + self.ETHERNET_HEADER] = cmd.value ebpf.pB[pos + self.ETHERNET_HEADER] = cmd.value
...@@ -399,6 +428,7 @@ class SyncGroupBase: ...@@ -399,6 +428,7 @@ class SyncGroupBase:
missed_counter = 0 missed_counter = 0
current_data = None current_data = None
logical_in = logical_out = None
def __init__(self, ec, devices, **kwargs): def __init__(self, ec, devices, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
...@@ -418,23 +448,49 @@ class SyncGroupBase: ...@@ -418,23 +448,49 @@ class SyncGroupBase:
async def to_operational(self): async def to_operational(self):
await gather(*[t.to_operational() for t in self.terminals]) await gather(*[t.to_operational() for t in self.terminals])
@asynccontextmanager
async def map_fmmu(self):
async with AsyncExitStack() as stack:
for terminal, bases in self.fmmu_maps.items():
base = bases.get(2)
if base is not None:
await stack.enter_async_context(
terminal.map_fmmu(base, True))
base = bases.get(3)
if base is not None:
await stack.enter_async_context(
terminal.map_fmmu(base, False))
yield
async def run(self): async def run(self):
data = self.asm_packet data = self.asm_packet
while True: async with self.map_fmmu():
self.ec.send_packet(data) ensure_future(self.to_operational())
try: while True:
data = await wait_for(self.ec.receive_index(self.packet_index), self.ec.send_packet(data)
timeout=0.1) try:
except TimeoutError: data = await wait_for(
self.missed_counter += 1 self.ec.receive_index(self.packet_index),
print("didn't receive in time", self.missed_counter) timeout=0.1)
continue except TimeoutError:
data = self.update_devices(data) self.missed_counter += 1
print("didn't receive in time", self.missed_counter)
continue
data = self.update_devices(data)
def allocate(self): def allocate(self):
self.packet = SterilePacket() self.packet = SterilePacket()
self.terminals = {t: t.allocate(self.packet, rw) terminals = {t: t.allocate(self.packet, rw)
for t, rw in self.terminals.items()} for t, rw in self.terminals.items()}
in_pos, out_pos, logical_in, logical_out = self.packet.append_fmmu()
offsets = (0, in_pos, out_pos)
self.terminals = {t: {sm: offsets[base] + off + Packet.DATAGRAM_HEADER
for sm, (base, off) in d.items()}
for t, d in terminals.items()}
offsets = (None, logical_in, logical_out)
self.fmmu_maps = {t: {sm: offsets[base] + off
for sm, (base, off) in d.items() if base != 0}
for t, d in terminals.items()}
class SyncGroup(SyncGroupBase): class SyncGroup(SyncGroupBase):
...@@ -453,9 +509,7 @@ class SyncGroup(SyncGroupBase): ...@@ -453,9 +509,7 @@ class SyncGroup(SyncGroupBase):
self.packet_index = SyncGroup.packet_index self.packet_index = SyncGroup.packet_index
SyncGroup.packet_index += 1 SyncGroup.packet_index += 1
self.asm_packet = self.packet.assemble(self.packet_index) self.asm_packet = self.packet.assemble(self.packet_index)
ret = ensure_future(self.run()) return ensure_future(self.run())
ensure_future(self.to_operational())
return ret
class FastSyncGroup(SyncGroupBase, XDP): class FastSyncGroup(SyncGroupBase, XDP):
...@@ -493,7 +547,6 @@ class FastSyncGroup(SyncGroupBase, XDP): ...@@ -493,7 +547,6 @@ class FastSyncGroup(SyncGroupBase, XDP):
def start(self): def start(self):
self.allocate() self.allocate()
self.task = ensure_future(self.run()) self.task = ensure_future(self.run())
ensure_future(self.to_operational())
return self.task return self.task
def cancel(self): def cancel(self):
......
...@@ -26,7 +26,10 @@ Low-level access to EtherCAT ...@@ -26,7 +26,10 @@ Low-level access to EtherCAT
this modules contains the code to actually talk to EtherCAT terminals. this modules contains the code to actually talk to EtherCAT terminals.
""" """
from asyncio import ensure_future, Event, Future, gather, get_event_loop, Protocol, Queue, Lock from asyncio import (
ensure_future, Event, Future, gather, get_event_loop, Protocol, Queue,
Lock)
from contextlib import asynccontextmanager
from enum import Enum from enum import Enum
from itertools import count from itertools import count
from random import randint from random import randint
...@@ -411,6 +414,8 @@ class Terminal: ...@@ -411,6 +414,8 @@ class Terminal:
await self.set_state(0x11) await self.set_state(0x11)
await self.set_state(1) await self.set_state(1)
self.fmmu_used = [None] * (await self.read(4, "B"))[0]
self.mbx_cnt = 1 self.mbx_cnt = 1
self.mbx_lock = Lock() self.mbx_lock = Lock()
...@@ -609,7 +614,7 @@ class Terminal: ...@@ -609,7 +614,7 @@ class Terminal:
return data[:4] + data2 return data[:4] + data2
async def eeprom_write_one(self, start, data): async def eeprom_write_one(self, start, data):
"""read 2 bytes from the eeprom at `start`""" """write 2 bytes to the eeprom at `start`"""
while (await self.read(0x502, "H"))[0] & 0x8000: while (await self.read(0x502, "H"))[0] & 0x8000:
pass pass
busy = 0x1000 busy = 0x1000
...@@ -837,3 +842,30 @@ class Terminal: ...@@ -837,3 +842,30 @@ class Terminal:
oe.name = data[8:].decode("utf8") oe.name = data[8:].decode("utf8")
od.entries[i] = oe od.entries[i] = oe
return ret return ret
@asynccontextmanager
async def map_fmmu(self, logical, write):
"""map the pdo to `logical` address.
:param write: a boolean indicating whether this is to be used
for writing (instead of reading).
"""
if write:
offset = self.pdo_out_off
size = self.pdo_out_sz
start = 1
else:
offset = self.pdo_in_off
size = self.pdo_in_sz
start = len(self.fmmu_used)
index = start - self.fmmu_used[start::-1].index(None) - 1
self.fmmu_used[index] = logical
try:
await self.write(0x600 + 0x10 * index, "IHBBHBBB3x", logical, size,
0, 7, offset, 0, 2 if write else 1, 1)
yield index
await self.write(0x60c + 0x10 * index, "B", 0)
finally:
self.fmmu_used[index] = None
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment