fixed axis 4 warning

This commit is contained in:
2026-09-29 17:41:11 +08:00
parent 720bd81031
commit e556c131bf
8 changed files with 171 additions and 5 deletions
+112
View File
@@ -0,0 +1,112 @@
'*******************************************************ECAT总线初始化
'''''''''
global CONST BUS_TYPE = 0 '总线类型。用于上位机区分当前模式
global CONST MAX_AXISNUM = 32 '最大轴数
global CONST Bus_Slot = 0 '槽位号0
global CONST Bus_AxisStart = 0 '总线轴起始轴号
global Bus_InitStatus '总线初始化完成状态
Bus_InitStatus = -1
global Bus_TotalAxisnum '检查扫描的总轴数
delay(3000) '延时3S等待驱动器上电
'**********************初始化ECAT总线
Ecat_Init()
end
global sub Ecat_Init()
for i=0 to MAX_AXISNUM - 1 '初始化还原轴类型
AXIS_ENABLE(i) = 0
atype(i)=0
next
Bus_InitStatus = -1
Bus_TotalAxisnum = 0
SLOT_STOP(Bus_Slot)
delay(200)
slot_scan(Bus_Slot) '开始扫描
if return then
?"总线扫描成功","连接设备数:"NODE_COUNT(Bus_Slot)
?
?"开始映射轴号"
for i=0 to NODE_COUNT(Bus_Slot)-1 '遍历总线下所有从站节点
if NODE_AXIS_COUNT(Bus_Slot,i) <>0 then '判断当前节点是否有电机
for j=0 to NODE_AXIS_COUNT(Bus_Slot,i)-1
AXIS_ADDRESS(Bus_AxisStart+i)=Bus_TotalAxisnum+1 '映射轴号
ATYPE(Bus_AxisStart+i)=65 '设置控制模式 65-位置 66-速度 67-转矩
DRIVE_PROFILE(Bus_AxisStart+i)= 4 '设置PROFILE功能
disable_group(Bus_AxisStart+i) '每轴单独分组
DRIVE_IO(Bus_AxisStart+i) = 128 + (Bus_AxisStart+i)*16 '映射驱动器上的IO状态
REV_IN(Bus_AxisStart+i) = 128 + (Bus_AxisStart+i)*16
FWD_IN(Bus_AxisStart+i) = 129 + (Bus_AxisStart+i)*16
DATUM_IN(Bus_AxisStart+i) = 130 + (Bus_AxisStart+i)*16
INVERT_IN(128 + (Bus_AxisStart+i)*16,ON)
INVERT_IN(129 + (Bus_AxisStart+i)*16,ON)
INVERT_IN(130 + (Bus_AxisStart+i)*16,ON)
Bus_TotalAxisnum=Bus_TotalAxisnum+1 '总轴数+1
next
endif
next
' Axis 4 is a Panasonic MINAS A6 servo, not a Leadshine CL3B stepper:
' its limit/datum inputs are active-high as the ZMC reads them, so they
' must NOT be inverted like the steppers above. Override the loop for
' that one axis only: Bus_TotalAxisnum-1 is the last axis the scan found,
' which on this machine is the servo and never a stepper.
INVERT_IN(128 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF)
INVERT_IN(129 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF)
INVERT_IN(130 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF)
?"轴号映射完成","连接总轴数:"Bus_TotalAxisnum
wa 2000
SLOT_START(Bus_Slot) '启动总线
if return then
?"总线开启成功"
?"开始清除驱动器错误(根据驱动器数据字典设置)"
for i= Bus_AxisStart to Bus_AxisStart + Bus_TotalAxisnum - 1
DRIVE_CONTROLWORD(i)=128 '根据驱动器数据字典
wa 100
DRIVE_CONTROLWORD(i)=6
wa 100
DRIVE_CONTROLWORD(i)=15
wa 100
next
?"驱动器错误清除完成"
wa 100
?"清除控制器错误"
datum(0)
DRIVE_CLEAR(0)
?"控制器错误清除完成"
wa 100
?"轴使能准备"
for i= Bus_AxisStart to Bus_AxisStart + Bus_TotalAxisnum - 1
base(i)
AXIS_ENABLE=1
next
wdog=1 '使能总开关
Bus_InitStatus = 1
?"轴使能完成"
else
?"总线开启失败"
Bus_InitStatus = 0
endif
else
?"总线扫描失败"
Bus_InitStatus = 0
endif
end sub
+2
View File
@@ -103,6 +103,8 @@
"state_pending": "Not started",
"state_running": "Initialising…",
"state_ok": "Initialised",
"state_unverified": "Init not confirmed",
"unverified_hint": "The controller still holds the initialisation flag from its last run; this power-up has not re-scanned the bus. If the platform will not move, download the BAS file and re-initialise.",
"state_failed": "Initialisation failed",
"state_timeout": "Initialisation timed out",
"nodes": "Nodes",
+2
View File
@@ -103,6 +103,8 @@
"state_pending": "未完成",
"state_running": "正在初始化…",
"state_ok": "初始化成功",
"state_unverified": "未确认初始化",
"unverified_hint": "控制器保留了上次的总线初始化标志,本次开机尚未重新扫描;如无法运动,请先下载 BAS 文件并重新初始化。",
"state_failed": "初始化失败",
"state_timeout": "初始化超时",
"nodes": "节点数量",
@@ -111,7 +111,16 @@ class AxisState:
@dataclass(frozen=True)
class BusStatus:
"""Bus-level state, from the ``.bas`` program's globals."""
"""Bus-level state, from the ``.bas`` program's globals.
``verified_this_session`` exists because ``BUS_TYPE``, ``Bus_InitStatus``
and ``Bus_TotalAxisnum`` are ZBasic **globals**: they live in controller
memory and survive the program finishing, a reconnect, and (with the ROM
program reloading) a reboot. ``Bus_InitStatus == 1`` therefore means "a scan
completed at some point", not "this bus is live now". A controller that was
power-cycled but not re-initialised still reports 1, and reporting that as
初始化成功 is what made a bus which had never been set up look ready.
"""
bus_type: BusType = "unknown"
init_status: int = INIT_RUNNING
@@ -121,6 +130,14 @@ class BusStatus:
bas_loaded: bool = False
init_started_at: float | None = None
message: str = ""
#: False until this session has seen the state machine through a run it
#: started (a download or a re-init). An adopted latch starts False.
verified_this_session: bool = False
@property
def adopted_unverified(self) -> bool:
"""The latch says initialised, but nothing here has confirmed it."""
return self.init_status == INIT_OK and not self.verified_this_session
@property
def initialized(self) -> bool:
@@ -401,7 +401,9 @@ class MotionService:
self._spec = spec
logger.info("Connected: %s via %s", spec.describe(), self._transport.source)
# A controller may already have a BAS program running from a previous
# session; adopt it rather than claiming the bus is uninitialised.
# session; adopt it rather than claiming the bus is uninitialised. The
# adopted state is *not* marked verified: its globals are a latch, and
# nothing in this session has yet seen a scan happen.
self._adopt_existing_bas(bas_path=bas_path)
return MotionResult.success(f"已连接 {spec.describe()}" if self._is_zh else f"Connected {spec.describe()}")
@@ -469,12 +471,14 @@ class MotionService:
self._bas_loaded = True
self._init_started_at = time.monotonic()
# A download is a run this session owns, so its outcome can be trusted.
self._bus = BusStatus(
bus_type=self._bus.bus_type,
init_status=INIT_RUNNING,
bas_path=filename,
bas_loaded=True,
init_started_at=self._init_started_at,
verified_this_session=True,
)
# Read BUS_TYPE so the SDO/RTEX panels enable for the right family.
try:
@@ -541,6 +545,7 @@ class MotionService:
bas_path=self._bus.bas_path,
bas_loaded=True,
init_started_at=self._init_started_at,
verified_this_session=True,
)
# Re-read BUS_TYPE now: the program may be a different family than the
# one the previous session left behind.
@@ -662,7 +667,13 @@ class MotionService:
)
return
verified = bus.verified_this_session
if init_status != INIT_RUNNING:
# A run this session owned has just settled: that is the only
# evidence available that the state machine actually moved, rather
# than the globals having been left at their last values.
if self._init_started_at is not None:
verified = True
self._init_started_at = None
try:
node_count = self._transport.bus_node_count(0)
@@ -679,6 +690,7 @@ class MotionService:
bas_loaded=True,
init_started_at=self._init_started_at,
message=message,
verified_this_session=verified,
)
def _read_axis(self, axis: int) -> AxisState:
@@ -967,4 +979,7 @@ def _replace_bus_type(bus: BusStatus, bus_type: BusType) -> BusStatus:
bas_loaded=bus.bas_loaded,
init_started_at=bus.init_started_at,
message=bus.message,
# Carried, not reset: re-reading BUS_TYPE is incidental, and dropping
# this would make a just-verified init look unverified again.
verified_this_session=bus.verified_this_session,
)
@@ -36,6 +36,7 @@ _STATE_KEYS = {
_STATE_TONES = {
"bus.state_running": "warn",
"bus.state_ok": "ok",
"bus.state_unverified": "warn",
"bus.state_failed": "error",
"bus.state_timeout": "error",
}
@@ -140,10 +141,19 @@ class BusPanel(QGroupBox):
self.choose_button.setEnabled(True)
def set_status(self, bus: BusStatus, *, now: float | None = None) -> None:
"""Render the bus state, including the vendor's timeout case."""
"""Render the bus state, including the vendor's timeout case.
An *adopted* ``Bus_InitStatus == 1`` is shown as unverified rather than
as success. The value is a ZBasic global that survives the program and a
reboot, so it proves a scan happened at some point, not that this bus is
live now — and calling that 初始化成功 is what made a machine which had
never been set up look ready to move.
"""
key = _STATE_KEYS.get(bus.init_status, "bus.state_pending")
if bus.init_status == INIT_FAILED and bus.message and "超时" in bus.message:
key = "bus.state_timeout"
elif bus.adopted_unverified:
key = "bus.state_unverified"
self._state_text = t(key)
self._state_tone = _STATE_TONES.get(key, "muted")
self.state_value.setText(self._state_text)
@@ -156,7 +166,7 @@ class BusPanel(QGroupBox):
self.type_value.setText(t(type_keys.get(bus.bus_type, "bus.type_unknown")))
self.nodes_value.setText(str(bus.node_count))
self.axes_value.setText(str(bus.total_axes))
self.message_label.setText(bus.message)
self.message_label.setText(bus.message or (t("bus.unverified_hint") if bus.adopted_unverified else ""))
# ------------------------------------------------------------- translations
@@ -224,6 +224,12 @@ class StatusStrip(QFrame):
if not connected:
self.chip_init.setText(t("bus.state_pending"))
self.chip_init.set_tone("muted")
elif snapshot.bus.adopted_unverified:
# The controller's initialisation global is a latch that survives the
# program and a reboot; without a run this session, "initialised" is
# not something this process has seen. Say so rather than claim it.
self.chip_init.setText(t("bus.state_unverified"))
self.chip_init.set_tone("warn")
elif snapshot.bus.initialized:
self.chip_init.setText(t("bus.state_ok"))
self.chip_init.set_tone("ok")
+3 -1
View File
@@ -878,7 +878,9 @@ class AutoCalMainWindow(QMainWindow):
t("status.poll_paused") if self.status_strip.paused else t("status.poll_ok"),
t("status.poll_every", ms=self.poller.interval_ms),
]
if snapshot.bus.init_status == 1:
if snapshot.bus.adopted_unverified:
parts.append(t("bus.state_unverified"))
elif snapshot.bus.init_status == 1:
parts.append(t("bus.state_ok"))
if self._last_error:
parts.append(f"{t('status.last_error')}: {self._last_error}")