feat(project): 更新项目配置并增加ESC EEPROM功能

- 添加新的SPI命令文件:app_spi_cmd_can_burst.cpp 和 app_spi_cmd_exmc_diag.cpp
- 移除旧的SPI命令文件:app_spi_cmd_ota_wait.cpp, app_spi_cmd_ping.cpp,
  app_spi_cmd_can_bridge.cpp, app_spi_cmd_can_feedback.cpp,
  app_spi_cmd_id_snapshot.cpp, app_spi_cmd_adc_feedback.cpp,
  app_spi_cmd_motor_state.cpp, app_spi_cmd_motor_version.cpp,
  app_spi_cmd_motor_ota.cpp
- 新增SSC EtherCAT从站协议栈相关文件组,包含头文件和源文件
- 添加ESC EEPROM操作相关的头文件和源文件
- 修正HBI寄存器EXMC偏移地址映射错误
- 实现8位、16位、32位ESC寄存器读写函数
- 改进EEPROM初始化逻辑,添加重试机制和诊断功能
This commit is contained in:
cbb
2026-08-09 03:27:21 +08:00
parent 8e5da18100
commit a2d0b28b3d
49 changed files with 13258 additions and 554 deletions
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<EtherCATInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="EtherCATInfo.xsd" Version="1.6">
<Vendor>
<Id>#x00000000</Id>
<Name>DCU Project</Name>
</Vendor>
<Descriptions>
<Groups>
<Group>
<Type>DCU</Type>
<Name LcId="1033">DCU Controller</Name>
</Group>
</Groups>
<Devices>
<Device Physics="YY">
<Type ProductCode="#x00009252" RevisionNo="#x00010000">GD32E508 DCU</Type>
<Name LcId="1033">GD32E508 DCU EtherCAT Slave</Name>
<GroupType>DCU</GroupType>
<Fmmu>Outputs</Fmmu>
<Fmmu>Inputs</Fmmu>
<!-- SM0: Mailbox Receive (Master -> Slave) -->
<Sm DefaultSize="256" StartAddress="#x1000" ControlByte="#x26" Enable="1">MBoxOut</Sm>
<!-- SM1: Mailbox Send (Slave -> Master) -->
<Sm DefaultSize="256" StartAddress="#x1200" ControlByte="#x22" Enable="1">MBoxIn</Sm>
<!-- SM2: Process Data Output (RxPDO, Master -> Slave) -->
<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">
<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>
</Entry>
</TxPdo>
<!-- TxPDO (Input) -->
<RxPdo 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>
</Entry>
</RxPdo>
<Mailbox DataLinkLayer="true">
<CoE SdoInfo="true" CompleteAccess="true" SegmentedSdo="true"/>
</Mailbox>
<Dc>
<OpMode>
<Name>DC SYNC0</Name>
<Desc>DC for synchronization</Desc>
<AssignActivate>#x0300</AssignActivate>
<CycleTimeSync0 Factor="1">0</CycleTimeSync0>
<ShiftTimeSync0>0</ShiftTimeSync0>
</OpMode>
<OpMode>
<Name>Free Run</Name>
<Desc>No synchronization</Desc>
<AssignActivate>#x0000</AssignActivate>
</OpMode>
</Dc>
<Eeprom>
<ByteSize>4096</ByteSize>
<ConfigData>050C000000000000</ConfigData>
</Eeprom>
</Device>
</Devices>
</Descriptions>
</EtherCATInfo>
+229
View File
@@ -0,0 +1,229 @@
/**
* @file dc_app.c
* @brief DCU EtherCAT 应用层 — SSC APPL_ 回调 + 应用对象字典
*
* 对象字典结构 (匹配 DCU_ESI.xml / eeprom_image.h):
* 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)
*/
#include "ecat_def.h"
#include "esc.h"
#include "ecatslv.h"
#include "ecatappl.h"
#include "objdef.h"
#include "dc_app.h"
#include <string.h>
/*---------------------------------------------------------------------------
* el9800appl.h 桩 include (SSC 内部需要)
*---------------------------------------------------------------------------*/
#define _EL9800APPL_ 1
#include "el9800appl.h"
#undef _EL9800APPL_
/*---------------------------------------------------------------------------
* PDO 数据缓冲
*---------------------------------------------------------------------------*/
/** 输出数据对象 0x7000 (主站→从站, RxPDO) */
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT64 u64Data[4]; /* 4×8 = 32 bytes */
} OBJ_STRUCT_PACKED_END
TOBJ7000;
/** 输入数据对象 0x6000 (从站→主站, TxPDO) */
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT64 u64Data[4]; /* 4×8 = 32 bytes */
} OBJ_STRUCT_PACKED_END
TOBJ6000;
static TOBJ7000 s_outputObj = {4, {0, 0, 0, 0}};
static TOBJ6000 s_inputObj = {4, {0, 0, 0, 0}};
/*---------------------------------------------------------------------------
* PDO Assign 对象 (0x1C12 / 0x1C13)
*---------------------------------------------------------------------------*/
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT16 aEntries[1];
} OBJ_STRUCT_PACKED_END
TOBJ_PDO_ASSIGN;
static TOBJ_PDO_ASSIGN sRxPDOassign = {1, {0x1600}};
static TOBJ_PDO_ASSIGN sTxPDOassign = {1, {0x1A00}};
/*---------------------------------------------------------------------------
* PDO Mapping 对象 (0x1600 / 0x1A00)
*---------------------------------------------------------------------------*/
typedef struct OBJ_STRUCT_PACKED_START {
UINT16 u16SubIndex0;
UINT32 aEntries[4];
} OBJ_STRUCT_PACKED_END
TOBJ_PDO_MAPPING;
/* 0x1600: 映射 0x7000:01..04, 每个 64 bits */
static TOBJ_PDO_MAPPING sRxPDOMap = {4, {
0x70000140, /* Index=0x7000, SubIndex=0x01, BitLen=64 */
0x70000240, /* Index=0x7000, SubIndex=0x02, BitLen=64 */
0x70000340, /* Index=0x7000, SubIndex=0x03, BitLen=64 */
0x70000440 /* Index=0x7000, SubIndex=0x04, BitLen=64 */
}};
/* 0x1A00: 映射 0x6000:01..04, 每个 64 bits */
static TOBJ_PDO_MAPPING sTxPDOMap = {4, {
0x60000140, /* Index=0x6000, SubIndex=0x01, BitLen=64 */
0x60000240, /* Index=0x6000, SubIndex=0x02, BitLen=64 */
0x60000340, /* Index=0x6000, SubIndex=0x03, BitLen=64 */
0x60000440 /* Index=0x6000, SubIndex=0x04, BitLen=64 */
}};
/*---------------------------------------------------------------------------
* Entry Descriptions
*---------------------------------------------------------------------------*/
/* PDO Assign (0x1C12/0x1C13): Sub0=UINT8 count, Sub1..n=UINT16 index */
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOAssignEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}
};
/* PDO Mapping (0x1600/0x1A00): Sub0=UINT8 count, Sub1..n=UINT32 mapping */
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOMappingEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}
};
/* Data object 0x7000/0x6000: Sub0=UINT8 count, Sub1..4=UINT64 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asDataEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE}
};
/*---------------------------------------------------------------------------
* Object Names
*---------------------------------------------------------------------------*/
OBJCONST UCHAR OBJMEM aName0x1C12[] = "RxPDO assign";
OBJCONST UCHAR OBJMEM aName0x1C13[] = "TxPDO assign";
OBJCONST UCHAR OBJMEM aName0x1600[] = "RxPDO mapping";
OBJCONST UCHAR OBJMEM aName0x1A00[] = "TxPDO mapping";
OBJCONST UCHAR OBJMEM aName0x7000[] = "Output Data";
OBJCONST UCHAR OBJMEM aName0x6000[] = "Input Data";
/*---------------------------------------------------------------------------
* Application Object Dictionary
*---------------------------------------------------------------------------*/
TOBJECT OBJMEM ApplicationObjDicList[] = {
/* 0x1C12: RxPDO Assign */
{NULL, NULL, 0x1C12, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C12, &sRxPDOassign, NULL, NULL, 0x0000},
/* 0x1600: RxPDO Mapping */
{NULL, NULL, 0x1600, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
asPDOMappingEntryDesc, aName0x1600, &sRxPDOMap, NULL, NULL, 0x0000},
/* 0x7000: Output Data (主站→从站) */
{NULL, NULL, 0x7000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asDataEntryDesc, aName0x7000, &s_outputObj, NULL, NULL, 0x0000},
/* 0x1C13: TxPDO Assign */
{NULL, NULL, 0x1C13, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C13, &sTxPDOassign, NULL, NULL, 0x0000},
/* 0x1A00: TxPDO Mapping */
{NULL, NULL, 0x1A00, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
asPDOMappingEntryDesc, aName0x1A00, &sTxPDOMap, NULL, NULL, 0x0000},
/* 0x6000: Input Data (从站→主站) */
{NULL, NULL, 0x6000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asDataEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
/* 终止条目 */
{NULL, NULL, 0xFFFF, {0, 0}, NULL, NULL, NULL, NULL, NULL, 0x000}
};
TOBJECT OBJMEM * VARMEM ApplicationObjDic = ApplicationObjDicList;
/*---------------------------------------------------------------------------
* APPL_GenerateMapping
*---------------------------------------------------------------------------*/
UINT16 APPL_GenerateMapping(UINT16 *pInputSize, UINT16 *pOutputSize)
{
*pInputSize = 32; /* 4×UINT64 = 32 bytes */
*pOutputSize = 32;
return 0;
}
/*---------------------------------------------------------------------------
* Mailbox Handler
*---------------------------------------------------------------------------*/
UINT16 APPL_StartMailboxHandler(void) { return 0; }
UINT16 APPL_StopMailboxHandler(void) { return 0; }
/*---------------------------------------------------------------------------
* Input Handler (SAFEOP 进入)
*---------------------------------------------------------------------------*/
UINT16 APPL_StartInputHandler(UINT16 *pIntMask)
{
*pIntMask = 0x0800U; /* SM3 AL Event */
return 0;
}
UINT16 APPL_StopInputHandler(void) { return 0; }
/*---------------------------------------------------------------------------
* Output Handler (SAFEOP→OP)
*---------------------------------------------------------------------------*/
UINT16 APPL_StartOutputHandler(void) { return 0; }
UINT16 APPL_StopOutputHandler(void) { return 0; }
/*---------------------------------------------------------------------------
* APPL_AckErrorInd
*---------------------------------------------------------------------------*/
void APPL_AckErrorInd(UINT16 stateTrans)
{
(void)stateTrans;
}
/*---------------------------------------------------------------------------
* APPL_InputMapping — 每周期从应用层填充 ESC 输入 PDO
*---------------------------------------------------------------------------*/
void APPL_InputMapping(UINT16 *pInputData)
{
memcpy(pInputData, &s_inputObj.u64Data[0], 32);
}
/*---------------------------------------------------------------------------
* APPL_OutputMapping — 每周期从 ESC 输出 PDO 读取到应用层
*---------------------------------------------------------------------------*/
void APPL_OutputMapping(UINT16 *pOutputData)
{
memcpy(&s_outputObj.u64Data[0], pOutputData, 32);
}
/*---------------------------------------------------------------------------
* APPL_Application — 主循环周期调用
*---------------------------------------------------------------------------*/
void APPL_Application(void)
{
/* Phase 2: 回环测试 — 将输出数据原样回填到输入数据。
* TwinCAT 主站写 0x7000 → ESC → APPL_OutputMapping → s_outputObj
* APPL_Application: s_inputObj = s_outputObj (回环)
* APPL_InputMapping → ESC → 主站读 0x6000
* 主站可验证:写入的值 == 读回的值。 */
s_inputObj.u64Data[0] = s_outputObj.u64Data[0];
s_inputObj.u64Data[1] = s_outputObj.u64Data[1];
s_inputObj.u64Data[2] = s_outputObj.u64Data[2];
s_inputObj.u64Data[3] = s_outputObj.u64Data[3];
}
+39
View File
@@ -0,0 +1,39 @@
/**
* @file dc_app.h
* @brief DCU EtherCAT 应用层接口声明
*
* 提供 SSC 所需的 APPL_ 回调函数声明。
* Phase 1: 桩实现,仅保证编译通过。
* Phase 3: 接入实际 PDO 映射(DCU 传感器输入 / 控制输出)。
*/
#ifndef COMM_SSC_PORT_DC_APP_H
#define COMM_SSC_PORT_DC_APP_H
#include "ecat_def.h"
#include "esc.h"
#include "ecatslv.h"
#ifdef __cplusplus
extern "C" {
#endif
/* SSC 状态机回调 (ecatslv.c 调用) */
UINT16 APPL_GenerateMapping(UINT16 *pInputSize, UINT16 *pOutputSize);
UINT16 APPL_StartMailboxHandler(void);
UINT16 APPL_StopMailboxHandler(void);
UINT16 APPL_StartInputHandler(UINT16 *pIntMask);
UINT16 APPL_StopInputHandler(void);
UINT16 APPL_StartOutputHandler(void);
UINT16 APPL_StopOutputHandler(void);
void APPL_AckErrorInd(UINT16 stateTrans);
/* 周期数据映射回调 (ecatappl.c 调用) */
void APPL_InputMapping(UINT16 *pInputData);
void APPL_OutputMapping(UINT16 *pOutputData);
void APPL_Application(void);
#ifdef __cplusplus
}
#endif
#endif /* COMM_SSC_PORT_DC_APP_H */
+734
View File
@@ -0,0 +1,734 @@
/**
* @file ecat_def.h
* @brief SSC V5.11 项目配置 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
*
* 基于 Beckhoff SSC V5.11 例程配置裁剪,适配本项目硬件:
* - MCU: GD32E508VE (Cortex-M33, 180MHz, little-endian)
* - ESC: LAN9252 via EXMC NOR/SRAM Region0, 16-bit HBI Indexed Address Mode
* - 协议: CoE (SDO + PDO), DC SYNC0, 无 FoE/EoE/AoE/SoE
*/
#ifndef _ECATDEF_H_
#define _ECATDEF_H_
#include <stdlib.h>
#include <string.h>
/*-----------------------------------------------------------------------------------------
------
------ Hardware Platform Selection
------
-----------------------------------------------------------------------------------------*/
/** EL9800_HW: 保留=1 以启用 el9800hw.h 接口(本项目重写该文件为 GD32 EXMC 版本)。 */
#ifndef EL9800_HW
#define EL9800_HW 1
#endif
#ifndef MCI_HW
#define MCI_HW 0
#endif
#ifndef FC1100_HW
#define FC1100_HW 0
#endif
#ifndef TIESC_HW
#define TIESC_HW 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ Controller / ESC Access Width
------
-----------------------------------------------------------------------------------------*/
/** GD32E508 是 32-bit MCU,但 EXMC 数据总线为 16-bit。
* SSC 用 CONTROLLER_16BIT 决定 MEM_ADDR 类型和指针算术。
* 与例程(STM32F407 SPI 16-bit)保持一致。 */
#ifndef CONTROLLER_16BIT
#define CONTROLLER_16BIT 1
#endif
#ifndef CONTROLLER_32BIT
#define CONTROLLER_32BIT 0
#endif
/** ESC 16-bit 访问(HBI DATA 寄存器为 16-bit word)。 */
#ifndef ESC_16BIT_ACCESS
#define ESC_16BIT_ACCESS 1
#endif
#ifndef ESC_32BIT_ACCESS
#define ESC_32BIT_ACCESS 0
#endif
/** 邮箱本地内存 16-bit 访问。 */
#ifndef MBX_16BIT_ACCESS
#define MBX_16BIT_ACCESS 1
#endif
/** GD32E508 为 little-endian。 */
#ifndef BIG_ENDIAN_16BIT
#define BIG_ENDIAN_16BIT 0
#endif
#ifndef BIG_ENDIAN_FORMAT
#define BIG_ENDIAN_FORMAT 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ Platform-specific (STM32 IO defines disabled)
------
-----------------------------------------------------------------------------------------*/
#ifndef _STM32_IO4
#define _STM32_IO4 0
#endif
#ifndef _STM32_IO8
#define _STM32_IO8 0
#endif
#ifndef EXT_DEBUGER_INTERFACE
#define EXT_DEBUGER_INTERFACE 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ LED / EEPROM
------
-----------------------------------------------------------------------------------------*/
/** LAN9252 支持 ESC 硬件驱动 Run/Error LED。 */
#ifndef UC_SET_ECAT_LED
#define UC_SET_ECAT_LED 0
#endif
#ifndef ESC_SUPPORT_ECAT_LED
#define ESC_SUPPORT_ECAT_LED 1
#endif
/** 不使用 EEPROM 仿真(本项目有真实 AT24C32)。 */
#ifndef ESC_EEPROM_EMULATION
#define ESC_EEPROM_EMULATION 0
#endif
/** AT24C32 = 4KB。 */
#ifndef ESC_EEPROM_SIZE
#define ESC_EEPROM_SIZE 0x1000
#endif
#ifndef EEPROM_READ_SIZE
#define EEPROM_READ_SIZE 0x08
#endif
#ifndef EEPROM_WRITE_SIZE
#define EEPROM_WRITE_SIZE 0x2
#endif
/*-----------------------------------------------------------------------------------------
------
------ Interrupt / Timer / DC
------
-----------------------------------------------------------------------------------------*/
/** AL Event 中断使能(ESC IRQ -> EXTI1)。 */
#ifndef AL_EVENT_ENABLED
#define AL_EVENT_ENABLED 1
#endif
/** Distributed Clocks 支持(SYNC0 -> EXTI0)。 */
#ifndef DC_SUPPORTED
#define DC_SUPPORTED 1
#endif
/** 看门狗由定时器中断检查。 */
#ifndef ECAT_TIMER_INT
#define ECAT_TIMER_INT 1
#endif
#ifndef INTERRUPTS_SUPPORTED
#define INTERRUPTS_SUPPORTED 1
#endif
/*-----------------------------------------------------------------------------------------
------
------ Application Selection
------
-----------------------------------------------------------------------------------------*/
/** 不使用 SSC 自带测试/示例应用,本项目自写 dc_app.c。 */
#ifndef TEST_APPLICATION
#define TEST_APPLICATION 0
#endif
#ifndef EL9800_APPLICATION
#define EL9800_APPLICATION 0
#endif
#ifndef CiA402_DEVICE
#define CiA402_DEVICE 0
#endif
#ifndef SAMPLE_APPLICATION
#define SAMPLE_APPLICATION 0
#endif
#ifndef SAMPLE_APPLICATION_INTERFACE
#define SAMPLE_APPLICATION_INTERFACE 0
#endif
/** 不使用 SSC 默认 main(),本项目有自己的 task 系统。 */
#ifndef USE_DEFAULT_MAIN
#define USE_DEFAULT_MAIN 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ Mailbox / Protocol Selection
------
-----------------------------------------------------------------------------------------*/
#ifndef MAILBOX_QUEUE
#define MAILBOX_QUEUE 1
#endif
/** CoE (CANopen over EtherCAT) — SDO + PDO mapping。 */
#ifndef COE_SUPPORTED
#define COE_SUPPORTED 1
#endif
#ifndef COMPLETE_ACCESS_SUPPORTED
#define COMPLETE_ACCESS_SUPPORTED 1
#endif
#ifndef SEGMENTED_SDO_SUPPORTED
#define SEGMENTED_SDO_SUPPORTED 1
#endif
#ifndef SDO_RES_INTERFACE
#define SDO_RES_INTERFACE 1
#endif
#ifndef USE_SINGLE_PDO_MAPPING_ENTRY_DESCR
#define USE_SINGLE_PDO_MAPPING_ENTRY_DESCR 0
#endif
#ifndef BACKUP_PARAMETER_SUPPORTED
#define BACKUP_PARAMETER_SUPPORTED 0
#endif
#ifndef STORE_BACKUP_PARAMETER_IMMEDIATELY
#define STORE_BACKUP_PARAMETER_IMMEDIATELY 0
#endif
/** 以下协议全部关闭。 */
#ifndef AOE_SUPPORTED
#define AOE_SUPPORTED 0
#endif
#ifndef DIAGNOSIS_SUPPORTED
#define DIAGNOSIS_SUPPORTED 0
#endif
#ifndef EMERGENCY_SUPPORTED
#define EMERGENCY_SUPPORTED 0
#endif
#ifndef VOE_SUPPORTED
#define VOE_SUPPORTED 0
#endif
#ifndef SOE_SUPPORTED
#define SOE_SUPPORTED 0
#endif
#ifndef EOE_SUPPORTED
#define EOE_SUPPORTED 0
#endif
#ifndef STATIC_ETHERNET_BUFFER
#define STATIC_ETHERNET_BUFFER 0
#endif
#ifndef FOE_SUPPORTED
#define FOE_SUPPORTED 0
#endif
#ifndef FOE_SAVE_FILES
#define FOE_SAVE_FILES 0
#endif
/** MAILBOX_SUPPORTED: CoE 开启则自动需要邮箱。 */
#ifndef MAILBOX_SUPPORTED
#define MAILBOX_SUPPORTED 1
#endif
#ifndef BOOTSTRAPMODE_SUPPORTED
#define BOOTSTRAPMODE_SUPPORTED 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ State Machine / Watchdog
------
-----------------------------------------------------------------------------------------*/
#ifndef OP_PD_REQUIRED
#define OP_PD_REQUIRED 1
#endif
/** PreOP 超时 2000ms。 */
#ifndef PREOPTIMEOUT
#define PREOPTIMEOUT 0x7D0
#endif
/** SafeOP->OP 超时 9000ms。 */
#ifndef SAFEOP2OPTIMEOUT
#define SAFEOP2OPTIMEOUT 0x2328
#endif
#ifndef EXPLICIT_DEVICE_ID
#define EXPLICIT_DEVICE_ID 0
#endif
/** LAN9252 支持 SM 看门狗。 */
#ifndef ESC_SM_WD_SUPPORTED
#define ESC_SM_WD_SUPPORTED 1
#endif
#ifndef STATIC_OBJECT_DIC
#define STATIC_OBJECT_DIC 0
#endif
#ifndef ESC_EEPROM_ACCESS_SUPPORT
#define ESC_EEPROM_ACCESS_SUPPORT 0
#endif
/*-----------------------------------------------------------------------------------------
------
------ Compiler / Type Defines (ARM Cortex-M33, GCC/AC6)
------
-----------------------------------------------------------------------------------------*/
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef BOOL
#define BOOL unsigned char
#endif
#ifndef UINT8
#define UINT8 unsigned char
#endif
#ifndef UINT16
#define UINT16 unsigned short
#endif
#ifndef UINT32
#define UINT32 unsigned long
#endif
#ifndef UINT64
#define UINT64 unsigned long long
#endif
#ifndef USHORT
#define USHORT unsigned short
#endif
#ifndef INT8
#define INT8 char
#endif
#ifndef INT16
#define INT16 short
#endif
#ifndef INT32
#define INT32 long
#endif
#ifndef CHAR
#define CHAR char
#endif
#ifndef UCHAR
#define UCHAR unsigned char
#endif
#ifndef SIZEOF
#define SIZEOF(x) sizeof(x)
#endif
/** MEM_ADDR: SSC 用于 ESC 内存访问的指针类型。
* CONTROLLER_16BIT=1 时为 UINT16。 */
#ifndef MEM_ADDR
#define MEM_ADDR UINT16
#endif
/** VARMEM: 动态变量内存类型修饰符(flat memory 留空)。 */
#ifndef VARMEM
#define VARMEM
#endif
#ifndef VARMEMSET
#define VARMEMSET memset
#endif
#ifndef VARMEMCPY
#define VARMEMCPY memcpy
#endif
/** GET_MEM_SIZE: 将字节数向上对齐到 MEM_ADDR 边界。 */
#ifndef GET_MEM_SIZE
#define GET_MEM_SIZE(ByteSize) (((ByteSize)+1) >> 1)
#endif
/** GET_MEM_BYTES: 将 MEM_ADDR 单位转回字节数。 */
#ifndef GET_MEM_BYTES
#define GET_MEM_BYTES(MemSize) ((MemSize) << 1)
#endif
/*-----------------------------------------------------------------------------------------
------
------ Memory Model (flat memory, no near/far/huge)
------
-----------------------------------------------------------------------------------------*/
#ifndef HUGE
#define HUGE
#endif
#ifndef HMEMSET
#define HMEMSET memset
#endif
#ifndef HMEMCPY
#define HMEMCPY memcpy
#endif
#ifndef HMEMCMP
#define HMEMCMP memcmp
#endif
#ifndef ESCMEM
#define ESCMEM
#endif
#ifndef ESCMEMCPY
#define ESCMEMCPY memcpy
#endif
#ifndef ESCMEMSET
#define ESCMEMSET memset
#endif
#ifndef ESCMBXMEMCPY
#define ESCMBXMEMCPY memcpy
#endif
#ifndef MBXMEM
#define MBXMEM
#endif
#ifndef MBXMEMCPY
#define MBXMEMCPY memcpy
#endif
#ifndef MBXMEMCMP
#define MBXMEMCMP memcmp
#endif
#ifndef MBXMEMSET
#define MBXMEMSET memset
#endif
#ifndef MBXSTRLEN
#define MBXSTRLEN strlen
#endif
#ifndef MBXSTRCPY
#define MBXSTRCPY memcpy
#endif
#ifndef OBJCONST
#define OBJCONST const
#endif
#ifndef VARCONST
#define VARCONST
#endif
#ifndef VARVOLATILE
#define VARVOLATILE volatile
#endif
#ifndef OBJMEM
#define OBJMEM
#endif
#ifndef OBJTOMBXMEMCPY
#define OBJTOMBXMEMCPY memcpy
#endif
#ifndef OBJTOMBXSTRCPY
#define OBJTOMBXSTRCPY memcpy
#endif
#ifndef OBJMEMCPY
#define OBJMEMCPY memcpy
#endif
#ifndef OBJSTRLEN
#define OBJSTRLEN strlen
#endif
#ifndef OBJSTRCPY
#define OBJSTRCPY memcpy
#endif
#ifndef MAKE_HUGE_PTR
#define MAKE_HUGE_PTR
#endif
#ifndef MAKE_PTR_TO_ESC
#define MAKE_PTR_TO_ESC
#endif
#ifndef EMCYMEMCPY
#define EMCYMEMCPY memcpy
#endif
#ifndef EMCYMEMSET
#define EMCYMEMSET memset
#endif
#ifndef EMCYMEM
#define EMCYMEM
#endif
/*-----------------------------------------------------------------------------------------
------
------ Struct Packing (ARM AC6: __attribute__((packed)) 不需要前后宏,留空即可)
------
-----------------------------------------------------------------------------------------*/
#ifndef STRUCT_PACKED_START
#define STRUCT_PACKED_START
#endif
#ifndef STRUCT_PACKED_END
#define STRUCT_PACKED_END
#endif
#ifndef MBX_STRUCT_PACKED_START
#define MBX_STRUCT_PACKED_START
#endif
#ifndef MBX_STRUCT_PACKED_END
#define MBX_STRUCT_PACKED_END
#endif
#ifndef OBJ_STRUCT_PACKED_START
#define OBJ_STRUCT_PACKED_START
#endif
#ifndef OBJ_STRUCT_PACKED_END
#define OBJ_STRUCT_PACKED_END
#endif
/*-----------------------------------------------------------------------------------------
------
------ Mailbox Size
------
-----------------------------------------------------------------------------------------*/
/** 最小邮箱字节数(主站可设置的下限)。 */
#ifndef MIN_MBX_SIZE
#define MIN_MBX_SIZE 0x0022
#endif
/** 最大邮箱字节数(主站可设置的上限)。 */
#ifndef MAX_MBX_SIZE
#define MAX_MBX_SIZE 0x0100
#endif
/*-----------------------------------------------------------------------------------------
------
------ PDO Size (match CONFIG_ETHERCAT_PDO_SIZE in sys/config.h)
------
-----------------------------------------------------------------------------------------*/
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。 */
#ifndef MAX_PD_OUTPUT_SIZE
#define MAX_PD_OUTPUT_SIZE 0x20
#endif
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。 */
#ifndef MAX_PD_INPUT_SIZE
#define MAX_PD_INPUT_SIZE 0x20
#endif
/*-----------------------------------------------------------------------------------------
------
------ Device Identity (CoE Object 0x1018)
------
-----------------------------------------------------------------------------------------*/
#ifndef VENDOR_ID
#define VENDOR_ID 0x00000000
#endif
#ifndef PRODUCT_CODE
#define PRODUCT_CODE 0x00009252
#endif
#ifndef REVISION_NUMBER
#define REVISION_NUMBER 0x00010000
#endif
#ifndef SERIAL_NUMBER
#define SERIAL_NUMBER 0x00000001
#endif
/*-----------------------------------------------------------------------------------------
------
------ Device Information (CoE Objects 0x1000/0x1008/0x1009/0x100A)
------
-----------------------------------------------------------------------------------------*/
#ifndef DEVICE_PROFILE_TYPE
#define DEVICE_PROFILE_TYPE 0x00000000
#endif
#ifndef DEVICE_NAME
#define DEVICE_NAME "GD32E508 DCU"
#endif
#ifndef DEVICE_NAME_LEN
#define DEVICE_NAME_LEN 0x0D
#endif
#ifndef DEVICE_HW_VERSION
#define DEVICE_HW_VERSION "1.0"
#endif
#ifndef DEVICE_HW_VERSION_LEN
#define DEVICE_HW_VERSION_LEN 0x3
#endif
#ifndef DEVICE_SW_VERSION
#define DEVICE_SW_VERSION "1.0"
#endif
#ifndef DEVICE_SW_VERSION_LEN
#define DEVICE_SW_VERSION_LEN 0x3
#endif
/*-----------------------------------------------------------------------------------------
------
------ ESC Memory Address Ranges (SM buffer allocation boundaries)
------
-----------------------------------------------------------------------------------------*/
#ifndef MIN_PD_WRITE_ADDRESS
#define MIN_PD_WRITE_ADDRESS 0x1000
#endif
#ifndef MAX_PD_WRITE_ADDRESS
#define MAX_PD_WRITE_ADDRESS 0x1FFF
#endif
#ifndef MIN_PD_READ_ADDRESS
#define MIN_PD_READ_ADDRESS 0x1000
#endif
#ifndef MAX_PD_READ_ADDRESS
#define MAX_PD_READ_ADDRESS 0x1FFF
#endif
#ifndef MIN_MBX_WRITE_ADDRESS
#define MIN_MBX_WRITE_ADDRESS 0x1000
#endif
#ifndef MAX_MBX_WRITE_ADDRESS
#define MAX_MBX_WRITE_ADDRESS 0x1FFF
#endif
#ifndef MIN_MBX_READ_ADDRESS
#define MIN_MBX_READ_ADDRESS 0x1000
#endif
#ifndef MAX_MBX_READ_ADDRESS
#define MAX_MBX_READ_ADDRESS 0x1FFF
#endif
/*-----------------------------------------------------------------------------------------
------
------ PDO Timing (DC sync parameters, unit: ns)
------
-----------------------------------------------------------------------------------------*/
#ifndef MIN_PD_CYCLE_TIME
#define MIN_PD_CYCLE_TIME 0x186A0
#endif
#ifndef MAX_PD_CYCLE_TIME
#define MAX_PD_CYCLE_TIME 0xC3500000
#endif
#ifndef PD_OUTPUT_CALC_AND_COPY_TIME
#define PD_OUTPUT_CALC_AND_COPY_TIME 0x0
#endif
#ifndef PD_OUTPUT_DELAY_TIME
#define PD_OUTPUT_DELAY_TIME 0x0
#endif
#ifndef PD_INPUT_CALC_AND_COPY_TIME
#define PD_INPUT_CALC_AND_COPY_TIME 0x0
#endif
#ifndef PD_INPUT_DELAY_TIME
#define PD_INPUT_DELAY_TIME 0x0
#endif
/*-----------------------------------------------------------------------------------------
------
------ Dynamic Memory
------
-----------------------------------------------------------------------------------------*/
#ifndef FREEMEM
#define FREEMEM(pointer) free((pointer))
#endif
#ifndef GETMEM
#define GETMEM(size) malloc((size))
#endif
#ifndef APPL_AllocMailboxBuffer
#define APPL_AllocMailboxBuffer(size) malloc((size))
#endif
#ifndef APPL_FreeMailboxBuffer
#define APPL_FreeMailboxBuffer(pointer) free((pointer))
#endif
#ifndef MEMCPY
#define MEMCPY memcpy
#endif
#ifndef ALLOCMEM
#define ALLOCMEM(size) malloc((size))
#endif
#endif /* _ECATDEF_H_ */
@@ -0,0 +1,53 @@
/**
* @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 */
@@ -0,0 +1,357 @@
#!/usr/bin/env python3
"""
Generate LAN9252 EEPROM binary image (ESI) 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)
Usage: python gen_eeprom.py > eeprom_image.h
"""
import struct
import sys
# ============================================================
# Configuration (must match ecat_def.h and DCU_ESI.xml)
# ============================================================
VENDOR_ID = 0x00000000
PRODUCT_CODE = 0x00009252
REVISION_NO = 0x00010000
SERIAL_NO = 0x00000001
# Mailbox configuration
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)
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_CONTROL = 0x8D
PDI_CONFIG = 0x0C
EEPROM_SIZE = 4096
# ============================================================
# Build EEPROM image
# ============================================================
eeprom = bytearray(EEPROM_SIZE)
# Fill with 0xFF (erased state)
eeprom[:] = b'\xFF' * EEPROM_SIZE
def w16(offset, value):
"""Write 16-bit little-endian value at byte offset."""
struct.pack_into('<H', eeprom, offset, value & 0xFFFF)
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
# Word 1: ESC Configuration (Sync Watchdog divider etc.) - 0x0000
w16(2, 0x0000)
# Word 2: PDI Config 2 + Sync Impulse Length - 0x0000
w16(4, 0x0000)
# Word 3: Alias + Reserved
w16(6, 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(42, MBX_RX_SIZE)
# Word 22: Standard TX mailbox offset
w16(44, MBX_TX_OFFSET)
# Word 23: Standard TX mailbox size
w16(46, MBX_TX_SIZE)
# Word 24: Mailbox protocol (CoE = bit 0)
w16(48, MBX_PROTOCOL)
# Words 25-31: Reserved
for i in range(50, 64, 2):
w16(i, 0x0000)
# --- 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
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
# 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)
# --- 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 = [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
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 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
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)
# --- 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
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)
# --- 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
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)
# --- 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
# ============================================================
# 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
# ============================================================
# 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)
# ============================================================
# Output as C header
# ============================================================
print("/**")
print(" * @file eeprom_image.h")
print(" * @brief LAN9252 EEPROM (ESI) binary image - auto-generated by gen_eeprom.py")
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" * Size: {EEPROM_SIZE} bytes (AT24C32)")
print(" *")
print(" * DO NOT EDIT - regenerate with: python gen_eeprom.py > eeprom_image.h")
print(" */")
print("#ifndef COMM_SSC_PORT_EEPROM_IMAGE_H")
print("#define COMM_SSC_PORT_EEPROM_IMAGE_H")
print("")
print("#include <stdint.h>")
print("")
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) */")
continue
print(f" /* 0x{row:04X} */ {hex_str},")
print("};")
print("")
print(f"static const uint32_t ESI_EEPROM_SIZE = {EEPROM_SIZE}U;")
print("")
print("#endif /* COMM_SSC_PORT_EEPROM_IMAGE_H */")
@@ -0,0 +1,345 @@
/**
* @file hw_gd32exmc.cpp
* @brief SSC V5.11 硬件 port 层 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
*
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
* 转发到 EscExmcBus (EXMC NOR/SRAM Region0, 16-bit 内存映射)。
*
* HBI Indexed 寻址协议:
* 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)
*
* 并发保护:
* - HW_EscRead/HW_EscWrite (主循环):调用前 SSC 已执行 DISABLE_AL_EVENT_INT
* (mask EXTI0+EXTI1),保证 HBI IDX 不被 ISR 抢占。
* - HW_EscReadIsr/HW_EscWriteIsr (ISR):在 preempt=0 的 SYNC0 ISR 内调用,
* 天然不会被抢占。
*/
/* SSC 配置与类型 */
#define _EL9800HW_ 1
#include "ecat_def.h"
#include "esc.h"
#include "el9800hw.h"
#include "ecatslv.h"
/* 项目硬件驱动 */
#include "comm/esc_exmc_bus.h"
#include "sys/config.h"
#include <cstdio>
#include <cstring>
/*---------------------------------------------------------------------------
* HBI Indexed Mode EXMC 偏移 (与 esc_lan9252.cpp 一致)
*---------------------------------------------------------------------------*/
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) */
/* AL Event Mask 寄存器影子 (ESC 0x0204) */
volatile uint16_t s_alEventMask = 0xFFFFU;
/* SSC 看门狗软件定时器 (DWT CYCCNT 基准) */
volatile uint32_t s_timerBase = 0U;
/*---------------------------------------------------------------------------
* 内部 HBI 访问原语 (无锁,调用者负责并发保护)
*---------------------------------------------------------------------------*/
inline void hbiSetIndex(uint16_t addr)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, addr);
}
inline uint16_t hbiReadLow(void)
{
return EscExmcBus::read16(HBI_DATA0_LOW_OFF);
}
inline uint16_t hbiReadHigh(void)
{
return EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
}
inline void hbiWriteLow(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_LOW_OFF, v);
}
inline void hbiWriteHigh(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
}
/** 从 ESC 地址读取 Len 字节到 pData (按 16-bit word 对齐处理) */
void hbiRead(void *pData, uint16_t address, uint16_t len)
{
uint8_t *dst = (uint8_t *)pData;
uint16_t addr = address;
uint16_t remaining = len;
/* 奇数起始地址:读低 byte */
if ((addr & 1U) != 0U && remaining > 0U) {
hbiSetIndex(addr & 0xFFFEU);
uint16_t w = hbiReadLow();
*dst++ = (uint8_t)((w >> 8) & 0xFFU);
addr++;
remaining--;
}
/* 对齐的 16-bit word 读取 */
while (remaining >= 2U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
dst[0] = (uint8_t)(w & 0xFFU);
dst[1] = (uint8_t)((w >> 8) & 0xFFU);
dst += 2;
addr += 2;
remaining -= 2;
}
/* 尾部奇数字节 */
if (remaining > 0U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
*dst = (uint8_t)(w & 0xFFU);
}
}
/** 向 ESC 地址写入 Len 字节 (按 16-bit word 对齐处理) */
void hbiWrite(const void *pData, uint16_t address, uint16_t len)
{
const uint8_t *src = (const uint8_t *)pData;
uint16_t addr = address;
uint16_t remaining = len;
/* 奇数起始地址:RMW 高 byte */
if ((addr & 1U) != 0U && remaining > 0U) {
uint16_t aligned = addr & 0xFFFEU;
hbiSetIndex(aligned);
uint16_t w = hbiReadLow();
w = (w & 0x00FFU) | ((uint16_t)(*src++) << 8);
hbiSetIndex(aligned);
hbiWriteLow(w);
addr++;
remaining--;
}
/* 对齐的 16-bit word 写入 */
while (remaining >= 2U) {
uint16_t w = (uint16_t)src[0] | ((uint16_t)src[1] << 8);
hbiSetIndex(addr);
hbiWriteLow(w);
src += 2;
addr += 2;
remaining -= 2;
}
/* 尾部奇数字节:RMW 低 byte */
if (remaining > 0U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
w = (w & 0xFF00U) | (uint16_t)(*src);
hbiSetIndex(addr);
hbiWriteLow(w);
}
}
} /* anonymous namespace */
/*---------------------------------------------------------------------------
* SSC Port API 实现 (extern "C" linkage)
*---------------------------------------------------------------------------*/
extern "C" {
/**
* @brief 硬件初始化。
* @note EXMC 总线与 ESC 基本探活已由 EthercatSlave::init() 完成。
* 本函数在 SSC MainInit() 之前被调用,做 SSC 特有的初始化:
* - 使能 DWT 周期计数器 (若尚未使能)
* - 读取 AL Event Mask 寄存器影子
*/
UINT8 HW_Init(void)
{
/* DWT 周期计数器 (可能已被 EscLan9252::init 使能,幂等操作) */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
s_timerBase = DWT->CYCCNT;
/* 读取当前 AL Event Mask */
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
s_alEventMask = hbiReadLow();
return 0; /* 0 = success */
}
void HW_Release(void)
{
/* 无需释放资源 */
}
/**
* @brief 读取 AL Event 寄存器 (0x0220),主循环上下文。
* @note 调用者已执行 DISABLE_AL_EVENT_INT。
*/
UINT16 HW_GetALEventRegister(void)
{
hbiSetIndex(ESC_AL_EVENT_OFFSET);
return hbiReadLow();
}
/**
* @brief 读取 AL Event 寄存器 (0x0220),ISR 上下文。
*/
UINT16 HW_GetALEventRegister_Isr(void)
{
hbiSetIndex(ESC_AL_EVENT_OFFSET);
return hbiReadLow();
}
/**
* @brief 清除 AL Event Mask 中的指定位 (写 ESC 0x0204)。
*/
void HW_ResetALEventMask(UINT16 intMask)
{
s_alEventMask &= ~intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
}
/**
* @brief 置位 AL Event Mask 中的指定位 (写 ESC 0x0204)。
*/
void HW_SetALEventMask(UINT16 intMask)
{
s_alEventMask |= intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
}
/**
* @brief 设置 EtherCAT Run/Error LED。
* @note LAN9252 支持 ESC 硬件驱动 LED (ESC_SUPPORT_ECAT_LED=1),
* 通过寄存器 0x0138/0x0139 控制。若硬件未接 LED 则忽略。
*/
void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
{
/* Run LED override (0x0138): bit0 = LED on */
hbiSetIndex(ESC_RUN_LED_OVERRIDE);
hbiWriteLow(RunLed ? 0x0001U : 0x0000U);
/* Error LED override (0x0139): bit0 = LED on */
hbiSetIndex(ESC_ERROR_LED_OVERRIDE);
hbiWriteLow(ErrLed ? 0x0001U : 0x0000U);
}
/*---------------------------------------------------------------------------
* ESC 内存读写 — 主循环版本 (SSC 已 mask 中断)
*---------------------------------------------------------------------------*/
void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiRead(pData, Address, Len);
}
void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiWrite(pData, Address, Len);
}
/*---------------------------------------------------------------------------
* ESC 内存读写 — ISR 版本 (preempt=0 内调用,无需额外保护)
*---------------------------------------------------------------------------*/
void HW_EscReadIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiRead(pData, Address, Len);
}
void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiWrite(pData, Address, Len);
}
/*---------------------------------------------------------------------------
* SyncManager 通道控制
*---------------------------------------------------------------------------*/
/**
* @brief 禁用指定 SM 通道 (清 SM Activate 寄存器 bit0)。
* @param channel SM 通道号 (0-15)
*/
void HW_DisableSyncManChannel(UINT8 channel)
{
/* SM 寄存器基址 0x0800,每个 SM 占 8 字节,Activate 在 offset +4 (bit0) */
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val &= ~0x0001U; /* 清 bit0 = deactivate */
hbiSetIndex(activateAddr);
hbiWriteLow(val);
}
/**
* @brief 使能指定 SM 通道 (置 SM Activate 寄存器 bit0)。
*/
void HW_EnableSyncManChannel(UINT8 channel)
{
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val |= 0x0001U;
hbiSetIndex(activateAddr);
hbiWriteLow(val);
}
/**
* @brief 获取指定 SM 通道的寄存器块指针。
* @note SSC 用此函数读取 SM 配置 (StartAddr, Length, Control, Status, Activate)。
* 由于 HBI 是间接访问,无法返回真实指针。SSC 在 EL9800_HW 模式下
* 实际通过 HW_EscRead 读取 SM 寄存器,此函数返回 NULL 并由调用者
* 使用 HW_EscRead 替代。
*
* 检查 ecatslv.c 中 HW_GetSyncMan 的使用方式:
* 如果 SSC 直接解引用返回的指针,则需要提供一个本地缓冲。
* 这里提供一个静态缓冲,每次调用时从 ESC 读入。
*/
static TSYNCMAN s_smBuf;
TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel)
{
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
hbiRead(&s_smBuf, smBase, SIZEOF_SM_REGISTER);
return &s_smBuf;
}
/*---------------------------------------------------------------------------
* 定时器 (SSC 看门狗用)
*---------------------------------------------------------------------------*/
/**
* @brief 获取定时器当前值 (ticks since HW_ClearTimer)。
* @note 使用 DWT CYCCNT (180MHz),ECAT_TIMER_INC_P_MS=180000。
*/
UINT32 HW_GetTimerValue(void)
{
return DWT->CYCCNT - s_timerBase;
}
/**
* @brief 清零定时器基准。
*/
void HW_ClearTimerValue(void)
{
s_timerBase = DWT->CYCCNT;
}
} /* extern "C" */