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:
@@ -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>
|
||||
|
||||
Binary file not shown.
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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},")
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user