feat(ethercat): LAN9252 bring-up完成,EEPROM烧录改主站侧并增强DC/AL可观测性

- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪,
  满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放
- 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000;
  ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0)
- 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output,
  否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF;
  gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐
- 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环
  烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过);
  HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址
- DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器,
  task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示
- AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码,
  task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag
- 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B);
  EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控
- CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径,
  邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1)
- motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3
- SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印
- 版本号更新至1.5.32
This commit is contained in:
cbb
2026-09-01 03:07:48 +08:00
parent a2d0b28b3d
commit cfe7725b26
32 changed files with 22382 additions and 1349 deletions
+8 -6
View File
@@ -5,10 +5,11 @@
* 本文件替换 Beckhoff EL9800 SPI 版本,提供 SSC 所需的 HW_EscRead/Write 宏
* 和中断/定时器控制宏。实际实现在 hw_gd32exmc.cpp。
*
* HBI Indexed 寻址(A2=PD12=A17, A1=PD11=A16):
* HBI Indexed 寻址(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16;
* EXMC 16-bit 模式地址左移 1 位,EXMC_A16=CPU 偏移 0x20000、A17=0x40000):
* IDX_0 = EXMC offset 0x00000 (A2=0, A1=0)
* DATA_0 = EXMC offset 0x20000 (A2=1, A1=0) 低 16 位
* = EXMC offset 0x30000 (A2=1, A1=1) 高 16 位
* DATA_0 = EXMC offset 0x40000 (A2=1, A1=0) 低 16 位
* = EXMC offset 0x60000 (A2=1, A1=1) 高 16 位
*/
#ifndef _EL9800HW_H_
#define _EL9800HW_H_
@@ -51,10 +52,11 @@
/*-----------------------------------------------------------------------------------------
------
------ AL Event Interrupt Mask (主循环保护)
------ AL Event Interrupt Mask (保留宏)
------
------ SSC 在主循环中访问 ESC 时需要 mask SYNC0 ISR (EXTI0, preempt=0)
------ 防止 HBI IDX 寄存器被 ISR 抢占导致地址错乱。
------ 1.5.27 起主循环 HBI 访问的互斥已内置到 hw_gd32exmc.cpp 的 HW_* 函数
------ (EscExmcBus::maskEnter/maskExit),SSC 源码不再需要显式调用本组宏。
------ 保留定义仅为兼容 SSC 变体中可能出现的引用。
-----------------------------------------------------------------------------------------*/
#ifndef DISABLE_AL_EVENT_INT
+67 -13
View File
@@ -26,30 +26,73 @@
<Sm StartAddress="#x1400" ControlByte="#x64" Enable="1">Outputs</Sm>
<!-- SM3: Process Data Input (TxPDO, Slave -> Master) -->
<Sm StartAddress="#x1480" ControlByte="#x20" Enable="1">Inputs</Sm>
<!-- RxPDO (Output) -->
<TxPdo Fixed="1" Mandatory="1" Sm="2">
<!-- RxPDO (Output, Master -> Slave): 索引 0x1600-0x17FF,映射 0x7000:01..04
(1.5.27:与 dc_app.c 对齐,4×64bit;原 TxPdo/RxPdo 元素互换 + 1×256bit 均已修正) -->
<RxPdo Fixed="1" Mandatory="1" Sm="2">
<Index>#x1600</Index>
<Name>Output</Name>
<Entry>
<Index>#x7000</Index>
<SubIndex>1</SubIndex>
<BitLen>256</BitLen>
<Name>Control Data</Name>
<DataType>ARRAY [0..31] OF BYTE</DataType>
<BitLen>64</BitLen>
<Name>Control Data 1</Name>
<DataType>ULINT</DataType>
</Entry>
</TxPdo>
<!-- TxPDO (Input) -->
<RxPdo Fixed="1" Mandatory="1" Sm="3">
<Entry>
<Index>#x7000</Index>
<SubIndex>2</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 2</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>3</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 3</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x7000</Index>
<SubIndex>4</SubIndex>
<BitLen>64</BitLen>
<Name>Control Data 4</Name>
<DataType>ULINT</DataType>
</Entry>
</RxPdo>
<!-- TxPDO (Input, Slave -> Master): 索引 0x1A00-0x1BFF,映射 0x6000:01..04 -->
<TxPdo Fixed="1" Mandatory="1" Sm="3">
<Index>#x1A00</Index>
<Name>Input</Name>
<Entry>
<Index>#x6000</Index>
<SubIndex>1</SubIndex>
<BitLen>256</BitLen>
<Name>Status Data</Name>
<DataType>ARRAY [0..31] OF BYTE</DataType>
<BitLen>64</BitLen>
<Name>Status Data 1</Name>
<DataType>ULINT</DataType>
</Entry>
</RxPdo>
<Entry>
<Index>#x6000</Index>
<SubIndex>2</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 2</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>3</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 3</Name>
<DataType>ULINT</DataType>
</Entry>
<Entry>
<Index>#x6000</Index>
<SubIndex>4</SubIndex>
<BitLen>64</BitLen>
<Name>Status Data 4</Name>
<DataType>ULINT</DataType>
</Entry>
</TxPdo>
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" CompleteAccess="true" SegmentedSdo="true"/>
</Mailbox>
@@ -60,6 +103,7 @@
<AssignActivate>#x0300</AssignActivate>
<CycleTimeSync0 Factor="1">0</CycleTimeSync0>
<ShiftTimeSync0>0</ShiftTimeSync0>
<CycleTimeSync1 Factor="1">0</CycleTimeSync1>
</OpMode>
<OpMode>
<Name>Free Run</Name>
@@ -69,7 +113,17 @@
</Dc>
<Eeprom>
<ByteSize>4096</ByteSize>
<ConfigData>050C000000000000</ConfigData>
<!-- SII 前 14 字节 = word0-6(word7 校验和由 TwinCAT 写 EEPROM 时自算,
与 eeprom_image.h 保持一致):
word0 = PDI Control 0x008D(HBI 16-bit indexed,DS 表14-2)
word1 = PDI Config 0x0150=0x00(CS/RD/WR 低有效,EXMC 匹配)
+ Sync/Latch 0x0151=0xCC(SYNC0/1 映射+输出,推挽低有效,
配 EXTI 下降沿;1.5.28 修复——原 0x00 会把 SYNC0 留在
LATCH 输入态,DC 模式下 SYNC0 引脚永不输出、过程数据死)
word2 = Sync 脉宽 0x1388=5000x10ns=50us(0=确认模式,不可用)
word3-6 = 0。
1.5.27 修复:原 0x05 首字节是 LAN9252 保留 PDI 值,会废掉 HBI。 -->
<ConfigData>8D0000CC88130000000000000000</ConfigData>
</Eeprom>
</Device>
</Devices>
+57 -1
View File
@@ -2,13 +2,14 @@
* @file dc_app.c
* @brief DCU EtherCAT 应用层 — SSC APPL_ 回调 + 应用对象字典
*
* 对象字典结构 (匹配 DCU_ESI.xml / eeprom_image.h):
* 对象字典结构 (匹配 DCU_ESI.xml / gen_eeprom.py):
* 0x1C12 RxPDO Assign → 0x1600
* 0x1600 RxPDO Mapping → 0x7000:01..04 (4×64bit = 32 bytes)
* 0x7000 Output Data (主站→从站, 32 bytes)
* 0x1C13 TxPDO Assign → 0x1A00
* 0x1A00 TxPDO Mapping → 0x6000:01..04 (4×64bit = 32 bytes)
* 0x6000 Input Data (从站→主站, 32 bytes)
* 0x2000 SYNC Diag (只读: sync0/sync1/escirq/uptime, 主站 SDO 轮询看 DC 通路)
*/
#include "ecat_def.h"
@@ -48,6 +49,29 @@ TOBJ6000;
static TOBJ7000 s_outputObj = {4, {0, 0, 0, 0}};
static TOBJ6000 s_inputObj = {4, {0, 0, 0, 0}};
/*---------------------------------------------------------------------------
* SYNC 诊断对象 0x2000 (只读, 主站 SDO 轮询)
* sub1=SYNC0 计数 sub2=SYNC1 计数 sub3=ESC IRQ 计数 sub4=uptime 秒
* 值在 APPL_InputMapping 每周期从 EthercatSync 拉取(见 ethercat_sync.cpp 的
* extern "C" getter)。主站用相邻两次 sync0 差 / uptime 差即得 SYNC0 速率。
*---------------------------------------------------------------------------*/
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT32 u32Sync0; /* sub1 */
UINT32 u32Sync1; /* sub2 */
UINT32 u32EscIrq; /* sub3 */
UINT32 u32UptimeS; /* sub4 */
} OBJ_STRUCT_PACKED_END
TOBJ2000;
static TOBJ2000 s_syncDiag = {4, 0, 0, 0, 0};
/* EthercatSync 计数器 getter (C++ 侧 extern "C" 导出) */
extern UINT32 EcatDbg_GetSync0(void);
extern UINT32 EcatDbg_GetSync1(void);
extern UINT32 EcatDbg_GetEscIrq(void);
extern UINT32 EcatDbg_GetUptimeSec(void);
/*---------------------------------------------------------------------------
* PDO Assign 对象 (0x1C12 / 0x1C13)
*---------------------------------------------------------------------------*/
@@ -109,6 +133,12 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asDataEntryDesc[] = {
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE}
};
/* SYNC 诊断 0x2000: Sub0=UINT8 count, Sub1..4=UINT32 (只读) */
OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}
};
/*---------------------------------------------------------------------------
* Object Names
*---------------------------------------------------------------------------*/
@@ -119,6 +149,7 @@ OBJCONST UCHAR OBJMEM aName0x1600[] = "RxPDO mapping";
OBJCONST UCHAR OBJMEM aName0x1A00[] = "TxPDO mapping";
OBJCONST UCHAR OBJMEM aName0x7000[] = "Output Data";
OBJCONST UCHAR OBJMEM aName0x6000[] = "Input Data";
OBJCONST UCHAR OBJMEM aName0x2000[] = "SYNC Diag";
/*---------------------------------------------------------------------------
* Application Object Dictionary
@@ -149,6 +180,10 @@ TOBJECT OBJMEM ApplicationObjDicList[] = {
{NULL, NULL, 0x6000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asDataEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asSyncDiagEntryDesc, aName0x2000, &s_syncDiag, NULL, NULL, 0x0000},
/* 终止条目 */
{NULL, NULL, 0xFFFF, {0, 0}, NULL, NULL, NULL, NULL, NULL, 0x000}
};
@@ -201,6 +236,14 @@ void APPL_AckErrorInd(UINT16 stateTrans)
*---------------------------------------------------------------------------*/
void APPL_InputMapping(UINT16 *pInputData)
{
/* 每 PDO 周期刷新 SYNC 诊断快照(free-run 走 MainLoop、DC 走 Sync0_Isr,
* 两条路径都会到这里)。UINT32 对齐读写在 CM4 上原子,SDO 读侧最坏拿到
* 跨字段相差一个周期的快照,对速率测量无影响。 */
s_syncDiag.u32Sync0 = EcatDbg_GetSync0();
s_syncDiag.u32Sync1 = EcatDbg_GetSync1();
s_syncDiag.u32EscIrq = EcatDbg_GetEscIrq();
s_syncDiag.u32UptimeS = EcatDbg_GetUptimeSec();
memcpy(pInputData, &s_inputObj.u64Data[0], 32);
}
@@ -227,3 +270,16 @@ void APPL_Application(void)
s_inputObj.u64Data[2] = s_outputObj.u64Data[2];
s_inputObj.u64Data[3] = s_outputObj.u64Data[3];
}
/*---------------------------------------------------------------------------
* AL 诊断快照 — task_runner 5s 心跳用 (C 链接, C++ 侧 extern "C" 声明)
* [19:16]=AL 状态(0x0130 低 4 位) [31:24]=AL 错误码(0x0130 高 8 位)
* [15:0] =AL 事件寄存器(0x0220, bit0=主站 AL 控制请求待处理)
*---------------------------------------------------------------------------*/
UINT32 EcatDbg_ReadAlDiag(void)
{
UINT16 alStatus = 0U;
HW_EscReadWord(alStatus, ESC_AL_STATUS_OFFSET);
alStatus = SWAPWORD(alStatus);
return ((UINT32)alStatus << 16) | (UINT32)SWAPWORD(HW_GetALEventRegister());
}
+1 -1
View File
@@ -563,7 +563,7 @@
/*-----------------------------------------------------------------------------------------
------
------ PDO Size (match CONFIG_ETHERCAT_PDO_SIZE in sys/config.h)
------ PDO Size (需与 DCU_ESI.xml / gen_eeprom.py 的 PDO 尺寸一致: 4×64bit=32B)
------
-----------------------------------------------------------------------------------------*/
@@ -1,53 +0,0 @@
/**
* @file eeprom_image.h
* @brief LAN9252 EEPROM (ESI) binary image - auto-generated by gen_eeprom.py
*
* Vendor ID: 0x00000000
* Product Code: 0x00009252
* Revision: 0x00010000
* Serial: 0x00000001
* Size: 4096 bytes (AT24C32)
*
* DO NOT EDIT - regenerate with: python gen_eeprom.py > eeprom_image.h
*/
#ifndef COMM_SSC_PORT_EEPROM_IMAGE_H
#define COMM_SSC_PORT_EEPROM_IMAGE_H
#include <stdint.h>
static const uint8_t ESI_EEPROM_IMAGE[4096] = {
/* 0x0000 */ 0x8D, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x92, 0xDF, 0x9E,
/* 0x0010 */ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x0020 */ 0x00, 0x10, 0x00, 0x01, 0x00, 0x12, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00, 0x12, 0x00, 0x01,
/* 0x0030 */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x0040 */ 0x00, 0x10, 0x00, 0x01, 0x26, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, 0x01, 0x22, 0x00, 0x01, 0x00,
/* 0x0050 */ 0x00, 0x14, 0x20, 0x00, 0x64, 0x00, 0x01, 0x00, 0x80, 0x14, 0x20, 0x00, 0x20, 0x00, 0x01, 0x00,
/* 0x0060 */ 0x01, 0x02, 0x0A, 0x0D, 0x02, 0x0B, 0x44, 0x43, 0x55, 0x20, 0x50, 0x72, 0x6F, 0x6A, 0x65, 0x63,
/* 0x0070 */ 0x74, 0x0C, 0x47, 0x44, 0x33, 0x32, 0x45, 0x35, 0x30, 0x38, 0x20, 0x44, 0x43, 0x55, 0x14, 0x08,
/* 0x0080 */ 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x0090 */ 0x1E, 0x01, 0x01, 0x02, 0x28, 0x10, 0x00, 0x10, 0x00, 0x01, 0x26, 0x00, 0x01, 0x00, 0x00, 0x12,
/* 0x00A0 */ 0x00, 0x01, 0x22, 0x00, 0x01, 0x00, 0x00, 0x14, 0x20, 0x00, 0x64, 0x00, 0x01, 0x00, 0x80, 0x14,
/* 0x00B0 */ 0x20, 0x00, 0x20, 0x00, 0x01, 0x00, 0x32, 0x11, 0x00, 0x16, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00,
/* 0x00C0 */ 0x00, 0x70, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x03, 0x00, 0x00, 0x00, 0x01,
/* 0x00D0 */ 0x00, 0x00, 0x60, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x3C, 0x09, 0x00, 0x03, 0x00, 0x00,
/* 0x00E0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x0100: (0xFF padding) */
/* 0x0200: (0xFF padding) */
/* 0x0300: (0xFF padding) */
/* 0x0400: (0xFF padding) */
/* 0x0500: (0xFF padding) */
/* 0x0600: (0xFF padding) */
/* 0x0700: (0xFF padding) */
/* 0x0800: (0xFF padding) */
/* 0x0900: (0xFF padding) */
/* 0x0A00: (0xFF padding) */
/* 0x0B00: (0xFF padding) */
/* 0x0C00: (0xFF padding) */
/* 0x0D00: (0xFF padding) */
/* 0x0E00: (0xFF padding) */
/* 0x0F00: (0xFF padding) */
};
static const uint32_t ESI_EEPROM_SIZE = 4096U;
#endif /* COMM_SSC_PORT_EEPROM_IMAGE_H */
+219 -251
View File
@@ -1,13 +1,45 @@
#!/usr/bin/env python3
"""
Generate LAN9252 EEPROM binary image (ESI) for GD32E508 DCU.
Generate LAN9252 EEPROM binary image (ESI/SII) for GD32E508 DCU.
Outputs a C header file with the EEPROM content as a byte array.
EEPROM format per ETG.1000.6 (EtherCAT Slave Information):
- All multi-byte values are little-endian
- Addressing is in bytes (the ESC EEPROM interface uses word addresses internally,
but the content is byte-addressed)
- Total size: 4096 bytes (AT24C32)
Layout per ETG.1000.6 section 5.4 "SII coding" (Table 16/17, verified against the
local spec copy ETG1000_6_V1i0i3; 1.5.27 rewrite of the previous generator whose
layout was shifted by 2 words from word 4 onwards and used XOR instead of CRC-8):
Word 0x00 PDI Control (byte0=PDI type 0x8D HBI 16-bit indexed,
byte1=ESC Configuration 0x00)
Word 0x01 PDI Configuration (0x0150 HBI mode: CS/RD/WR active-low,
RD/WR strobe mode -> 0x0000 matches EXMC)
Word 0x02 SyncImpulseLen 0x0000
Word 0x03 PDI Configuration2 0x0000
Word 0x04 Configured Station Alias
Word 0x05-0x06 Reserved (zero)
Word 0x07 Checksum: low byte = CRC-8 (poly 0x07, init 0xFF, no reflection,
no final XOR) over bytes 0..13; high byte = 0x00
Word 0x08-0x09 Vendor ID
Word 0x0A-0x0B Product Code
Word 0x0C-0x0D Revision Number
Word 0x0E-0x0F Serial Number
Word 0x10-0x13 Reserved (zero)
Word 0x14/0x15 Bootstrap Receive Mailbox Offset/Size
Word 0x16/0x17 Bootstrap Send Mailbox Offset/Size
Word 0x18/0x19 Standard Receive Mailbox Offset/Size
Word 0x1A/0x1B Standard Send Mailbox Offset/Size
Word 0x1C Mailbox Protocol (0x0004 = CoE per Table 18; NOT 0x0001)
Word 0x1D-0x3D Reserved (zero)
Word 0x3E Size of EEPROM = size in KiBit + 1 (AT24C32 32 KiBit -> 33)
Word 0x3F Version = 1
Word 0x40 (byte 128) first category. Category header is FOUR bytes:
[type word LE][following data size in words LE]
(old generator used a 2-byte header and started at byte 98).
Terminator: 0xFFFF word (Table 19 "End").
Category types (Table 19): STRINGS=10, General=30, FMMU=40, SyncM=41,
TxPDO=50, RxPDO=51, DC=60 ("future use", omitted - DC config comes from ESI XML).
RxPDO indexes 0x1600-0x17FF (outputs, SM2); TxPDO indexes 0x1A00-0x1BFF (inputs, SM3)
per Table 24. PDO entry datatype byte = CoE/ETG.5101 datatype code (UNSIGNED64=0x1B,
matches dc_app.c DEFTYPE_UNSIGNED64); BitLen is an Unsigned8 in the SII entry.
Usage: python gen_eeprom.py > eeprom_image.h
"""
@@ -15,19 +47,18 @@ import struct
import sys
# ============================================================
# Configuration (must match ecat_def.h and DCU_ESI.xml)
# Configuration (must match ecat_def.h / dc_app.c / DCU_ESI.xml)
# ============================================================
VENDOR_ID = 0x00000000
PRODUCT_CODE = 0x00009252
REVISION_NO = 0x00010000
SERIAL_NO = 0x00000001
# Mailbox configuration
# Mailbox configuration (ESC process RAM: mailbox 0x1000-0x13FF, PDO 0x1400-0x14BF)
MBX_RX_OFFSET = 0x1000 # SM0: Master -> Slave (receive mailbox)
MBX_RX_SIZE = 256
MBX_TX_OFFSET = 0x1200 # SM1: Slave -> Master (send mailbox)
MBX_TX_SIZE = 256
MBX_PROTOCOL = 0x0001 # CoE
# Process Data configuration
PD_OUT_OFFSET = 0x1400 # SM2: RxPDO (Master -> Slave outputs)
@@ -35,23 +66,38 @@ PD_OUT_SIZE = 32
PD_IN_OFFSET = 0x1480 # SM3: TxPDO (Slave -> Master inputs)
PD_IN_SIZE = 32
# PDI type: LAN9252 HBI 16-bit indexed mode
# 数据手册 LAN9252_en.pdf 表 14-2 (PDI 模式选择):
# 0x04=DIG I/O, 0x80=SPI, 0x88~0x8B=HBI 复用, 0x8C=HBI 变址8位, 0x8D=HBI 变址16位
# 本设计用 16-bit HBI Indexed Mode(A1/A2 选 IDX/DAT),必须写 0x8D。
# 注意:0x05 是保留值!若 EEPROM 写入 0x05 或 EEPROM 空白(默认 0x00 保留),
# LAN9252 的 PDI 无效,主机总线(HBI)完全不响应——表现为所有总线读回都是上次写入的回显。
# PDI type: LAN9252 HBI 16-bit indexed mode (DS Table 14-2: 0x8C=HBI 8-bit,
# 0x8D=HBI 16-bit). 0x05 is a reserved value on LAN9252 and disables the HBI PDI.
PDI_CONTROL = 0x8D
PDI_CONFIG = 0x0C
# Word 0 high byte = ESC Configuration register (0x0141): all zero
# (the old 0x0C hit "NOT set by EEPROM" DC bits - removed in 1.5.27).
ESC_CONFIG = 0x00
# Word 1 low byte = PDI Configuration register (0x0150) HBI-mode bits: ALE
# qual / RD_WR mode / CS / RD / WR / ALE polarities. 0x00 = normal active-low
# CS,RD,WR and RD/WR strobe mode - exactly what EXMC NE0/NOE/NWE provides
# (DS p22 pin table: RD/WR/CS "normally active low", polarity bit 0 = normal).
PDI_CONFIG = 0x00
# Word 1 high byte = Sync/Latch PDI Configuration register (0x0151), loaded
# from EEPROM word 1 bits 15:8 (DS Table 12-3 p202). 0xCC = SYNC1 Map=1,
# SYNC1 Output=1, SYNC0 Map=1, SYNC0 Output=1, both Driver/Polarity=00
# (push-pull ACTIVE LOW, DS p247). Active-low pulses pair with the firmware's
# falling-edge EXTI; with 0x00 the SYNC0/1 pins stay LATCH inputs and a DC
# SYNC0 event would never reach PB0 -> process data dead in DC mode (1.5.28).
# Same value as the official LAN9252-EVB-HBIXML.xml ConfigData 890E80CC...
SYNC_LATCH_CFG = 0xCC
# Word 2 = Pulse Length of SyncSignals register (0x0982), units of 10ns
# (DS p284). 5000 = 50us pulse, the LAN9252 EVB default. 0 = Acknowledge Mode
# (level held until PDI reads the SYNC0 status register) - unusable with an
# edge-triggered EXTI sampling design.
SYNC_PULSE_LEN = 5000
EEPROM_SIZE = 4096
EEPROM_SIZE = 4096 # AT24C32
EEPROM_KBIT = 32 # 4096 bytes = 32 KiBit
# ============================================================
# Build EEPROM image
# ============================================================
eeprom = bytearray(EEPROM_SIZE)
# Fill with 0xFF (erased state)
eeprom[:] = b'\xFF' * EEPROM_SIZE
eeprom = bytearray(b'\xFF' * EEPROM_SIZE)
def w16(offset, value):
"""Write 16-bit little-endian value at byte offset."""
@@ -61,274 +107,193 @@ def w32(offset, value):
"""Write 32-bit little-endian value at byte offset."""
struct.pack_into('<I', eeprom, offset, value & 0xFFFFFFFF)
# --- Header (bytes 0-63) ---
# Word 0: PDI Control + PDI Config
eeprom[0] = PDI_CONTROL
eeprom[1] = PDI_CONFIG
def sii_crc8(data):
"""ETG.1000.6 word-7 checksum: CRC-8 poly 0x07, init 0xFF, no reflection."""
crc = 0xFF
for b in data:
crc ^= b
for _ in range(8):
crc = ((crc << 1) ^ 0x07) & 0xFF if (crc & 0x80) else (crc << 1) & 0xFF
return crc
# Word 1: ESC Configuration (Sync Watchdog divider etc.) - 0x0000
w16(2, 0x0000)
# --- Fixed area, words 0x00-0x3F (bytes 0-127) ---
w16(0, (ESC_CONFIG << 8) | PDI_CONTROL) # PDI Control
w16(2, (SYNC_LATCH_CFG << 8) | PDI_CONFIG) # PDI Cfg 0x0150 + Sync/Latch 0x0151
w16(4, SYNC_PULSE_LEN) # Pulse Length of SyncSignals (x10ns)
w16(6, 0x0000) # PDI Configuration2
w16(8, 0x0000) # Configured Station Alias
w16(10, 0x0000) # Reserved
w16(12, 0x0000) # Reserved
# word 7 checksum written below (needs bytes 0-13 finalized first)
# Word 2: PDI Config 2 + Sync Impulse Length - 0x0000
w16(4, 0x0000)
w32(16, VENDOR_ID) # Vendor ID (words 0x08-0x09)
w32(20, PRODUCT_CODE) # Product Code (words 0x0A-0x0B)
w32(24, REVISION_NO) # Revision (words 0x0C-0x0D)
w32(28, SERIAL_NO) # Serial (words 0x0E-0x0F)
# Word 3: Alias + Reserved
w16(6, 0x0000)
w16(32, 0x0000) # Reserved (words 0x10-0x13)
w16(34, 0x0000)
w16(36, 0x0000)
w16(38, 0x0000)
# Words 4-5: Vendor ID
w32(8, VENDOR_ID)
# Words 6-7: Product Code
w32(12, PRODUCT_CODE)
# Words 8-9: Revision Number
w32(16, REVISION_NO)
# Words 10-11: Serial Number
w32(20, SERIAL_NO)
# Words 12-15: Reserved (0x0000)
for i in range(24, 32, 2):
w16(i, 0x0000)
# Word 16: Bootstrap RX mailbox offset
w16(32, MBX_RX_OFFSET)
# Word 17: Bootstrap RX mailbox size
w16(34, MBX_RX_SIZE)
# Word 18: Bootstrap TX mailbox offset
w16(36, MBX_TX_OFFSET)
# Word 19: Bootstrap TX mailbox size
w16(38, MBX_TX_SIZE)
# Word 20: Standard RX mailbox offset
w16(40, MBX_RX_OFFSET)
# Word 21: Standard RX mailbox size
w16(40, MBX_RX_OFFSET) # Bootstrap RX mailbox (words 0x14-0x15)
w16(42, MBX_RX_SIZE)
# Word 22: Standard TX mailbox offset
w16(44, MBX_TX_OFFSET)
# Word 23: Standard TX mailbox size
w16(44, MBX_TX_OFFSET) # Bootstrap TX mailbox (words 0x16-0x17)
w16(46, MBX_TX_SIZE)
w16(48, MBX_RX_OFFSET) # Standard RX mailbox (words 0x18-0x19)
w16(50, MBX_RX_SIZE)
w16(52, MBX_TX_OFFSET) # Standard TX mailbox (words 0x1A-0x1B)
w16(54, MBX_TX_SIZE)
w16(56, 0x0004) # Mailbox Protocol: CoE (word 0x1C)
# Word 24: Mailbox protocol (CoE = bit 0)
w16(48, MBX_PROTOCOL)
# Words 25-31: Reserved
for i in range(50, 64, 2):
for i in range(58, 124, 2): # Reserved (words 0x1D-0x3D)
w16(i, 0x0000)
w16(124, EEPROM_KBIT + 1) # Size = KiBit + 1 (word 0x3E)
w16(126, 0x0001) # Version = 1 (word 0x3F)
# --- SM Configuration (starts at byte 64, word 32) ---
# Each SM entry: 8 bytes
# [0:2] Start address (16-bit LE)
# [2:4] Length (16-bit LE)
# [4] Control byte
# [5] Status (0)
# [6] Activate (1=enabled)
# [7] PDI control (0)
sm_base = 64
# Checksum (word 0x07) over bytes 0..13 - must come after word 0-6 are final
w16(14, sii_crc8(eeprom[0:14]))
def write_sm(idx, start, length, control, activate=1):
off = sm_base + idx * 8
w16(off, start)
w16(off + 2, length)
eeprom[off + 4] = control
eeprom[off + 5] = 0 # status
eeprom[off + 6] = activate
eeprom[off + 7] = 0 # PDI control
# --- Categories start at word 0x40 = byte 128 ---
cat = bytearray()
# SM0: Mailbox Receive (Master -> Slave), 3-buffer mode
write_sm(0, MBX_RX_OFFSET, MBX_RX_SIZE, 0x26)
# SM1: Mailbox Send (Slave -> Master), 3-buffer mode
write_sm(1, MBX_TX_OFFSET, MBX_TX_SIZE, 0x22)
# SM2: Process Data Output (RxPDO), 3-buffer, watchdog trigger
write_sm(2, PD_OUT_OFFSET, PD_OUT_SIZE, 0x64)
# SM3: Process Data Input (TxPDO), 3-buffer
write_sm(3, PD_IN_OFFSET, PD_IN_SIZE, 0x20)
def append_category(cat_type, data):
"""ETG.1000.6 Table 17: [type word][size-in-words word][data...]"""
if len(data) % 2 != 0:
data += b'\x00' # pad to word boundary
cat.extend(struct.pack('<H', cat_type))
cat.extend(struct.pack('<H', len(data) // 2))
cat.extend(data)
# --- FMMU Configuration (after SM, byte 64 + 4*8 = 96) ---
# Each FMMU entry: 1 byte (type: 0x01=used for outputs, 0x02=used for inputs)
fmmu_base = sm_base + 4 * 8 # = 96
eeprom[fmmu_base] = 0x01 # FMMU0: outputs
eeprom[fmmu_base + 1] = 0x02 # FMMU1: inputs
# --- Categories (after FMMU) ---
# Category format: [Type (1 byte)] [Size in words (1 byte)] [Data...]
# Type 0 = end of categories
# Type 10 = Strings
# Type 20 = General
# Type 30 = FMMU
# Type 40 = SM
# Type 50 = PDO (TxPDO/RxPDO)
# Type 60 = DC
cat_offset = fmmu_base + 2 # = 98
# --- String Category (Type 10) ---
# First byte = number of strings, then for each: [len(1)] [chars...]
# STRINGS (10): count, then per string [len][chars]; indices 1-based
strings = [b"DCU Project", b"GD32E508 DCU"]
str_data = bytearray()
str_data.append(len(strings))
for s in strings:
str_data.append(len(s))
str_data.extend(s)
# Pad to even length
if len(str_data) % 2 != 0:
str_data.append(0)
str_words = len(str_data) // 2
append_category(10, bytes(str_data))
eeprom[cat_offset] = 10 # Type: Strings
eeprom[cat_offset + 1] = str_words
eeprom[cat_offset + 2: cat_offset + 2 + len(str_data)] = str_data
cat_offset += 2 + len(str_data)
# General (30): Table 21, 32 bytes
gen = bytearray(32)
gen[0] = 1 # GroupIdx -> "DCU Project"
gen[1] = 2 # ImgIdx -> "GD32E508 DCU"
gen[2] = 2 # OrderIdx -> "GD32E508 DCU" (matches XML <Type>)
gen[3] = 2 # NameIdx
gen[4] = 0 # Reserved
gen[5] = 0x23 # CoE Details: SDO(bit0)+SDO Info(bit1)+CompleteAccess(bit5)
gen[6] = 0 # FoE Details
gen[7] = 0 # EoE Details
gen[8] = 0 # SoEChannels
gen[9] = 0 # DS402Channels
gen[10] = 0 # SysmanClass
gen[11] = 0 # Flags
struct.pack_into('<h', gen, 12, 0) # CurrentOnEBus
struct.pack_into('<H', gen, 16, 0x0011) # Physical Port: port0/port1 = MII ("YY")
append_category(30, bytes(gen))
# --- General Category (Type 20) ---
# Format: [GroupInfo(1)] [ImageName(1)] [OrderIdx(1)] [CoE details... ]
# Simplified: just enough for TwinCAT to display
gen_data = bytearray(16)
gen_data[0] = 1 # Group info index (1-based into strings? or group ID)
gen_data[1] = 2 # Image name string index
gen_data[2] = 0 # Order index
gen_data[3] = 0 # CoE details: SDO info supported
gen_data[4] = 0x01 # CoE: SDO info
gen_data[5] = 0x00
gen_data[6] = 0 # FoE/EoE/etc flags
gen_data[7] = 0
# Physics: YY ( EtherCAT on both ports)
gen_data[8] = 0x03 # Physics byte: port0=YY, port1=YY
gen_data[9] = 0
# Watchdog divider
gen_data[10] = 0
gen_data[11] = 0
# SM watchdog
gen_data[12] = 0
gen_data[13] = 0
# SM watchdog divider
gen_data[14] = 0
gen_data[15] = 0
gen_words = len(gen_data) // 2
# FMMU (40): FMMU0=outputs(0x01), FMMU1=inputs(0x02)
append_category(40, bytes([0x01, 0x02]))
eeprom[cat_offset] = 20 # Type: General
eeprom[cat_offset + 1] = gen_words
eeprom[cat_offset + 2: cat_offset + 2 + len(gen_data)] = gen_data
cat_offset += 2 + len(gen_data)
# SyncM (41): Table 23, 8 bytes per SM:
# [StartAddr(2)][Length(2)][Control(1)][Status(1)][Enable(1)][Type(1)]
# Type: 1=mailbox out, 2=mailbox in, 3=process outputs, 4=process inputs
sm_entries = [
(MBX_RX_OFFSET, MBX_RX_SIZE, 0x26, 0x01, 0x01), # SM0 mailbox out
(MBX_TX_OFFSET, MBX_TX_SIZE, 0x22, 0x01, 0x02), # SM1 mailbox in
(PD_OUT_OFFSET, PD_OUT_SIZE, 0x64, 0x01, 0x03), # SM2 process outputs
(PD_IN_OFFSET, PD_IN_SIZE, 0x20, 0x01, 0x04), # SM3 process inputs
]
sm_data = bytearray()
for start, length, control, enable, sm_type in sm_entries:
sm_data.extend(struct.pack('<H', start))
sm_data.extend(struct.pack('<H', length))
sm_data.append(control)
sm_data.append(0) # Status: don't care
sm_data.append(enable) # Enable: bit0 = enable
sm_data.append(sm_type)
append_category(41, bytes(sm_data))
# --- FMMU Category (Type 30) ---
# List of FMMU usages (1 byte each): 0x01=Outputs, 0x02=Inputs
fmmu_cat = bytearray([0x01, 0x02])
if len(fmmu_cat) % 2 != 0:
fmmu_cat.append(0)
fmmu_words = len(fmmu_cat) // 2
def append_pdo(cat_type, pdo_index, sm_index, obj_index):
"""Table 24/25: header [index(2)][nEntry(1)][SyncM(1)][Sync(1)][NameIdx(1)]
[Flags(2)] + entries [idx(2)][sub(1)][name(1)][dtype(1)][bitlen(1)][flags(2)]."""
data = bytearray()
data.extend(struct.pack('<H', pdo_index))
data.append(4) # nEntry: 0x7000/0x6000:01..04 (dc_app.c)
data.append(sm_index) # SyncM
data.append(0) # DC Synchronization
data.append(0) # NameIdx
data.extend(struct.pack('<H', 0)) # Flags
for sub in range(1, 5):
data.extend(struct.pack('<H', obj_index))
data.append(sub) # SubIndex
data.append(0) # Entry NameIdx
data.append(0x1B) # DataType: UNSIGNED64 (ETG.5101 0x1B)
data.append(64) # BitLen (Unsigned8 in SII)
data.extend(struct.pack('<H', 0)) # Flags
append_category(cat_type, bytes(data))
eeprom[cat_offset] = 30 # Type: FMMU
eeprom[cat_offset + 1] = fmmu_words
eeprom[cat_offset + 2: cat_offset + 2 + len(fmmu_cat)] = fmmu_cat
cat_offset += 2 + len(fmmu_cat)
append_pdo(51, 0x1600, 2, 0x7000) # RXPDO: outputs (SM2)
append_pdo(50, 0x1A00, 3, 0x6000) # TXPDO: inputs (SM3)
# --- SM Category (Type 40) ---
# Each SM: [StartAddr(2)] [Length(2)] [Control(1)] [Status(1)] [Activate(1)] [PDI(1)]
sm_cat = bytearray()
for idx in range(4):
off = sm_base + idx * 8
sm_cat.extend(eeprom[off:off+8])
sm_words = len(sm_cat) // 2
# DC (60) is "for future use" per Table 19 and has no defined payload;
# DC configuration comes from DCU_ESI.xml <Dc> - omitted on purpose.
eeprom[cat_offset] = 40 # Type: SM
eeprom[cat_offset + 1] = sm_words
eeprom[cat_offset + 2: cat_offset + 2 + len(sm_cat)] = sm_cat
cat_offset += 2 + len(sm_cat)
# End of categories: word 0xFFFF (Table 19 "End")
cat.extend(struct.pack('<H', 0xFFFF))
# --- PDO Category (Type 50) ---
# TxPDO (RxPDO from master perspective, SM2):
# [Index(2)] [SyncManager(1)] [DC Sync(1)] [NameIdx(1)] [Flags(1)] [EntryCount(1)] [Reserved(1)]
# Then entries: [Index(2)] [SubIndex(1)] [NameIdx(1)] [DataType(1)] [BitLen(2)] [Flags(2)]
pdo_cat = bytearray()
# TxPDO 0x1600 (assigned to SM2, outputs)
pdo_cat.extend(struct.pack('<H', 0x1600)) # Index
pdo_cat.append(2) # SyncManager = SM2
pdo_cat.append(0) # DC Sync
pdo_cat.append(0) # Name string index (0 = use index as name)
pdo_cat.append(0x00) # Flags (fixed, mandatory)
pdo_cat.append(1) # Entry count
pdo_cat.append(0) # Reserved
# Entry 1: 0x7000:01, 256 bits
pdo_cat.extend(struct.pack('<H', 0x7000)) # Index
pdo_cat.append(1) # SubIndex
pdo_cat.append(0) # Name string index
pdo_cat.append(0x0F) # DataType: ARRAY OF BYTE (0x0F = BYTE in CoE)
pdo_cat.extend(struct.pack('<H', 256)) # BitLen
pdo_cat.extend(struct.pack('<H', 0)) # Flags
# RxPDO 0x1A00 (assigned to SM3, inputs)
pdo_cat.extend(struct.pack('<H', 0x1A00)) # Index
pdo_cat.append(3) # SyncManager = SM3
pdo_cat.append(0) # DC Sync
pdo_cat.append(0) # Name string index
pdo_cat.append(0x00) # Flags
pdo_cat.append(1) # Entry count
pdo_cat.append(0) # Reserved
# Entry 1: 0x6000:01, 256 bits
pdo_cat.extend(struct.pack('<H', 0x6000)) # Index
pdo_cat.append(1) # SubIndex
pdo_cat.append(0) # Name string index
pdo_cat.append(0x0F) # DataType: BYTE
pdo_cat.extend(struct.pack('<H', 256)) # BitLen
pdo_cat.extend(struct.pack('<H', 0)) # Flags
if len(pdo_cat) % 2 != 0:
pdo_cat.append(0)
pdo_words = len(pdo_cat) // 2
eeprom[cat_offset] = 50 # Type: PDO
eeprom[cat_offset + 1] = pdo_words
eeprom[cat_offset + 2: cat_offset + 2 + len(pdo_cat)] = pdo_cat
cat_offset += 2 + len(pdo_cat)
# --- DC Category (Type 60) ---
# DC settings: [AssignActivate(2)] [Sync0 Cycle(4)] [Sync0 Shift(4)] [Sync1 Cycle(4)] [Sync1 Shift(4)]
dc_cat = bytearray()
dc_cat.extend(struct.pack('<H', 0x0300)) # AssignActivate: SYNC0 enabled
dc_cat.extend(struct.pack('<I', 0)) # SYNC0 cycle time (0 = set by master)
dc_cat.extend(struct.pack('<I', 0)) # SYNC0 shift time
dc_cat.extend(struct.pack('<I', 0)) # SYNC1 cycle time (unused)
dc_cat.extend(struct.pack('<I', 0)) # SYNC1 shift time
dc_words = len(dc_cat) // 2
eeprom[cat_offset] = 60 # Type: DC
eeprom[cat_offset + 1] = dc_words
eeprom[cat_offset + 2: cat_offset + 2 + len(dc_cat)] = dc_cat
cat_offset += 2 + len(dc_cat)
# --- End of categories ---
eeprom[cat_offset] = 0 # Type 0 = end
eeprom[cat_offset + 1] = 0
if len(cat) > EEPROM_SIZE - 128:
raise SystemExit("category area overflow")
eeprom[128:128 + len(cat)] = cat
# ============================================================
# Compute checksum (word 0x0007, byte offset 14)
# Checksum = XOR of all words from offset 0 to (checksum_word - 1)
# Then stored so that XOR of all words 0..7 == 0x0000
# Self-check: re-parse the generated image
# ============================================================
# Actually per ETG.1000.6: checksum is at word 7 (byte 14-15)
# It's computed as: XOR of words 0..6, then checksum = ~xor (bitwise NOT)
# such that XOR of words 0..7 == 0xFFFF? No...
# Per spec: "The checksum is calculated by XORing all words from 0x00 to 0x06.
# The result is stored in word 0x07."
# Verification: XOR of words 0x00..0x07 should equal 0x0000.
xor_val = 0
for i in range(0, 14, 2):
xor_val ^= struct.unpack_from('<H', eeprom, i)[0]
w16(14, xor_val)
def u16(off):
return struct.unpack_from('<H', eeprom, off)[0]
def u32(off):
return struct.unpack_from('<I', eeprom, off)[0]
assert u16(0) == 0x008D, hex(u16(0))
assert u16(14) == sii_crc8(eeprom[0:14]) and (u16(14) >> 8) == 0
assert u32(16) == VENDOR_ID and u32(20) == PRODUCT_CODE
assert u32(24) == REVISION_NO and u32(28) == SERIAL_NO
assert u16(40) == MBX_RX_OFFSET and u16(48) == MBX_RX_OFFSET
assert u16(52) == MBX_TX_OFFSET and u16(56) == 0x0004
assert u16(124) == EEPROM_KBIT + 1 and u16(126) == 1
off = 128
types = []
while off < EEPROM_SIZE:
t, size = u16(off), u16(off + 2)
if t == 0xFFFF:
types.append('END')
break
types.append(t)
off += 4 + size * 2
assert types == [10, 30, 40, 41, 51, 50, 'END'], types
print("[gen_eeprom] self-check OK: checksum=0x%02X, categories=%s" %
(u16(14) & 0xFF, types), file=sys.stderr)
# ============================================================
# Output as C header
# ============================================================
print("/**")
print(" * @file eeprom_image.h")
print(" * @brief LAN9252 EEPROM (ESI) binary image - auto-generated by gen_eeprom.py")
print(" * @brief LAN9252 EEPROM (ESI/SII) binary image - auto-generated by gen_eeprom.py")
print(" *")
print(" * Layout per ETG.1000.6 section 5.4 (SII coding): identity at words 8-15,")
print(" * mailbox descriptors at words 20-28, Size/Version at words 62-63,")
print(" * categories from byte 128 with 4-byte headers, CRC-8 checksum in word 7.")
print(" *")
print(f" * Vendor ID: 0x{VENDOR_ID:08X}")
print(f" * Product Code: 0x{PRODUCT_CODE:08X}")
print(f" * Revision: 0x{REVISION_NO:08X}")
print(f" * Serial: 0x{SERIAL_NO:08X}")
print(f" * PDI Control: 0x{(ESC_CONFIG << 8) | PDI_CONTROL:04X} (HBI 16-bit indexed)")
print(f" * PDI Config: 0x{(SYNC_LATCH_CFG << 8) | PDI_CONFIG:04X} (0150=active-low strobes, 0151=SYNC0/1 output)")
print(f" * Sync Pulse: {SYNC_PULSE_LEN} (reg 0x0982, x10ns = {SYNC_PULSE_LEN * 10}us)")
print(f" * Checksum: 0x{u16(14) & 0xFF:02X} (CRC-8 poly 0x07 init 0xFF over bytes 0-13)")
print(f" * Size: {EEPROM_SIZE} bytes (AT24C32)")
print(" *")
print(" * DO NOT EDIT - regenerate with: python gen_eeprom.py > eeprom_image.h")
@@ -343,10 +308,13 @@ print(f"static const uint8_t ESI_EEPROM_IMAGE[{EEPROM_SIZE}] = {{")
for row in range(0, EEPROM_SIZE, 16):
chunk = eeprom[row:row+16]
hex_str = ", ".join(f"0x{b:02X}" for b in chunk)
# Only print non-0xFF rows with annotation, skip bulk 0xFF
if all(b == 0xFF for b in chunk):
if row == 64 or row % 256 == 0:
print(f" /* 0x{row:04X}: (0xFF padding) */")
# 1.5.28: emit the real 0xFF bytes. A comment-only padding row leaves
# those array elements zero-initialized (C static init) instead of
# the erased-0xFF state, and the firmware burns the whole array.
if row % 256 == 0:
print(f" /* 0x{row:04X}: 0xFF erase fill */")
print(f" /* 0x{row:04X} */ {hex_str},")
continue
print(f" /* 0x{row:04X} */ {hex_str},")
+32 -11
View File
@@ -5,16 +5,18 @@
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
* 转发到 EscExmcBus (EXMC NOR/SRAM Region0, 16-bit 内存映射)。
*
* HBI Indexed 寻址协议:
* HBI Indexed 寻址协议(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16;
* EXMC 16-bit 模式地址左移 1 位,见 esc_lan9252.cpp 头注释):
* 1. 写 HBI_IDX_0 (EXMC offset 0x00000) = ESC 寄存器字节地址
* 2. 读/写 HBI_DATA_0 低 word (EXMC offset 0x20000, A17=1)
* 3. 读/写 HBI_DATA_0 高 word (EXMC offset 0x30000, A17=1, A16=1)
* 2. 读/写 HBI_DATA_0 低 word (EXMC offset 0x40000, A2=1/A1=0)
* 3. 读/写 HBI_DATA_0 高 word (EXMC offset 0x60000, A2=1/A1=1)
*
* 并发保护:
* - HW_EscRead/HW_EscWrite (主循环):调用前 SSC 已执行 DISABLE_AL_EVENT_INT
* (mask EXTI0+EXTI1),保证 HBI IDX 不被 ISR 抢占。
* - HW_EscRead/HW_EscWrite 等主循环函数:内部经 EscExmcBus::maskEnter/Exit
* 屏蔽 EXTI0+EXTI1(HBI Indexed 是两步事务,不可被 ISR 打断)。
* - HW_EscReadIsr/HW_EscWriteIsr (ISR):在 preempt=0 的 SYNC0 ISR 内调用,
* 天然不会被抢占。
* 天然不会被抢占;PDI_Isr/Sync1_Isr(preempt=1) 由 ethercat_sync.cpp 包装器
* 屏蔽 EXTI0 后调用。
*/
/* SSC 配置与类型 */
@@ -37,8 +39,8 @@
namespace {
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x20000U; /* A2=1(A17), A1=0(A16) */
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x30000U; /* A2=1(A17), A1=1(A16) */
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x40000U; /* A2=1(EXMC_A17), A1=0(EXMC_A16) */
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x60000U; /* A2=1(EXMC_A17), A1=1(EXMC_A16) */
/* AL Event Mask 寄存器影子 (ESC 0x0204) */
volatile uint16_t s_alEventMask = 0xFFFFU;
@@ -184,12 +186,15 @@ void HW_Release(void)
/**
* @brief 读取 AL Event 寄存器 (0x0220),主循环上下文。
* @note 调用者已执行 DISABLE_AL_EVENT_INT。
* @note 内部屏蔽 EXTI0+EXTI1 保证 HBI 事务完整。
*/
UINT16 HW_GetALEventRegister(void)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(ESC_AL_EVENT_OFFSET);
return hbiReadLow();
const UINT16 v = hbiReadLow();
EscExmcBus::maskExit(prev);
return v;
}
/**
@@ -206,9 +211,11 @@ UINT16 HW_GetALEventRegister_Isr(void)
*/
void HW_ResetALEventMask(UINT16 intMask)
{
const uint32_t prev = EscExmcBus::maskEnter();
s_alEventMask &= ~intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
EscExmcBus::maskExit(prev);
}
/**
@@ -216,9 +223,11 @@ void HW_ResetALEventMask(UINT16 intMask)
*/
void HW_SetALEventMask(UINT16 intMask)
{
const uint32_t prev = EscExmcBus::maskEnter();
s_alEventMask |= intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
EscExmcBus::maskExit(prev);
}
/**
@@ -228,6 +237,7 @@ void HW_SetALEventMask(UINT16 intMask)
*/
void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
{
const uint32_t prev = EscExmcBus::maskEnter();
/* Run LED override (0x0138): bit0 = LED on */
hbiSetIndex(ESC_RUN_LED_OVERRIDE);
hbiWriteLow(RunLed ? 0x0001U : 0x0000U);
@@ -235,20 +245,25 @@ void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
/* Error LED override (0x0139): bit0 = LED on */
hbiSetIndex(ESC_ERROR_LED_OVERRIDE);
hbiWriteLow(ErrLed ? 0x0001U : 0x0000U);
EscExmcBus::maskExit(prev);
}
/*---------------------------------------------------------------------------
* ESC 内存读写 — 主循环版本 (SSC 已 mask 中断)
* ESC 内存读写 — 主循环版本 (内部带 HBI 互斥守卫)
*---------------------------------------------------------------------------*/
void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiRead(pData, Address, Len);
EscExmcBus::maskExit(prev);
}
void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiWrite(pData, Address, Len);
EscExmcBus::maskExit(prev);
}
/*---------------------------------------------------------------------------
@@ -279,11 +294,13 @@ void HW_DisableSyncManChannel(UINT8 channel)
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val &= ~0x0001U; /* 清 bit0 = deactivate */
hbiSetIndex(activateAddr);
hbiWriteLow(val);
EscExmcBus::maskExit(prev);
}
/**
@@ -294,11 +311,13 @@ void HW_EnableSyncManChannel(UINT8 channel)
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val |= 0x0001U;
hbiSetIndex(activateAddr);
hbiWriteLow(val);
EscExmcBus::maskExit(prev);
}
/**
@@ -317,7 +336,9 @@ static TSYNCMAN s_smBuf;
TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel)
{
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
const uint32_t prev = EscExmcBus::maskEnter();
hbiRead(&s_smBuf, smBase, SIZEOF_SM_REGISTER);
EscExmcBus::maskExit(prev);
return &s_smBuf;
}
+8
View File
@@ -172,6 +172,7 @@ V4.00 ECAT 7: The return values for the AL-StatusCode were changed to UINT16
#define _ECATSLV_ 1
#include "ecatslv.h"
#include <stdio.h> /* 1.5.32 bring-up 诊断: AL 事件/状态迁移打印 */
#undef _ECATSLV_
/* ECATCHANGE_START(V5.11) ECAT10*/
/*remove definition of _ECATSLV_ (#ifdef is used in ecatslv.h)*/
@@ -1335,6 +1336,9 @@ void SetALStatus(UINT8 alStatus, UINT16 alStatusCode)
Value = SWAPWORD(Value);
HW_EscWriteWord(Value,ESC_AL_STATUS_OFFSET);
/* 1.5.32 bring-up: SSC 写回的 AL 状态+错误码(与主站读到的不一致=ESC 写路径问题) */
printf("[ECAT][AL] -> 0x%02X code=0x%04X\r\n",
(unsigned)nAlStatus, (unsigned)alStatusCode);
}
/////////////////////////////////////////////////////////////////////////////////////////
@@ -2408,6 +2412,10 @@ void ECAT_Main(void)
HW_EscReadWord( EscAlControl, ESC_AL_CONTROL_OFFSET);
EscAlControl = SWAPWORD(EscAlControl);
/* 1.5.32 bring-up: PDI 看到主站的 AL 控制请求(无此行=事件未到 PDI/主循环卡死) */
printf("[ECAT][AL] evt=0x%04X req=0x%02X\r\n",
(unsigned)ALEventReg, (unsigned)(EscAlControl & 0x0FU));
/* reset AL Control event and the SM Change event (because the Sync Manager settings will be checked
in AL_ControlInd, too)*/