Compare commits
38
Commits
6aa21a1f9d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
662dcccbf2 | ||
|
|
d8b3c9767c | ||
|
|
f3d7fc0c30 | ||
|
|
a777be1cb4 | ||
|
|
28d8426052 | ||
|
|
5fd986299c | ||
|
|
63af85e7be | ||
|
|
7d9d9c70a1 | ||
|
|
6136c2363f | ||
|
|
6afaacd03a | ||
|
|
9ca3db26f8 | ||
|
|
1742d9a759 | ||
|
|
959e2aef6e | ||
|
|
0cd3d9bed2 | ||
|
|
ace5a1671b | ||
|
|
9f1c2d25dd | ||
|
|
70daa4b0c6 | ||
|
|
2ebd4ff1d2 | ||
|
|
f9ef1b8da2 | ||
|
|
44d407390b | ||
|
|
cfe7725b26 | ||
|
|
a2d0b28b3d | ||
|
|
8e5da18100 | ||
|
|
e06f5eb9a9 | ||
|
|
46ef19d882 | ||
|
|
edebde4a02 | ||
|
|
079c6442d4 | ||
|
|
934836618a | ||
|
|
fedd39b902 | ||
|
|
52d7f9be76 | ||
|
|
76da7a3dd6 | ||
|
|
a52d94687b | ||
|
|
8bc01dd654 | ||
|
|
7c05496b89 | ||
|
|
10989939e9 | ||
|
|
3691cf2aba | ||
|
|
33037bc5a3 | ||
|
|
b78e36ff37 |
+20
@@ -85,3 +85,23 @@ uv2csolution.log
|
||||
|
||||
# legacy intermediate project file
|
||||
*.cprj
|
||||
|
||||
# Verification & Test suite process artifacts
|
||||
/tests/
|
||||
/.agents/
|
||||
TEST_READY.md
|
||||
TEST_READY.html
|
||||
convert_md_to_html.py
|
||||
*.html
|
||||
|
||||
# IDE / 多智能体过程产物
|
||||
.trae/
|
||||
.worktrees/
|
||||
.qoder/
|
||||
.zcode/
|
||||
docs/traework-import/
|
||||
|
||||
# 本机固件 keep / 安全评审过程稿
|
||||
firmware_keep/
|
||||
SECURITY_AUDIT_REPORT.*
|
||||
ORIGINAL_REQUEST.md
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
# 清理剩余低风险可优化项 Plan
|
||||
|
||||
## Summary
|
||||
|
||||
在上一轮 `purge-legacy-dead-code` spec(已清除 23 项 A 类死代码 + 11 项 B/C/D 类改进)基础上,对 `GD32E508_DCU_APP/` 进行二次扫描,识别并处理 **13 项剩余低风险可优化项**(不改变运行期行为,构建可验证)。
|
||||
|
||||
覆盖三类:
|
||||
- **A 类残留死代码**(6 项):死别名、写而不读成员/静态、空头文件、冗余类型别名
|
||||
- **过期/缺失注释**(3 项):PRIMASK 过期说明、taskHeartbeat 过期注释、复位前 PRIMASK 缺解释
|
||||
- **重复代码提取**(4 项):spinDelay 延时循环、armRingPending 赋值模式、writeU32Le 跨文件去重、adc.cpp namespace 合并
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### 已完成(上一轮 spec)
|
||||
- A1-A14:23 项死代码清除(CAN 兼容轮询链/SPI v1 状态机/EtherCAT 死 API/ADC/USART0/Watchdog/TempSensor 死接口等)
|
||||
- B1-B6:6 项前瞻性桩加 TODO 标记
|
||||
- C1-C4:4 项条件编译优化
|
||||
- D1:1 项历史注释清理
|
||||
- 构建验证通过(43/43,0 错误,Code=47462)
|
||||
|
||||
### 本轮新发现(经 Grep 交叉验证)
|
||||
二次扫描发现 13 项上一轮未覆盖的低风险优化项,均不改变运行期行为。
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 第一组:A 类残留死代码清除(6 项)
|
||||
|
||||
#### 1.1 删除死类型别名 `canfd_diag_t`
|
||||
- **文件**:`GD32E508_DCU_APP/device/canfd.h:211`
|
||||
- **现状**:`using canfd_diag_t = CanFd::Diag;` — Grep 全项目仅命中声明自身,无任何使用方
|
||||
- **改法**:删除第 211 行及相邻空行
|
||||
- **风险**:低(纯类型别名删除)
|
||||
|
||||
#### 1.2 删除写而永不读成员 `m_ringTxLen`
|
||||
- **文件**:
|
||||
- `GD32E508_DCU_APP/device/spi/app_spi_command.h:119-120`(声明 + 注释)
|
||||
- `GD32E508_DCU_APP/device/spi/app_spi_command.cpp:407, 712, 745, 859, 867`(5 处写入)
|
||||
- **现状**:`volatile uint16_t m_ringTxLen = 0U;` 头注释称"供 onCsAssert 装入 DMA",但 `onCsAssert` 实际使用的是 `m_txLen`。在 `requestEsp32Read`(745-746 行)中 `m_ringTxLen` 与 `m_txLen` 被赋同值,`m_ringTxLen` 是冗余写者。Grep 确认 5 处写入、0 处读取。
|
||||
- **改法**:
|
||||
1. 删除 `app_spi_command.h:119-120`(注释 + 成员声明)
|
||||
2. 删除 `app_spi_command.cpp` 中 5 处 `m_ringTxLen = ...;` 赋值语句
|
||||
- **风险**:低(仅删除写操作,`m_txLen` 与 `m_ringTxLen` 始终同值,读取路径不变)
|
||||
- **注意**:745 行 `m_ringTxLen = (uint16_t)built_len;` 的下一行 `m_txLen = (uint16_t)built_len;` 必须保留
|
||||
|
||||
#### 1.3 删除写而永不读静态 `s_pushCount`
|
||||
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp:257, 282`
|
||||
- **现状**:`static uint32_t s_pushCount = 0U;` 仅被自增(282 行 `++s_pushCount;`),从未被读取、打印或暴露给诊断接口
|
||||
- **改法**:删除 257 行声明与 282 行自增语句(含其注释 `/* 仅保留计数,不打印心跳日志 */`)
|
||||
- **风险**:低(纯死状态)
|
||||
|
||||
#### 1.4 删除 4 个未使用本地别名
|
||||
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_command.cpp:49, 51, 54, 55`
|
||||
- **现状**:本地别名块(48-56 行)共 9 个别名,其中 `kTypeCanSend`/`kTypeAppPing`/`kRspAck`/`kRspWait` 4 个在文件内从未被引用(Grep 仅命中声明行)。其余 5 个(`kTypeBootReq`/`kTypeOtaReq`/`kTypeOtaData`/`kTypeOtaCancel`/`kRspError`)仍被使用。
|
||||
- **改法**:删除第 49、51、54、55 行
|
||||
- **风险**:低(删除 `static constexpr` 未使用符号)
|
||||
|
||||
#### 1.5 删除空头文件 `sys/hardfault.h`
|
||||
- **文件**:`GD32E508_DCU_APP/sys/hardfault.h`(整文件,仅 3 行 include guard)
|
||||
- **现状**:A13 清理后该头文件已无任何声明,仅剩 `#ifndef HARDFAULT_H / #define HARDFAULT_H / #endif`。`sys/hardfault.cpp` 不包含此头,全项目无任何 `#include "sys/hardfault.h"` 或 `#include "hardfault.h"`(Grep 确认 0 匹配)。
|
||||
- **改法**:删除整个文件
|
||||
- **风险**:低(无引用方)
|
||||
- **注意**:需确认构建脚本(CMakeLists.txt)中是否显式列出该头文件作为安装目标;若仅有 .cpp 参与编译则无影响
|
||||
|
||||
#### 1.6 删除 TempSensor 冗余类型别名(方案 A)
|
||||
- **文件**:
|
||||
- `GD32E508_DCU_APP/device/temp_sensor.h:12`(`using Data = Adc::Sample;`)
|
||||
- `GD32E508_DCU_APP/device/temp_sensor.h:24`(`using temp_sensor_data_t = TempSensor::Data;`)
|
||||
- `GD32E508_DCU_APP/sys/task_runner.cpp:62`(消费点 `temp_sensor_data_t sample{};`)
|
||||
- **现状**:两层 `using` 别名链(`temp_sensor_data_t` → `TempSensor::Data` → `Adc::Sample`),仅 1 处消费(task_runner.cpp:62)。`TempSensor` 类本身保留(`init()` 仍被 `initial.cpp:45` 调用,作为语义门面有价值)。
|
||||
- **改法**:
|
||||
1. 删除 `temp_sensor.h:12` 的 `using Data = Adc::Sample;`
|
||||
2. 删除 `temp_sensor.h:24` 的 `using temp_sensor_data_t = TempSensor::Data;`
|
||||
3. 修改 `task_runner.cpp:62` 将 `temp_sensor_data_t sample{};` 改为 `Adc::Sample sample{};`
|
||||
4. 确认 `task_runner.cpp` 已 `#include "device/adc.h"`(若未包含则需添加,但 task_runner.cpp 大概率已包含)
|
||||
- **风险**:低(仅类型别名替换,不改语义)
|
||||
- **注意**:`TempSensor` 类的 `init()` 声明与 doxygen 注释保留不动
|
||||
|
||||
---
|
||||
|
||||
### 第二组:过期/缺失注释修正(3 项)
|
||||
|
||||
#### 2.1 修正 `task_manager.h` PRIMASK 过期注释
|
||||
- **文件**:`GD32E508_DCU_APP/sys/task_manager.h:45`
|
||||
- **现状**:注释 `内部通过保持当前 PRIMASK 状态的临界区保证可由主循环与 ISR 共享调用`,但实现已迁移至 BASEPRI(`RT_CRITICAL_SAVE/RESTORE`)
|
||||
- **改法**:改为 `内部通过 BASEPRI 临界区(RT_CRITICAL_SAVE/RESTORE)保证可由主循环与 ISR 共享调用`
|
||||
- **风险**:低(纯注释)
|
||||
|
||||
#### 2.2 修正 `task_runner.cpp` taskHeartbeat 过期注释
|
||||
- **文件**:`GD32E508_DCU_APP/sys/task_runner.cpp:225`
|
||||
- **现状**:`taskHeartbeat` 的 doxygen 注释 `@note 当前仅保留任务入口。` 已过期失真——函数体实际已有实现(227 行:`OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); canFeedbackStartRound();`)。相邻的 `taskStatusLed`(216 行)确实空体,其注释准确。
|
||||
- **改法**:将 225 行注释改为 `@note 当前任务:推进 OTA Boot 请求、刷新 CAN 反馈快照、启动反馈推送轮次。`
|
||||
- **风险**:低(纯注释)
|
||||
- **注意**:`taskStatusLed` 的 216 行注释保持不动(空体桩是 B 类前瞻性桩)
|
||||
|
||||
#### 2.3 补充 `ota_boot_request.cpp` PRIMASK 解释注释
|
||||
- **文件**:`GD32E508_DCU_APP/sys/ota_boot_request.cpp:220`
|
||||
- **现状**:`__disable_irq(); NVIC_SystemReset();` 是全项目唯一使用 PRIMASK 而非 BASEPRI 的位置。这是**合法用法**(系统复位前最终屏蔽全部中断,防止复位过程中断嵌套),但缺乏解释,后续维护者可能误判为违反项目硬约束。
|
||||
- **改法**:在 220 行前加注释:`/* 复位前最终关中断:必须用 PRIMASK 全屏蔽(非 BASEPRI),确保 NVIC_SystemReset 过程中无任何中断嵌套 */`
|
||||
- **风险**:低(纯注释)
|
||||
|
||||
---
|
||||
|
||||
### 第三组:重复代码提取(4 项)
|
||||
|
||||
#### 3.1 提取 `spinDelay` 延时循环(CANFD.cpp)
|
||||
- **文件**:`GD32E508_DCU_APP/device/CANFD.cpp`
|
||||
- **现状**:9 处 `for (volatile uint32_t d = 0; d < NNNNN; ++d) {}` 纯延时循环散落在 `hardResetCan`、`runSelfTestFrame`、`init` 退出 init mode 兜底等多处。魔数 `{40000, 50000, 100000, 200000}` 无命名常量。
|
||||
- **改法**:
|
||||
1. 在文件顶部匿名 namespace 内新增:
|
||||
```cpp
|
||||
/* 纯软件延时循环(不依赖 SysTick,可在任何中断状态下使用) */
|
||||
static inline void spinDelay(uint32_t loops) {
|
||||
for (volatile uint32_t d = 0; d < loops; ++d) { }
|
||||
}
|
||||
```
|
||||
2. 把以下**纯延时循环**(无 break/条件)替换为 `spinDelay(N);`:
|
||||
- 94 行:`for (volatile uint32_t dly = 0; dly < 40000U; ++dly) { }` → `spinDelay(40000U);`
|
||||
- 120 行:`for (volatile uint32_t d = 0; d < 100000U; ++d) {}` → `spinDelay(100000U);`
|
||||
- 125 行:`for (volatile uint32_t d = 0; d < 50000U; ++d) {}` → `spinDelay(50000U);`
|
||||
- 130 行:`for (volatile uint32_t d = 0; d < 200000U; ++d) {}` → `spinDelay(200000U);`
|
||||
- 142 行:需确认是否纯延时(读取后判断)
|
||||
- 382 行:需确认是否纯延时
|
||||
- 384 行:需确认是否纯延时
|
||||
3. **不替换**带有条件 break 的轮询等待循环(如 355 行 `for (...) { if ((CAN_STAT(...) & CAN_STAT_IWS) == 0U) break; }`)——这些不是纯延时,保持原样
|
||||
- **风险**:低(纯结构提取,延时循环行为不变)
|
||||
- **注意**:执行时需逐一 Read 确认 134/142/382/384 行的实际内容,仅替换纯延时循环
|
||||
|
||||
#### 3.2 提取 `armRingPending` 赋值模式(app_spi_command.cpp)
|
||||
- **文件**:`GD32E508_DCU_APP/device/spi/app_spi_command.cpp`
|
||||
- **现状**:7 处重复的"按铃挂起"赋值模式:
|
||||
```
|
||||
m_ringPending = true;
|
||||
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||
```
|
||||
出现在 516-517, 556-557, 580-581, 604-605, 630-631, 699-700, 748-749 行。
|
||||
- **改法**:
|
||||
1. 在 `app_spi_command.h` 的 `AppSpiCommand` private 段新增声明:`void armRingPending();`
|
||||
2. 在 `app_spi_command.cpp` 新增实现:
|
||||
```cpp
|
||||
void AppSpiCommand::armRingPending() {
|
||||
m_ringPending = true;
|
||||
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||
}
|
||||
```
|
||||
3. 把 7 处两行赋值替换为 `armRingPending();`
|
||||
- **风险**:低(语义等价提取,两个独立赋值无依赖关系,顺序无关)
|
||||
- **注意**:第 7 处(748-749)原顺序为先 `m_ringDeadline` 后 `m_ringPending`,提取后顺序统一为方法内顺序,不影响语义
|
||||
|
||||
#### 3.3 `writeU32Le` 跨文件去重
|
||||
- **文件**:
|
||||
- `GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp:53-59`(`static void writeU32Le`)
|
||||
- `GD32E508_DCU_APP/device/spi/app_spi_cmd_id_snapshot.cpp:33-39`(`inline void writeU32Le`)
|
||||
- **现状**:两个 SPI 命令处理器的匿名 namespace 各自定义了字节序相同的 `writeU32Le(uint8_t *dst, uint32_t val)`,实现完全一致(仅参数名 value/val 不同)。
|
||||
- **改法**:
|
||||
1. 在 `GD32E508_DCU_APP/device/spi/app_spi_command_defs.h` 末尾(`SpiDefs` namespace 外或内,视现有结构而定)新增:
|
||||
```cpp
|
||||
inline void writeU32Le(uint8_t *dst, uint32_t val) {
|
||||
dst[0] = static_cast<uint8_t>(val & 0xFFU);
|
||||
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
|
||||
dst[2] = static_cast<uint8_t>((val >> 16) & 0xFFU);
|
||||
dst[3] = static_cast<uint8_t>((val >> 24) & 0xFFU);
|
||||
}
|
||||
```
|
||||
2. 删除 `app_spi_cmd_can_feedback.cpp:53-59` 的本地 `writeU32Le` 定义
|
||||
3. 删除 `app_spi_cmd_id_snapshot.cpp:33-39` 的本地 `writeU32Le` 定义
|
||||
4. 确认两个 .cpp 已 `#include "device/spi/app_spi_command_defs.h"`(大概率已包含,因它们使用 `SpiDefs::kType*` 常量)
|
||||
- **风险**:低(纯函数提取,实现完全一致)
|
||||
|
||||
#### 3.4 合并 `adc.cpp` 双重匿名 namespace
|
||||
- **文件**:`GD32E508_DCU_APP/device/adc.cpp:12-27, 29-35`
|
||||
- **现状**:同一文件有两个独立 `namespace { ... }` 块:
|
||||
- 第一个(12-27):含 `kMinValidVdda` 等校验常量与 `isValidAdcRaw`/`isFiniteFloat`
|
||||
- 第二个(29-35):含 `kDmaPeriph`/`kDmaChannel`/`s_dmaBuffer` 等 DMA 配置
|
||||
- 语义上都属于文件内静态,无必要分两块
|
||||
- **改法**:合并为单一 `namespace { ... }` 块,用空行 + 注释分隔逻辑分组:
|
||||
```cpp
|
||||
namespace {
|
||||
/* ---- 校验常量与辅助函数 ---- */
|
||||
constexpr float kMinValidVdda = 2.7f;
|
||||
... (原第一块内容)
|
||||
|
||||
/* ---- DMA 配置 ---- */
|
||||
constexpr uint32_t kDmaPeriph = DMA0;
|
||||
... (原第二块内容)
|
||||
} // namespace
|
||||
```
|
||||
- **风险**:低(纯结构调整)
|
||||
|
||||
---
|
||||
|
||||
## Assumptions & Decisions
|
||||
|
||||
### 假设
|
||||
1. 上一轮 `purge-legacy-dead-code` spec 的所有修改已合并且构建通过(已确认:Code=47462)
|
||||
2. `task_runner.cpp` 已 `#include "device/adc.h"`(因 `Adc::collectSample` 在 319 行被调用)
|
||||
3. 两个 SPI 命令处理器 .cpp 已 `#include "device/spi/app_spi_command_defs.h"`(因使用 `SpiDefs::kType*` 常量)
|
||||
4. CMake 构建脚本未显式安装 `sys/hardfault.h`(头文件通常不被列为编译目标)
|
||||
|
||||
### 决策
|
||||
1. **3.1 spinDelay 仅提取纯延时循环**:带条件 break 的轮询等待循环(如 355 行)保持原样,避免改变语义
|
||||
2. **1.6 TempSensor 保留类本身**:仅删冗余别名,`TempSensor::init()` 作为语义门面保留(`initial.cpp:45` 仍调用)
|
||||
3. **不处理 3 项中风险项**:CANFD.cpp 文件名大小写、init() 重构、volatile 审计——需独立 spec
|
||||
4. **不主动清理 #include**:即使某头文件因移除类型而变冗余,也不删除 #include(避免过度改动,构建不会因此报错)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
### 1. Grep 验证(清除项零残留)
|
||||
- `canfd_diag_t` → 0 匹配
|
||||
- `m_ringTxLen` → 0 匹配
|
||||
- `s_pushCount` → 0 匹配
|
||||
- `kTypeCanSend|kTypeAppPing|kRspAck|kRspWait`(在 app_spi_command.cpp 内)→ 0 匹配
|
||||
- `#include.*hardfault.h` → 0 匹配
|
||||
- `temp_sensor_data_t` → 0 匹配
|
||||
- `writeU32Le`(在 app_spi_cmd_can_feedback.cpp 和 app_spi_cmd_id_snapshot.cpp 内)→ 0 匹配(仅 app_spi_command_defs.h 中有定义)
|
||||
|
||||
### 2. Grep 验证(新增项存在)
|
||||
- `spinDelay` 在 CANFD.cpp 中出现 ≥1 次(定义)+ N 次(调用)
|
||||
- `armRingPending` 在 app_spi_command.h 中出现 1 次(声明)+ app_spi_command.cpp 中出现 1 次(定义)+ 7 次(调用)
|
||||
- `writeU32Le` 在 app_spi_command_defs.h 中出现 1 次(定义)
|
||||
- `BASEPRI 临界区` 在 task_manager.h:45 出现 1 次
|
||||
- `推进 OTA Boot 请求` 在 task_runner.cpp 出现 1 次
|
||||
- `复位前最终关中断` 在 ota_boot_request.cpp 出现 1 次
|
||||
|
||||
### 3. 构建验证
|
||||
- `cmake --build g:\Robot_v2\DCU\DCU_APP\tmp\1 --clean-first`
|
||||
- 0 编译错误
|
||||
- 0 新增警告(`#warning` 仅来自上一轮 C1 的 EXMC,不计入)
|
||||
- Program Size: Code 应略小于 47462(因删除了死代码)
|
||||
|
||||
### 4. 活跃代码完整性核查
|
||||
- `Adc::collectSample` 仍被 task_runner.cpp 引用
|
||||
- `TempSensor::init` 仍被 initial.cpp:45 引用
|
||||
- `m_txLen`/`m_ringPending`/`m_ringDeadline` 成员完整保留
|
||||
- `SpiDefs::kTypeCanSend` 等常量在 app_spi_command_defs.h 中完整保留(仅本地别名删除)
|
||||
- `HardFault_Handler`/`HardFault_Handler_C` 在 hardfault.cpp 中完整保留
|
||||
|
||||
## 任务分组与执行顺序
|
||||
|
||||
### 可并行执行的三组(文件集不交叠)
|
||||
|
||||
**组 A:device/spi/ 目录(1.2 + 1.3 + 1.4 + 3.2 + 3.3)**
|
||||
- 文件:`app_spi_command.h/.cpp`、`app_spi_cmd_can_feedback.cpp`、`app_spi_cmd_id_snapshot.cpp`、`app_spi_command_defs.h`
|
||||
- 注意:3.2(armRingPending)和 1.2(m_ringTxLen)都改 `app_spi_command.h/.cpp`,需顺序执行
|
||||
|
||||
**组 B:device/ 目录(1.1 + 1.6 + 3.1 + 3.4)**
|
||||
- 文件:`canfd.h`、`CANFD.cpp`、`temp_sensor.h`、`task_runner.cpp`、`adc.cpp`
|
||||
- 注意:1.6 改 `temp_sensor.h` + `task_runner.cpp`;3.1 改 `CANFD.cpp`
|
||||
|
||||
**组 C:sys/ 目录(1.5 + 2.1 + 2.2 + 2.3)**
|
||||
- 文件:`hardfault.h`(删除)、`task_manager.h`、`task_runner.cpp`、`ota_boot_request.cpp`
|
||||
- 注意:2.2 和 1.6 都改 `task_runner.cpp`,需顺序执行或合并到同一子代理
|
||||
|
||||
### 建议执行顺序
|
||||
1. 先并行执行组 A、组 B、组 C(三组文件集基本不交叠,仅 `task_runner.cpp` 跨组)
|
||||
2. `task_runner.cpp` 的改动(1.6 改类型 + 2.2 改注释)合并到同一子代理
|
||||
3. 最后执行构建验证
|
||||
@@ -1,82 +0,0 @@
|
||||
# Verification Checklist
|
||||
|
||||
## 使能开关(未焊接保护)
|
||||
- [x] `sys/config.h` 中 `CONFIG_ETHERCAT_ENABLE` 默认为 0
|
||||
- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `comm/` 全部代码不参与编译(用 `#if` 包裹)
|
||||
- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `sys/initial.cpp` 不调用 `EthercatSlave::init()` / `EthercatSync::init()`
|
||||
- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 `sys/task_runner.cpp` 的 `TASK_ETHERCAT_PROCESS` 分支空操作
|
||||
- [x] `CONFIG_ETHERCAT_ENABLE=0` 时 EXMC / EXTI0 / EXTI1 / EXTI2 不被配置
|
||||
- [x] `CONFIG_ETHERCAT_ENABLE=0` 时编译产物与改造前功能等价(ADC/CAN/SPI 正常)
|
||||
- [x] 阶段 1 实时架构改造(NVIC/BASEPRI/ISR 瘦身)不受 `CONFIG_ETHERCAT_ENABLE` 影响,始终生效
|
||||
- [x] `TASK_SPI_CMD_PROCESS` 与 `taskSpiCmdProcess()` 不受 `CONFIG_ETHERCAT_ENABLE` 限制(阶段1 改造已需要)
|
||||
|
||||
## 阶段 1:实时架构改造
|
||||
- [x] `sys/critical.h` 已创建,`RT_CRITICAL_ENTER/EXIT/SAVE/RESTORE` 宏基于 BASEPRI 实现
|
||||
- [x] `sys/config.h` 中 `CONFIG_NVIC_PRIORITY_GROUP` 设为 `NVIC_PRIGROUP_PRE4_SUB0`
|
||||
- [x] `sys/config.h` 中各中断优先级宏已按分层表配置(EtherCAT=0/调度器=1/CAN=2/SPI+EXTI+USART=3)
|
||||
- [x] `sys/config.h` 中 `CONFIG_MAIN_LOOP_WFI_ENABLE` 默认为 0
|
||||
- [x] `device/Timer.cpp` 中 `nvic_priority_group_set` 使用 `CONFIG_NVIC_PRIORITY_GROUP`
|
||||
- [x] `device/Timer.cpp` 中调度器 NVIC 优先级为 (1, 0)
|
||||
- [x] `sys/task_manager.cpp` 中 `set()` 和 `takePending()` 使用 BASEPRI 临界区
|
||||
- [x] `sys/task_manager.cpp` 中无残留 `__disable_irq/__set_PRIMASK`
|
||||
- [x] `device/CANFD.cpp` 中 `hasRxFrame()` 和 `consumeRxFrame()` 使用 BASEPRI 临界区
|
||||
- [x] `device/CANFD.cpp` 中 CAN RX0 NVIC 优先级为 (2, n)
|
||||
- [x] `device/CANFD.cpp` 中 `handleRxInterrupt()` 仅做入队+置位(验证未退化)
|
||||
- [x] `device/app_spi_command.cpp` 中 SPI0 NVIC 优先级为 (3, 0)
|
||||
- [x] `device/app_spi_command.cpp` 中 EXTI10_15 NVIC 优先级为 (3, 1)
|
||||
- [x] `device/app_spi_command.cpp` 中 `poll()` 使用 BASEPRI 临界区
|
||||
- [x] `device/app_spi_command.cpp` 中 `onCsRelease()` 不在 ISR 内调用 `processCommand()`(移主循环)
|
||||
- [x] `device/usart0.cpp` 中 USART0 NVIC 优先级为 (3, 0)
|
||||
- [x] `device/usart0.cpp` 中 `putCharNonBlocking()` 使用 BASEPRI 临界区
|
||||
- [x] `main.h` 中 `system_task_t` 增加 `TASK_ETHERCAT_PROCESS`
|
||||
- [x] `main.h` 中 `system_task_t` 增加 `TASK_SPI_CMD_PROCESS`
|
||||
- [x] `device/app_spi_command.h` 中增加 `processPendingCommand()` 公有方法声明
|
||||
- [x] `device/app_spi_command.cpp` 中 `onCsRelease()` 不再调用 `processCommand()`,改置位 `m_cmdPending` + `TASK_SPI_CMD_PROCESS`
|
||||
- [x] `device/app_spi_command.cpp` 中 `processPendingCommand()` 清除 `m_cmdPending` 后调用 `processCommand()`
|
||||
- [x] `main.cpp` 中 WFI 代码块由 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关控制
|
||||
- [x] `main.cpp` 中 WFI 关闭时使用 `__NOP()` 忙等
|
||||
- [x] 全项目搜索确认无遗漏的 `__disable_irq` / `__set_PRIMASK`(除 `sys/critical.h` 实现内)
|
||||
|
||||
## 阶段 2:LAN9252 ESC 驱动骨架
|
||||
- [x] `comm/esc_exmc_bus.h` 已创建,声明 `EscExmcBus` 类接口
|
||||
- [x] `comm/esc_exmc_bus.cpp` 已创建,EXMC NOR/SRAM 16-bit 初始化实现
|
||||
- [x] `comm/esc_exmc_bus.cpp` 中 GPIO 配置使用 `config.h` 引脚宏(基于标准映射 PD0/PD1/PD4/PD5/PD7/PD8-PD11/PD14/PD15,PE7-PE15)
|
||||
- [x] `comm/esc_exmc_bus.cpp` 中 `read16/write16` 通过内存映射地址(0x60000000)访问
|
||||
- [x] `comm/esc_lan9252.h` 已创建,声明 `EscLan9252` 类接口
|
||||
- [x] `comm/esc_lan9252.cpp` 已创建,实现 `init()` / `selfTest()` / `readReg` / `writeReg` / `readPdi` / `writePdi`
|
||||
- [x] `comm/esc_lan9252.cpp` 中 `selfTest()` 读取 ESC ID 寄存器并校验
|
||||
- [x] `comm/ethercat_slave.h` 已创建,声明 `EthercatSlave` 类与 PDO 双缓冲结构
|
||||
- [x] `comm/ethercat_slave.cpp` 已创建,实现周期处理与双缓冲
|
||||
- [x] `comm/ethercat_slave.cpp` 中 `periodicIsr()` 标记 `__attribute__((section(".ramfunc")))`
|
||||
- [x] `comm/ethercat_slave.cpp` 中 `periodicIsr()` 执行:读输入→写输出→翻转指针→置位任务
|
||||
- [x] `comm/ethercat_slave.cpp` 中 `getLatestInputs()` 无锁原子读取
|
||||
- [x] `comm/ethercat_sync.h` / `comm/ethercat_sync.cpp` 已创建
|
||||
- [x] `comm/ethercat_sync.cpp` 中 SYNC0 EXTI(PB0/EXTI0)配置完成,下降沿触发
|
||||
- [x] `comm/ethercat_sync.cpp` 中 IRQ EXTI(PB1/EXTI1)配置完成,下降沿触发
|
||||
- [x] `comm/ethercat_sync.cpp` 中 SYNC1 EXTI(PB2/EXTI2)配置完成,下降沿触发
|
||||
- [x] `comm/ethercat_sync.cpp` 中 EXTI0_IRQn NVIC 优先级 (0, 0)(SYNC0 独占 preempt=0)
|
||||
- [x] `comm/ethercat_sync.cpp` 中 EXTI1_IRQn NVIC 优先级 (1, 0)(IRQ preempt=1,可被 SYNC0 抢占)
|
||||
- [x] `comm/ethercat_sync.cpp` 中 EXTI2_IRQn NVIC 优先级 (1, 0)(SYNC1 preempt=1,备用)
|
||||
- [x] `comm/ethercat_sync.cpp` 中 `onSync0Exti()` 清 EXTI0 标志 → 调用 `EthercatSlave::periodicIsr()`
|
||||
- [x] `comm/ethercat_sync.cpp` 中 `onEscIrqExti()` 清 EXTI1 标志 → 置位 `TASK_ETHERCAT_PROCESS`(极简)
|
||||
- [x] `comm/ethercat_sync.cpp` 中 `onSync1Exti()` 清 EXTI2 标志(备用)
|
||||
- [x] `comm/ethercat_sync.cpp` 中提供 `EXTI0_IRQHandler` / `EXTI1_IRQHandler` / `EXTI2_IRQHandler` C 包装
|
||||
- [x] GPIO PB0/PB1/PB2 时钟使能与输入模式配置完成
|
||||
- [x] PA0 未被占用,保留给未来 STANDBY 模式唤醒(WKUP_PIN0)
|
||||
- [x] 降级模式 `CONFIG_ETHERCAT_SYNC_MODE==1` 时 TIMER0 配置可用
|
||||
|
||||
## 阶段 3:系统集成
|
||||
- [x] `sys/initial.cpp` 中 `AppInitializer::init()` 增加 `EthercatSlave::init()` 调用
|
||||
- [x] `sys/initial.cpp` 中增加 `EthercatSync::init()` 调用
|
||||
- [x] `sys/task_runner.cpp` 中增加 `taskEthercatProcess()` 方法
|
||||
- [x] `sys/task_runner.cpp` 中增加 `taskSpiCmdProcess()` 方法
|
||||
- [x] `sys/task_runner.cpp` 中 `runPending()` 增加 `TASK_ETHERCAT_PROCESS` 分支
|
||||
- [x] `sys/task_runner.cpp` 中 `runPending()` 增加 `TASK_SPI_CMD_PROCESS` 分支
|
||||
- [x] `sys/config.h` 中 NVIC 分组、优先级、EXMC 引脚、ESC 中断引脚、SYNC 模式等配置宏已定义
|
||||
- [x] 构建配置 `GD32E508_DCU_APP.cproject.yml` 包含 `comm/` 目录
|
||||
|
||||
## 编译验证
|
||||
- [x] 全量编译通过,无 error
|
||||
- [x] 无新增 warning
|
||||
- [x] 无遗漏的 `__disable_irq/__set_PRIMASK`(全项目搜索确认)
|
||||
- [x] 新增文件均在 `comm/` 目录下,不污染现有 `device/` / `sys/` 结构
|
||||
@@ -1,222 +0,0 @@
|
||||
# EtherCAT LAN9252 从站实时集成 Spec
|
||||
|
||||
## Why
|
||||
DCU_APP 后续要加入 EtherCAT 从站通信,要求周期 100us~250us、抖动 <1us(高性能运动控制级)。当前架构使用全局关中断(`__disable_irq`/PRIMASK)临界区、非抢占式协作调度、主循环 WFI 休眠,无法满足 <1us 抖动要求。需要先做实时架构改造,再新增 LAN9252 ESC 驱动骨架,并与现有系统集成。
|
||||
|
||||
## What Changes
|
||||
|
||||
### Requirement: EtherCAT 使能开关(未焊接硬件保护)
|
||||
系统 SHALL 提供编译时使能宏 `CONFIG_ETHERCAT_ENABLE`(默认 0=禁用),控制 EtherCAT 相关代码的编译与运行时初始化。未焊接 LAN9252 硬件时,该宏保持 0,系统 SHALL:
|
||||
1. 编译期:`comm/` 全部代码不参与编译(`#if CONFIG_ETHERCAT_ENABLE` 包裹),不增加任何 Flash/RAM 占用
|
||||
2. 运行时:`sys/initial.cpp` 不调用 `EthercatSlave::init()` / `EthercatSync::init()`,EXMC/EXTI0/EXTI1/EXTI2 不被配置
|
||||
3. 主循环:`TASK_ETHERCAT_PROCESS` / `TASK_SPI_CMD_PROCESS` 分支不被执行
|
||||
4. 阶段 1 实时架构改造(NVIC 分组/BASEPRI 临界区/ISR 瘦身)不受影响,始终生效
|
||||
|
||||
#### Scenario: 未焊接 LAN9252 时系统正常运行
|
||||
- **WHEN** `CONFIG_ETHERCAT_ENABLE` 为 0(默认)
|
||||
- **THEN** 编译产物与改造前功能等价,EXMC/EXTI0-2 未配置,主循环不调用 EtherCAT 任务,现有 ADC/CAN/SPI 功能正常
|
||||
|
||||
#### Scenario: 焊接 LAN9252 后启用
|
||||
- **WHEN** 用户焊接硬件后将 `CONFIG_ETHERCAT_ENABLE` 改为 1 并重新编译
|
||||
- **THEN** `comm/` 代码参与编译,初始化序列调用 EtherCAT,EXTI0-2 配置生效,周期通信启动
|
||||
|
||||
### 阶段 1:实时架构改造(前置必做,不受使能开关影响)
|
||||
- **BREAKING**: NVIC 优先级分组从 `NVIC_PRIGROUP_PRE2_SUB2` 改为 `NVIC_PRIGROUP_PRE4_SUB0`,抢占优先级从 2 位变为 4 位
|
||||
- **BREAKING**: 全部临界区从 `__disable_irq/__set_PRIMASK`(PRIMASK,屏蔽所有中断)改为 BASEPRI(仅屏蔽 preempt≥1,保留 preempt=0 给 EtherCAT)
|
||||
- 新增 `sys/critical.h`:统一 BASEPRI 临界区宏 `RT_CRITICAL_ENTER()/RT_CRITICAL_EXIT()`
|
||||
- ISR 瘦身:SPI CS release 中断与 CAN RX 中断只做"数据入队 + 置位标志",命令解析与处理移到主循环
|
||||
- 主循环 WFI 去除:EtherCAT 启用后改忙等,通过 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关控制
|
||||
- 各中断优先级重排,为 EtherCAT 预留 preempt=0
|
||||
|
||||
### 阶段 2:LAN9252 ESC 驱动骨架(新建 `comm/` 目录)
|
||||
- 新增 `comm/esc_lan9252.h` / `comm/esc_lan9252.cpp`:LAN9252 寄存器定义、HBI 读写接口、自检(0x87654321)
|
||||
- 新增 `comm/esc_exmc_bus.h` / `comm/esc_exmc_bus.cpp`:EXMC NOR/SRAM 16-bit 模式初始化、GPIO 配置、读写时序
|
||||
- 新增 `comm/ethercat_slave.h` / `comm/ethercat_slave.cpp`:从站周期处理接口、PDO 双缓冲(乒乓)、周期 ISR(放 RAM 执行段)
|
||||
- 新增 `comm/ethercat_sync.h` / `comm/ethercat_sync.cpp`:SYNC0 周期同步定时器(TIMER0,preempt=0)
|
||||
|
||||
### 阶段 3:系统集成(受 `CONFIG_ETHERCAT_ENABLE` 开关控制)
|
||||
- `main.h` 任务枚举增加 `TASK_ETHERCAT_PROCESS` / `TASK_SPI_CMD_PROCESS`(始终定义,不受开关影响)
|
||||
- `sys/initial.cpp` 初始化序列:`#if CONFIG_ETHERCAT_ENABLE` 包裹 `EthercatSlave::init()` / `EthercatSync::init()` 调用
|
||||
- `sys/task_runner.cpp` 增加 `taskEthercatProcess()` / `taskSpiCmdProcess()`,`runPending()` 分支受 `CONFIG_ETHERCAT_ENABLE` 保护(无 EtherCAT 时分支空操作)
|
||||
- `sys/config.h` 增加 LAN9252 / EXMC / SYNC 相关配置项(含引脚定义占位宏),`CONFIG_ETHERCAT_ENABLE` 默认 0
|
||||
- 构建配置纳入新文件(`comm/` 目录源文件纳入编译列表,实际编译由 `#if CONFIG_ETHERCAT_ENABLE` 控制)
|
||||
|
||||
## Impact
|
||||
- Affected specs: 无(首个 spec)
|
||||
- Affected code:
|
||||
- `main.cpp` / `main.h`:主循环 WFI、任务枚举
|
||||
- `sys/config.h`:NVIC 分组、优先级、LAN9252 配置
|
||||
- `sys/critical.h`(新建):BASEPRI 临界区
|
||||
- `sys/task_manager.cpp`:临界区换 BASEPRI
|
||||
- `sys/task_runner.cpp`:增加 EtherCAT 任务
|
||||
- `sys/initial.cpp`:增加 EtherCAT 初始化
|
||||
- `device/Timer.cpp`:NVIC 分组设置
|
||||
- `device/CANFD.cpp`:优先级、临界区、ISR 已基本瘦身
|
||||
- `device/app_spi_command.cpp`:优先级、临界区、ISR 瘦身(CS release 处理移主循环)
|
||||
- `device/usart0.cpp`:优先级、临界区
|
||||
- `comm/`(新建全部):ESC 驱动、EXMC 总线、EtherCAT 从站、SYNC 同步
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: NVIC 优先级分组为 4 位抢占 0 位子优先级
|
||||
系统 SHALL 使用 `NVIC_PRIGROUP_PRE4_SUB0` 分组,提供 4 个抢占优先级层级,为 EtherCAT 预留最高级(preempt=0)。
|
||||
|
||||
#### Scenario: EtherCAT 中断不被阻塞
|
||||
- **WHEN** 任意非 EtherCAT 代码进入临界区
|
||||
- **THEN** EtherCAT 中断(preempt=0)仍能立即响应,因为临界区使用 BASEPRI 仅屏蔽 preempt≥1
|
||||
|
||||
### Requirement: BASEPRI 临界区替代 PRIMASK
|
||||
系统 SHALL 使用 BASEPRI 实现临界区,阈值为 preempt≥1,确保 EtherCAT 中断(preempt=0)永不被阻塞。所有原有 `__disable_irq/__set_PRIMASK` 临界区 SHALL 替换为 BASEPRI 版本。
|
||||
|
||||
#### Scenario: 任务标志原子操作不阻塞 EtherCAT
|
||||
- **WHEN** `TaskManager::takePending()` 或 `TaskManager::set()` 执行期间
|
||||
- **THEN** EtherCAT 周期中断仍能抢占执行,BASEPRI 仅屏蔽 preempt≥1 的中断
|
||||
|
||||
### Requirement: 中断优先级分层
|
||||
系统 SHALL 按以下优先级表配置所有中断:
|
||||
|
||||
| 中断 | preempt | sub | 说明 |
|
||||
|---|---|---|---|
|
||||
| EtherCAT ESC 中断 / SYNC0 定时器 | 0 | 0 | 最高优先级,永不被阻塞 |
|
||||
| TIMER2 100us 调度器 | 1 | 0 | 系统调度节拍 |
|
||||
| CAN0/1/2 RX0 | 2 | 0/1/2 | CANFD 接收 |
|
||||
| SPI0 / EXTI10_15 / USART0 | 3 | 0/1 | 非实时通信 |
|
||||
|
||||
#### Scenario: EtherCAT 中断抢占 CAN 中断
|
||||
- **WHEN** CAN RX 中断正在处理时 EtherCAT 周期中断到达
|
||||
- **THEN** EtherCAT 中断立即抢占 CAN 中断执行
|
||||
|
||||
### Requirement: ISR 瘦身
|
||||
SPI CS release 中断和 CAN RX 中断 SHALL 只做"数据入队 + 置位任务标志",命令解析、协议处理、硬件发送等耗时操作 SHALL 移到主循环任务中执行。
|
||||
|
||||
#### Scenario: SPI CS release 不在 ISR 内做命令处理
|
||||
- **WHEN** SPI CS 释放中断触发
|
||||
- **THEN** ISR 仅将接收数据入队并置位 `TASK_SPI_LOG`(或新增的解析任务),`processCommand()` 在主循环中执行
|
||||
|
||||
### Requirement: LAN9252 ESC HBI 驱动
|
||||
系统 SHALL 通过 GD32E508 的 EXMC NOR/SRAM 模式提供 LAN9252 的 16-bit 并行 HBI 访问能力,包括:寄存器读、寄存器写、PDI 内存块读写、自检(读 0x87654321 校验)。
|
||||
|
||||
#### Scenario: ESC 自检通过
|
||||
- **WHEN** 系统初始化调用 `EscLan9252::selfTest()`
|
||||
- **THEN** 通过 EXMC 读取 LAN9252 的 ESC ID 寄存器,返回值校验通过(0x87654321 或对应芯片 ID)
|
||||
|
||||
#### Scenario: 寄存器读写正确
|
||||
- **WHEN** 调用 `EscLan9252::writeReg(addr, value)` 后调用 `EscLan9252::readReg(addr)`
|
||||
- **THEN** 读回值与写入值一致
|
||||
|
||||
### Requirement: EtherCAT 周期数据双缓冲
|
||||
系统 SHALL 使用 ping-pong 双缓冲机制实现 EtherCAT 周期数据交换:周期 ISR 使用一个缓冲区,主循环应用层使用另一个缓冲区,通过原子指针交换实现无锁访问。
|
||||
|
||||
#### Scenario: 主循环无锁读取最新 PDO
|
||||
- **WHEN** 周期 ISR 完成一帧输入数据读取并翻转缓冲区指针
|
||||
- **THEN** 主循环 `taskEthercatProcess()` 通过原子指针读取到最新一帧完整数据,无需关中断
|
||||
|
||||
### Requirement: 周期 ISR 放 RAM 执行段
|
||||
EtherCAT 周期 ISR SHALL 标记为 RAM 执行段(通过链接脚本 section 属性),避免 Flash wait state 造成抖动,保证 <1us 抖动目标。
|
||||
|
||||
#### Scenario: 周期 ISR 执行时间稳定
|
||||
- **WHEN** 周期 ISR 连续执行多次
|
||||
- **THEN** 执行时间波动 <1us,不受 Flash wait state 影响
|
||||
|
||||
### Requirement: SYNC0 周期同步由 ESC 硬件触发
|
||||
系统 SHALL 使用 LAN9252 的 SYNC0 输出引脚(PB0/EXTI0)经 MCU EXTI0_IRQn 独立向量触发周期中断(而非 MCU 内部定时器自计时),由 ESC 硬件同步保证抖动最小。EXTI0_IRQn 配置为 preempt=0,永不被任何其他中断(包括 ESC IRQ)阻塞。TIMER0 作为 DC 未同步时的降级备选(通过 `CONFIG_ETHERCAT_SYNC_MODE` 开关切换)。
|
||||
|
||||
#### Scenario: SYNC0 引脚触发周期中断
|
||||
- **WHEN** LAN9252 在 DC 同步点输出 SYNC0 脉冲
|
||||
- **THEN** EXTI0 中断立即触发(独立向量),handler 清标志后调用 `EthercatSlave::periodicIsr()`,抖动 <1us
|
||||
|
||||
#### Scenario: SYNC0 抢占 ESC IRQ
|
||||
- **WHEN** ESC IRQ 中断(EXTI1,preempt=1)正在处理时 SYNC0 周期中断到达
|
||||
- **THEN** SYNC0(EXTI0,preempt=0)立即抢占 IRQ 中断执行
|
||||
|
||||
#### Scenario: 降级模式用 TIMER0
|
||||
- **WHEN** `CONFIG_ETHERCAT_SYNC_MODE` 设为 TIMER 降级模式
|
||||
- **THEN** 使用 TIMER0 以固定周期触发,作为 DC 未同步前的过渡
|
||||
|
||||
### Requirement: ESC 事件中断(独立向量)
|
||||
系统 SHALL 配置 LAN9252 的 IRQ 输出引脚(PB1/EXTI1)经 MCU EXTI1_IRQn 独立向量触发 ESC 事件中断(用于 AL 状态变化、EEPROM 加载完成、看门狗触发等事件),preempt=1。由于拥有独立向量,IRQ 与 SYNC0 完全分离,可被 SYNC0 抢占。IRQ handler SHALL 极简:仅清 EXTI 标志 + 置位 `TASK_ETHERCAT_PROCESS`。
|
||||
|
||||
#### Scenario: ESC 事件触发中断
|
||||
- **WHEN** LAN9252 产生 ESC 事件(如 AL 状态变化)拉低 IRQ 引脚
|
||||
- **THEN** EXTI1 中断(独立向量)触发,handler 置位 `TASK_ETHERCAT_PROCESS`,主循环处理事件
|
||||
|
||||
#### Scenario: SYNC1 备用中断
|
||||
- **WHEN** LAN9252 输出 SYNC1 脉冲(PB2/EXTI2)
|
||||
- **THEN** EXTI2 中断(独立向量,preempt=1)触发,清标志(备用,暂不处理)
|
||||
|
||||
### Requirement: EtherCAT 应用层任务
|
||||
系统 SHALL 在主循环中提供非实时应用层 PDO 映射任务 `TASK_ETHERCAT_PROCESS`,从双缓冲读取最新 PDO 数据供应用层使用。
|
||||
|
||||
#### Scenario: 应用层消费 PDO 数据
|
||||
- **WHEN** 周期 ISR 置位 `TASK_ETHERCAT_PROCESS` 标志
|
||||
- **THEN** 主循环 `taskEthercatProcess()` 从双缓冲读取最新 PDO 数据并交给应用层处理
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 主循环空闲策略
|
||||
原: 空闲时关中断后进 WFI 休眠。
|
||||
改: 通过 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关控制;EtherCAT 启用时默认关闭 WFI 改忙等,避免唤醒延迟抖动。
|
||||
|
||||
### Requirement: 临界区实现
|
||||
原: 使用 `__disable_irq()`/`__set_PRIMASK()`(PRIMASK)全局关中断。
|
||||
改: 使用 `__set_BASEPRI()`/`__get_BASEPRI()`(BASEPRI)仅屏蔽 preempt≥1,保留 preempt=0 给 EtherCAT。统一通过 `sys/critical.h` 的 `RT_CRITICAL_ENTER()/RT_CRITICAL_EXIT()` 宏访问。
|
||||
|
||||
## Assumptions & Decisions
|
||||
|
||||
1. **ESC 型号**: LAN9252(内建 PHY),16-bit 并行 HBI 模式
|
||||
2. **MCU 接口**: GD32E508 EXMC NOR/SRAM 模式,16-bit 数据宽度
|
||||
3. **通信周期**: 100us~250us,抖动目标 <1us
|
||||
4. **SYNC0 同步**: 优先由 LAN9252 SYNC0 输出引脚(PB0/EXTI0)经 MCU EXTI0 独立向量触发(ESC 硬件同步,抖动最小);TIMER0 作为降级备选(`CONFIG_ETHERCAT_SYNC_MODE=1`)
|
||||
4a. **ESC 中断引脚(已确定)**: SYNC0=PB0(EXTI0,preempt=0)、IRQ=PB1(EXTI1,preempt=1)、SYNC1=PB2(EXTI2,preempt=1),三个独立 NVIC 向量,完全分离优先级;**保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)**
|
||||
4b. **深度睡眠兼容**: 引脚选择避开 PA0(WKUP),未来支持 STANDBY 最低功耗唤醒;Deep sleep 模式可用任意 EXTI(SYNC0/IRQ/CAN)唤醒
|
||||
5. **不包含**: 完整 EtherCAT 协议栈(SSC),后续可挂接到 `ethercat_slave.cpp` 接口
|
||||
6. **不包含**: EEPROM/ESI 配置内容、从站应用对象字典
|
||||
7. **引脚定义**: 基于 GD32E508 EXMC 标准映射 + 当前空闲引脚规划(见下表),配置项以宏定义在 `config.h`,用户可在原理图确认后调整
|
||||
8. **EXMC Region**: 使用 BANK0 Region1(NE1),基址 0x60000000
|
||||
9. **FPU 上下文**: 周期 ISR 不使用 FPU,避免惰性保存抖动;若协议栈后续需要 FPU,需评估
|
||||
10. **RAM 执行段**: 需要链接脚本支持(新增 `.ramfunc` section),但本次不改链接脚本,先用 `__attribute__((section))` 标记,实际 RAM 放置由后续配置
|
||||
11. **使能开关(未焊接保护)**: `CONFIG_ETHERCAT_ENABLE` 默认 0(禁用)。阶段 1 实时架构改造始终生效(不依赖硬件);阶段 2/3 的 EtherCAT 代码全部由 `#if CONFIG_ETHERCAT_ENABLE` 包裹,未焊接时 0→不编译不初始化,系统功能与改造前等价。用户焊接硬件后改为 1 重新编译即可启用
|
||||
|
||||
## 引脚定义(GD32E508 EXMC 标准映射 + ESC 中断)
|
||||
|
||||
### EXMC 16-bit 数据总线(全部 AF 复用,无当前占用冲突)
|
||||
| 信号 | MCU 引脚 | EXMC 功能 | 当前占用 |
|
||||
|---|---|---|---|
|
||||
| D0 | PD14 | EXMC_D0 | 空闲 |
|
||||
| D1 | PD15 | EXMC_D1 | 空闲 |
|
||||
| D2 | PD0 | EXMC_D2 | 空闲 |
|
||||
| D3 | PD1 | EXMC_D3 | 空闲 |
|
||||
| D4 | PE7 | EXMC_D4 | 空闲 |
|
||||
| D5 | PE8 | EXMC_D5 | 空闲 |
|
||||
| D6 | PE9 | EXMC_D6 | 空闲 |
|
||||
| D7 | PE10 | EXMC_D7 | 空闲 |
|
||||
| D8 | PE11 | EXMC_D8 | 空闲 |
|
||||
| D9 | PE12 | EXMC_D9 | 空闲 |
|
||||
| D10 | PE13 | EXMC_D10 | 空闲 |
|
||||
| D11 | PE14 | EXMC_D11 | 空闲 |
|
||||
| D12 | PE15 | EXMC_D12 | 空闲 |
|
||||
| D13 | PD8 | EXMC_D13 | 空闲 |
|
||||
| D14 | PD9 | EXMC_D14 | 空闲 |
|
||||
| D15 | PD10 | EXMC_D15 | 空闲 |
|
||||
|
||||
### EXMC 控制线
|
||||
| 信号 | MCU 引脚 | EXMC 功能 | 用途 |
|
||||
|---|---|---|---|
|
||||
| A16 | PD11 | EXMC_A16 | LAN9252 寄存器/数据选择(地址线) |
|
||||
| NOE | PD4 | EXMC_NOE | 读使能 |
|
||||
| NWE | PD5 | EXMC_NWE | 写使能 |
|
||||
| NE1 | PD7 | EXMC_NE1 | 片选(LAN9252) |
|
||||
|
||||
### ESC 中断引脚(已确定:PB0/PB1/PB2,三个独立向量,保留 PA0 给 STANDBY 唤醒)
|
||||
| 信号 | MCU 引脚 | EXTI | 中断向量 | preempt | 用途 |
|
||||
|---|---|---|---|---|---|
|
||||
| LAN9252 SYNC0 | PB0 | EXTI0 | **EXTI0_IRQn(独占)** | 0 | 周期同步触发(DC sync),永不被阻塞 |
|
||||
| LAN9252 SYNC1 | PB2 | EXTI2 | **EXTI2_IRQn(独占)** | 1 | 次周期同步(备用) |
|
||||
| LAN9252 IRQ | PB1 | EXTI1 | **EXTI1_IRQn(独占)** | 1 | ESC 事件(AL 状态等) |
|
||||
|
||||
> **引脚选择理由(考虑深度睡眠需求)**:
|
||||
> - PB0/PB1/PB2 分别对应 EXTI0/1/2,在 GD32E508 上各自拥有**独立的 NVIC 向量**(EXTI0_IRQn/EXTI1_IRQn/EXTI2_IRQn),彻底避免 EXTI5-9 共享 EXTI9_5_IRQn 的风险
|
||||
> - SYNC0 独占 preempt=0,IRQ/SYNC1 同级 preempt=1,SYNC0 可抢占 IRQ/SYNC1,满足 <1us 抖动目标
|
||||
> - **关键:保留 PA0 给未来 STANDBY 模式唤醒**。GD32E508 STANDBY 模式只有 PA0(WKUP_PIN0)能唤醒,若用于 SYNC0 会永久占用该唯一唤醒引脚。改用 PB0/PB1/PB2 后,未来 STANDBY 低功耗路径保留
|
||||
> - PB0/PB1/PB2 在当前代码中均未使用(已确认无占用冲突,仅 PB3/4/5=SPI0, PB9/12/13/14=CAN)
|
||||
> - 唤醒策略灵活:Deep sleep 模式可用 SYNC0/IRQ/CAN 等任意 EXTI 唤醒(恢复执行);Standby 模式用 PA0 唤醒(复位重启,适合完全断电场景)
|
||||
@@ -1,131 +0,0 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Task 1: 新增 `sys/critical.h` BASEPRI 临界区基础设施
|
||||
- [x] SubTask 1.1: 定义 `RT_CRITICAL_ENTER()` / `RT_CRITICAL_EXIT()` 宏,基于 `__set_BASEPRI()` / `__get_BASEPRI()`,阈值为 preempt≥1
|
||||
- [x] SubTask 1.2: 定义 `RT_CRITICAL_SAVE()` 返回保存值,`RT_CRITICAL_RESTORE(val)` 恢复,用于嵌套场景
|
||||
- [x] SubTask 1.3: 确认 GD32E508 (Cortex-M4F/M33) 支持 BASEPRI 寄存器
|
||||
|
||||
- [x] Task 2: 修改 `sys/config.h` 增加 NVIC 分组、优先级与引脚配置
|
||||
- [x] SubTask 2.0: 增加 `CONFIG_ETHERCAT_ENABLE` 宏(默认 0,禁用),作为所有 EtherCAT 相关代码的编译使能开关
|
||||
- [x] SubTask 2.1: 增加 `CONFIG_NVIC_PRIORITY_GROUP` 宏设为 `NVIC_PRIGROUP_PRE4_SUB0`
|
||||
- [x] SubTask 2.2: 增加各中断抢占优先级宏:`CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC=0` / `CONFIG_IRQ_PREEMPT_SCHED=1` / `CONFIG_IRQ_PREEMPT_CAN=2` / `CONFIG_IRQ_PREEMPT_ETHERCAT_ESC=1` / `CONFIG_IRQ_PREEMPT_SPI_USART=3`
|
||||
- [x] SubTask 2.3: 增加 `CONFIG_MAIN_LOOP_WFI_ENABLE` 开关宏(默认 0,关闭 WFI)
|
||||
- [x] SubTask 2.4: 增加 `CONFIG_ETHERCAT_SYNC_MODE` 宏(EXTI=0 默认 / TIMER=1 降级)
|
||||
- [x] SubTask 2.5: 增加 EXMC 引脚配置宏:D0-D15 / A16 / NOE / NWE / NE1 引脚(基于标准映射 PD0/PD1/PD4/PD5/PD7/PD8-PD11/PD14/PD15,PE7-PE15)
|
||||
- [x] SubTask 2.6: 增加 ESC 中断引脚宏:`CONFIG_ESC_SYNC0_PORT/PIN/EXTI/IRQn`(GPIOB/PB0/EXTI0/EXTI0_IRQn)、`CONFIG_ESC_IRQ_PORT/PIN/EXTI/IRQn`(GPIOB/PB1/EXTI1/EXTI1_IRQn)、`CONFIG_ESC_SYNC1_PORT/PIN/EXTI/IRQn`(GPIOB/PB2/EXTI2/EXTI2_IRQn),三个独立向量(保留 PA0 给 STANDBY 唤醒)
|
||||
- [x] SubTask 2.7: 增加 EXMC Region 基址宏 `CONFIG_ESC_EXMC_BASE_ADDR`(0x60000000)和 `CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1`
|
||||
|
||||
- [x] Task 3: 修改 `device/Timer.cpp` NVIC 分组与调度器优先级
|
||||
- [x] SubTask 3.1: `configureNvic()` 中 `nvic_priority_group_set` 改为 `CONFIG_NVIC_PRIORITY_GROUP`
|
||||
- [x] SubTask 3.2: `CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO` 改为 1,`CONFIG_SCHED_TIMER_IRQ_SUB_PRIO` 保持 0
|
||||
|
||||
- [x] Task 4: 修改 `sys/task_manager.cpp` 临界区换 BASEPRI
|
||||
- [x] SubTask 4.1: 移除本地 `enterCriticalSection()` / `exitCriticalSection()`(基于 PRIMASK)
|
||||
- [x] SubTask 4.2: 改用 `sys/critical.h` 的 `RT_CRITICAL_SAVE()/RESTORE()`
|
||||
- [x] SubTask 4.3: `set()` 和 `takePending()` 使用新临界区宏
|
||||
|
||||
- [x] Task 5: 修改 `device/CANFD.cpp` 优先级与临界区
|
||||
- [x] SubTask 5.1: 移除本地 `enterCriticalSection()` / `exitCriticalSection()`,改用 `sys/critical.h`
|
||||
- [x] 5.2: `init()` 中 `nvic_irq_enable` CAN RX0 优先级从 (1, n) 改为 (2, n)
|
||||
- [x] 5.3: 确认 `handleRxInterrupt()` 已只做入队+置位(当前已满足,验证即可)
|
||||
|
||||
- [x] Task 6: 修改 `device/app_spi_command.cpp` 优先级、临界区与 ISR 瘦身
|
||||
- [x] SubTask 6.1: `configureSpi0()` 中 SPI0 NVIC 优先级从 (2, 0) 改为 `(CONFIG_IRQ_PREEMPT_SPI_USART, 0)`
|
||||
- [x] SubTask 6.2: `configureExti()` 中 EXTI10_15 NVIC 优先级从 (1, 1) 改为 `(CONFIG_IRQ_PREEMPT_SPI_USART, 1)`
|
||||
- [x] SubTask 6.3: `poll()` 中 `__disable_irq/__enable_irq` 改用 `RT_CRITICAL_SAVE/RESTORE`
|
||||
- [x] SubTask 6.4: ISR 瘦身:`onCsRelease()` 中 `processCommand()` 移出 ISR,改为置位 `TASK_SPI_CMD_PROCESS` 标志由主循环执行
|
||||
- [x] SubTask 6.5: `AppSpiCommand` 类增加 `processPendingCommand()` 公有方法供主循环调用,内部调用 `processCommand()`
|
||||
- [x] SubTask 6.6: 增加 `volatile bool m_cmdPending` 标志,`onCsRelease()` 置位,`processPendingCommand()` 清除
|
||||
|
||||
- [x] Task 7: 修改 `device/usart0.cpp` 优先级与临界区
|
||||
- [x] SubTask 7.1: `init()` 中 USART0 NVIC 优先级从 (2, 0) 改为 (3, 0)
|
||||
- [x] SubTask 7.2: `putCharNonBlocking()` 中 `__disable_irq/__enable_irq` 改用 `RT_CRITICAL_SAVE/RESTORE`
|
||||
|
||||
- [x] Task 8: 修改 `main.h` 增加任务枚举
|
||||
- [x] SubTask 8.1: `system_task_t` 增加 `TASK_ETHERCAT_PROCESS`
|
||||
- [x] SubTask 8.2: `system_task_t` 增加 `TASK_SPI_CMD_PROCESS`(用于 SPI CS release 后的命令解析,移出 ISR)
|
||||
|
||||
- [x] Task 9: 修改 `main.cpp` 主循环 WFI 策略
|
||||
- [x] SubTask 9.1: 用 `#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0)` 包裹 WFI 代码块
|
||||
- [x] SubTask 9.2: WFI 关闭时改为 `__NOP()` 空转忙等
|
||||
- [x] SubTask 9.3: WFI 块内的临界区改用 `RT_CRITICAL_SAVE/RESTORE`
|
||||
|
||||
- [x] Task 10: 新建 `comm/esc_exmc_bus.h` EXMC 总线抽象接口
|
||||
- [x] SubTask 10.1: 声明 `EscExmcBus` 类:`init()` / `read16(addr)` / `write16(addr, data)` / `readBlock(addr, buf, len)` / `writeBlock(addr, buf, len)`
|
||||
- [x] SubTask 10.2: 定义 EXMC 寄存器基址宏、Region 选择宏(待引脚确认)
|
||||
|
||||
- [x] Task 11: 新建 `comm/esc_exmc_bus.cpp` EXMC 总线实现
|
||||
- [x] SubTask 11.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空)
|
||||
- [x] SubTask 11.1: `init()`:配置 EXMC NOR/SRAM Region(GPIO 时钟、EXMC 时钟、时序参数)
|
||||
- [x] SubTask 11.2: GPIO 配置:D0-D15 / A16 / NOE / NWE / NE1(引脚基于标准映射)
|
||||
- [x] SubTask 11.3: EXMC 时序参数:按 LAN9252 HBI 时序要求配置(read/write wait state)
|
||||
- [x] SubTask 11.4: `read16/write16`:通过内存映射地址(0x60000000)直接读写
|
||||
- [x] SubTask 11.5: `readBlock/writeBlock`:循环调用 read16/write16
|
||||
|
||||
- [x] Task 12: 新建 `comm/esc_lan9252.h` LAN9252 ESC 驱动接口
|
||||
- [x] SubTask 12.1: 定义 LAN9252 关键寄存器地址宏(ESC ID、PDI 控制、中断使能等)
|
||||
- [x] SubTask 12.2: 声明 `EscLan9252` 类:`init()` / `selfTest()` / `readReg(addr)` / `writeReg(addr, val)` / `readPdi(offset, buf, len)` / `writePdi(offset, buf, len)`
|
||||
|
||||
- [x] Task 13: 新建 `comm/esc_lan9252.cpp` LAN9252 ESC 驱动实现
|
||||
- [x] SubTask 13.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空)
|
||||
- [x] SubTask 13.1: `init()`:调用 `EscExmcBus::init()`,初始化 ESC 基本寄存器
|
||||
- [x] SubTask 13.2: `selfTest()`:读 ESC ID 寄存器校验(0x87654321 或芯片 ID)
|
||||
- [x] SubTask 13.3: `readReg/writeReg`:封装 EXMC 读写 + 地址映射
|
||||
- [x] SubTask 13.4: `readPdi/writePdi`:PDI 内存块读写
|
||||
|
||||
- [x] Task 14: 新建 `comm/ethercat_slave.h` 从站周期接口与双缓冲
|
||||
- [x] SubTask 14.1: 定义 PDO 输入/输出缓冲结构体(大小可配置)
|
||||
- [x] SubTask 14.2: 声明 `EthercatSlave` 类:`init()` / `periodicIsr()` / `getLatestInputs()` / `setOutputs()` / `process()`
|
||||
- [x] SubTask 14.3: 声明 ping-pong 双缓冲成员:两个输入缓冲 + 原子指针索引
|
||||
|
||||
- [x] Task 15: 新建 `comm/ethercat_slave.cpp` 从站周期实现
|
||||
- [x] SubTask 15.0: 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空)
|
||||
- [x] SubTask 15.1: `init()`:调用 `EscLan9252::init()` + `selfTest()` + 初始化双缓冲
|
||||
- [x] SubTask 15.2: `periodicIsr()`:标 `__attribute__((section(".ramfunc")))`,读输入 PDO → 写输出 PDO → 翻转缓冲指针 → 置位 `TASK_ETHERCAT_PROCESS`
|
||||
- [x] SubTask 15.3: `getLatestInputs()`:原子读取缓冲指针返回最新输入数据指针(无锁)
|
||||
- [x] SubTask 15.4: `setOutputs()`:写入输出缓冲供下次 ISR 发送
|
||||
- [x] SubTask 15.5: `process()`:应用层 PDO 映射入口(非实时,留接口)
|
||||
|
||||
- [x] Task 16: 新建 `comm/ethercat_sync.h` / `comm/ethercat_sync.cpp` SYNC0 同步(EXTI 独立向量 / TIMER0 降级)
|
||||
- [x] SubTask 16.0: `.cpp` 文件整体用 `#if CONFIG_ETHERCAT_ENABLE` 包裹实现(未使能时编译为空,EXTI0/1/2 不配置)
|
||||
- [x] SubTask 16.1: 声明 `EthercatSync` 类:`init()` / `onSync0Exti()` / `onEscIrqExti()` / `onSync1Exti()` / `onTimerIrq()`
|
||||
- [x] SubTask 16.2: `init()`:配置 SYNC0 EXTI(PB0/EXTI0,下降沿,NVIC EXTI0_IRQn preempt=0,sub=0)
|
||||
- [x] SubTask 16.3: `init()`:配置 IRQ EXTI(PB1/EXTI1,下降沿,NVIC EXTI1_IRQn preempt=1,sub=0)
|
||||
- [x] SubTask 16.4: `init()`:配置 SYNC1 EXTI(PB2/EXTI2,下降沿,NVIC EXTI2_IRQn preempt=1,sub=0)
|
||||
- [x] SubTask 16.5: `init()`:GPIO PB0/PB1/PB2 时钟使能与输入模式配置(GPIOB)
|
||||
- [x] SubTask 16.6: 降级模式(`CONFIG_ETHERCAT_SYNC_MODE==1`)时配置 TIMER0 周期 + NVIC preempt=0
|
||||
- [x] SubTask 16.7: `onSync0Exti()`:清 EXTI0 标志 → 调用 `EthercatSlave::periodicIsr()`
|
||||
- [x] SubTask 16.8: `onEscIrqExti()`:清 EXTI1 标志 → 置位 `TASK_ETHERCAT_PROCESS`
|
||||
- [x] SubTask 16.9: `onSync1Exti()`:清 EXTI2 标志(备用,暂不处理)
|
||||
- [x] SubTask 16.10: 提供 `EXTI0_IRQHandler` / `EXTI1_IRQHandler` / `EXTI2_IRQHandler` C 包装(及降级模式 `TIMER0_IRQHandler`)
|
||||
|
||||
- [x] Task 17: 修改 `sys/initial.cpp` 集成 EtherCAT 初始化(受使能开关保护)
|
||||
- [x] SubTask 17.1: `AppInitializer::init()` 中在 `SchedulerTimer::init()` 之后,用 `#if CONFIG_ETHERCAT_ENABLE` 包裹 `#include "comm/ethercat_slave.h"` 与 `comm/ethercat_sync.h"`
|
||||
- [x] SubTask 17.2: 在 `#if CONFIG_ETHERCAT_ENABLE` 块内调用 `EthercatSlave::init()`
|
||||
- [x] SubTask 17.3: 在 `EthercatSlave::init()` 之后(同块内)调用 `EthercatSync::init()`
|
||||
|
||||
- [x] Task 18: 修改 `sys/task_runner.cpp` 增加 EtherCAT 与 SPI 命令处理任务
|
||||
- [x] SubTask 18.1: 增加 `taskEthercatProcess()` 私有方法:用 `#if CONFIG_ETHERCAT_ENABLE` 包裹,内部调用 `EthercatSlave::getLatestInputs()` + `EthercatSlave::process()`
|
||||
- [x] SubTask 18.2: 增加 `taskSpiCmdProcess()` 私有方法:调用 `AppSpiCommandBus.processPendingCommand()`(此功能独立于 EtherCAT,不受 `CONFIG_ETHERCAT_ENABLE` 限制,阶段1 ISR 瘦身已需要)
|
||||
- [x] SubTask 18.3: `runPending()` 中增加 `TASK_ETHERCAT_PROCESS` 分支调用(受 `#if CONFIG_ETHERCAT_ENABLE` 保护)
|
||||
- [x] SubTask 18.4: `runPending()` 中增加 `TASK_SPI_CMD_PROCESS` 分支调用(无条件,阶段1 改造已需要)
|
||||
|
||||
- [x] Task 19: 构建配置纳入新文件
|
||||
- [x] SubTask 19.1: 确认 `GD32E508_DCU_APP.cproject.yml` 中 source 路径包含 `comm/` 目录
|
||||
- [x] SubTask 19.2: 确认 include 路径包含 `comm/` 目录
|
||||
|
||||
- [x] Task 20: 编译验证
|
||||
- [x] SubTask 20.1: 全量编译通过,无 error
|
||||
- [x] SubTask 20.2: 检查无新增 warning
|
||||
- [x] SubTask 20.3: 检查无遗漏的 `__disable_irq/__set_PRIMASK`(应全部改为 BASEPRI)
|
||||
|
||||
# Task Dependencies
|
||||
- Task 1 → Task 4, 5, 6, 7, 9(临界区宏是改造前提)
|
||||
- Task 2 → Task 3, 5, 6, 7, 16(config 优先级与引脚宏)
|
||||
- Task 8 → Task 6.4, 18(任务枚举先定义,才能在 ISR/task_runner 中引用)
|
||||
- Task 10, 12 → Task 11, 13(EXMC 接口定义在实现前)
|
||||
- Task 13, 14 → Task 15(ESC 驱动 + 从站接口在周期实现前)
|
||||
- Task 14, 16 → Task 15(双缓冲 + 同步中断在周期实现前)
|
||||
- Task 1-9(阶段1) 可与 Task 10-16(阶段2) 部分并行:阶段1 改造不依赖 comm/ 代码
|
||||
- Task 17, 18(阶段3) 依赖阶段1+阶段2 全部完成
|
||||
- Task 19 → Task 20(构建配置在编译验证前)
|
||||
@@ -30,7 +30,7 @@ solution:
|
||||
target-set:
|
||||
- set:
|
||||
images:
|
||||
- project-context: GD32E508_DCU_APP.Debug
|
||||
- project-context: GD32E508_DCU_APP.Release
|
||||
debugger:
|
||||
name: ST-Link@pyOCD
|
||||
clock: 4000000
|
||||
@@ -44,7 +44,7 @@ solution:
|
||||
|
||||
- type: Release
|
||||
debug: off
|
||||
optimize: balanced
|
||||
optimize: size
|
||||
|
||||
# List related projects.
|
||||
projects:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# A project translates into one executable or library.
|
||||
# A project translates into one executable or library.
|
||||
project:
|
||||
|
||||
groups:
|
||||
@@ -16,10 +16,19 @@ project:
|
||||
- file: ./sys/task_manager.cpp
|
||||
- file: ./sys/task_runner.h
|
||||
- file: ./sys/task_runner.cpp
|
||||
- file: ./sys/cpu_load.h
|
||||
- file: ./sys/cpu_load.cpp
|
||||
- file: ./sys/ota_boot_request.h
|
||||
- file: ./sys/ota_boot_request.cpp
|
||||
- file: ./sys/aes128.h
|
||||
- file: ./sys/aes128.cpp
|
||||
- file: ./sys/motor_ota_relay.h
|
||||
- file: ./sys/motor_ota_relay.cpp
|
||||
- file: ./sys/hardfault.cpp
|
||||
- file: ./sys/cpp_stubs.cpp
|
||||
- file: ./sys/config.h
|
||||
- file: ./sys/esp32_en.h
|
||||
- file: ./sys/esp32_en.cpp
|
||||
- group: device
|
||||
files:
|
||||
- file: ./device/adc.h
|
||||
@@ -30,10 +39,18 @@ project:
|
||||
- file: ./device/timer.cpp
|
||||
- file: ./device/usart0.h
|
||||
- file: ./device/usart0.cpp
|
||||
- file: ./device/temp_sensor.h
|
||||
- file: ./device/temp_sensor.cpp
|
||||
- file: ./device/watchdog.h
|
||||
- file: ./device/watchdog.cpp
|
||||
- file: ./device/motor_state.h
|
||||
- file: ./device/motor_state.cpp
|
||||
- file: ./device/motor_version.h
|
||||
- file: ./device/motor_version.cpp
|
||||
# GigaDevice DFP 未把 EXMC 注册为 CMSIS 组件(仅有 FMC=片内 Flash),
|
||||
# 但外设源文件存在于 Pack;LAN9252 并行总线需要 EXMC,故本地编入。
|
||||
- group: device_exmc
|
||||
files:
|
||||
- file: ./RTE/Device/GD32E508VE/gd32e50x_exmc.c
|
||||
- file: ./RTE/Device/GD32E508VE/gd32e50x_exmc.h
|
||||
- group: device/spi
|
||||
files:
|
||||
- file: ./device/spi/app_spi_command.h
|
||||
@@ -43,9 +60,21 @@ project:
|
||||
- file: ./device/spi/app_spi_cmd_boot_request.cpp
|
||||
- file: ./device/spi/app_spi_cmd_ota_wait.cpp
|
||||
- file: ./device/spi/app_spi_cmd_ping.cpp
|
||||
- file: ./device/spi/app_spi_cmd_init_status.cpp
|
||||
- file: ./device/spi/app_spi_cmd_can_bridge.cpp
|
||||
- file: ./device/spi/app_spi_cmd_can_burst.cpp
|
||||
- file: ./device/spi/app_spi_cmd_can_feedback.cpp
|
||||
- file: ./device/spi/app_spi_cmd_id_snapshot.cpp
|
||||
- file: ./device/spi/app_spi_cmd_adc_feedback.cpp
|
||||
- file: ./device/spi/app_spi_cmd_motor_state.cpp
|
||||
- file: ./device/spi/app_spi_cmd_motor_version.cpp
|
||||
- file: ./device/spi/app_spi_cmd_motor_ota.cpp
|
||||
- file: ./device/spi/app_spi_cmd_exmc_diag.cpp
|
||||
- file: ./device/spi/app_spi_diag.h
|
||||
- file: ./device/spi/app_spi_cmd_spi_diag.cpp
|
||||
- group: spi_link
|
||||
files:
|
||||
- file: ../../spi_link/spi_link_v2.cpp
|
||||
- group: comm
|
||||
files:
|
||||
- file: ./comm/esc_exmc_bus.h
|
||||
@@ -56,6 +85,39 @@ project:
|
||||
- file: ./comm/ethercat_slave.cpp
|
||||
- file: ./comm/ethercat_sync.h
|
||||
- file: ./comm/ethercat_sync.cpp
|
||||
- file: ./comm/ethercat_can_bridge.h
|
||||
- file: ./comm/ethercat_can_bridge.cpp
|
||||
# 电机 OTA 的 EtherCAT 邮箱进料通路(CoE 0x2510/0x2511 → MotorOtaRelay 环缓)
|
||||
- file: ./comm/ecat_motor_ota.h
|
||||
- file: ./comm/ecat_motor_ota.cpp
|
||||
- file: ./comm/ethercat_pdo.h
|
||||
- file: ./comm/ethercat_pdo.cpp
|
||||
# [DIFF-STATS] 临时仪表:差分写取证,删除时连同本两行与 comm/ethercat_diff_stats.* 一并移除
|
||||
- file: ./comm/ethercat_diff_stats.h
|
||||
- file: ./comm/ethercat_diff_stats.cpp
|
||||
- group: comm/ssc
|
||||
files:
|
||||
- file: ./comm/ssc/inc/ecatslv.h
|
||||
- file: ./comm/ssc/inc/ecatappl.h
|
||||
- file: ./comm/ssc/inc/mailbox.h
|
||||
- file: ./comm/ssc/inc/ecatcoe.h
|
||||
- file: ./comm/ssc/inc/sdoserv.h
|
||||
- file: ./comm/ssc/inc/objdef.h
|
||||
- file: ./comm/ssc/inc/coeappl.h
|
||||
- file: ./comm/ssc/inc/esc.h
|
||||
- file: ./comm/ssc/inc/applInterface.h
|
||||
- file: ./comm/ssc/inc/el9800hw.h
|
||||
- file: ./comm/ssc/src/ecatslv.c
|
||||
- file: ./comm/ssc/src/ecatappl.c
|
||||
- file: ./comm/ssc/src/mailbox.c
|
||||
- file: ./comm/ssc/src/ecatcoe.c
|
||||
- file: ./comm/ssc/src/sdoserv.c
|
||||
- file: ./comm/ssc/src/objdef.c
|
||||
- file: ./comm/ssc/src/coeappl.c
|
||||
- file: ./comm/ssc/port/hw_gd32exmc.cpp
|
||||
- file: ./comm/ssc/port/dc_app.c
|
||||
- file: ./comm/ssc/port/dc_app.h
|
||||
- file: ./comm/ssc/port/ecat_def.h
|
||||
|
||||
# List components to use for your application.
|
||||
# A software component is a re-usable unit that may be configurable.
|
||||
@@ -96,17 +158,28 @@ project:
|
||||
- -D__MICROLIB
|
||||
- -fshort-enums
|
||||
- -fshort-wchar
|
||||
# EXMC 未作为 CMSIS 组件分发;头文件在 RTE 本地目录
|
||||
- -IRTE/Device/GD32E508VE
|
||||
- -Wno-register
|
||||
- -IG:/Robot_v2/DCU/spi_link
|
||||
C:
|
||||
- -std=gnu11
|
||||
CPP:
|
||||
- -std=gnu++17
|
||||
# 全工程零使用 RTTI/异常(grep 验证),关闭以剥离 libcppabi 运行时(约 2.5 KB Flash)
|
||||
- -fno-rtti
|
||||
- -fno-exceptions
|
||||
ASM:
|
||||
- -DGD32E508
|
||||
- -DGD32E50X
|
||||
- -DHXTAL_VALUE=8000000U
|
||||
- -gdwarf-5
|
||||
- -D__MICROLIB
|
||||
# 启动文件经 legacy armasm 汇编:消 armclang 驱动"参数未使用"告警 + armasm 弃用告警(A1950W)
|
||||
- -Wno-unused-command-line-argument
|
||||
- -Wa,armasm,--diag_suppress=A1950W
|
||||
Link:
|
||||
- --library_type=microlib
|
||||
- --entry=Reset_Handler
|
||||
# 输出未引用段与总量报告,用于 Flash 尺寸归因
|
||||
- --info=unused,totals
|
||||
|
||||
@@ -0,0 +1,708 @@
|
||||
/*!
|
||||
\file gd32e50x_exmc.c
|
||||
\brief EXMC driver
|
||||
|
||||
\version 2026-02-09, V1.7.0, firmware for GD32E50x
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2025, GigaDevice Semiconductor Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "gd32e50x_exmc.h"
|
||||
|
||||
/* EXMC bank0 register reset value */
|
||||
#define BANK0_SNCTL_REGION0_RESET ((uint32_t)0x000030DBU)
|
||||
#define BANK0_SNCTL_REGION1_2_3_RESET ((uint32_t)0x000030D2U)
|
||||
#define BANK0_SNTCFG_RESET ((uint32_t)0x0FFFFFFFU)
|
||||
#define BANK0_SNWTCFG_RESET ((uint32_t)0x0FFFFFFFU)
|
||||
|
||||
/* EXMC bank1/2 register reset mask */
|
||||
#define BANK1_2_NPCTL_RESET ((uint32_t)0x00000018U)
|
||||
#define BANK1_2_NPINTEN_RESET ((uint32_t)0x00000042U)
|
||||
#define BANK1_2_NPCTCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK1_2_NPATCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
|
||||
/* EXMC bank3 register reset mask */
|
||||
#define BANK3_NPCTL_RESET ((uint32_t)0x00000018U)
|
||||
#define BANK3_NPINTEN_RESET ((uint32_t)0x00000043U)
|
||||
#define BANK3_NPCTCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK3_NPATCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK3_PIOTCFG3_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
|
||||
/* EXMC register bit offset */
|
||||
#define SNCTL_NRMUX_OFFSET ((uint32_t)1U)
|
||||
#define SNCTL_SBRSTEN_OFFSET ((uint32_t)8U)
|
||||
#define SNCTL_WRAPEN_OFFSET ((uint32_t)10U)
|
||||
#define SNCTL_WREN_OFFSET ((uint32_t)12U)
|
||||
#define SNCTL_NRWTEN_OFFSET ((uint32_t)13U)
|
||||
#define SNCTL_EXMODEN_OFFSET ((uint32_t)14U)
|
||||
#define SNCTL_ASYNCWAIT_OFFSET ((uint32_t)15U)
|
||||
|
||||
#define SNTCFG_AHLD_OFFSET ((uint32_t)4U)
|
||||
#define SNTCFG_DSET_OFFSET ((uint32_t)8U)
|
||||
#define SNTCFG_BUSLAT_OFFSET ((uint32_t)16U)
|
||||
|
||||
#define SNWTCFG_WAHLD_OFFSET ((uint32_t)4U)
|
||||
#define SNWTCFG_WDSET_OFFSET ((uint32_t)8U)
|
||||
#define SNWTCFG_WBUSLAT_OFFSET ((uint32_t)16U)
|
||||
|
||||
#define NPCTL_NDWTEN_OFFSET ((uint32_t)1U)
|
||||
#define NPCTL_ECCEN_OFFSET ((uint32_t)6U)
|
||||
|
||||
#define NPCTCFG_COMWAIT_OFFSET ((uint32_t)8U)
|
||||
#define NPCTCFG_COMHLD_OFFSET ((uint32_t)16U)
|
||||
#define NPCTCFG_COMHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define NPATCFG_ATTWAIT_OFFSET ((uint32_t)8U)
|
||||
#define NPATCFG_ATTHLD_OFFSET ((uint32_t)16U)
|
||||
#define NPATCFG_ATTHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define PIOTCFG_IOWAIT_OFFSET ((uint32_t)8U)
|
||||
#define PIOTCFG_IOHLD_OFFSET ((uint32_t)16U)
|
||||
#define PIOTCFG_IOHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define INTEN_INTS_OFFSET ((uint32_t)3U)
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC NOR/SRAM region
|
||||
\param[in] exmc_norsram_region: select the region of bank0
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_deinit(uint32_t exmc_norsram_region)
|
||||
{
|
||||
/* reset the registers */
|
||||
if(EXMC_BANK0_NORSRAM_REGION0 == exmc_norsram_region){
|
||||
EXMC_SNCTL(exmc_norsram_region) = BANK0_SNCTL_REGION0_RESET;
|
||||
}else{
|
||||
EXMC_SNCTL(exmc_norsram_region) = BANK0_SNCTL_REGION1_2_3_RESET;
|
||||
}
|
||||
EXMC_SNTCFG(exmc_norsram_region) = BANK0_SNTCFG_RESET;
|
||||
EXMC_SNWTCFG(exmc_norsram_region) = BANK0_SNWTCFG_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_norsram_parameter_struct with the default values
|
||||
\param[in] none
|
||||
\param[out] exmc_norsram_init_struct: the initialized struct exmc_norsram_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_struct_para_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_norsram_init_struct->norsram_region = EXMC_BANK0_NORSRAM_REGION0;
|
||||
exmc_norsram_init_struct->address_data_mux = ENABLE;
|
||||
exmc_norsram_init_struct->memory_type = EXMC_MEMORY_TYPE_SRAM;
|
||||
exmc_norsram_init_struct->databus_width = EXMC_NOR_DATABUS_WIDTH_8B;
|
||||
exmc_norsram_init_struct->burst_mode = DISABLE;
|
||||
exmc_norsram_init_struct->nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
|
||||
exmc_norsram_init_struct->wrap_burst_mode = DISABLE;
|
||||
exmc_norsram_init_struct->nwait_config = EXMC_NWAIT_CONFIG_BEFORE;
|
||||
exmc_norsram_init_struct->memory_write = ENABLE;
|
||||
exmc_norsram_init_struct->nwait_signal = ENABLE;
|
||||
exmc_norsram_init_struct->extended_mode = DISABLE;
|
||||
exmc_norsram_init_struct->asyn_wait = DISABLE;
|
||||
exmc_norsram_init_struct->write_mode = EXMC_ASYN_WRITE;
|
||||
|
||||
/* read/write timing configure */
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_address_setuptime = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_address_holdtime = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_data_setuptime = 0xFFU;
|
||||
exmc_norsram_init_struct->read_write_timing->bus_latency = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->syn_clk_division = EXMC_SYN_CLOCK_RATIO_16_CLK;
|
||||
exmc_norsram_init_struct->read_write_timing->syn_data_latency = EXMC_DATALAT_17_CLK;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
|
||||
/* write timing configure, when extended mode is used */
|
||||
exmc_norsram_init_struct->write_timing->asyn_address_setuptime = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_address_holdtime = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_data_setuptime = 0xFFU;
|
||||
exmc_norsram_init_struct->write_timing->bus_latency = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC NOR/SRAM region
|
||||
\param[in] exmc_norsram_parameter_struct: configure the EXMC NOR/SRAM parameter
|
||||
norsram_region: EXMC_BANK0_NORSRAM_REGIONx,x=0..3
|
||||
write_mode: EXMC_ASYN_WRITE,EXMC_SYN_WRITE
|
||||
extended_mode: ENABLE or DISABLE
|
||||
asyn_wait: ENABLE or DISABLE
|
||||
nwait_signal: ENABLE or DISABLE
|
||||
memory_write: ENABLE or DISABLE
|
||||
nwait_config: EXMC_NWAIT_CONFIG_BEFORE,EXMC_NWAIT_CONFIG_DURING
|
||||
wrap_burst_mode: ENABLE or DISABLE
|
||||
nwait_polarity: EXMC_NWAIT_POLARITY_LOW,EXMC_NWAIT_POLARITY_HIGH
|
||||
burst_mode: ENABLE or DISABLE
|
||||
databus_width: EXMC_NOR_DATABUS_WIDTH_8B,EXMC_NOR_DATABUS_WIDTH_16B
|
||||
memory_type: EXMC_MEMORY_TYPE_SRAM,EXMC_MEMORY_TYPE_PSRAM,EXMC_MEMORY_TYPE_NOR
|
||||
address_data_mux: ENABLE or DISABLE
|
||||
read_write_timing: struct exmc_norsram_timing_parameter_struct set the time
|
||||
asyn_access_mode: EXMC_ACCESS_MODE_A,EXMC_ACCESS_MODE_B,EXMC_ACCESS_MODE_C,EXMC_ACCESS_MODE_D
|
||||
syn_data_latency: EXMC_DATALAT_n_CLK,(n=2,..,17)
|
||||
syn_clk_division: EXMC_SYN_CLOCK_RATIO_DISABLE, EXMC_SYN_CLOCK_RATIO_n_CLK,(n=2,..,16)
|
||||
bus_latency: 1,..,16
|
||||
asyn_data_setuptime: 2,..,256
|
||||
asyn_address_holdtime: 2,..,16
|
||||
asyn_address_setuptime: 1,..,16
|
||||
write_timing: struct exmc_norsram_timing_parameter_struct set the time
|
||||
asyn_access_mode: EXMC_ACCESS_MODE_A,EXMC_ACCESS_MODE_B,EXMC_ACCESS_MODE_C,EXMC_ACCESS_MODE_D
|
||||
syn_data_latency: EXMC_DATALAT_n_CLK,(n=2,..,17)
|
||||
syn_clk_division: EXMC_SYN_CLOCK_RATIO_DISABLE, EXMC_SYN_CLOCK_RATIO_n_CLK,(n=2,..,16)
|
||||
bus_latency: 1,..,16
|
||||
asyn_data_setuptime: 2,..,256
|
||||
asyn_address_holdtime: 2,..,16
|
||||
asyn_address_setuptime: 1,..,16
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct)
|
||||
{
|
||||
uint32_t snctl = 0x00000000U,sntcfg = 0x00000000U,snwtcfg = 0x00000000U;
|
||||
|
||||
/* get the register value */
|
||||
snctl = EXMC_SNCTL(exmc_norsram_init_struct->norsram_region);
|
||||
|
||||
/* clear relative bits */
|
||||
snctl &= ((uint32_t)~(EXMC_SNCTL_NRMUX | EXMC_SNCTL_NRTP | EXMC_SNCTL_NRW | EXMC_SNCTL_SBRSTEN |
|
||||
EXMC_SNCTL_NREN | EXMC_SNCTL_NRWTPOL | EXMC_SNCTL_WRAPEN | EXMC_SNCTL_NRWTCFG |
|
||||
EXMC_SNCTL_WREN | EXMC_SNCTL_NRWTEN | EXMC_SNCTL_EXMODEN | EXMC_SNCTL_ASYNCWAIT |
|
||||
EXMC_SNCTL_SYNCWR ));
|
||||
|
||||
snctl |= (uint32_t)(exmc_norsram_init_struct->address_data_mux << SNCTL_NRMUX_OFFSET) |
|
||||
exmc_norsram_init_struct->memory_type |
|
||||
exmc_norsram_init_struct->databus_width |
|
||||
(exmc_norsram_init_struct->burst_mode << SNCTL_SBRSTEN_OFFSET) |
|
||||
exmc_norsram_init_struct->nwait_polarity |
|
||||
(exmc_norsram_init_struct->wrap_burst_mode << SNCTL_WRAPEN_OFFSET) |
|
||||
exmc_norsram_init_struct->nwait_config |
|
||||
(exmc_norsram_init_struct->memory_write << SNCTL_WREN_OFFSET) |
|
||||
(exmc_norsram_init_struct->nwait_signal << SNCTL_NRWTEN_OFFSET) |
|
||||
(exmc_norsram_init_struct->extended_mode << SNCTL_EXMODEN_OFFSET) |
|
||||
(exmc_norsram_init_struct->asyn_wait << SNCTL_ASYNCWAIT_OFFSET) |
|
||||
exmc_norsram_init_struct->write_mode;
|
||||
|
||||
sntcfg = (uint32_t)((exmc_norsram_init_struct->read_write_timing->asyn_address_setuptime - 1U ) & EXMC_SNTCFG_ASET )|
|
||||
(((exmc_norsram_init_struct->read_write_timing->asyn_address_holdtime - 1U ) << SNTCFG_AHLD_OFFSET ) & EXMC_SNTCFG_AHLD ) |
|
||||
(((exmc_norsram_init_struct->read_write_timing->asyn_data_setuptime - 1U ) << SNTCFG_DSET_OFFSET ) & EXMC_SNTCFG_DSET ) |
|
||||
(((exmc_norsram_init_struct->read_write_timing->bus_latency - 1U ) << SNTCFG_BUSLAT_OFFSET ) & EXMC_SNTCFG_BUSLAT )|
|
||||
exmc_norsram_init_struct->read_write_timing->syn_clk_division |
|
||||
exmc_norsram_init_struct->read_write_timing->syn_data_latency |
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_access_mode;
|
||||
|
||||
/* nor flash access enable */
|
||||
if(EXMC_MEMORY_TYPE_NOR == exmc_norsram_init_struct->memory_type){
|
||||
snctl |= (uint32_t)EXMC_SNCTL_NREN;
|
||||
}
|
||||
|
||||
/* extended mode configure */
|
||||
if(ENABLE == exmc_norsram_init_struct->extended_mode){
|
||||
snwtcfg = (uint32_t)((exmc_norsram_init_struct->write_timing->asyn_address_setuptime - 1U) & EXMC_SNWTCFG_WASET ) |
|
||||
(((exmc_norsram_init_struct->write_timing->asyn_address_holdtime -1U ) << SNWTCFG_WAHLD_OFFSET ) & EXMC_SNWTCFG_WAHLD )|
|
||||
(((exmc_norsram_init_struct->write_timing->asyn_data_setuptime -1U ) << SNWTCFG_WDSET_OFFSET ) & EXMC_SNWTCFG_WDSET )|
|
||||
(((exmc_norsram_init_struct->write_timing->bus_latency - 1U ) << SNWTCFG_WBUSLAT_OFFSET ) & EXMC_SNWTCFG_WBUSLAT ) |
|
||||
exmc_norsram_init_struct->write_timing->asyn_access_mode;
|
||||
}else{
|
||||
snwtcfg = BANK0_SNWTCFG_RESET;
|
||||
}
|
||||
|
||||
/* configure the registers */
|
||||
EXMC_SNCTL(exmc_norsram_init_struct->norsram_region) = snctl;
|
||||
EXMC_SNTCFG(exmc_norsram_init_struct->norsram_region) = sntcfg;
|
||||
EXMC_SNWTCFG(exmc_norsram_init_struct->norsram_region) = snwtcfg;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable EXMC NOR/PSRAM bank region
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_enable(uint32_t exmc_norsram_region)
|
||||
{
|
||||
EXMC_SNCTL(exmc_norsram_region) |= (uint32_t)EXMC_SNCTL_NRBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable EXMC NOR/PSRAM bank region
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM Bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_disable(uint32_t exmc_norsram_region)
|
||||
{
|
||||
EXMC_SNCTL(exmc_norsram_region) &= ~(uint32_t)EXMC_SNCTL_NRBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief configure CRAM page size
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[in] page_size: CRAM page size
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_CRAM_AUTO_SPLIT: the clock is generated only during synchronous access
|
||||
\arg EXMC_CRAM_PAGE_SIZE_128_BYTES: page size is 128 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_256_BYTES: page size is 256 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_512_BYTES: page size is 512 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_1024_BYTES: page size is 1024 bytes
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_page_size_config(uint32_t exmc_norsram_region, uint32_t page_size)
|
||||
{
|
||||
/* reset the bits */
|
||||
EXMC_SNCTL(exmc_norsram_region) &= ~EXMC_SNCTL_CPS;
|
||||
|
||||
/* set the CPS bits */
|
||||
EXMC_SNCTL(exmc_norsram_region) |= page_size;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC NAND bank
|
||||
\param[in] exmc_nand_bank: select the bank of NAND
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1..2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_deinit(uint32_t exmc_nand_bank)
|
||||
{
|
||||
/* EXMC_BANK1_NAND or EXMC_BANK2_NAND */
|
||||
EXMC_NPCTL(exmc_nand_bank) = BANK1_2_NPCTL_RESET;
|
||||
EXMC_NPINTEN(exmc_nand_bank) = BANK1_2_NPINTEN_RESET;
|
||||
EXMC_NPCTCFG(exmc_nand_bank) = BANK1_2_NPCTCFG_RESET;
|
||||
EXMC_NPATCFG(exmc_nand_bank) = BANK1_2_NPATCFG_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_nand_parameter_struct with the default values
|
||||
\param[in] none
|
||||
\param[out] the initialized struct exmc_nand_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_struct_para_init(exmc_nand_parameter_struct* exmc_nand_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_nand_init_struct->nand_bank = EXMC_BANK1_NAND;
|
||||
exmc_nand_init_struct->wait_feature = DISABLE;
|
||||
exmc_nand_init_struct->databus_width = EXMC_NAND_DATABUS_WIDTH_8B;
|
||||
exmc_nand_init_struct->ecc_logic = DISABLE;
|
||||
exmc_nand_init_struct->ecc_size = EXMC_ECC_SIZE_256BYTES;
|
||||
exmc_nand_init_struct->ctr_latency = 0x0U;
|
||||
exmc_nand_init_struct->atr_latency = 0x0U;
|
||||
exmc_nand_init_struct->common_space_timing->setuptime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->waittime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->holdtime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->setuptime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->waittime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->holdtime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->databus_hiztime = 0xFCU;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC NAND bank
|
||||
\param[in] exmc_nand_parameter_struct: configure the EXMC NAND parameter
|
||||
nand_bank: EXMC_BANK1_NAND,EXMC_BANK2_NAND
|
||||
ecc_size: EXMC_ECC_SIZE_xBYTES,x=256,512,1024,2048,4096
|
||||
atr_latency: EXMC_ALE_RE_DELAY_x_HCLK,x=1..16
|
||||
ctr_latency: EXMC_CLE_RE_DELAY_x_HCLK,x=1..16
|
||||
ecc_logic: ENABLE or DISABLE
|
||||
databus_width: EXMC_NAND_DATABUS_WIDTH_8B,EXMC_NAND_DATABUS_WIDTH_16B
|
||||
wait_feature: ENABLE or DISABLE
|
||||
common_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
attribute_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_init(exmc_nand_parameter_struct* exmc_nand_init_struct)
|
||||
{
|
||||
uint32_t npctl = 0x00000000U, npctcfg = 0x00000000U, npatcfg = 0x00000000U;
|
||||
|
||||
npctl = (uint32_t)(exmc_nand_init_struct->wait_feature << NPCTL_NDWTEN_OFFSET)|
|
||||
EXMC_NPCTL_NDTP |
|
||||
exmc_nand_init_struct->databus_width |
|
||||
(exmc_nand_init_struct->ecc_logic << NPCTL_ECCEN_OFFSET)|
|
||||
exmc_nand_init_struct->ecc_size |
|
||||
exmc_nand_init_struct->ctr_latency |
|
||||
exmc_nand_init_struct->atr_latency;
|
||||
|
||||
npctcfg = (uint32_t)((exmc_nand_init_struct->common_space_timing->setuptime - 1U) & EXMC_NPCTCFG_COMSET ) |
|
||||
(((exmc_nand_init_struct->common_space_timing->waittime - 1U) << NPCTCFG_COMWAIT_OFFSET) & EXMC_NPCTCFG_COMWAIT ) |
|
||||
((exmc_nand_init_struct->common_space_timing->holdtime << NPCTCFG_COMHLD_OFFSET) & EXMC_NPCTCFG_COMHLD ) |
|
||||
(((exmc_nand_init_struct->common_space_timing->databus_hiztime - 1U) << NPCTCFG_COMHIZ_OFFSET) & EXMC_NPCTCFG_COMHIZ );
|
||||
|
||||
npatcfg = (uint32_t)((exmc_nand_init_struct->attribute_space_timing->setuptime - 1U) & EXMC_NPATCFG_ATTSET ) |
|
||||
(((exmc_nand_init_struct->attribute_space_timing->waittime - 1U) << NPATCFG_ATTWAIT_OFFSET) & EXMC_NPATCFG_ATTWAIT ) |
|
||||
((exmc_nand_init_struct->attribute_space_timing->holdtime << NPATCFG_ATTHLD_OFFSET) & EXMC_NPATCFG_ATTHLD ) |
|
||||
(((exmc_nand_init_struct->attribute_space_timing->databus_hiztime -1U) << NPATCFG_ATTHIZ_OFFSET) & EXMC_NPATCFG_ATTHIZ );
|
||||
|
||||
/* EXMC_BANK1_NAND or EXMC_BANK2_NAND initialize */
|
||||
EXMC_NPCTL(exmc_nand_init_struct->nand_bank) = npctl;
|
||||
EXMC_NPCTCFG(exmc_nand_init_struct->nand_bank) = npctcfg;
|
||||
EXMC_NPATCFG(exmc_nand_init_struct->nand_bank) = npatcfg;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable NAND bank
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_enable(uint32_t exmc_nand_bank)
|
||||
{
|
||||
EXMC_NPCTL(exmc_nand_bank) |= EXMC_NPCTL_NDBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable NAND bank
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_disable(uint32_t exmc_nand_bank)
|
||||
{
|
||||
EXMC_NPCTL(exmc_nand_bank) &= (~EXMC_NPCTL_NDBKEN);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable or disable the EXMC NAND ECC function
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[in] newvalue: ENABLE or DISABLE
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_ecc_config(uint32_t exmc_nand_bank, ControlStatus newvalue)
|
||||
{
|
||||
if (ENABLE == newvalue){
|
||||
/* enable the selected NAND bank ECC function */
|
||||
EXMC_NPCTL(exmc_nand_bank) |= EXMC_NPCTL_ECCEN;
|
||||
}else{
|
||||
/* disable the selected NAND bank ECC function */
|
||||
EXMC_NPCTL(exmc_nand_bank) &= (~EXMC_NPCTL_ECCEN);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the EXMC ECC value
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval the error correction code(ECC) value
|
||||
*/
|
||||
uint32_t exmc_ecc_get(uint32_t exmc_nand_bank)
|
||||
{
|
||||
return (EXMC_NECC(exmc_nand_bank));
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC PC card bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_deinit(void)
|
||||
{
|
||||
/* EXMC_BANK3_PCCARD */
|
||||
EXMC_NPCTL3 = BANK3_NPCTL_RESET;
|
||||
EXMC_NPINTEN3 = BANK3_NPINTEN_RESET;
|
||||
EXMC_NPCTCFG3 = BANK3_NPCTCFG_RESET;
|
||||
EXMC_NPATCFG3 = BANK3_NPATCFG_RESET;
|
||||
EXMC_PIOTCFG3 = BANK3_PIOTCFG3_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_pccard_parameter_struct parameter with the default values
|
||||
\param[in] none
|
||||
\param[out] the initialized struct exmc_pccard_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_struct_para_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_pccard_init_struct->wait_feature = DISABLE;
|
||||
exmc_pccard_init_struct->ctr_latency = 0x0U;
|
||||
exmc_pccard_init_struct->atr_latency = 0x0U;
|
||||
exmc_pccard_init_struct->common_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->databus_hiztime = 0xFCU;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC PC card bank
|
||||
\param[in] exmc_pccard_parameter_struct: configure the EXMC NAND parameter
|
||||
atr_latency: EXMC_ALE_RE_DELAY_x_HCLK,x=1..16
|
||||
ctr_latency: EXMC_CLE_RE_DELAY_x_HCLK,x=1..16
|
||||
wait_feature: ENABLE or DISABLE
|
||||
common_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
attribute_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
io_space_timing: exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 0,..,255
|
||||
holdtime: 1,..,255
|
||||
waittime: 2,..,256
|
||||
setuptime: 1,..,256
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct)
|
||||
{
|
||||
/* configure the EXMC bank3 PC card control register */
|
||||
EXMC_NPCTL3 = (uint32_t)(exmc_pccard_init_struct->wait_feature << NPCTL_NDWTEN_OFFSET) |
|
||||
EXMC_NAND_DATABUS_WIDTH_16B |
|
||||
exmc_pccard_init_struct->ctr_latency |
|
||||
exmc_pccard_init_struct->atr_latency ;
|
||||
|
||||
/* configure the EXMC bank3 PC card common space timing configuration register */
|
||||
EXMC_NPCTCFG3 = (uint32_t)((exmc_pccard_init_struct->common_space_timing->setuptime - 1U)& EXMC_NPCTCFG_COMSET ) |
|
||||
(((exmc_pccard_init_struct->common_space_timing->waittime - 1U) << NPCTCFG_COMWAIT_OFFSET) & EXMC_NPCTCFG_COMWAIT ) |
|
||||
((exmc_pccard_init_struct->common_space_timing->holdtime << NPCTCFG_COMHLD_OFFSET) & EXMC_NPCTCFG_COMHLD ) |
|
||||
(((exmc_pccard_init_struct->common_space_timing->databus_hiztime - 1U) << NPCTCFG_COMHIZ_OFFSET) & EXMC_NPCTCFG_COMHIZ );
|
||||
|
||||
/* configure the EXMC bank3 PC card attribute space timing configuration register */
|
||||
EXMC_NPATCFG3 = (uint32_t)((exmc_pccard_init_struct->attribute_space_timing->setuptime - 1U) & EXMC_NPATCFG_ATTSET ) |
|
||||
(((exmc_pccard_init_struct->attribute_space_timing->waittime - 1U) << NPATCFG_ATTWAIT_OFFSET) & EXMC_NPATCFG_ATTWAIT ) |
|
||||
((exmc_pccard_init_struct->attribute_space_timing->holdtime << NPATCFG_ATTHLD_OFFSET) & EXMC_NPATCFG_ATTHLD )|
|
||||
(((exmc_pccard_init_struct->attribute_space_timing->databus_hiztime -1U) << NPATCFG_ATTHIZ_OFFSET) & EXMC_NPATCFG_ATTHIZ );
|
||||
|
||||
/* configure the EXMC bank3 PC card io space timing configuration register */
|
||||
EXMC_PIOTCFG3 = (uint32_t)((exmc_pccard_init_struct->io_space_timing->setuptime - 1U) & EXMC_PIOTCFG3_IOSET ) |
|
||||
(((exmc_pccard_init_struct->io_space_timing->waittime - 1U) << PIOTCFG_IOWAIT_OFFSET) & EXMC_PIOTCFG3_IOWAIT ) |
|
||||
((exmc_pccard_init_struct->io_space_timing->holdtime << PIOTCFG_IOHLD_OFFSET) & EXMC_PIOTCFG3_IOHLD )|
|
||||
((exmc_pccard_init_struct->io_space_timing->databus_hiztime << PIOTCFG_IOHIZ_OFFSET) & EXMC_PIOTCFG3_IOHIZ );
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable PC Card Bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_enable(void)
|
||||
{
|
||||
EXMC_NPCTL3 |= EXMC_NPCTL_NDBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable PC Card Bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_disable(void)
|
||||
{
|
||||
EXMC_NPCTL3 &= (~EXMC_NPCTL_NDBKEN);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable EXMC interrupt
|
||||
\param[in] exmc_bank: specifies the NAND bank,PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_enable(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) |= interrupt;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable EXMC interrupt
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_disable(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= (~interrupt);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get EXMC flag status
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC Card bank
|
||||
\param[in] flag: EXMC status and flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_FLAG_RISE: interrupt rising edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_LEVEL: interrupt high-level status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FALL: interrupt falling edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FIFOE: FIFO empty flag
|
||||
\param[out] none
|
||||
\retval FlagStatus: SET or RESET
|
||||
*/
|
||||
FlagStatus exmc_flag_get(uint32_t exmc_bank,uint32_t flag)
|
||||
{
|
||||
uint32_t status = 0x00000000U;
|
||||
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
status = EXMC_NPINTEN(exmc_bank);
|
||||
|
||||
if ((status & flag) != (uint32_t)flag ){
|
||||
/* flag is reset */
|
||||
return RESET;
|
||||
}else{
|
||||
/* flag is set */
|
||||
return SET;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear EXMC flag status
|
||||
\param[in] exmc_bank: specifie the NAND bank , PCCARD bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] flag: EXMC status and flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_FLAG_RISE: interrupt rising edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_LEVEL: interrupt high-level status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FALL: interrupt falling edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FIFOE: FIFO empty flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_flag_clear(uint32_t exmc_bank,uint32_t flag)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= (~flag);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get EXMC interrupt flag
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval FlagStatus: SET or RESET
|
||||
*/
|
||||
FlagStatus exmc_interrupt_flag_get(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
uint32_t status = 0x00000000U,interrupt_enable = 0x00000000U,interrupt_state = 0x00000000U;
|
||||
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
status = EXMC_NPINTEN(exmc_bank);
|
||||
interrupt_state = (status & (interrupt >> INTEN_INTS_OFFSET));
|
||||
|
||||
interrupt_enable = (status & interrupt);
|
||||
|
||||
if ((interrupt_enable) && (interrupt_state)){
|
||||
/* interrupt flag is set */
|
||||
return SET;
|
||||
}else{
|
||||
/* interrupt flag is reset */
|
||||
return RESET;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear EXMC interrupt flag
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_flag_clear(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= ~(interrupt >> INTEN_INTS_OFFSET);
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
/*!
|
||||
\file gd32e50x_exmc.c
|
||||
\brief EXMC driver
|
||||
|
||||
\version 2026-02-09, V1.7.0, firmware for GD32E50x
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2025, GigaDevice Semiconductor Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "gd32e50x_exmc.h"
|
||||
|
||||
/* EXMC bank0 register reset value */
|
||||
#define BANK0_SNCTL_REGION0_RESET ((uint32_t)0x000030DBU)
|
||||
#define BANK0_SNCTL_REGION1_2_3_RESET ((uint32_t)0x000030D2U)
|
||||
#define BANK0_SNTCFG_RESET ((uint32_t)0x0FFFFFFFU)
|
||||
#define BANK0_SNWTCFG_RESET ((uint32_t)0x0FFFFFFFU)
|
||||
|
||||
/* EXMC bank1/2 register reset mask */
|
||||
#define BANK1_2_NPCTL_RESET ((uint32_t)0x00000018U)
|
||||
#define BANK1_2_NPINTEN_RESET ((uint32_t)0x00000042U)
|
||||
#define BANK1_2_NPCTCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK1_2_NPATCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
|
||||
/* EXMC bank3 register reset mask */
|
||||
#define BANK3_NPCTL_RESET ((uint32_t)0x00000018U)
|
||||
#define BANK3_NPINTEN_RESET ((uint32_t)0x00000043U)
|
||||
#define BANK3_NPCTCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK3_NPATCFG_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
#define BANK3_PIOTCFG3_RESET ((uint32_t)0xFCFCFCFCU)
|
||||
|
||||
/* EXMC register bit offset */
|
||||
#define SNCTL_NRMUX_OFFSET ((uint32_t)1U)
|
||||
#define SNCTL_SBRSTEN_OFFSET ((uint32_t)8U)
|
||||
#define SNCTL_WRAPEN_OFFSET ((uint32_t)10U)
|
||||
#define SNCTL_WREN_OFFSET ((uint32_t)12U)
|
||||
#define SNCTL_NRWTEN_OFFSET ((uint32_t)13U)
|
||||
#define SNCTL_EXMODEN_OFFSET ((uint32_t)14U)
|
||||
#define SNCTL_ASYNCWAIT_OFFSET ((uint32_t)15U)
|
||||
|
||||
#define SNTCFG_AHLD_OFFSET ((uint32_t)4U)
|
||||
#define SNTCFG_DSET_OFFSET ((uint32_t)8U)
|
||||
#define SNTCFG_BUSLAT_OFFSET ((uint32_t)16U)
|
||||
|
||||
#define SNWTCFG_WAHLD_OFFSET ((uint32_t)4U)
|
||||
#define SNWTCFG_WDSET_OFFSET ((uint32_t)8U)
|
||||
#define SNWTCFG_WBUSLAT_OFFSET ((uint32_t)16U)
|
||||
|
||||
#define NPCTL_NDWTEN_OFFSET ((uint32_t)1U)
|
||||
#define NPCTL_ECCEN_OFFSET ((uint32_t)6U)
|
||||
|
||||
#define NPCTCFG_COMWAIT_OFFSET ((uint32_t)8U)
|
||||
#define NPCTCFG_COMHLD_OFFSET ((uint32_t)16U)
|
||||
#define NPCTCFG_COMHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define NPATCFG_ATTWAIT_OFFSET ((uint32_t)8U)
|
||||
#define NPATCFG_ATTHLD_OFFSET ((uint32_t)16U)
|
||||
#define NPATCFG_ATTHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define PIOTCFG_IOWAIT_OFFSET ((uint32_t)8U)
|
||||
#define PIOTCFG_IOHLD_OFFSET ((uint32_t)16U)
|
||||
#define PIOTCFG_IOHIZ_OFFSET ((uint32_t)24U)
|
||||
|
||||
#define INTEN_INTS_OFFSET ((uint32_t)3U)
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC NOR/SRAM region
|
||||
\param[in] exmc_norsram_region: select the region of bank0
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_deinit(uint32_t exmc_norsram_region)
|
||||
{
|
||||
/* reset the registers */
|
||||
if(EXMC_BANK0_NORSRAM_REGION0 == exmc_norsram_region){
|
||||
EXMC_SNCTL(exmc_norsram_region) = BANK0_SNCTL_REGION0_RESET;
|
||||
}else{
|
||||
EXMC_SNCTL(exmc_norsram_region) = BANK0_SNCTL_REGION1_2_3_RESET;
|
||||
}
|
||||
EXMC_SNTCFG(exmc_norsram_region) = BANK0_SNTCFG_RESET;
|
||||
EXMC_SNWTCFG(exmc_norsram_region) = BANK0_SNWTCFG_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_norsram_parameter_struct with the default values
|
||||
\param[in] none
|
||||
\param[out] exmc_norsram_init_struct: the initialized struct exmc_norsram_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_struct_para_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_norsram_init_struct->norsram_region = EXMC_BANK0_NORSRAM_REGION0;
|
||||
exmc_norsram_init_struct->address_data_mux = ENABLE;
|
||||
exmc_norsram_init_struct->memory_type = EXMC_MEMORY_TYPE_SRAM;
|
||||
exmc_norsram_init_struct->databus_width = EXMC_NOR_DATABUS_WIDTH_8B;
|
||||
exmc_norsram_init_struct->burst_mode = DISABLE;
|
||||
exmc_norsram_init_struct->nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
|
||||
exmc_norsram_init_struct->wrap_burst_mode = DISABLE;
|
||||
exmc_norsram_init_struct->nwait_config = EXMC_NWAIT_CONFIG_BEFORE;
|
||||
exmc_norsram_init_struct->memory_write = ENABLE;
|
||||
exmc_norsram_init_struct->nwait_signal = ENABLE;
|
||||
exmc_norsram_init_struct->extended_mode = DISABLE;
|
||||
exmc_norsram_init_struct->asyn_wait = DISABLE;
|
||||
exmc_norsram_init_struct->write_mode = EXMC_ASYN_WRITE;
|
||||
|
||||
/* read/write timing configure */
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_address_setuptime = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_address_holdtime = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_data_setuptime = 0xFFU;
|
||||
exmc_norsram_init_struct->read_write_timing->bus_latency = 0xFU;
|
||||
exmc_norsram_init_struct->read_write_timing->syn_clk_division = EXMC_SYN_CLOCK_RATIO_16_CLK;
|
||||
exmc_norsram_init_struct->read_write_timing->syn_data_latency = EXMC_DATALAT_17_CLK;
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
|
||||
/* write timing configure, when extended mode is used */
|
||||
exmc_norsram_init_struct->write_timing->asyn_address_setuptime = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_address_holdtime = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_data_setuptime = 0xFFU;
|
||||
exmc_norsram_init_struct->write_timing->bus_latency = 0xFU;
|
||||
exmc_norsram_init_struct->write_timing->asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC NOR/SRAM region
|
||||
\param[in] exmc_norsram_parameter_struct: configure the EXMC NOR/SRAM parameter
|
||||
norsram_region: EXMC_BANK0_NORSRAM_REGIONx,x=0..3
|
||||
write_mode: EXMC_ASYN_WRITE,EXMC_SYN_WRITE
|
||||
extended_mode: ENABLE or DISABLE
|
||||
asyn_wait: ENABLE or DISABLE
|
||||
nwait_signal: ENABLE or DISABLE
|
||||
memory_write: ENABLE or DISABLE
|
||||
nwait_config: EXMC_NWAIT_CONFIG_BEFORE,EXMC_NWAIT_CONFIG_DURING
|
||||
wrap_burst_mode: ENABLE or DISABLE
|
||||
nwait_polarity: EXMC_NWAIT_POLARITY_LOW,EXMC_NWAIT_POLARITY_HIGH
|
||||
burst_mode: ENABLE or DISABLE
|
||||
databus_width: EXMC_NOR_DATABUS_WIDTH_8B,EXMC_NOR_DATABUS_WIDTH_16B
|
||||
memory_type: EXMC_MEMORY_TYPE_SRAM,EXMC_MEMORY_TYPE_PSRAM,EXMC_MEMORY_TYPE_NOR
|
||||
address_data_mux: ENABLE or DISABLE
|
||||
read_write_timing: struct exmc_norsram_timing_parameter_struct set the time
|
||||
asyn_access_mode: EXMC_ACCESS_MODE_A,EXMC_ACCESS_MODE_B,EXMC_ACCESS_MODE_C,EXMC_ACCESS_MODE_D
|
||||
syn_data_latency: EXMC_DATALAT_n_CLK,(n=2,..,17)
|
||||
syn_clk_division: EXMC_SYN_CLOCK_RATIO_DISABLE, EXMC_SYN_CLOCK_RATIO_n_CLK,(n=2,..,16)
|
||||
bus_latency: 1,..,16
|
||||
asyn_data_setuptime: 2,..,256
|
||||
asyn_address_holdtime: 2,..,16
|
||||
asyn_address_setuptime: 1,..,16
|
||||
write_timing: struct exmc_norsram_timing_parameter_struct set the time
|
||||
asyn_access_mode: EXMC_ACCESS_MODE_A,EXMC_ACCESS_MODE_B,EXMC_ACCESS_MODE_C,EXMC_ACCESS_MODE_D
|
||||
syn_data_latency: EXMC_DATALAT_n_CLK,(n=2,..,17)
|
||||
syn_clk_division: EXMC_SYN_CLOCK_RATIO_DISABLE, EXMC_SYN_CLOCK_RATIO_n_CLK,(n=2,..,16)
|
||||
bus_latency: 1,..,16
|
||||
asyn_data_setuptime: 2,..,256
|
||||
asyn_address_holdtime: 2,..,16
|
||||
asyn_address_setuptime: 1,..,16
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct)
|
||||
{
|
||||
uint32_t snctl = 0x00000000U,sntcfg = 0x00000000U,snwtcfg = 0x00000000U;
|
||||
|
||||
/* get the register value */
|
||||
snctl = EXMC_SNCTL(exmc_norsram_init_struct->norsram_region);
|
||||
|
||||
/* clear relative bits */
|
||||
snctl &= ((uint32_t)~(EXMC_SNCTL_NRMUX | EXMC_SNCTL_NRTP | EXMC_SNCTL_NRW | EXMC_SNCTL_SBRSTEN |
|
||||
EXMC_SNCTL_NREN | EXMC_SNCTL_NRWTPOL | EXMC_SNCTL_WRAPEN | EXMC_SNCTL_NRWTCFG |
|
||||
EXMC_SNCTL_WREN | EXMC_SNCTL_NRWTEN | EXMC_SNCTL_EXMODEN | EXMC_SNCTL_ASYNCWAIT |
|
||||
EXMC_SNCTL_SYNCWR ));
|
||||
|
||||
snctl |= (uint32_t)(exmc_norsram_init_struct->address_data_mux << SNCTL_NRMUX_OFFSET) |
|
||||
exmc_norsram_init_struct->memory_type |
|
||||
exmc_norsram_init_struct->databus_width |
|
||||
(exmc_norsram_init_struct->burst_mode << SNCTL_SBRSTEN_OFFSET) |
|
||||
exmc_norsram_init_struct->nwait_polarity |
|
||||
(exmc_norsram_init_struct->wrap_burst_mode << SNCTL_WRAPEN_OFFSET) |
|
||||
exmc_norsram_init_struct->nwait_config |
|
||||
(exmc_norsram_init_struct->memory_write << SNCTL_WREN_OFFSET) |
|
||||
(exmc_norsram_init_struct->nwait_signal << SNCTL_NRWTEN_OFFSET) |
|
||||
(exmc_norsram_init_struct->extended_mode << SNCTL_EXMODEN_OFFSET) |
|
||||
(exmc_norsram_init_struct->asyn_wait << SNCTL_ASYNCWAIT_OFFSET) |
|
||||
exmc_norsram_init_struct->write_mode;
|
||||
|
||||
sntcfg = (uint32_t)((exmc_norsram_init_struct->read_write_timing->asyn_address_setuptime - 1U ) & EXMC_SNTCFG_ASET )|
|
||||
(((exmc_norsram_init_struct->read_write_timing->asyn_address_holdtime - 1U ) << SNTCFG_AHLD_OFFSET ) & EXMC_SNTCFG_AHLD ) |
|
||||
(((exmc_norsram_init_struct->read_write_timing->asyn_data_setuptime - 1U ) << SNTCFG_DSET_OFFSET ) & EXMC_SNTCFG_DSET ) |
|
||||
(((exmc_norsram_init_struct->read_write_timing->bus_latency - 1U ) << SNTCFG_BUSLAT_OFFSET ) & EXMC_SNTCFG_BUSLAT )|
|
||||
exmc_norsram_init_struct->read_write_timing->syn_clk_division |
|
||||
exmc_norsram_init_struct->read_write_timing->syn_data_latency |
|
||||
exmc_norsram_init_struct->read_write_timing->asyn_access_mode;
|
||||
|
||||
/* nor flash access enable */
|
||||
if(EXMC_MEMORY_TYPE_NOR == exmc_norsram_init_struct->memory_type){
|
||||
snctl |= (uint32_t)EXMC_SNCTL_NREN;
|
||||
}
|
||||
|
||||
/* extended mode configure */
|
||||
if(ENABLE == exmc_norsram_init_struct->extended_mode){
|
||||
snwtcfg = (uint32_t)((exmc_norsram_init_struct->write_timing->asyn_address_setuptime - 1U) & EXMC_SNWTCFG_WASET ) |
|
||||
(((exmc_norsram_init_struct->write_timing->asyn_address_holdtime -1U ) << SNWTCFG_WAHLD_OFFSET ) & EXMC_SNWTCFG_WAHLD )|
|
||||
(((exmc_norsram_init_struct->write_timing->asyn_data_setuptime -1U ) << SNWTCFG_WDSET_OFFSET ) & EXMC_SNWTCFG_WDSET )|
|
||||
(((exmc_norsram_init_struct->write_timing->bus_latency - 1U ) << SNWTCFG_WBUSLAT_OFFSET ) & EXMC_SNWTCFG_WBUSLAT ) |
|
||||
exmc_norsram_init_struct->write_timing->asyn_access_mode;
|
||||
}else{
|
||||
snwtcfg = BANK0_SNWTCFG_RESET;
|
||||
}
|
||||
|
||||
/* configure the registers */
|
||||
EXMC_SNCTL(exmc_norsram_init_struct->norsram_region) = snctl;
|
||||
EXMC_SNTCFG(exmc_norsram_init_struct->norsram_region) = sntcfg;
|
||||
EXMC_SNWTCFG(exmc_norsram_init_struct->norsram_region) = snwtcfg;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable EXMC NOR/PSRAM bank region
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_enable(uint32_t exmc_norsram_region)
|
||||
{
|
||||
EXMC_SNCTL(exmc_norsram_region) |= (uint32_t)EXMC_SNCTL_NRBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable EXMC NOR/PSRAM bank region
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM Bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_disable(uint32_t exmc_norsram_region)
|
||||
{
|
||||
EXMC_SNCTL(exmc_norsram_region) &= ~(uint32_t)EXMC_SNCTL_NRBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief configure CRAM page size
|
||||
\param[in] exmc_norsram_region: specifie the region of NOR/PSRAM bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK0_NORSRAM_REGIONx(x=0..3)
|
||||
\param[in] page_size: CRAM page size
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_CRAM_AUTO_SPLIT: the clock is generated only during synchronous access
|
||||
\arg EXMC_CRAM_PAGE_SIZE_128_BYTES: page size is 128 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_256_BYTES: page size is 256 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_512_BYTES: page size is 512 bytes
|
||||
\arg EXMC_CRAM_PAGE_SIZE_1024_BYTES: page size is 1024 bytes
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_norsram_page_size_config(uint32_t exmc_norsram_region, uint32_t page_size)
|
||||
{
|
||||
/* reset the bits */
|
||||
EXMC_SNCTL(exmc_norsram_region) &= ~EXMC_SNCTL_CPS;
|
||||
|
||||
/* set the CPS bits */
|
||||
EXMC_SNCTL(exmc_norsram_region) |= page_size;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC NAND bank
|
||||
\param[in] exmc_nand_bank: select the bank of NAND
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1..2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_deinit(uint32_t exmc_nand_bank)
|
||||
{
|
||||
/* EXMC_BANK1_NAND or EXMC_BANK2_NAND */
|
||||
EXMC_NPCTL(exmc_nand_bank) = BANK1_2_NPCTL_RESET;
|
||||
EXMC_NPINTEN(exmc_nand_bank) = BANK1_2_NPINTEN_RESET;
|
||||
EXMC_NPCTCFG(exmc_nand_bank) = BANK1_2_NPCTCFG_RESET;
|
||||
EXMC_NPATCFG(exmc_nand_bank) = BANK1_2_NPATCFG_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_nand_parameter_struct with the default values
|
||||
\param[in] none
|
||||
\param[out] the initialized struct exmc_nand_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_struct_para_init(exmc_nand_parameter_struct* exmc_nand_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_nand_init_struct->nand_bank = EXMC_BANK1_NAND;
|
||||
exmc_nand_init_struct->wait_feature = DISABLE;
|
||||
exmc_nand_init_struct->databus_width = EXMC_NAND_DATABUS_WIDTH_8B;
|
||||
exmc_nand_init_struct->ecc_logic = DISABLE;
|
||||
exmc_nand_init_struct->ecc_size = EXMC_ECC_SIZE_256BYTES;
|
||||
exmc_nand_init_struct->ctr_latency = 0x0U;
|
||||
exmc_nand_init_struct->atr_latency = 0x0U;
|
||||
exmc_nand_init_struct->common_space_timing->setuptime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->waittime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->holdtime = 0xFCU;
|
||||
exmc_nand_init_struct->common_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->setuptime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->waittime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->holdtime = 0xFCU;
|
||||
exmc_nand_init_struct->attribute_space_timing->databus_hiztime = 0xFCU;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC NAND bank
|
||||
\param[in] exmc_nand_parameter_struct: configure the EXMC NAND parameter
|
||||
nand_bank: EXMC_BANK1_NAND,EXMC_BANK2_NAND
|
||||
ecc_size: EXMC_ECC_SIZE_xBYTES,x=256,512,1024,2048,4096
|
||||
atr_latency: EXMC_ALE_RE_DELAY_x_HCLK,x=1..16
|
||||
ctr_latency: EXMC_CLE_RE_DELAY_x_HCLK,x=1..16
|
||||
ecc_logic: ENABLE or DISABLE
|
||||
databus_width: EXMC_NAND_DATABUS_WIDTH_8B,EXMC_NAND_DATABUS_WIDTH_16B
|
||||
wait_feature: ENABLE or DISABLE
|
||||
common_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
attribute_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_init(exmc_nand_parameter_struct* exmc_nand_init_struct)
|
||||
{
|
||||
uint32_t npctl = 0x00000000U, npctcfg = 0x00000000U, npatcfg = 0x00000000U;
|
||||
|
||||
npctl = (uint32_t)(exmc_nand_init_struct->wait_feature << NPCTL_NDWTEN_OFFSET)|
|
||||
EXMC_NPCTL_NDTP |
|
||||
exmc_nand_init_struct->databus_width |
|
||||
(exmc_nand_init_struct->ecc_logic << NPCTL_ECCEN_OFFSET)|
|
||||
exmc_nand_init_struct->ecc_size |
|
||||
exmc_nand_init_struct->ctr_latency |
|
||||
exmc_nand_init_struct->atr_latency;
|
||||
|
||||
npctcfg = (uint32_t)((exmc_nand_init_struct->common_space_timing->setuptime - 1U) & EXMC_NPCTCFG_COMSET ) |
|
||||
(((exmc_nand_init_struct->common_space_timing->waittime - 1U) << NPCTCFG_COMWAIT_OFFSET) & EXMC_NPCTCFG_COMWAIT ) |
|
||||
((exmc_nand_init_struct->common_space_timing->holdtime << NPCTCFG_COMHLD_OFFSET) & EXMC_NPCTCFG_COMHLD ) |
|
||||
(((exmc_nand_init_struct->common_space_timing->databus_hiztime - 1U) << NPCTCFG_COMHIZ_OFFSET) & EXMC_NPCTCFG_COMHIZ );
|
||||
|
||||
npatcfg = (uint32_t)((exmc_nand_init_struct->attribute_space_timing->setuptime - 1U) & EXMC_NPATCFG_ATTSET ) |
|
||||
(((exmc_nand_init_struct->attribute_space_timing->waittime - 1U) << NPATCFG_ATTWAIT_OFFSET) & EXMC_NPATCFG_ATTWAIT ) |
|
||||
((exmc_nand_init_struct->attribute_space_timing->holdtime << NPATCFG_ATTHLD_OFFSET) & EXMC_NPATCFG_ATTHLD ) |
|
||||
(((exmc_nand_init_struct->attribute_space_timing->databus_hiztime -1U) << NPATCFG_ATTHIZ_OFFSET) & EXMC_NPATCFG_ATTHIZ );
|
||||
|
||||
/* EXMC_BANK1_NAND or EXMC_BANK2_NAND initialize */
|
||||
EXMC_NPCTL(exmc_nand_init_struct->nand_bank) = npctl;
|
||||
EXMC_NPCTCFG(exmc_nand_init_struct->nand_bank) = npctcfg;
|
||||
EXMC_NPATCFG(exmc_nand_init_struct->nand_bank) = npatcfg;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable NAND bank
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_enable(uint32_t exmc_nand_bank)
|
||||
{
|
||||
EXMC_NPCTL(exmc_nand_bank) |= EXMC_NPCTL_NDBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable NAND bank
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_disable(uint32_t exmc_nand_bank)
|
||||
{
|
||||
EXMC_NPCTL(exmc_nand_bank) &= (~EXMC_NPCTL_NDBKEN);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable or disable the EXMC NAND ECC function
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[in] newvalue: ENABLE or DISABLE
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_nand_ecc_config(uint32_t exmc_nand_bank, ControlStatus newvalue)
|
||||
{
|
||||
if (ENABLE == newvalue){
|
||||
/* enable the selected NAND bank ECC function */
|
||||
EXMC_NPCTL(exmc_nand_bank) |= EXMC_NPCTL_ECCEN;
|
||||
}else{
|
||||
/* disable the selected NAND bank ECC function */
|
||||
EXMC_NPCTL(exmc_nand_bank) &= (~EXMC_NPCTL_ECCEN);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get the EXMC ECC value
|
||||
\param[in] exmc_nand_bank: specifie the NAND bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANKx_NAND(x=1,2)
|
||||
\param[out] none
|
||||
\retval the error correction code(ECC) value
|
||||
*/
|
||||
uint32_t exmc_ecc_get(uint32_t exmc_nand_bank)
|
||||
{
|
||||
return (EXMC_NECC(exmc_nand_bank));
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief deinitialize EXMC PC card bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_deinit(void)
|
||||
{
|
||||
/* EXMC_BANK3_PCCARD */
|
||||
EXMC_NPCTL3 = BANK3_NPCTL_RESET;
|
||||
EXMC_NPINTEN3 = BANK3_NPINTEN_RESET;
|
||||
EXMC_NPCTCFG3 = BANK3_NPCTCFG_RESET;
|
||||
EXMC_NPATCFG3 = BANK3_NPATCFG_RESET;
|
||||
EXMC_PIOTCFG3 = BANK3_PIOTCFG3_RESET;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize exmc_pccard_parameter_struct parameter with the default values
|
||||
\param[in] none
|
||||
\param[out] the initialized struct exmc_pccard_parameter_struct pointer
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_struct_para_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct)
|
||||
{
|
||||
/* configure the structure with default values */
|
||||
exmc_pccard_init_struct->wait_feature = DISABLE;
|
||||
exmc_pccard_init_struct->ctr_latency = 0x0U;
|
||||
exmc_pccard_init_struct->atr_latency = 0x0U;
|
||||
exmc_pccard_init_struct->common_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->common_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->attribute_space_timing->databus_hiztime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->setuptime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->waittime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->holdtime = 0xFCU;
|
||||
exmc_pccard_init_struct->io_space_timing->databus_hiztime = 0xFCU;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief initialize EXMC PC card bank
|
||||
\param[in] exmc_pccard_parameter_struct: configure the EXMC NAND parameter
|
||||
atr_latency: EXMC_ALE_RE_DELAY_x_HCLK,x=1..16
|
||||
ctr_latency: EXMC_CLE_RE_DELAY_x_HCLK,x=1..16
|
||||
wait_feature: ENABLE or DISABLE
|
||||
common_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
attribute_space_timing: struct exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 1,..,255
|
||||
holdtime: 1,..,254
|
||||
waittime: 2,..,255
|
||||
setuptime: 1,..,255
|
||||
io_space_timing: exmc_nand_pccard_timing_parameter_struct set the time
|
||||
databus_hiztime: 0,..,255
|
||||
holdtime: 1,..,255
|
||||
waittime: 2,..,256
|
||||
setuptime: 1,..,256
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct)
|
||||
{
|
||||
/* configure the EXMC bank3 PC card control register */
|
||||
EXMC_NPCTL3 = (uint32_t)(exmc_pccard_init_struct->wait_feature << NPCTL_NDWTEN_OFFSET) |
|
||||
EXMC_NAND_DATABUS_WIDTH_16B |
|
||||
exmc_pccard_init_struct->ctr_latency |
|
||||
exmc_pccard_init_struct->atr_latency ;
|
||||
|
||||
/* configure the EXMC bank3 PC card common space timing configuration register */
|
||||
EXMC_NPCTCFG3 = (uint32_t)((exmc_pccard_init_struct->common_space_timing->setuptime - 1U)& EXMC_NPCTCFG_COMSET ) |
|
||||
(((exmc_pccard_init_struct->common_space_timing->waittime - 1U) << NPCTCFG_COMWAIT_OFFSET) & EXMC_NPCTCFG_COMWAIT ) |
|
||||
((exmc_pccard_init_struct->common_space_timing->holdtime << NPCTCFG_COMHLD_OFFSET) & EXMC_NPCTCFG_COMHLD ) |
|
||||
(((exmc_pccard_init_struct->common_space_timing->databus_hiztime - 1U) << NPCTCFG_COMHIZ_OFFSET) & EXMC_NPCTCFG_COMHIZ );
|
||||
|
||||
/* configure the EXMC bank3 PC card attribute space timing configuration register */
|
||||
EXMC_NPATCFG3 = (uint32_t)((exmc_pccard_init_struct->attribute_space_timing->setuptime - 1U) & EXMC_NPATCFG_ATTSET ) |
|
||||
(((exmc_pccard_init_struct->attribute_space_timing->waittime - 1U) << NPATCFG_ATTWAIT_OFFSET) & EXMC_NPATCFG_ATTWAIT ) |
|
||||
((exmc_pccard_init_struct->attribute_space_timing->holdtime << NPATCFG_ATTHLD_OFFSET) & EXMC_NPATCFG_ATTHLD )|
|
||||
(((exmc_pccard_init_struct->attribute_space_timing->databus_hiztime -1U) << NPATCFG_ATTHIZ_OFFSET) & EXMC_NPATCFG_ATTHIZ );
|
||||
|
||||
/* configure the EXMC bank3 PC card io space timing configuration register */
|
||||
EXMC_PIOTCFG3 = (uint32_t)((exmc_pccard_init_struct->io_space_timing->setuptime - 1U) & EXMC_PIOTCFG3_IOSET ) |
|
||||
(((exmc_pccard_init_struct->io_space_timing->waittime - 1U) << PIOTCFG_IOWAIT_OFFSET) & EXMC_PIOTCFG3_IOWAIT ) |
|
||||
((exmc_pccard_init_struct->io_space_timing->holdtime << PIOTCFG_IOHLD_OFFSET) & EXMC_PIOTCFG3_IOHLD )|
|
||||
((exmc_pccard_init_struct->io_space_timing->databus_hiztime << PIOTCFG_IOHIZ_OFFSET) & EXMC_PIOTCFG3_IOHIZ );
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable PC Card Bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_enable(void)
|
||||
{
|
||||
EXMC_NPCTL3 |= EXMC_NPCTL_NDBKEN;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable PC Card Bank
|
||||
\param[in] none
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_pccard_disable(void)
|
||||
{
|
||||
EXMC_NPCTL3 &= (~EXMC_NPCTL_NDBKEN);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief enable EXMC interrupt
|
||||
\param[in] exmc_bank: specifies the NAND bank,PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_enable(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) |= interrupt;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief disable EXMC interrupt
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_disable(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= (~interrupt);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get EXMC flag status
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC Card bank
|
||||
\param[in] flag: EXMC status and flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_FLAG_RISE: interrupt rising edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_LEVEL: interrupt high-level status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FALL: interrupt falling edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FIFOE: FIFO empty flag
|
||||
\param[out] none
|
||||
\retval FlagStatus: SET or RESET
|
||||
*/
|
||||
FlagStatus exmc_flag_get(uint32_t exmc_bank,uint32_t flag)
|
||||
{
|
||||
uint32_t status = 0x00000000U;
|
||||
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
status = EXMC_NPINTEN(exmc_bank);
|
||||
|
||||
if ((status & flag) != (uint32_t)flag ){
|
||||
/* flag is reset */
|
||||
return RESET;
|
||||
}else{
|
||||
/* flag is set */
|
||||
return SET;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear EXMC flag status
|
||||
\param[in] exmc_bank: specifie the NAND bank , PCCARD bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] flag: EXMC status and flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_FLAG_RISE: interrupt rising edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_LEVEL: interrupt high-level status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FALL: interrupt falling edge status
|
||||
\arg EXMC_NAND_PCCARD_FLAG_FIFOE: FIFO empty flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_flag_clear(uint32_t exmc_bank,uint32_t flag)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= (~flag);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief get EXMC interrupt flag
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval FlagStatus: SET or RESET
|
||||
*/
|
||||
FlagStatus exmc_interrupt_flag_get(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
uint32_t status = 0x00000000U,interrupt_enable = 0x00000000U,interrupt_state = 0x00000000U;
|
||||
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
status = EXMC_NPINTEN(exmc_bank);
|
||||
interrupt_state = (status & (interrupt >> INTEN_INTS_OFFSET));
|
||||
|
||||
interrupt_enable = (status & interrupt);
|
||||
|
||||
if ((interrupt_enable) && (interrupt_state)){
|
||||
/* interrupt flag is set */
|
||||
return SET;
|
||||
}else{
|
||||
/* interrupt flag is reset */
|
||||
return RESET;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief clear EXMC interrupt flag
|
||||
\param[in] exmc_bank: specifies the NAND bank , PC card bank
|
||||
only one parameter can be selected which is shown as below:
|
||||
\arg EXMC_BANK1_NAND: the NAND bank1
|
||||
\arg EXMC_BANK2_NAND: the NAND bank2
|
||||
\arg EXMC_BANK3_PCCARD: the PC card bank
|
||||
\param[in] interrupt: EXMC interrupt flag
|
||||
only one parameter can be selected which are shown as below:
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_RISE: rising edge interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_LEVEL: high-level interrupt and corresponding flag
|
||||
\arg EXMC_NAND_PCCARD_INT_FLAG_FALL: falling edge interrupt and corresponding flag
|
||||
\param[out] none
|
||||
\retval none
|
||||
*/
|
||||
void exmc_interrupt_flag_clear(uint32_t exmc_bank,uint32_t interrupt)
|
||||
{
|
||||
/* NAND bank1,bank2 or PC card bank3 */
|
||||
EXMC_NPINTEN(exmc_bank) &= ~(interrupt >> INTEN_INTS_OFFSET);
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/*!
|
||||
\file gd32e50x_exmc.h
|
||||
\brief definitions for the EXMC
|
||||
|
||||
\version 2026-02-09, V1.7.0, firmware for GD32E50x
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2025, GigaDevice Semiconductor Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef GD32E50X_EXMC_H
|
||||
#define GD32E50X_EXMC_H
|
||||
|
||||
#include "gd32e50x.h"
|
||||
|
||||
/* EXMC definitions */
|
||||
#define EXMC (EXMC_BASE) /*!< EXMC register base address */
|
||||
|
||||
/* registers definitions */
|
||||
/* NOR/PSRAM */
|
||||
#define EXMC_SNCTL0 REG32(EXMC + 0x00U) /*!< EXMC SRAM/NOR flash control register0 */
|
||||
#define EXMC_SNTCFG0 REG32(EXMC + 0x04U) /*!< EXMC SRAM/NOR flash timing configuration register0 */
|
||||
#define EXMC_SNWTCFG0 REG32(EXMC + 0x104U) /*!< EXMC SRAM/NOR flash write timing configuration register0 */
|
||||
|
||||
#define EXMC_SNCTL1 REG32(EXMC + 0x08U) /*!< EXMC SRAM/NOR flash control register1 */
|
||||
#define EXMC_SNTCFG1 REG32(EXMC + 0x0CU) /*!< EXMC SRAM/NOR flash timing configuration register1 */
|
||||
#define EXMC_SNWTCFG1 REG32(EXMC + 0x10CU) /*!< EXMC SRAM/NOR flash write timing configuration register1 */
|
||||
|
||||
#define EXMC_SNCTL2 REG32(EXMC + 0x10U) /*!< EXMC SRAM/NOR flash control register2 */
|
||||
#define EXMC_SNTCFG2 REG32(EXMC + 0x14U) /*!< EXMC SRAM/NOR flash timing configuration register2 */
|
||||
#define EXMC_SNWTCFG2 REG32(EXMC + 0x114U) /*!< EXMC SRAM/NOR flash write timing configuration register2 */
|
||||
|
||||
#define EXMC_SNCTL3 REG32(EXMC + 0x18U) /*!< EXMC SRAM/NOR flash control register3 */
|
||||
#define EXMC_SNTCFG3 REG32(EXMC + 0x1CU) /*!< EXMC SRAM/NOR flash timing configuration register3 */
|
||||
#define EXMC_SNWTCFG3 REG32(EXMC + 0x11CU) /*!< EXMC SRAM/NOR flash write timing configuration register3 */
|
||||
|
||||
/* NAND/PC card */
|
||||
#define EXMC_NPCTL1 REG32(EXMC + 0x60U) /*!< EXMC NAND/PC card control register1 */
|
||||
#define EXMC_NPINTEN1 REG32(EXMC + 0x64U) /*!< EXMC NAND/PC card interrupt enable register1 */
|
||||
#define EXMC_NPCTCFG1 REG32(EXMC + 0x68U) /*!< EXMC NAND/PC card common space timing configuration register1 */
|
||||
#define EXMC_NPATCFG1 REG32(EXMC + 0x6CU) /*!< EXMC NAND/PC card attribute space timing configuration register1 */
|
||||
#define EXMC_NECC1 REG32(EXMC + 0x74U) /*!< EXMC NAND ECC register1 */
|
||||
|
||||
#define EXMC_NPCTL2 REG32(EXMC + 0x80U) /*!< EXMC NAND/PC card control register2 */
|
||||
#define EXMC_NPINTEN2 REG32(EXMC + 0x84U) /*!< EXMC NAND/PC card interrupt enable register2 */
|
||||
#define EXMC_NPCTCFG2 REG32(EXMC + 0x88U) /*!< EXMC NAND/PC card common space timing configuration register2 */
|
||||
#define EXMC_NPATCFG2 REG32(EXMC + 0x8CU) /*!< EXMC NAND/PC card attribute space timing configuration register2 */
|
||||
#define EXMC_NECC2 REG32(EXMC + 0x94U) /*!< EXMC NAND ECC register2 */
|
||||
|
||||
#define EXMC_NPCTL3 REG32(EXMC + 0xA0U) /*!< EXMC NAND/PC card control register3 */
|
||||
#define EXMC_NPINTEN3 REG32(EXMC + 0xA4U) /*!< EXMC NAND/PC card interrupt enable register3 */
|
||||
#define EXMC_NPCTCFG3 REG32(EXMC + 0xA8U) /*!< EXMC NAND/PC card common space timing configuration register3 */
|
||||
#define EXMC_NPATCFG3 REG32(EXMC + 0xACU) /*!< EXMC NAND/PC card attribute space timing configuration register3 */
|
||||
#define EXMC_PIOTCFG3 REG32(EXMC + 0xB0U) /*!< EXMC PC card I/O space timing configuration register */
|
||||
|
||||
/* bits definitions */
|
||||
/* EXMC_SNCTLx,x=0..3 */
|
||||
#define EXMC_SNCTL_NRBKEN BIT(0) /*!< NOR bank enable */
|
||||
#define EXMC_SNCTL_NRMUX BIT(1) /*!< NOR bank memory address/data multiplexing enable */
|
||||
#define EXMC_SNCTL_NRTP BITS(2,3) /*!< NOR bank memory type */
|
||||
#define EXMC_SNCTL_NRW BITS(4,5) /*!< NOR bank memory data bus width */
|
||||
#define EXMC_SNCTL_NREN BIT(6) /*!< NOR flash access enable */
|
||||
#define EXMC_SNCTL_SBRSTEN BIT(8) /*!< synchronous burst enable */
|
||||
#define EXMC_SNCTL_NRWTPOL BIT(9) /*!< NWAIT signal polarity */
|
||||
#define EXMC_SNCTL_WRAPEN BIT(10) /*!< wrapped burst mode enable */
|
||||
#define EXMC_SNCTL_NRWTCFG BIT(11) /*!< NWAIT signal configuration, only work in synchronous mode */
|
||||
#define EXMC_SNCTL_WREN BIT(12) /*!< write enable */
|
||||
#define EXMC_SNCTL_NRWTEN BIT(13) /*!< NWAIT signal enable */
|
||||
#define EXMC_SNCTL_EXMODEN BIT(14) /*!< extended mode enable */
|
||||
#define EXMC_SNCTL_ASYNCWAIT BIT(15) /*!< asynchronous wait enable */
|
||||
#define EXMC_SNCTL_CPS BITS(16,18) /*!< CRAM page size */
|
||||
#define EXMC_SNCTL_SYNCWR BIT(19) /*!< synchronous write config */
|
||||
|
||||
/* EXMC_SNTCFGx,x=0..3 */
|
||||
#define EXMC_SNTCFG_ASET BITS(0,3) /*!< asynchronous address setup time */
|
||||
#define EXMC_SNTCFG_AHLD BITS(4,7) /*!< asynchronous address hold time */
|
||||
#define EXMC_SNTCFG_DSET BITS(8,15) /*!< asynchronous data setup time */
|
||||
#define EXMC_SNTCFG_BUSLAT BITS(16,19) /*!< bus latency */
|
||||
#define EXMC_SNTCFG_CKDIV BITS(20,23) /*!< synchronous clock divide ratio */
|
||||
#define EXMC_SNTCFG_DLAT BITS(24,27) /*!< synchronous data latency for NOR flash */
|
||||
#define EXMC_SNTCFG_ASYNCMOD BITS(28,29) /*!< asynchronous access mode */
|
||||
|
||||
/* EXMC_SNWTCFGx,x=0..3 */
|
||||
#define EXMC_SNWTCFG_WASET BITS(0,3) /*!< asynchronous address setup time */
|
||||
#define EXMC_SNWTCFG_WAHLD BITS(4,7) /*!< asynchronous address hold time */
|
||||
#define EXMC_SNWTCFG_WDSET BITS(8,15) /*!< asynchronous data setup time */
|
||||
#define EXMC_SNWTCFG_WBUSLAT BITS(16,19) /*!< bus latency */
|
||||
#define EXMC_SNWTCFG_WASYNCMOD BITS(28,29) /*!< asynchronous access mode */
|
||||
|
||||
/* EXMC_NPCTLx,x=1..3 */
|
||||
#define EXMC_NPCTL_NDWTEN BIT(1) /*!< wait feature enable */
|
||||
#define EXMC_NPCTL_NDBKEN BIT(2) /*!< NAND bank enable */
|
||||
#define EXMC_NPCTL_NDTP BIT(3) /*!< NAND bank memory type */
|
||||
#define EXMC_NPCTL_NDW BITS(4,5) /*!< NAND bank memory data bus width */
|
||||
#define EXMC_NPCTL_ECCEN BIT(6) /*!< ECC enable */
|
||||
#define EXMC_NPCTL_CTR BITS(9,12) /*!< CLE to RE delay */
|
||||
#define EXMC_NPCTL_ATR BITS(13,16) /*!< ALE to RE delay */
|
||||
#define EXMC_NPCTL_ECCSZ BITS(17,19) /*!< ECC size */
|
||||
|
||||
/* EXMC_NPINTENx,x=1..3 */
|
||||
#define EXMC_NPINTEN_INTRS BIT(0) /*!< interrupt rising edge status */
|
||||
#define EXMC_NPINTEN_INTHS BIT(1) /*!< interrupt high-level status */
|
||||
#define EXMC_NPINTEN_INTFS BIT(2) /*!< interrupt falling edge status */
|
||||
#define EXMC_NPINTEN_INTREN BIT(3) /*!< interrupt rising edge detection enable */
|
||||
#define EXMC_NPINTEN_INTHEN BIT(4) /*!< interrupt high-level detection enable */
|
||||
#define EXMC_NPINTEN_INTFEN BIT(5) /*!< interrupt falling edge detection enable */
|
||||
#define EXMC_NPINTEN_FFEPT BIT(6) /*!< FIFO empty flag */
|
||||
|
||||
/* EXMC_NPCTCFGx,x=1..3 */
|
||||
#define EXMC_NPCTCFG_COMSET BITS(0,7) /*!< common memory setup time */
|
||||
#define EXMC_NPCTCFG_COMWAIT BITS(8,15) /*!< common memory wait time */
|
||||
#define EXMC_NPCTCFG_COMHLD BITS(16,23) /*!< common memory hold time */
|
||||
#define EXMC_NPCTCFG_COMHIZ BITS(24,31) /*!< common memory data bus HiZ time */
|
||||
|
||||
/* EXMC_NPATCFGx,x=1..3 */
|
||||
#define EXMC_NPATCFG_ATTSET BITS(0,7) /*!< attribute memory setup time */
|
||||
#define EXMC_NPATCFG_ATTWAIT BITS(8,15) /*!< attribute memory wait time */
|
||||
#define EXMC_NPATCFG_ATTHLD BITS(16,23) /*!< attribute memory hold time */
|
||||
#define EXMC_NPATCFG_ATTHIZ BITS(24,31) /*!< attribute memory data bus HiZ time */
|
||||
|
||||
/* EXMC_PIOTCFG3 */
|
||||
#define EXMC_PIOTCFG3_IOSET BITS(0,7) /*!< IO space setup time */
|
||||
#define EXMC_PIOTCFG3_IOWAIT BITS(8,15) /*!< IO space wait time */
|
||||
#define EXMC_PIOTCFG3_IOHLD BITS(16,23) /*!< IO space hold time */
|
||||
#define EXMC_PIOTCFG3_IOHIZ BITS(24,31) /*!< IO space data bus HiZ time */
|
||||
|
||||
/* EXMC_NECCx,x=1,2 */
|
||||
#define EXMC_NECC_ECC BITS(0,31) /*!< ECC result */
|
||||
|
||||
/* constants definitions */
|
||||
/* EXMC NOR/SRAM timing initialize struct */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t asyn_access_mode; /*!< asynchronous access mode */
|
||||
uint32_t syn_data_latency; /*!< configure the data latency, synchronous access mode valid */
|
||||
uint32_t syn_clk_division; /*!< configure the clock divide ratio, synchronous access mode valid */
|
||||
uint32_t bus_latency; /*!< configure the bus latency */
|
||||
uint32_t asyn_data_setuptime; /*!< configure the data setup time, asynchronous access mode valid */
|
||||
uint32_t asyn_address_holdtime; /*!< configure the address hold time, asynchronous access mode valid */
|
||||
uint32_t asyn_address_setuptime; /*!< configure the data setup time, asynchronous access mode valid */
|
||||
}exmc_norsram_timing_parameter_struct;
|
||||
|
||||
/* EXMC NOR/SRAM initialize struct */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t norsram_region; /*!< select the region of EXMC NOR/SRAM bank */
|
||||
uint32_t write_mode; /*!< the write mode, synchronous mode or asynchronous mode */
|
||||
uint32_t extended_mode; /*!< enable or disable the extended mode */
|
||||
uint32_t asyn_wait; /*!< enable or disable the asynchronous wait function */
|
||||
uint32_t nwait_signal; /*!< enable or disable the NWAIT signal while in synchronous bust mode */
|
||||
uint32_t memory_write; /*!< enable or disable the write operation */
|
||||
uint32_t nwait_config; /*!< NWAIT signal configuration, only work in synchronous mode */
|
||||
uint32_t wrap_burst_mode; /*!< enable or disable the wrap burst mode */
|
||||
uint32_t nwait_polarity; /*!< specifies the polarity of NWAIT signal from memory */
|
||||
uint32_t burst_mode; /*!< enable or disable the burst mode */
|
||||
uint32_t databus_width; /*!< specifies the databus width of external memory */
|
||||
uint32_t memory_type; /*!< specifies the type of external memory */
|
||||
uint32_t address_data_mux; /*!< specifies whether the data bus and address bus are multiplexed */
|
||||
exmc_norsram_timing_parameter_struct* read_write_timing; /*!< timing parameters for read and write if the extended mode is not used or the timing
|
||||
parameters for read if the extended mode is used */
|
||||
exmc_norsram_timing_parameter_struct* write_timing; /*!< timing parameters for write when the extended mode is used */
|
||||
}exmc_norsram_parameter_struct;
|
||||
|
||||
/* EXMC NAND/PC card timing initialize struct */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t databus_hiztime; /*!< configure the dadtabus HiZ time for write operation */
|
||||
uint32_t holdtime; /*!< configure the address hold time(or the data hold time for write operation) */
|
||||
uint32_t waittime; /*!< configure the minimum wait time */
|
||||
uint32_t setuptime; /*!< configure the address setup time */
|
||||
}exmc_nand_pccard_timing_parameter_struct;
|
||||
|
||||
/* EXMC NAND initialize struct */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t nand_bank; /*!< select the bank of NAND */
|
||||
uint32_t ecc_size; /*!< the page size for the ECC calculation */
|
||||
uint32_t atr_latency; /*!< configure the latency of ALE low to RB low */
|
||||
uint32_t ctr_latency; /*!< configure the latency of CLE low to RB low */
|
||||
uint32_t ecc_logic; /*!< enable or disable the ECC calculation logic */
|
||||
uint32_t databus_width; /*!< the NAND flash databus width */
|
||||
uint32_t wait_feature; /*!< enable or disable the wait feature */
|
||||
exmc_nand_pccard_timing_parameter_struct* common_space_timing; /*!< the timing parameters for NAND flash common space */
|
||||
exmc_nand_pccard_timing_parameter_struct* attribute_space_timing; /*!< the timing parameters for NAND flash attribute space */
|
||||
}exmc_nand_parameter_struct;
|
||||
|
||||
/* EXMC PC card initialize struct */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t atr_latency; /*!< configure the latency of ALE low to RB low */
|
||||
uint32_t ctr_latency; /*!< configure the latency of CLE low to RB low */
|
||||
uint32_t wait_feature; /*!< enable or disable the wait feature */
|
||||
exmc_nand_pccard_timing_parameter_struct* common_space_timing; /*!< the timing parameters for PC card common space */
|
||||
exmc_nand_pccard_timing_parameter_struct* attribute_space_timing; /*!< the timing parameters for PC card attribute space */
|
||||
exmc_nand_pccard_timing_parameter_struct* io_space_timing; /*!< the timing parameters for PC card IO space */
|
||||
}exmc_pccard_parameter_struct;
|
||||
|
||||
/* EXMC_register address */
|
||||
#define EXMC_SNCTL(region) REG32(EXMC + 0x08U * (region)) /*!< EXMC SRAM/NOR flash control registers, region = 0,1,2,3 */
|
||||
#define EXMC_SNTCFG(region) REG32(EXMC + 0x04U + 0x08U * (region)) /*!< EXMC SRAM/NOR flash timing configuration registers, region = 0,1,2,3 */
|
||||
#define EXMC_SNWTCFG(region) REG32(EXMC + 0x104U + 0x08U * (region)) /*!< EXMC SRAM/NOR flash write timing configuration registers, region = 0,1,2,3 */
|
||||
|
||||
#define EXMC_NPCTL(bank) REG32(EXMC + 0x40U + 0x20U * (bank)) /*!< EXMC NAND/PC card control registers, bank = 1,2,3 */
|
||||
#define EXMC_NPINTEN(bank) REG32(EXMC + 0x44U + 0x20U * (bank)) /*!< EXMC NAND/PC card interrupt enable registers, bank = 1,2,3 */
|
||||
#define EXMC_NPCTCFG(bank) REG32(EXMC + 0x48U + 0x20U * (bank)) /*!< EXMC NAND/PC card common space timing configuration registers, bank = 1,2,3 */
|
||||
#define EXMC_NPATCFG(bank) REG32(EXMC + 0x4CU + 0x20U * (bank)) /*!< EXMC NAND/PC card attribute space timing configuration registers, bank = 1,2,3 */
|
||||
#define EXMC_NECC(bank) REG32(EXMC + 0x54U + 0x20U * (bank)) /*!< EXMC NAND ECC registers, bank = 1,2 */
|
||||
|
||||
/* CRAM page size */
|
||||
#define SNCTL_CPS(regval) (BITS(16,18) & ((uint32_t)(regval) << 16))
|
||||
#define EXMC_CRAM_AUTO_SPLIT SNCTL_CPS(0) /*!< automatic burst split on page boundary crossing */
|
||||
#define EXMC_CRAM_PAGE_SIZE_128_BYTES SNCTL_CPS(1) /*!< page size is 128 bytes */
|
||||
#define EXMC_CRAM_PAGE_SIZE_256_BYTES SNCTL_CPS(2) /*!< page size is 256 bytes */
|
||||
#define EXMC_CRAM_PAGE_SIZE_512_BYTES SNCTL_CPS(3) /*!< page size is 512 bytes */
|
||||
#define EXMC_CRAM_PAGE_SIZE_1024_BYTES SNCTL_CPS(4) /*!< page size is 1024 bytes */
|
||||
|
||||
/* NOR bank memory data bus width */
|
||||
#define SNCTL_NRW(regval) (BITS(4,5) & ((uint32_t)(regval) << 4))
|
||||
#define EXMC_NOR_DATABUS_WIDTH_8B SNCTL_NRW(0) /*!< NOR data width is 8 bits */
|
||||
#define EXMC_NOR_DATABUS_WIDTH_16B SNCTL_NRW(1) /*!< NOR data width is 16 bits */
|
||||
|
||||
/* NOR bank memory type */
|
||||
#define SNCTL_NRTP(regval) (BITS(2,3) & ((uint32_t)(regval) << 2))
|
||||
#define EXMC_MEMORY_TYPE_SRAM SNCTL_NRTP(0) /*!< SRAM,ROM */
|
||||
#define EXMC_MEMORY_TYPE_PSRAM SNCTL_NRTP(1) /*!< PSRAM,CRAM */
|
||||
#define EXMC_MEMORY_TYPE_NOR SNCTL_NRTP(2) /*!< NOR flash */
|
||||
|
||||
/* asynchronous access mode */
|
||||
#define SNTCFG_ASYNCMOD(regval) (BITS(28,29) & ((uint32_t)(regval) << 28))
|
||||
#define EXMC_ACCESS_MODE_A SNTCFG_ASYNCMOD(0) /*!< mode A access */
|
||||
#define EXMC_ACCESS_MODE_B SNTCFG_ASYNCMOD(1) /*!< mode B access */
|
||||
#define EXMC_ACCESS_MODE_C SNTCFG_ASYNCMOD(2) /*!< mode C access */
|
||||
#define EXMC_ACCESS_MODE_D SNTCFG_ASYNCMOD(3) /*!< mode D access */
|
||||
|
||||
/* data latency for NOR flash */
|
||||
#define SNTCFG_DLAT(regval) (BITS(24,27) & ((uint32_t)(regval) << 24))
|
||||
#define EXMC_DATALAT_2_CLK SNTCFG_DLAT(0) /*!< data latency of first burst access is 2 EXMC_CLK */
|
||||
#define EXMC_DATALAT_3_CLK SNTCFG_DLAT(1) /*!< data latency of first burst access is 3 EXMC_CLK */
|
||||
#define EXMC_DATALAT_4_CLK SNTCFG_DLAT(2) /*!< data latency of first burst access is 4 EXMC_CLK */
|
||||
#define EXMC_DATALAT_5_CLK SNTCFG_DLAT(3) /*!< data latency of first burst access is 5 EXMC_CLK */
|
||||
#define EXMC_DATALAT_6_CLK SNTCFG_DLAT(4) /*!< data latency of first burst access is 6 EXMC_CLK */
|
||||
#define EXMC_DATALAT_7_CLK SNTCFG_DLAT(5) /*!< data latency of first burst access is 7 EXMC_CLK */
|
||||
#define EXMC_DATALAT_8_CLK SNTCFG_DLAT(6) /*!< data latency of first burst access is 8 EXMC_CLK */
|
||||
#define EXMC_DATALAT_9_CLK SNTCFG_DLAT(7) /*!< data latency of first burst access is 9 EXMC_CLK */
|
||||
#define EXMC_DATALAT_10_CLK SNTCFG_DLAT(8) /*!< data latency of first burst access is 10 EXMC_CLK */
|
||||
#define EXMC_DATALAT_11_CLK SNTCFG_DLAT(9) /*!< data latency of first burst access is 11 EXMC_CLK */
|
||||
#define EXMC_DATALAT_12_CLK SNTCFG_DLAT(10) /*!< data latency of first burst access is 12 EXMC_CLK */
|
||||
#define EXMC_DATALAT_13_CLK SNTCFG_DLAT(11) /*!< data latency of first burst access is 13 EXMC_CLK */
|
||||
#define EXMC_DATALAT_14_CLK SNTCFG_DLAT(12) /*!< data latency of first burst access is 14 EXMC_CLK */
|
||||
#define EXMC_DATALAT_15_CLK SNTCFG_DLAT(13) /*!< data latency of first burst access is 15 EXMC_CLK */
|
||||
#define EXMC_DATALAT_16_CLK SNTCFG_DLAT(14) /*!< data latency of first burst access is 16 EXMC_CLK */
|
||||
#define EXMC_DATALAT_17_CLK SNTCFG_DLAT(15) /*!< data latency of first burst access is 17 EXMC_CLK */
|
||||
|
||||
/* synchronous clock divide ratio */
|
||||
#define SNTCFG_CKDIV(regval) (BITS(20,23) & ((uint32_t)(regval) << 20))
|
||||
#define EXMC_SYN_CLOCK_RATIO_DISABLE SNTCFG_CKDIV(0) /*!< EXMC_CLK disable */
|
||||
#define EXMC_SYN_CLOCK_RATIO_2_CLK SNTCFG_CKDIV(1) /*!< frequency EXMC_CLK = HCLK/2 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_3_CLK SNTCFG_CKDIV(2) /*!< frequency EXMC_CLK = HCLK/3 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_4_CLK SNTCFG_CKDIV(3) /*!< frequency EXMC_CLK = HCLK/4 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_5_CLK SNTCFG_CKDIV(4) /*!< frequency EXMC_CLK = HCLK/5 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_6_CLK SNTCFG_CKDIV(5) /*!< frequency EXMC_CLK = HCLK/6 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_7_CLK SNTCFG_CKDIV(6) /*!< frequency EXMC_CLK = HCLK/7 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_8_CLK SNTCFG_CKDIV(7) /*!< frequency EXMC_CLK = HCLK/8 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_9_CLK SNTCFG_CKDIV(8) /*!< frequency EXMC_CLK = HCLK/9 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_10_CLK SNTCFG_CKDIV(9) /*!< frequency EXMC_CLK = HCLK/10 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_11_CLK SNTCFG_CKDIV(10) /*!< frequency EXMC_CLK = HCLK/11 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_12_CLK SNTCFG_CKDIV(11) /*!< frequency EXMC_CLK = HCLK/12 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_13_CLK SNTCFG_CKDIV(12) /*!< frequency EXMC_CLK = HCLK/13 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_14_CLK SNTCFG_CKDIV(13) /*!< frequency EXMC_CLK = HCLK/14 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_15_CLK SNTCFG_CKDIV(14) /*!< frequency EXMC_CLK = HCLK/15 */
|
||||
#define EXMC_SYN_CLOCK_RATIO_16_CLK SNTCFG_CKDIV(15) /*!< frequency EXMC_CLK = HCLK/16 */
|
||||
|
||||
/* ECC size */
|
||||
#define NPCTL_ECCSZ(regval) (BITS(17,19) & ((uint32_t)(regval) << 17))
|
||||
#define EXMC_ECC_SIZE_256BYTES NPCTL_ECCSZ(0) /* ECC size is 256 bytes */
|
||||
#define EXMC_ECC_SIZE_512BYTES NPCTL_ECCSZ(1) /* ECC size is 512 bytes */
|
||||
#define EXMC_ECC_SIZE_1024BYTES NPCTL_ECCSZ(2) /* ECC size is 1024 bytes */
|
||||
#define EXMC_ECC_SIZE_2048BYTES NPCTL_ECCSZ(3) /* ECC size is 2048 bytes */
|
||||
#define EXMC_ECC_SIZE_4096BYTES NPCTL_ECCSZ(4) /* ECC size is 4096 bytes */
|
||||
#define EXMC_ECC_SIZE_8192BYTES NPCTL_ECCSZ(5) /* ECC size is 8192 bytes */
|
||||
|
||||
/* ALE to RE delay */
|
||||
#define NPCTL_ATR(regval) (BITS(13,16) & ((uint32_t)(regval) << 13))
|
||||
#define EXMC_ALE_RE_DELAY_1_HCLK NPCTL_ATR(0) /* ALE to RE delay = 1*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_2_HCLK NPCTL_ATR(1) /* ALE to RE delay = 2*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_3_HCLK NPCTL_ATR(2) /* ALE to RE delay = 3*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_4_HCLK NPCTL_ATR(3) /* ALE to RE delay = 4*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_5_HCLK NPCTL_ATR(4) /* ALE to RE delay = 5*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_6_HCLK NPCTL_ATR(5) /* ALE to RE delay = 6*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_7_HCLK NPCTL_ATR(6) /* ALE to RE delay = 7*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_8_HCLK NPCTL_ATR(7) /* ALE to RE delay = 8*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_9_HCLK NPCTL_ATR(8) /* ALE to RE delay = 9*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_10_HCLK NPCTL_ATR(9) /* ALE to RE delay = 10*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_11_HCLK NPCTL_ATR(10) /* ALE to RE delay = 11*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_12_HCLK NPCTL_ATR(11) /* ALE to RE delay = 12*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_13_HCLK NPCTL_ATR(12) /* ALE to RE delay = 13*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_14_HCLK NPCTL_ATR(13) /* ALE to RE delay = 14*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_15_HCLK NPCTL_ATR(14) /* ALE to RE delay = 15*HCLK */
|
||||
#define EXMC_ALE_RE_DELAY_16_HCLK NPCTL_ATR(15) /* ALE to RE delay = 16*HCLK */
|
||||
|
||||
/* CLE to RE delay */
|
||||
#define NPCTL_CTR(regval) (BITS(9,12) & ((uint32_t)(regval) << 9))
|
||||
#define EXMC_CLE_RE_DELAY_1_HCLK NPCTL_CTR(0) /* CLE to RE delay = 1*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_2_HCLK NPCTL_CTR(1) /* CLE to RE delay = 2*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_3_HCLK NPCTL_CTR(2) /* CLE to RE delay = 3*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_4_HCLK NPCTL_CTR(3) /* CLE to RE delay = 4*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_5_HCLK NPCTL_CTR(4) /* CLE to RE delay = 5*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_6_HCLK NPCTL_CTR(5) /* CLE to RE delay = 6*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_7_HCLK NPCTL_CTR(6) /* CLE to RE delay = 7*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_8_HCLK NPCTL_CTR(7) /* CLE to RE delay = 8*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_9_HCLK NPCTL_CTR(8) /* CLE to RE delay = 9*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_10_HCLK NPCTL_CTR(9) /* CLE to RE delay = 10*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_11_HCLK NPCTL_CTR(10) /* CLE to RE delay = 11*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_12_HCLK NPCTL_CTR(11) /* CLE to RE delay = 12*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_13_HCLK NPCTL_CTR(12) /* CLE to RE delay = 13*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_14_HCLK NPCTL_CTR(13) /* CLE to RE delay = 14*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_15_HCLK NPCTL_CTR(14) /* CLE to RE delay = 15*HCLK */
|
||||
#define EXMC_CLE_RE_DELAY_16_HCLK NPCTL_CTR(15) /* CLE to RE delay = 16*HCLK */
|
||||
|
||||
/* NAND bank memory data bus width */
|
||||
#define NPCTL_NDW(regval) (BITS(4,5) & ((uint32_t)(regval) << 4))
|
||||
#define EXMC_NAND_DATABUS_WIDTH_8B NPCTL_NDW(0) /*!< NAND data width is 8 bits */
|
||||
#define EXMC_NAND_DATABUS_WIDTH_16B NPCTL_NDW(1) /*!< NAND data width is 16 bits */
|
||||
|
||||
/* EXMC NOR/SRAM bank region definition */
|
||||
#define EXMC_BANK0_NORSRAM_REGION0 ((uint32_t)0x00000000U) /*!< bank0 NOR/SRAM region0 */
|
||||
#define EXMC_BANK0_NORSRAM_REGION1 ((uint32_t)0x00000001U) /*!< bank0 NOR/SRAM region1 */
|
||||
#define EXMC_BANK0_NORSRAM_REGION2 ((uint32_t)0x00000002U) /*!< bank0 NOR/SRAM region2 */
|
||||
#define EXMC_BANK0_NORSRAM_REGION3 ((uint32_t)0x00000003U) /*!< bank0 NOR/SRAM region3 */
|
||||
|
||||
/* EXMC NOR/SRAM write mode */
|
||||
#define EXMC_ASYN_WRITE ((uint32_t)0x00000000U) /*!< asynchronous write mode */
|
||||
#define EXMC_SYN_WRITE EXMC_SNCTL_SYNCWR /*!< synchronous write mode */
|
||||
|
||||
/* EXMC NWAIT signal configuration */
|
||||
#define EXMC_NWAIT_CONFIG_BEFORE ((uint32_t)0x00000000U) /*!< NWAIT signal is active one data cycle before wait state */
|
||||
#define EXMC_NWAIT_CONFIG_DURING EXMC_SNCTL_NRWTCFG /*!< NWAIT signal is active during wait state */
|
||||
|
||||
/* EXMC NWAIT signal polarity configuration */
|
||||
#define EXMC_NWAIT_POLARITY_LOW ((uint32_t)0x00000000U) /*!< low level is active of NWAIT */
|
||||
#define EXMC_NWAIT_POLARITY_HIGH EXMC_SNCTL_NRWTPOL /*!< high level is active of NWAIT */
|
||||
|
||||
/* EXMC NAND/PC card bank definition */
|
||||
#define EXMC_BANK1_NAND ((uint32_t)0x00000001U) /*!< bank1 NAND flash */
|
||||
#define EXMC_BANK2_NAND ((uint32_t)0x00000002U) /*!< bank2 NAND flash */
|
||||
#define EXMC_BANK3_PCCARD ((uint32_t)0x00000003U) /*!< bank3 PC card */
|
||||
|
||||
/* EXMC flag bits */
|
||||
#define EXMC_NAND_PCCARD_FLAG_RISE EXMC_NPINTEN_INTRS /*!< interrupt rising edge status */
|
||||
#define EXMC_NAND_PCCARD_FLAG_LEVEL EXMC_NPINTEN_INTHS /*!< interrupt high-level status */
|
||||
#define EXMC_NAND_PCCARD_FLAG_FALL EXMC_NPINTEN_INTFS /*!< interrupt falling edge status */
|
||||
#define EXMC_NAND_PCCARD_FLAG_FIFOE EXMC_NPINTEN_FFEPT /*!< FIFO empty flag */
|
||||
|
||||
/* EXMC interrupt flag bits */
|
||||
#define EXMC_NAND_PCCARD_INT_FLAG_RISE EXMC_NPINTEN_INTREN /*!< rising edge interrupt and corresponding flag */
|
||||
#define EXMC_NAND_PCCARD_INT_FLAG_LEVEL EXMC_NPINTEN_INTHEN /*!< high-level interrupt and corresponding flag */
|
||||
#define EXMC_NAND_PCCARD_INT_FLAG_FALL EXMC_NPINTEN_INTFEN /*!< falling edge interrupt and corresponding flag */
|
||||
|
||||
/* function declarations */
|
||||
/* NOR/SRAM */
|
||||
/* deinitialize EXMC NOR/SRAM region */
|
||||
void exmc_norsram_deinit(uint32_t exmc_norsram_region);
|
||||
/* initialize exmc_norsram_parameter_struct with the default values */
|
||||
void exmc_norsram_struct_para_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct);
|
||||
/* initialize EXMC NOR/SRAM region */
|
||||
void exmc_norsram_init(exmc_norsram_parameter_struct* exmc_norsram_init_struct);
|
||||
/* enable EXMC NOR/SRAM region */
|
||||
void exmc_norsram_enable(uint32_t exmc_norsram_region);
|
||||
/* disable EXMC NOR/SRAM region */
|
||||
void exmc_norsram_disable(uint32_t exmc_norsram_region);
|
||||
/* configure CRAM page size */
|
||||
void exmc_norsram_page_size_config(uint32_t exmc_norsram_region, uint32_t page_size);
|
||||
|
||||
/* NAND */
|
||||
/* deinitialize EXMC NAND bank */
|
||||
void exmc_nand_deinit(uint32_t exmc_nand_bank);
|
||||
/* initialize exmc_nand_parameter_struct with the default values */
|
||||
void exmc_nand_struct_para_init(exmc_nand_parameter_struct* exmc_nand_init_struct);
|
||||
/* initialize EXMC NAND bank */
|
||||
void exmc_nand_init(exmc_nand_parameter_struct* exmc_nand_init_struct);
|
||||
/* enable EXMC NAND bank */
|
||||
void exmc_nand_enable(uint32_t exmc_nand_bank);
|
||||
/* disable EXMC NAND bank */
|
||||
void exmc_nand_disable(uint32_t exmc_nand_bank);
|
||||
/* enable or disable the EXMC NAND ECC function */
|
||||
void exmc_nand_ecc_config(uint32_t exmc_nand_bank, ControlStatus newvalue);
|
||||
/* get the EXMC ECC value */
|
||||
uint32_t exmc_ecc_get(uint32_t exmc_nand_bank);
|
||||
|
||||
/* PC card */
|
||||
/* deinitialize EXMC PC card bank */
|
||||
void exmc_pccard_deinit(void);
|
||||
/* initialize exmc_pccard_parameter_struct parameter with the default values */
|
||||
void exmc_pccard_struct_para_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct);
|
||||
/* initialize EXMC PC card bank */
|
||||
void exmc_pccard_init(exmc_pccard_parameter_struct* exmc_pccard_init_struct);
|
||||
/* enable EXMC PC card bank */
|
||||
void exmc_pccard_enable(void);
|
||||
/* disable EXMC PC card bank */
|
||||
void exmc_pccard_disable(void);
|
||||
|
||||
/* interrupt & flag functions */
|
||||
/* enable EXMC interrupt */
|
||||
void exmc_interrupt_enable(uint32_t exmc_bank,uint32_t interrupt);
|
||||
/* disable EXMC interrupt */
|
||||
void exmc_interrupt_disable(uint32_t exmc_bank,uint32_t interrupt);
|
||||
/* get EXMC flag status */
|
||||
FlagStatus exmc_flag_get(uint32_t exmc_bank,uint32_t flag);
|
||||
/* clear EXMC flag status */
|
||||
void exmc_flag_clear(uint32_t exmc_bank,uint32_t flag);
|
||||
/* get EXMC interrupt flag */
|
||||
FlagStatus exmc_interrupt_flag_get(uint32_t exmc_bank,uint32_t interrupt);
|
||||
/* clear EXMC interrupt flag */
|
||||
void exmc_interrupt_flag_clear(uint32_t exmc_bank,uint32_t interrupt);
|
||||
|
||||
#endif /* GD32E50X_EXMC_H */
|
||||
@@ -657,13 +657,15 @@ static void system_clock_hxtal(void)
|
||||
|
||||
/* if fail */
|
||||
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
|
||||
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
|
||||
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
|
||||
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
|
||||
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
|
||||
*((volatile uint32_t *)0x40010804U) = _ctl; }
|
||||
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
|
||||
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
|
||||
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
|
||||
{ const char *_msg = "HXTAL FAIL\r\n";
|
||||
for(int i=0; _msg[i]; i++){
|
||||
*((volatile uint32_t *)0x40013804U) = _msg[i];
|
||||
@@ -713,13 +715,15 @@ static void system_clock_72m_hxtal(void)
|
||||
|
||||
/* if fail */
|
||||
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
|
||||
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
|
||||
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
|
||||
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
|
||||
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
|
||||
*((volatile uint32_t *)0x40010804U) = _ctl; }
|
||||
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
|
||||
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
|
||||
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
|
||||
{ const char *_msg = "HXTAL FAIL\r\n";
|
||||
for(int i=0; _msg[i]; i++){
|
||||
*((volatile uint32_t *)0x40013804U) = _msg[i];
|
||||
@@ -831,13 +835,15 @@ static void system_clock_120m_hxtal(void)
|
||||
|
||||
/* if fail */
|
||||
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
|
||||
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
|
||||
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
|
||||
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
|
||||
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
|
||||
*((volatile uint32_t *)0x40010804U) = _ctl; }
|
||||
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
|
||||
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
|
||||
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
|
||||
{ const char *_msg = "HXTAL FAIL\r\n";
|
||||
for(int i=0; _msg[i]; i++){
|
||||
*((volatile uint32_t *)0x40013804U) = _msg[i];
|
||||
@@ -950,13 +956,15 @@ static void system_clock_168m_hxtal(void)
|
||||
|
||||
/* if fail */
|
||||
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
|
||||
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
|
||||
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
|
||||
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
|
||||
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
|
||||
*((volatile uint32_t *)0x40010804U) = _ctl; }
|
||||
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
|
||||
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
|
||||
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
|
||||
{ const char *_msg = "HXTAL FAIL\r\n";
|
||||
for(int i=0; _msg[i]; i++){
|
||||
*((volatile uint32_t *)0x40013804U) = _msg[i];
|
||||
@@ -1069,13 +1077,15 @@ static void system_clock_180m_hxtal(void)
|
||||
|
||||
/* if fail */
|
||||
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
|
||||
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
|
||||
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
|
||||
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
|
||||
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
|
||||
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
|
||||
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
|
||||
*((volatile uint32_t *)0x40010804U) = _ctl; }
|
||||
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
|
||||
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
|
||||
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
|
||||
{ const char *_msg = "HXTAL FAIL\r\n";
|
||||
for(int i=0; _msg[i]; i++){
|
||||
*((volatile uint32_t *)0x40013804U) = _msg[i];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1
|
||||
cd /d "G:\Robot_v2\DCU\DCU_APP\GD32E508_DCU_APP"
|
||||
set PACKED=/D"STRUCT_PACKED_START=" /D"STRUCT_PACKED_END=" /D"MBX_STRUCT_PACKED_START=" /D"MBX_STRUCT_PACKED_END=" /D"OBJ_STRUCT_PACKED_START=" /D"OBJ_STRUCT_PACKED_END="
|
||||
set INC=/I. /Icomm\ssc\inc /Icomm\ssc\port /IRTE\_Debug_GD32E508VE /I_syntaxcheck_stubs
|
||||
echo === mailbox.c ===
|
||||
cl /nologo /Zs /W0 /TC /std:c11 /utf-8 /Zp1 %PACKED% %INC% comm\ssc\src\mailbox.c
|
||||
if errorlevel 1 (echo MAILBOX_FAIL) else (echo MAILBOX_OK)
|
||||
echo === dc_app.c ===
|
||||
cl /nologo /Zs /W0 /TC /std:c11 /utf-8 /Zp1 %PACKED% %INC% comm\ssc\port\dc_app.c
|
||||
if errorlevel 1 (echo DC_APP_FAIL) else (echo DC_APP_OK)
|
||||
@@ -0,0 +1 @@
|
||||
/* syntax-check stub only: SSC C files never touch device registers directly */
|
||||
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* @file ecat_motor_ota.cpp
|
||||
* @brief EtherCAT 邮箱进料通路实现(职责与时序前提见 ecat_motor_ota.h)。
|
||||
*
|
||||
* 上下文:三个 SDO 钩子只在 MainLoop(ECAT_Application 调用链)里被调,泵 process()
|
||||
* 也在主循环,因此 48KB 暂存区没有并发写者。真正的硬保证仍是通道锁:即便主站循环
|
||||
* 被别的路径(页面自愈 / 守护重连)拉回来,运动帧也进不了正在升级的通道。
|
||||
*/
|
||||
#include "comm/ecat_motor_ota.h"
|
||||
|
||||
#include "sys/config.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/systick.h"
|
||||
|
||||
#include "spi_link_v2.h" /* v2_crc32:CRC32 MPEG-2(poly 0x04C11DB7,初值 0xFFFFFFFF,无反射、无末尾异或) */
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio> /* LOG_ERR 展开为 printf(sys/config.h) */
|
||||
|
||||
namespace {
|
||||
|
||||
/*============================================================================
|
||||
* 尺寸常量与守卫
|
||||
*==========================================================================*/
|
||||
constexpr uint32_t kChunkObjBytes = ECAT_MOTA_CHUNK_OBJ_BYTES;
|
||||
constexpr uint32_t kChunkHdrBytes = ECAT_MOTA_CHUNK_HDR_BYTES;
|
||||
constexpr uint32_t kImgMaxBytes = ECAT_MOTA_IMG_MAX_BYTES;
|
||||
constexpr uint8_t kCtlSubs = ECAT_MOTA_CTL_SUBS;
|
||||
|
||||
static_assert(kImgMaxBytes == MotorOtaRelay::kMaxFwSize,
|
||||
"暂存区上限必须与 MotorOtaRelay::kMaxFwSize 一致");
|
||||
static_assert(kChunkObjBytes * 8U <= 0xFFFFU,
|
||||
"0x2511 块长超出 SDO 单条目 BitSize(UINT16)上限");
|
||||
static_assert(kChunkObjBytes > kChunkHdrBytes, "数据窗要留出净载荷");
|
||||
|
||||
/** APP 的堆容量(RTE/Device/GD32E508VE/regions_GD32E508VE.h 的 __HEAP_SIZE)。
|
||||
* SSC 每笔分段下载都要 malloc(块长)(sdoserv.c:751),堆不够就从站直接回
|
||||
* abort 0x05040005「Out of memory」——2026-10-11 就是拿 4096 撞出来的。
|
||||
* 这里留 3 倍余量:谁要把块长往上抬,先抬 __HEAP_SIZE 再来改这一行。 */
|
||||
constexpr uint32_t kHeapBytes = 3072U;
|
||||
static_assert(kChunkObjBytes * 3U <= kHeapBytes,
|
||||
"块长受 APP 堆限制:malloc(块长) 必须留足余量,否则从站 OOM 拒收");
|
||||
|
||||
/** 单次 process() 最多搬运的字节数:把一圈的占用钳住,别在 MainLoop 里长时间拷 48KB。 */
|
||||
constexpr uint16_t kPumpSlice = 512U;
|
||||
|
||||
/*============================================================================
|
||||
* 会话状态
|
||||
*==========================================================================*/
|
||||
uint32_t s_cmd = 0U; /**< sub1: 最近一次执行的命令 */
|
||||
uint32_t s_total = 0U; /**< sub2: 声明的镜像总字节数 */
|
||||
uint32_t s_channel = 0U; /**< sub3: 目标 CAN 通道 */
|
||||
uint32_t s_node = 0U; /**< sub4: 目标节点 1..9 */
|
||||
uint32_t s_img_crc = 0U; /**< sub5: 整镜像期望 CRC32 */
|
||||
|
||||
uint32_t s_staged = 0U; /**< 已连续暂存字节数(水位,写块时前移) */
|
||||
uint8_t s_sess_err = ECAT_MOTA_OK;
|
||||
|
||||
bool s_armed = false; /**< 已声明总长,可收块 */
|
||||
bool s_started = false; /**< 已交给 relay(暂存区冻结,只剩泵) */
|
||||
bool s_crc_ok = false; /**< 整镜像 CRC 已核对通过 */
|
||||
bool s_last_chunk_ok = false;
|
||||
bool s_end_logged = false;/**< 终态日志只打一次 */
|
||||
|
||||
/* 终态锁存:relay 会话结束后可能很快回到 kIdle(主站下一拍才来读),
|
||||
* 不做锁存就会把「刷成功了」读成 kIdle,主站据此报的结论就成了猜的。 */
|
||||
bool s_final_valid = false;
|
||||
uint8_t s_final_state = 0U;
|
||||
uint8_t s_final_err = 0U;
|
||||
|
||||
uint32_t s_pump_pos = 0U; /**< 泵在暂存区里的读游标 */
|
||||
|
||||
/** 48KB 暂存区(.bss:启动零耗时;APP 当前 RAM 用量 35KB/128KB,放得下)。 */
|
||||
uint8_t s_img[kImgMaxBytes];
|
||||
|
||||
inline uint32_t loadU32(const uint8_t *p) {
|
||||
return static_cast<uint32_t>(p[0])
|
||||
| (static_cast<uint32_t>(p[1]) << 8)
|
||||
| (static_cast<uint32_t>(p[2]) << 16)
|
||||
| (static_cast<uint32_t>(p[3]) << 24);
|
||||
}
|
||||
|
||||
void resetSession()
|
||||
{
|
||||
s_armed = false;
|
||||
s_started = false;
|
||||
s_crc_ok = false;
|
||||
s_staged = 0U;
|
||||
s_pump_pos = 0U;
|
||||
s_total = 0U;
|
||||
s_last_chunk_ok = false;
|
||||
s_end_logged = false;
|
||||
s_final_valid = false;
|
||||
s_final_state = 0U;
|
||||
s_final_err = 0U;
|
||||
}
|
||||
|
||||
/** 只认「已连续暂存水位之内」的偏移:留洞会让泵卡住,而且说明主站发料顺序错了。 */
|
||||
uint8_t acceptChunk(uint32_t offset, uint32_t crc, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
if (!s_armed || s_total == 0U) {
|
||||
s_sess_err = ECAT_MOTA_E_NOTARMED;
|
||||
return s_sess_err;
|
||||
}
|
||||
if (s_started) {
|
||||
s_sess_err = ECAT_MOTA_E_STARTED;
|
||||
return s_sess_err;
|
||||
}
|
||||
if (offset > s_staged || offset + len > s_total) {
|
||||
s_sess_err = ECAT_MOTA_E_GAP;
|
||||
return s_sess_err;
|
||||
}
|
||||
if (len > 0U) {
|
||||
/* 块级校验:EtherCAT 数据报**头**有 CRC,载荷没有;邮箱分段拼装还是软件路径。
|
||||
* 这里挡一次,坏块就地重发,不会等到电机回 0x06 才发现(那时 flash 已擦)。 */
|
||||
if (v2_crc32(data, len) != crc) {
|
||||
s_sess_err = ECAT_MOTA_E_CHUNKCRC;
|
||||
s_last_chunk_ok = false;
|
||||
return s_sess_err;
|
||||
}
|
||||
std::memcpy(&s_img[offset], data, len);
|
||||
if (offset + len > s_staged) {
|
||||
s_staged = offset + len;
|
||||
}
|
||||
}
|
||||
s_last_chunk_ok = true;
|
||||
s_sess_err = ECAT_MOTA_OK;
|
||||
return ECAT_MOTA_OK;
|
||||
}
|
||||
|
||||
/** 整镜像核对 + 交给 relay。所有失败路径都必须在电机进 BOOT **之前**。 */
|
||||
bool beginTransfer()
|
||||
{
|
||||
if (s_total == 0U || s_total > kImgMaxBytes) {
|
||||
s_sess_err = ECAT_MOTA_E_BADLEN;
|
||||
return false;
|
||||
}
|
||||
if (s_staged != s_total) {
|
||||
s_sess_err = ECAT_MOTA_E_GAP;
|
||||
return false;
|
||||
}
|
||||
if (s_channel >= 3U || s_node < 1U || s_node > 9U) {
|
||||
s_sess_err = ECAT_MOTA_E_PARAM;
|
||||
return false;
|
||||
}
|
||||
/* DCU 自己正在回 BOOT(配置页写 + 复位在路上)时绝不能起电机会话:
|
||||
* 复位会把半程的刷写腰斩在 erase 之后。 */
|
||||
if (OtaBootRequest::isRebootPending()) {
|
||||
s_sess_err = ECAT_MOTA_E_BOOTPEND;
|
||||
return false;
|
||||
}
|
||||
/* 整镜像 CRC 先行:这一刻电机还在 APP 正常跑、擦除没开始,校验不过就是零代价失败。
|
||||
* 反过来「先进 BOOT 再验」才是变砖路径。 */
|
||||
if (v2_crc32(s_img, s_total) != s_img_crc) {
|
||||
s_sess_err = ECAT_MOTA_E_IMGCRC;
|
||||
return false;
|
||||
}
|
||||
s_crc_ok = true;
|
||||
|
||||
if (!MotorOtaRelay::start(static_cast<uint8_t>(s_channel), s_total,
|
||||
MotorOtaRelay::FeedSource::kEcat,
|
||||
static_cast<uint8_t>(s_node))) {
|
||||
s_sess_err = ECAT_MOTA_E_BUSY;
|
||||
s_crc_ok = false;
|
||||
return false;
|
||||
}
|
||||
s_started = true;
|
||||
s_end_logged = false;
|
||||
s_pump_pos = 0U;
|
||||
s_final_valid = false;
|
||||
s_sess_err = ECAT_MOTA_OK;
|
||||
LOG_ERR("[ECAT-MOTA] start ch=%lu node=%lu size=%lu crc=%08lX\r\n",
|
||||
(unsigned long)s_channel, (unsigned long)s_node,
|
||||
(unsigned long)s_total, (unsigned long)s_img_crc);
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
/*============================================================================
|
||||
* EcatMotorOta(C++ 侧:主循环钩子与查询)
|
||||
*==========================================================================*/
|
||||
namespace EcatMotorOta {
|
||||
|
||||
void process()
|
||||
{
|
||||
if (!s_started) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MotorOtaRelay::Status st = MotorOtaRelay::getStatus();
|
||||
|
||||
/* 电机侧自动重试会清环缓并把 fw_fed 归零:泵必须跟着回退,否则会把
|
||||
* 「后半段」当成已喂过,喂出一个前段缺失的镜像。 */
|
||||
if (st.fw_fed < s_pump_pos) {
|
||||
s_pump_pos = st.fw_fed;
|
||||
}
|
||||
|
||||
/* 泵:按环缓空位切片,搬不动就下一圈再来(不阻塞、不忙等、不碰 CAN)。 */
|
||||
while (s_pump_pos < s_total) {
|
||||
const uint16_t freeSpace = MotorOtaRelay::feedFreeSpace();
|
||||
if (freeSpace == 0U) {
|
||||
break;
|
||||
}
|
||||
const uint32_t remain = s_total - s_pump_pos;
|
||||
uint16_t n = static_cast<uint16_t>(remain < kPumpSlice ? remain : kPumpSlice);
|
||||
if (n > freeSpace) {
|
||||
n = freeSpace;
|
||||
}
|
||||
if (!MotorOtaRelay::feedAt(s_pump_pos, &s_img[s_pump_pos], n)) {
|
||||
break;
|
||||
}
|
||||
s_pump_pos += n;
|
||||
}
|
||||
|
||||
if (!MotorOtaRelay::isActive()) {
|
||||
if (!s_end_logged) {
|
||||
s_end_logged = true;
|
||||
s_final_valid = true;
|
||||
s_final_state = static_cast<uint8_t>(st.state);
|
||||
s_final_err = st.error_code;
|
||||
LOG_ERR("[ECAT-MOTA] end state=%u err=0x%02X acked=%u/%u fed=%lu/%lu\r\n",
|
||||
static_cast<unsigned>(st.state), static_cast<unsigned>(st.error_code),
|
||||
static_cast<unsigned>(st.packets_acked),
|
||||
static_cast<unsigned>(st.total_packets),
|
||||
(unsigned long)st.fw_fed, (unsigned long)st.fw_size);
|
||||
}
|
||||
s_started = false;
|
||||
s_crc_ok = false;
|
||||
/* 会话已结束:不再收块。主站要开下一轮必须重新写 0x2510:02 声明总长,
|
||||
* 那一步复位全部会话状态(包括上面的终态锁存)。 */
|
||||
s_armed = false;
|
||||
s_last_chunk_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool sessionActive()
|
||||
{
|
||||
return s_started;
|
||||
}
|
||||
|
||||
} /* namespace EcatMotorOta */
|
||||
|
||||
/*============================================================================
|
||||
* SDO 钩子(extern "C":dc_app.c 的对象表直接引用)
|
||||
*==========================================================================*/
|
||||
extern "C" {
|
||||
|
||||
uint8_t EcatMotorOtaChunkWrite(uint32_t dataSize, const uint8_t *bytes)
|
||||
{
|
||||
if (bytes == nullptr) {
|
||||
return ECAT_MOTA_E_BADLEN;
|
||||
}
|
||||
if (dataSize < kChunkHdrBytes) {
|
||||
s_sess_err = ECAT_MOTA_E_BADLEN;
|
||||
return s_sess_err;
|
||||
}
|
||||
if (dataSize > kChunkObjBytes) {
|
||||
s_sess_err = ECAT_MOTA_E_BADLEN;
|
||||
return s_sess_err;
|
||||
}
|
||||
|
||||
const uint32_t offset = loadU32(&bytes[0]);
|
||||
const uint32_t crc = loadU32(&bytes[4]);
|
||||
const uint32_t len = dataSize - kChunkHdrBytes;
|
||||
|
||||
const uint8_t rc = acceptChunk(offset, crc, &bytes[kChunkHdrBytes], len);
|
||||
if (rc != ECAT_MOTA_OK) {
|
||||
/* 一次失败只打一行:主站会重发同一块,串口刷屏会拖慢 MainLoop。 */
|
||||
LOG_ERR("[ECAT-MOTA] chunk reject off=%lu len=%lu err=0x%02X\r\n",
|
||||
(unsigned long)offset, (unsigned long)len, (unsigned)rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
uint8_t EcatMotorOtaCtlWrite(uint8_t subindex, uint32_t dataSize, const uint8_t *bytes)
|
||||
{
|
||||
if (bytes == nullptr || dataSize < 4U) {
|
||||
return ECAT_MOTA_E_BADLEN;
|
||||
}
|
||||
const uint32_t v = loadU32(bytes);
|
||||
|
||||
switch (subindex) {
|
||||
case 1U: {
|
||||
/* sub1 cmd:1 = 校验并启动,3 = 中止会话 */
|
||||
s_cmd = v;
|
||||
if (v == 1U) {
|
||||
if (!beginTransfer()) {
|
||||
return s_sess_err;
|
||||
}
|
||||
} else if (v == 3U) {
|
||||
if (s_started) {
|
||||
MotorOtaRelay::cancel();
|
||||
LOG_ERR("[ECAT-MOTA] cancel by master\r\n");
|
||||
}
|
||||
resetSession();
|
||||
s_sess_err = ECAT_MOTA_OK;
|
||||
} else {
|
||||
return ECAT_MOTA_E_PARAM;
|
||||
}
|
||||
return ECAT_MOTA_OK;
|
||||
}
|
||||
case 2U: {
|
||||
/* sub2 总长:声明即复位会话(主站重开一轮不需要先 abort) */
|
||||
if (v == 0U || v > kImgMaxBytes) {
|
||||
s_sess_err = ECAT_MOTA_E_BADLEN;
|
||||
return s_sess_err;
|
||||
}
|
||||
if (s_started) {
|
||||
s_sess_err = ECAT_MOTA_E_STARTED;
|
||||
return s_sess_err;
|
||||
}
|
||||
resetSession();
|
||||
s_total = v;
|
||||
s_armed = true;
|
||||
s_sess_err = ECAT_MOTA_OK;
|
||||
return ECAT_MOTA_OK;
|
||||
}
|
||||
case 3U:
|
||||
if (v >= 3U) {
|
||||
s_sess_err = ECAT_MOTA_E_PARAM;
|
||||
return s_sess_err;
|
||||
}
|
||||
s_channel = v;
|
||||
return ECAT_MOTA_OK;
|
||||
case 4U:
|
||||
if (v < 1U || v > 9U) {
|
||||
s_sess_err = ECAT_MOTA_E_PARAM;
|
||||
return s_sess_err;
|
||||
}
|
||||
s_node = v;
|
||||
return ECAT_MOTA_OK;
|
||||
case 5U:
|
||||
s_img_crc = v;
|
||||
return ECAT_MOTA_OK;
|
||||
default:
|
||||
/* sub0 是条目数;sub6..sub11 只读 */
|
||||
return ECAT_MOTA_E_PARAM;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EcatMotorOtaCtlRead(uint8_t subindex)
|
||||
{
|
||||
const MotorOtaRelay::Status st = MotorOtaRelay::getStatus();
|
||||
/* live = 会话还在跑,或从没跑过(那就照实反映 relay 当前状态);
|
||||
* 会话刚结束时返回锁存值,避免主站把「成功」读成 kIdle。 */
|
||||
const bool live = (s_started || !s_final_valid);
|
||||
|
||||
switch (subindex) {
|
||||
case 0U: return kCtlSubs;
|
||||
case 1U: return s_cmd;
|
||||
case 2U: return s_total;
|
||||
case 3U: return s_channel;
|
||||
case 4U: return s_node;
|
||||
case 5U: return s_img_crc;
|
||||
case 6U: return s_staged;
|
||||
case 7U: return live ? static_cast<uint32_t>(st.state)
|
||||
: static_cast<uint32_t>(s_final_state);
|
||||
case 8U: return st.packets_acked;
|
||||
case 9U: return st.total_packets;
|
||||
case 10U: return (static_cast<uint32_t>(s_sess_err) << 8)
|
||||
| (live ? static_cast<uint32_t>(st.error_code)
|
||||
: static_cast<uint32_t>(s_final_err));
|
||||
case 11U: {
|
||||
/* phase 位图:只报能被板子真实触发的事实,不做任何推测性状态。
|
||||
* bit0 已声明总长 · bit1 暂存齐 · bit2 整镜像 CRC 过 · bit3 会话在跑
|
||||
* bit4 上一块被接受 · bit5 本会话目标通道当前持锁 · bit6 relay 活跃 */
|
||||
uint32_t phase = 0U;
|
||||
if (s_armed) { phase |= 0x0001U; }
|
||||
if (s_total != 0U && s_staged == s_total) { phase |= 0x0002U; }
|
||||
if (s_crc_ok) { phase |= 0x0004U; }
|
||||
if (s_started) { phase |= 0x0008U; }
|
||||
if (s_last_chunk_ok) { phase |= 0x0010U; }
|
||||
if (MotorOtaRelay::isChannelLocked(static_cast<uint8_t>(s_channel))) { phase |= 0x0020U; }
|
||||
if (MotorOtaRelay::isActive()) { phase |= 0x0040U; }
|
||||
return phase;
|
||||
}
|
||||
default: return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file ecat_motor_ota.h
|
||||
* @brief EtherCAT(CoE 邮箱)电机 OTA 进料通路 —— 主站把电机固件灌进 DCU 本地暂存区,
|
||||
* 再由本模块泵进 MotorOtaRelay 现有的 4KB 环缓。
|
||||
*
|
||||
* 为什么要有它(不是重复造轮子):电机 OTA 的引擎一直是 MotorOtaRelay
|
||||
* (AES 握手 + CAN 停等 + 单发模式),只有一条进料路:ESP32 经 SPI 0xD1 喂。
|
||||
* 纯 EtherCAT 场合(只插网线、ESP32 WiFi 关断)那条 HTTP 打不通,
|
||||
* 于是前端把能力标成 supportsOta=false。本模块只换**进料口**,不换引擎:
|
||||
* - 分块写入仍走 MotorOtaRelay::feedAt(),offset/幂等/环缓满的判据只有一份;
|
||||
* - 加密、停等、重发、终态判定全部沿用原状态机;
|
||||
* - 通道独占锁同样沿用(EtherCAT 自己的 PDO 运动帧也被它挡在 OTA 之外)。
|
||||
*
|
||||
* 与 BOOT 的 0x2500/0x2501(刷 DCU 自身)的区别:那边落 flash,这边只落 SRAM 暂存区,
|
||||
* 全程不碰 fmc_*,因此不需要关中断擦写那套优雅关停。
|
||||
*
|
||||
* 对象(APP 字典,表项与钩子包装在 comm/ssc/port/dc_app.c):
|
||||
* 0x2510 会话控制(REC of UINT32,读写钩子返回实时值)
|
||||
* 0x2511 数据窗(VAR OCTETSTRING,4096B,载荷 [offset:u32 LE][crc32:u32 LE][data...])
|
||||
*
|
||||
* 本头刻意只用 <stdint.h> 类型:dc_app.c 是 C 且靠相对路径取 ecat_def.h,
|
||||
* 而 comm/ 下的 C++ 拿不到那套 SSC 类型。SDO abort 码的映射留在 dc_app.c 那一层。
|
||||
*/
|
||||
#ifndef COMM_ECAT_MOTOR_OTA_H
|
||||
#define COMM_ECAT_MOTOR_OTA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* ── 尺寸常量(宏:C 与 C++ 两侧共用同一真源) ──
|
||||
* 0x2511 对象字节数上限,含 8 字节自描述头。
|
||||
* 单条目 BitSize 是 UINT16 ⇒ 8191 字节是天花板,但**真正的上限是 APP 的堆**:
|
||||
* SSC 的分段下载缓冲是 `ALLOCMEM(ROUNDUPBYTE2WORD(size))` 即 malloc(size)
|
||||
* (comm/ssc/src/sdoserv.c:751,池不够就回 abort 0x05040005 Out of memory),
|
||||
* 而 APP 的 `__HEAP_SIZE` 只有 0xC00=3072B(BOOT 是 0x4000,所以 BOOT 的
|
||||
* 0x2501 能用 4096 —— 照抄块长不照抄堆就会当场失败,2026-10-11 实测)。
|
||||
* 取 1024:48KB 镜像 = 47 块,每块 1 initiate + 4 段 ≈ 5 个邮箱帧,
|
||||
* 总邮箱流量仍在亚秒级,且给 malloc 留出 3 倍余量。
|
||||
* 改这里必须同步 tools/ecat/ecat_native.c 的 MFO_CHUNK_OBJ(两边各自断言不了对方)。
|
||||
*/
|
||||
#define ECAT_MOTA_CHUNK_OBJ_BYTES 1024U
|
||||
#define ECAT_MOTA_CHUNK_HDR_BYTES 8U
|
||||
/** 电机固件上限:必须与 MotorOtaRelay::kMaxFwSize(48KB)一致,C++ 侧有 static_assert 守着。 */
|
||||
#define ECAT_MOTA_IMG_MAX_BYTES 0xC000U
|
||||
/** 0x2510 的子索引个数(sub1..sub11)。 */
|
||||
#define ECAT_MOTA_CTL_SUBS 11U
|
||||
|
||||
/**
|
||||
* ── 会话结果码 ──
|
||||
* 与 MotorOtaRelay 的协议错误码(0x01..0x3x,来自电机 BOOT)刻意分段:
|
||||
* 0x8x 永远是「DCU 本端把料挡了」,不是电机报错。看到 0x8x 查链路与参数,
|
||||
* 看到 0x0x/0x1x/0x3x 查电机与 CAN。同一份码既写进 0x2510:10 高字节(主站可读),
|
||||
* 也作为钩子的返回值(dc_app.c 再换成 SDO abort 码)。
|
||||
*/
|
||||
#define ECAT_MOTA_OK 0x00U
|
||||
#define ECAT_MOTA_E_BADLEN 0x81U /* 声明总长或块长非法 */
|
||||
#define ECAT_MOTA_E_CHUNKCRC 0x82U /* 块 CRC32 不符:邮箱搬坏了,主站重发该块 */
|
||||
#define ECAT_MOTA_E_GAP 0x83U /* 块偏移越界/留洞:主站发料顺序错 */
|
||||
#define ECAT_MOTA_E_IMGCRC 0x84U /* 整镜像 CRC32 不符:拒绝进 BOOT,一个字节都没发 */
|
||||
#define ECAT_MOTA_E_BUSY 0x85U /* relay 已在跑(另一条链路或上一会话未结束) */
|
||||
#define ECAT_MOTA_E_NOTARMED 0x86U /* 没声明总长就写数据窗 */
|
||||
#define ECAT_MOTA_E_STARTED 0x87U /* 已 start,暂存区冻结,不再收块 */
|
||||
#define ECAT_MOTA_E_PARAM 0x88U /* 通道/节点参数非法 */
|
||||
#define ECAT_MOTA_E_BOOTPEND 0x89U /* DCU 自己正在交接回 BOOT,此时不许刷电机 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace EcatMotorOta {
|
||||
|
||||
/** 主循环每圈调用:把暂存区泵进环缓,并在会话结束时收标志。 */
|
||||
void process(void);
|
||||
/** 本模块的会话是否活跃(已 start 且 relay 未进终态)。 */
|
||||
bool sessionActive(void);
|
||||
|
||||
} /* namespace EcatMotorOta */
|
||||
|
||||
#define ECAT_MOTA_LINKAGE extern "C"
|
||||
#else
|
||||
#define ECAT_MOTA_LINKAGE extern
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* SDO 钩子(dc_app.c 的对象表引用这三个,实现在 ecat_motor_ota.cpp)
|
||||
*
|
||||
* 返回 ECAT_MOTA_OK 或上面的 ECAT_MOTA_E_*;dc_app.c 把非 0 换成 SDO abort 码,
|
||||
* 主站既拿得到 abort,也能从 0x2510:10 读到具体是哪一条,不用猜。
|
||||
*---------------------------------------------------------------------------*/
|
||||
/** @brief 0x2510 写入:sub1=cmd(1=start / 3=abort)、sub2=总长、sub3=通道、sub4=node、sub5=整镜像 CRC32。 */
|
||||
ECAT_MOTA_LINKAGE uint8_t EcatMotorOtaCtlWrite(uint8_t subindex, uint32_t dataSize,
|
||||
const uint8_t *bytes);
|
||||
|
||||
/** @brief 0x2510 读取:返回该 sub 的实时值(进度不落地缓存,避免读到旧数)。 */
|
||||
ECAT_MOTA_LINKAGE uint32_t EcatMotorOtaCtlRead(uint8_t subindex);
|
||||
|
||||
/** @brief 0x2511 写入:一块镜像(自描述 offset + 块 CRC32),失败即 SDO abort。 */
|
||||
ECAT_MOTA_LINKAGE uint8_t EcatMotorOtaChunkWrite(uint32_t dataSize, const uint8_t *bytes);
|
||||
|
||||
#endif /* COMM_ECAT_MOTOR_OTA_H */
|
||||
@@ -1,88 +1,601 @@
|
||||
/**
|
||||
* @file esc_exmc_bus.cpp
|
||||
* @brief LAN9252 ESC 的 EXMC 16-bit 并行总线驱动实现。
|
||||
*
|
||||
* 使用 GigaDevice gd32e50x_exmc 标准库。
|
||||
* 注意:DFP 只把 FMC 注册为 CMSIS 组件,EXMC 未进组件列表;
|
||||
* 源文件放在 RTE/Device/... 并由 cproject group device_exmc 本地编译。
|
||||
*/
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "sys/config.h"
|
||||
#include "device/watchdog.h"
|
||||
#include <stdio.h>
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
extern "C" {
|
||||
#include "gd32e50x_rcu.h"
|
||||
#include "gd32e50x_exmc.h" /* RTE/Device/GD32E508VE 或 -I 同目录 */
|
||||
#include "gd32e50x_gpio.h"
|
||||
#ifdef EXMC_NORSRAM_REGION1
|
||||
#include "gd32e50x_exmc.h"
|
||||
#include "gd32e50x_rcu.h"
|
||||
}
|
||||
|
||||
/* 内存映射访问宏:基址 + CPU 字节偏移。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位(用户手册 p869:HADDR[25:1]→EXMC_A[24:0]):
|
||||
* EXMC_A16(PD11) 引脚对应偏移 bit17=0x20000,EXMC_A17(PD12) 对应 bit18=0x40000,
|
||||
* 即 1<<16 的 CPU 偏移驱动的是 A15 引脚而非 A16。 */
|
||||
#define ESC_EXMC_REG16(offset) \
|
||||
(*(volatile uint16_t *)(CONFIG_ESC_EXMC_BASE_ADDR + (offset)))
|
||||
|
||||
/* ========================================================================
|
||||
* GPIO 位拍(bit-bang)辅助函数 —— 完全绕过 EXMC 外设,
|
||||
* 用普通 GPIO 手动产生 LAN9252 HBI 时序,用于判定"芯片是否活着"。
|
||||
* 数据线物理映射:D0=PD14 D1=PD15 D2=PD0 D3=PD1 D4..D12=PE7..PE15
|
||||
* D13=PD8 D14=PD9 D15=PD10
|
||||
* 控制线:CS#=PD7(NE0) RD#=PD4(NOE) WR#=PD5(NWE) A1=PD11 A2=PD12
|
||||
* ======================================================================== */
|
||||
namespace {
|
||||
|
||||
/* 通断测试模式状态(提前声明供 diagDump() 检查) */
|
||||
static bool s_continuityActive = false;
|
||||
|
||||
/* GPIOD/GPIOE 上属于数据总线的引脚掩码 */
|
||||
constexpr uint32_t kDataMaskD =
|
||||
(1U << 0) | (1U << 1) | (1U << 8) | (1U << 9) | (1U << 10) | (1U << 14) |
|
||||
(1U << 15);
|
||||
constexpr uint32_t kDataMaskE = 0xFF80U; /* PE7..PE15 */
|
||||
|
||||
constexpr uint32_t kPinCs = 7U; /* PD7 */
|
||||
constexpr uint32_t kPinRd = 4U; /* PD4 */
|
||||
constexpr uint32_t kPinWr = 5U; /* PD5 */
|
||||
constexpr uint32_t kPinA1 = 11U; /* PD11 */
|
||||
constexpr uint32_t kPinA2 = 12U; /* PD12 */
|
||||
|
||||
/* bbDelay/bbReadData 仅被 diagDump 使用,随之一起门控,避免默认关闭时
|
||||
* 触发 -Wunused-function;bbDriveData 被 continuityDrive 复用,保持常开。 */
|
||||
#if CONFIG_ESC_EXMC_DIAG
|
||||
void bbDelay() {
|
||||
for (volatile uint32_t i = 0U; i < 3000U; i++) {
|
||||
} /* ~40-80us @180MHz,远大于 HBI 时序要求 */
|
||||
}
|
||||
|
||||
uint16_t bbReadData() {
|
||||
uint32_t d = GPIO_ISTAT(GPIOD);
|
||||
uint32_t e = GPIO_ISTAT(GPIOE);
|
||||
uint16_t v = 0U;
|
||||
if (d & (1U << 14)) v |= (uint16_t)(1U << 0); /* D0 */
|
||||
if (d & (1U << 15)) v |= (uint16_t)(1U << 1); /* D1 */
|
||||
if (d & (1U << 0)) v |= (uint16_t)(1U << 2); /* D2 */
|
||||
if (d & (1U << 1)) v |= (uint16_t)(1U << 3); /* D3 */
|
||||
if (e & (1U << 7)) v |= (uint16_t)(1U << 4); /* D4 */
|
||||
if (e & (1U << 8)) v |= (uint16_t)(1U << 5); /* D5 */
|
||||
if (e & (1U << 9)) v |= (uint16_t)(1U << 6); /* D6 */
|
||||
if (e & (1U << 10)) v |= (uint16_t)(1U << 7); /* D7 */
|
||||
if (e & (1U << 11)) v |= (uint16_t)(1U << 8); /* D8 */
|
||||
if (e & (1U << 12)) v |= (uint16_t)(1U << 9); /* D9 */
|
||||
if (e & (1U << 13)) v |= (uint16_t)(1U << 10); /* D10 */
|
||||
if (e & (1U << 14)) v |= (uint16_t)(1U << 11); /* D11 */
|
||||
if (e & (1U << 15)) v |= (uint16_t)(1U << 12); /* D12 */
|
||||
if (d & (1U << 8)) v |= (uint16_t)(1U << 13); /* D13 */
|
||||
if (d & (1U << 9)) v |= (uint16_t)(1U << 14); /* D14 */
|
||||
if (d & (1U << 10)) v |= (uint16_t)(1U << 15); /* D15 */
|
||||
return v;
|
||||
}
|
||||
#endif /* CONFIG_ESC_EXMC_DIAG (bbDelay + bbReadData) */
|
||||
|
||||
void bbDriveData(uint16_t v) {
|
||||
uint32_t dset = 0U, dclr = 0U, eset = 0U, eclr = 0U;
|
||||
/* GPIOD 数据位 */
|
||||
const uint32_t dpins[7] = {14U, 15U, 0U, 1U, 8U, 9U, 10U};
|
||||
const int dbits[7] = {0, 1, 2, 3, 13, 14, 15};
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
if (v & (1U << dbits[i])) dset |= (1U << dpins[i]);
|
||||
else dclr |= (1U << dpins[i]);
|
||||
}
|
||||
/* GPIOE 数据位:D4..D12 = PE7..PE15 = 数据位 4..12 */
|
||||
for (int bit = 4; bit <= 12; ++bit) {
|
||||
uint32_t pin = (uint32_t)(bit + 3); /* bit4->PE7 ... bit12->PE15 */
|
||||
if (v & (1U << bit)) eset |= (1U << pin);
|
||||
else eclr |= (1U << pin);
|
||||
}
|
||||
if (dset) GPIO_BOP(GPIOD) = dset;
|
||||
if (dclr) GPIO_BC(GPIOD) = dclr;
|
||||
if (eset) GPIO_BOP(GPIOE) = eset;
|
||||
if (eclr) GPIO_BC(GPIOE) = eclr;
|
||||
}
|
||||
|
||||
} /* anonymous namespace */
|
||||
|
||||
bool EscExmcBus::s_ok = false;
|
||||
bool EscExmcBus::init() {
|
||||
s_ok = false;
|
||||
ECAT_LOGI("[ECAT][EXMC] init: enable RCU clocks\r\n");
|
||||
rcu_periph_clock_enable(RCU_GPIOD);
|
||||
rcu_periph_clock_enable(RCU_GPIOE);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
rcu_periph_clock_enable(RCU_EXMC);
|
||||
/* 先 deinit 再 init。BOOT 跳转前 exmc_norsram_disable + 关时钟,
|
||||
* SNCTL/SNTCFG 仍留着 BOOT 的配置;APP 若只 enable+init 可能叠在残留位上。
|
||||
* deinit 把 Region0 打回复位默认,再按下面参数重铺。 */
|
||||
exmc_norsram_deinit(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0);
|
||||
/* 配置 16-bit 数据线 D0-D15 为 AF 推挽 */
|
||||
gpio_init(CONFIG_EXMC_D0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D0_PIN);
|
||||
gpio_init(CONFIG_EXMC_D1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D1_PIN);
|
||||
gpio_init(CONFIG_EXMC_D2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D2_PIN);
|
||||
gpio_init(CONFIG_EXMC_D3_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D3_PIN);
|
||||
gpio_init(CONFIG_EXMC_D4_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D4_PIN);
|
||||
gpio_init(CONFIG_EXMC_D5_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D5_PIN);
|
||||
gpio_init(CONFIG_EXMC_D6_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D6_PIN);
|
||||
gpio_init(CONFIG_EXMC_D7_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D7_PIN);
|
||||
gpio_init(CONFIG_EXMC_D8_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D8_PIN);
|
||||
gpio_init(CONFIG_EXMC_D9_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D9_PIN);
|
||||
gpio_init(CONFIG_EXMC_D10_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D10_PIN);
|
||||
gpio_init(CONFIG_EXMC_D11_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D11_PIN);
|
||||
gpio_init(CONFIG_EXMC_D12_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D12_PIN);
|
||||
gpio_init(CONFIG_EXMC_D13_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D13_PIN);
|
||||
gpio_init(CONFIG_EXMC_D14_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D14_PIN);
|
||||
gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D15_PIN);
|
||||
|
||||
/* 控制线:A16 / A17 / NOE / NWE / NE0
|
||||
* A17(PD12) → LAN9252 A2 (Pin 29,IDX/DATA 选择);A16(PD11) → LAN9252 A1 (Pin 25)。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位:HBI_DATA_0 的 CPU 偏移是 0x40000/0x60000,
|
||||
* 见 esc_lan9252.cpp 头注释。 */
|
||||
gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_A16_PIN);
|
||||
gpio_init(CONFIG_EXMC_A17_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_A17_PIN);
|
||||
gpio_init(CONFIG_EXMC_NOE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NOE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NWE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NWE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NE0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NE0_PIN);
|
||||
ECAT_LOGI(
|
||||
"[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE0)\r\n");
|
||||
|
||||
/*
|
||||
* EXMC NOR/SRAM Region0:16-bit、非复用、异步写。
|
||||
* 注意:GD32E508Vx (LQFP100) 只有 NE0 (PD7) 一个 NOR/SRAM 片选引脚,
|
||||
* NE0 归属 Region0 (0x60000000, SNCTL0),必须用 Region0。
|
||||
* 时序(AHB=180MHz,1 HCLK ≈ 5.56ns):
|
||||
* LAN9252 HBI 最小读/写周期约 100ns → data_setup ≥ 18 HCLK。
|
||||
* asyn_* 字段单位为 HCLK 计数(库文档范围
|
||||
* address_setup 1..16,data_setup 2..256)。
|
||||
*/
|
||||
exmc_norsram_parameter_struct norsram_init;
|
||||
exmc_norsram_timing_parameter_struct rw_timing;
|
||||
exmc_norsram_timing_parameter_struct wr_timing;
|
||||
|
||||
norsram_init.read_write_timing = &rw_timing;
|
||||
norsram_init.write_timing = &wr_timing;
|
||||
exmc_norsram_struct_para_init(&norsram_init);
|
||||
|
||||
norsram_init.norsram_region = CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0;
|
||||
norsram_init.write_mode = EXMC_ASYN_WRITE;
|
||||
norsram_init.extended_mode = DISABLE;
|
||||
norsram_init.asyn_wait = DISABLE;
|
||||
norsram_init.nwait_signal = DISABLE;
|
||||
norsram_init.memory_write = ENABLE;
|
||||
norsram_init.nwait_config = EXMC_NWAIT_CONFIG_BEFORE;
|
||||
norsram_init.wrap_burst_mode = DISABLE;
|
||||
norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
|
||||
norsram_init.burst_mode = DISABLE;
|
||||
norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;
|
||||
norsram_init.memory_type = EXMC_MEMORY_TYPE_SRAM;
|
||||
norsram_init.address_data_mux = DISABLE;
|
||||
|
||||
/* 读/写共用时序(非 extended mode)。时序余量按 DS 表 9-4/9-5 独立核算,见下方 BUSLAT。 */
|
||||
rw_timing.asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
rw_timing.syn_data_latency = EXMC_DATALAT_2_CLK;
|
||||
rw_timing.syn_clk_division = EXMC_SYN_CLOCK_RATIO_DISABLE;
|
||||
/* BUSLAT: 4。DS 表 9-4/9-5 要求 trdrd/twrwr/twrrd ≥ 13ns,
|
||||
* 该间隔由 EXMC 在两次访问之间插入的总线空闲期承担 = (1 固有 + BUSLAT) HCLK。
|
||||
* BUSLAT=1 → 2 HCLK ≈ 11.1ns,低于 13ns 下限,仅靠 AHB 取指间隙偶然兜底:
|
||||
* 常温刚好可用,温度升高(传播延迟增大)/电压下降(驱动减弱)即越界,表现为
|
||||
* 偶发读到 0xFFFF 或错值。
|
||||
* BUSLAT=4 → 5 HCLK ≈ 27.8ns,余量 2.1 倍,覆盖全温区与电压波动。
|
||||
* 代价:每次 HBI 访问多 3 HCLK(16.7ns)。满载 1024B 过程数据按每 DWORD
|
||||
* 两次 16-bit 访问计,增加约 256×2×16.7ns ≈ 8.6us,相对原 218us 增幅 3.9%,
|
||||
* 4ms 周期占用从 5.4% 升至 5.7%,无实质影响。 */
|
||||
rw_timing.bus_latency = 4U;
|
||||
rw_timing.asyn_address_setuptime = 4U; /* ~22ns。DS 表9-4/9-5: trdrd/twrwr/twrrd
|
||||
≥13ns——选通间隔=ADDSET 相,2 HCLK≈11.1ns
|
||||
不达标(仅靠 AHB 间隙兜底),4 HCLK 稳过 */
|
||||
rw_timing.asyn_address_holdtime = 2U; /* min 2,DS tah≥0 */
|
||||
rw_timing.asyn_data_setuptime = 20U; /* ~111ns ≥ 100ns HBI */
|
||||
|
||||
wr_timing = rw_timing;
|
||||
|
||||
ECAT_LOGI("[ECAT][EXMC] init: applying NOR/SRAM Region0 params (16b, async, "
|
||||
"ADDSET=%u AHLD=%u DSET=%u BUSLAT=%u HCLK)\r\n",
|
||||
(unsigned)rw_timing.asyn_address_setuptime,
|
||||
(unsigned)rw_timing.asyn_address_holdtime,
|
||||
(unsigned)rw_timing.asyn_data_setuptime,
|
||||
(unsigned)rw_timing.bus_latency);
|
||||
|
||||
exmc_norsram_init(&norsram_init);
|
||||
exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0);
|
||||
|
||||
/* === 清除 reserved bit 7 ===
|
||||
* 现象:exmc_norsram_init 后 EXMC_SNCTL0=0x00001091,bit 7(reserved)=1。
|
||||
* DFP 复位值 0x000030DB 的 bit 7=1,可能被硬件保留或导致未定义行为。
|
||||
* NRW(bits 4-5)=01 已经是 16-bit,无需修改。
|
||||
* 修复:清除 bit 7,保持其他位不变。 */
|
||||
uint32_t snctl_before = EXMC_SNCTL0;
|
||||
EXMC_SNCTL0 = snctl_before & ~BIT(7);
|
||||
uint32_t snctl_after = EXMC_SNCTL0;
|
||||
ECAT_LOGI("[ECAT][EXMC] init: SNCTL0 fix: before=0x%08lX after=0x%08lX (bit7 reserved cleared)\r\n",
|
||||
(unsigned long)snctl_before, (unsigned long)snctl_after);
|
||||
|
||||
/* 时序寄存器回读:确认写入的 BUSLAT 真正落到硬件。
|
||||
* SNTCFG0 位域: [3:0]=ASET-1 [7:4]=AHLD-1 [15:8]=DSET-1 [19:16]=BUSLAT-1。
|
||||
* 期望 ASET=4/AHLD=2/DSET=20/BUSLAT=4 → 0x0313_0000|... 逐字段解码打印,
|
||||
* 避免只看整字导致的位域误读;BUSLAT 实际值 <4 说明库或硬件截断,须立即排查。 */
|
||||
{
|
||||
const uint32_t tcfg = EXMC_SNTCFG0;
|
||||
const unsigned aset = (unsigned)((tcfg & EXMC_SNTCFG_ASET) >> 0) + 1U;
|
||||
const unsigned ahld = (unsigned)((tcfg & EXMC_SNTCFG_AHLD) >> 4) + 1U;
|
||||
const unsigned dset = (unsigned)((tcfg & EXMC_SNTCFG_DSET) >> 8) + 1U;
|
||||
const unsigned buslat = (unsigned)((tcfg & EXMC_SNTCFG_BUSLAT) >> 16) + 1U;
|
||||
ECAT_LOGI("[ECAT][EXMC] init: SNTCFG0=0x%08lX -> ASET=%u AHLD=%u DSET=%u BUSLAT=%u\r\n",
|
||||
(unsigned long)tcfg, aset, ahld, dset, buslat);
|
||||
/* 5 HCLK @180MHz = 27.8ns,DS trdrd 下限 13ns → 需 BUSLAT>=2 才达标,取 4 留余量 */
|
||||
if (buslat < 4U) {
|
||||
ECAT_LOGI("[ECAT][EXMC] WARN: BUSLAT=%u < 4, trdrd 余量不足,"
|
||||
" 温漂/压降时会偶发读到 0xFFFF 或错值\r\n", buslat);
|
||||
}
|
||||
}
|
||||
|
||||
/* 总线探活:LAN9252 处于 16-bit HBI Indexed Mode,无法通过固定偏移直接读取。
|
||||
* 真实的芯片 ID / AL 状态校验由 EscLan9252::init() 通过 HBI_IDX_0/HBI_DATA_0
|
||||
* 间接访问完成。 注意:LAN9252 上电后第一次 Host Bus
|
||||
* 操作必须是读周期,否则后续写操作会被忽略; EscLan9252 在 init 中首先读取
|
||||
* CHIP_ID,满足该约束。 */
|
||||
ECAT_LOGI("[ECAT][EXMC] init: Region0 enabled, chip ID will be verified by "
|
||||
"ESC driver\r\n");
|
||||
|
||||
/* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ----
|
||||
* 默认关闭。硬件层已确认全通,探针仅换板/补焊排查浮空时经
|
||||
* CONFIG_ESC_EXMC_DIAG 临时开启(会拆 EXMC 引脚成 GPIO,正常运行不该跑)。 */
|
||||
#if CONFIG_ESC_EXMC_DIAG
|
||||
diagDump();
|
||||
#endif
|
||||
|
||||
s_ok = true;
|
||||
ECAT_LOGI("[ECAT][EXMC] init: OK, bus enabled\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。
|
||||
* 使用 volatile uint16_t* 转换避免依赖 CMSIS 的 __IO 宏。 */
|
||||
#define ESC_EXMC_REG16(offset) (*(volatile uint16_t*)(CONFIG_ESC_EXMC_BASE_ADDR + (offset)))
|
||||
/* ========================================================================
|
||||
* 硬件诊断: 精简版 —— 只保留核心探活 + bit-bang + 位级回显
|
||||
* 整段由 CONFIG_ESC_EXMC_DIAG 门控,默认不编译。bbDelay/bbReadData/
|
||||
* bbDriveData 等位拍辅助函数在上方匿名 namespace 中,continuityDrive 仍复用,保留。
|
||||
* ======================================================================== */
|
||||
#if CONFIG_ESC_EXMC_DIAG
|
||||
void EscExmcBus::diagDump() {
|
||||
/* EtherCAT 同步 ISR 激活期间禁止诊断:本函数会把 EXMC 数据/控制线切成 GPIO,
|
||||
* 并大量 printf,会周期性拆毁运行中的 EtherCAT 总线。 */
|
||||
if (EthercatSync::isActive()) {
|
||||
printf("[EXMC-DIAG] skipped: EtherCAT sync ISR active (bus in use)\r\n");
|
||||
return;
|
||||
}
|
||||
/* 通断测试模式活跃时跳过诊断,避免 20s 周期 probe 覆盖用户设置的电平 */
|
||||
if (s_continuityActive) {
|
||||
printf("[EXMC-DIAG] skipped: continuity test mode active\r\n");
|
||||
return;
|
||||
}
|
||||
printf("[EXMC-DIAG] --- bit-bang probe (bypass EXMC) ---\r\n");
|
||||
/* NRST 实际电平回读:输入通路常连引脚,输出态亦读真实电压。
|
||||
* 驱动高却 LOW => ECAT_NRST 被外部按低 => LAN9252 一直在复位态。 */
|
||||
printf("[EXMC-DIAG] NRST(PB10) reads %s\r\n",
|
||||
(gpio_input_bit_get(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN) == SET)
|
||||
? "HIGH" : "LOW");
|
||||
|
||||
void EscExmcBus::init()
|
||||
{
|
||||
/* 使能 GPIO 与 EXMC 时钟 */
|
||||
rcu_periph_clock_enable(RCU_GPIOD);
|
||||
rcu_periph_clock_enable(RCU_GPIOE);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
#ifdef EXMC_NORSRAM_REGION1
|
||||
rcu_periph_clock_enable(RCU_EXMC);
|
||||
/* 保存原 GPIO 配置 */
|
||||
uint32_t save_ctl0_d = GPIO_CTL0(GPIOD);
|
||||
uint32_t save_ctl1_d = GPIO_CTL1(GPIOD);
|
||||
uint32_t save_ctl0_e = GPIO_CTL0(GPIOE);
|
||||
uint32_t save_ctl1_e = GPIO_CTL1(GPIOE);
|
||||
|
||||
/* 配置 16-bit 数据线 D0-D15 为 AF 推挽 */
|
||||
gpio_init(CONFIG_EXMC_D0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D0_PIN);
|
||||
gpio_init(CONFIG_EXMC_D1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D1_PIN);
|
||||
gpio_init(CONFIG_EXMC_D2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D2_PIN);
|
||||
gpio_init(CONFIG_EXMC_D3_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D3_PIN);
|
||||
gpio_init(CONFIG_EXMC_D4_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D4_PIN);
|
||||
gpio_init(CONFIG_EXMC_D5_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D5_PIN);
|
||||
gpio_init(CONFIG_EXMC_D6_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D6_PIN);
|
||||
gpio_init(CONFIG_EXMC_D7_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D7_PIN);
|
||||
gpio_init(CONFIG_EXMC_D8_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D8_PIN);
|
||||
gpio_init(CONFIG_EXMC_D9_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D9_PIN);
|
||||
gpio_init(CONFIG_EXMC_D10_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D10_PIN);
|
||||
gpio_init(CONFIG_EXMC_D11_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D11_PIN);
|
||||
gpio_init(CONFIG_EXMC_D12_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D12_PIN);
|
||||
gpio_init(CONFIG_EXMC_D13_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D13_PIN);
|
||||
gpio_init(CONFIG_EXMC_D14_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D14_PIN);
|
||||
gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D15_PIN);
|
||||
/* 控制线 OUT_PP, 数据线 IN */
|
||||
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7|GPIO_PIN_11|GPIO_PIN_12);
|
||||
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_8|
|
||||
GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14|GPIO_PIN_15);
|
||||
gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|
|
||||
GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15);
|
||||
|
||||
/* 控制线:A16 / NOE / NWE / NE1 */
|
||||
gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_A16_PIN);
|
||||
gpio_init(CONFIG_EXMC_NOE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NOE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NWE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NWE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NE1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NE1_PIN);
|
||||
/* 空闲:CS/RD/WR 高, A1=A2=0 */
|
||||
GPIO_BOP(GPIOD) = (1U<<7)|(1U<<4)|(1U<<5);
|
||||
GPIO_BC(GPIOD) = (1U<<11)|(1U<<12);
|
||||
bbDelay();
|
||||
|
||||
/* EXMC NOR/SRAM Region1 时序配置(16-bit,非复用,异步) */
|
||||
exmc_norsram_parameter_struct norsram_init;
|
||||
exmc_norsram_struct_para_init(&norsram_init);
|
||||
norsram_init.norsram_region = CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1;
|
||||
norsram_init.write_mode = EXMC_ASYN_WRITE;
|
||||
norsram_init.read_mode = EXMC_ASYN_READ;
|
||||
norsram_init.nwait_mode = EXMC_NWAIT_DISABLE;
|
||||
norsram_init.asyn_wait = DISABLE;
|
||||
norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
|
||||
norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;
|
||||
/* 时序:按 LAN9252 HBI 要求配置 read/write wait state
|
||||
* LAN9252 HBI 最小 read/write cycle = 100ns。
|
||||
* AHB=180MHz(RCU_MODIFY_UP 强制 /1),1 HCLK = 5.6ns。
|
||||
* 6 HCLK = 33ns → 不满足,需 ≥ 18 HCLK 才能达到 100ns。
|
||||
* 当前 ESC 未启用(selfTest FAIL),EXMC 库未纳入编译,此参数仅占位。
|
||||
* 启用 ESC 时需安装 EXMC 库并实测调整 HCLK 数。 */
|
||||
norsram_init.read_latency = EXMC_DEN_ALE_TR_6_HCLK; // 占位,启用 ESC 时需调整为 ≥18 HCLK
|
||||
norsram_init.write_latency = EXMC_DEN_ALE_TR_6_HCLK; // 占位,启用 ESC 时需调整为 ≥18 HCLK
|
||||
exmc_norsram_init(&norsram_init);
|
||||
exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1);
|
||||
#else
|
||||
/* EXMC 外设库未安装:仅占位,启用 EtherCAT 时需先安装 EXMC 组件。 */
|
||||
#endif
|
||||
/* 读 IDX_0 (A2=0) —— 复位默认值 0x1234 */
|
||||
GPIO_BC(GPIOD) = (1U<<7); /* CS=低 */
|
||||
bbDelay();
|
||||
GPIO_BC(GPIOD) = (1U<<4); /* RD=低 */
|
||||
bbDelay();
|
||||
uint16_t bb_idx0 = bbReadData();
|
||||
GPIO_BOP(GPIOD) = (1U<<4); /* RD=高 */
|
||||
bbDelay();
|
||||
GPIO_BOP(GPIOD) = (1U<<7); /* CS=高 */
|
||||
bbDelay();
|
||||
|
||||
printf("[EXMC-DIAG] bit-bang IDX_0 = 0x%04X (expect 0x1234)\r\n",
|
||||
(unsigned)bb_idx0);
|
||||
|
||||
/* 判读 */
|
||||
if (bb_idx0 == 0x1234U) {
|
||||
printf("[EXMC-DIAG] *** LAN9252 ALIVE ***\r\n");
|
||||
} else if (bb_idx0 == 0x0000U || bb_idx0 == 0xFFFFU) {
|
||||
printf("[EXMC-DIAG] FAIL: bus floating (0x%04X) -> data line broken or chip not driving\r\n",
|
||||
(unsigned)bb_idx0);
|
||||
} else {
|
||||
printf("[EXMC-DIAG] UNEXPECTED: 0x%04X -> partial data line issue?\r\n",
|
||||
(unsigned)bb_idx0);
|
||||
}
|
||||
|
||||
/* --- 预驱动/释放/读周期三段采样: 区分"被动拉低"与"芯片主动驱动" ---
|
||||
* 背景: bit-bang 读到稳定 0x0000, 而纯浮空输入应保持上次残留值或漂移。
|
||||
* 两种解释: (a) 板上下拉/对地漏电(被动) (b) 芯片在 RD# 有效期驱动 0(主动)。
|
||||
* 序列: 驱动 pattern -> 切输入快采样(电容应保持) -> 慢采样(检测下拉)
|
||||
* -> 拉低 CS#+RD# 采样(芯片若响应应驱动总线, 例如 0x1234)。 */
|
||||
printf("[EXMC-DIAG] --- predrive/release/read probe ---\r\n");
|
||||
static const uint16_t probe_pats[2] = {0xAAAAU, 0x5555U};
|
||||
for (int p = 0; p < 2; ++p) {
|
||||
uint16_t pat = probe_pats[p];
|
||||
/* 1) 数据线推挽驱动 pattern(控制线保持空闲: CS/RD/WR 高, A1/A2 低) */
|
||||
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskE);
|
||||
bbDriveData(pat);
|
||||
bbDelay();
|
||||
/* 2) 切浮空输入, 快采样(自身电容应保持 pattern) */
|
||||
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE);
|
||||
for (volatile int d = 0; d < 50; d++) {} /* ~微秒级 */
|
||||
uint16_t s_fast = bbReadData();
|
||||
/* 3) 慢采样(约 40-80us 后: 若板上有下拉会漏向 0) */
|
||||
bbDelay();
|
||||
uint16_t s_slow = bbReadData();
|
||||
/* 4) 读周期采样(CS#+RD# 低: 芯片若驱动会在总线上给出真实数据) */
|
||||
GPIO_BC(GPIOD) = (1U << 7) | (1U << 4);
|
||||
bbDelay();
|
||||
uint16_t s_rd = bbReadData();
|
||||
GPIO_BOP(GPIOD) = (1U << 4) | (1U << 7);
|
||||
bbDelay();
|
||||
printf("[EXMC-DIAG] pat=0x%04X fast=0x%04X slow=0x%04X rd=0x%04X\r\n",
|
||||
(unsigned)pat, (unsigned)s_fast, (unsigned)s_slow,
|
||||
(unsigned)s_rd);
|
||||
}
|
||||
|
||||
/* 恢复 GPIO */
|
||||
GPIO_CTL0(GPIOD) = save_ctl0_d;
|
||||
GPIO_CTL1(GPIOD) = save_ctl1_d;
|
||||
GPIO_CTL0(GPIOE) = save_ctl0_e;
|
||||
GPIO_CTL1(GPIOE) = save_ctl1_e;
|
||||
|
||||
/* EXMC 寄存器回读(一行) */
|
||||
printf("[EXMC-DIAG] SNCTL0=0x%08lX SNTCFG0=0x%08lX\r\n",
|
||||
(unsigned long)EXMC_SNCTL0, (unsigned long)EXMC_SNTCFG0);
|
||||
|
||||
/* EXMC raw 读 + 位级回显(仅在未响应时) */
|
||||
uint16_t idx0_raw = ESC_EXMC_REG16(0x00000);
|
||||
printf("[EXMC-DIAG] EXMC raw IDX_0 = 0x%04X\r\n", (unsigned)idx0_raw);
|
||||
|
||||
if (idx0_raw != 0x1234U) {
|
||||
/* 位级回显: 写 4 个 pattern 看哪些位回显(浮空)哪些位被驱动 */
|
||||
const uint16_t patterns[4] = {0x0000U, 0xFFFFU, 0xAAAAU, 0x5555U};
|
||||
uint16_t readbacks[4] = {0};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ESC_EXMC_REG16(0x00000) = patterns[i];
|
||||
for (volatile int d = 0; d < 50; d++) {}
|
||||
readbacks[i] = ESC_EXMC_REG16(0x00000);
|
||||
}
|
||||
uint16_t echo_bits = 0U, driven_bits = 0U;
|
||||
for (int b = 0; b < 16; ++b) {
|
||||
uint16_t mask = (uint16_t)(1U << b);
|
||||
bool all_echo = true;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if ((readbacks[i] & mask) != (patterns[i] & mask)) { all_echo = false; break; }
|
||||
}
|
||||
if (all_echo) echo_bits |= mask; else driven_bits |= mask;
|
||||
}
|
||||
printf("[EXMC-DIAG] echo bits(disconnected): 0x%04X driven bits(connected): 0x%04X\r\n",
|
||||
(unsigned)echo_bits, (unsigned)driven_bits);
|
||||
|
||||
if (echo_bits == 0xFFFFU) {
|
||||
printf("[EXMC-DIAG] ALL 16 bits floating -> CS not reaching LAN9252 or chip dead\r\n");
|
||||
printf("[EXMC-DIAG] -> run continuityTest() to check data lines with multimeter\r\n");
|
||||
} else if (driven_bits == 0xFFFFU) {
|
||||
printf("[EXMC-DIAG] ALL 16 bits driven -> HBI alive but IDX_0!=0x1234 (unexpected)\r\n");
|
||||
} else {
|
||||
printf("[EXMC-DIAG] PARTIAL echo -> broken data lines: ");
|
||||
for (int b = 0; b < 16; ++b) if (echo_bits & (1U << b)) printf("D%d ", b);
|
||||
printf("\r\n");
|
||||
}
|
||||
} else {
|
||||
printf("[EXMC-DIAG] === HBI ALIVE ===\r\n");
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_ESC_EXMC_DIAG */
|
||||
|
||||
/* ========================================================================
|
||||
* 通断测试: 网页逐个 pattern 触发,GD32 驱动数据线/控制线输出已知电平,
|
||||
* 用户用万用表在 LAN9252 端测量对应引脚电压。
|
||||
* 设计:
|
||||
* - continuityDrive(action) 不延时,驱动完立即返回,电平保持到下次调用
|
||||
* - action=0(stop) 恢复 GPIO 为 EXMC AF_PP,回到正常工作模式
|
||||
* - 不点按钮时引脚是高阻输入态(安全),手碰到也不短路
|
||||
* 判据:
|
||||
* - GD32 输出高(3.3V)但 LAN9252 端测到 0V/浮空 → 断线
|
||||
* - GD32 输出低(0V)但 LAN9252 端测到 3.3V → 断线/短路
|
||||
* - 电压跟随 → 连线正常
|
||||
* ======================================================================== */
|
||||
|
||||
static uint32_t s_save_ctl0_d = 0, s_save_ctl1_d = 0;
|
||||
static uint32_t s_save_ctl0_e = 0, s_save_ctl1_e = 0;
|
||||
static uint32_t s_maskPrev = 0U;
|
||||
|
||||
static void continuityEnterTestMode() {
|
||||
if (s_continuityActive) return;
|
||||
s_save_ctl0_d = GPIO_CTL0(GPIOD);
|
||||
s_save_ctl1_d = GPIO_CTL1(GPIOD);
|
||||
s_save_ctl0_e = GPIO_CTL0(GPIOE);
|
||||
s_save_ctl1_e = GPIO_CTL1(GPIOE);
|
||||
/* 测试期屏蔽 SYNC0/ESC IRQ/SYNC1:ISR 的 HBI 访问落在已被切成 GPIO 态的
|
||||
* 总线上只会读回垃圾并错写 IDX,SSC 状态可能被污染。 */
|
||||
s_maskPrev = EscExmcBus::maskEnter();
|
||||
/* 全部数据线 + 控制线设为 OUT_PP */
|
||||
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
kDataMaskD | (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr) |
|
||||
(1U<<kPinA1) | (1U<<kPinA2));
|
||||
gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskE);
|
||||
/* 控制线空闲态: CS/RD/WR=高, A1=A2=低 */
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
|
||||
s_continuityActive = true;
|
||||
printf("[CONT] test mode entered (GPIO -> OUT_PP, ECAT ISRs masked)\r\n");
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
uint16_t EscExmcBus::read16(uint32_t offset)
|
||||
{
|
||||
return ESC_EXMC_REG16(offset);
|
||||
static void continuityExitTestMode() {
|
||||
if (!s_continuityActive) return;
|
||||
GPIO_CTL0(GPIOD) = s_save_ctl0_d;
|
||||
GPIO_CTL1(GPIOD) = s_save_ctl1_d;
|
||||
GPIO_CTL0(GPIOE) = s_save_ctl0_e;
|
||||
GPIO_CTL1(GPIOE) = s_save_ctl1_e;
|
||||
EscExmcBus::maskExit(s_maskPrev);
|
||||
s_continuityActive = false;
|
||||
printf("[CONT] test mode exited (GPIO -> AF_PP restored, ISRs unmasked)\r\n");
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EscExmcBus::write16(uint32_t offset, uint16_t data)
|
||||
{
|
||||
ESC_EXMC_REG16(offset) = data;
|
||||
uint8_t EscExmcBus::continuityDrive(uint8_t action) {
|
||||
switch (action) {
|
||||
case 0: /* stop */
|
||||
continuityExitTestMode();
|
||||
return 0;
|
||||
case 1: /* 0xFFFF ALL HIGH */
|
||||
case 2: /* 0x0000 ALL LOW */
|
||||
case 3: /* 0xAAAA ODD HIGH */
|
||||
case 4: { /* 0x5555 EVEN HIGH */
|
||||
continuityEnterTestMode();
|
||||
static const uint16_t pats[4] = {0xFFFF, 0x0000, 0xAAAA, 0x5555};
|
||||
uint16_t pat = pats[action - 1];
|
||||
bbDriveData(pat);
|
||||
/* 控制线恢复空闲态(避免上次控制线测试残留) */
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
|
||||
printf("[CONT] pattern 0x%04X driven\r\n", (unsigned)pat);
|
||||
return 0;
|
||||
}
|
||||
case 5: /* CS low */
|
||||
case 6: /* RD low */
|
||||
case 7: /* WR low */
|
||||
case 8: /* A1 high */
|
||||
case 9: { /* A2 high */
|
||||
continuityEnterTestMode();
|
||||
/* 数据线恢复空闲(避免上次数据 pattern 残留) */
|
||||
bbDriveData(0x0000);
|
||||
uint32_t pin;
|
||||
bool drive_high;
|
||||
switch (action) {
|
||||
case 5: pin = kPinCs; drive_high = false; break; /* CS low */
|
||||
case 6: pin = kPinRd; drive_high = false; break; /* RD low */
|
||||
case 7: pin = kPinWr; drive_high = false; break; /* WR low */
|
||||
case 8: pin = kPinA1; drive_high = true; break; /* A1 high */
|
||||
case 9: pin = kPinA2; drive_high = true; break; /* A2 high */
|
||||
default: return 0xEE;
|
||||
}
|
||||
/* 先把所有控制线恢复空闲,再驱动目标线 */
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
|
||||
if (drive_high) {
|
||||
GPIO_BOP(GPIOD) = (1U << pin);
|
||||
} else {
|
||||
GPIO_BC(GPIOD) = (1U << pin);
|
||||
}
|
||||
printf("[CONT] control line PD%lu driven %s\r\n",
|
||||
(unsigned long)pin, drive_high ? "HIGH" : "LOW");
|
||||
return 0;
|
||||
}
|
||||
case 10: { /* READ HOLD: CS#+RD# 持续拉低, 数据线浮空输入
|
||||
* 用途: 万用表在 LAN9252 芯片侧引脚测量读周期数据电平。
|
||||
* 判据: 芯片 HBI 活着 => 驱动 IDX_0=0x1234 (0001 0010 0011 0100b) =>
|
||||
* 置位 bit2/4/5/9/12 => D2(Pin12)/D4(Pin49)/D5(Pin50)/D9(Pin19)/
|
||||
* D12(Pin21)=3.3V, 其余数据脚=0V(全部为确定电平)。
|
||||
* 全部中电平/漂移 => 芯片未驱动(区分"芯片不驱动"与"数据线断线":
|
||||
* GD32 侧 rd 采样无法区分两者, 芯片侧测量可以)。 */
|
||||
continuityEnterTestMode(); /* 幂等:已活跃则保持;内部已屏蔽 ECAT ISR */
|
||||
/* 数据线浮空输入(交给芯片驱动),控制线保持 OUT_PP */
|
||||
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE);
|
||||
/* 持续读周期: WR 高, CS#+RD# 低, A1=A2=0 -> 读 IDX_0 */
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinA1) | (1U<<kPinA2);
|
||||
printf("[CONT] READ-HOLD: CS#+RD# held low, data Hi-Z\r\n");
|
||||
printf("[CONT] measure at chip: D2(P12)/D4(P49)/D5(P50)/D9(P19)/D12(P21)=3.3V & others 0V => alive(0x1234); all floating => not driving\r\n");
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0xEE;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) uint16_t
|
||||
EscExmcBus::read16(uint32_t offset) {
|
||||
return ESC_EXMC_REG16(offset);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void
|
||||
EscExmcBus::write16(uint32_t offset, uint16_t data) {
|
||||
ESC_EXMC_REG16(offset) = data;
|
||||
}
|
||||
|
||||
uint32_t EscExmcBus::maskEnter() {
|
||||
uint32_t prev = 0U;
|
||||
/* EXTI0=SYNC0(preempt=0) 无法被 BASEPRI 屏蔽,只能关 NVIC;
|
||||
* EXTI1=ESC IRQ / EXTI2=SYNC1 (均 preempt=1) 的 PDI_Isr/Sync1_Isr 同样做
|
||||
* HBI 两步事务访问,一并屏蔽——否则主循环事务会被 Sync1_Isr 的
|
||||
* PDO_InputMapping→HW_EscWriteIsr 打断而错址。 */
|
||||
if (NVIC_GetEnableIRQ(EXTI0_IRQn) != 0U) {
|
||||
prev |= 1U;
|
||||
NVIC_DisableIRQ(EXTI0_IRQn);
|
||||
}
|
||||
if (NVIC_GetEnableIRQ(EXTI1_IRQn) != 0U) {
|
||||
prev |= 2U;
|
||||
NVIC_DisableIRQ(EXTI1_IRQn);
|
||||
}
|
||||
if (NVIC_GetEnableIRQ(EXTI2_IRQn) != 0U) {
|
||||
prev |= 4U;
|
||||
NVIC_DisableIRQ(EXTI2_IRQn);
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
void EscExmcBus::maskExit(uint32_t prev) {
|
||||
/* 按保存的状态恢复:外层已屏蔽的(如 SSC DISABLE_ESC_INT)不会被误开 */
|
||||
if ((prev & 4U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI2_IRQn);
|
||||
}
|
||||
if ((prev & 2U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI1_IRQn);
|
||||
}
|
||||
if ((prev & 1U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI0_IRQn);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* @file esc_exmc_bus.h
|
||||
* @brief LAN9252 ESC 的 EXMC 16-bit 并行总线抽象接口。
|
||||
* @note 通过 EXMC NOR/SRAM Region1(NE1,基址 0x60000000)以内存映射方式访问 LAN9252。
|
||||
* 16-bit 数据总线 + A16 地址线 + NOE/NWE/NE1 控制线。
|
||||
* @note 通过 EXMC NOR/SRAM Region0(NE0,基址 0x60000000)以内存映射方式访问 LAN9252。
|
||||
* 16-bit 数据总线 + A16/A17 地址线 + NOE/NWE/NE0 控制线。
|
||||
* (LQFP100 只有 NE0=PD7 一个片选引脚,故用 Region0/SNCTL0)
|
||||
*/
|
||||
#ifndef COMM_ESC_EXMC_BUS_H
|
||||
#define COMM_ESC_EXMC_BUS_H
|
||||
@@ -16,14 +17,42 @@
|
||||
*/
|
||||
class EscExmcBus {
|
||||
public:
|
||||
/** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。 */
|
||||
static void init();
|
||||
/** @brief 初始化 EXMC NOR/SRAM Region0 与全部 GPIO(AF 复用)。
|
||||
* @return bool true 表示 EXMC Region0 已成功使能且首次回读探活通过。 */
|
||||
static bool init();
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
|
||||
static uint16_t read16(uint32_t offset);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
|
||||
static void write16(uint32_t offset, uint16_t data);
|
||||
|
||||
/** @brief init 后查询:EXMC Region0 是否使能且探活通过(供上层诊断汇总)。 */
|
||||
static bool isOk() { return s_ok; }
|
||||
|
||||
/** @brief HBI 事务互斥入口:屏蔽 EXTI0(SYNC0,preempt=0)+ EXTI1(ESC IRQ,preempt=1)。
|
||||
* @note HBI Indexed 是两步事务(先写 IDX 再读写 DATA),不可被其他 HBI 用户打断,
|
||||
* 而 SYNC0 优先级 0 无法用 BASEPRI 屏蔽,只能关 NVIC。返回先前使能状态,
|
||||
* 配合 maskExit() 使用;保存/恢复语义不会破坏外层(如 SSC DISABLE_ESC_INT)的屏蔽。
|
||||
* 仅供主循环上下文的 HBI 访问使用(HW_* 非 Isr 版本)。 */
|
||||
static uint32_t maskEnter();
|
||||
|
||||
/** @brief HBI 事务互斥出口:按 maskEnter() 返回的状态恢复 NVIC 使能。 */
|
||||
static void maskExit(uint32_t prev);
|
||||
|
||||
/** @brief 硬件诊断:bit-bang 探活 + 位级回显分析(精简版)。
|
||||
* @note EtherCAT 同步 ISR 激活期间自动跳过(诊断会把 EXMC 引脚切成 GPIO)。 */
|
||||
static void diagDump();
|
||||
|
||||
/** @brief 通断测试:逐个 pattern 驱动(不延时,保持到下次调用)。
|
||||
* @param action 0=stop(恢复AF_PP), 1=0xFFFF, 2=0x0000, 3=0xAAAA, 4=0x5555,
|
||||
* 5=CS低, 6=RD低, 7=WR低, 8=A1高, 9=A2高,
|
||||
* 10=读保持(CS#+RD#持续低+数据线Hi-Z,万用表在芯片侧量读周期电平)
|
||||
* @return 0=OK, 0xEE=无效 action */
|
||||
static uint8_t continuityDrive(uint8_t action);
|
||||
|
||||
private:
|
||||
static bool s_ok; /**< EXMC Region0 使能 + 探活结果(init 后置位)。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_EXMC_BUS_H */
|
||||
|
||||
@@ -1,144 +1,718 @@
|
||||
/**
|
||||
* @file esc_lan9252.cpp
|
||||
* @brief LAN9252 EtherCAT ESC 驱动实现。
|
||||
*
|
||||
* 硬件使用 16-bit HBI Indexed Address Mode:
|
||||
* - A2=0,A1=0 (偏移 0x00000) -> HBI_IDX_0
|
||||
* - A2=1,A1=0 (偏移 0x40000) -> HBI_DATA_0 低 16 位
|
||||
* - A2=1,A1=1 (偏移 0x60000) -> HBI_DATA_0 高 16 位
|
||||
*
|
||||
* 地址线换算(16-bit 数据宽度时 EXMC 内部将 HADDR[25:1] 接到 EXMC_A[24:0]):
|
||||
* 板线:LAN9252 A1(Pin25)→PD11=EXMC_A16,A2(Pin29)→PD12=EXMC_A17。
|
||||
* GD32E50x 用户手册 p869:16-bit 数据宽度时 EXMC 内部将 HADDR[25:1] 接到
|
||||
* EXMC_A[24:0],即 EXMC_A16=CPU 偏移 bit17(0x20000)、EXMC_A17=bit18(0x40000)。
|
||||
* LAN9252 手册 Table 9-3:A2=1 选 HBI_DATA_0,A1 选低/高半字。
|
||||
*
|
||||
* 访问模型(DS 12.11/表 12-13):
|
||||
* HBI 索引直接寻址的只有 LAN9252 系统 CSR(BYTE_TEST 0x064 等)与访问端口;
|
||||
* EtherCAT 核心 CSR(0x0000-0x0FFF)经 ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300)
|
||||
* 间接访问,过程 RAM(0x1000-0x1FFF)经 PRAM FIFO(0x000-0x03C + 0x308-0x314) 访问。
|
||||
* 核心 CSR 不在 HBI 直接空间,直读恒 0、写丢弃。
|
||||
*/
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/systick.h"
|
||||
#include <stdio.h>
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
#include "gd32e50x_gpio.h"
|
||||
#include "gd32e50x_rcu.h"
|
||||
|
||||
namespace {
|
||||
/* LAN9252 ESC 关键寄存器地址(字节偏移,EXMC 访问) */
|
||||
constexpr uint16_t LAN9252_REG_CHIP_ID_L = 0x0000U; /**< 芯片 ID 低 16 位(=0x4321)。 */
|
||||
constexpr uint16_t LAN9252_REG_CHIP_ID_H = 0x0002U; /**< 芯片 ID 高 16 位(=0x8765)。 */
|
||||
constexpr uint32_t LAN9252_CHIP_ID_VALUE = 0x87654321U; /**< 期望的 32-bit 芯片 ID。 */
|
||||
/* HBI Indexed Mode 下 EXMC 偏移(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16)
|
||||
* EXMC 16-bit 模式地址左移 1 位:A16 引脚=CPU 偏移 0x20000,A17 引脚=CPU 偏移 0x40000。 */
|
||||
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
|
||||
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) */
|
||||
|
||||
/* PDI 内存基址偏移(实际取决于 LAN9252 PDI 配置,这里为示意值) */
|
||||
constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x1000U;
|
||||
/* ESC 关键寄存器字节地址。
|
||||
* 注: LAN9252 的 HBI 在 EEPROM 空白/无效时依然工作(数据手册 9.5.4.2:
|
||||
* 先轮询 HBI_IDX_0 默认值 0x1234,再轮询 BYTE_TEST(0x064)=0x87654321)。
|
||||
* 芯片身份探针用 BYTE_TEST 寄存器(0x064),不是 ESC 0x0000(那是类型寄存器=0xC0)。 */
|
||||
constexpr uint16_t LAN9252_REG_BYTE_TEST = 0x0064U; /**< 字节顺序测试寄存器(默认 0x87654321,LAN9252 手册 16.1.2)。 */
|
||||
constexpr uint32_t LAN9252_BYTE_TEST_VALUE = 0x87654321U; /**< BYTE_TEST 默认值(小端序)。 */
|
||||
constexpr uint16_t ESC_REG_DL_STATUS = 0x0110U; /**< DL Status:bit0 = PDI operational / EEPROM loaded。 */
|
||||
constexpr uint16_t ESC_REG_EEP_CSR = 0x0502U; /**< EEPROM Control/Status:bit11=校验错,bit12=加载错(0=OK)。 */
|
||||
|
||||
/* LAN9252 系统中断寄存器(HBI 索引直接可达,DS 8.3 表 0.1)。
|
||||
* IRQ 引脚上电默认全关:IRQ_CFG.IRQ_EN=0 且 INT_EN.ECAT_INT_EN=0,
|
||||
* 不配这两个寄存器则 PB1 永远收不到中断,SSC 的 PDI_Isr 从不执行。 */
|
||||
constexpr uint16_t LAN9252_REG_IRQ_CFG = 0x0054U; /**< 中断配置:bit8=IRQ_EN,bit4=IRQ_POL,bit0=IRQ_TYPE。 */
|
||||
constexpr uint16_t LAN9252_REG_INT_STS = 0x0058U; /**< 中断状态(R/WC):bit0=ECAT_INT。 */
|
||||
constexpr uint16_t LAN9252_REG_INT_EN = 0x005CU; /**< 中断使能:bit0=ECAT_INT_EN。 */
|
||||
constexpr uint32_t IRQ_CFG_IRQ_EN = 1UL << 8; /**< IRQ 引脚输出总闸。 */
|
||||
constexpr uint32_t IRQ_CFG_IRQ_POL = 1UL << 4; /**< 1=高有效,0=低有效(开漏时忽略本位)。 */
|
||||
constexpr uint32_t IRQ_CFG_IRQ_TYPE = 1UL << 0; /**< 1=推挽,0=开漏(开漏强制低有效)。 */
|
||||
constexpr uint32_t INT_EN_ECAT_INT = 1UL << 0; /**< 允许 EtherCAT 事件驱动 IRQ 引脚。 */
|
||||
|
||||
/* ESC 过程数据看门狗(DS 12.14.41-43)。SII 不携带看门狗字段,芯片复位默认
|
||||
* divider=0x09C2(100µs)/WD Time PD=0x03E8(1000) 即 100ms——等于
|
||||
* 100ms 手动调试周期,SM2 每 100ms 被判定超时,SSC 以 0x001B 拖回 SAFEOP。 */
|
||||
constexpr uint16_t ESC_REG_WD_DIVIDER = 0x0400U; /**< 看门狗分频(100µs 基准,复位默认 0x09C2)。 */
|
||||
constexpr uint16_t ESC_REG_WD_TIME_PD = 0x0420U; /**< 过程数据看门狗步数(复位默认 0x03E8=100ms)。 */
|
||||
constexpr uint16_t ESC_WD_TIME_PD_STEPS = 10000U; /**< 10000 × 100µs = 1s。 */
|
||||
|
||||
/* LAN9252 直接映射的 ESC 访问端口(DS 表 12-13;经 HBI 索引直接读写) */
|
||||
constexpr uint16_t ECAT_PRAM_RD_DATA = 0x0000U; /**< PRAM 读 FIFO(0x00-0x1C 别名,功能相同,突发用) */
|
||||
constexpr uint16_t ECAT_PRAM_WR_DATA = 0x0020U; /**< PRAM 写 FIFO(0x20-0x3C 别名) */
|
||||
constexpr uint16_t ECAT_CSR_DATA_REG = 0x0300U; /**< ESC 核心 CSR 间接访问数据寄存器 */
|
||||
constexpr uint16_t ECAT_CSR_CMD_REG = 0x0304U; /**< ESC 核心 CSR 间接访问命令寄存器 */
|
||||
constexpr uint16_t ECAT_PRAM_RD_ADDR_LEN = 0x0308U; /**< PRAM 读地址/长度 */
|
||||
constexpr uint16_t ECAT_PRAM_RD_CMD = 0x030CU; /**< PRAM 读命令/状态 */
|
||||
constexpr uint16_t ECAT_PRAM_WR_ADDR_LEN = 0x0310U; /**< PRAM 写地址/长度 */
|
||||
constexpr uint16_t ECAT_PRAM_WR_CMD = 0x0314U; /**< PRAM 写命令/状态 */
|
||||
constexpr uint32_t CSR_BUSY = 1UL << 31; /**< CSR_CMD:写 1 启动,完成自清 */
|
||||
constexpr uint32_t CSR_RNW = 1UL << 30; /**< CSR_CMD:1=读 0=写 */
|
||||
constexpr uint32_t PRAM_BUSY = 1UL << 31; /**< PRAM_*_CMD:写 1 启动,完成自清 */
|
||||
constexpr uint32_t PRAM_AVAIL = 1UL << 0; /**< PRAM_*_CMD:数据可用(读)/空间可用(写) */
|
||||
constexpr uint32_t POLL_MAX = 2000UL; /**< busy/avail 轮询上限(~ms 级,防芯片异常时死等) */
|
||||
|
||||
/* 电源管理(DS 6.3/表 5-1):芯片级睡眠 D1-D3。
|
||||
* READY=0 期间仅 HW_CFG/PMT_CTRL/BYTE_TEST/RESET_CTL 四个寄存器可访问(DS 6.2.3),
|
||||
* 恰好覆盖睡眠进入与唤醒流程所需;PMT_CTRL 可在睡眠态安全读(DS 6.3.5.1 注)。 */
|
||||
constexpr uint16_t LAN9252_REG_HW_CFG = 0x0074U; /**< 硬件配置:bit27=READY(与 PMT_CTRL bit0 同源,DS 6.3.5.1)。 */
|
||||
constexpr uint16_t LAN9252_REG_PMT_CTRL = 0x0084U; /**< 功耗管理控制:bit0=READY,bit28=PM_SLEEP_EN,bit31:29=PM_MODE。 */
|
||||
constexpr uint32_t PMT_CTRL_READY = 1UL << 0;
|
||||
constexpr uint32_t PMT_CTRL_PM_MODE_MSK = 7UL << 29; /**< 001=D1 010=D2 011=D3。 */
|
||||
constexpr uint32_t PMT_CTRL_PM_SLEEP_EN = 1UL << 28; /**< 置 1 进入 PM_MODE 指定的睡眠档(须与 PM_MODE 分两次写)。 */
|
||||
|
||||
/* ESC 核心 MII 管理(ETG.1000.4 ESC 寄存器 0x0510-0x0514,经 csrRead/csrWrite 间接访问;
|
||||
* 表 5-1 证实 PHY 寄存器不在 HBI 直接空间)。用于 D3 前置的 PHY General Power Down
|
||||
* (DS 6.3.4:D3 要求两 PHY 先 General Power Down)与唤醒后恢复。 */
|
||||
constexpr uint16_t ESC_REG_MII_CTRL = 0x0510U; /**< bit0=CMD(写 1 启动自清) bit1=WRITE bit8=BUSY。 */
|
||||
constexpr uint16_t ESC_REG_MII_ADDR = 0x0512U; /**< bit4:0=PHY_ADDR bit15:5=REG_ADDR。 */
|
||||
constexpr uint16_t ESC_REG_MII_DATA = 0x0514U; /**< bit15:0=MII 数据。 */
|
||||
constexpr uint16_t MII_CTRL_CMD = 1U << 0;
|
||||
constexpr uint16_t MII_CTRL_WRITE = 1U << 1;
|
||||
constexpr uint16_t MII_CTRL_BUSY = 1U << 8;
|
||||
constexpr uint8_t MII_REG_BMCR = 0U; /**< IEEE 802.3 Basic Control 寄存器 0。 */
|
||||
constexpr uint16_t MII_BMCR_POWER_DOWN = 1U << 11; /**< PHY_PWR_DWN(DS 11.2.8)。 */
|
||||
constexpr uint8_t MII_PHY_ADDR_B = 1U; /**< PHY B 地址恒 1(DS 11.1.1);A 模式相关 0/2,运行期判定。 */
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void setHbiIndex(uint16_t addr)
|
||||
{
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, addr);
|
||||
}
|
||||
|
||||
void EscLan9252::init()
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
uint16_t readDataLow()
|
||||
{
|
||||
/* 使能 DWT 周期计数器,供 periodicIsr 执行时间测量 */
|
||||
return EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
uint16_t readDataHigh()
|
||||
{
|
||||
return EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void writeDataLow(uint16_t v)
|
||||
{
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void writeDataHigh(uint16_t v)
|
||||
{
|
||||
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
uint32_t readReg32(uint16_t addr)
|
||||
{
|
||||
setHbiIndex(addr);
|
||||
uint16_t lo = readDataLow();
|
||||
uint16_t hi = readDataHigh();
|
||||
return ((uint32_t)hi << 16) | lo;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void writeReg32(uint16_t addr, uint32_t v)
|
||||
{
|
||||
setHbiIndex(addr);
|
||||
writeDataLow((uint16_t)(v & 0xFFFFU));
|
||||
writeDataHigh((uint16_t)(v >> 16));
|
||||
}
|
||||
|
||||
/* ---- 电源管理 helper(仅 pmDrive 诊断路径使用) ---- */
|
||||
|
||||
/** @brief DWT CYCCNT 忙等微秒。
|
||||
* @note EXTI ISR 上下文(0xA2 在 AppSpiCommand::onCsRelease 内同步执行)禁止用
|
||||
* SysTickTimer::delayMs:其基于 SysTick 中断递增 s_ms,中断被 CS EXTI
|
||||
* 压住时计数冻结会死等。DWT CYCCNT 已由 init() 使能,纯寄存器读,ISR 安全。 */
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void pmBusyWaitUs(uint32_t us)
|
||||
{
|
||||
const uint32_t start = DWT->CYCCNT;
|
||||
const uint32_t cycles = us * (SystemCoreClock / 1000000U);
|
||||
while ((DWT->CYCCNT - start) < cycles) { }
|
||||
}
|
||||
|
||||
/** @brief ESC 核心 MII 管理事务(读/写一个 PHY 寄存器)。
|
||||
* @note 调用方必须已持 HBI 互斥(pmDrive 的 maskEnter 段内)。MII 事务典型 ~25µs
|
||||
* (2.5MHz MDIO × 32bit 帧结构),BUSY 轮询上限 POLL_MAX 防芯片异常死等。 */
|
||||
bool miiXfer(uint8_t phyAddr, uint8_t regAddr, bool write, uint16_t &data)
|
||||
{
|
||||
EscLan9252::csrWrite(ESC_REG_MII_ADDR, ((uint32_t)regAddr << 5) | phyAddr, 2U);
|
||||
if (write) {
|
||||
EscLan9252::csrWrite(ESC_REG_MII_DATA, data, 2U);
|
||||
}
|
||||
EscLan9252::csrWrite(ESC_REG_MII_CTRL,
|
||||
write ? (MII_CTRL_CMD | MII_CTRL_WRITE) : MII_CTRL_CMD, 2U);
|
||||
for (uint32_t i = 0U; i < POLL_MAX; i++) {
|
||||
if ((EscLan9252::csrRead(ESC_REG_MII_CTRL, 2U, false) & MII_CTRL_BUSY) == 0U) {
|
||||
if (!write) {
|
||||
data = (uint16_t)EscLan9252::csrRead(ESC_REG_MII_DATA, 2U, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @brief 置/清两 PHY 的 BMCR.PowerDown(D3 进入前置要求/唤醒后恢复)。
|
||||
* @return 全部事务成功 true。PHY A 地址模式相关(DS 11.1.1:0 或 2,随 chip_mode_strap),
|
||||
* 以 BMCR 读回有效性判定(0x0000/0xFFFF=无响应),结果缓存在静态变量。 */
|
||||
bool phySetPowerDown(bool down)
|
||||
{
|
||||
static uint8_t addrA = 0xFFU; /* 0xFF=未判定 */
|
||||
if (addrA == 0xFFU) {
|
||||
addrA = 0U;
|
||||
uint16_t probe = 0xFFFFU;
|
||||
if (!miiXfer(0U, MII_REG_BMCR, false, probe) ||
|
||||
probe == 0x0000U || probe == 0xFFFFU) {
|
||||
addrA = 2U;
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
const uint8_t addrs[2] = { addrA, MII_PHY_ADDR_B };
|
||||
for (uint8_t i = 0U; i < 2U; i++) {
|
||||
uint16_t bmcr = 0U;
|
||||
ok = miiXfer(addrs[i], MII_REG_BMCR, false, bmcr) && ok;
|
||||
if (down) {
|
||||
bmcr |= MII_BMCR_POWER_DOWN;
|
||||
} else {
|
||||
bmcr &= (uint16_t)~MII_BMCR_POWER_DOWN;
|
||||
}
|
||||
ok = miiXfer(addrs[i], MII_REG_BMCR, true, bmcr) && ok;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** @brief 把 PMT_CTRL 值打包成单字节状态:bit0=READY bit1=PM_SLEEP_EN bit3:2=PM_MODE。 */
|
||||
uint8_t pmPackStatus(uint32_t pmt)
|
||||
{
|
||||
return (uint8_t)(((pmt & PMT_CTRL_READY) ? 0x01U : 0U) |
|
||||
((pmt & PMT_CTRL_PM_SLEEP_EN) ? 0x02U : 0U) |
|
||||
(((pmt >> 29) & 3U) << 2));
|
||||
}
|
||||
} /* anonymous namespace */
|
||||
|
||||
bool EscLan9252::s_busReady = false;
|
||||
bool EscLan9252::s_coreReady = false;
|
||||
bool EscLan9252::s_resetDone = false;
|
||||
bool EscLan9252::s_pmPhyDown = false;
|
||||
bool EscLan9252::s_pmSuspended = false;
|
||||
|
||||
void EscLan9252::hardwareReset()
|
||||
{
|
||||
/* 受控复位 ECAT_NRST(PB10 → LAN9252 Pin11 RST#,SCH 第1页 Pin47)。
|
||||
* 手册 18.6.3 要求 tpurstd(电源稳定→RST#释放)≥25ms、trstia(低脉宽)≥200µs,
|
||||
* 且释放前时钟输入已稳定。固件执行到此点时上电早已完成、晶振已稳定,
|
||||
* 统一 30ms 低脉宽同时覆盖两个下限。RST# 释放瞬间重锁 CHIP_MODE0/1 strap
|
||||
* (LED_LINK1/2 经 R35/R36 10k 下拉 → 00b = 2 端口 HBI 模式)。
|
||||
* 释放后保持推挽输出高:空闲电平确定,并保留运行期软件复位手段;
|
||||
* GD32 自身复位期间引脚回浮空,由 R44 上拉维持高,不会误复位 ESC。 */
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* RST# 拉低 */
|
||||
SysTickTimer::delayMs(CONFIG_ESC_NRST_ASSERT_MS);
|
||||
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* 释放复位 */
|
||||
/* 释放后等 50ms:warm jump 后芯片不是冷上电,内部状态机/PHY
|
||||
* 重锁比 POR 慢;等太短时随后的 HBI 探针会读到启动中垃圾值。
|
||||
* 50ms 仍远小于 300ms 轮询窗,且 init 单次成本可接受。 */
|
||||
SysTickTimer::delayMs(50U);
|
||||
/* 回读实际电平:GD32 输入通路常连引脚,输出态也能读到真实电压。
|
||||
* 驱动高却读回低 => ECAT_NRST 网络被外部短路到地(或引脚损伤),
|
||||
* LAN9252 被按死在复位态 —— 直接解释 HBI 全浮空 + 晶振照振。 */
|
||||
const bool pinHigh = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
|
||||
CONFIG_ESC_NRST_PIN) == SET);
|
||||
printf("[ECAT][ESC] hardwareReset: RST# low %ums -> released, pin reads %s\r\n",
|
||||
(unsigned)CONFIG_ESC_NRST_ASSERT_MS,
|
||||
pinHigh ? "HIGH" : "*** LOW (net shorted to GND / pin damaged) ***");
|
||||
/* RST# 复位清掉 PM 睡眠态(芯片回 D0),挂起门控随之解除 */
|
||||
s_pmSuspended = false;
|
||||
}
|
||||
|
||||
uint8_t EscLan9252::nrstDrive(uint8_t action)
|
||||
{
|
||||
/* SPI 0xA2 action 20+ 的 NRST 手动诊断入口。
|
||||
* 返回值 = 实际引脚电平(0/1),非 OK 码;0xEE = 无效 action。 */
|
||||
switch (action) {
|
||||
case 0: /* 恢复自动态:推挽输出高(与 hardwareReset 释放后一致) */
|
||||
case 2: /* 持续拉高 */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
|
||||
/* 复位释放后芯片重走上电流程回 D0,睡眠挂起随之解除 */
|
||||
s_pmSuspended = false;
|
||||
break;
|
||||
case 1: /* 持续拉低(手动保持复位) */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
|
||||
break;
|
||||
case 3: /* 释放:浮空输入,网络交给 R44 10k 上拉 */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_IN_FLOATING, 0,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
s_pmSuspended = false;
|
||||
break;
|
||||
case 4: /* 只读回,不改驱动 */
|
||||
break;
|
||||
default:
|
||||
return 0xEE;
|
||||
}
|
||||
const bool level = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
|
||||
CONFIG_ESC_NRST_PIN) == SET);
|
||||
static const char *const kName[5] = {
|
||||
"auto(high)", "drive LOW", "drive HIGH", "float", "read-only"};
|
||||
printf("[ECAT][NRST] %s -> pin reads %s\r\n", kName[action],
|
||||
level ? "HIGH" : "LOW");
|
||||
return level ? 1U : 0U;
|
||||
}
|
||||
|
||||
uint8_t EscLan9252::pmDrive(uint8_t op)
|
||||
{
|
||||
if (op > 4U) {
|
||||
return 0xEEU;
|
||||
}
|
||||
if (!s_busReady) {
|
||||
printf("[ECAT][PM] op%u rejected: HBI bus not ready\r\n", op);
|
||||
return 0xE1U;
|
||||
}
|
||||
|
||||
/* 0xA2 在 CS EXTI ISR 上下文执行(AppSpiCommand::onCsRelease 同步调用)。
|
||||
* HBI Indexed 是两步事务(写 IDX→读写 DATA),而 SYNC0(EXTI0,preempt=0)是
|
||||
* 全系统最高优先级、BASEPRI 挡不住,与主循环 HW_* 路径相同必须关 NVIC:
|
||||
* maskEnter 屏蔽 EXTI0/EXTI1 并返回先前状态,ISR 里调用同样正确(纯 NVIC
|
||||
* 寄存器保存/恢复,挂起的中断在 maskExit 后补执行)。
|
||||
* 残余竞态窗口:CS EXTI 优先级更高,可抢占主循环持锁中的 HBI 事务半截——
|
||||
* 人工低频诊断可接受(continuityDrive 直接切走 EXMC 引脚破坏性更大)。 */
|
||||
const uint32_t mask = EscExmcBus::maskEnter();
|
||||
|
||||
uint8_t status;
|
||||
if (op == 0U) {
|
||||
/* 状态查询。注意:HBI 读事务本身就会唤醒睡眠中的芯片,
|
||||
* 本操作的语义 = "读状态(若在睡眠则主动打破睡眠)",非无侵入观测。 */
|
||||
const uint32_t pmt = readReg32(LAN9252_REG_PMT_CTRL);
|
||||
status = pmPackStatus(pmt);
|
||||
/* 读到 D0(SLEEP_EN=0):芯片不在睡眠,若门控仍挂起(如 op4 唤醒超时后
|
||||
* 芯片延迟自醒)则自愈解除,恢复主循环/ISR 的 HBI 访问。 */
|
||||
if ((status & 0x02U) == 0U && s_pmSuspended) {
|
||||
s_pmSuspended = false;
|
||||
printf("[ECAT][PM] status: chip in D0, pm suspend gate released\r\n");
|
||||
}
|
||||
printf("[ECAT][PM] status: PMT_CTRL=0x%08lX READY=%u SLEEP_EN=%u MODE=D%u\r\n",
|
||||
(unsigned long)pmt,
|
||||
(unsigned)((status >> 0) & 1U), (unsigned)((status >> 1) & 1U),
|
||||
(unsigned)((status >> 2) & 3U));
|
||||
} else if (op <= 3U) {
|
||||
/* 进入 D1/D2/D3。D3 最深(晶振/PLL 全关,仅手动唤醒),手册要求先把两个 PHY
|
||||
* 置 General Power Down,否则 PLL 在 D2 不真关/D3 直接不满足前置。 */
|
||||
if (op == 3U) {
|
||||
s_pmPhyDown = phySetPowerDown(true);
|
||||
printf("[ECAT][PM] D3 pre: both PHY power-down %s\r\n",
|
||||
s_pmPhyDown ? "OK" : "FAIL (entering anyway)");
|
||||
}
|
||||
uint32_t v = readReg32(LAN9252_REG_PMT_CTRL);
|
||||
v = (v & ~PMT_CTRL_PM_MODE_MSK) | ((uint32_t)op << 29);
|
||||
/* PM_MODE 与 PM_SLEEP_EN 必须分两次写(DS 6.3.5.1 注:同一写中改 MODE 置 EN 无效)。 */
|
||||
writeReg32(LAN9252_REG_PMT_CTRL, v);
|
||||
writeReg32(LAN9252_REG_PMT_CTRL, v | PMT_CTRL_PM_SLEEP_EN);
|
||||
/* 立即挂起全部 HBI 消费者(task_runner 停 MainLoop/守护/心跳,
|
||||
* ethercat_sync 三 ISR 清 pending 后直接返回)——本函数运行于 CS EXTI ISR,
|
||||
* 退出后主循环第一圈 ECAT_Main 读 0x220 就会把刚睡着的芯片叫醒;
|
||||
* 此前置位窗口内本 ISR 是唯一运行者(maskEnter 已屏 EXTI0/1),无竞态。 */
|
||||
s_pmSuspended = true;
|
||||
/* 进入睡眠后任何 HBI 访问(含读!)都会立即触发唤醒——
|
||||
* 手册 6.3.4.2 只说 HBI "write" 唤醒,但芯片实际按主机 CS 活动判定,
|
||||
* 写入后的读回验证会把芯片叫回 D0。
|
||||
* 故此处置信构造返回值,不再读回;睡眠态观测交给"读状态"(读即唤醒,
|
||||
* 语义=主动打破睡眠),页面轮询须在睡眠态暂停。 */
|
||||
status = (uint8_t)(0x02U | ((uint32_t)op << 2)); /* READY=0 | SLEEP_EN=1 | MODE=op */
|
||||
static const char *const kMode[4] = {"D0", "D1", "D2", "D3"};
|
||||
printf("[ECAT][PM] enter %s: entered, no read-back (HBI read wakes chip)\r\n",
|
||||
kMode[op]);
|
||||
printf("[ECAT][PM] WARNING: EtherCAT comms halted, wake with 0xA2 action 34\r\n");
|
||||
} else {
|
||||
/* 手动唤醒(DS 6.3.4.2):对 BYTE_TEST 做一次 HBI 写触发,随后轮询
|
||||
* BYTE_TEST=0x87654321(接口恢复)→ PMT_CTRL.READY=1(回 D0 且 PLL 稳定)。
|
||||
* 手册标称 ≤2ms,轮询 50µs 步进、上限 10ms——EXTI ISR 内不做长阻塞。 */
|
||||
writeReg32(LAN9252_REG_BYTE_TEST, LAN9252_BYTE_TEST_VALUE);
|
||||
bool awake = false;
|
||||
for (uint32_t i = 0U; i < 200U; i++) {
|
||||
pmBusyWaitUs(50U);
|
||||
if (readReg32(LAN9252_REG_BYTE_TEST) == LAN9252_BYTE_TEST_VALUE) {
|
||||
awake = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (awake) {
|
||||
for (uint32_t i = 0U; i < 200U; i++) {
|
||||
pmBusyWaitUs(50U);
|
||||
if ((readReg32(LAN9252_REG_PMT_CTRL) & PMT_CTRL_READY) != 0U) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
awake = (readReg32(LAN9252_REG_PMT_CTRL) & PMT_CTRL_READY) != 0U;
|
||||
}
|
||||
if (awake && s_pmPhyDown) {
|
||||
/* D3 唤醒后恢复 PHY 上电,链路重新自协商(数秒后 LINK 恢复)。 */
|
||||
const bool restored = phySetPowerDown(false);
|
||||
s_pmPhyDown = !restored;
|
||||
printf("[ECAT][PM] wake: PHY power-up %s (autoneg re-negotiating)\r\n",
|
||||
restored ? "OK" : "FAIL (retry action 34)");
|
||||
}
|
||||
if (awake) {
|
||||
/* 唤醒成功:解除 HBI 挂起,主循环/ISR 恢复运行。
|
||||
* 超时则保持挂起——芯片大概率仍睡着;若实际已醒,下一次 op0
|
||||
* 状态查询读到 D0 会自愈解除。 */
|
||||
s_pmSuspended = false;
|
||||
}
|
||||
status = awake ? pmPackStatus(readReg32(LAN9252_REG_PMT_CTRL)) : 0U;
|
||||
printf("[ECAT][PM] wake: %s -> status=0x%02X (READY=%u SLEEP_EN=%u)\r\n",
|
||||
awake ? "OK" : "TIMEOUT (10ms, poll action 30 / retry 34)",
|
||||
status, (unsigned)(status & 1U), (unsigned)((status >> 1) & 1U));
|
||||
}
|
||||
|
||||
EscExmcBus::maskExit(mask);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool EscLan9252::init()
|
||||
{
|
||||
s_busReady = false; /* EXMC+BYTE_TEST; warm 先探再定是否 RST# */
|
||||
s_coreReady = false; /* s_coreReady 语义 = EEPROM 加载完成、EtherCAT 核心使能 */
|
||||
|
||||
/* 使能 DWT 周期计数器,供 hw_gd32exmc/SSC 执行时间测量 */
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
EscExmcBus::init();
|
||||
/* 先起 EXMC 再谈复位。
|
||||
* warm jump:BOOT 刚用过 HBI,芯片活着;此时若先 RST# 再起 EXMC,等于
|
||||
* 把一条本来能通的总线按进复位重协商,一旦 RST# 释放时序不稳就整段假死。
|
||||
* cold:POR 后芯片也需要 EXMC 才谈得上探活。
|
||||
* 顺序固定为:EXMC → 无复位探 BYTE_TEST → 不通才 RST# 重来。 */
|
||||
if (!EscExmcBus::init()) {
|
||||
printf("[ECAT][ESC] init: FAIL, EXMC bus not ready (see [ECAT][EXMC] log)\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* TODO: 完整 ESC 寄存器配置(需 SSC 或等价协议栈代码,本 spec 不实现)
|
||||
* ============================================================
|
||||
* 当前仅完成 EXMC 总线初始化与芯片 ID 读取。要使 SYNC0 实际触发,还需配置:
|
||||
*
|
||||
* 1. SyncManager(SM)初始化:
|
||||
* - SM0/SM1:邮箱输入/输出(IN/OUT),地址 0x0520-0x0530
|
||||
* - SM2:TxPDO(输入),地址 0x0530,配置为 3-buffer 模式
|
||||
* - SM3:RxPDO(输出),地址 0x0540,配置为 3-buffer 模式
|
||||
* - 各 SM 的 StartAddr/Length/Control/Status/Activate 寄存器
|
||||
*
|
||||
* 2. FMMU(Fieldbus Memory Mapping Unit)配置:
|
||||
* - FMMU0:TxPDO 映射(逻辑地址 → 物理地址,读)
|
||||
* - FMMU1:RxPDO 映射(逻辑地址 → 物理地址,写)
|
||||
* - 地址 0x0580-0x0590
|
||||
*
|
||||
* 3. AL Control(0x0120):写入 0x08 请求 OP 状态(需主站配合完成状态迁移)
|
||||
*
|
||||
* 4. SYNC0 生成寄存器:
|
||||
* - 0x0981-0x0982:SYNC0 周期(cycle time,单位 ns)
|
||||
* - 0x0983:SYNC0 脉冲长度
|
||||
* - 0x0984:SYNC0 控制(使能/触发模式)
|
||||
* - 需主站 DC 同步后由 ESC 自动生成 SYNC0 脉冲
|
||||
*
|
||||
* 这些步骤通常由 EtherCAT SSC(Slave Stack Code)或等价协议栈代码完成,
|
||||
* 涉及 AL 状态机管理、邮箱通信、PDO 映射协商等复杂逻辑。
|
||||
* 本 spec 仅通过 EthercatSlave::isEscReady() 的 AL 状态守卫使
|
||||
* "SYNC0 不会触发"在运行期可观测,完整配置留待 SSC 集成 spec。
|
||||
* ============================================================ */
|
||||
uint16_t idx0 = 0U;
|
||||
uint32_t byteTest = 0U;
|
||||
|
||||
/* 第 0 轮:不复位直接探。warm 残值下 BOOT 刚写过 HBI,BYTE_TEST 应立刻可读。
|
||||
* BYTE_TEST 只证明通路;idx0==0x1234 才证明「芯片处于 POR 默认态」。
|
||||
* warm jump 后 idx0 常为 0x0054(BOOT 索引事务残值)——与刚上电不同。
|
||||
* 为了「跳转 = 上电」,残值时补一发 RST# 回到 POR,再走与 cold 相同的探活。 */
|
||||
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
|
||||
const bool porLike = (byteTest == LAN9252_BYTE_TEST_VALUE) && (idx0 == 0x1234U);
|
||||
if (porLike) {
|
||||
printf("[ECAT][ESC] probe: BYTE_TEST ok without RST# (idx0=0x%04X) — POR 态,跳过复位\r\n",
|
||||
(unsigned)idx0);
|
||||
s_resetDone = true;
|
||||
} else if (byteTest == LAN9252_BYTE_TEST_VALUE) {
|
||||
/* 通路活但不是 POR(典型 warm jump:idx0=0x0054)。补 RST# 对齐上电。 */
|
||||
printf("[ECAT][ESC] probe: BYTE_TEST ok but idx0=0x%04X != 0x1234 — warm 残值,"
|
||||
"补 RST# 对齐上电 POR\r\n", (unsigned)idx0);
|
||||
hardwareReset();
|
||||
s_resetDone = true;
|
||||
if (!EscExmcBus::init()) {
|
||||
printf("[ECAT][ESC] init: FAIL, EXMC re-init after RST# (warm residual)\r\n");
|
||||
return false;
|
||||
}
|
||||
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
|
||||
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
|
||||
printf("[ECAT][ESC] probe: BYTE_TEST=0x%08lX after warm RST#, fall back to multi-round\r\n",
|
||||
(unsigned long)byteTest);
|
||||
} else {
|
||||
printf("[ECAT][ESC] probe: warm RST# ok (idx0=0x%04X, BYTE_TEST=0x87654321) — 与上电同态\r\n",
|
||||
(unsigned)idx0);
|
||||
}
|
||||
}
|
||||
|
||||
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
|
||||
printf("[ECAT][ESC] probe: BYTE_TEST=0x%08lX idx0=0x%04X, 补 RST# 最多 3 轮\r\n",
|
||||
(unsigned long)byteTest, (unsigned)idx0);
|
||||
s_resetDone = false; /* 失败路径首轮强制 RST#,与上电后探不通时一致 */
|
||||
bool alive = false;
|
||||
for (uint8_t attempt = 0U; attempt < 3U; ++attempt) {
|
||||
if (!s_resetDone || attempt > 0U) {
|
||||
hardwareReset();
|
||||
s_resetDone = true;
|
||||
}
|
||||
/* 复位后重配 EXMC:BOOT 关时钟时 PD/PE 停在 AF,MCU 侧寄存器也可能残留 */
|
||||
if (!EscExmcBus::init()) {
|
||||
continue;
|
||||
}
|
||||
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
/* 仅在「像复位中/未驱动」时轮询 0x1234;warm 残值直接测 BYTE_TEST */
|
||||
const bool idxLooksIdle = (idx0 == 0x0000U) || (idx0 == 0xFFFFU);
|
||||
if (idxLooksIdle || idx0 == 0x1234U) {
|
||||
for (uint32_t waited = 0U;
|
||||
idx0 != 0x1234U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS;
|
||||
waited += CONFIG_ESC_HBI_POLL_STEP_MS) {
|
||||
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
|
||||
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
}
|
||||
}
|
||||
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
|
||||
if (byteTest == LAN9252_BYTE_TEST_VALUE) {
|
||||
alive = true;
|
||||
printf("[ECAT][ESC] probe#%u: BYTE_TEST ok (idx0=0x%04X)\r\n",
|
||||
(unsigned)(attempt + 1U), (unsigned)idx0);
|
||||
break;
|
||||
}
|
||||
printf("[ECAT][ESC] probe#%u fail (idx0=0x%04X byte_test=0x%08lX)\r\n",
|
||||
(unsigned)(attempt + 1U), (unsigned)idx0, (unsigned long)byteTest);
|
||||
}
|
||||
if (!alive) {
|
||||
printf("[ECAT][ESC] init: FAIL, BYTE_TEST=0x%08lX (expect 0x87654321), idx0=0x%04X -> LAN not driving bus\r\n",
|
||||
(unsigned long)byteTest, (unsigned)idx0);
|
||||
printf("[ECAT][ESC] hint: check CS(PD7) pulse, CHIP_MODE0/1 straps=00, RST#/power\r\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx0 != 0x1234U) {
|
||||
printf("[ECAT][ESC] init: WARN idx0=0x%04X != 0x1234, BYTE_TEST ok -> 复位态未知但通路可用,放行\r\n",
|
||||
(unsigned)idx0);
|
||||
}
|
||||
printf("[ECAT][ESC] init: OK, HBI alive (idx0=0x%04X, BYTE_TEST=0x87654321)\r\n",
|
||||
(unsigned)idx0);
|
||||
s_busReady = true;
|
||||
|
||||
/* 等待 PDI 就绪、EtherCAT 核心可交互。
|
||||
* DL_STATUS 属 EtherCAT 核心寄存器,不在 HBI 直接空间,必须经 CSR 间接端口读。
|
||||
* 判据:DL_STATUS(0x0110) bit0 = PDI operational / EEPROM loaded。
|
||||
* 超时意味着 PDI 真的未就绪(EEPROM 内容/I2C/strap 问题),此时 SSC 的
|
||||
* AL Event Mask/邮箱 SM 配置会落空,由 initial.cpp 按 isCoreReady() 门控跳过。 */
|
||||
uint16_t dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U, false);
|
||||
uint32_t waited = 0U;
|
||||
while ((dlStatus & 0x0001U) == 0U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS) {
|
||||
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
|
||||
waited += CONFIG_ESC_HBI_POLL_STEP_MS;
|
||||
dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U, false);
|
||||
}
|
||||
if ((dlStatus & 0x0001U) != 0U) {
|
||||
s_coreReady = true;
|
||||
printf("[ECAT][ESC] PDI operational, EtherCAT core ready (DL_STATUS=0x%04X, waited %ums)\r\n",
|
||||
(unsigned)dlStatus, (unsigned)waited);
|
||||
enableEcatIrqOutput();
|
||||
configPdWatchdog();
|
||||
} else {
|
||||
printf("[ECAT][ESC] init: FAIL, PDI not operational after %ums "
|
||||
"(DL_STATUS=0x%04X EEP_CSR=0x%04X)\r\n",
|
||||
(unsigned)CONFIG_ESC_HBI_POLL_TIMEOUT_MS, (unsigned)dlStatus,
|
||||
(unsigned)csrRead(ESC_REG_EEP_CSR, 2U, false));
|
||||
printf("[ECAT][ESC] hint: 用 tools/ecat/ecat_esc_regs.py 从网络侧读 0x0502 错误位区分 EEPROM/I2C/内容问题\r\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscLan9252::selfTest()
|
||||
/**
|
||||
* @brief 使能 LAN9252 的 IRQ 引脚输出,让 EtherCAT 事件真正到达 GD32 的 PB1。
|
||||
* @note IRQ_CFG(0x054)/INT_EN(0x05C) 都是 LAN9252 系统 CSR
|
||||
* (HBI 索引直接可达,不走 CSR 间接端口),复位默认值均为 0,即
|
||||
* IRQ_EN=0(引脚永久去断言) 且 ECAT_INT_EN=0(EtherCAT 事件不上报)。
|
||||
* 不配这两个寄存器,PDI_Isr 不会触发,SSC 永远等不到 PROCESS_OUTPUT_EVENT,
|
||||
* SAFEOP->OP 被无限挂起(表现为主站 "进 OP 失败 code=0x0000")。
|
||||
* 极性必须与 ethercat_sync.cpp 的 EXTI 配置一致:那里是上拉输入 +
|
||||
* 下降沿触发,故此处选推挽(IRQ_TYPE=1) + 低有效(IRQ_POL=0)。
|
||||
* 注意 AL Event Mask(0x204) 由 SSC 在状态迁移时自行维护,不在此处设置。
|
||||
*/
|
||||
void EscLan9252::configPdWatchdog()
|
||||
{
|
||||
return (readChipId() == LAN9252_CHIP_ID_VALUE);
|
||||
/* 过程数据看门狗 100ms(复位默认)→1s。
|
||||
* 主站侧手动单步/100ms 调试周期会触发 SM2 看门狗:SSC 在 CheckIfEcatError()
|
||||
* 读 0x0440 发现已过期,调 AL_ControlInd(STATE_SAFEOP, 0x001B) 把 OP 拖回 SAFEOP,
|
||||
* 表现为"100ms 周期全 WKC 错、10/50ms 正常"。分频保持默认 100µs,只延长步数;
|
||||
* 正常周期(≤50ms)远小于 1s,行为不变。 */
|
||||
csrWrite(ESC_REG_WD_TIME_PD, ESC_WD_TIME_PD_STEPS, 2U);
|
||||
const uint32_t rd = csrRead(ESC_REG_WD_TIME_PD, 2U, false);
|
||||
if ((rd & 0xFFFFU) == ESC_WD_TIME_PD_STEPS) {
|
||||
printf("[ECAT][ESC] PD watchdog = 1s (0x0420=%u x 100us, default was 100ms)\r\n",
|
||||
(unsigned)ESC_WD_TIME_PD_STEPS);
|
||||
} else {
|
||||
printf("[ECAT][ESC] WARN: PD watchdog writeback 0x%04lX != %u, 100ms 调试周期会被 0x001B 拖回 SAFEOP\r\n",
|
||||
(unsigned long)(rd & 0xFFFFU), (unsigned)ESC_WD_TIME_PD_STEPS);
|
||||
}
|
||||
}
|
||||
|
||||
void EscLan9252::enableEcatIrqOutput()
|
||||
{
|
||||
/* 先清掉可能悬留的状态位(R/WC:写 1 清零),避免使能瞬间就有一个陈旧电平。 */
|
||||
writeReg32(LAN9252_REG_INT_STS, INT_EN_ECAT_INT);
|
||||
/* 允许 EtherCAT 事件汇入 IRQ。 */
|
||||
writeReg32(LAN9252_REG_INT_EN, INT_EN_ECAT_INT);
|
||||
/* 打开引脚总闸:推挽 + 低有效(IRQ_POL=0 即不置该位),去断言间隔保持 0=禁用。 */
|
||||
writeReg32(LAN9252_REG_IRQ_CFG, IRQ_CFG_IRQ_EN | IRQ_CFG_IRQ_TYPE);
|
||||
|
||||
const uint32_t cfg = readReg32(LAN9252_REG_IRQ_CFG);
|
||||
const uint32_t en = readReg32(LAN9252_REG_INT_EN);
|
||||
if ((cfg & IRQ_CFG_IRQ_EN) != 0U && (en & INT_EN_ECAT_INT) != 0U) {
|
||||
printf("[ECAT][ESC] IRQ output enabled (IRQ_CFG=0x%08lX INT_EN=0x%08lX, push-pull active-low)\r\n",
|
||||
(unsigned long)cfg, (unsigned long)en);
|
||||
} else {
|
||||
printf("[ECAT][ESC] WARN: IRQ enable readback failed (IRQ_CFG=0x%08lX INT_EN=0x%08lX)"
|
||||
" -> PDI_Isr 不会触发,SAFEOP->OP 会卡住\r\n",
|
||||
(unsigned long)cfg, (unsigned long)en);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EscLan9252::csrRead(uint16_t addr, uint8_t size, bool verify)
|
||||
{
|
||||
/* DS 12.11.1:写 CSR_CMD(BUSY|R_nW|SIZE|ADDR) 启动,busy 自清后读 CSR_DATA;
|
||||
* 有效数据总在 CSR_DATA 低位,硬件处理字节对齐。
|
||||
* verify=true(默认):CSR_DATA 是锁存寄存器(非 FIFO),命令不变时连读两次必一致;
|
||||
* 不一致即本拍 EXMC 毛刺——错读可能让 GetSyncMan 把错误值缓存成邮箱几何
|
||||
* (→SSC 邮箱楔死,邮箱几何另有 mailbox.c 构建期常量兜底),或让
|
||||
* CheckSmSettings 以瞬态值打断状态转换。对错读整事务重跑一次;
|
||||
* 两轮仍不一致则交出末拍。
|
||||
* verify=false:单读路径,供诊断回读/MII busy 轮询/init 轮询等低风险
|
||||
* 调用方使用;SM/AL/邮箱等状态机关键寄存器必须保持默认双读。 */
|
||||
if (!verify) {
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
|
||||
n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
return readReg32(ECAT_CSR_DATA_REG);
|
||||
}
|
||||
for (uint8_t attempt = 0U; attempt < 2U; ++attempt)
|
||||
{
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
|
||||
n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
const uint32_t d1 = readReg32(ECAT_CSR_DATA_REG);
|
||||
const uint32_t d2 = readReg32(ECAT_CSR_DATA_REG);
|
||||
if (d1 == d2)
|
||||
{
|
||||
return d1;
|
||||
}
|
||||
}
|
||||
return readReg32(ECAT_CSR_DATA_REG);
|
||||
}
|
||||
|
||||
void EscLan9252::csrWrite(uint16_t addr, uint32_t data, uint8_t size)
|
||||
{
|
||||
/* DS 12.11.2:先写 CSR_DATA(数据在低位),再写 CSR_CMD(BUSY|SIZE|ADDR,R_nW=0)。 */
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
writeReg32(ECAT_CSR_DATA_REG, data);
|
||||
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | ((uint32_t)size << 16) | addr);
|
||||
n = POLL_MAX;
|
||||
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
|
||||
}
|
||||
|
||||
void EscLan9252::pramRead(uint16_t addr, uint8_t *buf, uint16_t len)
|
||||
{
|
||||
/* DS 12.11.3:写 ADDR_LEN 再置 BUSY 启动;引擎按 DWORD 向下对齐填充读 FIFO,
|
||||
* 首 DWORD 有效字节从 (addr&3) 起(表 12-7),末 DWORD 尾部无效(表 12-8)。
|
||||
* 别名口 0x00-0x1C 功能相同,轮换寻址即官方突发用法;每读满一个 DWORD 弹一项。
|
||||
*
|
||||
* 必须逐 DWORD 轮询并弹前查 AVAIL 位。CMD 的 AVAIL_CNT(bits[12:8])批量弹模式
|
||||
* 对短读不可靠:首 DWORD 新鲜、后续 DWORD 可能取到 FIFO 陈旧项。邮箱短读/探针
|
||||
* 类小传输只验证过"逐 DWORD 查 AVAIL"模式,不要改回批量弹 DWORD。 */
|
||||
if (len == 0U) {
|
||||
return;
|
||||
}
|
||||
writeReg32(ECAT_PRAM_RD_ADDR_LEN, ((uint32_t)len << 16) | addr);
|
||||
writeReg32(ECAT_PRAM_RD_CMD, PRAM_BUSY);
|
||||
uint16_t done = 0U;
|
||||
uint16_t alias = 0U;
|
||||
while (done < len) {
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_PRAM_RD_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
|
||||
if (n == 0U) {
|
||||
break; /* 芯片异常:避免死等,交出残缺数据 */
|
||||
}
|
||||
const uint32_t dw = readReg32((uint16_t)(ECAT_PRAM_RD_DATA + alias));
|
||||
alias = (uint16_t)((alias + 4U) & 0x1FU);
|
||||
const uint8_t lead = (done == 0U) ? (uint8_t)(addr & 3U) : 0U;
|
||||
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
|
||||
for (uint8_t i = 0U; i < take; ++i) {
|
||||
buf[done + i] = (uint8_t)(dw >> (8U * (lead + i)));
|
||||
}
|
||||
done = (uint16_t)(done + take);
|
||||
}
|
||||
}
|
||||
|
||||
void EscLan9252::pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
/* DS 12.11.4:写 ADDR_LEN 再置 BUSY 启动;首 DWORD 有效字节从 (addr&3) 起,
|
||||
* 引擎只取有效字节;别名口 0x20-0x3C 轮换;写满一个 DWORD 推一项。
|
||||
* 与 pramRead 同样逐 DWORD 轮询(推前必查 AVAIL 位),邮箱小 PDU 写同类风险。 */
|
||||
if (len == 0U) {
|
||||
return;
|
||||
}
|
||||
writeReg32(ECAT_PRAM_WR_ADDR_LEN, ((uint32_t)len << 16) | addr);
|
||||
writeReg32(ECAT_PRAM_WR_CMD, PRAM_BUSY);
|
||||
uint16_t done = 0U;
|
||||
uint16_t alias = 0U;
|
||||
while (done < len) {
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
|
||||
if (n == 0U) {
|
||||
break; /* 芯片异常:放弃剩余字节 */
|
||||
}
|
||||
const uint8_t lead = (done == 0U) ? (uint8_t)((uint16_t)(addr + done) & 3U) : 0U;
|
||||
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
|
||||
uint32_t dw = 0U;
|
||||
for (uint8_t i = 0U; i < take; ++i) {
|
||||
dw |= (uint32_t)buf[done + i] << (8U * (lead + i));
|
||||
}
|
||||
writeReg32((uint16_t)(ECAT_PRAM_WR_DATA + alias), dw);
|
||||
alias = (uint16_t)((alias + 4U) & 0x1FU);
|
||||
done = (uint16_t)(done + take);
|
||||
}
|
||||
uint32_t n = POLL_MAX;
|
||||
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_BUSY) != 0U && --n != 0U) {}
|
||||
}
|
||||
|
||||
uint32_t EscLan9252::readChipId()
|
||||
{
|
||||
/* INIT-ONLY:init 阶段中断未使能,但仍走 initOnly 路径保持一致性 */
|
||||
const uint16_t id_low = initOnlyReadReg(LAN9252_REG_CHIP_ID_L);
|
||||
const uint16_t id_high = initOnlyReadReg(LAN9252_REG_CHIP_ID_H);
|
||||
return ((uint32_t)id_high << 16) | id_low;
|
||||
/* INIT-ONLY:init 阶段中断未使能,直接读取 BYTE_TEST(32-bit) */
|
||||
return readReg32(LAN9252_REG_BYTE_TEST);
|
||||
}
|
||||
|
||||
uint16_t EscLan9252::readReg(uint16_t addr)
|
||||
{
|
||||
/* ESC 寄存器地址按字节寻址,EXMC 16-bit 访问需要地址左移 1 位 */
|
||||
return EscExmcBus::read16((uint32_t)addr << 1U);
|
||||
}
|
||||
|
||||
void EscLan9252::writeReg(uint16_t addr, uint16_t val)
|
||||
{
|
||||
EscExmcBus::write16((uint32_t)addr << 1U, val);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EscLan9252::readPdi(uint16_t offset, uint8_t *buf, size_t len)
|
||||
{
|
||||
if (buf == nullptr || len == 0U) {
|
||||
return;
|
||||
}
|
||||
/* PDI 按 16-bit 读取,非对齐字节需特殊处理(此处简化为按半字读取) */
|
||||
const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U;
|
||||
const size_t half_count = len / 2U;
|
||||
for (size_t i = 0U; i < half_count; ++i) {
|
||||
uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(i * 2U));
|
||||
buf[i * 2U] = (uint8_t)(w & 0xFFU);
|
||||
buf[i * 2U + 1] = (uint8_t)((w >> 8) & 0xFFU);
|
||||
}
|
||||
/* 处理奇数字节 */
|
||||
if ((len & 1U) != 0U) {
|
||||
uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(half_count * 2U));
|
||||
buf[half_count * 2U] = (uint8_t)(w & 0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EscLan9252::writePdi(uint16_t offset, const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (buf == nullptr || len == 0U) {
|
||||
return;
|
||||
}
|
||||
const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U;
|
||||
const size_t half_count = len / 2U;
|
||||
for (size_t i = 0U; i < half_count; ++i) {
|
||||
uint16_t w = (uint16_t)buf[i * 2U] | ((uint16_t)buf[i * 2U + 1] << 8);
|
||||
EscExmcBus::write16(exmc_offset + (uint32_t)(i * 2U), w);
|
||||
}
|
||||
if ((len & 1U) != 0U) {
|
||||
EscExmcBus::write16(exmc_offset + (uint32_t)(half_count * 2U), (uint16_t)buf[half_count * 2U]);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- initOnly* 接口:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用 ----
|
||||
* 运行期禁止调用;需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。
|
||||
* 注:不再使用 PRIMASK 关中断(违反项目硬约束"Critical sections MUST use BASEPRI")。 */
|
||||
uint16_t EscLan9252::initOnlyReadReg(uint16_t addr)
|
||||
{
|
||||
return readReg(addr);
|
||||
/* ESC 核心 CSR 经间接端口读(核心寄存器不在 HBI 直接空间,直读恒 0) */
|
||||
return (uint16_t)csrRead(addr, 2U);
|
||||
}
|
||||
|
||||
void EscLan9252::initOnlyWriteReg(uint16_t addr, uint16_t val)
|
||||
void EscLan9252::initOnlyWriteReg(uint16_t addr, uint16_t v)
|
||||
{
|
||||
writeReg(addr, val);
|
||||
}
|
||||
|
||||
void EscLan9252::initOnlyReadPdi(uint16_t offset, uint8_t *buf, size_t len)
|
||||
{
|
||||
readPdi(offset, buf, len);
|
||||
}
|
||||
|
||||
void EscLan9252::initOnlyWritePdi(uint16_t offset, const uint8_t *buf, size_t len)
|
||||
{
|
||||
writePdi(offset, buf, len);
|
||||
csrWrite(addr, v, 2U);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
/**
|
||||
* @file esc_lan9252.h
|
||||
* @brief LAN9252 EtherCAT 从站控制器(ESC)驱动接口。
|
||||
* @note 通过 EscExmcBus 16-bit 并行总线访问 LAN9252 寄存器与 PDI 内存。
|
||||
* @note 通过 EscExmcBus 16-bit 并行总线,以 HBI Indexed Address Mode 访问 LAN9252。
|
||||
* 访问模型(DS 12.11):
|
||||
* - HBI 直接地址空间:仅 LAN9252 系统 CSR(BYTE_TEST 0x064 等)+ 访问端口
|
||||
* (PRAM FIFO 0x000-0x03C、ECAT_CSR_DATA/CMD 0x300/0x304、PRAM_ADDR_LEN/CMD
|
||||
* 0x308-0x314)——经 HBI_IDX_0/HBI_DATA_0 直接读写;
|
||||
* - EtherCAT 核心 CSR(0x0000-0x0FFF:AL Status、SyncManager、EEPROM 接口等)
|
||||
* 不在直接空间,必须经 ECAT_CSR_CMD/CSR_DATA 间接访问(csrRead/csrWrite);
|
||||
* - 过程 RAM(0x1000-0x1FFF:邮箱/PDO)必须经 PRAM FIFO(pramRead/pramWrite)。
|
||||
*
|
||||
* 接口调用约束(避免 EXMC 总线争用):
|
||||
* - readReg/writeReg/readPdi/writePdi:ISR-only,仅供 preempt=0 的 periodicIsr() 调用,无锁。
|
||||
* - initOnlyReadReg/initOnlyWriteReg/initOnlyReadPdi/initOnlyWritePdi:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。
|
||||
*
|
||||
* PDI offset 语义:offset 参数为 SyncManager 内存区相对偏移,输入 PDO(TxPDO)与输出 PDO(RxPDO)必须使用不同 offset,参见 CONFIG_ESC_PDI_INPUT_OFFSET / CONFIG_ESC_PDI_OUTPUT_OFFSET。
|
||||
* - 运行期 ESC 寄存器/PDI 访问统一走 SSC 移植层 hw_gd32exmc.cpp 的 HW 系列函数
|
||||
* (主循环路径带 HBI 互斥守卫,ISR 路径由 ethercat_sync.cpp 包装器保证互斥)。
|
||||
* - initOnly 系列:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用
|
||||
* (如 EthercatSlave::init 的芯片 ID/AL 状态探针)。
|
||||
*/
|
||||
#ifndef COMM_ESC_LAN9252_H
|
||||
#define COMM_ESC_LAN9252_H
|
||||
@@ -21,14 +28,35 @@
|
||||
*/
|
||||
class EscLan9252 {
|
||||
public:
|
||||
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。 */
|
||||
static void init();
|
||||
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。
|
||||
* @note 内部先执行一次 hardwareReset() 受控复位(s_resetDone 门控,
|
||||
* 重复调用不会二次复位 SSC 已配置好的芯片),再探活 EXMC/HBI。
|
||||
* @return bool true 表示 EXMC 总线探活通过(可继续读取芯片 ID)。 */
|
||||
static bool init();
|
||||
|
||||
/**
|
||||
* @brief ESC 自检:读取芯片 ID 寄存器校验。
|
||||
* @return bool true 表示 ID 匹配(0x87654321),false 表示失败。
|
||||
*/
|
||||
static bool selfTest();
|
||||
/** @brief INIT-ONLY:ECAT_NRST(PB10)受控复位序列(拉低 CONFIG_ESC_NRST_ASSERT_MS 后释放)。
|
||||
* @note 满足 LAN9252 手册 18.6.3 tpurstd≥25ms/trstia≥200µs;RST# 释放瞬间
|
||||
* 重锁 CHIP_MODE strap。运行期调用会打断 EtherCAT 通信,仅供 init/诊断用。 */
|
||||
static void hardwareReset();
|
||||
|
||||
/** @brief 诊断:手动控制/回读 ECAT_NRST(PB10)实际电平(SPI 0xA2 action 20+ 转发到此)。
|
||||
* @param action 0=恢复自动态(推挽输出高) 1=持续拉低 2=持续拉高 3=浮空输入(交给 R44) 4=只读回
|
||||
* @return 实际引脚电平 0/1(GD32 输入通路常连引脚,输出态亦读真实电压);0xEE=无效 action。
|
||||
* 判读:action 1→2 切换后引脚电平不变 => 网络被外部短路或驱动器损伤;
|
||||
* 驱动高读回低 => ECAT_NRST 被按死在复位,LAS9252 永在复位态。 */
|
||||
static uint8_t nrstDrive(uint8_t action);
|
||||
|
||||
/** @brief 诊断:LAN9252 芯片级电源管理(休眠/唤醒,SPI 0xA2 action 30+ 转发到此)。
|
||||
* @param op 0=读状态 1=进 D1 2=进 D2 3=进 D3(先两 PHY General Power Down) 4=手动唤醒
|
||||
* @return 打包状态字节 bit0=READY bit1=PM_SLEEP_EN bit3:2=PM_MODE;
|
||||
* 唤醒 op 另有 1=成功 0=超时;0xEE=无效 op;0xE1=HBI 总线未就绪。
|
||||
* @note DS 6.3:PMT_CTRL(0x084) PM_MODE[31:29] 选档 + PM_SLEEP_EN(bit28) 进入,
|
||||
* 两字段必须分两次写。D1/D2/D3 下主机接口停摆、EtherCAT 通信中断(主站
|
||||
* wkc 报错),唤醒后 PM_MODE/PM_SLEEP_EN 自清、寄存器配置保留、READY 回高
|
||||
* (正常 ≤2ms)。D3 最深(晶振/PLL 全关)且手册要求先置两 PHY BMCR.PowerDown
|
||||
* (经 ESC 核心 MII 管理 0x0510 间接访问,PHY A 地址模式相关 0/2 运行期判定),
|
||||
* 唤醒后本函数自动恢复 PHY 上电,链路重新自协商需数秒。 */
|
||||
static uint8_t pmDrive(uint8_t op);
|
||||
|
||||
/**
|
||||
* @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。
|
||||
@@ -36,29 +64,72 @@ public:
|
||||
*/
|
||||
static uint32_t readChipId();
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static uint16_t readReg(uint16_t addr);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void writeReg(uint16_t addr, uint16_t val);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void readPdi(uint16_t offset, uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void writePdi(uint16_t offset, const uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SSC 移植层(HW_*)。
|
||||
* @note 经 CSR_CMD/CSR_DATA 间接端口读 ESC 核心 CSR
|
||||
* (EtherCAT 核心寄存器不在 HBI 直接地址空间,直读恒 0)。 */
|
||||
static uint16_t initOnlyReadReg(uint16_t addr);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
|
||||
static void initOnlyWriteReg(uint16_t addr, uint16_t val);
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用。 */
|
||||
static void initOnlyWriteReg(uint16_t addr, uint16_t v);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
|
||||
static void initOnlyReadPdi(uint16_t offset, uint8_t *buf, size_t len);
|
||||
/** @brief ESC 核心 CSR 间接读(ECAT_CSR_CMD/DATA 端口,0x0000-0x0FFF 地址空间)。
|
||||
* @note LAN9252 的 HBI/SPI 直接地址空间只有 LAN9252 系统 CSR 与访问端口
|
||||
* (DS 12.11/表 12-13),EtherCAT 核心寄存器(0x0000-0x0FFF)必须经
|
||||
* CSR_CMD(0x304)/CSR_DATA(0x300) 间接访问;过程 RAM(0x1000-0x1FFF)
|
||||
* 必须经 PRAM FIFO。size=1/2/4(size2 需偶地址,size4 需 4 字节对齐)。
|
||||
* 调用方负责 HBI 互斥(主循环 EscExmcBus::maskEnter / 已在 ISR 上下文)。
|
||||
* @param verify true(默认)=双读比对+不一致重试(防 EXMC 毛刺,SM/AL/邮箱
|
||||
* 等关键路径必须开);false=单读(诊断回读/MII busy 轮询/init 等低风险路径,
|
||||
* 省约一半 CSR 间接访问)。 */
|
||||
static uint32_t csrRead(uint16_t addr, uint8_t size, bool verify = true);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
|
||||
static void initOnlyWritePdi(uint16_t offset, const uint8_t *buf, size_t len);
|
||||
/** @brief ESC 核心 CSR 间接写(ECAT_CSR_CMD/DATA 端口)。约束同 csrRead。 */
|
||||
static void csrWrite(uint16_t addr, uint32_t data, uint8_t size);
|
||||
|
||||
/** @brief 过程 RAM(0x1000-0x1FFF)经 PRAM 读 FIFO 读取 len 字节到 buf。 */
|
||||
static void pramRead(uint16_t addr, uint8_t *buf, uint16_t len);
|
||||
|
||||
/** @brief 过程 RAM(0x1000-0x1FFF)经 PRAM 写 FIFO 写入 len 字节。 */
|
||||
static void pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len);
|
||||
|
||||
/** @brief EXMC 总线 + BYTE_TEST 往返探针通过(init 成功)后为 true。
|
||||
* @note HBI_IDX_0 复位默认值仅作诊断读数,不参与放行
|
||||
* (它只证明"芯片刚出过复位",证明不了 HBI 通路)。
|
||||
* 与 init() 返回值一致,可作运行期查询。 */
|
||||
static bool isBusReady() { return s_busReady; }
|
||||
|
||||
/** @brief EEPROM(SII) 加载完成、EtherCAT 核心使能。
|
||||
* @note false 时核心寄存器对 PDI 读全 0、写静默丢弃,SSC MainInit 会全部
|
||||
* 落空(AL Event Mask/邮箱 SM 配置写不进),调用方必须跳过 SSC 初始化。 */
|
||||
static bool isCoreReady() { return s_coreReady; }
|
||||
|
||||
/** @brief ESC 已被 pmDrive 置入 D1-D3 睡眠、所有 HBI 消费者必须静默。
|
||||
* @note 睡眠中任何 HBI 访问(含读)都会把芯片打回 D0。置位期间
|
||||
* task_runner 停调 MainLoop()/AL 守护/AL 心跳,ethercat_sync 三个 EXTI
|
||||
* ISR 清 pending 后直接返回——否则芯片进入睡眠后毫秒级就被主循环的
|
||||
* ECAT_Main(读 0x220)叫醒,根本睡不住。
|
||||
* 解除:pmDrive 唤醒/读到 D0、nrstDrive 释放复位、hardwareReset。
|
||||
* ISR 上下文安全:读一个 bool 静态成员,无锁。 */
|
||||
static bool pmSuspended() { return s_pmSuspended; }
|
||||
|
||||
private:
|
||||
/** @brief 使能 LAN9252 的 IRQ 引脚输出(IRQ_CFG 0x054 / INT_EN 0x05C)。
|
||||
* @note 由 init() 在 PDI/核心就绪后调用。两寄存器复位默认全 0,
|
||||
* 不配则 EtherCAT 事件永不到达 PB1、SSC 的 PDI_Isr 从不执行,
|
||||
* SAFEOP->OP 被无限挂起。 */
|
||||
static void enableEcatIrqOutput();
|
||||
|
||||
/** @brief 过程数据看门狗 100ms(复位默认)→1s。
|
||||
* @note 由 init() 在 PDI/核心就绪后调用。写 ESC_REG_WD_TIME_PD(0x0420)=10000
|
||||
* (100µs 分频保持默认)。不写则主站 100ms 手动调试周期触发 SM2 看门狗,
|
||||
* SSC 以 0x001B 拖回 SAFEOP。 */
|
||||
static void configPdWatchdog();
|
||||
|
||||
static bool s_busReady; /**< init 成功(EXMC+BYTE_TEST 往返;IDX 残值不挡)后置位。 */
|
||||
static bool s_coreReady; /**< EEPROM 加载完成、EtherCAT 核心使能后置位。 */
|
||||
static bool s_resetDone; /**< hardwareReset 已执行门控(防二次复位清掉 SSC 配置)。 */
|
||||
static bool s_pmPhyDown; /**< pmDrive 进 D3 时两 PHY 已被置 PowerDown,唤醒后需恢复。 */
|
||||
static bool s_pmSuspended; /**< ESC 睡眠挂起:置位期间全固件 HBI 消费者静默(见 pmSuspended)。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_LAN9252_H */
|
||||
#endif /* COMM_ESC_LAN9252_H */
|
||||
@@ -0,0 +1,501 @@
|
||||
/**
|
||||
* @file ethercat_can_bridge.cpp
|
||||
* @brief EtherCAT PDO ↔ CAN 转发桥实现。
|
||||
*/
|
||||
#include "comm/ethercat_can_bridge.h"
|
||||
#include "device/canfd.h"
|
||||
#include "device/motor_state.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "sys/systick.h"
|
||||
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header /* __DMB():seqlock 的编译器/总线屏障 */
|
||||
|
||||
#include <cstring>
|
||||
|
||||
volatile uint8_t EthercatCanBridge::s_lastSeq = 0U;
|
||||
volatile bool EthercatCanBridge::s_seqPrimed = false;
|
||||
volatile bool EthercatCanBridge::s_coldStartPending = false;
|
||||
volatile uint8_t EthercatCanBridge::s_lastTxResult = 0U;
|
||||
volatile uint16_t EthercatCanBridge::s_txCount = 0U;
|
||||
volatile uint16_t EthercatCanBridge::s_dropCount = 0U;
|
||||
volatile uint32_t EthercatCanBridge::s_lastTxId = 0U;
|
||||
volatile uint16_t EthercatCanBridge::s_rx00ffCount = 0U;
|
||||
volatile uint8_t EthercatCanBridge::s_outSeenSeq = 0U;
|
||||
|
||||
volatile bool EthercatCanBridge::s_rxValid = false;
|
||||
volatile bool EthercatCanBridge::s_rxPublished = false;
|
||||
volatile uint32_t EthercatCanBridge::s_xrxSeq = 0U;
|
||||
volatile uint8_t EthercatCanBridge::s_rxCh = 0U;
|
||||
volatile uint8_t EthercatCanBridge::s_rxDlc = 0U;
|
||||
volatile bool EthercatCanBridge::s_rxTruncated = false;
|
||||
volatile uint32_t EthercatCanBridge::s_rxId = 0U;
|
||||
volatile uint32_t EthercatCanBridge::s_rxCount = 0U;
|
||||
uint8_t EthercatCanBridge::s_rxData[kEcatCanXRxDataMax] = {0};
|
||||
|
||||
volatile bool EthercatCanBridge::s_verValid = false;
|
||||
volatile uint32_t EthercatCanBridge::s_verHoldUntilMs = 0U;
|
||||
uint8_t EthercatCanBridge::s_verCh = 0U;
|
||||
uint8_t EthercatCanBridge::s_verDlc = 0U;
|
||||
uint32_t EthercatCanBridge::s_verId = 0U;
|
||||
uint8_t EthercatCanBridge::s_verData[kEcatCanXRxDataMax] = {0};
|
||||
|
||||
namespace {
|
||||
/** 版本应答在 PDO 段3 的保持时长(ms)。主站 1ms 周期下足够被采到。 */
|
||||
constexpr uint32_t kVerHoldMs = 300U;
|
||||
|
||||
inline uint16_t canSubId(uint32_t id) { return static_cast<uint16_t>(id & 0xFFFFU); }
|
||||
} // namespace
|
||||
|
||||
uint8_t EthercatCanBridge::s_motShadow[kEcatMotRegionBytes] = {0};
|
||||
uint32_t EthercatCanBridge::s_motPubSeq[kMotorMaxNodes + 1U] = {0};
|
||||
uint8_t EthercatCanBridge::s_motPubOnline[kMotorMaxNodes + 1U] = {0};
|
||||
uint16_t EthercatCanBridge::s_motPubMask = 0U;
|
||||
uint8_t EthercatCanBridge::s_motPubCount = 0U;
|
||||
bool EthercatCanBridge::s_motShadowValid = false;
|
||||
|
||||
namespace {
|
||||
/** 小端读写:PDO 字节序按 EtherCAT 惯例固定小端。 */
|
||||
inline uint32_t rd32le(const uint8_t *p)
|
||||
{
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
inline void wr32le(uint8_t *p, uint32_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v); p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
inline void wr16le(uint8_t *p, uint16_t v)
|
||||
{
|
||||
p[0] = (uint8_t)(v); p[1] = (uint8_t)(v >> 8);
|
||||
}
|
||||
|
||||
/** 段 2 头 + count 槽在影子区中的总长度。 */
|
||||
inline uint16_t motRegionLen(uint8_t count)
|
||||
{
|
||||
return (uint16_t)(kEcatMotHdrSize + (uint16_t)count * kEcatMotSlotSize);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint8_t EthercatCanBridge::sendBatch(const uint8_t *out)
|
||||
{
|
||||
uint8_t want = out[ECAT_TXB_OFF_COUNT];
|
||||
if (want == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
if (want > kEcatTxBatchMax) {
|
||||
want = kEcatTxBatchMax;
|
||||
}
|
||||
const bool stopOnError = (out[ECAT_TXB_OFF_FLAGS] & 0x01U) != 0U;
|
||||
|
||||
uint8_t sent = 0U;
|
||||
uint16_t off = ECAT_TXB_OFF_ITEMS;
|
||||
|
||||
for (uint8_t i = 0U; i < want; ++i) {
|
||||
if ((uint16_t)(off + ECAT_FRM_HDR_SIZE) > kEcatPdoOutBytes) {
|
||||
break;
|
||||
}
|
||||
const uint8_t *f = &out[off];
|
||||
uint8_t dlc = f[ECAT_FRM_OFF_DLC];
|
||||
if (dlc > kEcatCanFdDataMax) {
|
||||
dlc = kEcatCanFdDataMax;
|
||||
}
|
||||
if ((uint16_t)(off + ECAT_FRM_HDR_SIZE + dlc) > kEcatPdoOutBytes) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t ch = f[ECAT_FRM_OFF_CH];
|
||||
CanFd::CanTxResult r;
|
||||
if (!CanFd::isChannelEnabled(ch)) {
|
||||
r = CanFd::CanTxResult::kErrInvalid;
|
||||
} else if (MotorOtaRelay::isChannelLocked(ch)) {
|
||||
/* 该通道正被电机 OTA 独占:PDO 运动帧一律不投递。
|
||||
* OTA 用单发模式(ARD=1),一旦挤进同一个邮箱就会 abort 掉在途的
|
||||
* 0x101 固件包,或让电机 BOOT 收到非预期长度直接回 0x05/0x06。
|
||||
* 计数与 lastTxResult 走 0xE7,主站侧能看到「被 OTA 挡了」而不是「邮箱满」。 */
|
||||
r = CanFd::CanTxResult::kErrOtaLocked;
|
||||
} else {
|
||||
r = CanFd::enqueueTx(ch, rd32le(&f[ECAT_FRM_OFF_ID]),
|
||||
&f[ECAT_FRM_HDR_SIZE], dlc);
|
||||
}
|
||||
|
||||
if (r == CanFd::CanTxResult::kOk) {
|
||||
++s_txCount;
|
||||
++sent;
|
||||
s_lastTxId = rd32le(&f[ECAT_FRM_OFF_ID]);
|
||||
} else {
|
||||
s_lastTxResult = (uint8_t)r;
|
||||
if (stopOnError) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
off = (uint16_t)(off + ECAT_FRM_HDR_SIZE + dlc);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 段 2:电机整轮密排。
|
||||
*
|
||||
* 1. 扫 MotorState,每个节点号 1..9 在 3 个通道里取 seq 最新的一条;
|
||||
* 2. 若节点集合与各节点 seq 均与上一拍发布相同 → 整段复用影子区(跳过重读);
|
||||
* 3. 否则按 node_id 升序密排写入,空节点不占位;seqlock 奇数(写入中)的槽
|
||||
* 本拍省略,下拍自愈 —— 不发半新半旧的 48B。
|
||||
*/
|
||||
uint8_t EthercatCanBridge::fillMotorRound(uint8_t *in)
|
||||
{
|
||||
const MotorStateTable &mt = MotorState::table();
|
||||
|
||||
/* ── 1. 每节点选一条最新槽 ── */
|
||||
const MotorSlot *picks[kMotorMaxNodes];
|
||||
uint8_t nodes[kMotorMaxNodes];
|
||||
uint8_t n = 0U;
|
||||
|
||||
for (uint8_t node = 1U; node <= kMotorMaxNodes; ++node) {
|
||||
const MotorSlot *best = nullptr;
|
||||
for (uint8_t ch = 0U; ch < kMotorChannels; ++ch) {
|
||||
const MotorSlot &s =
|
||||
mt.slots[(uint8_t)((node - 1U) * kMotorChannels + ch)];
|
||||
if (s.node_id == 0U) {
|
||||
continue;
|
||||
}
|
||||
if (best == nullptr || s.seq >= best->seq) {
|
||||
best = &s;
|
||||
}
|
||||
}
|
||||
if (best != nullptr) {
|
||||
picks[n] = best;
|
||||
nodes[n] = node;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 2. 判「上一轮已填充且未变」──
|
||||
* 节点集合一致 + 每节点 seq/online 一致 + 头字段(active/boot)一致 + 写侧
|
||||
* seqlock 偶数 → 复用影子。online 必须比:checkOffline 超时置 0 时不 bump
|
||||
* seq,只比 seq 会把「已离线」永远报成在线。 */
|
||||
bool unchanged = s_motShadowValid && (n == s_motPubCount)
|
||||
&& mt.active_count == s_motShadow[ECAT_MOTW_OFF_ACTIVE - ECAT_MOTW_OFF_COUNT]
|
||||
&& mt.boot_active == s_motShadow[ECAT_MOTW_OFF_BOOT - ECAT_MOTW_OFF_COUNT]
|
||||
&& s_motShadow[ECAT_MOTW_OFF_MAGIC - ECAT_MOTW_OFF_COUNT]
|
||||
== kEcatMotLayoutMagic;
|
||||
if (unchanged) {
|
||||
for (uint8_t i = 0U; i < n; ++i) {
|
||||
const uint8_t node = nodes[i];
|
||||
const MotorSlot &s = *picks[i];
|
||||
if ((s.wr_seq & 1U) != 0U
|
||||
|| s.seq != s_motPubSeq[node]
|
||||
|| s.online != s_motPubOnline[node]
|
||||
|| (s_motPubMask & (uint16_t)(1U << node)) == 0U) {
|
||||
unchanged = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uint16_t region = motRegionLen(unchanged ? s_motPubCount : n);
|
||||
|
||||
/* 集合+seq+online 未变:整段复用影子,避免 8kHz 下每拍重建密排区。
|
||||
* 头字段 ACTIVE/BOOT/MAGIC/GSEQ 在影子起点 COUNT 之后,一并复用。 */
|
||||
if (unchanged && s_motShadowValid && s_motPubCount > 0U) {
|
||||
memcpy(&in[ECAT_MOTW_OFF_COUNT], s_motShadow, region);
|
||||
return s_motPubCount;
|
||||
}
|
||||
|
||||
/* ── 3. 重建密排区 ── */
|
||||
in[ECAT_MOTW_OFF_ACTIVE] = mt.active_count;
|
||||
in[ECAT_MOTW_OFF_BOOT] = mt.boot_active;
|
||||
in[ECAT_MOTW_OFF_MAGIC] = kEcatMotLayoutMagic;
|
||||
wr32le(&in[ECAT_MOTW_OFF_GLOBAL_SEQ], mt.global_seq);
|
||||
|
||||
uint8_t w = 0U;
|
||||
uint16_t newMask = 0U;
|
||||
for (uint8_t i = 0U; i < n; ++i) {
|
||||
const MotorSlot &s = *picks[i];
|
||||
if ((s.wr_seq & 1U) != 0U) {
|
||||
continue; /* 写入中:本拍省略,下拍自愈 */
|
||||
}
|
||||
__DMB();
|
||||
|
||||
uint8_t *p = &in[ECAT_MOTW_OFF_SLOTS + (uint16_t)w * kEcatMotSlotSize];
|
||||
p[ECAT_MOT_OFF_NODE] = nodes[i];
|
||||
p[ECAT_MOT_OFF_CH] = s.channel;
|
||||
p[ECAT_MOT_OFF_ONLINE] = s.online;
|
||||
p[ECAT_MOT_OFF_DLC] = s.dlc;
|
||||
wr32le(&p[ECAT_MOT_OFF_SEQ], s.seq);
|
||||
memcpy(&p[ECAT_MOT_OFF_STATUS], s.data, kMotorStatusDlc);
|
||||
|
||||
s_motPubSeq[nodes[i]] = s.seq;
|
||||
s_motPubOnline[nodes[i]] = s.online;
|
||||
newMask |= (uint16_t)(1U << nodes[i]);
|
||||
++w;
|
||||
}
|
||||
|
||||
in[ECAT_MOTW_OFF_COUNT] = w;
|
||||
|
||||
/* 更新影子:下次集合+seq 未变时整段复用 */
|
||||
memcpy(s_motShadow, &in[ECAT_MOTW_OFF_COUNT], motRegionLen(w));
|
||||
s_motPubMask = newMask;
|
||||
s_motPubCount = w;
|
||||
s_motShadowValid = true;
|
||||
return w;
|
||||
}
|
||||
|
||||
bool EthercatCanBridge::peekOutput(const uint8_t *head)
|
||||
{
|
||||
/* 输出探针:12B 头里 SEQ@0 / FLAGS。seq 无变化沿且无清统计请求时返回 false,
|
||||
* PDO_OutputMapping 只补读 SM2 末字,跳过 384B 全量读。
|
||||
* 恒 true 会让 8kHz 每拍都全量读,单拍被 PRAM 打爆(6kHz 即掉速)。 */
|
||||
if (head == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t seq = head[ECAT_CAN_TX_OFF_SEQ];
|
||||
|
||||
if (!s_seqPrimed) {
|
||||
s_seqPrimed = true;
|
||||
s_lastSeq = seq;
|
||||
s_coldStartPending = true;
|
||||
return true; /* 冷启动首包强制全量读,避免漏掉首帧命令 */
|
||||
}
|
||||
|
||||
if (seq != s_lastSeq) {
|
||||
return true;
|
||||
}
|
||||
return (head[ECAT_CAN_TX_OFF_FLAGS] & 0x01U) != 0U;
|
||||
}
|
||||
|
||||
void EthercatCanBridge::resetSequenceState()
|
||||
{
|
||||
s_seqPrimed = false;
|
||||
s_lastSeq = 0U;
|
||||
s_coldStartPending = false;
|
||||
}
|
||||
|
||||
void EthercatCanBridge::processOutput(const uint8_t *out)
|
||||
{
|
||||
if (out == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t seq = out[ECAT_CAN_TX_OFF_SEQ];
|
||||
s_outSeenSeq = seq; /* 诊断:processOutput 本拍看到的输出 seq */
|
||||
|
||||
if (!s_seqPrimed) {
|
||||
s_seqPrimed = true;
|
||||
s_coldStartPending = true;
|
||||
}
|
||||
|
||||
/* 无论 seq 是否变化,优先处理非 seq 驱动的命令(FLAGS bit0 清统计) */
|
||||
if ((out[ECAT_CAN_TX_OFF_FLAGS] & 0x01U) != 0U) {
|
||||
s_txCount = 0U;
|
||||
s_dropCount = 0U;
|
||||
s_rxCount = 0U;
|
||||
s_rx00ffCount = 0U;
|
||||
s_rxPublished = true;
|
||||
}
|
||||
|
||||
const uint8_t dlcRaw = out[ECAT_CAN_TX_OFF_DLC];
|
||||
const uint8_t batchCount = out[ECAT_TXB_OFF_COUNT];
|
||||
const bool hasPayload = (dlcRaw > 0U) || (batchCount > 0U);
|
||||
|
||||
bool executeCommand = false;
|
||||
if (s_coldStartPending) {
|
||||
s_coldStartPending = false;
|
||||
/* 冷启动:有载荷就发(不依赖 s_lastSeq 是否已被探针碰过) */
|
||||
if (hasPayload) {
|
||||
executeCommand = true;
|
||||
} else {
|
||||
s_lastSeq = seq;
|
||||
return;
|
||||
}
|
||||
} else if (seq != s_lastSeq) {
|
||||
executeCommand = true;
|
||||
} else if (hasPayload && seq != s_lastSeq) {
|
||||
executeCommand = true;
|
||||
}
|
||||
|
||||
if (!executeCommand) {
|
||||
return;
|
||||
}
|
||||
s_lastSeq = seq;
|
||||
|
||||
const uint8_t ch = out[ECAT_CAN_TX_OFF_CH];
|
||||
uint8_t dlc = dlcRaw;
|
||||
if (dlc > kEcatCanTxDataMax) {
|
||||
dlc = kEcatCanTxDataMax;
|
||||
}
|
||||
|
||||
if (dlc > 0U) {
|
||||
if (!CanFd::isChannelEnabled(ch)) {
|
||||
s_lastTxResult = (uint8_t)CanFd::CanTxResult::kErrInvalid;
|
||||
} else if (MotorOtaRelay::isChannelLocked(ch)) {
|
||||
/* 同 sendBatch:OTA 独占通道,周期运动帧不投邮箱(0xE7 上报主站)。 */
|
||||
s_lastTxResult = (uint8_t)CanFd::CanTxResult::kErrOtaLocked;
|
||||
} else {
|
||||
const CanFd::CanTxResult r =
|
||||
CanFd::enqueueTx(ch, rd32le(&out[ECAT_CAN_TX_OFF_ID]),
|
||||
&out[ECAT_CAN_TX_OFF_DATA], dlc);
|
||||
s_lastTxResult = (uint8_t)r;
|
||||
if (r == CanFd::CanTxResult::kOk) {
|
||||
++s_txCount;
|
||||
s_lastTxId = rd32le(&out[ECAT_CAN_TX_OFF_ID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(void)sendBatch(out);
|
||||
}
|
||||
|
||||
void EthercatCanBridge::buildInput(uint8_t *in)
|
||||
{
|
||||
if (in == nullptr) {
|
||||
return;
|
||||
}
|
||||
/* 不整包 memset(600):8kHz 下每拍 Process 都会走到这里,600B 清零
|
||||
* 与后续覆盖写重复。各段自己写满有效域;未用槽由 fillMotorRound
|
||||
* 按 count 截断,主站只迭代 count,空洞区可为残值。 */
|
||||
|
||||
/* ── 段 1(0..31):桥状态 + 短快照 ── */
|
||||
in[ECAT_CAN_RX_OFF_ACK] = s_lastSeq;
|
||||
in[ECAT_CAN_RX_OFF_TXRES] = s_lastTxResult;
|
||||
wr16le(&in[ECAT_CAN_RX_OFF_TXCNT], s_txCount);
|
||||
wr16le(&in[ECAT_CAN_RX_OFF_DROP], s_dropCount);
|
||||
wr32le(&in[ECAT_CAN_RX_OFF_RXCNT], s_rxCount);
|
||||
wr32le(&in[ECAT_CAN_RX_OFF_TXTXID], s_lastTxId);
|
||||
wr16le(&in[ECAT_CAN_RX_OFF_RX00FF], s_rx00ffCount);
|
||||
|
||||
const bool xrxStable = ((s_xrxSeq & 1U) == 0U);
|
||||
|
||||
if (xrxStable && s_rxValid) {
|
||||
uint8_t flags = 0x01U;
|
||||
if (s_rxTruncated) {
|
||||
flags |= 0x02U;
|
||||
}
|
||||
in[ECAT_CAN_RX_OFF_CH] = s_rxCh;
|
||||
in[ECAT_CAN_RX_OFF_DLC] = s_rxDlc;
|
||||
in[ECAT_CAN_RX_OFF_FLAGS] = flags;
|
||||
wr32le(&in[ECAT_CAN_RX_OFF_ID], s_rxId);
|
||||
memcpy(&in[ECAT_CAN_RX_OFF_DATA], s_rxData, kEcatCanRxDataMax);
|
||||
s_rxPublished = true;
|
||||
} else {
|
||||
in[ECAT_CAN_RX_OFF_FLAGS] = 0U;
|
||||
in[ECAT_CAN_RX_OFF_CH] = 0U;
|
||||
in[ECAT_CAN_RX_OFF_DLC] = 0U;
|
||||
}
|
||||
|
||||
const uint8_t ch = s_rxValid ? s_rxCh : 0U;
|
||||
const uint32_t backlog = CanFd::isChannelEnabled(ch) ? CanFd::txQueueBacklog(ch) : 0U;
|
||||
in[ECAT_CAN_RX_OFF_BACKLOG] = (backlog > 255U) ? 255U : (uint8_t)backlog;
|
||||
|
||||
/* ── 段 2(32..543):电机整轮密排 ── */
|
||||
(void)fillMotorRound(in);
|
||||
|
||||
/* ── 段 3(544..583):长帧快照(通用,不专宠版本) ──
|
||||
* 语义 =「最近一帧非 STATUS 长帧」:CONFIG/版本/节点ID 谁后到谁上。
|
||||
* STATUS(0x0100) 由段2 密排承载,不进本槽(否则 48B STATUS 会淹没一切)。
|
||||
* 例外:仅当刚收到 0x00FF 的短粘滞窗口内,优先摆版本应答,
|
||||
* 给主站一个稳定采样窗;窗口结束后立刻交还给 CONFIG 等,避免其它长帧失效。 */
|
||||
if (xrxStable) {
|
||||
const uint32_t nowMs = SysTickTimer::getMs();
|
||||
const bool stickyLive = s_verValid && static_cast<int32_t>(nowMs - s_verHoldUntilMs) < 0;
|
||||
/* 段3 RSVD@547:processOutput 看到的输出 seq(诊断用,不参与解析) */
|
||||
in[ECAT_XRX_OFF_RSVD] = s_outSeenSeq;
|
||||
if (stickyLive) {
|
||||
in[ECAT_XRX_OFF_CH] = s_verCh;
|
||||
in[ECAT_XRX_OFF_DLC] = s_verDlc;
|
||||
in[ECAT_XRX_OFF_FLAGS] = 0x01U;
|
||||
wr32le(&in[ECAT_XRX_OFF_ID], s_verId);
|
||||
memcpy(&in[ECAT_XRX_OFF_DATA], s_verData, kEcatCanXRxDataMax);
|
||||
} else if (s_rxValid) {
|
||||
in[ECAT_XRX_OFF_CH] = s_rxCh;
|
||||
in[ECAT_XRX_OFF_DLC] = s_rxDlc;
|
||||
in[ECAT_XRX_OFF_FLAGS] = 0x01U;
|
||||
wr32le(&in[ECAT_XRX_OFF_ID], s_rxId);
|
||||
memcpy(&in[ECAT_XRX_OFF_DATA], s_rxData, kEcatCanXRxDataMax);
|
||||
}
|
||||
}
|
||||
|
||||
/* 段 5 由 EthercatPdo::fillDiagTail 写。 */
|
||||
}
|
||||
|
||||
void EthercatCanBridge::onCanFrame(uint8_t channel, uint32_t id,
|
||||
const uint8_t *data, uint8_t length)
|
||||
{
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* STATUS(0x0100) 由 MotorState 段2 密排承载,不写入 xrx。
|
||||
* 原注释本就如此约定;实现曾漏过滤,导致高频 STATUS 盖掉 CONFIG/版本。 */
|
||||
if (canSubId(id) == 0x0100U) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t copyLen = length;
|
||||
if (copyLen > kEcatCanXRxDataMax) {
|
||||
copyLen = kEcatCanXRxDataMax;
|
||||
}
|
||||
|
||||
/* ── 快照 seqlock:奇数=正在写,消费者本拍跳过段 1/段 3 ── */
|
||||
++s_xrxSeq;
|
||||
__DMB();
|
||||
|
||||
if (s_rxValid && !s_rxPublished) {
|
||||
++s_dropCount;
|
||||
}
|
||||
|
||||
memcpy(s_rxData, data, copyLen);
|
||||
if (copyLen < kEcatCanXRxDataMax) {
|
||||
memset(&s_rxData[copyLen], 0, (size_t)(kEcatCanXRxDataMax - copyLen));
|
||||
}
|
||||
|
||||
s_rxCh = channel;
|
||||
s_rxId = id;
|
||||
s_rxDlc = length;
|
||||
s_rxTruncated = (length > kEcatCanRxDataMax);
|
||||
++s_rxCount;
|
||||
s_rxValid = true;
|
||||
s_rxPublished = false;
|
||||
|
||||
if (canSubId(id) == 0x00FFU) {
|
||||
if (s_rx00ffCount < 0xFFFFU) {
|
||||
++s_rx00ffCount;
|
||||
}
|
||||
}
|
||||
|
||||
/* 版本应答:只做短粘滞,供主站在窗口内采到;不长期霸占 xrx */
|
||||
if (canSubId(id) == 0x00FFU) {
|
||||
s_verCh = channel;
|
||||
s_verDlc = length;
|
||||
s_verId = id;
|
||||
memcpy(s_verData, data, copyLen);
|
||||
if (copyLen < kEcatCanXRxDataMax) {
|
||||
memset(&s_verData[copyLen], 0, (size_t)(kEcatCanXRxDataMax - copyLen));
|
||||
}
|
||||
s_verHoldUntilMs = SysTickTimer::getMs() + kVerHoldMs;
|
||||
s_verValid = true;
|
||||
}
|
||||
|
||||
__DMB();
|
||||
++s_xrxSeq;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void EcatCan_ProcessOutput(const uint8_t *out)
|
||||
{
|
||||
EthercatCanBridge::processOutput(out);
|
||||
}
|
||||
|
||||
void EcatCan_BuildInput(uint8_t *in)
|
||||
{
|
||||
EthercatCanBridge::buildInput(in);
|
||||
}
|
||||
|
||||
void EcatCan_ResetSequenceState(void)
|
||||
{
|
||||
EthercatCanBridge::resetSequenceState();
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* @file ethercat_can_bridge.h
|
||||
* @brief EtherCAT PDO ↔ CAN 转发桥。
|
||||
*
|
||||
* 输出 PDO(0x7000, 主站→从站, 384B) = 待发送的 CAN 帧(数据区 32B)
|
||||
* 输入 PDO(0x6000, 从站→主站, 600B) = 桥状态 + 电机整轮状态 + 长帧快照 + 诊断
|
||||
*
|
||||
* 发送用**序号沿触发**:仅当 seq 字节变化时才真正发一帧。PDO 是周期性刷新的,
|
||||
* 若按「内容非空即发」会把同一帧每个周期重发一次;seq 变化沿是 PDO 里表达
|
||||
* 「一次性事件」的标准做法,主站只需每次发送前把 seq 加一。
|
||||
*
|
||||
* RX 侧不能自己去 CanFd::consumeRxFrame() —— 那个队列已有唯一消费者
|
||||
* TaskRunner::taskCanRxParse(),两个消费者会互相抢帧,OTA relay / 电机状态
|
||||
* 会随机丢帧。改为由 taskCanRxParse() 的分发链回调 onCanFrame() 投递。
|
||||
*
|
||||
* 段 2 语义:一帧 = 总线电机整轮状态。
|
||||
*
|
||||
* · 每一拍输入 PDO 携带**总线上已识别电机的完整一轮状态**(最多 9 个);
|
||||
* · **空槽不发送**:密排,count = 本拍实际打包的电机数;
|
||||
* · 主站按 count 迭代,每槽自带 node_id,自动识别,无需 win_base 滑窗;
|
||||
* · 版本/CONFIG 走段 1/段 3「最近一帧」快照;
|
||||
* · 上一轮已打包且 seq 未变的电机,本轮跳过 MotorState 重读与 48B 拷贝,
|
||||
* 直接复用影子区(s_motShadow)。
|
||||
*
|
||||
* 输入 PDO 总长 600B;主站解码必须与段 2/3 偏移一致
|
||||
* (tools/ecat 的 decode_input / transport-ecat.js 等)。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_CAN_BRIDGE_H
|
||||
#define COMM_ETHERCAT_CAN_BRIDGE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "comm/ethercat_pdo_size.h"
|
||||
#include "device/motor_state.h" /* kMotorMaxNodes / kMotorChannels */
|
||||
|
||||
/** 输出 PDO(主站→从站, 384B)段 1(0..47):单帧发送。 */
|
||||
enum : uint8_t {
|
||||
ECAT_CAN_TX_OFF_SEQ = 0U, /**< 发送序号:变化沿触发一次发送(单帧+批量一起) */
|
||||
ECAT_CAN_TX_OFF_CH = 1U, /**< 目标通道 0/1/2 */
|
||||
ECAT_CAN_TX_OFF_DLC = 2U, /**< 数据长度 0..32;0 表示本次不发单帧(只发批量) */
|
||||
ECAT_CAN_TX_OFF_FLAGS = 3U, /**< bit0=1 请求清零统计;其余保留 */
|
||||
ECAT_CAN_TX_OFF_ID = 4U, /**< CAN ID,小端 uint32(29 位扩展帧) */
|
||||
ECAT_CAN_TX_OFF_WIN = 8U, /**< 当前忽略(电机整轮密排,无滑窗);保留占位 */
|
||||
/* 9..15 保留:让数据区落在 16 字节边界 */
|
||||
ECAT_CAN_TX_OFF_DATA = 16U, /**< 数据区起点,32 字节(一帧电机 CMD) */
|
||||
};
|
||||
|
||||
/** 输出 PDO 段 2(48..383):CANFD 批量发送区。 */
|
||||
enum : uint16_t {
|
||||
ECAT_TXB_OFF_COUNT = 48U, /**< 本次要发的帧数 0..kEcatTxBatchMax;0=不发批量 */
|
||||
ECAT_TXB_OFF_FLAGS = 49U, /**< bit0=1 遇错即停(默认继续发完并各自记结果) */
|
||||
ECAT_TXB_OFF_RSVD = 50U, /**< 保留 2B,让帧数组从 52 起、每帧 8B 头对齐 */
|
||||
ECAT_TXB_OFF_ITEMS = 52U, /**< 帧数组起点,每项 (8B 头 + dlc 数据) 紧密排列 */
|
||||
};
|
||||
|
||||
/** 批量帧项内部布局(头 8 字节,之后紧跟 dlc 字节数据)。收发两向共用。 */
|
||||
enum : uint8_t {
|
||||
ECAT_FRM_OFF_CH = 0U, /**< 通道 0/1/2 */
|
||||
ECAT_FRM_OFF_DLC = 1U, /**< 数据长度 0..64(CANFD 全量) */
|
||||
ECAT_FRM_OFF_FLAGS = 2U, /**< 发送向:保留; 接收向 bit0=有效 */
|
||||
ECAT_FRM_OFF_RES = 3U, /**< 发送向:从站回填该帧发送结果; 接收向:保留 */
|
||||
ECAT_FRM_OFF_ID = 4U, /**< CAN ID,小端 uint32 */
|
||||
ECAT_FRM_HDR_SIZE = 8U, /**< 头长度,数据紧跟其后 */
|
||||
};
|
||||
|
||||
/** 输入 PDO 段 1(0..31):桥状态 + 最近一帧短快照 + 收发诊断。 */
|
||||
enum : uint8_t {
|
||||
ECAT_CAN_RX_OFF_ACK = 0U, /**< 回显已执行的发送序号,主站据此确认送达 */
|
||||
ECAT_CAN_RX_OFF_CH = 1U, /**< 该帧来自哪个通道 */
|
||||
ECAT_CAN_RX_OFF_DLC = 2U, /**< 该帧数据长度(原始 DLC,未被截断前的值) */
|
||||
ECAT_CAN_RX_OFF_FLAGS = 3U, /**< bit0=有过接收;bit1=数据被截断到 8B */
|
||||
ECAT_CAN_RX_OFF_ID = 4U, /**< 该帧 CAN ID,小端 uint32 */
|
||||
ECAT_CAN_RX_OFF_DATA = 8U, /**< 数据区起点,8 字节(长帧见段 3) */
|
||||
ECAT_CAN_RX_OFF_RXCNT = 16U, /**< 累计接收帧数,小端 uint32 */
|
||||
ECAT_CAN_RX_OFF_TXRES = 20U, /**< 最近一次发送结果(CanTxResult 原值,0=OK) */
|
||||
ECAT_CAN_RX_OFF_BACKLOG = 21U, /**< 该通道软件 TX 队列积压 */
|
||||
ECAT_CAN_RX_OFF_TXCNT = 22U, /**< 累计发送受理帧数,小端 uint16 */
|
||||
ECAT_CAN_RX_OFF_DROP = 24U, /**< 段 1/段 3 快照「未取先覆」次数(u16 累计回绕) */
|
||||
ECAT_CAN_RX_OFF_TXTXID = 26U, /**< 最近 enqueueTx 成功的 CAN ID,小端 uint32 */
|
||||
ECAT_CAN_RX_OFF_RX00FF = 30U, /**< CAN RX 收到 0x00FF 次数,小端 uint16 */
|
||||
};
|
||||
|
||||
/**
|
||||
* 输入 PDO 段 2(32..543):电机整轮状态(密排)。
|
||||
*
|
||||
* 主站解析:
|
||||
* count = in[32] // 0..9,本拍实际打包的电机数
|
||||
* for (i = 0; i < count; i++)
|
||||
* slot = in[40 + i*56] // node_id/online/dlc/seq/status
|
||||
*
|
||||
* 空节点不占位;槽序按 node_id 升序;同号多通道取 seq 最新的一条。
|
||||
*/
|
||||
enum : uint16_t {
|
||||
ECAT_MOTW_OFF_COUNT = 32U, /**< 本拍密排电机数 0..9(主站按此迭代) */
|
||||
ECAT_MOTW_OFF_ACTIVE = 33U, /**< 在线电机数(与 count 可不同:离线但仍报最后一轮) */
|
||||
ECAT_MOTW_OFF_BOOT = 34U, /**< 1=近 3s 收到过 0x103 BOOT 心跳 */
|
||||
ECAT_MOTW_OFF_MAGIC = 35U, /**< 恒 kEcatMotLayoutMagic(0xA5):密排布局标识 */
|
||||
ECAT_MOTW_OFF_GLOBAL_SEQ = 36U, /**< 状态表全局刷新序号,小端 uint32 */
|
||||
ECAT_MOTW_OFF_SLOTS = 40U, /**< 密排槽数组起点,count×56B */
|
||||
};
|
||||
|
||||
/** 电机状态槽内部布局(56 字节)。 */
|
||||
enum : uint8_t {
|
||||
ECAT_MOT_OFF_NODE = 0U, /**< 节点号 1..9(密排下不会出现 0) */
|
||||
ECAT_MOT_OFF_CH = 1U, /**< CAN 通道 0/1/2 */
|
||||
ECAT_MOT_OFF_ONLINE = 2U, /**< 1=在线 */
|
||||
ECAT_MOT_OFF_DLC = 3U, /**< 最后一帧 STATUS 的 DLC(正常 48) */
|
||||
ECAT_MOT_OFF_SEQ = 4U, /**< STATUS 刷新序号,小端 uint32 */
|
||||
ECAT_MOT_OFF_STATUS = 8U, /**< 原始 STATUS payload,48 字节 */
|
||||
};
|
||||
|
||||
/** 输入 PDO 段 3(544..583):最近一帧完整快照(版本/CONFIG 帧要 24B+)。 */
|
||||
enum : uint16_t {
|
||||
ECAT_XRX_OFF_CH = 544U, /**< 通道 */
|
||||
ECAT_XRX_OFF_DLC = 545U, /**< 原始 DLC(可达 32) */
|
||||
ECAT_XRX_OFF_FLAGS = 546U, /**< bit0=有效 */
|
||||
ECAT_XRX_OFF_RSVD = 547U,
|
||||
ECAT_XRX_OFF_ID = 548U, /**< CAN ID,小端 uint32 */
|
||||
ECAT_XRX_OFF_DATA = 552U, /**< 数据区,32 字节完整 payload */
|
||||
};
|
||||
|
||||
/** 输入 PDO 段 5(584..595):设备诊断区(位置未变)。 */
|
||||
enum : uint16_t {
|
||||
ECAT_DIAG_OFF_LOAD = 584U, /**< 负载率,小端 uint16,单位 0.1%(1000=100%) */
|
||||
ECAT_DIAG_OFF_TORN = 586U, /**< CAN 取帧撕裂丢帧数(低 16 位,饱和) */
|
||||
ECAT_DIAG_OFF_SEQ = 588U, /**< 诊断区序号,小端 uint16,每完成 1s 窗口 +1 */
|
||||
ECAT_DIAG_OFF_OVERFULL = 590U, /**< CAN RX FIFO 满溢计数 RFO0(u16 饱和) */
|
||||
ECAT_DIAG_OFF_RELTIMEOUT = 592U, /**< RFD0 释放握手超时计数(u16 饱和) */
|
||||
ECAT_DIAG_OFF_REJECT = 594U, /**< CAN 内容级拼接帧命中计数(u16 饱和) */
|
||||
};
|
||||
|
||||
/** 单帧数据在输出 PDO / 长帧快照区中的最大字节数(电机 CMD 是 DLC 32)。 */
|
||||
constexpr uint8_t kEcatCanTxDataMax = 32U;
|
||||
/** 段 1 短快照数据区字节数。 */
|
||||
constexpr uint8_t kEcatCanRxDataMax = 8U;
|
||||
/** 段 3 长快照数据区字节数。 */
|
||||
constexpr uint8_t kEcatCanXRxDataMax = 32U;
|
||||
|
||||
/** 单个电机状态槽字节数:8B 头 + 48B STATUS。 */
|
||||
constexpr uint8_t kEcatMotSlotSize = 56U;
|
||||
/** 一拍最多打包的电机数 = 节点号 1..9 各一槽(同号多通道取最新)。 */
|
||||
constexpr uint8_t kEcatMotSlotsPerPdo = kMotorMaxNodes; /* 9 */
|
||||
/** 段 2 头长度(COUNT..GLOBAL_SEQ,8B)。 */
|
||||
constexpr uint8_t kEcatMotHdrSize = 8U;
|
||||
/** 段 2 密排区最大字节:头 8B + 9×56B = 512B,落在 32..543。 */
|
||||
constexpr uint16_t kEcatMotRegionBytes =
|
||||
(uint16_t)(kEcatMotHdrSize
|
||||
+ (uint16_t)kEcatMotSlotsPerPdo * kEcatMotSlotSize);
|
||||
/** 布局魔数,写在 offset 35。主站据此区分密排布局与旧滑窗布局(两者
|
||||
* Ibytes 同为 600,不能只靠长度分支)。0=非密排布局。 */
|
||||
constexpr uint8_t kEcatMotLayoutMagic = 0xA5U;
|
||||
|
||||
/** 过程数据长度,唯一真源见 comm/ethercat_pdo_size.h。 */
|
||||
constexpr uint16_t kEcatPdoOutBytes = ECAT_PDO_OUT_BYTES; /* 384B */
|
||||
constexpr uint16_t kEcatPdoInBytes = ECAT_PDO_IN_BYTES; /* 600B */
|
||||
|
||||
/** CANFD 单帧数据上限。 */
|
||||
constexpr uint8_t kEcatCanFdDataMax = 64U;
|
||||
|
||||
/** 批量发送区可用字节数与最坏情况(全 DLC64)帧数上限。 */
|
||||
constexpr uint16_t kEcatTxBatchBytes = (uint16_t)(kEcatPdoOutBytes - ECAT_TXB_OFF_ITEMS);
|
||||
constexpr uint8_t kEcatTxBatchMax =
|
||||
(uint8_t)(kEcatTxBatchBytes / (ECAT_FRM_HDR_SIZE + kEcatCanFdDataMax));
|
||||
|
||||
/* ── 布局守卫 ── */
|
||||
static_assert(ECAT_MOTW_OFF_SLOTS
|
||||
+ (uint16_t)kEcatMotSlotsPerPdo * kEcatMotSlotSize
|
||||
== ECAT_XRX_OFF_CH,
|
||||
"段2密排区必须与段3首尾相接");
|
||||
static_assert(ECAT_XRX_OFF_DATA + kEcatCanXRxDataMax == ECAT_DIAG_OFF_LOAD,
|
||||
"段3必须与段5首尾相接");
|
||||
static_assert(ECAT_CAN_TX_OFF_DATA + kEcatCanTxDataMax <= ECAT_TXB_OFF_COUNT,
|
||||
"单帧发送数据区与批量发送区重叠");
|
||||
static_assert(ECAT_TXB_OFF_ITEMS < kEcatPdoOutBytes, "批量发送区起点越界");
|
||||
static_assert(kEcatTxBatchMax >= 1U, "批量发送区装不下一帧 DLC64");
|
||||
static_assert(ECAT_DIAG_OFF_REJECT + 2U <= kEcatPdoInBytes, "段5诊断区越界");
|
||||
static_assert(ECAT_MOTW_OFF_COUNT + kEcatMotRegionBytes <= ECAT_XRX_OFF_CH,
|
||||
"段2密排区越界");
|
||||
|
||||
/**
|
||||
* @brief EtherCAT ↔ CAN 转发桥。
|
||||
* @note 全静态;PDO 侧函数在 Sync0_Isr(preempt=0) 或主循环 MainLoop 上下文执行,
|
||||
* onCanFrame() 在主循环 taskCanRxParse() 上下文执行。共享状态用 volatile +
|
||||
* 单生产者/单消费者语义。多字段一组用 seqlock:
|
||||
* - 段 1/段 3 的「最近一帧快照」→ s_xrxSeq;
|
||||
* - 电机槽 48B payload → MotorSlot.wr_seq。
|
||||
* Sync0 是 preempt=0,critical.h 的 BASEPRI 方案挡不住它,故只能无锁协议。
|
||||
*/
|
||||
class EthercatCanBridge {
|
||||
public:
|
||||
/**
|
||||
* @brief 输出探针:seq 变化时请求全量读。
|
||||
* @note 不再刷新 win_base(电机整轮密排,无滑窗);仍处理 seq 沿
|
||||
* 与 FLAGS bit0 清统计。
|
||||
*/
|
||||
static bool peekOutput(const uint8_t *head);
|
||||
|
||||
/** @brief 复位输出 sequence 与冷启动同步状态。 */
|
||||
static void resetSequenceState();
|
||||
|
||||
static void processOutput(const uint8_t *out);
|
||||
|
||||
/**
|
||||
* @brief 填充输入 PDO:桥状态 + 电机整轮密排 + 长帧快照。
|
||||
* @param[out] in kEcatPdoInBytes 字节输入过程数据。
|
||||
*/
|
||||
static void buildInput(uint8_t *in);
|
||||
|
||||
/**
|
||||
* @brief CAN 收帧投递入口(旁路复制,不消费帧)。
|
||||
*/
|
||||
static void onCanFrame(uint8_t channel, uint32_t id,
|
||||
const uint8_t *data, uint8_t length);
|
||||
|
||||
/** @brief 桥接是否已收到过至少一帧(诊断用)。 */
|
||||
static bool hasReceived() { return s_rxValid; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 填充段 2 电机整轮密排区(含差分跳过)。
|
||||
* @return 本拍打包的电机数(写入 count 字段)。
|
||||
*/
|
||||
static uint8_t fillMotorRound(uint8_t *in);
|
||||
|
||||
static uint8_t sendBatch(const uint8_t *out);
|
||||
|
||||
static volatile uint8_t s_lastSeq;
|
||||
static volatile bool s_seqPrimed;
|
||||
static volatile bool s_coldStartPending;
|
||||
static volatile uint8_t s_lastTxResult;
|
||||
static volatile uint16_t s_txCount;
|
||||
static volatile uint16_t s_dropCount;
|
||||
/** 最近一次 enqueueTx 成功的 CAN ID(诊断:ECAT 是否真的发出去) */
|
||||
static volatile uint32_t s_lastTxId;
|
||||
/** CAN RX 收到 0x00FF(版本应答)次数(诊断:电机是否回帧) */
|
||||
static volatile uint16_t s_rx00ffCount;
|
||||
/** processOutput 本拍读到的输出 PDO seq(诊断:ESC 输出是否到达应用层) */
|
||||
static volatile uint8_t s_outSeenSeq;
|
||||
|
||||
/* 最近一帧接收快照:taskCanRxParse 写、PDO 读。s_xrxSeq 为 seqlock。
|
||||
* STATUS(0x0100) 禁止写入本快照 —— 它由 MotorState 段2 密排承载;
|
||||
* 否则高频 STATUS 会持续冲掉版本应答(0x00FF)等低频长帧。 */
|
||||
static volatile bool s_rxValid;
|
||||
static volatile bool s_rxPublished;
|
||||
static volatile uint32_t s_xrxSeq;
|
||||
static volatile uint8_t s_rxCh;
|
||||
static volatile uint8_t s_rxDlc;
|
||||
static volatile bool s_rxTruncated;
|
||||
static volatile uint32_t s_rxId;
|
||||
static volatile uint32_t s_rxCount;
|
||||
static uint8_t s_rxData[kEcatCanXRxDataMax];
|
||||
|
||||
/* 版本应答粘滞快照:0x00FF 到达后在 PDO 段3 保持发布一段时间,
|
||||
* 避免被随后的 CONFIG 等帧在主站采样前覆盖。 */
|
||||
static volatile bool s_verValid;
|
||||
static volatile uint32_t s_verHoldUntilMs;
|
||||
static uint8_t s_verCh;
|
||||
static uint8_t s_verDlc;
|
||||
static uint32_t s_verId;
|
||||
static uint8_t s_verData[kEcatCanXRxDataMax];
|
||||
|
||||
/* ── 段 2 差分填充影子 ──
|
||||
* 上一拍已发布的密排区副本 + 各节点上次发布的 seq/online + 头字段。
|
||||
* 节点集合、seq、online、active/boot 全未变时整段复用,跳过 MotorState
|
||||
* 重读与逐槽 48B 拷贝。掩码必须 u16:节点号 1..9,bit9 超出 uint8_t。 */
|
||||
static uint8_t s_motShadow[kEcatMotRegionBytes];
|
||||
static uint32_t s_motPubSeq[kMotorMaxNodes + 1U]; /* 下标=节点号 */
|
||||
static uint8_t s_motPubOnline[kMotorMaxNodes + 1U]; /* checkOffline 不 bump seq,须单独比 */
|
||||
static uint16_t s_motPubMask; /**< bit n=1:节点 n 在影子区中 */
|
||||
static uint8_t s_motPubCount;
|
||||
static bool s_motShadowValid;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
/** @brief 供 dc_app.c(C 文件)调用的 shim:处理输出 PDO。 */
|
||||
void EcatCan_ProcessOutput(const uint8_t *out);
|
||||
/** @brief 供 dc_app.c 调用的 shim:填充输入 PDO。 */
|
||||
void EcatCan_BuildInput(uint8_t *in);
|
||||
/** @brief 供 dc_app.c 调用的 shim:复位输出 sequence 状态机。 */
|
||||
void EcatCan_ResetSequenceState(void);
|
||||
}
|
||||
|
||||
#endif /* COMM_ETHERCAT_CAN_BRIDGE_H */
|
||||
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @file ethercat_diff_stats.cpp
|
||||
* @brief [DIFF-STATS] 输入镜像差分统计实现(度量定义与存在理由见 ethercat_diff_stats.h)。
|
||||
*
|
||||
* 整文件由 CONFIG_ECAT_DIFF_STATS 门控:置 0 时不参与编译,挂接点各自也有 #if,
|
||||
* 因此关掉之后固件与改造前逐字节等价。
|
||||
*/
|
||||
#include "comm/ethercat_diff_stats.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
#include "comm/ethercat_pdo_size.h"
|
||||
#include "sys/config.h"
|
||||
|
||||
#if CONFIG_ECAT_DIFF_STATS
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kInBytes = (uint16_t)ECAT_PDO_IN_BYTES;
|
||||
constexpr uint32_t kInWords = (uint32_t)kInBytes / 4U; /* 600/4=150,无尾巴字节 */
|
||||
constexpr uint8_t kSegCount = 5U;
|
||||
|
||||
/**
|
||||
* @brief 各段字节上界(开区间),用于把变化量归到布局段落。
|
||||
* @note 1.6.21 布局:段1(0..31) 段2(32..543) 段3(544..583) 段4 已删 段5(584..599)。
|
||||
* 段4 上界=段5 起点,区间为空、恒 0,子索引保留以免动对象字典。
|
||||
*/
|
||||
constexpr uint16_t kSegEnd[kSegCount] = {32U, 544U, 584U, 584U, kInBytes};
|
||||
|
||||
/** @brief 上一份已发布的镜像。alignas(4) 是让优化器能把 4 字节 memcpy 折成单条
|
||||
* LDR 的前提——丢了它,比对会从 ~150 次字访问退化成 600 次字节访问。 */
|
||||
alignas(4) uint8_t s_prev[kInBytes];
|
||||
bool s_havePrev = false;
|
||||
|
||||
/* ── 正在累积的窗口 ── */
|
||||
uint32_t s_curCycles = 0U;
|
||||
uint32_t s_curChangedMax = 0U;
|
||||
uint32_t s_curRunsTotal = 0U;
|
||||
uint32_t s_curRunsMax = 0U;
|
||||
uint32_t s_curCompareCyc = 0U;
|
||||
uint32_t s_curSeg[kSegCount] = {0U, 0U, 0U, 0U, 0U};
|
||||
uint32_t s_winStart = 0U;
|
||||
|
||||
/* ── 上一个已完成的窗口(getter 只读这份,保证主站拿到的是稳定值)── */
|
||||
uint32_t s_winCycles = 0U;
|
||||
uint32_t s_winChangedMax = 0U;
|
||||
uint32_t s_winRunsTotal = 0U;
|
||||
uint32_t s_winRunsMax = 0U;
|
||||
uint32_t s_winCompareCyc = 0U;
|
||||
uint32_t s_winSeg[kSegCount] = {0U, 0U, 0U, 0U, 0U};
|
||||
uint32_t s_windowSeq = 0U;
|
||||
|
||||
/** @brief 字节偏移所属段号。只对"确实变了"的字调用,线性 5 次比较足够。 */
|
||||
uint8_t segOf(uint32_t byteOff)
|
||||
{
|
||||
for (uint8_t i = 0U; i < kSegCount; ++i) {
|
||||
if (byteOff < (uint32_t)kSegEnd[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return (uint8_t)(kSegCount - 1U); /* 理论上到不了:kSegEnd[4] == kInBytes */
|
||||
}
|
||||
|
||||
/** @brief 结算窗口:把累加值搬成"上一个完整窗口",再清零重新计时。 */
|
||||
void rollWindow(uint32_t now)
|
||||
{
|
||||
s_winCycles = s_curCycles;
|
||||
s_winChangedMax = s_curChangedMax;
|
||||
s_winRunsTotal = s_curRunsTotal;
|
||||
s_winRunsMax = s_curRunsMax;
|
||||
s_winCompareCyc = s_curCompareCyc;
|
||||
for (uint8_t i = 0U; i < kSegCount; ++i) {
|
||||
s_winSeg[i] = s_curSeg[i];
|
||||
s_curSeg[i] = 0U;
|
||||
}
|
||||
s_curCycles = 0U;
|
||||
s_curChangedMax = 0U;
|
||||
s_curRunsTotal = 0U;
|
||||
s_curRunsMax = 0U;
|
||||
s_curCompareCyc = 0U;
|
||||
s_winStart = now;
|
||||
s_windowSeq++;
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
/**
|
||||
* @brief 与上一份已发布镜像比对并累计统计。
|
||||
* @note .ramfunc:每拍在 PDI_Isr 里跑。放 flash 会带来等待态,既污染本仪表
|
||||
* 自己要测的 compareCyc,也给被测的写侧窗口添无谓的额外占用。
|
||||
*/
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EthercatDiffStats::observe(const uint8_t *cur) noexcept
|
||||
{
|
||||
const uint32_t t0 = DWT->CYCCNT;
|
||||
|
||||
if (!s_havePrev) {
|
||||
/* 首拍只建基准:否则第一拍会把"整 600B 都变了"记成样本,把 changedMax
|
||||
* 和段计数全钉在无意义的上限上。 */
|
||||
std::memcpy(s_prev, __builtin_assume_aligned(cur, 4), kInBytes);
|
||||
s_havePrev = true;
|
||||
s_winStart = DWT->CYCCNT;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t changed = 0U;
|
||||
uint32_t runs = 0U;
|
||||
bool inRun = false;
|
||||
|
||||
/* 对齐必须在调用点再声明一次:alignas(4) 只到形参为止,函数签名是
|
||||
* const uint8_t*,优化器看不见对齐就无从折字访问。实测两版都因此慢到不能用——
|
||||
* 移位版反汇编出 8 条 ldrb.w/字(41µs/拍),memcpy 版 21µs/拍 —— 而这个数会被
|
||||
* 直接读成"差分判断很贵",是仪表自己造的假象,不是差分的成本。 */
|
||||
const uint8_t *prevW = static_cast<const uint8_t *>(__builtin_assume_aligned(s_prev, 4));
|
||||
const uint8_t *curW = static_cast<const uint8_t *>(__builtin_assume_aligned(cur, 4));
|
||||
|
||||
for (uint32_t w = 0U; w < kInWords; ++w) {
|
||||
const uint32_t o = w * 4U;
|
||||
uint32_t a;
|
||||
uint32_t b;
|
||||
/* assume_aligned + memcpy:不碰严格别名,又能让 -Oz 折成单条 LDR。 */
|
||||
std::memcpy(&a, prevW + o, 4U);
|
||||
std::memcpy(&b, curW + o, 4U);
|
||||
if (a == b) {
|
||||
inRun = false;
|
||||
continue;
|
||||
}
|
||||
changed += 4U;
|
||||
if (!inRun) {
|
||||
runs++;
|
||||
inRun = true;
|
||||
}
|
||||
s_curSeg[segOf(o)] += 4U;
|
||||
}
|
||||
|
||||
s_curCycles++;
|
||||
s_curRunsTotal += runs;
|
||||
if (changed > s_curChangedMax) { s_curChangedMax = changed; }
|
||||
if (runs > s_curRunsMax) { s_curRunsMax = runs; }
|
||||
|
||||
std::memcpy(s_prev, curW, kInBytes);
|
||||
|
||||
const uint32_t t1 = DWT->CYCCNT;
|
||||
/* 比对自身开销:不含上面那次 600B 快照搬运(那是差分写真要做时要付的成本之一,
|
||||
* 单列出来才看得清"光判断"值多少钱)。 */
|
||||
s_curCompareCyc += (uint32_t)(t1 - t0);
|
||||
|
||||
/* SystemCoreClock 个计数 = 1s(与 sys/cpu_load.cpp 同法;DWT 32 位约 23.8s
|
||||
* 绕回,窗口远短于此,无绕回问题)。 */
|
||||
if ((uint32_t)(t1 - s_winStart) >= SystemCoreClock) {
|
||||
rollWindow(t1);
|
||||
}
|
||||
}
|
||||
|
||||
void EthercatDiffStats::reset() noexcept
|
||||
{
|
||||
s_havePrev = false;
|
||||
}
|
||||
|
||||
uint32_t EthercatDiffStats::windowSeq() noexcept { return s_windowSeq; }
|
||||
uint32_t EthercatDiffStats::cycles() noexcept { return s_winCycles; }
|
||||
uint32_t EthercatDiffStats::changedMax() noexcept { return s_winChangedMax; }
|
||||
uint32_t EthercatDiffStats::runsTotal() noexcept { return s_winRunsTotal; }
|
||||
uint32_t EthercatDiffStats::runsMax() noexcept { return s_winRunsMax; }
|
||||
uint32_t EthercatDiffStats::seg1Bytes() noexcept { return s_winSeg[0]; }
|
||||
uint32_t EthercatDiffStats::seg2Bytes() noexcept { return s_winSeg[1]; }
|
||||
uint32_t EthercatDiffStats::seg3Bytes() noexcept { return s_winSeg[2]; }
|
||||
uint32_t EthercatDiffStats::seg4Bytes() noexcept { return s_winSeg[3]; }
|
||||
uint32_t EthercatDiffStats::seg5Bytes() noexcept { return s_winSeg[4]; }
|
||||
uint32_t EthercatDiffStats::compareCyc() noexcept { return s_winCompareCyc; }
|
||||
|
||||
/* ── 供 dc_app.c(C 侧对象字典) 取值:一次拿全 11 个窗口值,避免 11 次跨语言调用 ── */
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* @brief [DIFF-STATS] 读上一个完整 1s 窗口的统计值。
|
||||
* @param[out] out 依次对应 0x2000 sub13..sub23。
|
||||
* 形参用 uint32_t 而非 SSC 的 UINT32 别名:本翻译单元不引 SSC 头,
|
||||
* 两者同为 32 位无符号,C 侧声明仍按 UINT32 写。
|
||||
*/
|
||||
void EcatDiffStats_GetWindow(uint32_t *out)
|
||||
{
|
||||
if (out == nullptr) {
|
||||
return;
|
||||
}
|
||||
out[0] = EthercatDiffStats::windowSeq();
|
||||
out[1] = EthercatDiffStats::cycles();
|
||||
out[2] = EthercatDiffStats::changedMax();
|
||||
out[3] = EthercatDiffStats::runsTotal();
|
||||
out[4] = EthercatDiffStats::runsMax();
|
||||
out[5] = EthercatDiffStats::seg1Bytes();
|
||||
out[6] = EthercatDiffStats::seg2Bytes();
|
||||
out[7] = EthercatDiffStats::seg3Bytes();
|
||||
out[8] = EthercatDiffStats::seg4Bytes();
|
||||
out[9] = EthercatDiffStats::seg5Bytes();
|
||||
out[10] = EthercatDiffStats::compareCyc();
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
#else /* !CONFIG_ECAT_DIFF_STATS */
|
||||
/* 关掉时仍导出同名取值函数并填 0:这样 dc_app.c 的挂接点不需要再套一层
|
||||
* #if,子索引数量也保持与开启版一致(主站读到的恒 0,脚本按 winseq==0 判'未启用')。
|
||||
* 留一个空函数体反而会漏掉符号——1.6.20 首编就是栽在这里(L6218E)。 */
|
||||
extern "C" void EcatDiffStats_GetWindow(uint32_t *out)
|
||||
{
|
||||
if (out == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0U; i < 11U; ++i) {
|
||||
out[i] = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ECAT_DIFF_STATS */
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file ethercat_diff_stats.h
|
||||
* @brief [DIFF-STATS] 临时仪表:量化"若把输入 PDO 改成差分写,每拍真正需要写多少字节、
|
||||
* 变化点碎成几个连续段(run)"。只测量,不改变任何现有写行为。
|
||||
*
|
||||
* 存在的理由:差分写(方案 A)的收益完全取决于"每拍有多少字节真的变了",而 run 碎片
|
||||
* 数量决定它会不会把一次 600B 整块写退化成几百次带 AVAIL 轮询的小写。这两个数今天
|
||||
* 都没有实测依据,所以先只上仪表,拿分布再决定 A1(语义分区条件写) 值不值得做、
|
||||
* A2(块粒度差分) 的碎片上限 N 该取几。
|
||||
*
|
||||
* 顺带把"比较本身要多久"也测出来(sub23 累计 DWT 周期)——这是差分方案最容易被
|
||||
* 质疑的成本项,用实测代替拍脑袋。
|
||||
*
|
||||
* @note 结果按 1s 窗口滚动(与 sys/cpu_load.cpp 同法,用 DWT 周期判窗口边界),
|
||||
* getter 返回**上一个已完成的完整窗口**,因此主站任何时刻 SDO 读到的都是
|
||||
* 稳定值,不会拿到半累积的窗口。窗口未满时 windowSeq()==0,读侧应显示
|
||||
* "等待首个完整窗口"而不是当 0 解读。
|
||||
*
|
||||
* @note 删除方法:删本文件与 ethercat_diff_stats.cpp + 删全工程 [DIFF-STATS] 标记行
|
||||
* + 把 sys/config.h 的 CONFIG_ECAT_DIFF_STATS 置 0。对象字典 0x2000 的
|
||||
* sub13..23 字段建议保留(恒 0,不影响任何既有子索引)。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_DIFF_STATS_H
|
||||
#define COMM_ETHERCAT_DIFF_STATS_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief 输入镜像差分统计。
|
||||
* @note 全静态;observe() 在 PDI_Isr 上下文每拍调用一次,故内部状态无需临界区
|
||||
* (唯一写者),getter 由 APPL_InputMapping 同上下文读取。
|
||||
*/
|
||||
class EthercatDiffStats {
|
||||
public:
|
||||
/**
|
||||
* @brief 与上一份已发布的输入镜像比对并累计统计。
|
||||
* @param[in] cur 刚发布完成的完整输入 PDO 镜像(kEcatPdoInBytes 字节)。
|
||||
* @note 内部自持上一份副本,调用方无需额外维护缓冲、也不必自己 memcpy。
|
||||
* 首次调用只建立基准,不产生样本(否则会把"全 600B 都变了"记成第一拍)。
|
||||
*/
|
||||
static void observe(const uint8_t *cur) noexcept;
|
||||
|
||||
/** @brief 丢弃基准,下一次调用重新起算(AL 状态跳变、布局变更时用)。 */
|
||||
static void reset() noexcept;
|
||||
|
||||
/* ── 对象字典 0x2000 sub13..23 的取值接口,均为"上一个完整 1s 窗口" ── */
|
||||
static uint32_t windowSeq() noexcept; /**< sub13 已完成窗口数,0=还没有完整窗口 */
|
||||
static uint32_t cycles() noexcept; /**< sub14 窗口内参与比对的拍数 */
|
||||
static uint32_t changedMax() noexcept; /**< sub15 单拍变化字节峰值 */
|
||||
static uint32_t runsTotal() noexcept; /**< sub16 窗口内 run 总数(配 cycles 求均值) */
|
||||
static uint32_t runsMax() noexcept; /**< sub17 单拍 run 数峰值,A2 的 N 由它定 */
|
||||
static uint32_t seg1Bytes() noexcept; /**< sub18 段1(0..31) 桥状态+短快照 */
|
||||
static uint32_t seg2Bytes() noexcept; /**< sub19 段2(32..543) 电机整轮密排 */
|
||||
static uint32_t seg3Bytes() noexcept; /**< sub20 段3(544..583) 长帧快照 */
|
||||
static uint32_t seg4Bytes() noexcept; /**< sub21 已删(1.6.21 恒 0) */
|
||||
static uint32_t seg5Bytes() noexcept; /**< sub22 段5(584..599) 诊断区+保留 */
|
||||
static uint32_t compareCyc() noexcept; /**< sub23 比对自身累计 DWT 周期(本征开销) */
|
||||
};
|
||||
|
||||
#endif /* COMM_ETHERCAT_DIFF_STATS_H */
|
||||
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* @file ethercat_pdo.cpp
|
||||
* @brief EtherCAT 过程数据(PDO)收发模块实现。
|
||||
*/
|
||||
#include "comm/ethercat_pdo.h"
|
||||
#include "comm/ethercat_can_bridge.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header /* __DMB() */
|
||||
/* [DIFF-STATS] 临时仪表挂接(删除:去掉本块与 process() 内的 observe 块)。
|
||||
* 上一行的 sys/config.h 必须在此之前:宏未定义时 #if 会静默按 0 求值,
|
||||
* 开关就会被悄悄当成"关掉"。 */
|
||||
#if CONFIG_ECAT_DIFF_STATS
|
||||
#include "comm/ethercat_diff_stats.h"
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
/* SSC 输出过程数据:SM2 地址与长度(ecatslv.c,类型为 SSC UINT16=uint16) */
|
||||
extern uint16_t nEscAddrOutputData;
|
||||
extern uint16_t nPdOutputSize;
|
||||
void HW_EscRead(void *pData, uint16_t Address, uint16_t Len);
|
||||
}
|
||||
|
||||
/* alignas(4) 必须在类内声明与出类定义上同时标注:AC6(Clang) 否则报
|
||||
* "'alignas' must be specified on definition"。用途见 ethercat_pdo.h。 */
|
||||
alignas(4) uint8_t EthercatPdo::s_out[EthercatPdo::kPdoOutBytes] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_in[EthercatPdo::kPdoInBytes] = {0};
|
||||
/* 输入 PDO 的工作缓冲:buildInput 填这里,填完整块发布到 s_in(见 process())。
|
||||
* 读侧 writeInputs 只读 s_in,因此永远看不到"填到一半"的镜像。 */
|
||||
alignas(4) uint8_t EthercatPdo::s_build[EthercatPdo::kPdoInBytes] = {0};
|
||||
|
||||
namespace {
|
||||
/* 待交回主站的输入数据双缓冲(ping-pong,配合 __DMB() 保证原子发布与 tear-free 读写) */
|
||||
alignas(4) uint8_t s_inBuf[2][EthercatPdo::kPdoInBytes] = {{0}, {0}};
|
||||
volatile uint8_t s_inActiveIdx = 0U;
|
||||
}
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh1[32] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh2[ECAT_XRX_OFF_CH - 32U] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh3[kEcatPdoInBytes - ECAT_XRX_OFF_CH] = {0};
|
||||
uint8_t EthercatPdo::s_escFlush1 = 0U;
|
||||
uint8_t EthercatPdo::s_escFlush2 = 0U;
|
||||
uint8_t EthercatPdo::s_escFlush3 = 0U;
|
||||
bool EthercatPdo::s_escSh1Valid = false;
|
||||
bool EthercatPdo::s_escSh2Valid = false;
|
||||
bool EthercatPdo::s_escSh3Valid = false;
|
||||
|
||||
volatile uint32_t EthercatPdo::s_outputCycles = 0U;
|
||||
volatile uint32_t EthercatPdo::s_inputCycles = 0U;
|
||||
volatile uint32_t EthercatPdo::s_reentryCount = 0U;
|
||||
/** OutputMapping 本拍是否已刷新 s_out(process 兜底读的新鲜度门) */
|
||||
static volatile bool s_outMappedThisCycle = false;
|
||||
volatile uint32_t EthercatPdo::s_processSeq = 0U;
|
||||
|
||||
void EthercatPdo::readOutputs(const uint8_t *esc)
|
||||
{
|
||||
if (esc == nullptr) {
|
||||
return;
|
||||
}
|
||||
memcpy(s_out, esc, kPdoOutBytes);
|
||||
++s_outputCycles;
|
||||
}
|
||||
|
||||
bool EthercatPdo::outputProbe(const uint8_t *head)
|
||||
{
|
||||
return EthercatCanBridge::peekOutput(head);
|
||||
}
|
||||
|
||||
void EthercatPdo::process()
|
||||
{
|
||||
/* 重入防护:SSC 的 ECAT_Application()→本函数 在**四个上下文**都会被
|
||||
* 调用 —— PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop(见 ssc/src/ecatappl.c
|
||||
* 388、499、527、641 行)。而 MainLoop 那条路只用 DISABLE_ESC_INT() 关了
|
||||
* EXTI1(ESC IRQ),**挡不住 EXTI0(Sync0,preempt=0) 和 EXTI2(Sync1,preempt=1)**。
|
||||
*
|
||||
* 竞态:MainLoop 把 s_in 填到一半时被 Sync0/Sync1
|
||||
* 抢占 → 内层重入本函数,memset 清空后重填整个输入 PDO,随后 PDO_InputMapping()
|
||||
* 把这一份发给 ESC;外层回来又按自己那轮的进度接着往下写。于是发出去的镜像里
|
||||
* 批量区相邻两条来自**不同轮次**。
|
||||
*
|
||||
* 处理:已在填充中就直接返回。输入 PDO 是周期量,跳过一次下一拍即补,
|
||||
* 远好过发出一份跨轮次拼接的镜像。 */
|
||||
static volatile bool s_building = false;
|
||||
if (s_building) {
|
||||
++s_reentryCount;
|
||||
return;
|
||||
}
|
||||
s_building = true;
|
||||
|
||||
/* PDO ↔ CAN 转发:输出 PDO 的 seq 字节变化沿触发一次 CAN 发送,
|
||||
* 输入 PDO 回填桥状态 + 电机状态窗口(布局见 ethercat_can_bridge.h)。
|
||||
*
|
||||
* 顺序必须是先 processOutput 后 buildInput:buildInput 会把本周期刚执行的
|
||||
* seq 回显成 ack,主站因此能在下一周期立刻确认送达;反过来则 ack 恒滞后一拍。
|
||||
* processOutput 还负责取走主站请求的状态窗口下标,也必须在 buildInput 之前。
|
||||
*
|
||||
* 填的是**工作缓冲** s_build,填完整体发布到 s_in:只有重入门还不够 ——
|
||||
* 内层被门挡住后,它随后的 PDO_InputMapping() 仍会去读 s_in,若 buildInput
|
||||
* 直接写 s_in,读到的就是外层写到一半的半成品。填在别处、整块发布之后,
|
||||
* 读侧要么拿到上一份完整镜像、要么拿到这一份完整镜像,不存在中间态。 */
|
||||
/* 双缓冲发布:填入非活跃槽 nextIdx,完成后通过 __DMB() 与 s_inActiveIdx 切换
|
||||
* 实现原子发布,彻底避免中断读侧(PDI_Isr / Sync0_Isr)与主循环之间的读写撕裂。 */
|
||||
const uint8_t nextIdx = 1U - s_inActiveIdx;
|
||||
uint8_t *build = s_inBuf[nextIdx];
|
||||
|
||||
/* 输出兜底全量读:仅当 OutputMapping 本拍未刷新 s_out,且
|
||||
* (s_out 看起来是死的 或 距上次强制读已跑过一段 process) 才读。
|
||||
* 每拍 process() 都 HW_EscRead 384B 会与 PDO_OutputMapping 叠成双份 HBI,
|
||||
* PDO 2kHz 即把 ISR 成本打满(历史探针版 4kHz load≈80%)。 */
|
||||
static uint32_t s_lastForceReadSeq = 0U;
|
||||
if (nPdOutputSize > 0U && nEscAddrOutputData != 0U && !s_outMappedThisCycle) {
|
||||
const bool outDead = (s_out[0] == 0U && s_out[1] == 0U && s_out[8] == 0U);
|
||||
const uint32_t seqNow = s_processSeq;
|
||||
/* 仅 s_out 疑似死数据时强制全量读;探针已处理 seq 沿。
|
||||
* 按拍数兜底间隔拉到 256,避免 8kHz 下周期性 384B HBI。 */
|
||||
if (outDead || (uint32_t)(seqNow - s_lastForceReadSeq) >= 256U) {
|
||||
const uint16_t nread = (nPdOutputSize > kPdoOutBytes)
|
||||
? kPdoOutBytes
|
||||
: nPdOutputSize;
|
||||
uint8_t escOut[kEcatPdoOutBytes];
|
||||
const uint16_t prefix = (nread < 52U) ? nread : 52U;
|
||||
HW_EscRead(escOut, nEscAddrOutputData, prefix);
|
||||
const uint8_t batchCount = (prefix > 48U) ? escOut[48] : 0U;
|
||||
if (batchCount > 0U && nread > prefix) {
|
||||
HW_EscRead(&escOut[prefix],
|
||||
(uint16_t)(nEscAddrOutputData + prefix),
|
||||
(uint16_t)(nread - prefix));
|
||||
}
|
||||
readOutputs(escOut);
|
||||
s_lastForceReadSeq = seqNow;
|
||||
}
|
||||
}
|
||||
s_outMappedThisCycle = false;
|
||||
|
||||
EthercatCanBridge::processOutput(s_out);
|
||||
EthercatCanBridge::buildInput(build);
|
||||
fillDiagTail(build);
|
||||
|
||||
__DMB();
|
||||
s_inActiveIdx = nextIdx;
|
||||
/* 不再 memcpy 到 s_in:writeInputs/稀疏写/差分写都读 s_inBuf[activeIdx],
|
||||
* 每拍多拷 600B 是纯浪费(8kHz 下不可接受)。 */
|
||||
++s_processSeq;
|
||||
|
||||
#if CONFIG_ECAT_DIFF_STATS
|
||||
/* [DIFF-STATS] 只测量:与上一份已发布镜像比对并累计到 1s 窗口,经 0x2000
|
||||
* sub13..23 上报。不改变上面的整块写行为。仪表自持上一份副本,这里不需要额外缓冲。 */
|
||||
EthercatDiffStats::observe(s_inBuf[nextIdx]);
|
||||
#endif
|
||||
|
||||
s_building = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 填充输入 PDO 段 5 设备诊断区(632..639)。
|
||||
* @param[out] in 全长输入过程数据缓冲(buildInput 刚清零+填完段 1~4)。
|
||||
* @note 负载率 1s 滚动一个窗口,seq 随窗口递增——主站只认"seq 在变"的负载值。
|
||||
* 每周期重写 6 字节比"值变化才写"的分支判断更便宜,也不引入隐藏状态。
|
||||
*/
|
||||
void EthercatPdo::fillDiagTail(uint8_t *in)
|
||||
{
|
||||
static uint16_t s_diagTick = 0U;
|
||||
const uint16_t load = CpuLoad::loadPermille();
|
||||
in[ECAT_DIAG_OFF_LOAD] = (uint8_t)(load & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_LOAD + 1] = (uint8_t)(load >> 8);
|
||||
|
||||
/* CAN 取帧撕裂丢帧数。只有 16 位,饱和不回绕 —— 主站看的是
|
||||
* "有没有涨、涨多快",回绕会被误读成计数清零。 */
|
||||
const CanFd::Diag *cd = CanFd::diag();
|
||||
const uint32_t torn = (cd != nullptr) ? cd->rx_torn_count : 0U;
|
||||
const uint16_t torn16 = (torn > 0xFFFFU) ? 0xFFFFU : (uint16_t)torn;
|
||||
in[ECAT_DIAG_OFF_TORN] = (uint8_t)(torn16 & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_TORN + 1] = (uint8_t)(torn16 >> 8);
|
||||
|
||||
/* seq 写「本函数被调用次数」而不是 windowSeq。
|
||||
* windowSeq 依赖 CpuLoad::tick() 滚窗;Process 若在 tick 前停摆,
|
||||
* 主站会长期读到 0 且分不清「没跑」和「窗口未满」。自由计数每拍+1,
|
||||
* 冻结看门狗与 app/load 的「seq 在涨」语义都更干净。 */
|
||||
++s_diagTick;
|
||||
in[ECAT_DIAG_OFF_SEQ] = (uint8_t)(s_diagTick & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_SEQ + 1] = (uint8_t)(s_diagTick >> 8);
|
||||
|
||||
/* CAN RX FIFO 满溢计数 RFO0:与内容级 reject 计数是否同涨,
|
||||
* 是"残余拼接是否仍源自溢出"的观测指标。 */
|
||||
const uint32_t of = (cd != nullptr) ? cd->rx_fifo_overfull_count : 0U;
|
||||
const uint16_t of16 = (of > 0xFFFFU) ? 0xFFFFU : (uint16_t)of;
|
||||
in[ECAT_DIAG_OFF_OVERFULL] = (uint8_t)(of16 & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_OVERFULL + 1] = (uint8_t)(of16 >> 8);
|
||||
|
||||
/* RFD0 释放握手超时计数。dequeue 握手没完成 = 窗口/读指针失同步前兆。 */
|
||||
const uint32_t rt = (cd != nullptr) ? cd->rx_release_timeout_count : 0U;
|
||||
const uint16_t rt16 = (rt > 0xFFFFU) ? 0xFFFFU : (uint16_t)rt;
|
||||
in[ECAT_DIAG_OFF_RELTIMEOUT] = (uint8_t)(rt16 & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_RELTIMEOUT + 1] = (uint8_t)(rt16 >> 8);
|
||||
|
||||
/* 内容级拼接帧拦截次数:MI/MP 校验挡不住的指针错位拼接由入队前
|
||||
* 结构自洽校验兜底,语义同上 —— 主站看"涨不涨"判断拼接是否仍在发生。 */
|
||||
const uint32_t rj = (cd != nullptr) ? cd->rx_content_reject_count : 0U;
|
||||
const uint16_t rj16 = (rj > 0xFFFFU) ? 0xFFFFU : (uint16_t)rj;
|
||||
in[ECAT_DIAG_OFF_REJECT] = (uint8_t)(rj16 & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_REJECT + 1] = (uint8_t)(rj16 >> 8);
|
||||
}
|
||||
|
||||
void EthercatPdo::writeInputs(uint8_t *esc)
|
||||
{
|
||||
if (esc == nullptr) {
|
||||
return;
|
||||
}
|
||||
const uint8_t activeIdx = s_inActiveIdx;
|
||||
__DMB();
|
||||
memcpy(esc, s_inBuf[activeIdx], kPdoInBytes);
|
||||
++s_inputCycles;
|
||||
}
|
||||
|
||||
bool EthercatPdo::sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
|
||||
uint16_t *off1, uint16_t *len1)
|
||||
{
|
||||
if (off0 == nullptr || len0 == nullptr || off1 == nullptr || len1 == nullptr) {
|
||||
return false;
|
||||
}
|
||||
#if !CONFIG_ECAT_SPARSE_ESC_WRITE
|
||||
return false;
|
||||
#else
|
||||
/* 仅密排布局:count 有效且两段之间确有空洞才分段。
|
||||
* magic 不对 / count 越界 / count 撑满到与段3相接 → 整包,避免陈旧空洞被误读。 */
|
||||
const uint8_t activeIdx = s_inActiveIdx;
|
||||
__DMB();
|
||||
const uint8_t *cur = s_inBuf[activeIdx];
|
||||
if (cur[ECAT_MOTW_OFF_MAGIC] != kEcatMotLayoutMagic) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t count = cur[ECAT_MOTW_OFF_COUNT];
|
||||
if (count > kEcatMotSlotsPerPdo) {
|
||||
return false;
|
||||
}
|
||||
const uint16_t headEnd =
|
||||
(uint16_t)(ECAT_MOTW_OFF_SLOTS + (uint16_t)count * kEcatMotSlotSize);
|
||||
if (headEnd >= ECAT_XRX_OFF_CH) {
|
||||
return false; /* 9 电机满配:A+B 无缝,整包更省一次 EXMC 事务 */
|
||||
}
|
||||
*off0 = 0U;
|
||||
*len0 = headEnd;
|
||||
*off1 = ECAT_XRX_OFF_CH;
|
||||
*len1 = (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
/** 段2 有效长度(稀疏:到 count*56 为止;否则整段)。 */
|
||||
uint16_t escSeg2WriteLen(const uint8_t *in)
|
||||
{
|
||||
#if CONFIG_ECAT_SPARSE_ESC_WRITE
|
||||
if (in[ECAT_MOTW_OFF_MAGIC] == kEcatMotLayoutMagic) {
|
||||
const uint8_t count = in[ECAT_MOTW_OFF_COUNT];
|
||||
if (count <= kEcatMotSlotsPerPdo) {
|
||||
const uint16_t headEnd =
|
||||
(uint16_t)(ECAT_MOTW_OFF_SLOTS + (uint16_t)count * kEcatMotSlotSize);
|
||||
if (headEnd < ECAT_XRX_OFF_CH) {
|
||||
return headEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)in;
|
||||
#endif
|
||||
return ECAT_XRX_OFF_CH;
|
||||
}
|
||||
|
||||
/** 判定是否需要写某区:无影子 / 冲刷中 / 内容变化。变化时刷新影子并重置冲刷=2。 */
|
||||
bool diffNeedWrite(const uint8_t *cur, uint16_t off, uint16_t cmpLen,
|
||||
uint8_t *shadow, bool &valid, uint8_t &flush)
|
||||
{
|
||||
const bool changed = !valid || memcmp(cur + off, shadow, cmpLen) != 0;
|
||||
if (changed) {
|
||||
memcpy(shadow, cur + off, cmpLen);
|
||||
valid = true;
|
||||
flush = 2U; /* 本拍 + 再 2 拍,冲完 3-buffer */
|
||||
return true;
|
||||
}
|
||||
if (flush > 0U) {
|
||||
--flush;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} /* anonymous namespace */
|
||||
|
||||
uint8_t EthercatPdo::escWritePlan(uint16_t *offs, uint16_t *lens)
|
||||
{
|
||||
if (offs == nullptr || lens == nullptr) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
#if !CONFIG_ECAT_DIFF_ESC_WRITE
|
||||
/* 回退:稀疏两段或整包 */
|
||||
uint8_t n = 0U;
|
||||
uint16_t o0, l0, o1, l1;
|
||||
if (sparseEscWritePlan(&o0, &l0, &o1, &l1)) {
|
||||
offs[n] = o0; lens[n] = l0; ++n;
|
||||
offs[n] = o1; lens[n] = l1; ++n;
|
||||
} else {
|
||||
offs[0] = 0U; lens[0] = kPdoInBytes; n = 1U;
|
||||
}
|
||||
return n;
|
||||
#else
|
||||
/* 三区差分:段1 桥状态 / 段2 电机密排(稀疏写有效长) / 段3+5 快照诊断。
|
||||
* 段1 计数器几乎每拍变,段2 稳定时省最大头,段3+5 多数拍稳定。
|
||||
* 无论各段是否变化,必须保证每周期必定写入 SM3 结束地址 (kPdoInBytes - 1),
|
||||
* 以触发 LAN9252 3-buffer 提交换冲,否则主站将读到冻结的旧缓冲。 */
|
||||
const uint8_t activeIdx = s_inActiveIdx;
|
||||
__DMB();
|
||||
const uint8_t *cur = s_inBuf[activeIdx];
|
||||
|
||||
uint8_t n = 0U;
|
||||
if (diffNeedWrite(cur, 0U, 32U, s_escSh1, s_escSh1Valid, s_escFlush1)) {
|
||||
offs[n] = 0U;
|
||||
lens[n] = 32U;
|
||||
++n;
|
||||
}
|
||||
if (diffNeedWrite(cur, 32U, (uint16_t)(ECAT_XRX_OFF_CH - 32U),
|
||||
s_escSh2, s_escSh2Valid, s_escFlush2)) {
|
||||
offs[n] = 32U;
|
||||
/* 写长:稀疏时只写到 count*56,但影子比对的是完整 32..543(含清零空洞)。
|
||||
* 空洞不写入 ESC 无妨:主站只按 count 迭代。 */
|
||||
const uint16_t wlen = escSeg2WriteLen(cur);
|
||||
lens[n] = (wlen > 32U) ? (uint16_t)(wlen - 32U) : 0U;
|
||||
if (lens[n] == 0U) {
|
||||
/* count=0:段2 头 8 字节仍要写(count/active/magic) */
|
||||
lens[n] = 8U;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
bool seg3Included = false;
|
||||
if (diffNeedWrite(cur, ECAT_XRX_OFF_CH, (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH),
|
||||
s_escSh3, s_escSh3Valid, s_escFlush3)) {
|
||||
offs[n] = ECAT_XRX_OFF_CH;
|
||||
lens[n] = (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH);
|
||||
++n;
|
||||
seg3Included = true;
|
||||
}
|
||||
if (!seg3Included) {
|
||||
/* 段 3 未包含时,补写末 2 字节以保证触碰 SM3 结束地址触发硬件提交 */
|
||||
offs[n] = (uint16_t)(kPdoInBytes - 2U);
|
||||
lens[n] = 2U;
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EthercatPdo::snapshotOutput(uint8_t *dst)
|
||||
{
|
||||
if (dst == nullptr) {
|
||||
return;
|
||||
}
|
||||
memcpy(dst, s_out, kPdoOutBytes);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
/* 输出探针,SSC PDI_Isr 的 PDO_OutputMapping 每周期调用(C 链接)。
|
||||
* 返回 0/1;SSC 侧声明为 UINT8,小返回值零扩展,调用约定兼容。 */
|
||||
uint8_t EcatPdo_OutputProbe(const uint8_t *head)
|
||||
{
|
||||
return EthercatPdo::outputProbe(head) ? 1U : 0U;
|
||||
}
|
||||
|
||||
void EcatPdo_ReadOutputs(const uint8_t *esc)
|
||||
{
|
||||
EthercatPdo::readOutputs(esc);
|
||||
}
|
||||
|
||||
void EcatPdo_NoteOutputMapped(void)
|
||||
{
|
||||
s_outMappedThisCycle = true;
|
||||
}
|
||||
|
||||
void EcatPdo_Process(void)
|
||||
{
|
||||
EthercatPdo::process();
|
||||
}
|
||||
|
||||
void EcatPdo_WriteInputs(uint8_t *esc)
|
||||
{
|
||||
EthercatPdo::writeInputs(esc);
|
||||
}
|
||||
|
||||
/* 稀疏写计划:SSC PDO_InputMapping 据此决定 HW_EscWriteIsr 写几段。
|
||||
* 返回 1=两段有效;0=回退整包。 */
|
||||
uint8_t EcatPdo_SparseEscPlan(uint16_t *off0, uint16_t *len0,
|
||||
uint16_t *off1, uint16_t *len1)
|
||||
{
|
||||
return EthercatPdo::sparseEscWritePlan(off0, len0, off1, len1) ? 1U : 0U;
|
||||
}
|
||||
|
||||
/* 差分写计划:SSC PDO_InputMapping 逐段 HW_EscWriteIsr。 */
|
||||
uint8_t EcatPdo_EscWritePlan(uint16_t *offs, uint16_t *lens, uint8_t maxSegs)
|
||||
{
|
||||
if (offs == nullptr || lens == nullptr || maxSegs == 0U
|
||||
|| maxSegs > EthercatPdo::kEscPlanMaxSegs) {
|
||||
return 0U;
|
||||
}
|
||||
uint16_t o[EthercatPdo::kEscPlanMaxSegs];
|
||||
uint16_t l[EthercatPdo::kEscPlanMaxSegs];
|
||||
const uint8_t n = EthercatPdo::escWritePlan(o, l);
|
||||
if (n == 0U || n > maxSegs) {
|
||||
return 0U;
|
||||
}
|
||||
for (uint8_t i = 0U; i < n; ++i) {
|
||||
offs[i] = o[i];
|
||||
lens[i] = l[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* HBI/EXMC 事务互斥(仅屏蔽 EXTI0/1/2,不关全局中断) */
|
||||
uint32_t EscExmcBus_MaskEnter(void)
|
||||
{
|
||||
return EscExmcBus::maskEnter();
|
||||
}
|
||||
|
||||
void EscExmcBus_MaskExit(uint32_t prev)
|
||||
{
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
uint32_t HW_EscExmcMaskEnter(void)
|
||||
{
|
||||
return EscExmcBus::maskEnter();
|
||||
}
|
||||
|
||||
void HW_EscExmcMaskExit(uint32_t prev)
|
||||
{
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
uint32_t EcatPdo_ProcessSeq(void)
|
||||
{
|
||||
return EthercatPdo::processSeq();
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file ethercat_pdo.h
|
||||
* @brief EtherCAT 过程数据(PDO)收发模块。
|
||||
*
|
||||
* 一个 EtherCAT 周期在 SSC 里被拆成三个回调,本类按这三步各提供一个入口:
|
||||
* 1. APPL_OutputMapping() → readOutputs() 从 ESC 取走主站下发的输出数据
|
||||
* 2. APPL_Application() → process() 跑应用逻辑(当前是 PDO↔CAN 转发)
|
||||
* 3. APPL_InputMapping() → writeInputs() 把输入数据交回 ESC(从站→主站)
|
||||
*
|
||||
* 缓冲区归本类所有,dc_app.c 只保留 SSC 对象字典(0x7000/0x6000 的 packed 结构体
|
||||
* 是 SDO 存储,必须留在 C 侧)与三行 extern "C" 转发。这样"收发"这件事只有一个
|
||||
* 归属:想加新的过程数据语义只改本类,不必再动 SSC 移植层。
|
||||
*
|
||||
* @note 输出/输入**长度不相等**(64B / 384B),为电机控制台腾出
|
||||
* 状态窗口空间。长度常量统一由 ethercat_can_bridge.h 提供,避免三处各写
|
||||
* 一个魔数后失配。
|
||||
*
|
||||
* @note 上下文:free-run 时三个回调都在主循环(ECAT_Main / ECAT_Application);
|
||||
* 启用 DC 后 readOutputs/writeInputs 改由 Sync0_Isr(preempt=0) 调用,而
|
||||
* process() 仍在主循环。因此 s_out/s_in 是跨上下文共享的——两者都只在
|
||||
* 单一写者下被整块读写,且 CM4 上 memcpy 的撕裂最坏只会让某个周期读到
|
||||
* 上/下半帧混合。对 seq 沿触发语义无害(seq 是单字节,要么旧要么新,
|
||||
* 不会产生第三个值),故不加临界区,避免在 ISR 里关中断拖累抖动。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_PDO_H
|
||||
#define COMM_ETHERCAT_PDO_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "comm/ethercat_can_bridge.h"
|
||||
|
||||
/**
|
||||
* @brief EtherCAT 过程数据收发。
|
||||
* @note 全静态类,与 EthercatSlave / EthercatSync 同风格。
|
||||
*/
|
||||
class EthercatPdo {
|
||||
public:
|
||||
/** @brief 输出过程数据长度(主站→从站)。 */
|
||||
static constexpr uint16_t kPdoOutBytes = kEcatPdoOutBytes;
|
||||
/** @brief 输入过程数据长度(从站→主站)。 */
|
||||
static constexpr uint16_t kPdoInBytes = kEcatPdoInBytes;
|
||||
|
||||
/**
|
||||
* @brief 取走主站下发的输出过程数据(主站→从站)。
|
||||
* @param[in] esc 指向 ESC 输出缓冲的 kPdoOutBytes 字节。
|
||||
* @note 由 APPL_OutputMapping() 每周期调用;只做搬运不做业务,
|
||||
* 业务在 process() 里跑,保证 ISR 上下文(DC 模式)足够短。
|
||||
*/
|
||||
static void readOutputs(const uint8_t *esc);
|
||||
|
||||
/**
|
||||
* @brief 输出探针(输出按需全量读,配合 SSC PDO_OutputMapping)。
|
||||
* @param[in] head 输出 PDO 头 12B(覆盖 SEQ@0 / WIN@8 / FLAGS@3)。
|
||||
* @return true=需要全量读(seq 沿或清统计请求);false=本周期跳过全量读。
|
||||
* @note 每周期由 PDI_Isr 在读 ESC 前调用:做 seq 基准 priming 与沿判定;
|
||||
* s_lastSeq 的推进仍留在全量路径(processOutput)。不再
|
||||
* 维护 win_base(段 2 电机整轮密排,无滑窗)。
|
||||
*/
|
||||
static bool outputProbe(const uint8_t *head);
|
||||
|
||||
/**
|
||||
* @brief 跑一个应用周期:消费输出、生成输入。
|
||||
* @note 由 APPL_Application() 在主循环调用。
|
||||
*/
|
||||
static void process();
|
||||
|
||||
/**
|
||||
* @brief 交回输入过程数据(从站→主站)。
|
||||
* @param[out] esc 指向 ESC 输入缓冲的 kPdoInBytes 字节。
|
||||
* @note 由 APPL_InputMapping() 每周期调用。只填 RAM/对象存储;
|
||||
* 真正写 ESC 的字节量由 PDO_InputMapping 经 sparseEscWritePlan 决定。
|
||||
*/
|
||||
static void writeInputs(uint8_t *esc);
|
||||
|
||||
/**
|
||||
* @brief 稀疏写 ESC 的两段计划:段1+段2有效区、段3+段5;跳过空槽区。
|
||||
* @param[out] off0/len0 第一段相对 PDO 起点的偏移与长度
|
||||
* @param[out] off1/len1 第二段
|
||||
* @return true=可按两段写;false=应回退整包 600B(开关关/非密排/count 越界/两段相接)
|
||||
* @note 必须在 writeInputs() 之后调用,读的是已发布的 s_in。
|
||||
*/
|
||||
static bool sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
|
||||
uint16_t *off1, uint16_t *len1);
|
||||
|
||||
/**
|
||||
* @brief 输入 PDO 写 ESC 的多段差分计划(未启用时回退稀疏/整包)。
|
||||
* @param[out] offs 段偏移数组,至少 kEscPlanMaxSegs 项
|
||||
* @param[out] lens 段长度数组,至少 kEscPlanMaxSegs 项
|
||||
* @return 段数 1..kEscPlanMaxSegs;调用方逐段 HW_EscWriteIsr。
|
||||
* @note 必须在 writeInputs()/s_in 发布之后、每拍一次调用。按三区差分:
|
||||
* 段1(0..31 桥状态)、段2(32..543 电机密排,写时用稀疏有效长)、
|
||||
* 段3+5(544..599)。变化后连续写 3 拍冲完 3-buffer 才允许跳过。
|
||||
* 全段跳过时返回「末 2 字节」一段以完成 SM 事件。有副作用。
|
||||
*/
|
||||
static uint8_t escWritePlan(uint16_t *offs, uint16_t *lens);
|
||||
|
||||
/** escWritePlan 最多段数。 */
|
||||
static constexpr uint8_t kEscPlanMaxSegs = 4U;
|
||||
|
||||
/** @brief 累计取走输出的周期数(主站在发过程数据的证据)。 */
|
||||
static uint32_t outputCycles() { return s_outputCycles; }
|
||||
|
||||
/** @brief 累计交回输入的周期数。 */
|
||||
static uint32_t inputCycles() { return s_inputCycles; }
|
||||
/** @brief process() 完整填完一拍输入的序号。新鲜度救援看这个,而不是
|
||||
* InputMapping 的 s_inMapSeq——后者只证明「在拷陈旧 s_in」。 */
|
||||
static uint32_t processSeq() { return s_processSeq; }
|
||||
/** @brief process() 被重入拦下的次数。持续增长 = 跨上下文抢占确实在发生
|
||||
* (PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop 互相打断),也就证明了
|
||||
* 批量区跨轮次拼接的成因;恒为 0 则说明该机制推断有误。 */
|
||||
static uint32_t reentryCount() { return s_reentryCount; }
|
||||
|
||||
/** @brief 是否已开始过程数据交换(收到过至少一个输出周期)。 */
|
||||
static bool isExchanging() { return s_outputCycles != 0U; }
|
||||
|
||||
/**
|
||||
* @brief 拷出最近一份输出过程数据快照(诊断用)。
|
||||
* @param[out] dst 至少 kPdoOutBytes 字节。
|
||||
*/
|
||||
static void snapshotOutput(uint8_t *dst);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 填充输入 PDO 段 5 设备诊断区(尾部 8 字节)。
|
||||
* @param[out] in 全长输入过程数据缓冲。
|
||||
* @note 布局常量 ECAT_DIAG_OFF_* 见 ethercat_can_bridge.h;当前携带
|
||||
* CPU 负载率(0.1% 单位) + 窗口序号,取自 sys/cpu_load.h。
|
||||
*/
|
||||
static void fillDiagTail(uint8_t *in);
|
||||
|
||||
/** @brief 最近一份主站下发的输出数据。
|
||||
* @note alignas(4):让整块 memcpy(writeInputs / [DIFF-STATS] 的快照拷贝)按
|
||||
* 字宽走而非逐字节。比对主循环本身不依赖它——已改成手拼 4 字节移位,
|
||||
* 因为 -Oz 下 memcpy(&a,p,4) 会编成函数调用,见 ethercat_diff_stats.cpp。 */
|
||||
alignas(4) static uint8_t s_out[kPdoOutBytes];
|
||||
/** @brief 待交回主站的输入数据(已发布的完整镜像,writeInputs 只读它)。 */
|
||||
alignas(4) static uint8_t s_in[kPdoInBytes];
|
||||
/** @brief 输入 PDO 工作缓冲:buildInput 填这里,填完整块发布到 s_in。
|
||||
* ECAT_Application() 会在 PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop 四个
|
||||
* 上下文被调用并互相抢占,直接写 s_in 会让读侧取到跨轮次的半成品。 */
|
||||
alignas(4) static uint8_t s_build[kPdoInBytes];
|
||||
/** @brief 写 ESC 差分影子:段1 / 段2 / 段3+5 三区。 */
|
||||
alignas(4) static uint8_t s_escSh1[32];
|
||||
alignas(4) static uint8_t s_escSh2[ECAT_XRX_OFF_CH - 32U];
|
||||
alignas(4) static uint8_t s_escSh3[kEcatPdoInBytes - ECAT_XRX_OFF_CH];
|
||||
static uint8_t s_escFlush1;
|
||||
static uint8_t s_escFlush2;
|
||||
static uint8_t s_escFlush3;
|
||||
static bool s_escSh1Valid;
|
||||
static bool s_escSh2Valid;
|
||||
static bool s_escSh3Valid;
|
||||
/** @brief process() 被重入拦下的次数(诊断用,见 reentryCount())。 */
|
||||
static volatile uint32_t s_reentryCount;
|
||||
|
||||
static volatile uint32_t s_outputCycles;
|
||||
static volatile uint32_t s_inputCycles;
|
||||
static volatile uint32_t s_processSeq;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
/** @brief 供 dc_app.c(C 文件)调用:取走输出过程数据。 */
|
||||
void EcatPdo_ReadOutputs(const uint8_t *esc);
|
||||
/** @brief 供 dc_app.c 调用:跑一个应用周期。 */
|
||||
void EcatPdo_Process(void);
|
||||
/** @brief 供 dc_app.c 调用:交回输入过程数据。 */
|
||||
void EcatPdo_WriteInputs(uint8_t *esc);
|
||||
/** @brief 供 dc_app.c 新鲜度救援:process() 是否还在推进。 */
|
||||
uint32_t EcatPdo_ProcessSeq(void);
|
||||
/** @brief PDO_OutputMapping 探针命中并完成全量读后调用(本拍 s_out 已新鲜)。 */
|
||||
void EcatPdo_NoteOutputMapped(void);
|
||||
}
|
||||
|
||||
#endif /* COMM_ETHERCAT_PDO_H */
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file ethercat_pdo_size.h
|
||||
* @brief EtherCAT 过程数据长度 —— C / C++ 共用的唯一真源(1.5.55)。
|
||||
*
|
||||
* 过程数据长度必须在**五处**保持一致,任一处失配的表现都是运行期 AL 错误
|
||||
* (PREOP→SAFEOP 报 0x001A/0x0011),排查成本远高于编译期报错:
|
||||
*
|
||||
* 1. comm/ssc/port/ecat_def.h MAX_PD_OUTPUT_SIZE / MAX_PD_INPUT_SIZE(编译门限)
|
||||
* 2. comm/ssc/port/dc_app.c 0x7000/0x6000 对象尺寸 + APPL_GenerateMapping
|
||||
* 3. comm/ssc/port/gen_eeprom.py PD_OUT_SIZE / PD_IN_SIZE(SII,需重刷 EEPROM)
|
||||
* 4. comm/ssc/port/DCU_ESI.xml SM 长度 + PDO 条目
|
||||
* 5. comm/ethercat_can_bridge.h 桥接层字节布局
|
||||
*
|
||||
* 本头只放纯 #define,故 C(dc_app.c)与 C++(ethercat_can_bridge.h)都能包含,
|
||||
* 双方各自用 _Static_assert / static_assert 绑到这两个宏上。改尺寸时先改这里,
|
||||
* 编译器会把所有没跟上的地方报出来;EEPROM 侧(3/4)没法编译期检查,改完必须
|
||||
* 重新生成 SII 并**断电冷启动**,ESC 只在冷启动时重新加载 EEPROM。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_PDO_SIZE_H
|
||||
#define COMM_ETHERCAT_PDO_SIZE_H
|
||||
|
||||
/**
|
||||
* ── 1.5.56 扩到 DPRAM 硬上限(64/384 → 384/640)──
|
||||
*
|
||||
* 「一次能发多大」的真实约束不是 EtherCAT 协议(单帧可载 ~1486B 数据报),
|
||||
* 而是 LAN9252 的 4KB DPRAM 加 3-buffer 同步管理器:
|
||||
*
|
||||
* 邮箱 SM0/SM1 各 256B 固定占 0x1000-0x13FF;
|
||||
* 过程数据区只剩 0x1400-0x1FFF = 3072B;
|
||||
* SM2/SM3 都是 3-buffer 模式(control 0x64/0x20,bit1=0),实占 3×size。
|
||||
* ⇒ (out + in) × 3 ≤ 3072 ⇒ **out + in ≤ 1024B 是硬上限**。
|
||||
*
|
||||
* 384 + 640 = 1024,SM2 占 0x1400-0x187F、SM3 占 0x1880-0x1FFF,
|
||||
* 恰好用满到最后一字节,没有任何浪费也没有越界。
|
||||
*
|
||||
* 其余曾疑似的天花板都已实测排除,都不是瓶颈:
|
||||
* - SII/EEPROM:640B 输入需 80 个映射条目,SII 共用 1284/4096B(余 2812B);
|
||||
* nEntry 是单字节字段,80 远低于 255 上限。
|
||||
* - EXMC/HBI 搬运:满载 1024B ≈ 218us/周期(每 DWORD 约 0.85us),
|
||||
* 4ms 周期只占 5.4%,甚至 1ms 周期也留有余量。
|
||||
* - MAX_PD_*_SIZE 只影响 ecatappl.c 的 aPdOutputData/aPdInputData 静态数组,
|
||||
* 共 1024B RAM,在 128KB SRAM 上无压力。
|
||||
*
|
||||
* 若将来还要更大,只有两条路(都有代价,非必要不动):
|
||||
* a) 邮箱压到 MIN_MBX_SIZE(0x22) 级别 → 上限约 1280B,但会拖慢 SDO/FoE;
|
||||
* b) 改 1-buffer 模式(control bit1=1)→ 上限 3072B,但失去三缓冲的
|
||||
* 读写并发保护,主从同时访问同一缓冲会读到半新半旧的过程数据。
|
||||
*/
|
||||
|
||||
/** 输出过程数据字节数(主站→从站,SM2):48×64bit。 */
|
||||
#define ECAT_PDO_OUT_BYTES 384
|
||||
|
||||
/**
|
||||
* 输入过程数据字节数(从站→主站,SM3):75×64bit = 600B。
|
||||
* 布局(1.6.21 密排):段1..2 0..543 · 段3 544..583 · 段5 584..595。
|
||||
* 改长度须同步 bridge/dc_app/gen_eeprom/ESI/主站 decode,并烧 EEPROM 冷启动。
|
||||
*
|
||||
* 注意:DWT sub10 量的是从站 PDI 写 ESC 耗时,**不是**主站 rx 段。
|
||||
* 主站 rx 主要由线速往返决定(约 1KB @100Mbit ≈ 80µs 量级),两边 µs 不要对拍。
|
||||
*/
|
||||
#define ECAT_PDO_IN_BYTES 600
|
||||
|
||||
/** DPRAM 中过程数据区起址与 SM 缓冲数 —— 上限推导的依据,供静态断言使用。 */
|
||||
#define ECAT_PD_AREA_START 0x1400
|
||||
#define ECAT_PD_AREA_END 0x1FFF
|
||||
#define ECAT_PD_SM_BUFFERS 3
|
||||
|
||||
#if (ECAT_PDO_OUT_BYTES + ECAT_PDO_IN_BYTES) * ECAT_PD_SM_BUFFERS > \
|
||||
(ECAT_PD_AREA_END - ECAT_PD_AREA_START + 1)
|
||||
#error "out+in 超出 LAN9252 过程数据区(3-buffer 模式下 (out+in)*3 必须 <= 3072)"
|
||||
#endif
|
||||
|
||||
#endif /* COMM_ETHERCAT_PDO_SIZE_H */
|
||||
@@ -1,153 +1,164 @@
|
||||
/**
|
||||
* @file ethercat_slave.cpp
|
||||
* @brief EtherCAT 从站周期处理实现。
|
||||
* @brief EtherCAT 从站 init 探针实现。
|
||||
*
|
||||
* 运行期 PDO 交换由 SSC 协议栈完成(Sync0_Isr → ECAT_Application →
|
||||
* dc_app.c 的 APPL_InputMapping/APPL_OutputMapping),本文件只负责
|
||||
* init 阶段的 HBI 探活、芯片 ID 与 AL 状态诊断。
|
||||
*/
|
||||
#include "comm/ethercat_slave.h"
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "main.h"
|
||||
#include <cstdio>
|
||||
|
||||
PdoBuffer EthercatSlave::s_inputs[3] = {};
|
||||
PdoBuffer EthercatSlave::s_outputs[3] = {};
|
||||
PdoBuffer EthercatSlave::s_appInputs = {};
|
||||
PdoBuffer EthercatSlave::s_appOutputs = {};
|
||||
volatile uint8_t EthercatSlave::s_inputWriteIdx = 0U;
|
||||
volatile uint8_t EthercatSlave::s_outputReadIdx = 0U;
|
||||
volatile uint16_t EthercatSlave::s_periodicSeq = 0U;
|
||||
uint16_t EthercatSlave::s_processedSeq = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrMaxCycles = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrAvgCycles = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrCount = 0U;
|
||||
bool EthercatSlave::s_escReady = false;
|
||||
uint32_t EthercatSlave::s_chipId = 0U;
|
||||
uint8_t EthercatSlave::s_alState = 0U;
|
||||
|
||||
/* ISR 执行时间评估:
|
||||
* - 若 DWT 实测 max > CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD(5µs),需评估:
|
||||
* a) 减小 CONFIG_ETHERCAT_PDO_SIZE(默认 32 字节)
|
||||
* b) 优化 EXMC 时序参数(esc_exmc_bus.cpp 中 read_latency/write_latency)
|
||||
* - 若 max ≤ 5µs,接受现状;preempt=0 阻塞窗口 ≤ 5µs,CAN/SPI 中断需容忍此窗口。
|
||||
* SPI0 从机在此窗口内不会 RX 溢出(假设 SPI 时钟 ≤ 10MHz,5µs ≥ 50 bit,远小于一帧)。 */
|
||||
|
||||
void EthercatSlave::init()
|
||||
{
|
||||
EscLan9252::init();
|
||||
ECAT_LOGI("[ECAT][SLAVE] init: begin\r\n");
|
||||
|
||||
/* 第 1 层调试可观测性:读取芯片 ID 并打印 selfTest 结果 */
|
||||
if (!EscLan9252::init()) {
|
||||
/* EXMC 总线未就绪:无法继续读芯片 ID 与 AL 状态,统一上报 FAIL。 */
|
||||
s_chipId = 0U;
|
||||
s_alState = 0U;
|
||||
s_escReady = false;
|
||||
LOG_ERR("[ECAT][SLAVE] init: FAIL, ESC bus not ready\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 第 1 层调试可观测性:读取 BYTE_TEST 寄存器(0x064,默认 0x87654321)并打印 selfTest 结果 */
|
||||
s_chipId = EscLan9252::readChipId();
|
||||
const bool chipIdOk = (s_chipId == 0x87654321U);
|
||||
ECAT_LOGI("[ECAT][SLAVE] byte_test=0x%08lX expected=0x%08lX selfTest=%s\r\n",
|
||||
(unsigned long)s_chipId, (unsigned long)0x87654321UL,
|
||||
chipIdOk ? "PASS" : "FAIL");
|
||||
|
||||
/* AL 状态守卫:仅在芯片 ID 匹配时读取 AL 状态(ID 不匹配时 EXMC 可能读回 0/0xFFFF,无意义)。
|
||||
* AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */
|
||||
s_alState = 0U;
|
||||
uint16_t alStatusReg = 0U; /* 0x0130:AL Status(bit0-3=state,bit8-15=AL Status Code) */
|
||||
if (chipIdOk) {
|
||||
s_alState = (uint8_t)(EscLan9252::initOnlyReadReg(0x0130U) & 0x000FU);
|
||||
alStatusReg = EscLan9252::initOnlyReadReg(0x0130U);
|
||||
s_alState = (uint8_t)(alStatusReg & 0x000FU);
|
||||
}
|
||||
const uint8_t alCode = (uint8_t)((alStatusReg >> 8U) & 0xFFU);
|
||||
s_escReady = chipIdOk && (s_alState >= AL_STATE_SAFEOP);
|
||||
|
||||
/* AL 状态码 → 可读字符串(EtherCAT 状态机定义,见 ETG.1000.6)。
|
||||
* 0x01=INIT, 0x02=PREOP, 0x03=BOOT, 0x04=SAFEOP, 0x08=OP。
|
||||
* 本工程 AL_STATE_SAFEOP=0x04 与官方定义一致;守卫 s_alState>=0x04,
|
||||
* 因此 OP(0x08) 也会通过,SAFEOP(0x04) 刚好通过,BOOT/PREOP/INIT 不通过。 */
|
||||
const char *alStateName = "UNKNOWN";
|
||||
switch (s_alState) {
|
||||
case 0x00U: alStateName = "NONE"; break;
|
||||
case 0x01U: alStateName = "INIT"; break;
|
||||
case 0x02U: alStateName = "PREOP"; break;
|
||||
case 0x03U: alStateName = "BOOT"; break;
|
||||
case 0x04U: alStateName = "SAFEOP"; break;
|
||||
case 0x08U: alStateName = "OP"; break;
|
||||
default: break;
|
||||
}
|
||||
/* AL Status Code(寄存器 0x0130 高 8 位):非 0 表示从站进入错误状态
|
||||
* (如 SM Watchdog=0x1B、FMMU 配置错、邮箱协议错),需主站介入。 */
|
||||
|
||||
/* ============================================================
|
||||
* bring-up 诊断:经 CSR 间接端口读三个关键寄存器
|
||||
* + 一次写回环,确认 EtherCAT 核心侧状态。
|
||||
* 注意主站读身份/AL 走 ESC 以太网侧 + EEPROM(SII),完全不经过 GD32 的 PDI,
|
||||
* 所以"主站能读到身份"不能证明下面任何一项成立。
|
||||
* 访问必须经 ECAT_CSR_CMD/CSR_DATA(EtherCAT 核心寄存器不在 HBI 直接
|
||||
* 地址空间,DS 12.11);直读索引恒读全 0。
|
||||
* 交叉验证用 tools/ecat/ecat_esc_regs.py 从网络侧读同样的寄存器。
|
||||
* 本段在 EthercatSync::init() 使能 EXTI 之前执行,满足 initOnly* 的 INIT-ONLY 约束。
|
||||
* ============================================================ */
|
||||
uint16_t dlStatus = 0U;
|
||||
if (chipIdOk) {
|
||||
/* (1) DL Status 0x0110 bit0 = PDI operational。
|
||||
* ESC 只有认为 PDI 就绪时,才接受 PDI 侧(GD32 经 HBI)写入的 AL Status;
|
||||
* 该位为 0 时 SSC 的 SetALStatus 被静默丢弃 -> 主站恒读 INIT/code=0。 */
|
||||
dlStatus = EscLan9252::initOnlyReadReg(0x0110U);
|
||||
/* (2) ESC Configuration 0x0140:PDI 类型是否真被 EEPROM 加载为 HBI 16-bit(0x8D)。
|
||||
* 读回 0x00/0xFF 说明 EEPROM 的 PDI Control 未生效、ESC 的 PDI 没启用
|
||||
* (此时 HBI 读寄存器仍可能照常工作,极易误判为"硬件全通")。 */
|
||||
const uint16_t escCfg = EscLan9252::initOnlyReadReg(0x0140U);
|
||||
/* (3) EEPROM 接口状态 0x0502-0x0503(16 位字)。位定义按 ETG.1000.4 表 49
|
||||
* 与 SSC esc.h:bit11=Checksum Error、bit12=EEPROM loading status(0=OK)、
|
||||
* bit13=Missing EEPROM Acknowledge、bit14=Write Error、bit15=Busy。
|
||||
* 0 全部代表正常。
|
||||
* 真正的"EEPROM 已加载/PDI 可用"判据是 DL_STATUS(0x0110) bit0,见上方 (1)。 */
|
||||
const uint16_t eepCsr = EscLan9252::initOnlyReadReg(0x0502U);
|
||||
LOG_ERR("[ECAT][DIAG] DL_STATUS(0x110)=0x%04X pdi_oper=%u | "
|
||||
"ESC_CFG(0x140)=0x%04X pdi_type=0x%02X(expect 0x8D) | "
|
||||
"EEP_CSR(0x502)=0x%04X crc_err=%u load_err=%u ack_err=%u wr_err=%u busy=%u"
|
||||
" (全 0=正常)\r\n",
|
||||
(unsigned)dlStatus, (unsigned)(dlStatus & 0x0001U),
|
||||
(unsigned)escCfg, (unsigned)(escCfg & 0x00FFU),
|
||||
(unsigned)eepCsr,
|
||||
(unsigned)((eepCsr >> 11U) & 0x1U),
|
||||
(unsigned)((eepCsr >> 12U) & 0x1U),
|
||||
(unsigned)((eepCsr >> 13U) & 0x1U),
|
||||
(unsigned)((eepCsr >> 14U) & 0x1U),
|
||||
(unsigned)((eepCsr >> 15U) & 0x1U));
|
||||
|
||||
/* (4) CSR 端口写回环:用 AL Event Mask(0x0204)做写-读-还原。该寄存器按设计
|
||||
* 就是 PDI 侧可写(PDI 用它选择哪些 AL Event 产生 IRQ),不像 0x0012 Station
|
||||
* Alias 在部分 ESC 上对 PDI 只读(会造成假 FAIL);本段在 MainInit/HW_Init
|
||||
* 之前执行,还原后不影响 SSC 后续建立的 mask 影子。
|
||||
* 回环必须经 ECAT_CSR_CMD/CSR_DATA 间接端口执行。 */
|
||||
const uint16_t maskSaved = EscLan9252::initOnlyReadReg(0x0204U);
|
||||
EscLan9252::initOnlyWriteReg(0x0204U, 0x0A50U);
|
||||
const uint16_t maskRead = EscLan9252::initOnlyReadReg(0x0204U);
|
||||
EscLan9252::initOnlyWriteReg(0x0204U, maskSaved); /* 还原,交回 SSC 管理 */
|
||||
if (maskRead == 0x0A50U) {
|
||||
LOG_ERR("[ECAT][DIAG] CSR write loopback PASS (0x204 wrote 0x0A50 read back OK)\r\n");
|
||||
} else {
|
||||
LOG_ERR("[ECAT][DIAG] CSR write loopback FAIL: wrote 0x0A50 read 0x%04X"
|
||||
" -> 先查 CSR_CMD(0x304) busy 是否自清/EXMC 时序,再疑硬件\r\n",
|
||||
(unsigned)maskRead);
|
||||
}
|
||||
}
|
||||
|
||||
/* 是否有主站在线:DL_STATUS(0x0110) 的 Physical Link 位 bit4/5/6 对应 Port0/1/2
|
||||
* (DS 12.14.19)。三个端口都无 link = 网线未插/无主站,此时 AL 状态机根本没人驱动
|
||||
* (AL 迁移由主站写 AL Control 0x0120 触发,从站不自跳),停在 INIT 是唯一正确行为。
|
||||
* 参考:接主站时 DL_STATUS 通常为 0x5611(Port0 有 link 有 comm);
|
||||
* 空载时为 0x5503(三口 loop closed、link/comm 全 0)。 */
|
||||
const bool anyPhyLink = chipIdOk && ((dlStatus & 0x0070U) != 0U);
|
||||
|
||||
if (!chipIdOk) {
|
||||
printf("[ECAT] ESC chip id=0x%08lX selfTest=FAIL\r\n", (unsigned long)s_chipId);
|
||||
printf("[ECAT] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
|
||||
LOG_ERR("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
|
||||
} else if (s_alState < AL_STATE_SAFEOP) {
|
||||
printf("[ECAT] ESC chip id=0x%08lX selfTest=PASS, but AL state=0x%02X < SAFEOP(0x%02X)\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState, (unsigned)AL_STATE_SAFEOP);
|
||||
printf("[ECAT] SYNC0 will not fire, full ESC config requires SSC integration\r\n");
|
||||
/* 区分"等主站"与"真故障":两者 AL 都 < SAFEOP,但只有后者需要排查。
|
||||
* 冷启动无主站时必然走到这里,报 WAIT_MASTER 而非 FAIL。 */
|
||||
if (!anyPhyLink) {
|
||||
LOG_ERR("[ECAT][SLAVE] WAIT_MASTER: no PHY link (DL_STATUS=0x%04X), "
|
||||
"AL=0x%02X(%s) 由主站驱动,空载停在 INIT 属预期\r\n",
|
||||
(unsigned)dlStatus, (unsigned)s_alState, alStateName);
|
||||
} else {
|
||||
LOG_ERR("[ECAT][SLAVE] chip id PASS, PHY link up (DL_STATUS=0x%04X) but "
|
||||
"AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
|
||||
(unsigned)dlStatus, (unsigned)s_alState, alStateName,
|
||||
(unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
|
||||
LOG_ERR("[ECAT][SLAVE] SYNC0 未触发:检查主站是否逐级 write_state"
|
||||
" (PREOP->config_map->SAFEOP->OP) 并已配置 DC\r\n");
|
||||
}
|
||||
} else {
|
||||
printf("[ECAT] ESC ready, chip id=0x%08lX, AL state=0x%02X\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState);
|
||||
ECAT_LOGI("[ECAT][SLAVE] ESC ready, chip id=0x%08lX, AL state=0x%02X(%s), code=0x%02X\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
|
||||
}
|
||||
|
||||
s_inputWriteIdx = 0U;
|
||||
s_outputReadIdx = 0U;
|
||||
s_periodicSeq = 0U;
|
||||
s_processedSeq = 0U;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EthercatSlave::periodicIsr()
|
||||
{
|
||||
const uint32_t cycStart = DWT->CYCCNT;
|
||||
|
||||
/* 1. 读取输入 PDO 到当前写入缓冲 */
|
||||
EscLan9252::readPdi(CONFIG_ESC_PDI_INPUT_OFFSET, s_inputs[s_inputWriteIdx].data, CONFIG_ETHERCAT_PDO_SIZE);
|
||||
s_inputs[s_inputWriteIdx].len = CONFIG_ETHERCAT_PDO_SIZE;
|
||||
s_inputWriteIdx = (s_inputWriteIdx + 1U) % 3U;
|
||||
|
||||
/* 2. 写入输出 PDO(从当前读取缓冲) */
|
||||
EscLan9252::writePdi(CONFIG_ESC_PDI_OUTPUT_OFFSET, s_outputs[s_outputReadIdx].data, s_outputs[s_outputReadIdx].len);
|
||||
s_outputReadIdx = (s_outputReadIdx + 1U) % 3U;
|
||||
|
||||
/* 3. 递增周期序列号通知主循环(替代 TaskManager::set,避免 BASEPRI 竞态) */
|
||||
s_periodicSeq++;
|
||||
|
||||
const uint32_t elapsed = DWT->CYCCNT - cycStart;
|
||||
s_isrCount++;
|
||||
if (elapsed > s_isrMaxCycles) {
|
||||
s_isrMaxCycles = elapsed;
|
||||
/* 终判措辞:PDI/EEPROM 就绪但只差主站时报 WAIT_MASTER 而非 FAIL——本函数的
|
||||
* s_escReady 要求 AL>=SAFEOP,无主站永远达不到,而 SSC 放行门控实际用的是
|
||||
* isBusReady()&&isCoreReady()(见 initial.cpp),两者结论相反不应都叫 FAIL。 */
|
||||
const char *verdict;
|
||||
if (s_escReady) {
|
||||
verdict = "OK";
|
||||
} else if (chipIdOk && EscLan9252::isCoreReady() && !anyPhyLink) {
|
||||
verdict = "WAIT_MASTER (PDI 就绪,等主站接入)";
|
||||
} else {
|
||||
verdict = "FAIL(partial)";
|
||||
}
|
||||
/* 滑动平均:avg = avg + (elapsed - avg) / 16 (近似 EMA,避免除法精度问题) */
|
||||
s_isrAvgCycles = s_isrAvgCycles + ((int32_t)elapsed - (int32_t)s_isrAvgCycles) / 16;
|
||||
}
|
||||
|
||||
const PdoBuffer* EthercatSlave::getLatestInputs()
|
||||
{
|
||||
/* 返回最新完成的缓冲 = (writeIdx + 2) % 3,与 ISR 当前写入位置隔一个 */
|
||||
return &s_inputs[(s_inputWriteIdx + 2U) % 3U];
|
||||
}
|
||||
|
||||
void EthercatSlave::setOutputs(const PdoBuffer *outputs)
|
||||
{
|
||||
if (outputs == nullptr) {
|
||||
return;
|
||||
}
|
||||
/* 写入与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限。
|
||||
* 2 周期宽限下拷贝竞态窗口消除,前提是主循环处理时间 < 2 × 周期时间。 */
|
||||
const uint8_t write_idx = (s_outputReadIdx + 2U) % 3U;
|
||||
s_outputs[write_idx] = *outputs;
|
||||
}
|
||||
|
||||
void EthercatSlave::process()
|
||||
{
|
||||
/* 应用层 PDO 桥接(非实时,由主循环周期序列号或 ESC IRQ 触发)。
|
||||
* 运行期 PDO 交换仅通过 getLatestInputs()/setOutputs() 三缓冲进行;ESC 寄存器访问仅限 init 阶段。
|
||||
*
|
||||
* 框架级桥接:从三缓冲取最新输入到 s_appInputs,把 s_appOutputs 推回三缓冲。
|
||||
* TODO: 应用对象字典映射(具体输入字节对应哪个应用变量,如字节 0-3=温度浮点),
|
||||
* 由后续应用层 spec 定义;应用层通过 getApplicationInputs()/setApplicationOutputs() 访问快照。 */
|
||||
const PdoBuffer *latest = getLatestInputs();
|
||||
if (latest != nullptr) {
|
||||
s_appInputs = *latest;
|
||||
}
|
||||
setOutputs(&s_appOutputs);
|
||||
}
|
||||
|
||||
const PdoBuffer* EthercatSlave::getApplicationInputs()
|
||||
{
|
||||
return &s_appInputs;
|
||||
}
|
||||
|
||||
void EthercatSlave::setApplicationOutputs(const PdoBuffer *outputs)
|
||||
{
|
||||
if (outputs == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_appOutputs = *outputs;
|
||||
}
|
||||
|
||||
bool EthercatSlave::hasPendingPeriodic()
|
||||
{
|
||||
return s_periodicSeq != s_processedSeq;
|
||||
}
|
||||
|
||||
void EthercatSlave::markPeriodicProcessed()
|
||||
{
|
||||
s_processedSeq = s_periodicSeq;
|
||||
}
|
||||
|
||||
void EthercatSlave::getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count)
|
||||
{
|
||||
if (maxCycles) *maxCycles = s_isrMaxCycles;
|
||||
if (avgCycles) *avgCycles = s_isrAvgCycles;
|
||||
if (count) *count = s_isrCount;
|
||||
LOG_ERR("[ECAT][SLAVE] init: %s\r\n", verdict);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
/**
|
||||
* @file ethercat_slave.h
|
||||
* @brief EtherCAT 从站周期处理接口与 PDO 三缓冲(triple buffer)结构。
|
||||
* @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入;
|
||||
* 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。
|
||||
*
|
||||
* 通知路径分离:
|
||||
* 周期路径(periodicIsr)用 s_periodicSeq 序列号通知主循环;
|
||||
* 事件路径(ESC IRQ)用 TASK_ETHERCAT_PROCESS 标志。
|
||||
* 两者独立,避免 preempt=0 ISR 与 BASEPRI 临界区的 RMW 竞态。
|
||||
* @brief EtherCAT 从站 init 探针与运行状态查询接口。
|
||||
* @note 运行期 PDO 交换由 SSC 协议栈完成 —— 周期路径为 Sync0_Isr →
|
||||
* ECAT_Application → APPL_InputMapping/OutputMapping(comm/ssc/port/dc_app.c),
|
||||
* 主循环路径为 MainLoop → ECAT_Main。本类仅保留 init 探针与诊断查询。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_SLAVE_H
|
||||
#define COMM_ETHERCAT_SLAVE_H
|
||||
@@ -15,129 +11,27 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
/** @brief 默认 PDO 大小(可按需调整)。 */
|
||||
constexpr size_t CONFIG_ETHERCAT_PDO_SIZE = 32U;
|
||||
|
||||
/** @brief EtherCAT AL 状态码(EtherCAT 协议定义,ESC AL Status 寄存器低 4 位)。 */
|
||||
constexpr uint8_t AL_STATE_INIT = 0x01U; /**< INIT 状态。 */
|
||||
constexpr uint8_t AL_STATE_PREOP = 0x02U; /**< PREOP 状态。 */
|
||||
constexpr uint8_t AL_STATE_BOOT = 0x03U; /**< BOOT 状态。 */
|
||||
constexpr uint8_t AL_STATE_SAFEOP = 0x04U; /**< SAFEOP 状态(SYNC0 可触发下限)。 */
|
||||
constexpr uint8_t AL_STATE_OP = 0x08U; /**< OP 状态。 */
|
||||
|
||||
/**
|
||||
* @brief PDO 数据缓冲(输入/输出共用结构)。
|
||||
* @note 周期 ISR 与应用层通过三缓冲无锁交换。
|
||||
*/
|
||||
struct PdoBuffer {
|
||||
uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */
|
||||
size_t len = 0U; /**< 有效数据长度。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief EtherCAT 从站周期处理。
|
||||
* @note 周期路径(periodicIsr)必须极简:读输入→写输出→模 3 轮转指针→递增序列号。
|
||||
* @brief EtherCAT 从站 init 探针与状态查询。
|
||||
*/
|
||||
class EthercatSlave {
|
||||
public:
|
||||
/** @brief 初始化 ESC 与三缓冲。 */
|
||||
/** @brief 初始化 ESC(HBI 探活 + 芯片 ID + AL 状态),供 init 汇总诊断。 */
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* @brief 周期中断处理(由 SYNC0 EXTI 触发)。
|
||||
* @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。
|
||||
* 流程:读输入 PDO → 写输出 PDO → 模 3 轮转索引 → 递增 s_periodicSeq。
|
||||
* 不再调用 TaskManager::set,避免 preempt=0 与 BASEPRI 临界区的 RMW 竞态。
|
||||
*/
|
||||
static void periodicIsr();
|
||||
|
||||
/**
|
||||
* @brief 获取最新输入 PDO(主循环调用,无锁)。
|
||||
* @return const PdoBuffer* 最新输入缓冲只读指针;返回与 ISR 当前写入位置隔一个的缓冲。
|
||||
* @note 三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间。
|
||||
*/
|
||||
static const PdoBuffer* getLatestInputs();
|
||||
|
||||
/**
|
||||
* @brief 设置下次周期要发送的输出 PDO(主循环调用)。
|
||||
* @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。
|
||||
* @note 写入与 ISR 当前读取位置隔一个的缓冲,无需任何临界区。
|
||||
*/
|
||||
static void setOutputs(const PdoBuffer *outputs);
|
||||
|
||||
/**
|
||||
* @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 或周期序列号触发)。
|
||||
* @note 留接口,具体映射逻辑由协议栈后续实现。
|
||||
*/
|
||||
static void process();
|
||||
|
||||
/**
|
||||
* @brief 获取应用层输入 PDO 快照(主循环调用)。
|
||||
* @return const PdoBuffer* 最近一次 process() 拷贝的输入快照只读指针。
|
||||
* @note 返回的指针在下次 process() 调用前有效;应用层应在 process() 后立即读取。
|
||||
*/
|
||||
static const PdoBuffer* getApplicationInputs();
|
||||
|
||||
/**
|
||||
* @brief 设置应用层输出 PDO 快照(主循环调用)。
|
||||
* @param[in] outputs 输出数据指针(内容会被拷贝到内部快照)。
|
||||
* @note 下次 process() 调用时,内部快照通过 setOutputs() 推入三缓冲。
|
||||
*/
|
||||
static void setApplicationOutputs(const PdoBuffer *outputs);
|
||||
|
||||
/**
|
||||
* @brief 检查是否有新的周期数据待主循环处理。
|
||||
* @return bool true 表示 s_periodicSeq 与已处理序号不同。
|
||||
* @note 主循环 runPending() 优先调用此接口,替代原 TASK_ETHERCAT_PROCESS 周期路径。
|
||||
*/
|
||||
static bool hasPendingPeriodic();
|
||||
|
||||
/**
|
||||
* @brief 标记当前周期数据已被主循环处理。
|
||||
* @note 调用此接口前应已调用 taskEthercatProcess() 完成本次 PDO 映射。
|
||||
*/
|
||||
static void markPeriodicProcessed();
|
||||
|
||||
/** @brief 读取 ISR 执行时间诊断(max/avg 单位:CPU 周期数,除以 180 得 µs)。 */
|
||||
static void getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count);
|
||||
|
||||
/** @brief ESC 自检是否通过(init 后可查询,用于决定是否使能同步中断)。 */
|
||||
static bool isEscReady() { return s_escReady; }
|
||||
|
||||
/** @brief 读取 ESC 芯片 ID(供诊断打印;未就绪时为上次读到的值)。 */
|
||||
static uint32_t getChipId() { return s_chipId; }
|
||||
|
||||
/** @brief 读取 ESC AL 状态码(供诊断打印与 SSC 集成;未就绪时为上次读到的值)。 */
|
||||
/** @brief 读取 ESC AL 状态码(供诊断打印;未就绪时为上次读到的值)。 */
|
||||
static uint8_t getAlState() { return s_alState; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 输入三缓冲。
|
||||
* ISR 按 s_inputWriteIdx 模 3 轮转写入;getLatestInputs() 读取 (writeIdx + 2) % 3。
|
||||
*/
|
||||
static PdoBuffer s_inputs[3];
|
||||
/**
|
||||
* @brief 输出三缓冲。
|
||||
* ISR 按 s_outputReadIdx 模 3 轮转读取;setOutputs() 写入 (readIdx + 2) % 3,
|
||||
* 与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限消除拷贝竞态。
|
||||
*/
|
||||
static PdoBuffer s_outputs[3];
|
||||
/** @brief 应用层输入快照(主循环访问,process() 从三缓冲拷贝;无需三缓冲)。 */
|
||||
static PdoBuffer s_appInputs;
|
||||
/** @brief 应用层输出快照(主循环访问,process() 推入三缓冲;无需三缓冲)。 */
|
||||
static PdoBuffer s_appOutputs;
|
||||
static volatile uint8_t s_inputWriteIdx; /**< ISR 当前写入索引;模 3 轮转。 */
|
||||
static volatile uint8_t s_outputReadIdx; /**< ISR 当前读取索引;模 3 轮转。 */
|
||||
|
||||
/** @brief 周期路径通知序列号(ISR 单字原子递增,主循环比对;uint16_t 提供 6.55s 回绕周期)。 */
|
||||
static volatile uint16_t s_periodicSeq;
|
||||
/** @brief 主循环已处理序号(由 hasPendingPeriodic/markPeriodicProcessed 维护)。 */
|
||||
static uint16_t s_processedSeq;
|
||||
|
||||
static volatile uint32_t s_isrMaxCycles; /**< ISR 最大执行周期数(180MHz 基准)。 */
|
||||
static volatile uint32_t s_isrAvgCycles; /**< ISR 平均执行周期数(滑动累计)。 */
|
||||
static volatile uint32_t s_isrCount; /**< ISR 执行次数计数。 */
|
||||
|
||||
static bool s_escReady; /**< ESC 自检结果(init 后置位)。 */
|
||||
static uint32_t s_chipId; /**< ESC 芯片 ID(诊断用)。 */
|
||||
static uint8_t s_alState; /**< ESC AL 状态码(init 后可查询,供 isEscReady 守卫)。 */
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
/**
|
||||
* @file ethercat_sync.cpp
|
||||
* @brief EtherCAT SYNC0 周期同步实现。
|
||||
* @brief EtherCAT SYNC0/IRQ/SYNC1 中断入口 — 转发到 SSC 协议栈。
|
||||
*
|
||||
* SYNC0 (EXTI0, preempt=0) → Sync0_Isr() : DC 周期同步, PDO 交换
|
||||
* ESC IRQ (EXTI1, preempt=1) → PDI_Isr() : AL Event (邮箱/SM 事件)
|
||||
* SYNC1 (EXTI2, preempt=1) → Sync1_Isr() : 预留
|
||||
*/
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "comm/ethercat_slave.h"
|
||||
#include "comm/esc_lan9252.h" /* pmSuspended:ESC 睡眠挂起期 ISR 拒碰 HBI */
|
||||
#include "sys/config.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "main.h"
|
||||
#include "main.h" /* g_app_uptime_sec */
|
||||
#include "sys/cpu_load.h"
|
||||
#include <cstdio>
|
||||
|
||||
extern "C" {
|
||||
#include "gd32e50x_rcu.h"
|
||||
@@ -16,10 +21,26 @@ extern "C" {
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 1)
|
||||
#include "gd32e50x_timer.h"
|
||||
#endif
|
||||
|
||||
/* SSC 协议栈 ISR 入口 (ecatappl.c) */
|
||||
void PDI_Isr(void);
|
||||
void Sync0_Isr(void);
|
||||
void Sync1_Isr(void);
|
||||
}
|
||||
|
||||
bool EthercatSync::s_active = false;
|
||||
volatile uint32_t EthercatSync::s_sync0Count = 0U;
|
||||
volatile uint32_t EthercatSync::s_sync1Count = 0U;
|
||||
volatile uint32_t EthercatSync::s_escIrqCount = 0U;
|
||||
|
||||
void EthercatSync::init()
|
||||
{
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
printf("[ECAT][SYNC] init: mode=EXTI (hardware SYNC0, CONFIG_ETHERCAT_SYNC_MODE=0)\r\n");
|
||||
#else
|
||||
printf("[ECAT][SYNC] init: mode=TIMER0 (degraded, CONFIG_ETHERCAT_SYNC_MODE=1)\r\n");
|
||||
#endif
|
||||
|
||||
/* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
@@ -27,6 +48,10 @@ void EthercatSync::init()
|
||||
/* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */
|
||||
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN);
|
||||
printf("[ECAT][SYNC] init: GPIOB pins configured (SYNC0=PB%u IRQ=PB%u SYNC1=PB%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
|
||||
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
/* ---- 默认:EXTI 外部触发模式 ---- */
|
||||
@@ -36,20 +61,9 @@ void EthercatSync::init()
|
||||
exti_interrupt_enable(CONFIG_ESC_SYNC0_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC0_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_SYNC0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
|
||||
|
||||
/* ESC IRQ:PB1 -> EXTI1,下降沿,preempt=1 */
|
||||
gpio_exti_source_select(CONFIG_ESC_IRQ_EXTI_SOURCE_PORT, CONFIG_ESC_IRQ_EXTI_SOURCE_PIN);
|
||||
exti_init(CONFIG_ESC_IRQ_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
|
||||
exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_IRQ_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_ESC, 0U);
|
||||
|
||||
/* SYNC1:PB2 -> EXTI2,下降沿,preempt=1(备用) */
|
||||
gpio_exti_source_select(CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
|
||||
exti_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
|
||||
exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_SYNC1_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1, 0U);
|
||||
printf("[ECAT][SYNC] init: SYNC0 EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
|
||||
#else
|
||||
/* ---- 降级模式:TIMER0 内部定时 ---- */
|
||||
rcu_periph_clock_enable(RCU_TIMER0);
|
||||
@@ -64,23 +78,35 @@ void EthercatSync::init()
|
||||
timer_init(TIMER0, &timer_initpara);
|
||||
timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);
|
||||
timer_interrupt_enable(TIMER0, TIMER_INT_UP);
|
||||
nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
|
||||
nvic_irq_enable(TIMER0_UP_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
|
||||
timer_enable(TIMER0);
|
||||
printf("[ECAT][SYNC] init: TIMER0 enabled (100us, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
|
||||
#endif
|
||||
|
||||
/* ESC IRQ:PB1 -> EXTI1,下降沿,preempt=1 */
|
||||
/* 公共:ESC IRQ (PB1->EXTI1) + SYNC1 (PB2->EXTI2) */
|
||||
gpio_exti_source_select(CONFIG_ESC_IRQ_EXTI_SOURCE_PORT, CONFIG_ESC_IRQ_EXTI_SOURCE_PIN);
|
||||
exti_init(CONFIG_ESC_IRQ_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
|
||||
exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_IRQ_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_ESC, 0U);
|
||||
printf("[ECAT][SYNC] init: ESC IRQ EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_ESC);
|
||||
|
||||
/* SYNC1:PB2 -> EXTI2,下降沿,preempt=1(备用) */
|
||||
gpio_exti_source_select(CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
|
||||
exti_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
|
||||
exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_SYNC1_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1, 0U);
|
||||
#endif
|
||||
printf("[ECAT][SYNC] init: SYNC1 EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1);
|
||||
|
||||
printf("[ECAT][SYNC] init: OK\r\n");
|
||||
/* 全部 EXTI/NVIC 使能完成,HBI 出现并发用户(主循环 vs SYNC0/IRQ ISR),
|
||||
* 之后 EXMC 诊断必须让位(diagDump 内部据此跳过)。 */
|
||||
s_active = true;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
@@ -89,7 +115,14 @@ void EthercatSync::onSync0Exti()
|
||||
/* 直接寄存器访问,绕过 GD32 库函数以消除可变抖动 */
|
||||
if ((EXTI_PD & (uint32_t)CONFIG_ESC_SYNC0_EXTI) != RESET) {
|
||||
EXTI_PD = (uint32_t)CONFIG_ESC_SYNC0_EXTI;
|
||||
EthercatSlave::periodicIsr();
|
||||
/* ESC 睡眠挂起期,清了 pending 就走——Sync0_Isr 内的 PRAM/CSR
|
||||
* 访问全是 HBI,读即唤醒。睡眠中 SYNC0 引脚本应无沿,此分支只兜
|
||||
* 进入睡眠瞬间的残留 pending。 */
|
||||
if (EscLan9252::pmSuspended()) {
|
||||
return;
|
||||
}
|
||||
++s_sync0Count;
|
||||
Sync0_Isr();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +130,21 @@ void EthercatSync::onEscIrqExti()
|
||||
{
|
||||
if (exti_interrupt_flag_get(CONFIG_ESC_IRQ_EXTI) != RESET) {
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
|
||||
/* 极简:仅置位任务,事件处理移主循环 */
|
||||
TaskManager::set(1UL << TASK_ETHERCAT_PROCESS);
|
||||
/* ESC 睡眠挂起期直接返回——PDI_Isr 会读 0x220/PRAM(HBI),读即唤醒 */
|
||||
if (EscLan9252::pmSuspended()) {
|
||||
return;
|
||||
}
|
||||
++s_escIrqCount;
|
||||
/* PDI_Isr(preempt=1) 可被 Sync0_Isr(preempt=0) 抢占;HBI Indexed 是两步
|
||||
* 事务(IDX 写 + DATA 读),被抢占即错址。此处屏蔽 EXTI0 实现互斥:
|
||||
* SYNC0 边沿在屏蔽期到来会锁存在 pending,退出后立即补偿执行,
|
||||
* 代价是 PDI_Isr 剩余时长(µs 级)计入 SYNC0 抖动。 */
|
||||
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
PDI_Isr();
|
||||
if (prevSync0 != 0U) {
|
||||
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +152,18 @@ void EthercatSync::onSync1Exti()
|
||||
{
|
||||
if (exti_interrupt_flag_get(CONFIG_ESC_SYNC1_EXTI) != RESET) {
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
|
||||
/* SYNC1 备用,暂不处理 */
|
||||
/* ESC 睡眠挂起期直接返回——Sync1_Isr 内为 HBI 访问 */
|
||||
if (EscLan9252::pmSuspended()) {
|
||||
return;
|
||||
}
|
||||
++s_sync1Count;
|
||||
/* 同 onEscIrqExti:Sync1_Isr(preempt=1) 内的 HBI 访问需与 SYNC0 互斥 */
|
||||
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
Sync1_Isr();
|
||||
if (prevSync0 != 0U) {
|
||||
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +172,13 @@ void EthercatSync::onTimerIrq()
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 1)
|
||||
if (timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP) != RESET) {
|
||||
timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);
|
||||
EthercatSlave::periodicIsr();
|
||||
/* ESC 睡眠挂起期跳过——Sync0_Isr 内为 HBI 访问 */
|
||||
if (!EscLan9252::pmSuspended()) {
|
||||
Sync0_Isr();
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* EXTI 模式下 TIMER0 不使用,本函数为占位;CONFIG_ETHERCAT_SYNC_MODE=1 时激活。 */
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -125,13 +187,33 @@ extern "C" {
|
||||
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EXTI0_IRQHandler(void) { EthercatSync::onSync0Exti(); }
|
||||
void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
|
||||
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
|
||||
void EXTI0_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
EthercatSync::onSync0Exti();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
|
||||
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
|
||||
#else
|
||||
void TIMER0_IRQHandler(void) { EthercatSync::onTimerIrq(); }
|
||||
void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
|
||||
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
|
||||
void TIMER0_UP_TIMER9_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
EthercatSync::onTimerIrq();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
|
||||
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
|
||||
#endif
|
||||
|
||||
/* ---- SSC 诊断对象 0x2000 的 C 侧访问 (dc_app.c APPL_InputMapping 每周期拉取) ---- */
|
||||
uint32_t EcatDbg_GetSync0(void) { return EthercatSync::getSync0Count(); }
|
||||
uint32_t EcatDbg_GetSync1(void) { return EthercatSync::getSync1Count(); }
|
||||
uint32_t EcatDbg_GetEscIrq(void) { return EthercatSync::getEscIrqCount(); }
|
||||
uint32_t EcatDbg_GetUptimeSec(void){ return g_app_uptime_sec; }
|
||||
uint32_t EcatDbg_GetTick1ms(void) { return g_scheduler.tick_1ms; }
|
||||
|
||||
/* 剖分埋点:供 ecatappl.c (C) 读取 DWT 周期计数 */
|
||||
uint32_t EcatDbg_Dwt(void) { return DWT->CYCCNT; }
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -8,11 +8,24 @@
|
||||
#ifndef COMM_ETHERCAT_SYNC_H
|
||||
#define COMM_ETHERCAT_SYNC_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class EthercatSync {
|
||||
public:
|
||||
/** @brief 初始化 SYNC0/IRQ/SYNC1 EXTI 引脚与 NVIC(或降级模式 TIMER0)。 */
|
||||
static void init();
|
||||
|
||||
/** @brief 同步 ISR 是否已激活(init 成功使能 EXTI NVIC 后为 true)。
|
||||
* @note EXMC/HBI 诊断(会拆总线)必须在此返回 false 时才允许执行。 */
|
||||
static bool isActive() { return s_active; }
|
||||
|
||||
/** @brief SYNC0 中断累计次数(DC 通路验证:主站设 1ms 周期时应 ~1000/s)。 */
|
||||
static uint32_t getSync0Count() { return s_sync0Count; }
|
||||
/** @brief SYNC1 中断累计次数。 */
|
||||
static uint32_t getSync1Count() { return s_sync1Count; }
|
||||
/** @brief ESC IRQ(PDI AL Event)中断累计次数。 */
|
||||
static uint32_t getEscIrqCount() { return s_escIrqCount; }
|
||||
|
||||
/** @brief SYNC0 EXTI 中断处理(EXTI0)。 */
|
||||
static void onSync0Exti();
|
||||
/** @brief ESC IRQ EXTI 中断处理(EXTI1)。 */
|
||||
@@ -21,6 +34,12 @@ public:
|
||||
static void onSync1Exti();
|
||||
/** @brief TIMER0 降级模式中断处理。 */
|
||||
static void onTimerIrq();
|
||||
|
||||
private:
|
||||
static bool s_active; /**< init() 成功后置位(EXTI NVIC 已使能,HBI 有并发用户)。 */
|
||||
static volatile uint32_t s_sync0Count; /**< SYNC0 ISR 累计次数(ISR 内自增)。 */
|
||||
static volatile uint32_t s_sync1Count; /**< SYNC1 ISR 累计次数。 */
|
||||
static volatile uint32_t s_escIrqCount; /**< ESC IRQ ISR 累计次数。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ETHERCAT_SYNC_H */
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
\addtogroup EcatAppl EtherCAT application
|
||||
@{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file applInterface.c
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief Definition of the application interface functions
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version - :<br>
|
||||
V5.10.1 : Start file change log
|
||||
*/
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#ifndef _APPL_INTERFACE_H_
|
||||
#define _APPL_INTERFACE_H_
|
||||
|
||||
#include "ecat_def.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esc.h"
|
||||
#include "ecatslv.h"
|
||||
#include "objdef.h"
|
||||
#include "ecatappl.h"
|
||||
|
||||
|
||||
|
||||
#endif /*#ifndef _APPL_INTERFACE_H_*/
|
||||
|
||||
#if defined(_APPL_INTERFACE_) && (_APPL_INTERFACE_ == 1)
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Function pointer which are used to call optional functions from the application
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 if successful or greater 0 in case of an error
|
||||
|
||||
\param wordaddr Word address of the EEPROM data to be read
|
||||
|
||||
\brief This function shall copy EEPROM data to the ESC EEPROM data register (0x508:0x50F/0x50B).
|
||||
\brief The EEPROM data starting at the specified word address and the length specified with "EEPROM_READ_SIZE".
|
||||
\brief The data shall be copied to the ESC EEPROM buffer (ESC offset 0x508)
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO UINT16 (* pAPPL_EEPROM_Read)(UINT32 wordaddr);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 if successful or greater 0 in case of an error
|
||||
|
||||
\param wordaddr Word address of the EEPROM data to be written
|
||||
|
||||
\brief This function shall copy data from the ESC EEPROM data register (0x508:0x50F/0x50B) to the EEPROM memory.
|
||||
\brief The EEPROM data starting at the specified word address and the length specified with "EEPROM_WRITE_SIZE".
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO UINT16(*pAPPL_EEPROM_Write)(UINT32 wordaddr);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 if successful or greater 0 in case of an error
|
||||
|
||||
\brief This function shall copy the EEPROM reload information to the ESC EEPROM data register (0x508:0x50F/0x50B).
|
||||
\brief Read the ESC data sheet for the reload information (e.g. Beckhoff IPCore ESC Datasheet section II, clause 3.45.1)
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO UINT16(*pAPPL_EEPROM_Reload)(void);
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Functions provided by the SSC to be called from the application
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 if successful or greater 0 in case of an error
|
||||
|
||||
\brief Initialize the generic slave stack.
|
||||
\brief This function should be called after the platform including operating system and
|
||||
\brief ESC is ready to use.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO UINT16 MainInit(void);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function handles the low priority function like EtherCAT state machine
|
||||
\brief handling, mailbox protocols and if no synchronization is enabled also the
|
||||
\brief application.
|
||||
\brief This function shall be called cyclically from the application.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO void MainLoop(void);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param alStatus requested AL Status
|
||||
\param alStatusCode requested AL Status Code
|
||||
|
||||
\brief This function shall be called by the application to trigger state transition in case
|
||||
\brief of an application error or to complete a pending transition.
|
||||
\brief If the function was called due to an error it shall be again if the error is gone.
|
||||
\brief NOTE: state requests to a higher state than the current state are not allowed.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
PROTO void ECAT_StateChange(UINT8 alStatus, UINT16 alStatusCode);
|
||||
|
||||
#undef PROTO
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* \addtogroup CoE CAN Application Profile over EtherCAT
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file coeappl.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
#ifndef _COEAPPL_H_
|
||||
#define _COEAPPL_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "objdef.h"
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
#endif //_COEAPPL_H_
|
||||
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_COEAPPL_) && (_COEAPPL_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
PROTO void COE_ObjInit(void);
|
||||
PROTO void COE_Main(void);
|
||||
PROTO UINT16 COE_ObjDictionaryInit(void);
|
||||
PROTO UINT16 COE_AddObjectToDic(TOBJECT OBJMEM * pNewObjEntry);
|
||||
PROTO void COE_RemoveDicEntry(UINT16 index);
|
||||
PROTO void COE_ClearObjDictionary(void);
|
||||
PROTO OBJCONST TOBJECT OBJMEM * COE_GetObjectDictionary(void);
|
||||
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
\addtogroup EcatAppl EtherCAT application
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file ecatappl.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.10:<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 ECAT13: Update Synchronisation handling (FreeRun,SM Sync, Sync0, Sync1)<br>
|
||||
Compare DC UINT configuration (by ESC Config data) vs. DC activation register settings<br>
|
||||
Update 0x1C3x entries<br>
|
||||
V5.10 ECAT4: Update alignment marco for 8 to 15 bit alignments (16 and 32 Bit controllers)<br>
|
||||
Bugfix calculate LED blink frequency<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Description
|
||||
------
|
||||
------ ecatappl.h
|
||||
------
|
||||
------ EtherCAT Slave Application
|
||||
------ ------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef _ECATAPPL_H_
|
||||
#define _ECATAPPL_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/*Set to unsigned short to handle bit entries correct*/
|
||||
#define BOOLEAN(x) unsigned short(x):1 /**< \brief Marco to define BOOLEAN object entry*/
|
||||
#define BIT1(x) unsigned short(x):1 /**< \brief Marco to define BIT1 object entry*/
|
||||
#define BIT2(x) unsigned short(x):2 /**< \brief Marco to define BIT2 object entry*/
|
||||
#define BIT3(x) unsigned short(x):3 /**< \brief Marco to define BIT3 object entry*/
|
||||
#define BIT4(x) unsigned short(x):4 /**< \brief Marco to define BIT4 object entry*/
|
||||
#define BIT5(x) unsigned short(x):5 /**< \brief Marco to define BIT5 object entry*/
|
||||
#define BIT6(x) unsigned short(x):6 /**< \brief Marco to define BIT5 object entry*/
|
||||
#define BIT7(x) unsigned short(x):7 /**< \brief Marco to define BIT6 object entry*/
|
||||
#define BIT8(x) unsigned short(x):8 /**< \brief Marco to define BIT7 object entry*/
|
||||
#define ALIGN0(x)
|
||||
#define ALIGN1(x) unsigned short(x):1; /**< \brief Marco to define ALIGN1 object entry*/
|
||||
#define ALIGN2(x) unsigned short(x):2; /**< \brief Marco to define ALIGN2 object entry*/
|
||||
#define ALIGN3(x) unsigned short(x):3; /**< \brief Marco to define ALIGN3 object entry*/
|
||||
#define ALIGN4(x) unsigned short(x):4; /**< \brief Marco to define ALIGN4 object entry*/
|
||||
#define ALIGN5(x) unsigned short(x):5; /**< \brief Marco to define ALIGN5 object entry*/
|
||||
#define ALIGN6(x) unsigned short(x):6; /**< \brief Marco to define ALIGN6 object entry*/
|
||||
#define ALIGN7(x) unsigned short(x):7; /**< \brief Marco to define ALIGN7 object entry*/
|
||||
#define ALIGN8(x) unsigned short(x):8; /**< \brief Marco to define ALIGN8 object entry*/
|
||||
#define ALIGN9(x) unsigned short(x):9; /**< \brief Marco to define ALIGN9 object entry*/
|
||||
#define ALIGN10(x) unsigned short(x):10; /**< \brief Marco to define ALIGN10 object entry*/
|
||||
#define ALIGN11(x) unsigned short(x):11; /**< \brief Marco to define ALIGN11 object entry*/
|
||||
#define ALIGN12(x) unsigned short(x):12; /**< \brief Marco to define ALIGN12 object entry*/
|
||||
#define ALIGN13(x) unsigned short(x):13; /**< \brief Marco to define ALIGN13 object entry*/
|
||||
#define ALIGN14(x) unsigned short(x):14; /**< \brief Marco to define ALIGN14 object entry*/
|
||||
#define ALIGN15(x) unsigned short(x):15; /**< \brief Marco to define ALIGN15 object entry*/
|
||||
#endif //_ECATAPPL_H_
|
||||
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_ECATAPPL_) && (_ECATAPPL_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
PROTO BOOL bEcatWaitForInputUpdate; /**< \brief Wait for input update*/
|
||||
PROTO BOOL bEtherCATRunLed; /**< \brief Current run LED value*/
|
||||
PROTO BOOL bEtherCATErrorLed; /**< \brief Current error LED value*/
|
||||
PROTO BOOL bRunApplication; /**< \brief Indicates if the stack shall be running (if false the Hardware will be released)*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
|
||||
PROTO void ECAT_CheckTimer(void);
|
||||
PROTO void PDI_Isr(void);
|
||||
PROTO void Sync0_Isr(void);
|
||||
PROTO void Sync1_Isr(void);
|
||||
PROTO void ECAT_Application(void);
|
||||
PROTO void PDO_ResetOutputs(void);
|
||||
PROTO void PDO_ReadInputs(void);
|
||||
PROTO void PDO_InputMapping(void);
|
||||
|
||||
PROTO void CalcSMCycleTime(void);
|
||||
|
||||
|
||||
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* \addtogroup CoE CAN Application Profile over EtherCAT
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file ecatcoe.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.11 COE4: "change prototype of ""COE_ContinueInd()"" return <> 0 if a failure occurred"<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
#ifndef _ECATCOE_H_
|
||||
|
||||
#define _ECATCOE_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "mailbox.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------
|
||||
- Error codes
|
||||
-----------------------------------------------*/
|
||||
#define ERROR_COEINVALIDSERVICE 0x01 /**< \brief Invalid SDO service*/
|
||||
#define ERROR_COENOTSUPPORTED 0x02 /**< \brief Not supported service*/
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- COE services
|
||||
-----------------------------------------------*/
|
||||
#define COESERVICE_EMERGENCY 0x01 /**< \brief CoE Emergency*/
|
||||
#define COESERVICE_SDOREQUEST 0x02 /**< \brief CoE SDO request*/
|
||||
#define COESERVICE_SDORESPONSE 0x03 /**< \brief CoE SDO response*/
|
||||
#define COESERVICE_TXPDO 0x04 /**< \brief CoE TxPDO*/
|
||||
#define COESERVICE_RXPDO 0x05 /**< \brief CoE RxPDO*/
|
||||
#define COESERVICE_TXPDOREMREQ 0x06 /**< \brief CoE TxPDO map request*/
|
||||
#define COESERVICE_RXPDOREMREQ 0x07 /**< \brief CoE RxPDO map request*/
|
||||
#define COESERVICE_SDOINFO 0x08 /**< \brief CoE SDO Info*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- COE Structures
|
||||
-----------------------------------------------*/
|
||||
#define COEHEADER_COESERVICESHIFT 12 /**< \brief CoE service shift (within CoE header)*/
|
||||
#define COEHEADER_COESERVICEMASK 0xF000 /**< \brief CoE service mask (within CoE header)*/
|
||||
|
||||
#define COE_HEADER_SIZE 2 /**< \brief CoE header size*/
|
||||
|
||||
typedef UINT16 TCOEHEADER; /**< \brief CoE header*/
|
||||
|
||||
|
||||
/**
|
||||
* \brief CoE Mailbox header
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
UINT16 Data[((MAX_MBX_DATA_SIZE)-(COE_HEADER_SIZE)) >> 1]; /**< \brief CoE data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TCOEMBX;
|
||||
|
||||
|
||||
#endif //_ECATCOE_H_
|
||||
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_ECATCOE_) && (_ECATCOE_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
PROTO TMBX MBXMEM * VARMEM pCoeSendStored; /** if the mailbox service could not be sent (or stored),
|
||||
the CoE service will be stored in this variable
|
||||
and will be sent automatically from the mailbox handler
|
||||
(COE_ContinueInd) when the send mailbox will be read
|
||||
the next time from the master */
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
PROTO void COE_Init(void);
|
||||
PROTO UINT8 COE_ServiceInd(TCOEMBX MBXMEM *pCoeMbx);
|
||||
/*ECATCHANGE_START(V5.11) COE4*/
|
||||
PROTO UINT8 COE_ContinueInd(TMBX MBXMEM * pMbx);
|
||||
/*ECATCHANGE_END(V5.11) COE4*/
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* \addtogroup ESM EtherCAT State Machine
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file ecatslv.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.10:<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
V5.11 ECAT4: enhance SM/Sync monitoring for input/output only slaves<br>
|
||||
V5.11 ECAT9: add new AL Status Codes (ALSTATUSCODE_FW_SII_NOT_MATCH/0x0006, ALSTATUSCODE_FW_UPDATE_FAILED / 0x0007)<br>
|
||||
V5.11 ESM7: "add Sync define for 0x22 (""SYNCTYPE_SM2_SYNCHRON""), support value 0x22 for 0x1C33.1 (SM2 sync)"<br>
|
||||
V5.11 HW1: "move hardware independent functions ""HW_DisableSyncManChannel()"", ""HW_EnableSyncManChannel()"", ""HW_GetSyncMan()"", ""HW_ResetALEventMask()"", ""HW_SetALEventMask()"" to ecatalv.c"<br>
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 COE1: Define one entry description for all 0x1C3x objects and change data type of SI11,12,13 to UINT16 (according ETG.1020)<br>
|
||||
V5.10 ECAT12: Add new AL Status Codes (0x2E,0x52,0x70)<br>
|
||||
V5.10 ECAT13: Update Synchronisation handling (FreeRun,SM Sync, Sync0, Sync1)<br>
|
||||
Compare DC UINT configuration (by ESC Config data) vs. DC activation register settings<br>
|
||||
Update 0x1C3x entries<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
|
||||
#ifndef _ECATSLV_H_
|
||||
#define _ECATSLV_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
#if MCI_HW
|
||||
#include "mcihw.h"
|
||||
#endif
|
||||
#if EL9800_HW
|
||||
#include "el9800hw.h"
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef OBJGETNEXTSTR
|
||||
/* the old definition was not working with all compilers */
|
||||
/* old: #define OBJGETNEXTSTR(p) ( (OBJCONST CHAR OBJMEM * )( ((UINT32) p) + OBJSTRLEN( (OBJCONST CHAR OBJMEM *) p ) + 1 ) ) */
|
||||
#define OBJGETNEXTSTR(p) ( (OBJCONST CHAR OBJMEM * )( &((p)[OBJSTRLEN( (OBJCONST CHAR OBJMEM *) (p) ) + 1]) ) ) /**< \brief Macro to get next name within the objetc name string*/
|
||||
#endif
|
||||
|
||||
#ifndef LO_BYTE
|
||||
#define LO_BYTE 0 /**< \brief LowByte within an WORD*/
|
||||
#endif
|
||||
|
||||
#ifndef HI_BYTE
|
||||
#define HI_BYTE 1 /**< \brief HighByte within an WORD*/
|
||||
#endif
|
||||
|
||||
#ifndef LOLO_BYTE
|
||||
#define LOLO_BYTE 0 /**< \brief LowLowByte within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef LOHI_BYTE
|
||||
#define LOHI_BYTE 1 /**< \brief LowHighByte within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef HILO_BYTE
|
||||
#define HILO_BYTE 2 /**< \brief HighLowByte within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef HIHI_BYTE
|
||||
#define HIHI_BYTE 3 /**< \brief HighHighByte within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef LO_WORD
|
||||
#define LO_WORD 0 /**< \brief LowWord within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef HI_WORD
|
||||
#define HI_WORD 1 /**< \brief HighWord within an DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef SWAPWORD
|
||||
#define SWAPWORD(x) (x) /**< \brief Macro to swap a WORD*/
|
||||
#endif
|
||||
|
||||
#ifndef SWAPDWORD
|
||||
#define SWAPDWORD(x) (x) /**< \brief Macro to swap a DWORD*/
|
||||
#endif
|
||||
|
||||
#ifndef LOBYTE
|
||||
#define LOBYTE(x) ((x)&0xFF) /**< \brief LowByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef HIBYTE
|
||||
#define HIBYTE(x) (((x)&0xFF00)>>8)/**< \brief HighwByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef LOLOBYTE
|
||||
#define LOLOBYTE(x) ((x)&0xFF)/**< \brief LowLowByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef LOHIBYTE
|
||||
#define LOHIBYTE(x) (((x)&0xFF00)>>8)/**< \brief LowHighByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef HILOBYTE
|
||||
#define HILOBYTE(x) (((x)&0xFF0000)>>16)/**< \brief HighLowByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef HIHIBYTE
|
||||
#define HIHIBYTE(x) (((x)&0xFF000000)>>24)/**< \brief HighHighByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef LOWORD
|
||||
#define LOWORD(x) ((x)&0xFFFF)/**< \brief LowWord mask*/
|
||||
#endif
|
||||
|
||||
#ifndef HIWORD
|
||||
#define HIWORD(x) (((x)&0xFFFF0000)>>16)/**< \brief HighByte mask*/
|
||||
#endif
|
||||
|
||||
#ifndef BIT2BYTE
|
||||
#define BIT2BYTE(x) (((x)+7)>>3) /**< \brief Marco to convert Bit size to Byte size (round up)*/
|
||||
#endif
|
||||
|
||||
#ifndef BYTE2BIT
|
||||
#define BYTE2BIT(x) ((x)<<3) /**< \brief Marco to convert Byte size to Bit size*/
|
||||
#endif
|
||||
|
||||
#ifndef BIT2WORD
|
||||
#define BIT2WORD(x) (((x)+15)>>4) /**< \brief Marco to convert Bit size to Word size (round up)*/
|
||||
#endif
|
||||
|
||||
#ifndef BYTE2WORD
|
||||
#define BYTE2WORD(x) (((x)+1)>>1) /**< \brief Marco to convert Bytes size to Word size (round up)*/
|
||||
#endif
|
||||
|
||||
#ifndef ROUNDUPBYTE2WORD
|
||||
#define ROUNDUPBYTE2WORD(x) ((((x)+1)>>1)<<1) /**< \brief Round up Byte size to even number of Words*/
|
||||
#endif
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- State defines
|
||||
-----------------------------------------------*/
|
||||
#define STATE_INIT ((UINT8) 0x01) /**< \brief State INIT*/
|
||||
#define STATE_PREOP ((UINT8) 0x02) /**< \brief State PreOP*/
|
||||
#define STATE_BOOT ((UINT8) 0x03) /**< \brief State BOOT*/
|
||||
#define STATE_SAFEOP ((UINT8) 0x04) /**< \brief State SafeOP*/
|
||||
#define STATE_OP ((UINT8) 0x08) /**< \brief State OP*/
|
||||
|
||||
#define STATE_MASK ((UINT8) 0x0F) /**< \brief State mask*/
|
||||
#define STATE_CHANGE ((UINT8) 0x10) /**< \brief State change (Error indication/acknowledge) mask and value*/
|
||||
#define STATE_DEVID ((UINT8) 0x20) /**< \brief Request/response Explicit device ID*/
|
||||
|
||||
#define BOOT_2_INIT ((UINT8)((STATE_BOOT) << 4) | (STATE_INIT)) /**< \brief State transition BOOT to INIT*/
|
||||
#define BOOT_2_PREOP ((UINT8)((STATE_BOOT) << 4) | (STATE_PREOP)) /**< \brief State transition BOOT to INIT*/
|
||||
#define BOOT_2_SAFEOP ((UINT8)((STATE_BOOT) << 4) | (STATE_SAFEOP)) /**< \brief State transition BOOT to INIT*/
|
||||
#define BOOT_2_OP ((UINT8)((STATE_BOOT) << 4) | (STATE_OP)) /**< \brief State transition BOOT to INIT*/
|
||||
|
||||
#define INIT_2_BOOT ((UINT8)((STATE_INIT) << 4) | (STATE_BOOT)) /**< \brief State transition INIT to BOOT*/
|
||||
#define PREOP_2_BOOT ((UINT8)((STATE_PREOP) << 4) | (STATE_BOOT)) /**< \brief State transition PREOP to BOOT*/
|
||||
#define SAFEOP_2_BOOT ((UINT8)((STATE_SAFEOP) << 4) | (STATE_BOOT)) /**< \brief State transition SAFEOP to BOOT*/
|
||||
#define OP_2_BOOT ((UINT8)((STATE_OP) << 4) | (STATE_BOOT)) /**< \brief State transition OP to BOOT*/
|
||||
|
||||
#define INIT_2_INIT ((UINT8)((STATE_INIT) << 4) | (STATE_INIT)) /**< \brief State transition INIT to INIT*/
|
||||
#define INIT_2_PREOP ((UINT8)((STATE_INIT) << 4) | (STATE_PREOP)) /**< \brief State transition INIT to PREOP*/
|
||||
#define INIT_2_SAFEOP ((UINT8)((STATE_INIT) << 4) | (STATE_SAFEOP)) /**< \brief State transition INIT to SAFEOP*/
|
||||
#define INIT_2_OP ((UINT8)((STATE_INIT) << 4) | (STATE_OP)) /**< \brief State transition INIT to OP*/
|
||||
|
||||
#define PREOP_2_INIT ((UINT8)((STATE_PREOP) << 4) | (STATE_INIT)) /**< \brief State transition PREOP to INIT*/
|
||||
#define PREOP_2_PREOP ((UINT8)((STATE_PREOP) << 4) | (STATE_PREOP)) /**< \brief State transition PREOP to PREOP*/
|
||||
#define PREOP_2_SAFEOP ((UINT8)((STATE_PREOP) << 4) | (STATE_SAFEOP)) /**< \brief State transition PREOP to SAFEOP*/
|
||||
#define PREOP_2_OP ((UINT8)((STATE_PREOP) << 4) | (STATE_OP)) /**< \brief State transition PREOP to OP*/
|
||||
|
||||
#define SAFEOP_2_INIT ((UINT8)((STATE_SAFEOP) << 4) | (STATE_INIT)) /**< \brief State transition SAFEOP to INIT*/
|
||||
#define SAFEOP_2_PREOP ((UINT8)((STATE_SAFEOP) << 4) | (STATE_PREOP)) /**< \brief State transition SAFEOP to PREOP*/
|
||||
#define SAFEOP_2_SAFEOP ((UINT8)((STATE_SAFEOP) << 4) | (STATE_SAFEOP)) /**< \brief State transition SAFEOP to SAFEOP*/
|
||||
#define SAFEOP_2_OP ((UINT8)((STATE_SAFEOP) << 4) |( STATE_OP)) /**< \brief State transition SAFEOP to OP*/
|
||||
|
||||
#define OP_2_INIT ((UINT8)((STATE_OP) << 4) | (STATE_INIT)) /**< \brief State transition OP to INIT*/
|
||||
#define OP_2_PREOP ((UINT8)((STATE_OP) << 4) | (STATE_PREOP)) /**< \brief State transition OP to PREOP*/
|
||||
#define OP_2_SAFEOP ((UINT8)((STATE_OP) << 4) | (STATE_SAFEOP)) /**< \brief State transition OP to SAFEOP*/
|
||||
#define OP_2_OP ((UINT8)((STATE_OP) << 4) | (STATE_OP)) /**< \brief State transition OP to OP*/
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
ESM transition error codes
|
||||
|
||||
*/
|
||||
|
||||
#define SYNCMANCHODDADDRESS 0x00 /**< \brief Emergency and Diagnosis code for an odd SyncManager address*/
|
||||
#define SYNCMANCHADDRESS 0x01 /**< \brief Emergency and Diagnosis code for an invalid SyncManager address*/
|
||||
#define SYNCMANCHSIZE 0x02 /**< \brief Emergency and Diagnosis code for an invalid SyncManager size*/
|
||||
#define SYNCMANCHSETTINGS 0x03 /**< \brief Emergency and Diagnosis code for an invalid SyncManager settings*/
|
||||
#define ERROR_SYNCMANCH(code, channel) ((code)+((channel)<<2)) /**< \brief Marco to add SyncManager channel*/
|
||||
#define ERROR_SYNCMANCHODDADDRESS(channel) ((SYNCMANCHODDADDRESS)+((channel)<<2)) /**< \brief Marco to add SyncManager channel*/
|
||||
#define ERROR_SYNCMANCHADDRESS(channel) ((SYNCMANCHADDRESS)+((channel)<<2)) /**< \brief Marco to add SyncManager channel*/
|
||||
#define ERROR_SYNCMANCHSIZE(channel) ((SYNCMANCHSIZE)+((channel)<<2)) /**< \brief Marco to add SyncManager channel*/
|
||||
#define ERROR_SYNCMANCHSETTINGS(channel) ((SYNCMANCHSETTINGS)+((channel)<<2)) /**< \brief Marco to add SyncManager channel*/
|
||||
#define ERROR_SYNCTYPES 0x80 /**< \brief Emergency and Diagnosis code for an invalid Sync types*/
|
||||
#define ERROR_DCSYNCCONTROL 0x81 /**< \brief Emergency and Diagnosis code for an invalid DC Sync control*/
|
||||
#define ERROR_DCSYNC0CYCLETIME 0x82 /**< \brief Emergency and Diagnosis code for an invalid Sync0 cycle time*/
|
||||
#define ERROR_DCSYNC1CYCLETIME 0x83 /**< \brief Emergency and Diagnosis code for an invalid Sync1 cycle time*/
|
||||
#define ERROR_DCCYCLEPARAMETER 0x84 /**< \brief Emergency and Diagnosis code for an invalid Cycle parameter*/
|
||||
#define ERROR_DCLATCHCONTROL 0x85 /**< \brief Emergency and Diagnosis code for an invalid Latch control*/
|
||||
|
||||
#define ERROR_INVALIDSTATE 0xF0 /**< \brief Emergency and Diagnosis code for an invalid State*/
|
||||
#define ERROR_NOMEMORY 0xF1 /**< \brief Emergency and Diagnosis code for no memory*/
|
||||
#define ERROR_OBJECTDICTIONARY 0xF2 /**< \brief Emergency and Diagnosis code for generic object dictionary error*/
|
||||
#define ERROR_NOSYNCMANACCESS 0xF3 /**< \brief Emergency and Diagnosis code for no SyncManager access*/
|
||||
#define ERROR_NOOFRXPDOS 0xF4 /**< \brief Emergency and Diagnosis code for no RxPDOs*/
|
||||
#define ERROR_NOOFTXPDOS 0xF5 /**< \brief Emergency and Diagnosis code for no TxPDOs*/
|
||||
#define ERROR_STATECHANGE 0xF6 /**< \brief Emergency and Diagnosis code state change error*/
|
||||
|
||||
#define NOERROR_NOSTATECHANGE 0xFE /**< \brief Indicates no state change*/
|
||||
#define NOERROR_INWORK 0xFF /**< \brief Indication for no error but operation is pending*/
|
||||
|
||||
#define EMCY_SM_ERRORCODE 0xA000 /**< \brief Emergency and Diagnosis code for an SyncManager error*/
|
||||
#define EMCY_SM_DEVICESPECIFIC 0xFF00 /**< \brief Emergency and Diagnosis code for a device specific error*/
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- AL Status Codes
|
||||
-----------------------------------------------*/
|
||||
#define ALSTATUSCODE_NOERROR 0x0000 /**< \brief No error*/
|
||||
#define ALSTATUSCODE_UNSPECIFIEDERROR 0x0001 /**< \brief Unspecified error*/
|
||||
#define ALSTATUSCODE_NOMEMORY 0x0002 /**< \brief No Memory*/
|
||||
/* ECATCHANGE_START(V5.11) ECAT9*/
|
||||
#define ALSTATUSCODE_FW_SII_NOT_MATCH 0x0006 /**< \brief Firmware and EEPROM do not match. Slave needs BOOT-INIT transition*/
|
||||
#define ALSTATUSCODE_FW_UPDATE_FAILED 0x0007 /**< \brief Firmware update not successful. Old firmware still running*/
|
||||
/* ECATCHANGE_END(V5.11) ECAT9*/
|
||||
#define ALSTATUSCODE_INVALIDALCONTROL 0x0011 /**< \brief Invalid requested state change*/
|
||||
#define ALSTATUSCODE_UNKNOWNALCONTROL 0x0012 /**< \brief Unknown requested state*/
|
||||
#define ALSTATUSCODE_BOOTNOTSUPP 0x0013 /**< \brief Bootstrap not supported*/
|
||||
#define ALSTATUSCODE_NOVALIDFIRMWARE 0x0014 /**< \brief No valid firmware*/
|
||||
#define ALSTATUSCODE_INVALIDMBXCFGINBOOT 0x0015 /**< \brief Invalid mailbox configuration (BOOT state)*/
|
||||
#define ALSTATUSCODE_INVALIDMBXCFGINPREOP 0x0016 /**< \brief Invalid mailbox configuration (PreOP state)*/
|
||||
#define ALSTATUSCODE_INVALIDSMCFG 0x0017 /**< \brief Invalid sync manager configuration*/
|
||||
#define ALSTATUSCODE_NOVALIDINPUTS 0x0018 /**< \brief No valid inputs available*/
|
||||
#define ALSTATUSCODE_NOVALIDOUTPUTS 0x0019 /**< \brief No valid outputs*/
|
||||
#define ALSTATUSCODE_SYNCERROR 0x001A /**< \brief Synchronization error*/
|
||||
#define ALSTATUSCODE_SMWATCHDOG 0x001B /**< \brief Sync manager watchdog*/
|
||||
#define ALSTATUSCODE_SYNCTYPESNOTCOMPATIBLE 0x001C /**< \brief Invalid Sync Manager Types*/
|
||||
#define ALSTATUSCODE_INVALIDSMOUTCFG 0x001D /**< \brief Invalid Output Configuration*/
|
||||
#define ALSTATUSCODE_INVALIDSMINCFG 0x001E /**< \brief Invalid Input Configuration*/
|
||||
#define ALSTATUSCODE_INVALIDWDCFG 0x001F /**< \brief Invalid Watchdog Configuration*/
|
||||
#define ALSTATUSCODE_WAITFORCOLDSTART 0x0020 /**< \brief Slave needs cold start*/
|
||||
#define ALSTATUSCODE_WAITFORINIT 0x0021 /**< \brief Slave needs INIT*/
|
||||
#define ALSTATUSCODE_WAITFORPREOP 0x0022 /**< \brief Slave needs PREOP*/
|
||||
#define ALSTATUSCODE_WAITFORSAFEOP 0x0023 /**< \brief Slave needs SAFEOP*/
|
||||
#define ALSTATUSCODE_INVALIDINPUTMAPPING 0x0024 /**< \brief Invalid Input Mapping*/
|
||||
#define ALSTATUSCODE_INVALIDOUTPUTMAPPING 0x0025 /**< \brief Invalid Output Mapping*/
|
||||
#define ALSTATUSCODE_INCONSISTENTSETTINGS 0x0026 /**< \brief Inconsistent Settings*/
|
||||
#define ALSTATUSCODE_FREERUNNOTSUPPORTED 0x0027 /**< \brief FreeRun not supported*/
|
||||
#define ALSTATUSCODE_SYNCHRONNOTSUPPORTED 0x0028 /**< \brief SyncMode not supported*/
|
||||
#define ALSTATUSCODE_FREERUNNEEDS3BUFFERMODE 0x0029 /**< \brief FreeRun needs 3Buffer Mode*/
|
||||
#define ALSTATUSCODE_BACKGROUNDWATCHDOG 0x002A /**< \brief Background Watchdog*/
|
||||
#define ALSTATUSCODE_NOVALIDINPUTSANDOUTPUTS 0x002B /**< \brief No Valid Inputs and Outputs*/
|
||||
#define ALSTATUSCODE_FATALSYNCERROR 0x002C /**< \brief Fatal Sync Error*/
|
||||
#define ALSTATUSCODE_NOSYNCERROR 0x002D /**< \brief No Sync Error*/
|
||||
#define ALSTATUSCODE_CYCLETIMETOOSMALL 0x002E /**< \brief EtherCAT cycle time smaller Minimum Cycle Time supported by slave*/
|
||||
#define ALSTATUSCODE_DCINVALIDSYNCCFG 0x0030 /**< \brief Invalid DC SYNCH Configuration*/
|
||||
#define ALSTATUSCODE_DCINVALIDLATCHCFG 0x0031 /**< \brief Invalid DC Latch Configuration*/
|
||||
#define ALSTATUSCODE_DCPLLSYNCERROR 0x0032 /**< \brief PLL Error*/
|
||||
#define ALSTATUSCODE_DCSYNCIOERROR 0x0033 /**< \brief DC Sync IO Error*/
|
||||
#define ALSTATUSCODE_DCSYNCMISSEDERROR 0x0034 /**< \brief DC Sync Timeout Error*/
|
||||
#define ALSTATUSCODE_DCINVALIDSYNCCYCLETIME 0x0035 /**< \brief DC Invalid Sync Cycle Time*/
|
||||
#define ALSTATUSCODE_DCSYNC0CYCLETIME 0x0036 /**< \brief DC Sync0 Cycle Time*/
|
||||
#define ALSTATUSCODE_DCSYNC1CYCLETIME 0x0037 /**< \brief DC Sync1 Cycle Time*/
|
||||
#define ALSTATUSCODE_MBX_AOE 0x0041 /**< \brief MBX_AOE*/
|
||||
#define ALSTATUSCODE_MBX_EOE 0x0042 /**< \brief MBX_EOE*/
|
||||
#define ALSTATUSCODE_MBX_COE 0x0043 /**< \brief MBX_COE*/
|
||||
#define ALSTATUSCODE_MBX_FOE 0x0044 /**< \brief MBX_FOE*/
|
||||
#define ALSTATUSCODE_MBX_SOE 0x0045 /**< \brief MBX_SOE*/
|
||||
#define ALSTATUSCODE_MBX_VOE 0x004F /**< \brief MBX_VOE*/
|
||||
#define ALSTATUSCODE_EE_NOACCESS 0x0050 /**< \brief EEPROM no access*/
|
||||
#define ALSTATUSCODE_EE_ERROR 0x0051 /**< \brief EEPROM Error*/
|
||||
#define ALSTATUSCODE_EXT_HARDWARE_NOT_READY 0x0052 /**< \brief External hardware not ready. This AL Status Code should be used if the EtherCAT-Slave refused the state transition due to an external connection to another device or signal is missing*/
|
||||
#define ALSTATUSCODE_MODULE_ID_LIST_NOT_MATCH 0x0070 /**< \brief Detected Module Ident List (0xF030) and Configured Module Ident List (0xF050) does not match*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- Configured Sync Type (0x1C32.1 / 0x1C33.1)
|
||||
-----------------------------------------------*/
|
||||
#define SYNCTYPE_FREERUN 0x0000 /**< \brief Sync type FreeRun*/
|
||||
/*ECATCHANGE_START(V5.11) ESM7*/
|
||||
#define SYNCTYPE_SM_SYNCHRON 0x0001 /**< \brief SyncManager synchron (synchron to the corresponding SM, 0x1C32.1 -> SM2 ; 0x1C33.1 -> SM3) */
|
||||
#define SYNCTYPE_SM2_SYNCHRON 0x0022 /**< \brief SyncManager2 synchron (only used for 0x1C33.1)*/
|
||||
/*ECATCHANGE_END(V5.11) ESM7*/
|
||||
#define SYNCTYPE_DCSYNC0 0x0002 /**< \brief Sync type Sync0 synchron*/
|
||||
#define SYNCTYPE_DCSYNC1 0x0003 /**< \brief Sync type Sync1 synchron*/
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- AL Event masks
|
||||
-----------------------------------------------*/
|
||||
#define AL_CONTROL_EVENT ((UINT16) 0x01) /**< \brief AL Control event*/
|
||||
#define SYNC0_EVENT ((UINT16) 0x04) /**< \brief Sync0 event*/
|
||||
#define SYNC1_EVENT ((UINT16) 0x08) /**< \brief Sync1 event*/
|
||||
#define SM_CHANGE_EVENT ((UINT16) 0x10) /**< \brief SyncManager changed event*/
|
||||
#define EEPROM_CMD_PENDING ((UINT16) 0x20) /**< \brief EEPROM command pending*/
|
||||
#define MAILBOX_WRITE_EVENT ((UINT16) 0x0100) /**< \brief MBoxOut write event*/
|
||||
#define MAILBOX_READ_EVENT ((UINT16) 0x0200) /**< \brief MBoxIn read event*/
|
||||
#define PROCESS_OUTPUT_EVENT ((UINT16) 0x0400) /**< \brief Output process data write event*/
|
||||
#define PROCESS_INPUT_EVENT ((UINT16) 0x0800) /**< \brief Input process data read event*/
|
||||
|
||||
|
||||
#ifndef MAX_PD_SYNC_MAN_CHANNELS
|
||||
#define MAX_PD_SYNC_MAN_CHANNELS 2 /**< \brief maximum number of process data SyncManager channels*/
|
||||
#endif
|
||||
#define MAX_NUMBER_OF_SYNCMAN ((MAX_PD_SYNC_MAN_CHANNELS)+2) /**< \brief Maximum number of SyncManagers (max number of Pd SM + 2 mailbox SyncManager)*/
|
||||
|
||||
#define MAILBOX_WRITE 0 /**< \brief SyncManager ID for MBoxOut (master to slave)*/
|
||||
#define MAILBOX_READ 1 /**< \brief SyncManager ID for MBoxIn (slave to master)*/
|
||||
#define PROCESS_DATA_OUT 2 /**< \brief SyncManager ID for output process data (master to slave)*/
|
||||
#define PROCESS_DATA_IN 3 /**< \brief SyncManager ID for input process data (slave to master)*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define MEMORY_START_ADDRESS 0x1000 /**< \brief ESC DPRAM start address*/
|
||||
|
||||
|
||||
#ifndef DC_SYNC_ACTIVE
|
||||
#define DC_SYNC_ACTIVE ESC_DC_SYNC0_ACTIVE_MASK /**< \brief Expected activated Sync signals by the master*/
|
||||
#endif
|
||||
#ifndef DC_EVENT_MASK
|
||||
#define DC_EVENT_MASK PROCESS_OUTPUT_EVENT /**< \brief AL Event mask (value of register 0x204) in case of DC synchronisation*/
|
||||
#endif
|
||||
|
||||
|
||||
#endif //_ECATSLV_H_
|
||||
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_ECATSLV_) && (_ECATSLV_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
PROTO BOOL bEcatOutputUpdateRunning; /**< \brief Indicates the OP state, will be set in StartOutputHandler
|
||||
and reset in StopOutputHandler*/
|
||||
|
||||
PROTO BOOL bEcatInputUpdateRunning; /**< \brief Indicates the SAFEOP or OP state, will be set in StartInputHandler
|
||||
and reset in Stop InputHandler*/
|
||||
|
||||
PROTO BOOL bEcatFirstOutputsReceived; /**< \brief Indicates if outputs were received (SM2-event)
|
||||
or inputs were read (SM3-event, if the output size is 0),
|
||||
has to be set by the application and reset in StopOutputHandler*/
|
||||
|
||||
PROTO BOOL bWdTrigger; /**< \brief Indicates that the SM2 WD trigger bit (0x814 bit6) is set.*/
|
||||
|
||||
PROTO BOOL bDcSyncActive; /**< \brief Indicates that the Distributed Clocks synchronization is active,*/
|
||||
|
||||
PROTO INT16 EsmTimeoutCounter; /**< \brief Counter used to detect an ESM timeout.
|
||||
-1 indicates a deactivated counter and 0 is expired*/
|
||||
|
||||
#define MAX_SM_EVENT_MISSED 4 /**< \brief threshold of max missed counter value (0x1C32.11 / 0x1C33.11)*/
|
||||
|
||||
PROTO BOOL bDcRunning; /**< \brief Indicates if Sync0 events are received*/
|
||||
|
||||
PROTO UINT16 u16SmSync0Counter; /**< /brief Incremented by one on every Sync0 event and reset to 0 on every SM event. It is used to check the SM/Sync0 sequence */
|
||||
PROTO UINT16 u16SmSync0Value; /**< /brief Allowed Sync0 events within one SM cycle. If 0 the Sequence check is disabled */
|
||||
|
||||
PROTO BOOL bSmSyncSequenceValid; /**< \brief Set to true if SM/Sync0 sequence is valid*/
|
||||
|
||||
PROTO INT16 i16WaitForPllRunningTimeout; /**< \brief The time bPllRunnig shall be true while state change from SafeOp to Op*/
|
||||
|
||||
PROTO INT16 i16WaitForPllRunningCnt; /**< \brief Incremented with every valid Sync-SyncManger cycle */
|
||||
|
||||
PROTO UINT16 Sync0WdCounter; /**< \brief Sync0 watchdog counter*/
|
||||
PROTO UINT16 Sync0WdValue; /**< \brief Sync0 watchdog value*/
|
||||
|
||||
PROTO UINT16 Sync1WdCounter; /**< \brief Sync1 watchdog counter*/
|
||||
PROTO UINT16 Sync1WdValue; /**< \brief Sync1 watchdog value*/
|
||||
|
||||
PROTO UINT16 LatchInputSync0Value; /**< \brief Sync0 event on which the inputs shall be latched and copied to the ESC buffer. If the inputs shall be latched base don Sync1 the value is set to 0.*/
|
||||
PROTO UINT16 LatchInputSync0Counter; /**< \brief Sync0 counter used to get the Sync0 event to latch the Inputs. */
|
||||
|
||||
|
||||
|
||||
|
||||
PROTO BOOL bEscIntEnabled; /**< \brief Indicates that the ESC interrupt is enabled (SM2/3 or SYNC0/1-event),
|
||||
will be set in StartInputHandler and reset in StopInputHandler*/
|
||||
|
||||
PROTO BOOL b3BufferMode; /**< \brief Indicates that inputs and outputs are running in 3-Buffer-Mode*/
|
||||
|
||||
PROTO BOOL bLocalErrorFlag; /**< \brief Contains the information if the application has a local error*/
|
||||
PROTO UINT16 u16LocalErrorCode; /**< \brief Reason for local error*/
|
||||
PROTO BOOL bApplEsmPending; /**< \brief Indicates if the local application ESM function need to be called from Al_ConntrolRes (is true if NOERR_INWORK is returned by generic ESM function)*/
|
||||
PROTO BOOL bEcatWaitForAlControlRes; /**< \brief Contains the information that the state machine waits for an acknowledge
|
||||
for the last AL_ControlInd from the application/generic stack*/
|
||||
|
||||
PROTO UINT16 nEcatStateTrans; /**< \brief Current state transition*/
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
|
||||
PROTO UINT16 nPdInputSize; /**< \brief Contains the input size (SM3 size)/SM2 if no outputs are supported, has to be written by the application*/
|
||||
|
||||
PROTO UINT16 nPdOutputSize; /**< \brief Contains the output size (SM2 size), has to be written by the application*/
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
|
||||
PROTO UINT8 nMaxSyncMan; /**< \brief Contains the maximum number of Sync Manager channels, will be initialized in ECAT_Main*/
|
||||
|
||||
PROTO UINT16 nMaxEscAddress; /**< \brief Max supported address by the ESC (register(0x1000) + size of the supported DPRAM in Byte)*/
|
||||
|
||||
PROTO UINT8 nAlStatus; /**< \brief Contains the actual AL Status, will be written in AL_ControlInd*/
|
||||
|
||||
|
||||
PROTO UINT16 EcatWdValue; /**< \brief Contains the value of the watchdog in 100us, will be written in StartInputHandler.
|
||||
In case that the ESC watchdog feature is used this variable just indicates if the watchdog is enabled or disabled*/
|
||||
PROTO UINT16 nEscAddrOutputData; /**< \brief Contains the SM address for the output process data*/
|
||||
PROTO UINT16 nEscAddrInputData; /**< \brief Contains the SM address for the input process data*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
PROTO void EnableSyncManChannel(UINT8 channel);
|
||||
PROTO void DisableSyncManChannel(UINT8 channel);
|
||||
PROTO TSYNCMAN ESCMEM *GetSyncMan(UINT8 channel);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
PROTO void SetALStatus(UINT8 alStatus, UINT16 alStatusCode);
|
||||
PROTO void AL_ControlInd(UINT8 alControl, UINT16 alStatusCode);
|
||||
PROTO void DC_CheckWatchdog(void);
|
||||
PROTO void CheckIfEcatError(void);
|
||||
PROTO void ECAT_Init(void);
|
||||
|
||||
PROTO void ECAT_Main(void);
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @file el9800appl.h
|
||||
* @brief EL9800 应用层桩头文件 — 本项目不使用 EL9800 示例应用
|
||||
*
|
||||
* ecatappl.c / ecatslv.c 无条件 include 此文件。
|
||||
* 本项目的应用逻辑在 dc_app.c/h 中实现。
|
||||
*/
|
||||
#ifndef _EL9800APPL_H_
|
||||
#define _EL9800APPL_H_
|
||||
|
||||
#include "ecatappl.h"
|
||||
#include "objdef.h"
|
||||
#include "dc_app.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Application Object Dictionary
|
||||
------
|
||||
------ Phase 1: 空(无应用层专用 OD 条目)。
|
||||
------ Phase 3: 在此添加 DCU 专用对象(如 0x2000 传感器数据)。
|
||||
------ coeappl.c 中 COE_ObjInit() 会检查此指针,NULL 则跳过。
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#if defined(_EL9800APPL_) && (_EL9800APPL_ == 1)
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
PROTO TOBJECT OBJMEM * VARMEM ApplicationObjDic;
|
||||
|
||||
#undef PROTO
|
||||
|
||||
#endif /* _EL9800APPL_H_ */
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @file el9800hw.h
|
||||
* @brief SSC 硬件抽象层 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
|
||||
*
|
||||
* 本文件替换 Beckhoff EL9800 SPI 版本,提供 SSC 所需的 HW_EscRead/Write 宏
|
||||
* 和中断/定时器控制宏。实际实现在 hw_gd32exmc.cpp。
|
||||
*
|
||||
* HBI Indexed 寻址(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16;
|
||||
* EXMC 16-bit 模式地址左移 1 位,EXMC_A16=CPU 偏移 0x20000、A17=0x40000):
|
||||
* IDX_0 = EXMC offset 0x00000 (A2=0, A1=0)
|
||||
* DATA_0 = EXMC offset 0x40000 (A2=1, A1=0) 低 16 位
|
||||
* = EXMC offset 0x60000 (A2=1, A1=1) 高 16 位
|
||||
*/
|
||||
#ifndef _EL9800HW_H_
|
||||
#define _EL9800HW_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "esc.h"
|
||||
|
||||
/* GD32E508 CMSIS (通过 RTE 组件获取) */
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Hardware Timer Settings
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/** SSC 看门狗定时器分辨率:ticks per ms。
|
||||
* 本项目使用 SysTick (180MHz / 1000 = 180000 ticks/ms)。
|
||||
* 实际由 hw_gd32exmc.cpp 中 HW_GetTimer() 实现决定。 */
|
||||
#define ECAT_TIMER_INC_P_MS 180000
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Interrupt Control (ESC IRQ = EXTI1, PB1)
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef DISABLE_ESC_INT
|
||||
#define DISABLE_ESC_INT() NVIC_DisableIRQ(EXTI1_IRQn)
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_ESC_INT
|
||||
#define ENABLE_ESC_INT() NVIC_EnableIRQ(EXTI1_IRQn)
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ AL Event Interrupt Mask (保留宏)
|
||||
------
|
||||
------ 1.5.27 起主循环 HBI 访问的互斥已内置到 hw_gd32exmc.cpp 的 HW_* 函数
|
||||
------ (EscExmcBus::maskEnter/maskExit),SSC 源码不再需要显式调用本组宏。
|
||||
------ 保留定义仅为兼容 SSC 变体中可能出现的引用。
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef DISABLE_AL_EVENT_INT
|
||||
#define DISABLE_AL_EVENT_INT() do { NVIC_DisableIRQ(EXTI0_IRQn); NVIC_DisableIRQ(EXTI1_IRQn); } while(0)
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_AL_EVENT_INT
|
||||
#define ENABLE_AL_EVENT_INT() do { NVIC_EnableIRQ(EXTI1_IRQn); NVIC_EnableIRQ(EXTI0_IRQn); } while(0)
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Timer Access
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef HW_GetTimer
|
||||
#define HW_GetTimer() HW_GetTimerValue()
|
||||
#endif
|
||||
|
||||
#ifndef HW_ClearTimer
|
||||
#define HW_ClearTimer() HW_ClearTimerValue()
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ ESC Access Macros (SSC 核心接口)
|
||||
------
|
||||
------ MEM_ADDR = UINT16 (CONTROLLER_16BIT=1)
|
||||
------ 所有宏最终调用 HW_EscRead / HW_EscWrite (hw_gd32exmc.cpp)
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#define HW_EscReadByte(ByteValue, Address) HW_EscRead(((MEM_ADDR *)&(ByteValue)), ((UINT16)(Address)), 1)
|
||||
#define HW_EscReadWord(WordValue, Address) HW_EscRead(((MEM_ADDR *)&(WordValue)), ((UINT16)(Address)), 2)
|
||||
#define HW_EscReadDWord(DWordValue, Address) HW_EscRead(((MEM_ADDR *)&(DWordValue)), ((UINT16)(Address)), 4)
|
||||
#define HW_EscReadMbxMem(pData, Address, Len) HW_EscRead(((MEM_ADDR *)(pData)), ((UINT16)(Address)), (Len))
|
||||
|
||||
#define HW_EscReadByteIsr(ByteValue, Address) HW_EscReadIsr(((MEM_ADDR *)&(ByteValue)), ((UINT16)(Address)), 1)
|
||||
#define HW_EscReadWordIsr(WordValue, Address) HW_EscReadIsr(((MEM_ADDR *)&(WordValue)), ((UINT16)(Address)), 2)
|
||||
#define HW_EscReadDWordIsr(DWordValue, Address) HW_EscReadIsr(((MEM_ADDR *)&(DWordValue)), ((UINT16)(Address)), 4)
|
||||
|
||||
#define HW_EscWriteByte(ByteValue, Address) HW_EscWrite(((MEM_ADDR *)&(ByteValue)), ((UINT16)(Address)), 1)
|
||||
#define HW_EscWriteWord(WordValue, Address) HW_EscWrite(((MEM_ADDR *)&(WordValue)), ((UINT16)(Address)), 2)
|
||||
#define HW_EscWriteDWord(DWordValue, Address) HW_EscWrite(((MEM_ADDR *)&(DWordValue)), ((UINT16)(Address)), 4)
|
||||
#define HW_EscWriteMbxMem(pData, Address, Len) HW_EscWrite(((MEM_ADDR *)(pData)), ((UINT16)(Address)), (Len))
|
||||
|
||||
#define HW_EscWriteByteIsr(ByteValue, Address) HW_EscWriteIsr(((MEM_ADDR *)&(ByteValue)), ((UINT16)(Address)), 1)
|
||||
#define HW_EscWriteWordIsr(WordValue, Address) HW_EscWriteIsr(((MEM_ADDR *)&(WordValue)), ((UINT16)(Address)), 2)
|
||||
#define HW_EscWriteDWordIsr(DWordValue, Address) HW_EscWriteIsr(((MEM_ADDR *)&(DWordValue)), ((UINT16)(Address)), 4)
|
||||
|
||||
#endif /* _EL9800HW_H_ */
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Function Prototypes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#if _EL9800HW_
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
PROTO UINT8 HW_Init(void);
|
||||
PROTO void HW_Release(void);
|
||||
PROTO UINT16 HW_GetALEventRegister(void);
|
||||
PROTO UINT16 HW_GetALEventRegister_Isr(void);
|
||||
PROTO void HW_ResetALEventMask(UINT16 intMask);
|
||||
PROTO void HW_SetALEventMask(UINT16 intMask);
|
||||
PROTO void HW_SetLed(UINT8 RunLed, UINT8 ErrLed);
|
||||
|
||||
PROTO void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len);
|
||||
PROTO void HW_EscReadIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len);
|
||||
PROTO void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len);
|
||||
PROTO void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len);
|
||||
|
||||
PROTO void HW_DisableSyncManChannel(UINT8 channel);
|
||||
PROTO void HW_EnableSyncManChannel(UINT8 channel);
|
||||
PROTO TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel);
|
||||
|
||||
/* Timer helpers (implemented in hw_gd32exmc.cpp) */
|
||||
PROTO UINT32 HW_GetTimerValue(void);
|
||||
PROTO void HW_ClearTimerValue(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef PROTO
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* \addtogroup ESCRegister ESC Register
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file esc.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief EtherCAT Slave Controller related defines and Types
|
||||
|
||||
\version 5.10
|
||||
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 ESC1: Update address register offset for 32Bit ESC access<br>
|
||||
V5.10 ESC2: Check if defined SM settings do not exceed the available DPRAM range (in error case AL Status 0x14 is returned)<br>
|
||||
V5.10 ESC3: Handle DC cControl register values in case of 32Bit ESC access (a Sync activation mask need to defined/used)<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
#ifndef _ESC_H_
|
||||
#define _ESC_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#define SIZEOF_SM_REGISTER 8 /**< \brief Each SyncManger has 8Byte Configuration/Status Registers*/
|
||||
#define MAX_NO_OF_SYNC_MAN 16/**< \brief Maximum Number of SyncManager channels supported by an ESC*/
|
||||
|
||||
#define BL_PAGE_SIZE 512
|
||||
|
||||
/*---------------------------------------------
|
||||
- ESC Offsets (Detailed Information about the registers are located in the ESC Datasheets e.g. ET1100 Datasheet www.beckhoff.com/english.asp?download/ethercat_development_products.htm?id=71003127100387)
|
||||
-----------------------------------------------*/
|
||||
#define ESC_INFO_OFFSET 0x0000 /**< \brief ESC information registers startoffset*/
|
||||
|
||||
#define ESC_COMM_INFO_OFFSET 0x0004 /**< \brief Communication information registers startoffset*/
|
||||
|
||||
#define ESC_DPRAM_SIZE_OFFSET 0x0006 /**< \brief Register Description: Size of the supported DPRAM in KB*/
|
||||
#define ESC_SM_CHANNELS_MASK 0xFF00 /**< \brief Mask based on ESC offset "ESC_COMM_INFO_OFFSET"*/
|
||||
#define ESC_SM_CHANNELS_SHIFT 8 /**< \brief Bit shift based on ESC offset "ESC_COMM_INFO_OFFSET"*/
|
||||
#define ESC_DPRAM_SIZE_MASK 0x00FF
|
||||
|
||||
|
||||
#define ESC_SLAVE_ADDRESS_OFFSET 0x0010 /**< \brief Register Description: Address used for node addressing (FPxx commands)*/
|
||||
|
||||
|
||||
|
||||
#define ESC_AL_CONTROL_OFFSET 0x0120 /**< \brief Register Description: Initiate State Transition of the Device State Machine*/
|
||||
#define ESC_AL_STATUS_OFFSET 0x0130 /**< \brief Register Description: Actual State of the Device State Machine*/
|
||||
#define ESC_AL_STATUS_CODE_OFFSET 0x0134 /**< \brief Register Description: AL Status Code*/
|
||||
|
||||
#define ESC_RUN_LED_OVERRIDE 0x0138 /**< \brief Register Description: Set Ecat Run indication via ESC. (not all ESC types support this feature)*/
|
||||
#define ESC_ERROR_LED_OVERRIDE 0x0139 /**< \brief Register Description: Set Ecat Error indication via ESC. (not all ESC types support this feature)*/
|
||||
|
||||
#define ESC_PDI_CONTROL_OFFSET 0x0140 /**< \brief Register Description: Specifies the process data interface*/
|
||||
|
||||
#define ESC_AL_EVENTMASK_OFFSET 0x0204 /**< \brief Register Description: AL Event masking of the AL Event Request register Events for mapping to PDI IRQ signal*/
|
||||
#define ESC_AL_EVENT_OFFSET 0x0220 /**< \brief Register Description: "Mirror" register for ESC events*/
|
||||
|
||||
#define ESC_WD_DIVIDER_OFFSET 0x0400 /**< \brief Register Description: Number of 25 MHz tics (minus 2) that represents the basic watchdog increment. (Default value is 100us = 2498)*/
|
||||
|
||||
#define ESC_PD_WD_TIME 0x0420 /**< \brief Register Description: Number of basic watchdog increments (Default value with Watchdog divider 100us means 100ms Watchdog)*/
|
||||
#define ESC_PD_WD_STATE 0x0440 /**< \brief Register Description: Watchdog Status of Process Data (triggered by SyncManagers)*/
|
||||
#define ESC_PD_WD_TRIGGER_MASK 0x0001 /**< \brief Trigger state of the process data watchdog*/
|
||||
|
||||
#define ESC_EEPROM_CONFIG_OFFSET 0x0500 /**< \brief Register Description: EEPROM Access Configuration*/
|
||||
/*EEPROM config and access state bit mask (based on "ESC_EEPROM_CONFIG_OFFSET") - START*/
|
||||
#define ESC_EEPROM_ASSIGN_TO_PDI_MASK 0x0001 /**< \brief Description (0x500.0): PDI has EEPROM control*/
|
||||
#define ESC_EEPROM_LOCKED_BY_PDI_MASK 0x0100 /**< \brief Description (0x500.8): PDI locked EEPROM access*/
|
||||
/*EEPROM config and access state bit mask (based on "ESC_EEPROM_CONFIG_OFFSET") - END*/
|
||||
|
||||
#define ESC_EEPROM_CONTROL_OFFSET 0x0502
|
||||
/* EEPROM command and status bit masks (based on "ESC_EEPROM_CONTROL_OFFSET") - START*/
|
||||
#define ESC_EEPROM_SUPPORTED_READBYTES_MASK 0x0040 /**< \brief Description (0x502.6): Supported number of EEPROM read bytes: 0-> 4 Bytes; 1 -> 8 Bytes*/
|
||||
#define ESC_EEPROM_CMD_MASK 0x0700 /**< \brief Description (0x502.8:10): Command bit mask*/
|
||||
#define ESC_EEPROM_CMD_READ_MASK 0x0100 /**< \brief Description (0x502.8): Currently executed read command*/
|
||||
#define ESC_EEPROM_CMD_WRITE_MASK 0x0200 /**< \brief Description (0x502.9): Initialize Write Command*/
|
||||
#define ESC_EEPROM_CMD_RELOAD_MASK 0x0400 /**< \brief Description (0x502.10): Trigger EEPROM reload*/
|
||||
#define ESC_EEPROM_ERROR_MASK 0x7800 /**< \brief Description : Mask all EEPROM error bits; Checksum error (0x0502.11); EEPROM not loaded (0x0502.12); Missing EEPROM Acknowledge (0x0502.13); Write Error (0x0502.14)*/
|
||||
#define ESC_EEPROM_ERROR_CRC 0x0800 /**< \brief Description (0x502.11): EEPROM CRC Error*/
|
||||
#define ESC_EEPROM_ERROR_LOAD 0x1000 /**< \brief Description (0x502.11): EEPROM loading status (0 = OK)*/
|
||||
#define ESC_EEPROM_ERROR_CMD_ACK 0x2000 /**< \brief Description (0x502.13): EEPROM Acknowledge/Command*/
|
||||
#define ESC_EEPROM_BUSY_MASK 0x8000 /**< \brief Description (0x502.15): EEPROM Busy*/
|
||||
/* EEPROM command and status bit masks (based on "ESC_EEPROM_CONTROL_OFFSET") - END*/
|
||||
#define ESC_EEPROM_ADDRESS_OFFSET 0x0504
|
||||
#define ESC_EEPROM_DATA_OFFSET 0x0508
|
||||
|
||||
#define ESC_SYNCMAN_REG_OFFSET 0x0800 /**< \brief Register Description: Start address of the SyncManager Configuration/Staus registers*/
|
||||
#define ESC_SYNCMAN_CONTROL_OFFSET 0x0804 /**< \brief Register Description: SyncManager Setting Register*/
|
||||
|
||||
|
||||
#define ESC_SYNCMAN_ACTIVE_OFFSET 0x0806 /**< \brief Register Description: SyncManager Activation Register*/
|
||||
|
||||
|
||||
#define ESC_SYSTEMTIME_OFFSET 0x0910 /**< \brief Register Description: Local copy of the System Time*/
|
||||
|
||||
#define ESC_DC_UNIT_CONTROL_OFFSET 0x0980 /**< \brief Register Description: Control registers for Cycle and Sync Unit (can be predefined with the "AssignActivate" Element in the device description, for further information see ETG.2000)*/
|
||||
#define ESC_DC_SYNC_UNIT_ACTIVE_MASK 0x0100 /**< \brief Description (0x980.8): Sync Out Unit is activated*/
|
||||
#define ESC_DC_SYNC0_ACTIVE_MASK 0x0200 /**< \brief Description (0x980.9): Sync0 generation is activated*/
|
||||
#define ESC_DC_SYNC1_ACTIVE_MASK 0x0400 /**< \brief Description (0x980.10): Sync1 generation is activated*/
|
||||
#define ESC_DC_SYNC_UNIT_AUTO_ACTIVE_MASK 0x0800 /**< \brief Description (0x980.11): Sync Out Unit is activated automatic when System time was written*/
|
||||
|
||||
#define ESC_DC_SYNC0_CYCLETIME_OFFSET 0x09A0 /**< \brief Register Description: 32Bit Time between two consecutive SYNC0 pulses in ns*/
|
||||
#define ESC_DC_SYNC1_CYCLETIME_OFFSET 0x09A4 /**< \brief Register Description: 32Bit Time between two consecutive SYNC1 pulses in ns*/
|
||||
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- Sync Manager
|
||||
-----------------------------------------------*/
|
||||
/** \brief SyncManager Register struture*/
|
||||
typedef struct STRUCT_PACKED_START
|
||||
{
|
||||
|
||||
UINT16 PhysicalStartAddress; /**< \brief SyncManager address*/
|
||||
UINT16 Length; /**< \brief SyncManager length*/
|
||||
|
||||
UINT16 Settings[2]; /**< \brief Register values 0x0804 : 0x0807*/
|
||||
|
||||
/*Defines to access :
|
||||
* - SM Control register 0x0804
|
||||
* - SM Status register 0x0805
|
||||
*
|
||||
* all defines are based on 0x0804
|
||||
*/
|
||||
|
||||
/*SyncManger control (0x0804) access*/
|
||||
#define SM_SETTING_CONTROL_OFFSET 0 /**< \brief Offset to value of register 0x0804*/
|
||||
#define SM_SETTING_MODE_MASK 0x0002 /**< \brief SyncManager mode mask*/
|
||||
#define SM_SETTING_MODE_THREE_BUFFER_VALUE 0x0000 /**< \brief SyncManager 3Buffer mode value*/
|
||||
#define SM_SETTING_MODE_ONE_BUFFER_VALUE 0x0002 /**< \brief SyncManager 1Buffer mode value*/
|
||||
#define SM_SETTING_DIRECTION_MASK 0x000C /**< \brief SyncManager direction mask*/
|
||||
#define SM_SETTING_DIRECTION_READ_VALUE 0x0000 /**< \brief SyncManager read direction*/
|
||||
#define SM_SETTING_DIRECTION_WRITE_VALUE 0x0004 /**< \brief SyncManager write direction*/
|
||||
#define SM_SETTING_WATCHDOG_VALUE 0x0040 /**< \brief SyncManager watchdog value*/
|
||||
|
||||
/*SyncManger status (0x0805) access*/
|
||||
#define SM_STATUS_MBX_BUFFER_FULL 0x0800 /**< \brief Indicates in one buffer mode if buffer was completely written*/
|
||||
|
||||
/*Defines to access :
|
||||
* - SM Active register 0x0806
|
||||
* - SM PDI Control register 0x0807
|
||||
*
|
||||
* all defines are based on 0x0806
|
||||
*/
|
||||
|
||||
/*SyncManger active (0x0806) access*/
|
||||
#define SM_SETTING_ACTIVATE_OFFSET 1 /**< \brief Offset to value of register 0x0806*/
|
||||
#define SM_SETTING_ENABLE_VALUE 0x0001 /**< \brief SyncManager enable*/
|
||||
#define SM_SETTING_REPAET_REQ_MASK 0x0002 /**< \brief SyncManager Repeat request*/
|
||||
#define SM_SETTING_REPEAT_REQ_SHIFT 0 /**< \brief SyncManager Repeat request shift*/
|
||||
|
||||
/*SyncManger PDI Control (0x0807) access*/
|
||||
#define SM_SETTING_PDI_DISABLE 0x0100 /**< \brief Bit0 of register 0x0807 (if 1 SM is disabled from PDI)*/
|
||||
#define SM_SETTING_REPEAT_ACK 0x0200 /**< \brief Bit1 of register 0x0807*/
|
||||
}STRUCT_PACKED_END
|
||||
TSYNCMAN;
|
||||
|
||||
|
||||
|
||||
#endif //_ESC_H_
|
||||
/** @}*/
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* \addtogroup Mailbox Mailbox Functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file mailbox.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
V5.11 MBX1: "return value of ""MBX_StartMailboxHandler()"" changed to UINT16"<br>
|
||||
V5.11 MBX3: set application triggered emergency and EoE data to pending if no mailbox queue is supported and another mailbox request is currently handled, Handle only one mailbox request at a time (in case that MAILBPX_QUEUE is disabled)<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
|
||||
#ifndef _MAILBOX_H_
|
||||
#define _MAILBOX_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#define MBX_TYPE_AOE 1 /**< \brief Mailbox type AoE*/
|
||||
#define MBX_TYPE_EOE 2 /**< \brief Mailbox type EoE*/
|
||||
#define MBX_TYPE_COE 3 /**< \brief Mailbox type CoE*/
|
||||
#define MBX_TYPE_FOE 4 /**< \brief Mailbox type FoE*/
|
||||
#define MBX_TYPE_SOE 5 /**< \brief Mailbox type SoE*/
|
||||
#define MBX_TYPE_VOE 15 /**< \brief Mailbox type VoE*/
|
||||
|
||||
#define EMCY_SERVICE ((UINT8) 0x0001) /**< \brief Emergency service*/
|
||||
#define COE_SERVICE ((UINT8) 0x0002) /**< \brief CoE service*/
|
||||
#define SOE_SERVICE ((UINT8) 0x0004) /**< \brief SoE service*/
|
||||
#define EOE_SERVICE ((UINT8) 0x0008) /**< \brief EoE service*/
|
||||
#define AOE_SERVICE ((UINT8) 0x0010) /**< \brief AoE service*/
|
||||
#define VOE_SERVICE ((UINT8) 0x0020) /**< \brief VoE service*/
|
||||
#define FOE_SERVICE ((UINT8) 0x0040) /**< \brief FoE service*/
|
||||
#define FRAGMENTS_FOLLOW ((UINT8) 0x0080) /**< \brief Fragments follow service*/
|
||||
|
||||
#ifndef DISABLE_MBX_INT
|
||||
#define DISABLE_MBX_INT /**< \brief Disable mailbox interrupt*/
|
||||
#endif
|
||||
#ifndef ENABLE_MBX_INT
|
||||
#define ENABLE_MBX_INT /**< \brief Enable mailbox interrupt*/
|
||||
#endif
|
||||
|
||||
#ifndef ENTER_MBX_CRITICAL
|
||||
#define ENTER_MBX_CRITICAL /**< \brief Enter mailbox critical section*/
|
||||
#endif
|
||||
|
||||
#ifndef LEAVE_MBX_CRITICAL
|
||||
#define LEAVE_MBX_CRITICAL /**< \brief Leave mailbox critical section*/
|
||||
#endif
|
||||
|
||||
#ifndef MAX_MBX_QUEUE_SIZE
|
||||
#define MAX_MBX_QUEUE_SIZE 10 /**< \brief Mailbox queue size*/
|
||||
#endif
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- Command Codes for the mailbox type 0
|
||||
-----------------------------------------------*/
|
||||
#define MBXSERVICE_MBXERRORCMD 0x01 /**< \brief Mailbox error command*/
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- Error Codes for a mailbox error response
|
||||
-----------------------------------------------*/
|
||||
#define MBXERR_SYNTAX 0x01 /**< \brief Mailbox error "syntax"*/
|
||||
#define MBXERR_UNSUPPORTEDPROTOCOL 0x02 /**< \brief Mailbox error "unsupported protocol"*/
|
||||
#define MBXERR_INVALIDCHANNEL 0x03 /**< \brief Mailbox error "invalid channel"*/
|
||||
#define MBXERR_SERVICENOTSUPPORTED 0x04 /**< \brief Mailbox error "service not supported"*/
|
||||
#define MBXERR_INVALIDHEADER 0x05 /**< \brief Mailbox error "invalid header"*/
|
||||
#define MBXERR_SIZETOOSHORT 0x06 /**< \brief Mailbox error "Size too short"*/
|
||||
#define MBXERR_NOMOREMEMORY 0x07 /**< \brief Mailbox error "No memory"*/
|
||||
#define MBXERR_INVALIDSIZE 0x08 /**< \brief Mailbox error "Invalid size"*/
|
||||
#define MBXERR_SERVICEINWORK 0x09 /**< \brief Mailbox error "Service in work"*/
|
||||
|
||||
|
||||
/**
|
||||
* \brief Mailbox header
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 Length; /**< \brief Length*/
|
||||
UINT16 Address; /**< \brief Address*/
|
||||
|
||||
UINT16 Flags[1]; /**< \brief Flags*/
|
||||
#define MBX_OFFS_TYPE 0 /**< \brief Protocol type offset*/
|
||||
#define MBX_MASK_TYPE 0x0F00 /**< \brief Protocol type mask*/
|
||||
#define MBX_SHIFT_TYPE 8 /**< \brief Protocol type shift*/
|
||||
#define MBX_OFFS_COUNTER 0 /**< \brief Protocol counter offset*/
|
||||
#define MBX_MASK_COUNTER 0xF000 /**< \brief Protocol counter mask*/
|
||||
#define MBX_SHIFT_COUNTER 12 /**< \brief Protocol counter shift*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TMBXHEADER;
|
||||
|
||||
|
||||
#define MBX_HEADER_SIZE SIZEOF(TMBXHEADER) /**< \brief Mailbox header size*/
|
||||
|
||||
|
||||
#define MAX_MBX_DATA_SIZE (MAX_MBX_SIZE - MBX_HEADER_SIZE) /**< \brief Mailbox data size*/
|
||||
|
||||
/**
|
||||
* \brief Mailbox datagram
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
UINT16 Data[(MAX_MBX_DATA_SIZE >> 1)]; /**< \brief Mailbox data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TMBX;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Mailbox queue
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
UINT16 firstInQueue; /**< \brief First in Queue*/
|
||||
UINT16 lastInQueue; /**< \brief Last in Queue*/
|
||||
UINT16 maxQueueSize; /**< \brief Max queue size*/
|
||||
TMBX MBXMEM * queue[(MAX_MBX_QUEUE_SIZE)+1]; /**< \brief Queue buffer*/
|
||||
} TMBXQUEUE;
|
||||
|
||||
|
||||
|
||||
#endif //_MAILBOX_H_
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_MAILBOX_) && (_MAILBOX_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
PROTO BOOL bReceiveMbxIsLocked; /**< \brief Receive mailbox is locked (MBoxOut, default SM0)*/
|
||||
PROTO BOOL bSendMbxIsFull; /**< \brief Send mailbox is full (MBoxIn, default SM1)*/
|
||||
PROTO BOOL bMbxRunning; /**< \brief Mailbox running (At least PreOP state)*/
|
||||
PROTO BOOL bMbxRepeatToggle; /**< \brief Mailbox repeat toggle*/
|
||||
PROTO UINT16 u16SendMbxSize; /**< \brief Send mailbox size (default SM1)*/
|
||||
PROTO UINT16 u16ReceiveMbxSize; /**< \brief Receive mailbox size (default SM0)*/
|
||||
PROTO UINT16 u16EscAddrReceiveMbx; /**< \brief Receive mailbox address (default SM0)*/
|
||||
PROTO UINT16 u16EscAddrSendMbx; /**< \brief Send mailbox address (default SM1)*/
|
||||
PROTO UINT8 u8MbxWriteCounter; /**< \brief Write mailbox counter*/
|
||||
PROTO UINT8 u8MbxReadCounter; /**< \brief Read mailbox counter*/
|
||||
PROTO TMBX MBXMEM asMbx[2]; /**< \brief Mailbox buffer (for repeat service)*/
|
||||
PROTO UINT8 u8MailboxSendReqStored; /**< \brief Mailbox send request stored*/
|
||||
PROTO TMBX MBXMEM * psWriteMbx; /**< \brief Pointer to write mailbox buffer*/
|
||||
PROTO TMBX MBXMEM * psReadMbx; /**< \brief Pointer to read mailbox buffer*/
|
||||
PROTO TMBX MBXMEM * psRepeatMbx; /**< \brief Pointer to repeat mailbox buffer*/
|
||||
PROTO TMBX MBXMEM * psStoreMbx; /**< \brief Pointer to store mailbox buffer*/
|
||||
PROTO TMBXQUEUE MBXMEM sMbxSendQueue; /**< \brief Send mailbox queue*/
|
||||
PROTO TMBXQUEUE MBXMEM sMbxReceiveQueue; /**< \brief Receive mailbox queue*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
PROTO void MBX_Init(void);
|
||||
/* ECATCHANGE_START(V5.11) MBX1*/
|
||||
PROTO UINT16 MBX_StartMailboxHandler(void);
|
||||
/* ECATCHANGE_END(V5.11) MBX1*/
|
||||
PROTO void MBX_StopMailboxHandler(void);
|
||||
PROTO void MBX_MailboxWriteInd(TMBX MBXMEM *pMbx);
|
||||
PROTO void MBX_MailboxReadInd(void);
|
||||
PROTO void MBX_MailboxRepeatReq(void);
|
||||
PROTO UINT8 MBX_MailboxSendReq(TMBX MBXMEM * pMbx, UINT8 flags);
|
||||
PROTO void MBX_CheckAndCopyMailbox(void);
|
||||
PROTO UINT8 MBX_CopyToSendMailbox(TMBX MBXMEM *pMbx);
|
||||
PROTO void MBX_Main(void);
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* \addtogroup CoE CAN Application Profile over EtherCAT
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file objdef.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief CAN application profile over EtherCAT object dictionary
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.10:<br>
|
||||
V5.11 COE2: update synctype supported flag for freerun<br>
|
||||
V5.11 COE3: change 0x10F3.2 (Sync Error limit) from UINT32 to UINT16 (according to the ETG.1020)<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 COE1: Define one entry description for all 0x1C3x objects and change data type of SI11,12,13 to UINT16 (according ETG.1020)<br>
|
||||
V5.10 COE2: Change return value of Get ObjectSize to UINT32<br>
|
||||
Change object size to UINT32<br>
|
||||
V5.10 ECAT13: Update Synchronisation handling (FreeRun,SM Sync, Sync0, Sync1)<br>
|
||||
Compare DC UINT configuration (by ESC Config data) vs. DC activation register settings<br>
|
||||
Update 0x1C3x entries<br>
|
||||
V5.10 ECAT8: Update Datatype defines according ETG.1020<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
|
||||
#ifndef _OBJDEF_H_
|
||||
#define _OBJDEF_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "sdoserv.h"
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------
|
||||
- DataTypes
|
||||
-----------------------------------------------*/
|
||||
#define DEFTYPE_NULL 0x0000 /**< \brief Null*/
|
||||
#define DEFTYPE_BOOLEAN 0x0001 /**< \brief BOOLEAN*/
|
||||
#define DEFTYPE_INTEGER8 0x0002 /**< \brief INTEGER8*/
|
||||
#define DEFTYPE_INTEGER16 0x0003 /**< \brief INTEGER16*/
|
||||
#define DEFTYPE_INTEGER32 0x0004 /**< \brief INTEGER32*/
|
||||
#define DEFTYPE_UNSIGNED8 0x0005 /**< \brief UNSIGNED8*/
|
||||
#define DEFTYPE_UNSIGNED16 0x0006 /**< \brief UNSIGNED16*/
|
||||
#define DEFTYPE_UNSIGNED32 0x0007 /**< \brief UNSIGNED32*/
|
||||
#define DEFTYPE_REAL32 0x0008 /**< \brief REAL32*/
|
||||
#define DEFTYPE_VISIBLESTRING 0x0009 /**< \brief VISIBLE_STRING*/
|
||||
#define DEFTYPE_OCTETSTRING 0x000A /**< \brief OCTET_STRING*/
|
||||
#define DEFTYPE_UNICODE_STRING 0x000B /**< \brief UNICODE_STRING*/
|
||||
#define DEFTYPE_TIME_OF_DAY 0x000C /**< \brief TIME_OF_DAY*/
|
||||
#define DEFTYPE_TIME_DIFFERENCE 0x000D /**< \brief TIME_DIFFERENCE*/
|
||||
#define DEFTYPE_INTEGER24 0x0010 /**< \brief INTEGER24*/
|
||||
#define DEFTYPE_REAL64 0x0011 /**< \brief REAL64*/
|
||||
#define DEFTYPE_INTEGER40 0x0012 /**< \brief INTEGER40*/
|
||||
#define DEFTYPE_INTEGER48 0x0013 /**< \brief INTEGER48*/
|
||||
#define DEFTYPE_INTEGER56 0x0014 /**< \brief INTEGER56*/
|
||||
#define DEFTYPE_INTEGER64 0x0015 /**< \brief INTEGER64*/
|
||||
#define DEFTYPE_UNSIGNED24 0x0016 /**< \brief UNSIGNED24*/
|
||||
#define DEFTYPE_UNSIGNED40 0x0018 /**< \brief UNSIGNED40*/
|
||||
#define DEFTYPE_UNSIGNED48 0x0019 /**< \brief UNSIGNED48*/
|
||||
#define DEFTYPE_UNSIGNED56 0x001A /**< \brief UNSIGNED56*/
|
||||
#define DEFTYPE_UNSIGNED64 0x001B /**< \brief UNSIGNED64*/
|
||||
#define DEFTYPE_GUID 0x001D /**< \brief DEFTYPE_GUID*/
|
||||
#define DEFTYPE_BYTE 0x001E /**< \brief DEFTYPE_BYTE*/
|
||||
#define DEFTYPE_WORD 0x001F /**< \brief DEFTYPE_WORD*/
|
||||
#define DEFTYPE_DWORD 0x0020 /**< \brief DEFTYPE_DWORD*/
|
||||
#define DEFTYPE_PDOMAPPING 0x0021 /**< \brief PDO_MAPPING*/
|
||||
#define DEFTYPE_IDENTITY 0x0023 /**< \brief IDENTITY*/
|
||||
#define DEFTYPE_COMMAND 0x0025 /**< \brief COMMAND_PAR*/
|
||||
#define DEFTYPE_PDOCOMPAR 0x0027 /**< \brief PDO_PARAMETER*/
|
||||
#define DEFTYPE_ENUM 0x0028 /**< \brief DEFTYPE_ENUM */
|
||||
#define DEFTYPE_SMPAR 0x0029 /**< \brief SM_SYNCHRONISATION*/
|
||||
#define DEFTYPE_RECORD 0x002A /**< \brief DEFTYPE_RECORD */
|
||||
#define DEFTYPE_BACKUP 0x002B /**< \brief BACKUP_PARAMETER*/
|
||||
#define DEFTYPE_MDP 0x002C /**< \brief MODULAR_DEVICE_PROFILE*/
|
||||
#define DEFTYPE_BITARR8 0x002D /**< \brief BITARR8*/
|
||||
#define DEFTYPE_BITARR16 0x002E /**< \brief BITARR16*/
|
||||
#define DEFTYPE_BITARR32 0x002F /**< \brief BITARR32*/
|
||||
#define DEFTYPE_BIT1 0x0030 /**< \brief BIT1*/
|
||||
#define DEFTYPE_BIT2 0x0031 /**< \brief BIT2*/
|
||||
#define DEFTYPE_BIT3 0x0032 /**< \brief BIT3*/
|
||||
#define DEFTYPE_BIT4 0x0033 /**< \brief BIT4*/
|
||||
#define DEFTYPE_BIT5 0x0034 /**< \brief BIT5*/
|
||||
#define DEFTYPE_BIT6 0x0035 /**< \brief BIT6*/
|
||||
#define DEFTYPE_BIT7 0x0036 /**< \brief BIT7*/
|
||||
#define DEFTYPE_BIT8 0x0037 /**< \brief BIT8*/
|
||||
#define DEFTYPE_ARRAY_OF_INT 0x0260 /**< \brief DEFTYPE_ARRAY_OF_INT*/
|
||||
#define DEFTYPE_ARRAY_OF_SINT 0x0261 /**< \brief DEFTYPE_ARRAY_OF_SINT*/
|
||||
#define DEFTYPE_ARRAY_OF_DINT 0x0262 /**< \brief DEFTYPE_ARRAY_OF_DINT*/
|
||||
#define DEFTYPE_ARRAY_OF_UDINT 0x0263 /**< \brief DEFTYPE_ARRAY_OF_UDINT*/
|
||||
#define DEFTYPE_ERRORHANDLING 0x0281 /**< \brief DEFTYPE_ERRORHANDLING*/
|
||||
#define DEFTYPE_DIAGHISTORY 0x0282 /**< \brief DEFTYPE_DIAGHISTORY*/
|
||||
#define DEFTYPE_SYNCSTATUS 0x0283 /**< \brief DEFTYPE_SYNCSTATUS*/
|
||||
#define DEFTYPE_SYNCSETTINGS 0x0284 /**< \brief DEFTYPE_SYNCSETTINGS*/
|
||||
#define DEFTYPE_FSOEFRAME 0x0285 /**< \brief DEFTYPE_FSOEFRAME*/
|
||||
#define DEFTYPE_FSOECOMMPAR 0x0286 /**< \brief DEFTYPE_FSOECOMMPAR*/
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- Flags for supported Synctypes (0x1C32.4 / 0x1C33.4)
|
||||
-----------------------------------------------*/
|
||||
|
||||
/*ECATCHANGE_START(V5.11) COE2*/
|
||||
#define SYNCTYPE_FREERUNSUPP 0x0001 /**< \brief FreeRun supported*/
|
||||
/*ECATCHANGE_END(V5.11) COE2*/
|
||||
#define SYNCTYPE_SYNCHRONSUPP 0x0002 /**< \brief SyncManager synchron supported*/
|
||||
#define SYNCTYPE_DCSYNC0SUPP 0x0004 /**< \brief Sync0 synchron supported*/
|
||||
#define SYNCTYPE_DCSYNC1SUPP 0x0008 /**< \brief Sync1 synchron supported*/
|
||||
#define SYNCTYPE_SUBCYCLESUPP 0x0010 /**< \brief Subordinated Application with fixed Sync0 supported*/
|
||||
#define SYNCTYPE_LOCALSHIFTSUPP 0x0020 /**< \brief Event shift with local timer supported. Output shift : 0x1C32.4 ; Input shift : 0x1C33.4 (not supported by the default SSC)*/
|
||||
#define SYNCTYPE_SHIFTBYSYNC1SUPP 0x0040 /**< \brief Event shift with Sync1 event supported. Output shift : 0x1C32.4 ; Input shift : 0x1C33.4*/
|
||||
#define SYNCTYPE_MEASURE_DELAYSUPP 0x0200 /**< \brief Delay times should be measured (not supported by the default SSC, only the cycle time can be measured (1ms resolution)*/
|
||||
#define SYNCTYPE_FIXED_DELAYSUPP 0x0400 /**< \brief Delay times are fixed*/
|
||||
#define SYNCTYPE_TIMESVARIABLE 0x4000 /**< \brief Dynamic Cycle Times supported*/
|
||||
|
||||
|
||||
#define IS_PDO_ASSIGN(x) ((x >= 0x1C10) && (x <= 0x1C2F)) /**< \brief Marco to check if object index is SyncManager assign object*/
|
||||
#define IS_RX_PDO(x) (((x) >= 0x1600) && ((x) <= 0x17FF)) /**< \brief Marco to check if object index RxPDO mapping object*/
|
||||
#define IS_TX_PDO(x) (((x) >= 0x1A00) && ((x) <= 0x1BFF)) /**< \brief Marco to check if object index TxPDO mapping object*/
|
||||
|
||||
|
||||
/**
|
||||
* \brief Object dictionary entry structure
|
||||
*/
|
||||
typedef struct OBJ_ENTRY
|
||||
{
|
||||
struct OBJ_ENTRY *pPrev; /**< \brief Previous entry(object) in the object dictionary list*/
|
||||
struct OBJ_ENTRY *pNext; /**< \brief Next entry(object) in the object dictionary list*/
|
||||
|
||||
UINT16 Index; /**< \brief Object index*/
|
||||
TSDOINFOOBJDESC ObjDesc; /**< \brief Object access, type and code*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM *pEntryDesc; /**< \brief pointer to object entry descriptions*/
|
||||
OBJCONST UCHAR OBJMEM *pName; /**< \brief Pointer to object and entry names*/
|
||||
void MBXMEM *pVarPtr; /**< \brief Pointer to object buffer*/
|
||||
UINT8 (* Read)( UINT16 Index, UINT8 Subindex, UINT32 Size, UINT16 MBXMEM * pData, UINT8 bCompleteAccess ); /**< \brief Function pointer to read function (if NULL default read function will be used)*/
|
||||
UINT8 (* Write)( UINT16 Index, UINT8 Subindex, UINT32 Size, UINT16 MBXMEM * pData, UINT8 bCompleteAccess ); /**< \brief Function pointer to write function (if NULL default write function will be used)*/
|
||||
UINT16 NonVolatileOffset; /**< \brief Offset within the non volatile memory (need to be defined for backup objects)*/
|
||||
}
|
||||
TOBJECT;
|
||||
|
||||
|
||||
/**
|
||||
* Object 0x1C3x (SyncManager Parameter) data structure
|
||||
*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 subindex0;/**< \brief SubIndex0*/
|
||||
UINT16 u16SyncType; /**< \brief SunbIndex 001: Sync type*/
|
||||
UINT32 u32CycleTime;/**< \brief SunbIndex 002: Cycle time*/
|
||||
UINT32 u32ShiftTime;/**< \brief SunbIndex 003: Shift time (not supported, variable is only used as a place holder)*/
|
||||
UINT16 u16SyncTypesSupported;/**< \brief SunbIndex 004: Supported Sync types*/
|
||||
UINT32 u32MinCycleTime;/**< \brief SunbIndex 005: Min cycle time*/
|
||||
UINT32 u32CalcAndCopyTime;/**< \brief SunbIndex 006: Calc and Copy time*/
|
||||
UINT32 u32Si7Reserved;/**< \brief SunbIndex 007: 32Bit Reserved*/
|
||||
UINT16 u16GetCycleTime;/**< \brief SunbIndex 008: Get cycle time*/
|
||||
UINT32 u32DelayTime; /**< \brief SunbIndex 009: Delay time*/
|
||||
UINT32 u32Sync0CycleTime; /**< \brief SunbIndex 010: Sync0 cycle time*/
|
||||
UINT16 u16SmEventMissedCounter; /**< \brief SunbIndex 011: SyncManager event missed counter*/
|
||||
UINT16 u16CycleExceededCounter; /**< \brief SunbIndex 012: Cycle exceed counter*/
|
||||
UINT16 u16Si13Reserved; /**< \brief SunbIndex 013: Shift too short (not supported, only padding)*/
|
||||
UINT16 u16Si14Reserved; /**< \brief SubIndex14 not supported*/
|
||||
UINT32 u32Si15Reserved; /**< \brief SubIndex15 not supported*/
|
||||
UINT32 u32Si16Reserved; /**< \brief SubIndex16 not supported*/
|
||||
UINT32 u32Si17Reserved; /**< \brief SubIndex17 not supported*/
|
||||
UINT32 u32Si18Reserved; /**< \brief SubIndex18 not supported*/
|
||||
UINT8 u8SyncError; /**< \brief Sync Error*/
|
||||
}OBJ_STRUCT_PACKED_END
|
||||
TSYNCMANPAR;
|
||||
|
||||
/**
|
||||
* Cycle diagnosis
|
||||
*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 syncFailedCounter; /**< \brief Sync Failed Counter*/
|
||||
}OBJ_STRUCT_PACKED_END
|
||||
TCYCLEDIAG;
|
||||
|
||||
|
||||
/**
|
||||
* Object 0x10F1 (Error Settings Object) data structure
|
||||
*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0; /**< \brief SubIndex0*/
|
||||
UINT32 u32LocalErrorReaction; /**< \brief Local error reaction*/
|
||||
/*ECATCHANGE_START(V5.11) COE3*/
|
||||
UINT16 u16SyncErrorCounterLimit; /**< \brief Sync error counter limit*/
|
||||
/*ECATCHANGE_END(V5.11) COE3*/
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ10F1;
|
||||
|
||||
#endif //_OBJDEF_H_
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_OBJDEF_) && (_OBJDEF_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Indicates if the synchronisation was written by the user on startup (by writing 0x1C3x.1).
|
||||
* If it was not written the sync mode will be set based on the DC registers 0x980:0x981
|
||||
*/
|
||||
PROTO BOOL bSyncSetByUser;
|
||||
|
||||
/**
|
||||
* \brief Cycle diagnosis variable
|
||||
*/
|
||||
PROTO TCYCLEDIAG sCycleDiag;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Object 0x1C32 (SyncManager 2 Parameter) object variable
|
||||
*/
|
||||
PROTO TSYNCMANPAR MBXMEM sSyncManOutPar;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Object 0x1C33 (SyncManager 3 Parameter) object variable
|
||||
*/
|
||||
PROTO TSYNCMANPAR MBXMEM sSyncManInPar;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Object 0x10F1 (Error Settings) object variable
|
||||
*
|
||||
* SubIndex0 : 2
|
||||
* Local error reaction : 1
|
||||
* Sync error counter limit : MAX_SM_EVENT_MISSED
|
||||
*/
|
||||
PROTO TOBJ10F1 sErrorSettings
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_OBJDEF_) && (_OBJDEF_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
= {2, 0x01, MAX_SM_EVENT_MISSED}
|
||||
#endif
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Default entry name "SubIndex 000"
|
||||
*/
|
||||
PROTO CHAR OBJMEM aSubindexDesc[13]
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_OBJDEF_) && (_OBJDEF_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
= { "SubIndex 000" }
|
||||
#endif
|
||||
;
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
PROTO OBJCONST TOBJECT OBJMEM * OBJ_GetObjectHandle( UINT16 index );
|
||||
PROTO UINT32 OBJ_GetObjectLength( UINT16 index, UINT8 subindex, OBJCONST TOBJECT OBJMEM * pObjEntry, UINT8 bCompleteAccess);
|
||||
PROTO UINT16 OBJ_GetNoOfObjects(UINT8 listType);
|
||||
PROTO UINT16 OBJ_GetObjectList(UINT16 listType, UINT16 *pIndex, UINT16 size, UINT16 MBXMEM *pData,UINT8 *pAbort);
|
||||
PROTO UINT16 OBJ_GetDesc( UINT16 index, UINT8 subindex, OBJCONST TOBJECT OBJMEM * pObjEntry, UINT16 MBXMEM * pData );
|
||||
PROTO OBJCONST TSDOINFOENTRYDESC OBJMEM * OBJ_GetEntryDesc(OBJCONST TOBJECT OBJMEM * pObjEntry, UINT8 Subindex);
|
||||
PROTO OBJCONST TSDOINFOOBJDESC OBJMEM * OBJ_GetObjDesc(OBJCONST TOBJECT OBJMEM * pObjEntry);
|
||||
PROTO UINT16 OBJ_GetEntryOffset(UINT8 subindex, OBJCONST TOBJECT OBJMEM * pObjEntry);
|
||||
PROTO UINT8 CheckSyncTypeValue(UINT16 index, UINT16 NewSyncType);
|
||||
PROTO UINT8 OBJ_Read(UINT16 index, UINT8 subindex, UINT32 objSize, OBJCONST TOBJECT OBJMEM * pObjEntry, UINT16 MBXMEM * pData, UINT8 bCompleteAccess);
|
||||
PROTO UINT8 OBJ_Write(UINT16 index, UINT8 subindex, UINT32 dataSize, OBJCONST TOBJECT OBJMEM * pObjEntry, UINT16 MBXMEM * pData, UINT8 bCompleteAccess);
|
||||
PROTO void COE_WriteBackupEntry(UINT8 subindex, OBJCONST TOBJECT OBJMEM * pObjEntry);
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,641 @@
|
||||
/**
|
||||
* \addtogroup CoE CAN Application Profile over EtherCAT
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
\file sdoserv.h
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief SDO Service definitions
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.10:<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
V5.11 SDO10: add new SDO abort code 0x6010004 (complete access not supported)<br>
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 SDO3: Add new SDO Abort Code (0x06090033)<br>
|
||||
<br>Changes to version - :<br>
|
||||
V5.01 : Start file change log
|
||||
*/
|
||||
|
||||
#ifndef _SDOSERV_H_
|
||||
|
||||
#define _SDOSERV_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "ecatcoe.h"
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Defines and Types
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* \addtogroup SdoErrorCodes SDO Error Codes
|
||||
* @{
|
||||
*/
|
||||
#define ERROR_SDOINVALIDCOMMAND 0x1101 /**< \brief Invalid SDO command*/
|
||||
#define ERROR_SDOINVALIDHEADER 0x1102 /**< \brief Invalid SDO header*/
|
||||
#define ERROR_SDONOTSUPPORTED 0x1103 /**< \brief SDO service not supported*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SdoHeaderData SDO Header and Data
|
||||
* @{
|
||||
*/
|
||||
#define SDOHEADER_SIZEINDICATOR ((UINT8) 0x01) /**< \brief Size Indicator size of data in Data Set Size specified*/
|
||||
#define SDOHEADER_TRANSFERTYPE ((UINT8) 0x02) /**< \brief Transfer Type<br>1: Expedited<br>0: Normal*/
|
||||
#define SDOHEADER_DATASETSIZE ((UINT8) 0x0C) /**< \brief Data Set Size (used for Expedited transfer)*/
|
||||
#define SDOHEADER_COMPLETEACCESS ((UINT8) 0x10) /**< \brief Complete Access indication<br>0: Only specified entry will be transferred<br>1: Whole object (with or without Subindex0) will be transferred)*/
|
||||
#define SDOHEADER_COMMAND ((UINT8) 0xE0) /**< \brief SDO Command*/
|
||||
#define SDOHEADERSHIFT_SIZEINDICATOR ((UINT8) 0) /**< \brief Shift for Size Indicator*/
|
||||
#define SDOHEADERSHIFT_TRANSFERTYPE ((UINT8) 1) /**< \brief Shift for Transfer Type*/
|
||||
#define SDOHEADERSHIFT_DATASETSIZE ((UINT8) 2) /**< \brief Shift for Data Set Size*/
|
||||
#define SDOHEADERSHIFT_INDEXACCESS ((UINT8) 4) /**< \brief Shift for Complete Access*/
|
||||
#define SDOHEADERSHIFT_COMMAND ((UINT8) 5) /**< \brief Shift for SDO Command*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SdoServices SDO Services
|
||||
* @{
|
||||
*/
|
||||
#define SDOSERVICE_INITIATEDOWNLOADREQ ((UINT8) (0x01 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO download request*/
|
||||
#define SDOSERVICE_INITIATEDOWNLOADRES ((UINT8) (0x03 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO download response*/
|
||||
#define SDOSERVICE_DOWNLOADSEGMENTREQ ((UINT8) (0x00 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO segmented download request*/
|
||||
#define SDOSERVICE_DOWNLOADSEGMENTRES ((UINT8) (0x01 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO segmented download response*/
|
||||
#define SDOSERVICE_INITIATEUPLOADREQ ((UINT8) (0x02 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO upload request*/
|
||||
#define SDOSERVICE_INITIATEUPLOADRES ((UINT8) (0x02 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO upload response*/
|
||||
#define SDOSERVICE_UPLOADSEGMENTREQ ((UINT8) (0x03 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO segmented upload request*/
|
||||
#define SDOSERVICE_UPLOADSEGMENTRES ((UINT8) (0x00 << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO segmented upload response*/
|
||||
#define SDOSERVICE_ABORTTRANSFER ((UINT8) (((UINT8) 0x04) << (SDOHEADERSHIFT_COMMAND))) /**< \brief SDO abort*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SdoHeaderData SDO Header and Data
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Structure to handle the basic SDO header
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 Sdo[2]; /**< \brief 32Bit SDO header buffer*/
|
||||
#define SDOHEADER_COMMANDOFFSET 0 /**< \brief Memory offset for the command*/
|
||||
#define SDOHEADER_INDEXLOOFFSET 0 /**< \brief Memory offset for the low Byte of the object index*/
|
||||
#define SDOHEADER_INDEXHIOFFSET 1 /**< \brief Memory offset for the high Byte of the object index*/
|
||||
#define SDOHEADER_SUBINDEXOFFSET 1 /**< \brief Memory offset for subindex*/
|
||||
#define SDOHEADER_COMMANDMASK 0xFF /**< \brief Mask to get the command Byte*/
|
||||
#define SDOHEADER_INDEXLOSHIFT 8 /**< \brief Shift to get the low Byte of the object index*/
|
||||
#define SDOHEADER_INDEXHIMASK 0xFF /**< \brief Mask to get the high byte of the object index*/
|
||||
#define SDOHEADER_SUBINDEXSHIFT 8 /**< \brief Shift to get the subindex*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDOHEADER;
|
||||
|
||||
|
||||
#define INITSDO_HEADER_SIZE SIZEOF(TINITSDOHEADER) /**< \brief Size of the SDO header*/
|
||||
|
||||
|
||||
/**
|
||||
*Complete SDO datagram including mailbox header, CoE header and SDO header
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDOMBX;
|
||||
|
||||
|
||||
#define MAX_EXPEDITED_DATA 4 /**< \brief Maximum number of Bytes handled via an expedited transfer*/
|
||||
#define MIN_SEGMENTED_DATA ((UINT16) 7) /**< \brief Minimum number of Bytes required for a segmented transfer*/
|
||||
#define EXPEDITED_FRAME_SIZE ( COE_HEADER_SIZE + INITSDO_HEADER_SIZE + (MAX_EXPEDITED_DATA) ) /**< \brief Size of an expedited mailbox datagram*/
|
||||
#define DOWNLOAD_NORM_REQ_SIZE ( COE_HEADER_SIZE + INITSDO_HEADER_SIZE + 4 ) /**< \brief Size of an normal download request*/
|
||||
/* HBu 06.02.06: names of defines changed */
|
||||
/* HBu 21.03.06: the SDO_Download-Response has to have always 8 bytes */
|
||||
#define DOWNLOAD_NORM_RES_SIZE ( COE_HEADER_SIZE + INITSDO_HEADER_SIZE + 4 ) /**< \brief Frame size of an normal download response*/
|
||||
#define UPLOAD_NORM_RES_SIZE ( COE_HEADER_SIZE + INITSDO_HEADER_SIZE + 4 ) /**< \brief Frame size of an normal upload response*/
|
||||
#define SEGMENT_NORM_HEADER_SIZE ( COE_HEADER_SIZE + 1 ) /**< \brief Minimum SDO header size*/
|
||||
#define SEGMENT_NORM_RES_SIZE ( (SEGMENT_NORM_HEADER_SIZE) + (MIN_SEGMENTED_DATA) ) /**< \brief Minimum SDO size*/
|
||||
#define SEGMENT_MBX_SIZE ( MBX_HEADER_SIZE + (SEGMENT_NORM_HEADER_SIZE) )/**< \brief Minimum mailbox datagram*/
|
||||
|
||||
|
||||
/**
|
||||
* Segmented SDO header including data
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 SegHeader; /**< \brief 8Bit header buffer and first 8Bit of the Data*/
|
||||
#define SEGHEADER_MASK ((UINT16) 0xFF) /**< \brief SDO header mask*/
|
||||
#define SEGHEADER_NOMOREFOLLOWS ((UINT16) 0x01) /**< \brief More segments follow flag*/
|
||||
#define SEGHEADER_SEGDATASIZE ((UINT16) 0x0E) /**< \brief Segmented data size*/
|
||||
#define SEGHEADER_TOGGLE ((UINT16) 0x10) /**< \brief Toogle flag*/
|
||||
#define SEGHEADER_COMMAND ((UINT16) 0xE0) /**< \brief Command identifier*/
|
||||
#define SEGHEADERSHIFT_SEGDATASIZE ((UINT16) 1) /**< \brief Shift for Segmented data size*/
|
||||
#define SEGHEADERSHIFT_TOGGLE ((UINT16) 4) /**< \brief Shift for toggle flag*/
|
||||
#define SEGHEADERSHIFT_COMMAND ((UINT16) 5) /**< \brief Shift for command identifier*/
|
||||
#define SEGHDATA_MASK ((UINT16) 0xFF00) /**< \brief Data mask*/
|
||||
#define SEGDATASHIFT ((UINT16) 8) /**< \brief Shift for first data Byte*/
|
||||
UINT16 Data[(((MAX_MBX_DATA_SIZE)-(SEGMENT_NORM_HEADER_SIZE) - 1 ) >> 1)]; /**< \brief Data buffer (second and following Bytes, the first byte is located in the header variable)*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOSEGHEADERDATA;
|
||||
|
||||
|
||||
/**
|
||||
*Expedited download request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
UINT16 Data[(MAX_EXPEDITED_DATA) >> 1]; /**< \brief Data buffer*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDODOWNLOADEXPREQMBX;
|
||||
|
||||
/**
|
||||
*Normal download request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
UINT16 CompleteSize[2]; /**< \brief Complete object size*/
|
||||
UINT16 Data[(((MAX_MBX_DATA_SIZE)-(DOWNLOAD_NORM_REQ_SIZE)) >> 1)]; /**< \brief Data buffer*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDODOWNLOADNORMREQMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Expedited and normal download response:
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDODOWNLOADRESMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Segmented download request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TSDOSEGHEADERDATA SdoHeader; /**< \brief SDO header (data is included in header)*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TDOWNLOADSDOSEGREQMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Segmented download response
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
UINT8 SegHeader; /**< \brief SDO header*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TDOWNLOADSDOSEGRESMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Expedited and normal upload request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDOUPLOADREQMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Expedited upload response
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
UINT16 Data[((MAX_EXPEDITED_DATA) >> 1)]; /**< \brief Data buffer*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDOUPLOADEXPRESMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Normal upload response
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
UINT16 CompleteSize[2]; /**< \brief Complete object size*/
|
||||
UINT16 Data[(((MAX_MBX_DATA_SIZE)-(UPLOAD_NORM_RES_SIZE)) >> 1)]; /**< \brief Data buffer*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TINITSDOUPLOADNORMRESMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Segmented upload request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
UINT8 SegHeader; /**< \brief SDO header*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TUPLOADSDOSEGREQMBX;
|
||||
|
||||
/**
|
||||
*Segmented upload response
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TSDOSEGHEADERDATA SdoHeader; /**< \brief SDO header (data is included in header)*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TUPLOADSDOSEGRESMBX;
|
||||
|
||||
|
||||
/**
|
||||
*Abort request
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TINITSDOHEADER SdoHeader; /**< \brief SDO header*/
|
||||
UINT32 AbortCode; /**< \brief SDO abort code*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TABORTSDOTRANSFERREQMBX;
|
||||
|
||||
|
||||
#define ABORT_NORM_RES_SIZE (SIZEOF(TABORTSDOTRANSFERREQMBX) - SIZEOF(TMBXHEADER))/**< \brief Size of the SDO Abort datagram (without the mailbox header)*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SDOAbort SDO Abort Codes
|
||||
* @{
|
||||
*/
|
||||
#define ABORT_NOERROR 0x00000000 /**< \brief No SDO error*/
|
||||
#define ABORT_TOGGLE_BIT_NOT_CHANGED 0x05030000 /**< \brief Toggle bit not changed*/
|
||||
#define ABORT_SDO_PROTOCOL_TIMEOUT 0x05040000 /**< \brief SDO timeout*/
|
||||
#define ABORT_COMMAND_SPECIFIER_UNKNOWN 0x05040001 /**< \brief Command specifier unknown*/
|
||||
#define ABORT_OUT_OF_MEMORY 0x05040005 /**< \brief Out of memory*/
|
||||
#define ABORT_UNSUPPORTED_ACCESS 0x06010000 /**< \brief Unsupported Access*/
|
||||
#define ABORT_WRITE_ONLY_ENTRY 0x06010001 /**< \brief Write only entry*/
|
||||
#define ABORT_READ_ONLY_ENTRY 0x06010002 /**< \brief Read only entry*/
|
||||
#define ABORT_ENTRY_CANT_BE_WRITTEN_SI0_NOT_0 0x06010003 /**< \brief Entry can not be written because Subindex0 is not 0*/
|
||||
/* ECATCHANGE_START(V5.11) SDO10*/
|
||||
#define ABORT_COMPLETE_ACCESS_NOT_SUPPORTED 0x06010004 /**< \brief The object can not be accessed via complete access*/
|
||||
/* ECATCHANGE_END(V5.11) SDO10*/
|
||||
#define ABORT_OBJECT_NOT_EXISTING 0x06020000 /**< \brief Object not existing*/
|
||||
#define ABORT_OBJECT_CANT_BE_PDOMAPPED 0x06040041 /**< \brief Object can not be mapped to PDO*/
|
||||
#define ABORT_MAPPED_OBJECTS_EXCEED_PDO 0x06040042 /**< \brief Mapped Object exceeds PDO*/
|
||||
#define ABORT_PARAM_IS_INCOMPATIBLE 0x06040043 /**< \brief Parameter is incompatible*/
|
||||
#define ABORT_INTERNAL_DEVICE_INCOMPATIBILITY 0x06040047 /**< \brief Device incompatibility*/
|
||||
#define ABORT_HARDWARE_ERROR 0x06060000 /**< \brief Hardware error*/
|
||||
#define ABORT_PARAM_LENGTH_ERROR 0x06070010 /**< \brief Parameter length error*/
|
||||
#define ABORT_PARAM_LENGTH_TOO_LONG 0x06070012 /**< \brief Parameter is too long*/
|
||||
#define ABORT_PARAM_LENGTH_TOO_SHORT 0x06070013 /**< \brief Parameter is too short*/
|
||||
#define ABORT_SUBINDEX_NOT_EXISTING 0x06090011 /**< \brief Subindex (Entry) not exists*/
|
||||
#define ABORT_VALUE_EXCEEDED 0x06090030 /**< \brief Value exceeds*/
|
||||
#define ABORT_VALUE_TOO_GREAT 0x06090031 /**< \brief Value is too great*/
|
||||
#define ABORT_VALUE_TOO_SMALL 0x06090032 /**< \brief Value is too small*/
|
||||
#define ABORT_MODULE_ID_LIST_NOT_MATCH 0x06090033 /**< \brief Detected Module Ident List (0xF030) and Configured Module Ident list (0xF050) does not match*/
|
||||
#define ABORT_MAX_VALUE_IS_LESS_THAN_MIN_VALUE 0x06090036 /**< \brief Value is less than minimum value*/
|
||||
#define ABORT_GENERAL_ERROR 0x08000000 /**< \brief General error*/
|
||||
#define ABORT_DATA_CANNOT_BE_READ_OR_STORED 0x08000020 /**< \brief Data can not be read or written*/
|
||||
#define ABORT_DATA_CANNOT_BE_READ_OR_STORED_BECAUSE_OF_LOCAL_CONTROL 0x08000021 /**< \brief Data can not be accessed because of local control*/
|
||||
#define ABORT_DATA_CANNOT_BE_READ_OR_STORED_IN_THIS_STATE 0x08000022 /**< \brief Data can not be read or written in the current state*/
|
||||
#define ABORT_NO_OBJECT_DICTIONARY_IS_PRESENT 0x08000023 /**< \brief Object is not in the object dictionary*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SDOAbortIndex SDO Abort Codes Identifier
|
||||
* @{
|
||||
* Internal the SDO abort codes are handled in an array.
|
||||
*/
|
||||
#define ABORTIDX_TOGGLE_BIT_NOT_CHANGED 0x01 /**< \brief Index of "Toggle bit not changed"*/
|
||||
#define ABORTIDX_SDO_PROTOCOL_TIMEOUT 0x02 /**< \brief Index of "SDO timeout"*/
|
||||
#define ABORTIDX_COMMAND_SPECIFIER_UNKNOWN 0x03 /**< \brief Index of "Command specifier unknown"*/
|
||||
#define ABORTIDX_OUT_OF_MEMORY 0x04 /**< \brief Index of "Out of memory"*/
|
||||
#define ABORTIDX_UNSUPPORTED_ACCESS 0x05 /**< \brief Index of "Unsupported Access"*/
|
||||
#define ABORTIDX_WRITE_ONLY_ENTRY 0x06 /**< \brief Index of "Write only entry"*/
|
||||
#define ABORTIDX_READ_ONLY_ENTRY 0x07 /**< \brief Index of "Read only entry"*/
|
||||
#define ABORTIDX_OBJECT_NOT_EXISTING 0x08 /**< \brief Index of "Object not existing"*/
|
||||
#define ABORTIDX_OBJECT_CANT_BE_PDOMAPPED 0x09 /**< \brief Index of "Object can not be mapped to PDO"*/
|
||||
#define ABORTIDX_MAPPED_OBJECTS_EXCEED_PDO 0x0A /**< \brief Index of "Mapped Object exceeds PDO"*/
|
||||
#define ABORTIDX_PARAM_IS_INCOMPATIBLE 0x0B /**< \brief Index of "Parameter is incompatible"*/
|
||||
#define ABORTIDX_INTERNAL_DEVICE_INCOMPATIBILITY 0x0C /**< \brief Index of "Device incompatibility"*/
|
||||
#define ABORTIDX_HARDWARE_ERROR 0x0D /**< \brief Index of "Hardware error"*/
|
||||
#define ABORTIDX_PARAM_LENGTH_ERROR 0x0E /**< \brief Index of "Parameter length error"*/
|
||||
#define ABORTIDX_PARAM_LENGTH_TOO_LONG 0x0F /**< \brief Index of "Parameter is too long"*/
|
||||
#define ABORTIDX_PARAM_LENGTH_TOO_SHORT 0x10 /**< \brief Index of "Parameter is too short"*/
|
||||
#define ABORTIDX_SUBINDEX_NOT_EXISTING 0x11 /**< \brief Index of "Subindex (Entry) not exists"*/
|
||||
#define ABORTIDX_VALUE_EXCEEDED 0x12 /**< \brief Index of "Value exceeds"*/
|
||||
#define ABORTIDX_VALUE_TOO_GREAT 0x13 /**< \brief Index of "Value is too great"*/
|
||||
#define ABORTIDX_VALUE_TOO_SMALL 0x14 /**< \brief Index of "Value is too small"*/
|
||||
#define ABORTIDX_MODULE_ID_LIST_NOT_MATCH 0x15 /**< \brief Index of "Unequal Module Id list"*/
|
||||
#define ABORTIDX_MAX_VALUE_IS_LESS_THAN_MIN_VALUE 0x16 /**< \brief Index of "Value is less than minimum value"*/
|
||||
#define ABORTIDX_GENERAL_ERROR 0x17 /**< \brief Index of "General error"*/
|
||||
#define ABORTIDX_DATA_CANNOT_BE_READ_OR_STORED 0x18 /**< \brief Index of "Data can not be read or written"*/
|
||||
#define ABORTIDX_DATA_CANNOT_BE_ACCESSED_BECAUSE_OF_LOCAL_CONTROL 0x19 /**< \brief Index of "Data can not be accessed because of local control"*/
|
||||
#define ABORTIDX_IN_THIS_STATE_DATA_CANNOT_BE_READ_OR_STORED 0x1A /**< \brief Index of "Data can not be read or written in the current state"*/
|
||||
#define ABORTIDX_NO_OBJECT_DICTIONARY_IS_PRESENT 0x1B /**< \brief Index of "Object is not in the object dictionary"*/
|
||||
#define ABORTIDX_ENTRY_CANT_BE_WRITTEN_SI0_NOT_0 0x1C /**< \brief Index of "Entry can not be written because Subindex0 is not 0"*/
|
||||
/* ECATCHANGE_START(V5.11) SDO10*/
|
||||
#define ABORTIDX_COMPLETE_ACCESS_NOT_SUPPORTED 0x1D /**< \brief The object can not be accessed via complete access*/
|
||||
/* ECATCHANGE_END(V5.11) SDO10*/
|
||||
#define ABORTIDX_WORKING 0xFF /**< \brief Index of application is handling the SDO request*/
|
||||
/** @}*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
*\addtogroup SdoServices SDO Services
|
||||
* @{
|
||||
*/
|
||||
#define SDOINFOSERVICE_OBJDICTIONARYLIST_Q 0x01 /**< \brief SDO Info Get object list request*/
|
||||
#define SDOINFOSERVICE_OBJDICTIONARYLIST_S 0x02 /**< \brief SDO Info Get object list response*/
|
||||
#define SDOINFOSERVICE_OBJDESCRIPTION_Q 0x03 /**< \brief SDO Info Get object description request*/
|
||||
#define SDOINFOSERVICE_OBJDESCRIPTION_S 0x04 /**< \brief SDO Info Get object description response*/
|
||||
#define SDOINFOSERVICE_ENTRYDESCRIPTION_Q 0x05 /**< \brief SDO Info Get entry description request*/
|
||||
#define SDOINFOSERVICE_ENTRYDESCRIPTION_S 0x06 /**< \brief SDO Info Get entry description request*/
|
||||
#define SDOINFOSERVICE_ERROR_Q 0x07 /**< \brief SDO Info Error*/
|
||||
#define SDOINFOSERVICE_INCOMPLETE 0x80 /**< \brief SDO Info Incomplete flag*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
*\addtogroup SdoHeaderData SDO Header and Data
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* SDO Info Object list
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 ListType; /**< \brief List type variable*/
|
||||
#define INFO_LIST_TYPE_LENGTH 0 /**< \brief Length of all list types*/
|
||||
#define INFO_LIST_TYPE_ALL 1 /**< \brief All objects*/
|
||||
#define INFO_LIST_TYPE_RXPDO 2 /**< \brief Only RxPDO mappable objects*/
|
||||
#define INFO_LIST_TYPE_TXPDO 3 /**< \brief Only TxPDO mappable objects*/
|
||||
#define INFO_LIST_TYPE_BACKUP 4 /**< \brief Only backup objects*/
|
||||
#define INFO_LIST_TYPE_SET 5 /**< \brief Only setting objects*/
|
||||
#define INFO_LIST_TYPE_MAX 5 /**< \brief Max list identifier*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOLIST;
|
||||
|
||||
|
||||
/**
|
||||
* Object description
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 DataType; /**< \brief Object data type*/
|
||||
UINT16 ObjFlags; /**< \brief Object flags (including object code and max subindex)*/
|
||||
|
||||
/*Object Code and MaxSubindex will be set manually in the object dictionary. The value is always created in little endian format*/
|
||||
#define OBJFLAGS_MAXSUBINDEXMASK 0x00FF /**< \brief Max subindex mask*/
|
||||
#define OBJFLAGS_MAXSUBINDEXSHIFT 0 /**< \brief Max subindex shift*/
|
||||
#define OBJFLAGS_OBJCODEMASK 0x0F00 /**< \brief Mask Object code mask*/
|
||||
#define OBJFLAGS_OBJCODESHIFT 8 /**< \brief Object code shift*/
|
||||
|
||||
#define OBJCODE_VAR 0x07 /**< \brief Object code VARIABLE*/
|
||||
#define OBJCODE_ARR 0x08 /**< \brief Object code ARRAY*/
|
||||
#define OBJCODE_REC 0x09 /**< \brief Object code RECORD*/
|
||||
|
||||
// char Name[]; // rest of mailbox data
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOOBJDESC;
|
||||
|
||||
|
||||
#define SDO_INFO_OBJ_DESC_SIZE SIZEOF(TSDOINFOOBJDESC) /**< \brief Object description size*/
|
||||
|
||||
|
||||
/**
|
||||
* SDO Info object description service data
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 Index; /**< \brief Object index*/
|
||||
TSDOINFOOBJDESC Res; /**< \brief Description data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOOBJ;
|
||||
|
||||
|
||||
#define SDO_INFO_OBJ_DESC_RES_SIZE SIZEOF(TSDOINFOOBJ)/**< \brief Size of the object description service data*/
|
||||
|
||||
|
||||
/**
|
||||
* SDO Info entry description
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 DataType; /**< \brief Entry data type*/
|
||||
UINT16 BitLength; /**< \brief Entry bit size*/
|
||||
UINT16 ObjAccess; /**< \brief Entry access rights*/
|
||||
/**<
|
||||
Bit 0: Read Access in Pre-Op
|
||||
<br>Bit 1: Read Access in Safe-Op
|
||||
<br>Bit 2: Read Access in Op
|
||||
<br>Bit 3: Write Access in Pre-Op
|
||||
<br>Bit 4: Write Access in Safe-Op
|
||||
<br>Bit 5: Write Access in Op
|
||||
<br>Bit 6: mappable in RxPDO
|
||||
<br>Bit 7: mappable in TxPDO
|
||||
<br>Bit 8: entry will be included in backup
|
||||
<br>Bit 9: entry will be included in settings*/
|
||||
|
||||
#define ACCESS_READWRITE 0x003F /**< \brief Read/write in all states*/
|
||||
#define ACCESS_READ 0x0007 /**< \brief Read only in all states*/
|
||||
#define ACCESS_READ_PREOP 0x0001 /**< \brief Read only in PreOP*/
|
||||
#define ACCESS_READ_SAFEOP 0x0002 /**< \brief Read only in SafeOP*/
|
||||
#define ACCESS_READ_OP 0x0004 /**< \brief Read only in OP*/
|
||||
#define ACCESS_WRITE 0x0038 /**< \brief Write only in all states*/
|
||||
#define ACCESS_WRITE_PREOP 0x0008 /**< \brief Write only in PreOP*/
|
||||
#define ACCESS_WRITE_SAFEOP 0x0010 /**< \brief Write only in SafeOP*/
|
||||
#define ACCESS_WRITE_OP 0x0020 /**< \brief Write only in OP*/
|
||||
#define OBJACCESS_NOPDOMAPPING 0x0000 /**< \brief Not PDO mappable*/
|
||||
#define OBJACCESS_RXPDOMAPPING 0x0040 /**< \brief Mappable in RxPDOs*/
|
||||
#define OBJACCESS_TXPDOMAPPING 0x0080 /**< \brief Mappable in TxPDOs*/
|
||||
#define OBJACCESS_BACKUP 0x0100 /**< \brief Backup entry*/
|
||||
#define OBJACCESS_SETTINGS 0x0200 /**< \brief Setting Entry*/
|
||||
#define OBJACCESS_SAFEINPUTS 0x0400 /**< \brief Safe input*/
|
||||
#define OBJACCESS_SAFEOUTPUTS 0x0800 /**< \brief Safe output*/
|
||||
#define OBJACCESS_SAFEPARAMETER 0x1000 /**< \brief Safe parameter*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOENTRYDESC;
|
||||
|
||||
|
||||
/**
|
||||
* SDO Info entry description service data
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 Index; /**< \brief Index of the parent object*/
|
||||
UINT16 Info; /**< \brief Info data including the subindex and identifier for the payload data*/
|
||||
#define ENTRY_MASK_SUBINDEX 0x00FF /**< \brief Subindex mask*/
|
||||
#define ENTRY_SUBINDEX_SHIFT 0 /**< \brief Subindex shift*/
|
||||
#define ENTRY_MASK_VALUEINFO 0xFF00 /**< \brief Value info mask*/
|
||||
#define ENTRY_VALUEINFO_SHIFT 8 /**< \brief Value info shift*/
|
||||
|
||||
TSDOINFOENTRYDESC Res; /**< \brief Entry description data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOENTRY;
|
||||
|
||||
|
||||
/**
|
||||
* SDO info Error
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT32 ErrorCode; /**< \brief SDO Info error code*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOERROR;
|
||||
|
||||
|
||||
#define SDO_INFO_ERROR_SIZE SIZEOF(TSDOINFOERROR)/**< \brief SDO Info error size*/
|
||||
|
||||
|
||||
/**
|
||||
*SDO Info header
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
UINT16 InfoHead; /**< \brief Opcode, Incomplete flag and 8 reserved bits*/
|
||||
#define INFOHEAD_OPCODE_MASK 0x007F /**< \brief Opcode mask*/
|
||||
#define INFOHEAD_OPCODE_SHIFT 0 /**< \brief Opcode shift*/
|
||||
#define INFOHEADER_INCOMPLETE_MASK 0x0080 /**< \brief Incomplete mask*/
|
||||
#define INFOHEADER_INCOMPLETE_SHIFT 0 /**< \brief Incomplete shift, the incomplete value "SDOINFOSERVICE_INCOMPLETE" is defined as a UINT8 => no shift required*/
|
||||
|
||||
UINT16 FragmentsLeft; /**< \brief Number of fragments which will follow*/
|
||||
|
||||
union MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TSDOINFOLIST List;/**< \brief List data*/
|
||||
TSDOINFOOBJ Obj;/**< \brief Object Description data*/
|
||||
TSDOINFOENTRY Entry;/**< \brief Entry description data*/
|
||||
TSDOINFOERROR Error;/**< \brief Error data*/
|
||||
UINT16 Data[1];/**< \brief Unspecified data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
Data;/**< \brief Service data*/
|
||||
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFOHEADER;
|
||||
|
||||
|
||||
/**
|
||||
* SDO Info complete datagram
|
||||
*/
|
||||
typedef struct MBX_STRUCT_PACKED_START
|
||||
{
|
||||
TMBXHEADER MbxHeader; /**< \brief Mailbox header*/
|
||||
TCOEHEADER CoeHeader; /**< \brief CoE header*/
|
||||
TSDOINFOHEADER SdoHeader; /**< \brief SDO Info header and data*/
|
||||
}MBX_STRUCT_PACKED_END
|
||||
TSDOINFORMATION;
|
||||
|
||||
|
||||
#define SIZEOF_SDOINFOHEAD 4 /**< \brief SDO Info header size*/
|
||||
#define SIZEOF_SDOINFO ( (COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD )) /**< \brief SDO Info and CoE Size*/
|
||||
#define SIZEOF_SDOINFOSTRUCT ( (MBX_HEADER_SIZE) + (COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD) ) /**< \brief Complete SDO Info datagram size*/
|
||||
#define SIZEOF_SDOINFOLISTHEAD 2 /**< \brief SDO Info list header size*/
|
||||
#define SIZEOF_SDOINFOLISTSTRUCT (( COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD) + (SIZEOF_SDOINFOLISTHEAD) ) /**< \brief Complete SDO Info list size*/
|
||||
#define SIZEOF_SDOINFOOBJSTRUCT ( (COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD) + (SDO_INFO_OBJ_DESC_RES_SIZE) ) /**< \brief Complete SDO Info object description response size*/
|
||||
#define SIZEOF_SDOINFOENTRYREQHEAD 4 /**< \brief SDO Info entry description request size*/
|
||||
#define SIZEOF_SDOINFOENTRYREQSTRUCT ( (COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD) + (SIZEOF_SDOINFOENTRYREQHEAD) ) /**< \brief SDO Info entry description request size*/
|
||||
#define SIZEOF_SDOINFOERRORSTRUCT ( (COE_HEADER_SIZE) + (SIZEOF_SDOINFOHEAD) + (SDO_INFO_ERROR_SIZE) ) /**< \brief SDO Info error size*/
|
||||
|
||||
|
||||
#define SDO_INFO_HEADER_BYTE_SIZE ((SIZEOF_SDOINFOSTRUCT)+(SIZEOF_SDOINFOLISTHEAD)) /**< \brief SDO Info size*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SdoPendingHandler SDO Pending Response
|
||||
* @{
|
||||
* If this feature is enabled (SDO_RES_INTERFACE 1) a SDO request may set to pending and is completed afterwards.
|
||||
* This can be used to forward the read/write request an additional thread, uController.
|
||||
*/
|
||||
#define SDO_PENDING_WRITE 0x1 /**< \brief SDO write request is pending*/
|
||||
#define SDO_PENDING_SEG_WRITE 0x2 /**< \brief SDO segmented write request is pending*/
|
||||
#define SDO_PENDING_READ 0x3 /**< \brief SDO read request is pending*/
|
||||
#define SDO_PENDING_SEG_READ 0x4 /**< \brief SDO segmented read request is pending*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
#endif //_SDOSRV_H_
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Variables
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
#if defined(_SDOSERV_) && (_SDOSERV_ == 1)
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
#define PROTO
|
||||
#else
|
||||
#define PROTO extern
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup SdoPendingHandler SDO Pending Response
|
||||
*@{
|
||||
*/
|
||||
PROTO UINT8 u8PendingSdo; /**< \brief Identifier for the pending SDO service (see SDO_PENDING_XXX defines)*/
|
||||
PROTO BOOL bStoreCompleteAccess; /**< \brief Indicates if the pending SDO service is an complete access*/
|
||||
PROTO UINT8 u8StoreSubindex; /**< \brief Contains the subindex for the pending SDO request*/
|
||||
PROTO UINT16 u16StoreIndex; /**< \brief Contains the Index for the pending SDO request*/
|
||||
PROTO UINT32 u32StoreDataSize; /**< \brief Complete data size to be handled*/
|
||||
PROTO UINT16 MBXMEM *pStoreData; /**< \brief Pointer to the buffer to be handled*/
|
||||
PROTO UINT8 (* pSdoPendFunc)( UINT16 Index, UINT8 Subindex, UINT32 Size, UINT16 MBXMEM * pData, UINT8 bCompleteAccess ); /**< \brief Function pointer for the SDO pending request handler*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
PROTO MEM_ADDR VARMEM aSdoInfoHeader[GET_MEM_SIZE(SDO_INFO_HEADER_BYTE_SIZE)]; /**< \brief SDO Info buffer*/
|
||||
|
||||
|
||||
/**
|
||||
* \addtogroup SegmentedSdo Segmented SDO
|
||||
* @{
|
||||
*/
|
||||
PROTO UINT16 VARMEM * VARMEM pSdoSegData; /**< \brief Pointer to the buffer handling to full object/entry data*/
|
||||
PROTO UINT16 VARMEM nSdoInfoFragmentsLeft; /**< \brief Number of fragments which need to be transmitted*/
|
||||
/** @}*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Global Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
PROTO UINT8 SDOS_SdoInfoInd(TSDOINFORMATION MBXMEM *pSdoInfoInd);
|
||||
PROTO UINT8 SDOS_SdoInd(TINITSDOMBX MBXMEM *pSdoInd);
|
||||
|
||||
PROTO void SDOS_SdoRes(UINT8 abort, UINT32 objLength, UINT16 MBXMEM *pData);
|
||||
|
||||
#undef PROTO
|
||||
/** @}*/
|
||||
@@ -0,0 +1,945 @@
|
||||
<?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 DefaultSize="384" StartAddress="#x1400" ControlByte="#x64" Enable="1">Outputs</Sm>
|
||||
<!-- SM3: Process Data Input (TxPDO, Slave -> Master) -->
|
||||
<Sm DefaultSize="600" StartAddress="#x1880" ControlByte="#x20" Enable="1">Inputs</Sm>
|
||||
<!-- RxPDO (Output, Master -> Slave): 索引 0x1600-0x17FF,映射 0x7000:01..48
|
||||
1.5.56 扩到 48 条目(384B):兼容单帧区 48B + CANFD 批量发送区 336B。
|
||||
与 dc_app.c 的 PDO_OUT_ENTRIES / gen_eeprom.py 的 PD_OUT_SIZE 必须一致,
|
||||
否则主站按旧长度建映射,SAFEOP 报 AL 0x001A/0x0011。 -->
|
||||
<RxPdo Fixed="1" Mandatory="1" Sm="2">
|
||||
<Index>#x1600</Index>
|
||||
<Name>Output</Name>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>1</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 1</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<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>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>5</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 5</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>6</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 6</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>7</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 7</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>8</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 8</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>9</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 9</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>10</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 10</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>11</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 11</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>12</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 12</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>13</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 13</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>14</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 14</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>15</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 15</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>16</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 16</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>17</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 17</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>18</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 18</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>19</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 19</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>20</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 20</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>21</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 21</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>22</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 22</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>23</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 23</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>24</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 24</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>25</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 25</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>26</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 26</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>27</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 27</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>28</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 28</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>29</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 29</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>30</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 30</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>31</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 31</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>32</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 32</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>33</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 33</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>34</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 34</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>35</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 35</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>36</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 36</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>37</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 37</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>38</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 38</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>39</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 39</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>40</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 40</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>41</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 41</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>42</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 42</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>43</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 43</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>44</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 44</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>45</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 45</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>46</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 46</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>47</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 47</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>48</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 48</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</RxPdo>
|
||||
<!-- TxPDO (Input, Slave -> Master): 索引 0x1A00-0x1BFF,映射 0x6000:01..75
|
||||
1.5.56 扩到 80 条目(640B):电机状态窗口 + CANFD 批量接收区 280B。
|
||||
SM3 起址随之挪到 #x1880 —— SM2 是 3-buffer 模式,384B×3 占到 #x187F。
|
||||
1.6.1 输入瘦身 640→496: 批量接收条目数据 64→32B, 条目 80→62。
|
||||
1.6.15 496→600: 批量接收改定长覆盖窗口 5 槽(每槽 12B 头含 u32 seqlock
|
||||
+ 32B 数据), 条目 62→75, SM3 实占 3×600=1800B (0x1880-0x1F67),
|
||||
out+in=984 <= 1024 硬上限。推导见 comm/ethercat_pdo_size.h。
|
||||
撞址会被 SSC ecatslv.c 重叠检查拦下,报 INVALIDSMOUTCFG。 -->
|
||||
<TxPdo Fixed="1" Mandatory="1" Sm="3">
|
||||
<Index>#x1A00</Index>
|
||||
<Name>Input</Name>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>1</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 1</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<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>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>5</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 5</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>6</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 6</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>7</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 7</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>8</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 8</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>9</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 9</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>10</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 10</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>11</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 11</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>12</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 12</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>13</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 13</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>14</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 14</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>15</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 15</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>16</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 16</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>17</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 17</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>18</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 18</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>19</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 19</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>20</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 20</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>21</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 21</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>22</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 22</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>23</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 23</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>24</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 24</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>25</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 25</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>26</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 26</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>27</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 27</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>28</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 28</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>29</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 29</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>30</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 30</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>31</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 31</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>32</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 32</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>33</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 33</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>34</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 34</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>35</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 35</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>36</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 36</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>37</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 37</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>38</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 38</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>39</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 39</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>40</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 40</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>41</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 41</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>42</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 42</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>43</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 43</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>44</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 44</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>45</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 45</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>46</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 46</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>47</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 47</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>48</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 48</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>49</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 49</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>50</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 50</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>51</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 51</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>52</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 52</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>53</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 53</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>54</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 54</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>55</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 55</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>56</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 56</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>57</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 57</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>58</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 58</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>59</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 59</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>60</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 60</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>61</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 61</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>62</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 62</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>63</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 63</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>64</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 64</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>65</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 65</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>66</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 66</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>67</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 67</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>68</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 68</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>69</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 69</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>70</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 70</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>71</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 71</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>72</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 72</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>73</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 73</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>74</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 74</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>75</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 75</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</TxPdo>
|
||||
<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>
|
||||
<CycleTimeSync1 Factor="1">0</CycleTimeSync1>
|
||||
</OpMode>
|
||||
<OpMode>
|
||||
<Name>Free Run</Name>
|
||||
<Desc>No synchronization</Desc>
|
||||
<AssignActivate>#x0000</AssignActivate>
|
||||
</OpMode>
|
||||
</Dc>
|
||||
<Eeprom>
|
||||
<ByteSize>4096</ByteSize>
|
||||
<!-- 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>
|
||||
</Descriptions>
|
||||
</EtherCATInfo>
|
||||
Binary file not shown.
@@ -0,0 +1,953 @@
|
||||
/**
|
||||
* @file dc_app.c
|
||||
* @brief DCU EtherCAT 应用层 — SSC APPL_ 回调 + 应用对象字典
|
||||
*
|
||||
* 对象字典结构 (匹配 DCU_ESI.xml / gen_eeprom.py):
|
||||
* 0x1C12 RxPDO Assign → 0x1600
|
||||
* 0x1600 RxPDO Mapping → 0x7000:01..08 (8×64bit = 64 bytes)
|
||||
* 0x7000 Output Data (主站→从站, 64 bytes)
|
||||
* 0x1C13 TxPDO Assign → 0x1A00
|
||||
* 0x1A00 TxPDO Mapping → 0x6000:01..48 (48×64bit = 384 bytes)
|
||||
* 0x6000 Input Data (从站→主站, 384 bytes)
|
||||
* 0x2000 SYNC Diag (只读: sync0/sync1/escirq/uptime, 主站 SDO 轮询看 DC 通路)
|
||||
*
|
||||
* 0x7000/0x6000/0x1600/0x1A00 为 OBJCODE_ARR(原因见下方 Entry Descriptions 注释)。
|
||||
*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
#include "esc.h"
|
||||
#include "ecatslv.h"
|
||||
#include "ecatappl.h"
|
||||
#include "objdef.h"
|
||||
#include "dc_app.h"
|
||||
#include "../../ethercat_pdo_size.h" /* ECAT_PDO_OUT_BYTES / ECAT_PDO_IN_BYTES */
|
||||
#include "../../ecat_motor_ota.h" /* 0x2510/0x2511:电机 OTA 邮箱进料的尺寸、会话码与钩子 */
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h> /* offsetof:下方布局守卫断言使用 */
|
||||
#include <stdio.h> /* printf:0x204 守护 WARN 日志 */
|
||||
|
||||
/* CPU 负载挂钩(sys/cpu_load.cpp,与 /api/app/load 同口径) */
|
||||
extern void CpuLoad_Enter(void);
|
||||
extern void CpuLoad_Leave(void);
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* el9800appl.h 桩 include (SSC 内部需要)
|
||||
*---------------------------------------------------------------------------*/
|
||||
#define _EL9800APPL_ 1
|
||||
#include "el9800appl.h"
|
||||
#undef _EL9800APPL_
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* PDO 数据缓冲
|
||||
*
|
||||
* 长度的唯一真源是 comm/ethercat_pdo_size.h;本文件用 _Static_assert 与之绑定,
|
||||
* 改一处漏一处会编译失败。为何硬上限是 out+in≤1024B 见该头文件的推导。
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
/** 输出/输入 PDO 的 64bit 条目数。 */
|
||||
#define PDO_OUT_ENTRIES 48u /* 48×8 = 384 B */
|
||||
#define PDO_IN_ENTRIES 75u /* 75×8 = 600 B (覆盖窗口) */
|
||||
|
||||
/** 输出数据对象 0x7000 (主站→从站, RxPDO) */
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0;
|
||||
UINT64 u64Data[PDO_OUT_ENTRIES];
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ7000;
|
||||
|
||||
/** 输入数据对象 0x6000 (从站→主站, TxPDO) */
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0;
|
||||
UINT64 u64Data[PDO_IN_ENTRIES];
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ6000;
|
||||
|
||||
static TOBJ7000 s_outputObj = {PDO_OUT_ENTRIES, {0}};
|
||||
static TOBJ6000 s_inputObj = {PDO_IN_ENTRIES, {0}};
|
||||
|
||||
/* 布局守卫:SSC 的 SDO 单条目访问按"si0 占 16bit,其后按 BitLength 紧排"
|
||||
* 计算偏移(OBJ_GetEntryOffset),故 UINT64 数组必须紧跟在 UINT16 之后落在字节 2。
|
||||
* OBJ_STRUCT_PACKED_START 曾被留空导致其落在字节 8、SDO 读写错位,加断言防回归。 */
|
||||
_Static_assert(offsetof(TOBJ7000, u64Data) == 2, "TOBJ7000 未紧排:检查 OBJ_STRUCT_PACKED_START");
|
||||
_Static_assert(offsetof(TOBJ6000, u64Data) == 2, "TOBJ6000 未紧排:检查 OBJ_STRUCT_PACKED_START");
|
||||
_Static_assert(sizeof(TOBJ7000) == 2 + 8 * PDO_OUT_ENTRIES, "TOBJ7000 尺寸应为 2+384");
|
||||
_Static_assert(sizeof(TOBJ6000) == 2 + 8 * PDO_IN_ENTRIES, "TOBJ6000 尺寸应为 2+600");
|
||||
|
||||
/* 与 C++ 侧桥接层的长度约定绑定:任一处改了尺寸而忘了另一处,这里直接编译失败,
|
||||
* 而不是等到运行期 SAFEOP 报 AL 0x001A/0x0011 再回头查。 */
|
||||
_Static_assert(8 * PDO_OUT_ENTRIES == ECAT_PDO_OUT_BYTES, "输出 PDO 长度与桥接层不一致");
|
||||
_Static_assert(8 * PDO_IN_ENTRIES == ECAT_PDO_IN_BYTES, "输入 PDO 长度与桥接层不一致");
|
||||
/* SSC 编译期门限(ecat_def.h)必须容得下:否则 ecatslv.c 在 SM 校验时拒绝进 SAFEOP。 */
|
||||
_Static_assert(8 * PDO_OUT_ENTRIES <= MAX_PD_OUTPUT_SIZE, "MAX_PD_OUTPUT_SIZE 太小");
|
||||
_Static_assert(8 * PDO_IN_ENTRIES <= MAX_PD_INPUT_SIZE, "MAX_PD_INPUT_SIZE 太小");
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 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 */
|
||||
/* 剖分埋点(sub5..12,DWT 周期/计数累加,主站 1s 差分) */
|
||||
UINT32 u32PdiCyc; /* sub5: PDI_Isr 全函数累计 */
|
||||
UINT32 u32OutCyc; /* sub6: PDO_OutputMapping(输出探针/全量读) */
|
||||
UINT32 u32FreeRunCyc; /* sub7: MainLoop free-run 块(SM 同步下恒 0) */
|
||||
UINT32 u32PdiCnt; /* sub8: PDI_Isr 触发次数 */
|
||||
UINT32 u32InCyc; /* sub9: PDO_InputMapping 全程(APPL+输入PDO写) */
|
||||
UINT32 u32InWriteCyc; /* sub10: 其中 HW_EscWriteIsr 输入PDO 过程数据写 */
|
||||
UINT32 u32AlCyc; /* sub11: PDI_Isr 内 AL 事件读×2+超限应答 */
|
||||
UINT32 u32AppCyc; /* sub12: ECAT_Application(桥 processOutput) */
|
||||
/* [DIFF-STATS] 差分写取证仪表(sub13..23):上一个完整 1s 窗口的值,
|
||||
* 取自 comm/ethercat_diff_stats.cpp。CONFIG_ECAT_DIFF_STATS 置 0 时恒 0,
|
||||
* 字段保留(不影响任何既有子索引,也不动 PDO/映射,故无需重刷 SII)。 */
|
||||
UINT32 u32DsWinSeq; /* sub13: 已完成窗口数,0=还没有完整窗口(勿把 0 当"无变化") */
|
||||
UINT32 u32DsCycles; /* sub14: 窗口内参与比对的拍数 */
|
||||
UINT32 u32DsChangedMax; /* sub15: 单拍变化字节峰值(差分写的理论上限=600×拍数) */
|
||||
UINT32 u32DsRunsTotal; /* sub16: 窗口内 run(连续变化段)总数,配 sub14 求均值 */
|
||||
UINT32 u32DsRunsMax; /* sub17: 单拍 run 数峰值 —— 块粒度差分的碎片上限由它定 */
|
||||
UINT32 u32DsSeg1; /* sub18: 段1(0..31) 桥状态+短快照 变化字节合计 */
|
||||
UINT32 u32DsSeg2; /* sub19: 段2(32..543) 电机整轮密排 */
|
||||
UINT32 u32DsSeg3; /* sub20: 段3(544..583) 长帧快照 */
|
||||
UINT32 u32DsSeg4; /* sub21: 已删(恒 0) */
|
||||
UINT32 u32DsSeg5; /* sub22: 段5(584..599) 诊断区+保留 */
|
||||
UINT32 u32DsCompareCyc; /* sub23: 比对自身累计 DWT 周期(u32 粒度,不含快照拷贝) */
|
||||
UINT32 u32OpFreshRescues; /* sub24: OP 输入新鲜度救援次数(主站网页看,不打串口) */
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ2000;
|
||||
|
||||
static TOBJ2000 s_syncDiag = {24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/* 同上:UINT32 首条目必须落在字节 2(未紧排时为 4)。 */
|
||||
_Static_assert(offsetof(TOBJ2000, u32Sync0) == 2, "TOBJ2000 未紧排:检查 OBJ_STRUCT_PACKED_START");
|
||||
|
||||
/* 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);
|
||||
/* 剖分埋点:ecatappl.c 累加器 + ethercat_sync.cpp 的 DWT 读 */
|
||||
extern UINT32 EcatDbg_Dwt(void);
|
||||
extern volatile UINT32 g_dbg_pdiCyc;
|
||||
extern volatile UINT32 g_dbg_outCyc;
|
||||
extern volatile UINT32 g_dbg_freerunCyc;
|
||||
extern volatile UINT32 g_dbg_pdiCnt;
|
||||
extern volatile UINT32 g_dbg_inCyc;
|
||||
extern volatile UINT32 g_dbg_inWriteCyc;
|
||||
extern volatile UINT32 g_dbg_alCyc;
|
||||
extern volatile UINT32 g_dbg_appCyc;
|
||||
|
||||
/* [DIFF-STATS] 差分写统计窗口值(comm/ethercat_diff_stats.cpp),out[0..10] 对应 sub13..23 */
|
||||
void EcatDiffStats_GetWindow(UINT32 *out);
|
||||
|
||||
/* OP 输入新鲜度 — APPL_InputMapping 每次成功刷新就自增 */
|
||||
static volatile UINT32 s_inMapSeq = 0U;
|
||||
static UINT32 s_inMapSeqSeen = 0U;
|
||||
static UINT32 s_inStaleBaseCyc = 0U;
|
||||
static UINT32 s_opFreshRescues = 0U;
|
||||
#include <stdint.h>
|
||||
/* DFP 的 SystemCoreClock 是 uint32_t(=unsigned int);SSC 的 UINT32 是 unsigned long,
|
||||
* 不能用 UINT32 重复声明。 */
|
||||
extern uint32_t SystemCoreClock;
|
||||
|
||||
/* 过程数据收发模块 (comm/ethercat_pdo.cpp 的 extern "C" shim)。
|
||||
* 本文件为纯 SSC 移植层:只留对象字典 + 三行转发。 */
|
||||
extern void EcatPdo_ReadOutputs(const UINT8 *esc);
|
||||
extern void EcatPdo_Process(void);
|
||||
extern uint32_t EcatPdo_ProcessSeq(void);
|
||||
extern void EcatPdo_WriteInputs(UINT8 *esc);
|
||||
|
||||
/* 0x2502 用:进 BOOT 的异步请求(sys/ota_boot_request.cpp 的 extern "C" shim)。
|
||||
* 只置 pending 标志,真正的配置页写入 + 复位在主循环下一圈,见 ObjBootReqWrite 注。 */
|
||||
extern void EcatEnterBootRequestAsync(void);
|
||||
|
||||
/* 0x2503 用:ESP32 EN(PC9) 控制(sys/esp32_en.cpp 的 extern "C" shim)。
|
||||
* 经 EtherCAT SDO 下发,**不走 ESP32 WiFi**——WiFi 关断后主站仍能拉回使能。 */
|
||||
extern UINT8 EcatEsp32EnDrive(UINT8 action);
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 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)
|
||||
*
|
||||
* 映射条目值 = Index<<16 | SubIndex<<8 | BitLen,这里 BitLen 恒为 0x40(64bit)。
|
||||
* 48 个条目手写既冗长又容易错位,改用宏按 8 个一组展开。
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
#define PDO_MAP_ENTRY(idx, sub) (((UINT32)(idx) << 16) | ((UINT32)(sub) << 8) | 0x40u)
|
||||
/* 一组 8 个:sub = base+1 .. base+8 */
|
||||
#define PDO_MAP_8(idx, base) \
|
||||
PDO_MAP_ENTRY(idx, (base) + 1), PDO_MAP_ENTRY(idx, (base) + 2), \
|
||||
PDO_MAP_ENTRY(idx, (base) + 3), PDO_MAP_ENTRY(idx, (base) + 4), \
|
||||
PDO_MAP_ENTRY(idx, (base) + 5), PDO_MAP_ENTRY(idx, (base) + 6), \
|
||||
PDO_MAP_ENTRY(idx, (base) + 7), PDO_MAP_ENTRY(idx, (base) + 8)
|
||||
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0;
|
||||
UINT32 aEntries[PDO_OUT_ENTRIES];
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ_RXPDO_MAPPING;
|
||||
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0;
|
||||
UINT32 aEntries[PDO_IN_ENTRIES];
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ_TXPDO_MAPPING;
|
||||
|
||||
/* 0x1600: 映射 0x7000:01..48, 每个 64 bits */
|
||||
static TOBJ_RXPDO_MAPPING sRxPDOMap = {PDO_OUT_ENTRIES, {
|
||||
PDO_MAP_8(0x7000, 0), PDO_MAP_8(0x7000, 8), PDO_MAP_8(0x7000, 16),
|
||||
PDO_MAP_8(0x7000, 24), PDO_MAP_8(0x7000, 32), PDO_MAP_8(0x7000, 40)
|
||||
}};
|
||||
|
||||
/* 0x1A00: 映射 0x6000:01..75, 每个 64 bits(9×8 + 3) */
|
||||
static TOBJ_TXPDO_MAPPING sTxPDOMap = {PDO_IN_ENTRIES, {
|
||||
PDO_MAP_8(0x6000, 0), PDO_MAP_8(0x6000, 8), PDO_MAP_8(0x6000, 16),
|
||||
PDO_MAP_8(0x6000, 24), PDO_MAP_8(0x6000, 32), PDO_MAP_8(0x6000, 40),
|
||||
PDO_MAP_8(0x6000, 48), PDO_MAP_8(0x6000, 56), PDO_MAP_8(0x6000, 64),
|
||||
PDO_MAP_ENTRY(0x6000, 73), PDO_MAP_ENTRY(0x6000, 74), PDO_MAP_ENTRY(0x6000, 75)
|
||||
}};
|
||||
|
||||
_Static_assert(sizeof(sRxPDOMap.aEntries) / 4 == PDO_OUT_ENTRIES, "0x1600 条目数不符");
|
||||
_Static_assert(sizeof(sTxPDOMap.aEntries) / 4 == PDO_IN_ENTRIES, "0x1A00 条目数不符");
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Entry Descriptions
|
||||
*
|
||||
* OBJ_GetEntryDesc()(objdef.c:554) 只对 OBJCODE_ARR 复用
|
||||
* pEntryDesc[1] 描述所有子索引; 对 OBJCODE_REC 是逐下标索引 pEntryDesc[Subindex]。
|
||||
* 因此 REC 对象的描述数组必须给满(Sub0..SubN 共 N+1 项), 否则 SDO Info 越界读到
|
||||
* 相邻常量数组的垃圾。
|
||||
* 条目类型完全一致时用 OBJCODE_ARR —— 描述数组固定 2 项, 由 SSC 复用 [1]。
|
||||
* 注意: 可被 PDO 映射的数据子条目必须带 OBJACCESS_RxPDOMAPPING/TxPDOMAPPING,
|
||||
* 否则主站校验 0x1600/0x1A00 的映射目标时会拒绝(官方 sampleappl.h 同此)。
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
/* PDO Assign (0x1C12/0x1C13): ARR, Sub0=UINT8 count, Sub1..n=UINT16 index。
|
||||
* ARR 只需 2 项(Sub0 + 通用项), 由 OBJ_GetEntryDesc 复用 [1]。 */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOAssignEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}
|
||||
};
|
||||
|
||||
/* PDO Mapping (0x1600/0x1A00): ARR of UINT32 映射条目, 2 项即可。 */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOMappingEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* Sub0: 条目数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ} /* Sub1..n: 通用项 */
|
||||
};
|
||||
|
||||
/* 0x7000 Output Data: ARR of UINT64; 子条目可被 RxPDO 映射。 */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asOutputEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING}
|
||||
};
|
||||
|
||||
/* 0x6000 Input Data: ARR of UINT64; 子条目可被 TxPDO 映射, 输入侧只读。 */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asInputEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING}
|
||||
};
|
||||
|
||||
/* SYNC 诊断 0x2000: REC, sub0..sub24 共 25 项, 纯只读不参与 PDO 映射,
|
||||
* 改子索引数无需重刷 SII/EEPROM。sub5..12 为剖分埋点(DWT 周期/计数累加);sub13..23 为 [DIFF-STATS] 差分写取证仪表;
|
||||
* sub24 = OP 输入新鲜度救援次数(不打串口,网页 /api/sync_diag 读)。
|
||||
* 行数必须与 TOBJ2000 字段一一对应:少一行不会编译报错,只会让 SDO 从错误的
|
||||
* 偏移取值 —— 故逐行标号便于核对。 */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* sub0 : 子索引个数 = 24 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub1 : SYNC0 计数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub2 : SYNC1 计数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub3 : ESC IRQ 计数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub4 : uptime 秒 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub5 : PDI_Isr 累计周期 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub6 : PDO_OutputMapping */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub7 : MainLoop free-run 块 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub8 : PDI_Isr 次数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub9 : PDO_InputMapping 全程 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub10 : 其中输入 PDO 过程数据写 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub11 : AL 事件读×2+超限应答 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub12 : ECAT_Application */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub13 : [DIFF-STATS] 已完成窗口数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub14 : [DIFF-STATS] 窗口内拍数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub15 : [DIFF-STATS] 单拍变化字节峰值 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub16 : [DIFF-STATS] run 总数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub17 : [DIFF-STATS] 单拍 run 峰值 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub18 : [DIFF-STATS] 段1 变化字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub19 : [DIFF-STATS] 段2 变化字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub20 : [DIFF-STATS] 段3 变化字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub21 : [DIFF-STATS] 段4 变化字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub22 : [DIFF-STATS] 段5 变化字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub23 : [DIFF-STATS] 比对自身 DWT 周期 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub24 : OP 输入新鲜度救援次数 */
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 0x2502 引导程序交接请求(主站 → APP:「让你回 BOOT」)
|
||||
*
|
||||
* 为什么要有它:EtherCAT 直灌的会话对象在 **BOOT** 里(0x2500/0x2501),APP 的字典
|
||||
* 根本没有这两个对象 —— 实测对着 APP 写 0x2500 回 abort=0x06020000「对象不存在」。
|
||||
* 所以一台正在跑 APP 的机器必须先交接,才能走总线刷写。量产那条交接只有
|
||||
* SPI 0xB0 和 CAN 0x320+"OTA!",没有总线内的入口;常驻刷写必须补上。
|
||||
*
|
||||
* 语义:写 1 = 请求进 BOOT;写 0 是无操作(不做"取消",避免半路误清)。
|
||||
* 动作复用现成的 OtaBootRequest 异步链:这里**只置 pending 标志**,配置页三字的
|
||||
* Flash 写入 + NVIC_SystemReset 由主循环 OtaBootRequest::process() 下一圈执行
|
||||
* (sys/initial.cpp 每圈调;与 SPI 0xB0 在 ISR 里只置标志同一个形状),
|
||||
* 所以 SDO 响应先发出去、复位后到,不会把 SM1 半包留在总线上。
|
||||
*
|
||||
* 交接后板子会稳定停在 BOOT:APP 写的是 EN=NOT_AVAL,而 BOOT 的救砖门
|
||||
* ecatArmed() = !(CHECK==AVAL && EN==AVAL) ⇒ 顺手就把 0x2500 直灌解锁了,
|
||||
* 不需要 word8 令牌(也别依赖它:APP 的 otaConfigSet 只保全 8 个字,一擦页就没了)。
|
||||
*---------------------------------------------------------------------------*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x2502[] = {
|
||||
/* VAR 只有 :00 一个条目,且必须带写权限位(SSC 的通用写权限门在 objdef.c:1325,
|
||||
* 早于 1352 的回调分发 —— BOOT 的 0x2500 就是栽在只给 ACCESS_READ 上) */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE},
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2502[] = "GD32 boot request";
|
||||
|
||||
static UINT32 s_bootReq = 0U; /**< 最近一次写入值,读回用于主站自证 */
|
||||
|
||||
static UINT8 ObjBootReqWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
const UINT8 *p8 = (const UINT8 *)pData;
|
||||
UINT32 v;
|
||||
|
||||
(void)index;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (subindex != 0U) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING;
|
||||
}
|
||||
if (pData == NULL || dataSize < 4U) {
|
||||
return ABORTIDX_PARAM_LENGTH_TOO_SHORT;
|
||||
}
|
||||
v = (UINT32)p8[0] | ((UINT32)p8[1] << 8) | ((UINT32)p8[2] << 16) | ((UINT32)p8[3] << 24);
|
||||
s_bootReq = v;
|
||||
|
||||
if (v == 1U) {
|
||||
EcatEnterBootRequestAsync();
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 0x2503 ESP32 WiFi EN(PC9 / WLESS_EN)控制(主站 → APP)
|
||||
*
|
||||
* 为什么走 EtherCAT 而不是 SPI 0xA2 / WiFi HTTP:WiFi 使能被拉低后 ESP32
|
||||
* 当场掉线,经 /spi/send 的控制路径跟被控对象同生共死——关得掉、开不回来。
|
||||
* EtherCAT 主站在 PC 侧,与 ESP32 完全解耦,关断/开启都能下发。
|
||||
*
|
||||
* 语义(UINT32 bit[7:0]=action,与 sys/esp32_en.h Esp32En::drive 对齐):
|
||||
* 0=拉高(开) 1=拉低保持(关) 2=拉高 3=浮空 4=只读
|
||||
* 读回:bit[7:0] = 实际引脚电平 0/1(0xEE=无效 action)。
|
||||
*---------------------------------------------------------------------------*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x2503[] = {
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE},
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2503[] = "ESP32 WiFi EN";
|
||||
|
||||
static UINT32 s_esp32EnVal = 1U; /**< 读回缓存:默认 1=上电已释放 EN */
|
||||
|
||||
static UINT8 ObjEsp32EnWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
const UINT8 *p8 = (const UINT8 *)pData;
|
||||
UINT32 v;
|
||||
UINT8 action, st;
|
||||
|
||||
(void)index;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (subindex != 0U) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING;
|
||||
}
|
||||
if (pData == NULL || dataSize < 4U) {
|
||||
return ABORTIDX_PARAM_LENGTH_TOO_SHORT;
|
||||
}
|
||||
v = (UINT32)p8[0] | ((UINT32)p8[1] << 8) | ((UINT32)p8[2] << 16) | ((UINT32)p8[3] << 24);
|
||||
action = (UINT8)(v & 0xFFU);
|
||||
st = EcatEsp32EnDrive(action);
|
||||
s_esp32EnVal = (UINT32)st;
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 0x2510 / 0x2511 电机 OTA 邮箱进料通路(主站 → APP)
|
||||
*
|
||||
* 为什么要有它:电机 OTA 的引擎在 GD32(MotorOtaRelay:AES 握手 + CAN 停等),
|
||||
* 但固件明文只有一条进料路 —— ESP32 经 SPI 0xD1 喂。只插网线的场合那条 HTTP
|
||||
* 打不通,于是前端把电机 OTA 标成「本链路不支持」。这两个对象把进料口换成
|
||||
* EtherCAT 邮箱,引擎一行没动:数据窗的写回调最终调的还是 MotorOtaRelay::feedAt()。
|
||||
*
|
||||
* 形状完全照 BOOT 已验证的 0x2500/0x2501(comm/ecat/boot_ecat_app.cpp):
|
||||
* - REC 的条目统一 UINT32,偏移才和 SSC「si0 占 2 字节 + 按 BitLength 紧排」一致;
|
||||
* - 数据窗建成 VAR(不是 ARR):VAR 没有 sub0 计数字段,主站按 `:00` 整块写;
|
||||
* VAR 的 maxSubindex 恒为 0 ⇒ 写 `:1` 会直接 ABORTIDX_SUBINDEX_NOT_EXISTING;
|
||||
* complete access 对 VAR 一律 UNSUPPORTED ⇒ 主站必须传 CA=FALSE;
|
||||
* - 条目描述必须带写权限位,objdef.c:1325 的通用写权限门早于 1352 的回调分发,
|
||||
* 少了它回调一次都不会进(BOOT 的 0x2500 当初就这么静默躺平过)。
|
||||
* 业务逻辑(暂存、CRC、泵、终态锁存)在 comm/ecat_motor_ota.cpp,本层只做
|
||||
* SDO 字节 ↔ 纯整型参数的翻译。
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 bSubIndex; /**< sub0:条目数,SSC 按 UINT16 读 */
|
||||
UINT32 u32Cmd; /**< sub1:W 1=start 3=abort;R 最近一次命令 */
|
||||
UINT32 u32Total; /**< sub2:镜像总字节数 */
|
||||
UINT32 u32Channel; /**< sub3:目标 CAN 通道 0..2 */
|
||||
UINT32 u32Node; /**< sub4:目标节点 1..9 */
|
||||
UINT32 u32ImgCrc; /**< sub5:整镜像 CRC32(MPEG-2,与 SPI 链路同款) */
|
||||
UINT32 u32Staged; /**< sub6:已连续暂存字节数(只读) */
|
||||
UINT32 u32RelayState; /**< sub7:MotorOtaRelay 状态(只读) */
|
||||
UINT32 u32PkAcked; /**< sub8:已确认包数(只读) */
|
||||
UINT32 u32PkTotal; /**< sub9:总包数(只读) */
|
||||
UINT32 u32Err; /**< sub10:高字节=本模块会话码 0x8x,低字节=电机协议错误码 */
|
||||
UINT32 u32Phase; /**< sub11:位图,见 ecat_motor_ota.cpp */
|
||||
}
|
||||
OBJ_STRUCT_PACKED_END
|
||||
TOBJ2510;
|
||||
|
||||
_Static_assert(offsetof(TOBJ2510, u32Cmd) == 2, "TOBJ2510 未紧排:检查 OBJ_STRUCT_PACKED_START");
|
||||
_Static_assert(sizeof(TOBJ2510) == 2 + 4 * 11, "TOBJ2510 尺寸应为 2+44");
|
||||
|
||||
static TOBJ2510 s_motorOtaCtl = {ECAT_MOTA_CTL_SUBS, 0U, 0U, 0U, 0U, 0U,
|
||||
0U, 0U, 0U, 0U, 0U, 0U};
|
||||
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asMotorOtaCtlEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* sub0 : 子索引个数 = 11 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* sub1 : cmd(必须带写位,否则回调进不来) */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* sub2 : 镜像总长 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* sub3 : CAN 通道 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* sub4 : 节点号 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* sub5 : 整镜像 CRC32 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub6 : 已暂存字节 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub7 : relay 状态 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub8 : 已 ACK 包数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub9 : 总包数 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub10 : 会话码<<8 | 协议错误码 */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub11 : phase 位图 */
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2510[] = "Motor OTA control";
|
||||
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asMotorOtaChunkEntryDesc[] = {
|
||||
/* 唯一条目 = 一整块 [offset:u32][crc32:u32][data...]。
|
||||
* 写回调在 objdef.c:1352 早分发,SSC 的默认拷贝路径不会执行。 */
|
||||
{DEFTYPE_OCTETSTRING, (UINT16)(ECAT_MOTA_CHUNK_OBJ_BYTES * 8U), ACCESS_READWRITE},
|
||||
};
|
||||
static OBJCONST UCHAR aName0x2511[] = "Motor firmware chunk";
|
||||
|
||||
/** 数据窗的落地缓冲:写路径不依赖它(回调自己取字节),保留是给默认读路径的落点。 */
|
||||
static UINT8 s_motorOtaChunk[ECAT_MOTA_CHUNK_OBJ_BYTES];
|
||||
|
||||
static UINT8 ObjMotorOtaCtlWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
const UINT8 rc = EcatMotorOtaCtlWrite(subindex, dataSize, (const UINT8 *)pData);
|
||||
|
||||
(void)index;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (subindex == 0U) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING; /* sub0 是条目数,不可写 */
|
||||
}
|
||||
if (subindex >= 2U + ECAT_MOTA_CTL_SUBS) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING;
|
||||
}
|
||||
if (rc != ECAT_MOTA_OK) {
|
||||
/* 具体原因不靠 abort 码分类:主站读 0x2510:10 的高字节拿真码。 */
|
||||
return ABORTIDX_DATA_CANNOT_BE_READ_OR_STORED;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/** 0x2510 读取:逐 subindex 从 ecat_motor_ota 取**实时**值,不读 s_motorOtaCtl 的镜像。 */
|
||||
static UINT8 ObjMotorOtaCtlRead(UINT16 index, UINT8 subindex, UINT32 objSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
const UINT32 v = EcatMotorOtaCtlRead(subindex);
|
||||
UINT8 *p8 = (UINT8 *)pData;
|
||||
|
||||
(void)index;
|
||||
(void)objSize;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (pData == NULL) {
|
||||
return ABORTIDX_PARAM_LENGTH_TOO_SHORT;
|
||||
}
|
||||
if (subindex == 0U) {
|
||||
/* sub0 是 UINT8(条目数),只占载荷一个字节 */
|
||||
p8[0] = (UINT8)v;
|
||||
} else {
|
||||
p8[0] = (UINT8)(v & 0xFFU);
|
||||
p8[1] = (UINT8)((v >> 8) & 0xFFU);
|
||||
p8[2] = (UINT8)((v >> 16) & 0xFFU);
|
||||
p8[3] = (UINT8)((v >> 24) & 0xFFU);
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
static UINT8 ObjMotorOtaChunkWrite(UINT16 index, UINT8 subindex, UINT32 dataSize,
|
||||
UINT16 MBXMEM *pData, UINT8 bCompleteAccess)
|
||||
{
|
||||
const UINT8 rc = EcatMotorOtaChunkWrite(dataSize, (const UINT8 *)pData);
|
||||
|
||||
(void)index;
|
||||
(void)bCompleteAccess;
|
||||
|
||||
if (subindex != 0U) {
|
||||
return ABORTIDX_SUBINDEX_NOT_EXISTING; /* VAR 只有 :00 */
|
||||
}
|
||||
if (rc == ECAT_MOTA_E_GAP || rc == ECAT_MOTA_E_PARAM || rc == ECAT_MOTA_E_BADLEN) {
|
||||
return ABORTIDX_VALUE_EXCEEDED;
|
||||
}
|
||||
if (rc != ECAT_MOTA_OK) {
|
||||
return ABORTIDX_DATA_CANNOT_BE_READ_OR_STORED;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* 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";
|
||||
OBJCONST UCHAR OBJMEM aName0x2000[] = "SYNC Diag";
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Application Object Dictionary
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
TOBJECT OBJMEM ApplicationObjDicList[] = {
|
||||
/* 0x1C12: RxPDO Assign (ARR of UINT16; DEFTYPE 为元素类型) */
|
||||
{NULL, NULL, 0x1C12, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
|
||||
asPDOAssignEntryDesc, aName0x1C12, &sRxPDOassign, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x1600: RxPDO Mapping (ARR of UINT32, PDO_OUT_ENTRIES 个映射条目) */
|
||||
{NULL, NULL, 0x1600, {DEFTYPE_PDOMAPPING, PDO_OUT_ENTRIES | (OBJCODE_ARR << 8)},
|
||||
asPDOMappingEntryDesc, aName0x1600, &sRxPDOMap, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x7000: Output Data (主站→从站, ARR of UINT64) */
|
||||
{NULL, NULL, 0x7000, {DEFTYPE_UNSIGNED64, PDO_OUT_ENTRIES | (OBJCODE_ARR << 8)},
|
||||
asOutputEntryDesc, 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 (ARR of UINT32, PDO_IN_ENTRIES 个映射条目) */
|
||||
{NULL, NULL, 0x1A00, {DEFTYPE_PDOMAPPING, PDO_IN_ENTRIES | (OBJCODE_ARR << 8)},
|
||||
asPDOMappingEntryDesc, aName0x1A00, &sTxPDOMap, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x6000: Input Data (从站→主站, ARR of UINT64) */
|
||||
{NULL, NULL, 0x6000, {DEFTYPE_UNSIGNED64, PDO_IN_ENTRIES | (OBJCODE_ARR << 8)},
|
||||
asInputEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
|
||||
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 24 | (OBJCODE_REC << 8)},
|
||||
asSyncDiagEntryDesc, aName0x2000, &s_syncDiag, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x2502: 引导程序交接请求(写 1 = 回 BOOT 等灌写;见 ObjBootReqWrite 注) */
|
||||
{NULL, NULL, 0x2502, {DEFTYPE_UNSIGNED32, 0 | (OBJCODE_VAR << 8)},
|
||||
sEntryDesc0x2502, aName0x2502, &s_bootReq, NULL, ObjBootReqWrite, 0x0000},
|
||||
|
||||
/* 0x2503: ESP32 WiFi EN(PC9) —— 经 EtherCAT 控制,WiFi 关断后仍可拉回 */
|
||||
{NULL, NULL, 0x2503, {DEFTYPE_UNSIGNED32, 0 | (OBJCODE_VAR << 8)},
|
||||
sEntryDesc0x2503, aName0x2503, &s_esp32EnVal, NULL, ObjEsp32EnWrite, 0x0000},
|
||||
|
||||
/* 0x2510: 电机 OTA 会话控制(写 sub2 声明总长 → 反复写 0x2511 灌块 → 写 sub1=1 启动) */
|
||||
{NULL, NULL, 0x2510, {DEFTYPE_RECORD, ECAT_MOTA_CTL_SUBS | (OBJCODE_REC << 8)},
|
||||
asMotorOtaCtlEntryDesc, aName0x2510, &s_motorOtaCtl,
|
||||
ObjMotorOtaCtlRead, ObjMotorOtaCtlWrite, 0x0000},
|
||||
|
||||
/* 0x2511: 电机固件数据窗(VAR OCTETSTRING,主站分段下载攒完才回调一次) */
|
||||
{NULL, NULL, 0x2511, {DEFTYPE_OCTETSTRING, 0 | (OBJCODE_VAR << 8)},
|
||||
asMotorOtaChunkEntryDesc, aName0x2511, &s_motorOtaChunk,
|
||||
NULL, ObjMotorOtaChunkWrite, 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 = ECAT_PDO_IN_BYTES; /* 75×UINT64 = 600 bytes */
|
||||
*pOutputSize = ECAT_PDO_OUT_BYTES; /* 48×UINT64 = 384 bytes */
|
||||
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。
|
||||
*
|
||||
* pIntMask 指向 SSC 全局 u16ALEventMask,StartInputHandler() 已按同步模式算好:
|
||||
* free-run + 有输出 PDO 时它是 PROCESS_OUTPUT_EVENT(0x0400)(ecatslv.c:959/1090),
|
||||
* 即 SM2 收到输出这一事件才是 SM-Synchron 模式的同步节拍。
|
||||
* 若写死 0x0800(PROCESS_INPUT_EVENT) 会把 0x0400 顶掉,而随后的
|
||||
* SetALEventMask() 只做“或”,于是寄存器 0x204 永远缺 SM2 位:
|
||||
* 输出到达不再产生 IRQ,PDI_Isr 只能被主站“读输入”事件带起来,
|
||||
* 输入刷新因此永远落在主站读之后 —— SM3 是 3-buffer 缓冲模式,
|
||||
* 主站读到未被从站重写的缓冲不累加 WKC,表现为几乎每周期 wkc < expected_wkc。
|
||||
*
|
||||
* 官方 sampleappl.c / el9800appl.c 同样只 return,不碰 pIntMask。 */
|
||||
(void)pIntMask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
UINT16 APPL_StopInputHandler(void) { return 0; }
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Output Handler (SAFEOP→OP)
|
||||
*---------------------------------------------------------------------------*/
|
||||
UINT16 APPL_StartOutputHandler(void)
|
||||
{
|
||||
EcatCan_ResetSequenceState();
|
||||
return 0;
|
||||
}
|
||||
|
||||
UINT16 APPL_StopOutputHandler(void)
|
||||
{
|
||||
EcatCan_ResetSequenceState();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* APPL_AckErrorInd
|
||||
*---------------------------------------------------------------------------*/
|
||||
void APPL_AckErrorInd(UINT16 stateTrans)
|
||||
{
|
||||
(void)stateTrans;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* APPL_InputMapping — 每周期从应用层填充 ESC 输入 PDO
|
||||
*---------------------------------------------------------------------------*/
|
||||
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();
|
||||
/* 剖分埋点快照(sub5..12) */
|
||||
s_syncDiag.u32PdiCyc = g_dbg_pdiCyc;
|
||||
s_syncDiag.u32OutCyc = g_dbg_outCyc;
|
||||
s_syncDiag.u32FreeRunCyc = g_dbg_freerunCyc;
|
||||
s_syncDiag.u32PdiCnt = g_dbg_pdiCnt;
|
||||
s_syncDiag.u32InCyc = g_dbg_inCyc;
|
||||
s_syncDiag.u32InWriteCyc = g_dbg_inWriteCyc;
|
||||
s_syncDiag.u32AlCyc = g_dbg_alCyc;
|
||||
s_syncDiag.u32AppCyc = g_dbg_appCyc;
|
||||
s_syncDiag.u32OpFreshRescues = s_opFreshRescues;
|
||||
|
||||
/* [DIFF-STATS] 差分写统计快照(sub13..23):一次取全 11 个窗口值,
|
||||
* 取的都是"上一个已完成的 1s 窗口",故 SDO 读不会拿到半累积值。 */
|
||||
{
|
||||
UINT32 ds[11];
|
||||
EcatDiffStats_GetWindow(ds);
|
||||
s_syncDiag.u32DsWinSeq = ds[0];
|
||||
s_syncDiag.u32DsCycles = ds[1];
|
||||
s_syncDiag.u32DsChangedMax = ds[2];
|
||||
s_syncDiag.u32DsRunsTotal = ds[3];
|
||||
s_syncDiag.u32DsRunsMax = ds[4];
|
||||
s_syncDiag.u32DsSeg1 = ds[5];
|
||||
s_syncDiag.u32DsSeg2 = ds[6];
|
||||
s_syncDiag.u32DsSeg3 = ds[7];
|
||||
s_syncDiag.u32DsSeg4 = ds[8];
|
||||
s_syncDiag.u32DsSeg5 = ds[9];
|
||||
s_syncDiag.u32DsCompareCyc = ds[10];
|
||||
}
|
||||
|
||||
/* 先让 EthercatPdo 把本周期的输入数据写进 0x6000 的对象存储,
|
||||
* 再交给 ESC。经由对象存储中转(而非直接写 pInputData)是为了让主站
|
||||
* SDO 读 0x6000 时看到与过程数据一致的内容。 */
|
||||
EcatPdo_WriteInputs((UINT8 *)&s_inputObj.u64Data[0]);
|
||||
memcpy(pInputData, &s_inputObj.u64Data[0], ECAT_PDO_IN_BYTES);
|
||||
/* OP 输入新鲜度不变量的活性计数。每次真正走到输入映射就自增,
|
||||
* 供 EcatDbg_OpInputFreshness 判断 PDI 路径是否还活着。 */
|
||||
++s_inMapSeq;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* APPL_OutputMapping — 每周期从 ESC 输出 PDO 读取到应用层
|
||||
*---------------------------------------------------------------------------*/
|
||||
void APPL_OutputMapping(UINT16 *pOutputData)
|
||||
{
|
||||
memcpy(&s_outputObj.u64Data[0], pOutputData, ECAT_PDO_OUT_BYTES);
|
||||
EcatPdo_ReadOutputs((const UINT8 *)&s_outputObj.u64Data[0]);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* APPL_Application — 主循环周期调用
|
||||
*---------------------------------------------------------------------------*/
|
||||
void APPL_Application(void)
|
||||
{
|
||||
/* 应用逻辑在 EthercatPdo::process()(当前实现是 PDO↔CAN 转发)。
|
||||
* 不 CpuLoad 包裹:SM/DC 下本函数在 PDI/Sync0 ISR 内已被 isrEnter/Exit
|
||||
* 计入;free-run 下若主循环每圈调用,再包会抬高 load_perm。 */
|
||||
EcatPdo_Process();
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* AL 诊断快照 — task_runner 5s 心跳用 (C 链接, C++ 侧 extern "C" 声明)
|
||||
* [19:16]=AL 状态(0x0130 低 4 位) [31:24]=AL 错误码(0x0130 高 8 位)
|
||||
* [15:0] =AL 事件寄存器(0x0220, bit0=主站 AL 控制请求待处理)
|
||||
*
|
||||
* ESC 寄存器经 PDI/CSR 读出即主机序(LAN9252 CSR 引擎按
|
||||
* 16-bit 主机总线给出寄存器"值";线上大端只存在于网络侧与 PRAM 原始帧数据),
|
||||
* 与 SSC 自身 GetSyncMan/mailbox 的裸读约定一致,勿加 SWAPWORD。
|
||||
*---------------------------------------------------------------------------*/
|
||||
UINT32 EcatDbg_ReadAlDiag(void)
|
||||
{
|
||||
UINT16 alStatus = 0U;
|
||||
HW_EscReadWord(alStatus, ESC_AL_STATUS_OFFSET);
|
||||
return ((UINT32)alStatus << 16) | (UINT32)HW_GetALEventRegister();
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈
|
||||
* (C 链接, task_runner 250ms 节流调用)
|
||||
*
|
||||
* 守护(1)0x204 缺位自愈:从站 AL>=SAFEOP 且有输出 PDO 时,SSC 不变式是
|
||||
* "0x204 ⊇ u16ALEventMask"。读实际值比对,缺位则补写并打 WARN。
|
||||
* 先补期望值再比对——下行转换路径(ecatslv.c AL_ControlInd 尾部)同时清
|
||||
* 0x204 的 PDO 事件位、bEscIntEnabled、bEcatFirstOutputsReceived;若期望值
|
||||
* 本身缺 SM2 位,旧判定恒通过、缺位永远补不回。SM-Synchron+有输出 PDO 的
|
||||
* 不变式是 u16ALEventMask ⊇ PROCESS_OUTPUT_EVENT(ecatslv.c:959/1086)。
|
||||
*
|
||||
* 自愈(2)SM2 事件楔死:SAFEOP 下 0x220 bit10 挂起 且 标志未置位
|
||||
* = PDI_Isr 通路楔死。从主循环直接完成 PDI_Isr 该做的事:置位
|
||||
* bEcatFirstOutputsReceived + 读 SM2 首/末字应答事件(ecatappl.c:317-320 同款)。
|
||||
* 协议等价:bit10 挂起 = 主站已写过 SM2 = "收到输出"成立。
|
||||
* WARN 附带全部内部旗标(bEscInt/bDcSync/期望与实际 mask/看门狗状态),
|
||||
* 下次复现一行日志即可定位楔死路径。
|
||||
*
|
||||
* 自愈(3)OP 态 PDI_Isr 门控:AL==OP 且 bEscIntEnabled==FALSE 不可
|
||||
* 合法出现——门控只在下行转换尾部被清(ecatslv.c:1273),而下行必改 AL 状态;
|
||||
* 守护与 ESM 同在主循环上下文串行,看到"OP 且门关着"即重入路径漏了恢复段。
|
||||
* 此时 PDI_Isr 整体被门控(ecatappl.c:282),PDO_OutputMapping 停调、输入冻结。
|
||||
* 修法:重开门 + 应答挂起的 SM2 事件(掩码补位由守护(1)在同次调用完成),
|
||||
* 下一帧 LRW 的 SM2 中断即恢复整条过程数据链路。
|
||||
*---------------------------------------------------------------------------*/
|
||||
UINT32 EcatDbg_GuardAleventMask(void)
|
||||
{
|
||||
/* SSC 全局(ecatslv.c 定义;除 u16ALEventMask 外均已由 ecatslv.h PROTO 声明) */
|
||||
extern UINT16 u16ALEventMask;
|
||||
extern BOOL bMbxRunning; /* mailbox.c 定义(dc_app.c 未包含 mailbox.h,局部声明) */
|
||||
|
||||
UINT16 evt = 0U;
|
||||
UINT16 actual = 0U;
|
||||
UINT16 wdState = 0U;
|
||||
UINT16 dummy = 0U;
|
||||
const UINT8 al = (UINT8)(nAlStatus & 0x0FU);
|
||||
|
||||
if (al < 2U /* STATE_PREOP */) {
|
||||
return 0xFFFFU; /* 状态不适用:返回哨兵值,调用方不打印 */
|
||||
}
|
||||
|
||||
/* (0) 邮箱 SM 地址/长度失配兜底:
|
||||
* 邮箱几何构建期常量化(mailbox.c 直达 ecat_def.h:MBX_SM0/1_START/SIZE),
|
||||
* CSR 错值无进入缓存的路径;本守护为运行期内存踩踏的最后兜底:
|
||||
* AL>=PREOP 时用寄存器真值校验 SSC 缓存的 start/len,失配即写回 + WARN。
|
||||
* ESC 寄存器经 PDI/CSR 读出即主机序,勿加 SWAPWORD。 */
|
||||
if (al >= 2U && bMbxRunning) {
|
||||
UINT16 sm0Start = 0U, sm0Len = 0U, sm1Start = 0U, sm1Len = 0U;
|
||||
HW_EscReadWord(sm0Start, ESC_SYNCMAN_REG_OFFSET);
|
||||
HW_EscReadWord(sm0Len, ESC_SYNCMAN_REG_OFFSET + 2U);
|
||||
HW_EscReadWord(sm1Start, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER);
|
||||
HW_EscReadWord(sm1Len, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER + 2U);
|
||||
if (sm0Len >= 8U && sm1Len >= 8U
|
||||
&& (u16EscAddrReceiveMbx != sm0Start || u16EscAddrSendMbx != sm1Start
|
||||
|| u16ReceiveMbxSize != sm0Len || u16SendMbxSize != sm1Len)) {
|
||||
printf("[ECAT][MBX] WARN: SM addr/len mismatch, SSC rx=0x%04X/%u tx=0x%04X/%u "
|
||||
"vs reg SM0=0x%04X/%u SM1=0x%04X/%u -> restored\r\n",
|
||||
(unsigned)u16EscAddrReceiveMbx, (unsigned)u16ReceiveMbxSize,
|
||||
(unsigned)u16EscAddrSendMbx, (unsigned)u16SendMbxSize,
|
||||
(unsigned)sm0Start, (unsigned)sm0Len,
|
||||
(unsigned)sm1Start, (unsigned)sm1Len);
|
||||
u16EscAddrReceiveMbx = sm0Start;
|
||||
u16ReceiveMbxSize = sm0Len;
|
||||
u16EscAddrSendMbx = sm1Start;
|
||||
u16SendMbxSize = sm1Len;
|
||||
}
|
||||
}
|
||||
|
||||
if (al < 4U /* STATE_SAFEOP */ || nPdOutputSize == 0U) {
|
||||
return 0xFFFFU; /* 以下仅 SAFEOP+ 适用 */
|
||||
}
|
||||
|
||||
/* PDI 寄存器读即主机序,勿加 SWAPWORD。 */
|
||||
evt = HW_GetALEventRegister();
|
||||
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
|
||||
|
||||
/* (1) 0x204 缺位自愈:先补期望值,再比对补实际值 */
|
||||
if ((u16ALEventMask & PROCESS_OUTPUT_EVENT) == 0U) {
|
||||
u16ALEventMask |= PROCESS_OUTPUT_EVENT;
|
||||
printf("[ECAT][AEM] WARN: expected AL mask lacked SM2 bit -> 0x%04X\r\n",
|
||||
(unsigned)u16ALEventMask);
|
||||
}
|
||||
if ((UINT16)(actual & u16ALEventMask) != u16ALEventMask) {
|
||||
const UINT16 before = actual;
|
||||
HW_SetALEventMask(u16ALEventMask);
|
||||
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
|
||||
printf("[ECAT][AEM] WARN: AL Event Mask lost bits, actual=0x%04X expect=0x%04X -> restored 0x%04X\r\n",
|
||||
(unsigned)before, (unsigned)u16ALEventMask, (unsigned)actual);
|
||||
}
|
||||
|
||||
/* (2) SM2 事件楔死自愈:仅 SAFEOP + 标志未置位 + bit10 挂起时触发
|
||||
* (OP 态下 LRW 每周期置位/清除 bit10 属正常,不能误报) */
|
||||
if (al == 4U /* SAFEOP */ && (evt & 0x0400U) != 0U && bEcatFirstOutputsReceived == FALSE) {
|
||||
HW_EscReadWord(wdState, ESC_PD_WD_STATE);
|
||||
bEcatFirstOutputsReceived = TRUE;
|
||||
HW_EscReadWord(dummy, nEscAddrOutputData);
|
||||
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
|
||||
printf("[ECAT][AEM] WARN: SM2 event stuck evt=0x%04X, PDI_Isr path wedged "
|
||||
"(bEscInt=%u bDcSync=%u mask_exp=0x%04X mask_act=0x%04X wd=0x%04X) "
|
||||
"-> flag set + event acked\r\n",
|
||||
(unsigned)evt,
|
||||
(unsigned)(bEscIntEnabled ? 1U : 0U),
|
||||
(unsigned)(bDcSyncActive ? 1U : 0U),
|
||||
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
|
||||
}
|
||||
|
||||
/* (3) OP 态 PDI_Isr 门控自愈:OP 下 bEscIntEnabled==FALSE
|
||||
* 不可合法出现——门控只在下行转换尾部被清(ecatslv.c:1273),而下行必改
|
||||
* AL 状态;守护与 ESM 同在主循环上下文串行,看到"OP 且门关着"即 SAFEOP
|
||||
* 重入路径漏了恢复段。此时 PDI_Isr 整体被门控(ecatappl.c:282),
|
||||
* PDO_OutputMapping 停调、输入冻结。
|
||||
* 修法:重开门 + 应答挂起的 SM2 事件(ISR 非 OP 分支同款读首/末字);
|
||||
* 0x204 掩码补位已由上面(1)在同次调用完成,下一帧 LRW 即恢复。 */
|
||||
if (al == 8U /* OP */ && nPdOutputSize > 0U && bEscIntEnabled == FALSE) {
|
||||
HW_EscReadWord(wdState, ESC_PD_WD_STATE);
|
||||
bEscIntEnabled = TRUE;
|
||||
HW_EscReadWord(dummy, nEscAddrOutputData);
|
||||
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
|
||||
printf("[ECAT][AEM] WARN: OP with PDI_Isr gated, evt=0x%04X "
|
||||
"(bDcSync=%u mask_exp=0x%04X mask_act=0x%04X wd=0x%04X) "
|
||||
"-> gate reopened + event acked\r\n",
|
||||
(unsigned)evt,
|
||||
(unsigned)(bDcSyncActive ? 1U : 0U),
|
||||
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* OP 输入新鲜度不变量
|
||||
*
|
||||
* SM-Synchron 稳态下 MainLoop 的 free-run 分支被
|
||||
* (!bEscIntEnabled || !bEcatFirstOutputsReceived) && !bDcSyncActive
|
||||
* 整段跳过,输入 PDO **只**由 PDI_Isr 的 PDO_InputMapping 刷新。
|
||||
* PDI 通路一停(门控/掩码/EXTI),ESC 仍答 WKC、AL 仍 OP,主站读到的是
|
||||
* 冻住的旧缓冲。
|
||||
*
|
||||
* 不变量:「OP ⇒ 输入必须在 T ms 内被重写」。
|
||||
* - 正常路径:Process 每拍自增 seq
|
||||
* - 异常路径:seq 停涨超过阈值 → 主循环强制 PDO_InputMapping
|
||||
* (屏中断防双写)+ 重开门/应答 SM2,主站侧 gseq/rx 立刻恢复前进
|
||||
* 不依赖主站重拉、不依赖人工复位。MainLoop 若本身死锁则本函数也停 ——
|
||||
* 那是另一类故障(心跳会停),不在本不变量覆盖范围。
|
||||
*---------------------------------------------------------------------------*/
|
||||
void EcatDbg_OpInputFreshness(void)
|
||||
{
|
||||
const UINT8 al = (UINT8)(nAlStatus & 0x0FU);
|
||||
const UINT32 now = EcatDbg_Dwt();
|
||||
/* 活性看 processSeq(Process 真填过输入),不能看 s_inMapSeq。
|
||||
* InputMapping 可在 Process 停摆时继续拷陈旧 s_in,seq 照涨 → 救援永不触发。 */
|
||||
const UINT32 seq = EcatPdo_ProcessSeq();
|
||||
|
||||
if (al != 8U || nPdInputSize == 0U) {
|
||||
s_inMapSeqSeen = seq;
|
||||
s_inStaleBaseCyc = now;
|
||||
return;
|
||||
}
|
||||
if (seq != s_inMapSeqSeen) {
|
||||
s_inMapSeqSeen = seq;
|
||||
s_inStaleBaseCyc = now;
|
||||
return;
|
||||
}
|
||||
|
||||
/* seq 未推进:等满阈值再介入,避开两拍之间的正常空隙。
|
||||
* 5ms @1ms 周期 = 连丢 ≥5 拍;@4ms 周期 = 连丢 1~2 拍,仍足够判死。 */
|
||||
{
|
||||
const UINT32 thresh = (SystemCoreClock / 1000U) * 5U;
|
||||
if ((UINT32)(now - s_inStaleBaseCyc) < thresh) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 强制刷新:先重开门/首输出标志(门关着时 PDI 永远不会回来),
|
||||
* 再在 EXMC 互斥窗口(仅屏蔽 EXTI0/1/2)内做一次输入映射,保证不关全局中断(Preempt 0 CAN RX 不受影响)。
|
||||
* 必须先 EcatPdo_Process() 再 PDO_InputMapping()。
|
||||
* Process 先填好 buildInput+fillDiagTail,InputMapping 再发布,救援才有内容可写。 */
|
||||
{
|
||||
UINT16 dummy = 0U;
|
||||
const UINT16 evt = HW_GetALEventRegister();
|
||||
if (!bEscIntEnabled) {
|
||||
bEscIntEnabled = TRUE;
|
||||
}
|
||||
if (!bEcatFirstOutputsReceived) {
|
||||
bEcatFirstOutputsReceived = TRUE;
|
||||
HW_EscReadWord(dummy, nEscAddrOutputData);
|
||||
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
|
||||
}
|
||||
/* 救援路径计入 busy(低频;与 ISR/任务派发同一口径)。
|
||||
* 不要把 MainLoop 整段包进 CpuLoad——见 task_runner / cpu_load.h。 */
|
||||
CpuLoad_Enter();
|
||||
const uint32_t prevMask = EscExmcBus_MaskEnter();
|
||||
EcatPdo_Process();
|
||||
PDO_InputMapping();
|
||||
EscExmcBus_MaskExit(prevMask);
|
||||
CpuLoad_Leave();
|
||||
++s_opFreshRescues;
|
||||
s_inMapSeqSeen = EcatPdo_ProcessSeq();
|
||||
s_inStaleBaseCyc = EcatDbg_Dwt();
|
||||
/* 不打串口:计数经 0x2000:24 / /api/sync_diag 上到网页,避免 5ms 刷屏。 */
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 EcatDbg_OpFreshRescues(void)
|
||||
{
|
||||
return s_opFreshRescues;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file dc_app.h
|
||||
* @brief DCU EtherCAT 应用层接口声明
|
||||
*
|
||||
* 提供 SSC 所需的 APPL_ 回调函数声明。
|
||||
*/
|
||||
#ifndef COMM_SSC_PORT_DC_APP_H
|
||||
#define COMM_SSC_PORT_DC_APP_H
|
||||
|
||||
#include "ecat_def.h"
|
||||
#include "esc.h"
|
||||
#include "ecatslv.h"
|
||||
#include <stdint.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);
|
||||
|
||||
/* OP 输入新鲜度不变量(task_runner 每圈调用;内部廉价,仅冻结时强制重写) */
|
||||
void EcatDbg_OpInputFreshness(void);
|
||||
UINT32 EcatDbg_OpFreshRescues(void);
|
||||
|
||||
/* HBI/EXMC 事务互斥(仅屏蔽 EXTI0/1/2,不关全局中断) */
|
||||
uint32_t EscExmcBus_MaskEnter(void);
|
||||
void EscExmcBus_MaskExit(uint32_t prev);
|
||||
uint32_t HW_EscExmcMaskEnter(void);
|
||||
void HW_EscExmcMaskExit(uint32_t prev);
|
||||
|
||||
/* 复位输出 sequence 状态机 */
|
||||
void EcatCan_ResetSequenceState(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* COMM_SSC_PORT_DC_APP_H */
|
||||
@@ -0,0 +1,784 @@
|
||||
/**
|
||||
* @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
|
||||
|
||||
/** 看门狗由定时器中断检查。
|
||||
* ECAT_TIMER_INT=0:MainLoop 按 DWT CYCCNT 轮询(hw_gd32exmc.cpp 的
|
||||
* HW_GetTimerValue/HW_ClearTimerValue),每 1ms 调一次 ECAT_CheckTimer。
|
||||
* 若置 1 则要求端口提供调用 ECAT_CheckTimer() 的 1ms 硬件定时器 ISR——
|
||||
* 本工程无该 ISR,ECAT_CheckTimer 零调用者会导致 EsmTimeoutCounter 永不递减
|
||||
* (状态迁移永不超时)及 DC PLL 计数器不前进(残留 DC 激活位时 SAFEOP+0x0000
|
||||
* 永久挂起)。 */
|
||||
#ifndef ECAT_TIMER_INT
|
||||
#define ECAT_TIMER_INT 0
|
||||
#endif
|
||||
|
||||
/** DWT CYCCNT @180MHz 的每毫秒 tick 数(ECAT_TIMER_INT=0 时 MainLoop 用)。 */
|
||||
#ifndef ECAT_TIMER_INC_P_MS
|
||||
#define ECAT_TIMER_INC_P_MS 180000U
|
||||
#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
|
||||
------
|
||||
------ 这些宏是 SSC 紧排结构体的唯一手段——SSC 的 SDO 读写按"si0 占 16bit,
|
||||
------ 其后各条目按 BitLength 紧排"计算字节偏移(OBJ_GetEntryOffset, objdef.c:609),
|
||||
------ 留空则结构体按编译器自然对齐,与该假设不符,导致 SDO 单条目访问
|
||||
------ 全部读写错位内存;SSC 内部 0x1C3x/0x10F1 等同样受影响。
|
||||
------ AC6/armclang 支持 GCC 风格 __attribute__((packed)),置于 struct 关键字之后
|
||||
------ (即 START 位置)对整个结构体生效。
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef STRUCT_PACKED_START
|
||||
#define STRUCT_PACKED_START __attribute__((packed))
|
||||
#endif
|
||||
|
||||
#ifndef STRUCT_PACKED_END
|
||||
#define STRUCT_PACKED_END
|
||||
#endif
|
||||
|
||||
#ifndef MBX_STRUCT_PACKED_START
|
||||
#define MBX_STRUCT_PACKED_START __attribute__((packed))
|
||||
#endif
|
||||
|
||||
#ifndef MBX_STRUCT_PACKED_END
|
||||
#define MBX_STRUCT_PACKED_END
|
||||
#endif
|
||||
|
||||
#ifndef OBJ_STRUCT_PACKED_START
|
||||
#define OBJ_STRUCT_PACKED_START __attribute__((packed))
|
||||
#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 (需与 DCU_ESI.xml / gen_eeprom.py / dc_app.c 三处一致)
|
||||
------
|
||||
------ 上限依据:邮箱占 0x1000-0x13FF,过程数据区仅 0x1400-0x1FFF=3072B,
|
||||
------ SM2/SM3 均 3-buffer 实占 3×size ⇒ out+in ≤ 1024B。
|
||||
------ 推导与实测(SII 余量/EXMC 耗时)见 comm/ethercat_pdo_size.h。
|
||||
------
|
||||
------ 这两个宏是**编译期硬门限**,必须先于其它改动生效:
|
||||
------ - ecatappl.c:157 用它们定义 PD 暂存区 aPdOutputData/aPdInputData
|
||||
------ - ecatslv.c:509/574 在 SM 校验时拿 SMLength 与之比较,超限直接返回
|
||||
------ ALSTATUSCODE_INVALIDSMINCFG/OUTCFG,表现为 PREOP→SAFEOP 失败
|
||||
------ 忘改这里会在进 SAFEOP 时报 AL 错误,而不是静默截断。
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。0x180 = 384B */
|
||||
#ifndef MAX_PD_OUTPUT_SIZE
|
||||
#define MAX_PD_OUTPUT_SIZE 0x180
|
||||
#endif
|
||||
|
||||
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。0x280 = 640B */
|
||||
#ifndef MAX_PD_INPUT_SIZE
|
||||
#define MAX_PD_INPUT_SIZE 0x280
|
||||
#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
|
||||
|
||||
/* 0x100A 的位长。SSC 默认 3 字符只装得下占位 "1.0";这里抬到 16,真实值由
|
||||
* sys/initial.cpp 起板时写进 acSoftwareversion(与 BOOT 侧同款改动)。 */
|
||||
#ifndef DEVICE_SW_VERSION_LEN
|
||||
#define DEVICE_SW_VERSION_LEN 0x10
|
||||
#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
|
||||
|
||||
/* 本板邮箱几何构建期常量。
|
||||
* 取值 = gen_eeprom.py 的 MBX_RX_OFFSET/SIZE(0x1000/256) 与 MBX_TX_OFFSET/SIZE
|
||||
* (0x1200/256),即 DCU_ESI.xml <Sm> 与 ESC 上电自 SII 加载的寄存器真值;主站
|
||||
* config_init 也只是照抄同一份 SII。MBX_StartMailboxHandler 以此直达缓存,
|
||||
* 不信任单次 CSR 间接读,CSR 读仅作交叉校验(mailbox.c 打 WARN)。
|
||||
* ★ 改 SII 邮箱几何必须同步改这里(gen_eeprom.py 顶部注释同样有约束)。★ */
|
||||
#ifndef MBX_SM0_START
|
||||
#define MBX_SM0_START 0x1000
|
||||
#endif
|
||||
|
||||
#ifndef MBX_SM0_SIZE
|
||||
#define MBX_SM0_SIZE 0x0100
|
||||
#endif
|
||||
|
||||
#ifndef MBX_SM1_START
|
||||
#define MBX_SM1_START 0x1200
|
||||
#endif
|
||||
|
||||
#ifndef MBX_SM1_SIZE
|
||||
#define MBX_SM1_SIZE 0x0100
|
||||
#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,363 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate LAN9252 EEPROM binary image (ESI/SII) for GD32E508 DCU.
|
||||
Outputs a C header file with the EEPROM content as a byte array.
|
||||
|
||||
Layout per ETG.1000.6 section 5.4 "SII coding" (Table 16/17, verified against the
|
||||
local spec copy ETG1000_6_V1i0i3):
|
||||
|
||||
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
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
# ============================================================
|
||||
# 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 (ESC process RAM: mailbox 0x1000-0x13FF, PDO 0x1400-0x1FFF)
|
||||
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
|
||||
|
||||
# Process Data configuration
|
||||
#
|
||||
# 必须与 comm/ethercat_pdo_size.h 的 ECAT_PDO_OUT_BYTES / ECAT_PDO_IN_BYTES 一致
|
||||
# ——固件侧有 _Static_assert 守着,但 EEPROM 这一侧无法编译期校验,只能靠这里对齐。
|
||||
#
|
||||
# ★ 为何 out+in 最大只能是 1024 ★
|
||||
# 邮箱 SM0/SM1 各 256B 固定占 0x1000-0x13FF,过程数据区只剩 0x1400-0x1FFF=3072B。
|
||||
# SM2/SM3 都是 3-buffer 模式(control 0x64 / 0x20,bit1=0),实际占用 3×size:
|
||||
# SM2 384B -> 0x1400-0x187F (384×3 = 1152B)
|
||||
# SM3 600B -> 0x1880-0x1F67 (600×3 = 1800B), DPRAM 留 120B 余量
|
||||
# (384+600)×3 = 2952 ≤ 3072 = 过程数据区全长。
|
||||
# SSC 在 ecatslv.c:708-721 按 (start + size*buffer) 做重叠检查,撞了会在
|
||||
# PREOP->SAFEOP 返回 ALSTATUSCODE_INVALIDSMOUTCFG,不是静默出错。
|
||||
# 要再更大只能压邮箱(拖慢 SDO/FoE)或改 1-buffer(失去读写并发保护),见
|
||||
# comm/ethercat_pdo_size.h 的完整推导。
|
||||
PD_OUT_OFFSET = 0x1400 # SM2: RxPDO (Master -> Slave outputs)
|
||||
PD_OUT_SIZE = 384 # 48×64bit
|
||||
PD_IN_OFFSET = 0x1880 # SM3: TxPDO (Slave -> Master inputs)
|
||||
PD_IN_SIZE = 600 # 75×64bit
|
||||
# 批量接收为**定长覆盖窗口** 5 槽(每槽 12B 头含 u32 seqlock
|
||||
# + 32B 数据 = 44B,共 220B,落在 364..583),段5诊断区 584。
|
||||
# 5 槽 = 5000 帧/s@1ms。
|
||||
# SM3 实占 3×600=1800B (0x1880-0x1F67),out+in=984 ≤ 1024 硬上限,留 120B 余量;
|
||||
# 依据见 comm/ethercat_pdo_size.h。
|
||||
# 改后必须重烧 EEPROM 并断电冷启动。
|
||||
|
||||
# 3-buffer 占用与 DPRAM 上限自检:改尺寸/偏移时立刻在生成阶段拦住,
|
||||
# 而不是等烧进 EEPROM、冷启动后再看 AL 错误码。
|
||||
_PD_BUFFERS = 3
|
||||
assert PD_OUT_OFFSET >= MBX_TX_OFFSET + MBX_TX_SIZE, \
|
||||
"过程数据区起点压到了邮箱上(SSC ecatslv.c:708 会报 INVALIDSMINCFG)"
|
||||
assert PD_OUT_OFFSET + PD_OUT_SIZE * _PD_BUFFERS <= PD_IN_OFFSET, \
|
||||
"SM2 的 3 个缓冲会盖住 SM3,请把 PD_IN_OFFSET 往后挪"
|
||||
assert PD_IN_OFFSET + PD_IN_SIZE * _PD_BUFFERS - 1 <= 0x1FFF, \
|
||||
"SM3 超出 LAN9252 4KB DPRAM (0x1FFF)"
|
||||
assert PD_OUT_SIZE % 8 == 0 and PD_IN_SIZE % 8 == 0, \
|
||||
"PDO 尺寸必须是 8 的整数倍(映射条目是 64bit)"
|
||||
|
||||
# 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
|
||||
# Word 0 high byte = ESC Configuration register (0x0141): all zero
|
||||
# (0x0C would hit "NOT set by EEPROM" DC bits).
|
||||
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.
|
||||
# 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 # AT24C32
|
||||
EEPROM_KBIT = 32 # 4096 bytes = 32 KiBit
|
||||
|
||||
# ============================================================
|
||||
# Build EEPROM image
|
||||
# ============================================================
|
||||
eeprom = bytearray(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)
|
||||
|
||||
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
|
||||
|
||||
# --- 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)
|
||||
|
||||
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)
|
||||
|
||||
w16(32, 0x0000) # Reserved (words 0x10-0x13)
|
||||
w16(34, 0x0000)
|
||||
w16(36, 0x0000)
|
||||
w16(38, 0x0000)
|
||||
|
||||
w16(40, MBX_RX_OFFSET) # Bootstrap RX mailbox (words 0x14-0x15)
|
||||
w16(42, MBX_RX_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)
|
||||
|
||||
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)
|
||||
|
||||
# Checksum (word 0x07) over bytes 0..13 - must come after word 0-6 are final
|
||||
w16(14, sii_crc8(eeprom[0:14]))
|
||||
|
||||
# --- Categories start at word 0x40 = byte 128 ---
|
||||
cat = bytearray()
|
||||
|
||||
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)
|
||||
|
||||
# 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)
|
||||
append_category(10, bytes(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))
|
||||
|
||||
# FMMU (40): FMMU0=outputs(0x01), FMMU1=inputs(0x02)
|
||||
append_category(40, bytes([0x01, 0x02]))
|
||||
|
||||
# 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))
|
||||
|
||||
def append_pdo(cat_type, pdo_index, sm_index, obj_index, pd_size):
|
||||
"""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)].
|
||||
|
||||
nEntry 由过程数据长度推导(每条目 UNSIGNED64 = 8B),须与 dc_app.c 的
|
||||
PDO_OUT_ENTRIES / PDO_IN_ENTRIES 一致;写死 4 会在扩容后让主站按 32B 建映射,
|
||||
与从站 APPL_GenerateMapping 报的长度不符,SAFEOP 阶段报 AL 0x001A/0x0011。"""
|
||||
n_entry = pd_size // 8
|
||||
if not 1 <= n_entry <= 255:
|
||||
raise SystemExit(f"PDO 条目数 {n_entry} 超出 SII 单字节 nEntry 范围")
|
||||
data = bytearray()
|
||||
data.extend(struct.pack('<H', pdo_index))
|
||||
data.append(n_entry) # nEntry: 0x7000/0x6000:01..n (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, n_entry + 1):
|
||||
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))
|
||||
|
||||
append_pdo(51, 0x1600, 2, 0x7000, PD_OUT_SIZE) # RXPDO: outputs (SM2)
|
||||
append_pdo(50, 0x1A00, 3, 0x6000, PD_IN_SIZE) # TXPDO: inputs (SM3)
|
||||
|
||||
# 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.
|
||||
|
||||
# End of categories: word 0xFFFF (Table 19 "End")
|
||||
cat.extend(struct.pack('<H', 0xFFFF))
|
||||
|
||||
if len(cat) > EEPROM_SIZE - 128:
|
||||
raise SystemExit("category area overflow")
|
||||
eeprom[128:128 + len(cat)] = cat
|
||||
|
||||
# ============================================================
|
||||
# Self-check: re-parse the generated image
|
||||
# ============================================================
|
||||
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/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")
|
||||
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)
|
||||
if all(b == 0xFF for b in chunk):
|
||||
# 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},")
|
||||
|
||||
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,297 @@
|
||||
/**
|
||||
* @file hw_gd32exmc.cpp
|
||||
* @brief SSC V5.11 硬件 port 层 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
|
||||
*
|
||||
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
|
||||
* 转发到 EscLan9252 的间接访问端口:
|
||||
* - EtherCAT 核心 CSR(0x0000-0x0FFF:AL Status/Event、SyncManager、LED 等)
|
||||
* → ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300) 间接端口;
|
||||
* - 过程 RAM(0x1000-0x1FFF:邮箱 0x1000+、PDO 0x1400+)
|
||||
* → PRAM FIFO(0x000-0x03C 数据口 + 0x308-0x314 地址/命令)。
|
||||
* 核心 CSR 不在 HBI 直接空间,直读/直写会使核心 CSR 全部
|
||||
* 读 0/写丢弃,SSC 对主站 AL Control 完全失聪。
|
||||
*
|
||||
* 并发保护(不变):
|
||||
* - HW_EscRead/HW_EscWrite 等主循环函数:内部经 EscExmcBus::maskEnter/Exit
|
||||
* 屏蔽 EXTI0+EXTI1(访问端口是多步事务,不可被 ISR 打断)。
|
||||
* - HW_EscReadIsr/HW_EscWriteIsr (ISR):在 preempt=0 的 SYNC0 ISR 内调用,
|
||||
* 天然不会被抢占;PDI_Isr/Sync1_Isr(preempt=1) 由 ethercat_sync.cpp 包装器
|
||||
* 屏蔽 EXTI0 后调用。
|
||||
*/
|
||||
|
||||
/* SSC 配置与类型 */
|
||||
#define _EL9800HW_ 1
|
||||
#include "ecat_def.h"
|
||||
#include "esc.h"
|
||||
#include "el9800hw.h"
|
||||
#include "ecatslv.h"
|
||||
|
||||
/* 项目硬件驱动 */
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "sys/config.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
/* AL Event Mask 寄存器影子 (ESC 0x0204) */
|
||||
volatile uint16_t s_alEventMask = 0xFFFFU;
|
||||
|
||||
/* SSC 看门狗软件定时器 (DWT CYCCNT 基准) */
|
||||
volatile uint32_t s_timerBase = 0U;
|
||||
|
||||
constexpr uint16_t ESC_PRAM_BASE = 0x1000U; /**< 过程 RAM 起点(DS 12.11):≥此地址走 PRAM FIFO */
|
||||
|
||||
/** 从 ESC 地址读取 Len 字节到 pData(按地址自动路由 CSR 端口 / PRAM FIFO) */
|
||||
void escRead(MEM_ADDR *pData, uint16_t address, uint16_t len)
|
||||
{
|
||||
uint8_t *dst = (uint8_t *)pData;
|
||||
if (address >= ESC_PRAM_BASE) {
|
||||
EscLan9252::pramRead(address, dst, len);
|
||||
return;
|
||||
}
|
||||
/* CSR 端口:size 1 任意地址,size 2 需偶地址(DS 表 12-14) */
|
||||
uint16_t addr = address;
|
||||
uint16_t remaining = len;
|
||||
if ((addr & 1U) != 0U && remaining > 0U) {
|
||||
*dst++ = (uint8_t)EscLan9252::csrRead(addr, 1U);
|
||||
addr++;
|
||||
remaining--;
|
||||
}
|
||||
while (remaining >= 2U) {
|
||||
const uint16_t w = (uint16_t)EscLan9252::csrRead(addr, 2U);
|
||||
dst[0] = (uint8_t)(w & 0xFFU);
|
||||
dst[1] = (uint8_t)(w >> 8);
|
||||
dst += 2;
|
||||
addr += 2;
|
||||
remaining -= 2;
|
||||
}
|
||||
if (remaining > 0U) {
|
||||
*dst = (uint8_t)EscLan9252::csrRead(addr, 1U);
|
||||
}
|
||||
}
|
||||
|
||||
/** 向 ESC 地址写入 Len 字节(按地址自动路由 CSR 端口 / PRAM FIFO) */
|
||||
void escWrite(const MEM_ADDR *pData, uint16_t address, uint16_t len)
|
||||
{
|
||||
const uint8_t *src = (const uint8_t *)pData;
|
||||
if (address >= ESC_PRAM_BASE) {
|
||||
EscLan9252::pramWrite(address, src, len);
|
||||
return;
|
||||
}
|
||||
uint16_t addr = address;
|
||||
uint16_t remaining = len;
|
||||
if ((addr & 1U) != 0U && remaining > 0U) {
|
||||
EscLan9252::csrWrite(addr, *src, 1U);
|
||||
src++;
|
||||
addr++;
|
||||
remaining--;
|
||||
}
|
||||
while (remaining >= 2U) {
|
||||
const uint16_t w = (uint16_t)src[0] | ((uint16_t)src[1] << 8);
|
||||
EscLan9252::csrWrite(addr, w, 2U);
|
||||
src += 2;
|
||||
addr += 2;
|
||||
remaining -= 2;
|
||||
}
|
||||
if (remaining > 0U) {
|
||||
EscLan9252::csrWrite(addr, *src, 1U);
|
||||
}
|
||||
}
|
||||
|
||||
} /* 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 */
|
||||
s_alEventMask = (uint16_t)EscLan9252::csrRead(ESC_AL_EVENTMASK_OFFSET, 2U);
|
||||
|
||||
return 0; /* 0 = success */
|
||||
}
|
||||
|
||||
void HW_Release(void)
|
||||
{
|
||||
/* 无需释放资源 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取 AL Event 寄存器 (0x0220),主循环上下文。
|
||||
* @note 内部屏蔽 EXTI0+EXTI1 保证访问端口事务完整。
|
||||
*/
|
||||
UINT16 HW_GetALEventRegister(void)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
const UINT16 v = (UINT16)EscLan9252::csrRead(ESC_AL_EVENT_OFFSET, 2U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取 AL Event 寄存器 (0x0220),ISR 上下文。
|
||||
*/
|
||||
UINT16 HW_GetALEventRegister_Isr(void)
|
||||
{
|
||||
return (UINT16)EscLan9252::csrRead(ESC_AL_EVENT_OFFSET, 2U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除 AL Event Mask 中的指定位 (写 ESC 0x0204)。
|
||||
*/
|
||||
void HW_ResetALEventMask(UINT16 intMask)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
s_alEventMask &= ~intMask;
|
||||
EscLan9252::csrWrite(ESC_AL_EVENTMASK_OFFSET, s_alEventMask, 2U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 置位 AL Event Mask 中的指定位 (写 ESC 0x0204)。
|
||||
*/
|
||||
void HW_SetALEventMask(UINT16 intMask)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
s_alEventMask |= intMask;
|
||||
EscLan9252::csrWrite(ESC_AL_EVENTMASK_OFFSET, s_alEventMask, 2U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置 EtherCAT Run/Error LED。
|
||||
* @note LAN9252 支持 ESC 硬件驱动 LED (ESC_SUPPORT_ECAT_LED=1),
|
||||
* 通过寄存器 0x0138/0x0139 控制。若硬件未接 LED 则忽略。
|
||||
*/
|
||||
void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
/* Run LED override (0x0138): bit0 = LED on */
|
||||
EscLan9252::csrWrite(ESC_RUN_LED_OVERRIDE, RunLed ? 0x0001U : 0x0000U, 2U);
|
||||
/* Error LED override (0x0139): bit0 = LED on */
|
||||
EscLan9252::csrWrite(ESC_ERROR_LED_OVERRIDE, ErrLed ? 0x0001U : 0x0000U, 2U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* ESC 内存读写 — 主循环版本 (内部带 HBI 互斥守卫)
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
escRead(pData, Address, Len);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
escWrite(pData, Address, Len);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* ESC 内存读写 — ISR 版本 (preempt=0 内调用,无需额外保护)
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
void HW_EscReadIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
|
||||
{
|
||||
escRead(pData, Address, Len);
|
||||
}
|
||||
|
||||
void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
|
||||
{
|
||||
escWrite(pData, Address, Len);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* SyncManager 通道控制
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @brief 禁用指定 SM 通道 (清 SM Activate 寄存器 0x0806+8n bit0)。
|
||||
* @param channel SM 通道号 (0-15)
|
||||
* @note SM Activate 在偏移 +6(bit0=SM Enable);+4 是 SM Control(操作模式字段)。
|
||||
*/
|
||||
void HW_DisableSyncManChannel(UINT8 channel)
|
||||
{
|
||||
const uint16_t activateAddr = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
|
||||
+ (uint16_t)channel * SIZEOF_SM_REGISTER + 6U);
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
uint8_t val = (uint8_t)EscLan9252::csrRead(activateAddr, 1U);
|
||||
val &= (uint8_t)~0x01U; /* 清 bit0 = deactivate */
|
||||
EscLan9252::csrWrite(activateAddr, val, 1U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能指定 SM 通道 (置 SM Activate 寄存器 0x0806+8n bit0)。
|
||||
*/
|
||||
void HW_EnableSyncManChannel(UINT8 channel)
|
||||
{
|
||||
const uint16_t activateAddr = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
|
||||
+ (uint16_t)channel * SIZEOF_SM_REGISTER + 6U);
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
uint8_t val = (uint8_t)EscLan9252::csrRead(activateAddr, 1U);
|
||||
val |= 0x01U;
|
||||
EscLan9252::csrWrite(activateAddr, val, 1U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定 SM 通道的寄存器块指针。
|
||||
* @note SSC 用此函数读取 SM 配置 (StartAddr, Length, Control, Status, Activate)。
|
||||
* 访问是间接端口,无法返回真实指针;提供静态缓冲,每次调用时从 ESC 读入。
|
||||
*/
|
||||
static TSYNCMAN s_smBuf;
|
||||
|
||||
TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel)
|
||||
{
|
||||
const uint16_t smBase = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
|
||||
+ (uint16_t)channel * SIZEOF_SM_REGISTER);
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
escRead((MEM_ADDR *)&s_smBuf, smBase, SIZEOF_SM_REGISTER);
|
||||
EscExmcBus::maskExit(prev);
|
||||
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" */
|
||||
@@ -0,0 +1,799 @@
|
||||
/**
|
||||
\addtogroup CoE CAN Application Profile over EtherCAT
|
||||
@{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file coeappl.c
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief Implementation
|
||||
This file contains an example for CoE services and the CoE object dictionary
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.10:<br>
|
||||
V5.11 COE1: update invalid end entry in the object dictionaries (error with some compilers)<br>
|
||||
V5.11 COE3: change 0x10F3.2 (Sync Error limit) from UINT32 to UINT16 (according to the ETG.1020)<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
V5.11 ECAT4: enhance SM/Sync monitoring for input/output only slaves<br>
|
||||
<br>Changes to version V5.01:<br>
|
||||
V5.10 COE1: Define one entry description for all 0x1C3x objects and change data type of SI11,12,13 to UINT16 (according ETG.1020)<br>
|
||||
V5.10 COE2: Change return value of Get ObjectSize to UINT32<br>
|
||||
Change object size to UINT32<br>
|
||||
V5.10 ECAT13: Update Synchronisation handling (FreeRun,SM Sync, Sync0, Sync1)<br>
|
||||
Compare DC UINT configuration (by ESC Config data) vs. DC activation register settings<br>
|
||||
Update 0x1C3x entries<br>
|
||||
<br>Changes to version V5.0:<br>
|
||||
V5.01 APPL3: Include library demo application<br>
|
||||
V5.01 COE1: Remove alignment entry (SI33) from objects 0x1C32/0x1C33<br>
|
||||
V5.01 EL9800 1: Read/Write functions for setting object 0x8020 moved to el9800appl.c<br>
|
||||
V5.01 EL9800 2: Add TxPdo Parameter object 0x1802<br>
|
||||
V5.01 SDO6: Update SDO response interface handling. (used if the object access function returns "ABORTIDX_WORKING" and SDO_RES_INTERFACE is active)<br>
|
||||
<br>Changes to version V4.40:<br>
|
||||
V5.0 TEST1: Add test application. See Application Note ET9300 for more details.<br>
|
||||
V5.0 COE1: Add reserved Subindex7 to object 0x1C32 and 0x1C33.<br>
|
||||
V5.0 COE4: Create object dictionary on device startup.<br>
|
||||
V5.0 COE6: Update entry descriptions for object with the code ARRAY.<br>
|
||||
V5.0 ECAT2: Reference to application specific object dictionary is set to "ApplicationObjDic".<br>
|
||||
<br>Changes to version V4.30:<br>
|
||||
V4.40 COE 6: change dynamic object dictionary handling<br>
|
||||
V4.40 CiA402 2: enable freerun Sync Mode for CiA402 Device (The motion controller application will only be triggered if DC Synchronisation is disabled)<br>
|
||||
V4.40 COE5: move identification and synchronisation object values to ecat_def.h<br>
|
||||
V4.40 DIAG1: Add diagnosis object 0x10F3<br>
|
||||
V4.40 OBJ3: Update the global object dictionary pointer if a new list head is added<br>
|
||||
V4.40 COE4: Prevent access NULL pointer<br>
|
||||
V4.40 SYNC1: Initialize 0x1C3x objects<br>
|
||||
V4.40 HW1: Add (adapt) objects for FC1100 support<br>
|
||||
<br>Changes to version V4.20:<br>
|
||||
V4.30 OBJ 3: Handle object dictionary in double link list<br>
|
||||
V4.30 OBJ 2: General EtherCAT device objects are moved from chnappl.h to coeappl.c<br>
|
||||
V4.30 SYNC: set 0x1C32:05; 0x1C33:05 (minCyleTime) to MIN_PD_CYCLE_TIME
|
||||
(should be set dynamic if required)<br>
|
||||
V4.30 PDO: rename PDO specific function calls
|
||||
(PDO specific functions are moved to ecatappl.c)<br>
|
||||
V4.20 PIC24: Add EL9800_4 (PIC24) required source code<br>
|
||||
V4.11 ECAT 1: C166: MinDelayTime renamed in CalcAndCopyTime<br>
|
||||
<br>Changes to version V4.07:<br>
|
||||
V4.10 ECAT 1: To save and restore arrays in the object directory.<br>
|
||||
The code to get the size of an array entry was changed.<br>
|
||||
V4.10 COE 1: The SM-Parameter were extended<br>
|
||||
<br>Changes to version V4.06:<br>
|
||||
V4.07 COEAPPL 1: The PDO numbers were adapted according top the modular device profile,<br>
|
||||
the PDO numbers increment now with every module (in chnappl.h)<br>
|
||||
V4.07 COEAPPL 2: The example is working for the NIOS with the evaluation board DBC2C20 V1.2<br>
|
||||
which is available by Altera<br>
|
||||
V4.07 ECAT 1: The sources for SPI and MCI were merged (in ecat_def.h<br>
|
||||
set the switch MCI_HW to 1 when using the MCI,<br>
|
||||
set the switch SPI_HW to 1 when using the SPI<br>
|
||||
<br>Changes to version V4.03:<br>
|
||||
V4.04 SDO 1: The SDO interface was changed in that way that a SDO response<br>
|
||||
could be sent by the application to a later time. In that case<br>
|
||||
the functions OBJ_Read and OBJ_Write shall return the value<br>
|
||||
ABORTIDX_WORKING. To send the SDO response the new function SDOS_SdoRes<br>
|
||||
has to be called by the application. While waiting for the call<br>
|
||||
of SDOS_SdoRes the SDO interface will answer to another SDO request<br>
|
||||
with the error MBXERR_SERVICEINWORK in the mailbox protocol.<br>
|
||||
In this example the reading and writing of object 0x8020 is delayed,<br>
|
||||
the function SDOS_SdoRes will be called from COE_Main.<br>
|
||||
<br>Changes to version V4.00:<br>
|
||||
V4.01 COEAPPL 1: The object dictionary and the mapping functions are now defined<br>
|
||||
in chnappl.h (chnappl.inc made problems with some developing tools)<br>
|
||||
<br>Changes to version V3.20:<br>
|
||||
V4.00 COEAPPL 1: The object dictionary and the mapping functions are now defined<br>
|
||||
in chnappl.c and were adapted to the modular device profile<br>
|
||||
V4.00 COEAPPL 2: The handling of backup parameters was included according to<br>
|
||||
the EtherCAT Guidelines and Protocol Enhancements Specification<br>
|
||||
V4.00 COEAPPL 3: The handling of the TxPDO Toggle was included according to<br>
|
||||
the EtherCAT Guidelines and Protocol Enhancements Specification<br>
|
||||
V4.00 COEAPPL 4: The handling of the TxPDO State was included according to<br>
|
||||
the EtherCAT Guidelines and Protocol Enhancements Specification<br>
|
||||
V4.00 ECAT 1: The handling of the Sync Manager Parameter was included according to<br>
|
||||
the EtherCAT Guidelines and Protocol Enhancements Specification
|
||||
*/
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
#include "ecatslv.h"
|
||||
|
||||
#define _COEAPPL_ 1
|
||||
#include "coeappl.h"
|
||||
#undef _COEAPPL_
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
/*remove definition of _COEAPPL_ (#ifdef is used in coeappl.h)*/
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
|
||||
#define _OBJD_
|
||||
|
||||
/*Add Application specific Objects*/
|
||||
#include "el9800appl.h"
|
||||
|
||||
|
||||
#undef _OBJD_
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Backup Parameter
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Generic EtherCAT device object
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1000
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1000 (Device type) variable to handle the object data
|
||||
*/
|
||||
UINT32 u32Devicetype = (DEVICE_PROFILE_TYPE);
|
||||
|
||||
/**
|
||||
* \brief 0x1000 (Device type) entry description
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x1000 = {DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ};
|
||||
|
||||
/**
|
||||
* \brief 0x1000 (Device type) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1000[] = "Device type";
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1001
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1001 (Error Register) variable to handle the object data
|
||||
*/
|
||||
UINT16 u16ErrorRegister = 0x0;
|
||||
|
||||
/**
|
||||
* \brief 0x1001 (Error Register) entry description
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x1001 = {DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ};
|
||||
|
||||
/**
|
||||
* \brief 0x1001 (Error Register) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1001[] = "Error register";
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1008
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1008 (Device name) variable to handle the object data
|
||||
*/
|
||||
CHAR acDevicename[] = DEVICE_NAME;
|
||||
|
||||
/**
|
||||
* \brief 0x1008 (Device name) entry description
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x1008 = {DEFTYPE_VISIBLESTRING, BYTE2BIT(DEVICE_NAME_LEN), ACCESS_READ};
|
||||
|
||||
/**
|
||||
* \brief 0x1008 (Device name) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1008[] = "Device name";
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1009
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1009 (Hardware version) variable to handle the object data
|
||||
*/
|
||||
CHAR acHardwareversion[] = DEVICE_HW_VERSION;
|
||||
|
||||
/**
|
||||
* \brief 0x1009 (Hardware version) entry description
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x1009 = {DEFTYPE_VISIBLESTRING,BYTE2BIT(DEVICE_HW_VERSION_LEN), ACCESS_READ};
|
||||
|
||||
/**
|
||||
* \brief 0x1009 (Hardware version) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1009[] = "Hardware version";
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x100A
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x100A (Software version) variable to handle the object data
|
||||
*
|
||||
* 必须显式给到 DEVICE_SW_VERSION_LEN+1:条目描述符按 LEN 声明位长,SSC 会照长度
|
||||
* 整块回给主站;用 =DEVICE_SW_VERSION 推出来只有 4 字节,读到的是越界内容。
|
||||
* 真实版本号由 sys/initial.cpp 起板时调 AppSetSoftwareVersion() 覆盖。
|
||||
*/
|
||||
CHAR acSoftwareversion[DEVICE_SW_VERSION_LEN + 1] = DEVICE_SW_VERSION;
|
||||
|
||||
/** @brief 覆盖 0x100A 上报的软件版本串(容量只有本文件知道,故 setter 放这儿)。 */
|
||||
void AppSetSoftwareVersion(const char *version)
|
||||
{
|
||||
unsigned int i = 0U;
|
||||
|
||||
if (version == NULL) {
|
||||
return;
|
||||
}
|
||||
/* 本文件没 include stdio,手写有界拷贝:超长就截断,末尾必带 NUL。 */
|
||||
for (; i + 1U < sizeof(acSoftwareversion) && version[i] != '\0'; i++) {
|
||||
acSoftwareversion[i] = version[i];
|
||||
}
|
||||
acSoftwareversion[i] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief 0x1009 (Hardware version) entry description
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM sEntryDesc0x100A = {DEFTYPE_VISIBLESTRING,BYTE2BIT(DEVICE_SW_VERSION_LEN), ACCESS_READ};
|
||||
|
||||
/**
|
||||
* \brief 0x1009 (Hardware version) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x100A[] = "Software version";
|
||||
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1018
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1018 (Identity) object structure
|
||||
*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0; /**< \brief SubIndex 0*/
|
||||
UINT32 u32VendorID; /**< \brief EtherCAT vendor ID */
|
||||
UINT32 u32Productcode; /**< \brief Product code*/
|
||||
UINT32 u32Revision; /**< \brief Revision number*/
|
||||
UINT32 u32Serialnumber; /**< \brief Serial number*/
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ1018;
|
||||
|
||||
/**
|
||||
* \brief 0x1018 (Identity) variable to handle the object data
|
||||
*/
|
||||
TOBJ1018 sIdentity = {4, (VENDOR_ID), (PRODUCT_CODE), (REVISION_NUMBER), (SERIAL_NUMBER)};
|
||||
|
||||
/**
|
||||
* \brief 0x1018 (Identity) entry descriptions
|
||||
*
|
||||
* Subindex 000
|
||||
* SubIndex 001: Vendor ID
|
||||
* SubIndex 002: Product code
|
||||
* SubIndex 003: Revision
|
||||
* SubIndex 004: Serial number
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asEntryDesc0x1018[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x8, ACCESS_READ },
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}};
|
||||
|
||||
/**
|
||||
* \brief 0x1018 (Identity) object and entry names
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1018[] = "Identity\000Vendor ID\000Product code\000Revision\000Serial number\000\377";
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x10F1
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x10F1 (Error settings) entry description
|
||||
* Subindex 000
|
||||
* SubIndex 001: Local Error Reaction
|
||||
* SubIndex 002: Sync Error Counter Limit
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asEntryDesc0x10F1[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x8, ACCESS_READ },
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE},
|
||||
/*ECATCHANGE_START(V5.11) COE3*/
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READWRITE}};
|
||||
/*ECATCHANGE_END(V5.11) COE3*/
|
||||
|
||||
/**
|
||||
* \brief 0x10F1 (Error settings) object and entry names
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x10F1[] = "Error Settings\000Local Error Reaction\000Sync Error Counter Limit\000\377";
|
||||
|
||||
|
||||
//object declaration and initialization in objdef.h
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1C00
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1C00 (Sync manager type) object structure
|
||||
*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0; /**< \brief SubIndex 0*/
|
||||
UINT8 aEntries[4]; /**< \brief Entry buffer (one for each SyncManager)*/
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ1C00;
|
||||
|
||||
/**
|
||||
* \brief 0x1C00 (Sync manager type) variable to handle the object data
|
||||
*
|
||||
* SI 0 : 4 (4 SyncManagers available)
|
||||
* SI1 (SM0): Mailbox Out (0x1)
|
||||
* SI2 (SM1): Mailbox In (0x2)
|
||||
* SI3 (SM2): Process data Out (0x3)
|
||||
* SI4 (SM3): Process data In (0x4)
|
||||
*
|
||||
*/
|
||||
TOBJ1C00 sSyncmanagertype = {0x04, {0x01, 0x02, 0x03, 0x04}};
|
||||
|
||||
|
||||
/**
|
||||
* \brief 0x1C00 (Sync manager type) entry descriptions
|
||||
*
|
||||
* The object is an array so only the first entry is described (which is used for all entries).
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asEntryDesc0x1C00[2] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}};
|
||||
|
||||
/**
|
||||
* \brief 0x1C00 (Sync manager type) object name
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1C00[] = "Sync manager type";
|
||||
|
||||
|
||||
/*---------------------------------------------
|
||||
- 0x1C3x
|
||||
-----------------------------------------------*/
|
||||
/**
|
||||
* \brief 0x1C3x (SyncManager parameter) entry description
|
||||
*
|
||||
* The entry descriptions are used for 0x1C32 and 0x1C33
|
||||
*/
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asEntryDesc0x1C3x[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x8, ACCESS_READ }, /* Subindex 000 */
|
||||
{DEFTYPE_UNSIGNED16, 0x10, (ACCESS_READ | ACCESS_WRITE_PREOP)}, /* SubIndex 001: Synchronization Type */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* SubIndex 002: Cycle Time */
|
||||
{0x0000, 0x20, 0}, /* SubIndex 003: Shift Time (not supported)*/
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}, /* SubIndex 004: Synchronization Types supported */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* SubIndex 005: Minimum Cycle Time */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* SubIndex 006: Calc and Copy Time */
|
||||
{0x0000, 0x20, 0}, /* Subindex 007 Minimum Delay Time (not supported)*/
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READWRITE}, /* SubIndex 008: Get Cycle Time */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* SubIndex 009: Delay Time */
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READWRITE}, /* SubIndex 010: Sync0 Cycle Time */
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}, /* SubIndex 011: SM-Event Missed */
|
||||
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}, /* SubIndex 012: Cycle Time Too Small */
|
||||
{0x0000, 0x10, 0}, /* SubIndex 013: Shift Too Short Counter (not supported)*/
|
||||
{0x0000, 0x10, 0}, /* Subindex 014: RxPDO Toggle Failed (not supported)*/
|
||||
{0x0000, 0x20, 0}, /* Subindex 015: Minimum Cycle Distance (not supported)*/
|
||||
{0x0000, 0x20, 0}, /* Subindex 016: Maximum Cycle Distance (not supported)*/
|
||||
{0x0000, 0x20, 0}, /* Subindex 017: Minimum SM Sync Distance (not supported)*/
|
||||
{0x0000, 0x20, 0}, /* Subindex 018: Maximum SM Sync Distance (not supported)*/
|
||||
{0x0000, 0, 0}, /* Subindex 019 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 020 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 021 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 022 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 023 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 024 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 025 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 026 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 027 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 028 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 029 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 030 doesn't exist */
|
||||
{0x0000, 0, 0}, /* Subindex 031 doesn't exist */
|
||||
{DEFTYPE_BOOLEAN, 0x01, ACCESS_READ}}; /* SubIndex 032: Sync error */
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
/**
|
||||
* \brief 0x1C32 (SyncManager 2 parameter) object and entry names
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1C32[] = "SM output parameter\000Synchronization Type\000Cycle Time\000\000Synchronization Types supported\000Minimum Cycle Time\000Calc and Copy Time\000\000Get Cycle Time\000Delay Time\000Sync0 Cycle Time\000SM-Event Missed\000Cycle Time Too Small\000Shift Time Too Short\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000Sync Error\000\377";
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
/**
|
||||
* \brief 0x1C33 (SyncManager 3 parameter) object and entry names
|
||||
*/
|
||||
OBJCONST UCHAR OBJMEM aName0x1C33[] = "SM input parameter\000Synchronization Type\000Cycle Time\000\000Synchronization Types supported\000Minimum Cycle Time\000Calc and Copy Time\000\000Get Cycle Time\000Delay Time\000Sync0 Cycle Time\000SM-Event Missed\000Cycle Time Too Small\000Shift Time Too Short\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000Sync Error\000\377";
|
||||
|
||||
/******************************************************************************
|
||||
** Object Dictionary
|
||||
******************************************************************************/
|
||||
/**
|
||||
* \brief Object dictionary pointer
|
||||
*/
|
||||
TOBJECT OBJMEM * ObjDicList = NULL;
|
||||
|
||||
/**
|
||||
* \brief List of generic application independent objects
|
||||
*/
|
||||
TOBJECT OBJMEM GenObjDic[] = {
|
||||
/* Object 0x1000 */
|
||||
{NULL,NULL, 0x1000, {DEFTYPE_UNSIGNED32, 0 | (OBJCODE_VAR << 8)}, &sEntryDesc0x1000, aName0x1000, &u32Devicetype, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1001 */
|
||||
{NULL,NULL, 0x1001, {DEFTYPE_UNSIGNED8, 0 | (OBJCODE_VAR << 8)}, &sEntryDesc0x1001, aName0x1001, &u16ErrorRegister, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1008 */
|
||||
{NULL,NULL, 0x1008, {DEFTYPE_VISIBLESTRING, 0 | (OBJCODE_VAR << 8)}, &sEntryDesc0x1008, aName0x1008, acDevicename, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1009 */
|
||||
{NULL,NULL, 0x1009, {DEFTYPE_VISIBLESTRING, 0 | (OBJCODE_VAR << 8)}, &sEntryDesc0x1009, aName0x1009, acHardwareversion, NULL, NULL, 0x0000 },
|
||||
/* Object 0x100A */
|
||||
{NULL,NULL, 0x100A, {DEFTYPE_VISIBLESTRING, 0 | (OBJCODE_VAR << 8)}, &sEntryDesc0x100A, aName0x100A, acSoftwareversion, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1018 */
|
||||
{NULL,NULL, 0x1018, {DEFTYPE_IDENTITY, 4 | (OBJCODE_REC << 8)}, asEntryDesc0x1018, aName0x1018, &sIdentity, NULL, NULL, 0x0000 },
|
||||
/* Object 0x10F1 */
|
||||
{NULL,NULL, 0x10F1, {DEFTYPE_RECORD, 2 | (OBJCODE_REC << 8)}, asEntryDesc0x10F1, aName0x10F1, &sErrorSettings, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1C00 */
|
||||
{NULL,NULL, 0x1C00, {DEFTYPE_UNSIGNED8, 4 | (OBJCODE_ARR << 8)}, asEntryDesc0x1C00, aName0x1C00, &sSyncmanagertype, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1C32 */
|
||||
{NULL,NULL, 0x1C32, {DEFTYPE_SMPAR, 32 | (OBJCODE_REC << 8)}, asEntryDesc0x1C3x, aName0x1C32, &sSyncManOutPar, NULL, NULL, 0x0000 },
|
||||
/* Object 0x1C33 */
|
||||
{NULL,NULL, 0x1C33, {DEFTYPE_SMPAR, 32 | (OBJCODE_REC << 8)}, asEntryDesc0x1C3x, aName0x1C33, &sSyncManInPar, NULL, NULL, 0x0000 },
|
||||
|
||||
/*end of entries*/
|
||||
/*ECATCHANGE_START(V5.11) COE1*/
|
||||
{NULL,NULL, 0xFFFF, {0, 0}, NULL, NULL, NULL, NULL, NULL, 0x000}};
|
||||
/*ECATCHANGE_END(V5.11) COE1*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief returns the pointer to the object dictionary
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
OBJCONST TOBJECT OBJMEM * COE_GetObjectDictionary(void)
|
||||
{
|
||||
return (OBJCONST TOBJECT OBJMEM *) ObjDicList;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function initialize the several objects
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void COE_ObjInit(void)
|
||||
{
|
||||
|
||||
/* initialize the Sync Manager Output parameter object 0x1C32 */
|
||||
|
||||
sSyncManOutPar.subindex0 = 32;
|
||||
/*
|
||||
subindex 1 contains the actual synchronization mode, it could be written
|
||||
from the master to switch between ECAT FreeRun and ECAT Synchron Mode
|
||||
if the slave supports both modes,
|
||||
in DC mode (selected by the DC registers) this value will be overwritten
|
||||
with SYNCTYPE_DCSYNC0 or SYNCTYPE_DCSYNC1 */
|
||||
/*default mode is ECAT Synchron Mode */
|
||||
sSyncManOutPar.u16SyncType = SYNCTYPE_FREERUN;
|
||||
|
||||
/* subindex 2 contains the cycle time of the application,
|
||||
in ECAT FreeRun mode it could be used for a timer interrupt to run the application,
|
||||
in ECAT Synchron mode it could be written from the master with its local cycle time
|
||||
that the slave can check if this cycle time is supported,
|
||||
in DC Mode this value will be overwritten with the DC cycle time register */
|
||||
sSyncManOutPar.u32CycleTime = 0;
|
||||
/* only for DC Mode important: the subindex 3 contains the time shift between the
|
||||
SYNC0 (SYNC1) signal and when the outputs are put to the hardware to allow the
|
||||
master a very exactly calculation of delay times*/
|
||||
sSyncManOutPar.u32ShiftTime = 0;
|
||||
|
||||
/* the subindex 4 contains the supported synchronization types */
|
||||
|
||||
sSyncManOutPar.u16SyncTypesSupported = SYNCTYPE_FREERUNSUPP /* ECAT FreeRun Mode is supported */
|
||||
| SYNCTYPE_TIMESVARIABLE /* the execution times depend on the connected modules */
|
||||
| SYNCTYPE_SYNCHRONSUPP /* ECAT Synchron Mode is supported */
|
||||
| SYNCTYPE_DCSYNC0SUPP /* DC Sync0 Mode is supported */
|
||||
| SYNCTYPE_DCSYNC1SUPP /* DC Sync1 Mode is supported */
|
||||
| SYNCTYPE_SUBCYCLESUPP /*Subordinated application cycles supported*/
|
||||
;
|
||||
|
||||
/* subindex 5 contains the minimum cycle time the slave is able to support,
|
||||
will be calculated dynamically because it depends on the connected modules
|
||||
(in this example we will make an online measurement in the ESC Interrupt Routine).
|
||||
For the sample application this value is set to MIN_PD_CYCLE_TIME */
|
||||
sSyncManOutPar.u32MinCycleTime = MIN_PD_CYCLE_TIME;
|
||||
/* only for DC Mode important: subindex 6 contains the minimum delay time the slave
|
||||
needs after receiving the SM2-event before the SYNC0(SYNC1) can be received without delays
|
||||
will be calculated dynamically because it depends on the connected modules
|
||||
(in this example we will make an online measurement in the ESC Interrupt Routine) */
|
||||
sSyncManOutPar.u32CalcAndCopyTime = (PD_OUTPUT_CALC_AND_COPY_TIME);
|
||||
|
||||
/*subindex 8: trigger cycle time measurement*/
|
||||
sSyncManOutPar.u16GetCycleTime = 0;
|
||||
|
||||
/*subindex 9: time from start driving outputs until outputs are valid*/
|
||||
sSyncManOutPar.u32DelayTime = (PD_OUTPUT_DELAY_TIME);
|
||||
|
||||
/*subindex 32: indicates if a synchronisation error has occurred*/
|
||||
sSyncManOutPar.u8SyncError = 0;
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
/* initialize the Sync Manager Input parameter object 0x1C33 */
|
||||
sSyncManInPar.subindex0 = 32;
|
||||
/* default mode is ECAT Synchron Mode, if output size > 0 the inputs are updated with the SM2-event */
|
||||
sSyncManInPar.u16SyncType = SYNCTYPE_FREERUN;
|
||||
|
||||
/* subindex 2: same as 0x1C32:02 */
|
||||
sSyncManInPar.u32CycleTime = sSyncManOutPar.u32CycleTime;
|
||||
/* only for DC Mode important: subindex 3 contains the time shift between the
|
||||
SYNC0 (SYNC1) signal and when the inputs are got to the hardware to allow the
|
||||
master a very exactly calculation of delay times,
|
||||
will be calculated dynamically because it depends on the connected modules
|
||||
(in this example we will make an online measurement in the ESC Interrupt Routine) */
|
||||
sSyncManInPar.u32ShiftTime = 0;
|
||||
/* subindex 4: same as 0x1C32:04 */
|
||||
sSyncManInPar.u16SyncTypesSupported = sSyncManOutPar.u16SyncTypesSupported;
|
||||
|
||||
/* subindex 5: same as 0x1C32:05 */
|
||||
sSyncManInPar.u32MinCycleTime = MIN_PD_CYCLE_TIME;
|
||||
/* subindex 6: delay read inputs, calculation and copy to SM buffer*/
|
||||
sSyncManInPar.u32CalcAndCopyTime = (PD_INPUT_CALC_AND_COPY_TIME);
|
||||
/*subindex 8: trigger cycle time measurement*/
|
||||
sSyncManInPar.u16GetCycleTime = 0;
|
||||
/*subindex 9: delay to prepare input latch*/
|
||||
sSyncManInPar.u32DelayTime = (PD_INPUT_DELAY_TIME);
|
||||
|
||||
/*subindex 32: incremented if a synchronisation error has occurred*/
|
||||
sSyncManInPar.u8SyncError = 0;
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
|
||||
/*Indicate no user specified Sync mode*/
|
||||
bSyncSetByUser = FALSE;
|
||||
|
||||
{
|
||||
UINT16 result = COE_ObjDictionaryInit();
|
||||
if(result != 0)
|
||||
{
|
||||
/*clear already linked objects*/
|
||||
COE_ClearObjDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
u8PendingSdo = 0;
|
||||
bStoreCompleteAccess = FALSE;
|
||||
u16StoreIndex = 0;
|
||||
u8StoreSubindex = 0;
|
||||
u32StoreDataSize = 0;
|
||||
pStoreData = NULL;
|
||||
pSdoPendFunc = NULL;
|
||||
|
||||
pSdoSegData = NULL;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 object successful added to object dictionary
|
||||
ALSTATUSCODE_XX add object failed
|
||||
|
||||
\brief This function adds an object to the object dictionary
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
UINT16 COE_AddObjectToDic(TOBJECT OBJMEM * pNewObjEntry)
|
||||
{
|
||||
if(pNewObjEntry != NULL)
|
||||
{
|
||||
if(ObjDicList == NULL)
|
||||
{
|
||||
/* Object dictionary is empty */
|
||||
ObjDicList = pNewObjEntry;
|
||||
ObjDicList->pNext = NULL;
|
||||
ObjDicList->pPrev = NULL;
|
||||
return 0;
|
||||
}
|
||||
else if(ObjDicList->Index > pNewObjEntry->Index)
|
||||
{
|
||||
/*insert new object dictionary head*/
|
||||
pNewObjEntry->pPrev = NULL;
|
||||
pNewObjEntry->pNext = ObjDicList;
|
||||
ObjDicList->pPrev = pNewObjEntry;
|
||||
ObjDicList = pNewObjEntry;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
TOBJECT OBJMEM * pDicEntry = ObjDicList;
|
||||
while(pDicEntry != NULL)
|
||||
{
|
||||
if(pDicEntry->Index == pNewObjEntry->Index)
|
||||
{
|
||||
/*object already exists in object dictionary*/
|
||||
return ALSTATUSCODE_UNSPECIFIEDERROR;
|
||||
}
|
||||
else if(pDicEntry->Index > pNewObjEntry->Index)
|
||||
{
|
||||
pNewObjEntry->pPrev = pDicEntry->pPrev;
|
||||
pNewObjEntry->pNext = pDicEntry;
|
||||
|
||||
if(pDicEntry->pPrev != NULL)
|
||||
pDicEntry->pPrev->pNext = pNewObjEntry;
|
||||
|
||||
pDicEntry->pPrev = pNewObjEntry;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if(pDicEntry->pNext == NULL)
|
||||
{
|
||||
/*Last entry reached => add object to list tail*/
|
||||
pDicEntry->pNext = pNewObjEntry;
|
||||
pNewObjEntry->pPrev = pDicEntry;
|
||||
pNewObjEntry->pNext = NULL;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*The new object index is smaller than the current index. Get next object handle.*/
|
||||
pDicEntry = pDicEntry->pNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ALSTATUSCODE_UNSPECIFIEDERROR;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function removes an object to the object dictionary
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void COE_RemoveDicEntry(UINT16 index)
|
||||
{
|
||||
TOBJECT OBJMEM * pDicEntry = ObjDicList;
|
||||
|
||||
while(pDicEntry != NULL)
|
||||
{
|
||||
if(pDicEntry->Index == index)
|
||||
{
|
||||
TOBJECT OBJMEM *pPrevEntry = pDicEntry->pPrev;
|
||||
TOBJECT OBJMEM *pNextEntry = pDicEntry->pNext;
|
||||
|
||||
if(pPrevEntry != NULL)
|
||||
pPrevEntry->pNext = pNextEntry;
|
||||
|
||||
if(pNextEntry != NULL)
|
||||
pNextEntry->pPrev = pPrevEntry;
|
||||
|
||||
pDicEntry->pPrev = NULL;
|
||||
pDicEntry->pNext = NULL;
|
||||
/*Update Object dictionary pointer if list head was removed*/
|
||||
if(pDicEntry->Index == ObjDicList->Index)
|
||||
{
|
||||
ObjDicList = pNextEntry;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pDicEntry = pDicEntry->pNext;
|
||||
}
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function clear the object dictionary
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void COE_ClearObjDictionary(void)
|
||||
{
|
||||
TOBJECT OBJMEM * pObjEntry = (TOBJECT OBJMEM *) ObjDicList;
|
||||
UINT16 Index = 0;
|
||||
|
||||
while(pObjEntry != NULL)
|
||||
{
|
||||
Index = pObjEntry->Index;
|
||||
pObjEntry = pObjEntry->pNext;
|
||||
|
||||
COE_RemoveDicEntry(Index);
|
||||
}
|
||||
ObjDicList = NULL;
|
||||
}
|
||||
|
||||
|
||||
UINT16 AddObjectsToObjDictionary(TOBJECT OBJMEM * pObjEntry)
|
||||
{
|
||||
UINT16 result = 0;
|
||||
TOBJECT OBJMEM * pEntry = (TOBJECT OBJMEM *)pObjEntry;
|
||||
|
||||
while(pEntry->Index != 0xFFFF)
|
||||
{
|
||||
result = COE_AddObjectToDic(pEntry);
|
||||
|
||||
if(result != 0)
|
||||
return result;
|
||||
|
||||
pEntry++;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\return 0 object dictionary created successful
|
||||
ALSTATUSCODE_XX create object dictionary failed
|
||||
|
||||
\brief This function initialize the object dictionary
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
UINT16 COE_ObjDictionaryInit(void)
|
||||
{
|
||||
UINT16 result = 0;
|
||||
|
||||
/*Reset object dictionary pointer*/
|
||||
ObjDicList = NULL;
|
||||
|
||||
result = AddObjectsToObjDictionary((TOBJECT OBJMEM *) GenObjDic);
|
||||
|
||||
if(result != 0)
|
||||
return result;
|
||||
if(ApplicationObjDic != NULL)
|
||||
{
|
||||
result = AddObjectsToObjDictionary((TOBJECT OBJMEM *) ApplicationObjDic);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief is called for background calculations which should not influence the
|
||||
ECAT_Application in synchronous modes
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void COE_Main(void)
|
||||
{
|
||||
UINT8 abort = 0;
|
||||
if(pSdoPendFunc != NULL)
|
||||
{
|
||||
abort = pSdoPendFunc(u16StoreIndex,u8StoreSubindex,u32StoreDataSize,pStoreData,bStoreCompleteAccess);
|
||||
|
||||
if(abort != ABORTIDX_WORKING)
|
||||
{
|
||||
switch(u8PendingSdo)
|
||||
{
|
||||
case SDO_PENDING_SEG_WRITE:
|
||||
if(pSdoSegData)
|
||||
{
|
||||
/* the allocated buffer can be released */
|
||||
FREEMEM( (UINT16 VARMEM *) pSdoSegData );
|
||||
pSdoSegData = NULL;
|
||||
}
|
||||
case SDO_PENDING_WRITE:
|
||||
/*send SDO Download Response*/
|
||||
SDOS_SdoRes(abort, 0, NULL);
|
||||
break;
|
||||
|
||||
case SDO_PENDING_SEG_READ:
|
||||
case SDO_PENDING_READ:
|
||||
/* send SDO upload response */
|
||||
SDOS_SdoRes(abort, u32StoreDataSize, pStoreData);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
u8PendingSdo = 0;
|
||||
u16StoreIndex = 0;
|
||||
u8StoreSubindex = 0;
|
||||
u32StoreDataSize = 0;
|
||||
pStoreData = NULL;
|
||||
bStoreCompleteAccess = 0;
|
||||
pSdoPendFunc = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
\addtogroup EcatAppl EtherCAT application
|
||||
@{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
\file ecatappl.c
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief Implementation
|
||||
This file contains the Process Data interface
|
||||
|
||||
\version 5.11
|
||||
*/
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
#include "ecatslv.h"
|
||||
|
||||
#define _ECATAPPL_ 1
|
||||
#include "ecatappl.h"
|
||||
#undef _ECATAPPL_
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
/*remove definition of _ECATAPPL_ (#ifdef is used in ecatappl.h)*/
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
|
||||
#include "coeappl.h"
|
||||
|
||||
|
||||
/* ECATCHANGE_START(V5.11) ECAT11*/
|
||||
#define _APPL_INTERFACE_ 1
|
||||
#include "applInterface.h"
|
||||
#undef _APPL_INTERFACE_
|
||||
/* ECATCHANGE_END(V5.11) ECAT11*/
|
||||
|
||||
#include "el9800appl.h"
|
||||
|
||||
|
||||
|
||||
/*--------------------------------------------------------------------------------------
|
||||
------
|
||||
------ local Types and Defines
|
||||
------
|
||||
--------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
#ifndef ECAT_TIMER_INC_P_MS
|
||||
/**
|
||||
* \todo Define the timer ticks per ms
|
||||
*/
|
||||
#warning "Define the timer ticks per ms"
|
||||
#endif /* #ifndef ECAT_TIMER_INC_P_MS */
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ local variables and constants
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/*variables only required to calculate values for SM Synchronisation objects (0x1C3x)*/
|
||||
UINT16 u16BusCycleCntMs; //used to calculate the bus cycle time in Ms
|
||||
UINT32 StartTimerCnt; //variable to store the timer register value when get cycle time was triggered
|
||||
BOOL bCycleTimeMeasurementStarted; // indicates if the bus cycle measurement is started
|
||||
|
||||
UINT16 aPdOutputData[(MAX_PD_OUTPUT_SIZE>>1)];
|
||||
UINT16 aPdInputData[(MAX_PD_INPUT_SIZE>>1)];
|
||||
|
||||
/*ECAT_DBG_START(剖分埋点)*/
|
||||
/* 负载归因埋点:DWT 周期累加器,经 0x2000 sub5..8 上报,主站 1s 差分出速率。
|
||||
* EcatDbg_Dwt() 定义在 comm/ethercat_sync.cpp (extern "C")。 */
|
||||
extern UINT32 EcatDbg_Dwt(void);
|
||||
#define ecatdbg_dwt() EcatDbg_Dwt()
|
||||
/* CPU 负载挂钩(sys/cpu_load.cpp) */
|
||||
extern void CpuLoad_Enter(void);
|
||||
extern void CpuLoad_Leave(void);
|
||||
/* 1ms 调度节拍(ethercat_sync.cpp extern "C") */
|
||||
extern UINT32 EcatDbg_GetTick1ms(void);
|
||||
/* 输出探针(实现见 comm/ethercat_pdo.cpp,C 链接) */
|
||||
extern UINT8 EcatPdo_OutputProbe(UINT8 const *head);
|
||||
extern void EcatPdo_NoteOutputMapped(void);
|
||||
/* 稀疏写 ESC 计划(密排 count 只写有效段; 实现见 ethercat_pdo.cpp) */
|
||||
extern UINT8 EcatPdo_SparseEscPlan(UINT16 *off0, UINT16 *len0, UINT16 *off1, UINT16 *len1);
|
||||
/* 差分写 ESC 多段计划(段头/段尾 + 3-buffer 冲刷; 实现见 ethercat_pdo.cpp) */
|
||||
extern UINT8 EcatPdo_EscWritePlan(UINT16 *offs, UINT16 *lens, UINT8 maxSegs);
|
||||
volatile UINT32 g_dbg_pdiCyc = 0; /* sub5: PDI_Isr 全函数累计周期 */
|
||||
volatile UINT32 g_dbg_outCyc = 0; /* sub6: PDO_OutputMapping(输出探针/全量读) */
|
||||
volatile UINT32 g_dbg_freerunCyc = 0; /* sub7: MainLoop free-run 分支(SM 同步下恒 0) */
|
||||
volatile UINT32 g_dbg_pdiCnt = 0; /* sub8: PDI_Isr 触发次数(=每周期 1 次) */
|
||||
volatile UINT32 g_dbg_inCyc = 0; /* sub9: PDO_InputMapping 全程(APPL+过程数据写) */
|
||||
volatile UINT32 g_dbg_inWriteCyc = 0; /* sub10: 其中 HW_EscWriteIsr 640B 过程数据写 */
|
||||
volatile UINT32 g_dbg_alCyc = 0; /* sub11: PDI_Isr 内 AL 事件读×2+超限应答 */
|
||||
volatile UINT32 g_dbg_appCyc = 0; /* sub12: ECAT_Application(桥 processOutput) */
|
||||
/*ECAT_DBG_END*/
|
||||
|
||||
/*variables are declared in ecatslv.c*/
|
||||
extern VARVOLATILE UINT16 u16dummy;
|
||||
BOOL bInitFinished = FALSE; /** < \brief indicates if the initialization is finished*/
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ local functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Functions
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function will copies the inputs from the local memory to the ESC memory
|
||||
to the hardware
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PDO_InputMapping(void)
|
||||
{
|
||||
/*ECAT_DBG(剖分:输入映射全程 + 过程数据写细分)*/
|
||||
/* 稀疏写:APPL 仍整填 aPdInputData(对齐 SDO 0x6000),但 HW_EscWriteIsr
|
||||
* 只写密排有效段+段3/5,跳过空槽区 —— 省的是 PRAM 字节时间,不是 RAM 拷贝。
|
||||
* 差分写:在稀疏之上再跳过「与影子一致且 3-buffer 冲刷完成」的段。 */
|
||||
const UINT32 dbgT0 = ecatdbg_dwt();
|
||||
APPL_InputMapping((UINT16*)aPdInputData);
|
||||
const UINT32 dbgT1 = ecatdbg_dwt();
|
||||
{
|
||||
UINT16 offs[4], lens[4];
|
||||
const UINT8 nseg = EcatPdo_EscWritePlan(offs, lens, 4U);
|
||||
if (nseg == 0U) {
|
||||
HW_EscWriteIsr(((MEM_ADDR *) aPdInputData), nEscAddrInputData, nPdInputSize );
|
||||
} else {
|
||||
for (UINT8 i = 0U; i < nseg; ++i) {
|
||||
HW_EscWriteIsr(((MEM_ADDR *)((UINT8 *)aPdInputData + offs[i])),
|
||||
(UINT16)(nEscAddrInputData + offs[i]), lens[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_dbg_inWriteCyc += ecatdbg_dwt() - dbgT1;
|
||||
g_dbg_inCyc += ecatdbg_dwt() - dbgT0;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function will copies the outputs from the ESC memory to the local memory
|
||||
to the hardware. This function is only called in case of an SM2
|
||||
(output process data) event.
|
||||
|
||||
输出按需全量读:每周期先读输出 PDO 头 12B 探针(覆盖 SEQ@0
|
||||
与 WIN@8),经 EcatPdo_OutputProbe 判定——seq 无变化沿且无清统计请求时,
|
||||
跳过全量读(只补读 SM2 末字完成事件应答)。主站滚动窗口扫槽依赖的
|
||||
WIN 字节在探针内每周期刷新,窗口扫描不受影响。
|
||||
SM2 应答语义不变:首字(探针内)+末字(两分支各自覆盖)。
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void PDO_OutputMapping(void)
|
||||
{
|
||||
/*ECAT_DBG(剖分:输出映射全程)*/
|
||||
const UINT32 dbgT0 = ecatdbg_dwt();
|
||||
|
||||
/* 输出映射:12B 探针判定是否有新输出。
|
||||
* 「每拍始终全量读 384B」会在 PDO 提速后把 PDI 成本抬高一个数量级
|
||||
* (历史:探针版 4kHz load≈80%;全量+process 双读时 2kHz 即饱和)。
|
||||
* 探针命中才全量读;否则只补读 SM2 末字。s_out 长期不更新由
|
||||
* EthercatPdo::process() 按新鲜度门兜底全量读(不是每拍)。 */
|
||||
UINT8 head[12];
|
||||
HW_EscReadIsr(((MEM_ADDR *) head), nEscAddrOutputData, 12U );
|
||||
|
||||
if ( EcatPdo_OutputProbe(head) )
|
||||
{
|
||||
/* 有新 seq 时不必每次拉满 384B:先读命令前缀(含批量 count@48)。
|
||||
* 8kHz 主站每拍都递增 seq 时,若 count==0 仍全量读会每拍吃满 PRAM,
|
||||
* 把 CAN 解析饿死(gseq/s 从 ~3750 掉到数百)。 */
|
||||
const UINT16 prefix = (nPdOutputSize < 52U) ? nPdOutputSize : 52U;
|
||||
HW_EscReadIsr(((MEM_ADDR *)aPdOutputData), nEscAddrOutputData, prefix);
|
||||
const UINT8 batchCount = (prefix > 48U) ? ((const UINT8 *)aPdOutputData)[48] : 0U;
|
||||
if (batchCount > 0U && nPdOutputSize > prefix) {
|
||||
HW_EscReadIsr(((MEM_ADDR *)((UINT8 *)aPdOutputData + prefix)),
|
||||
(UINT16)(nEscAddrOutputData + prefix),
|
||||
(UINT16)(nPdOutputSize - prefix));
|
||||
}
|
||||
APPL_OutputMapping((UINT16*) aPdOutputData);
|
||||
EcatPdo_NoteOutputMapped();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nPdOutputSize >= 2U) {
|
||||
HW_EscReadWordIsr(u16dummy,(nEscAddrOutputData+nPdOutputSize-2));
|
||||
}
|
||||
}
|
||||
g_dbg_outCyc += ecatdbg_dwt() - dbgT0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function shall be called every 1ms.
|
||||
\brief If the switch ECAT_TIMER_INT is 0, the watchdog control is implemented without using
|
||||
\brief interrupts. In this case a local timer register is checked every ECAT_Main cycle
|
||||
\brief and the function is triggered if 1 ms is elapsed
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ECAT_CheckTimer(void)
|
||||
{
|
||||
if(sSyncManOutPar.u32CycleTime == 0)
|
||||
{
|
||||
u16BusCycleCntMs++;
|
||||
}
|
||||
|
||||
/*decrement the state transition timeout counter*/
|
||||
if(bEcatWaitForAlControlRes && (EsmTimeoutCounter > 0))
|
||||
{
|
||||
EsmTimeoutCounter--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DC_CheckWatchdog();
|
||||
}
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT6*/
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function is called from the PDI_Isr and is used to calculate the bus cycle time
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void HandleBusCycleCalculation(void)
|
||||
{
|
||||
/*calculate the cycle time if device is in SM Sync mode and Cycle time was not calculated yet*/
|
||||
if ( !bDcSyncActive && bEscIntEnabled)
|
||||
{
|
||||
BOOL bTiggerCalcCycleTime = FALSE;
|
||||
|
||||
if(sSyncManOutPar.u16GetCycleTime == 1)
|
||||
bTiggerCalcCycleTime = TRUE;
|
||||
if(bTiggerCalcCycleTime)
|
||||
{
|
||||
/*get bus cycle time triggered */
|
||||
sSyncManOutPar.u32CycleTime = 0;
|
||||
sSyncManOutPar.u16GetCycleTime = 0;
|
||||
|
||||
sSyncManInPar.u32CycleTime = 0;
|
||||
sSyncManInPar.u16GetCycleTime = 0;
|
||||
|
||||
u16BusCycleCntMs = 0;
|
||||
bCycleTimeMeasurementStarted = TRUE;
|
||||
StartTimerCnt = (UINT32) HW_GetTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(bCycleTimeMeasurementStarted == TRUE)
|
||||
{
|
||||
UINT32 CurTimerCnt = (UINT32)HW_GetTimer();
|
||||
/*ECATCHANGE_START(V5.11) ECAT3*/
|
||||
UINT32 CalcCycleTime = 0;
|
||||
|
||||
|
||||
#if ECAT_TIMER_INC_P_MS
|
||||
CalcCycleTime = (UINT32)u16BusCycleCntMs * 1000000 + (((INT32)(CurTimerCnt-StartTimerCnt))*1000000/ECAT_TIMER_INC_P_MS); //get elapsed cycle time in ns
|
||||
#endif
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
sSyncManOutPar.u32CycleTime = CalcCycleTime;
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
sSyncManInPar.u32CycleTime = CalcCycleTime;
|
||||
u16BusCycleCntMs = 0;
|
||||
StartTimerCnt = 0;
|
||||
bCycleTimeMeasurementStarted = FALSE;
|
||||
|
||||
/*ECATCHANGE_END(V5.11) ECAT3*/
|
||||
/* CiA402 Motion controller cycle time is only set if DC Synchronisation is active*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*ECATCHANGE_END(V5.11) ECAT6*/
|
||||
|
||||
void PDI_Isr(void)
|
||||
{
|
||||
/*ECAT_DBG(剖分:ISR 全程)*/
|
||||
const UINT32 dbgT0 = ecatdbg_dwt();
|
||||
if(bEscIntEnabled)
|
||||
{
|
||||
/* get the AL event register */
|
||||
const UINT32 dbgTa = ecatdbg_dwt();
|
||||
UINT16 ALEvent = HW_GetALEventRegister_Isr();
|
||||
g_dbg_alCyc += ecatdbg_dwt() - dbgTa;
|
||||
ALEvent = SWAPWORD(ALEvent);
|
||||
|
||||
if ( ALEvent & PROCESS_OUTPUT_EVENT )
|
||||
{
|
||||
if(bDcRunning && bDcSyncActive)
|
||||
{
|
||||
/* Reset SM/Sync0 counter. Will be incremented on every Sync0 event*/
|
||||
u16SmSync0Counter = 0;
|
||||
}
|
||||
if(sSyncManOutPar.u16SmEventMissedCounter > 0)
|
||||
sSyncManOutPar.u16SmEventMissedCounter--;
|
||||
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT6*/
|
||||
//calculate the bus cycle time if required
|
||||
HandleBusCycleCalculation();
|
||||
/*ECATCHANGE_END(V5.11) ECAT6*/
|
||||
|
||||
/* Outputs were updated, set flag for watchdog monitoring */
|
||||
bEcatFirstOutputsReceived = TRUE;
|
||||
|
||||
|
||||
/*
|
||||
handle output process data event
|
||||
*/
|
||||
if ( bEcatOutputUpdateRunning )
|
||||
{
|
||||
/* slave is in OP, update the outputs */
|
||||
PDO_OutputMapping();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Just acknowledge the process data event in the INIT,PreOP and SafeOP state */
|
||||
HW_EscReadWordIsr(u16dummy,nEscAddrOutputData);
|
||||
HW_EscReadWordIsr(u16dummy,(nEscAddrOutputData+nPdOutputSize-2));
|
||||
}
|
||||
}
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
if (( ALEvent & PROCESS_INPUT_EVENT ) && (nPdOutputSize == 0))
|
||||
{
|
||||
//calculate the bus cycle time if required
|
||||
HandleBusCycleCalculation();
|
||||
}
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
|
||||
/*
|
||||
Call ECAT_Application() in SM Sync mode (non-DC only).
|
||||
|
||||
DC:应用节拍已由 Sync0_Isr 拥有(bDcSyncActive),PDI 再跑一次
|
||||
会把 Process/buildInput 算两遍 load_perm。
|
||||
|
||||
非 DC:仍只在 SM/SM2 类型时在 PDI 内跑应用。
|
||||
**不要**「凡有 AL 过程事件就跑」——8kHz 时 PDI 每拍都带事件,
|
||||
Process+InputMapping 热路径会被放大,单拍超过 ~120µs 墙,
|
||||
实测 6kHz 即掉出(曾可 8kHz)。
|
||||
负载随 PDO 上升由 ISR 包装 + free-run 1ms 节流路径体现,
|
||||
见 sys/cpu_load.h;输出侧保持 12B 探针,避免每拍全量读。
|
||||
*/
|
||||
if (!bDcSyncActive
|
||||
&& (sSyncManOutPar.u16SyncType == SYNCTYPE_SM_SYNCHRON
|
||||
|| sSyncManOutPar.u16SyncType == SYNCTYPE_SM2_SYNCHRON
|
||||
|| sSyncManInPar.u16SyncType == SYNCTYPE_SM_SYNCHRON
|
||||
|| sSyncManInPar.u16SyncType == SYNCTYPE_SM2_SYNCHRON))
|
||||
{
|
||||
const UINT32 dbgTb = ecatdbg_dwt();
|
||||
ECAT_Application();
|
||||
g_dbg_appCyc += ecatdbg_dwt() - dbgTb;
|
||||
}
|
||||
|
||||
if ( bEcatInputUpdateRunning
|
||||
/*ECATCHANGE_START(V5.11) ESM7*/
|
||||
&& ((sSyncManInPar.u16SyncType == SYNCTYPE_SM_SYNCHRON) || (sSyncManInPar.u16SyncType == SYNCTYPE_SM2_SYNCHRON))
|
||||
/*ECATCHANGE_END(V5.11) ESM7*/
|
||||
)
|
||||
{
|
||||
/* EtherCAT slave is at least in SAFE-OPERATIONAL, update inputs */
|
||||
const UINT32 dbgTc = ecatdbg_dwt();
|
||||
PDO_InputMapping();
|
||||
g_dbg_inCyc += ecatdbg_dwt() - dbgTc;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if cycle exceed
|
||||
*/
|
||||
/*if next SM event was triggered during runtime increment cycle exceed counter*/
|
||||
const UINT32 dbgTd = ecatdbg_dwt();
|
||||
ALEvent = HW_GetALEventRegister_Isr();
|
||||
g_dbg_alCyc += ecatdbg_dwt() - dbgTd;
|
||||
ALEvent = SWAPWORD(ALEvent);
|
||||
|
||||
if ( ALEvent & PROCESS_OUTPUT_EVENT )
|
||||
{
|
||||
sSyncManOutPar.u16CycleExceededCounter++;
|
||||
sSyncManInPar.u16CycleExceededCounter = sSyncManOutPar.u16CycleExceededCounter;
|
||||
|
||||
/* Acknowledge the process data event*/
|
||||
HW_EscReadWordIsr(u16dummy,nEscAddrOutputData);
|
||||
HW_EscReadWordIsr(u16dummy,(nEscAddrOutputData+nPdOutputSize-2));
|
||||
}
|
||||
} //if(bEscIntEnabled)
|
||||
/*ECAT_DBG(剖分:ISR 全程 收尾)*/
|
||||
g_dbg_pdiCyc += ecatdbg_dwt() - dbgT0;
|
||||
g_dbg_pdiCnt++;
|
||||
}
|
||||
|
||||
void Sync0_Isr(void)
|
||||
{
|
||||
|
||||
Sync0WdCounter = 0;
|
||||
|
||||
if(bDcSyncActive)
|
||||
{
|
||||
|
||||
if ( bEcatInputUpdateRunning )
|
||||
{
|
||||
LatchInputSync0Counter++;
|
||||
}
|
||||
|
||||
/*ECATCHANGE_START(V5.11) ECAT4*/
|
||||
if(u16SmSync0Value > 0)
|
||||
{
|
||||
/* Check if Sm-Sync sequence is invalid */
|
||||
if (u16SmSync0Counter > u16SmSync0Value)
|
||||
{
|
||||
/*ECATCHANGE_START(V5.11) COE3*/
|
||||
if ((nPdOutputSize > 0) && (sSyncManOutPar.u16SmEventMissedCounter <= sErrorSettings.u16SyncErrorCounterLimit))
|
||||
{
|
||||
/*ECATCHANGE_END(V5.11) COE3*/
|
||||
sSyncManOutPar.u16SmEventMissedCounter = sSyncManOutPar.u16SmEventMissedCounter + 3;
|
||||
}
|
||||
|
||||
/*ECATCHANGE_START(V5.11) COE3*/
|
||||
if ((nPdInputSize > 0) && (nPdOutputSize == 0) && (sSyncManInPar.u16SmEventMissedCounter <= sErrorSettings.u16SyncErrorCounterLimit))
|
||||
{
|
||||
/*ECATCHANGE_END(V5.11) COE3*/
|
||||
sSyncManInPar.u16SmEventMissedCounter = sSyncManInPar.u16SmEventMissedCounter + 3;
|
||||
}
|
||||
|
||||
} // if (u16SmSync0Counter > u16SmSync0Value)
|
||||
|
||||
|
||||
if ((nPdOutputSize == 0) && (nPdInputSize > 0))
|
||||
{
|
||||
/* Input only with DC, check if the last input data was read*/
|
||||
UINT16 ALEvent = HW_GetALEventRegister_Isr();
|
||||
ALEvent = SWAPWORD(ALEvent);
|
||||
|
||||
if ((ALEvent & PROCESS_INPUT_EVENT) == 0)
|
||||
{
|
||||
/* no input data was read by the master, increment the sm missed counter*/
|
||||
u16SmSync0Counter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Reset SM/Sync0 counter*/
|
||||
u16SmSync0Counter = 0;
|
||||
|
||||
sSyncManInPar.u16SmEventMissedCounter = 0;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
u16SmSync0Counter++;
|
||||
}
|
||||
}//SM -Sync monitoring enabled
|
||||
/*ECATCHANGE_END(V5.11) ECAT4*/
|
||||
|
||||
|
||||
if(!bEscIntEnabled && bEcatOutputUpdateRunning)
|
||||
{
|
||||
/* Output mapping was not done by the PDI ISR */
|
||||
PDO_OutputMapping();
|
||||
}
|
||||
|
||||
/* Application is synchronized to SYNC0 event*/
|
||||
ECAT_Application();
|
||||
|
||||
if ( bEcatInputUpdateRunning
|
||||
&& (LatchInputSync0Value > 0) && (LatchInputSync0Value == LatchInputSync0Counter) ) /* Inputs shall be latched on a specific Sync0 event */
|
||||
{
|
||||
/* EtherCAT slave is at least in SAFE-OPERATIONAL, update inputs */
|
||||
PDO_InputMapping();
|
||||
|
||||
if(LatchInputSync0Value == 1)
|
||||
{
|
||||
/* if inputs are latched on every Sync0 event (otherwise the counter is reset on the next Sync1 event) */
|
||||
LatchInputSync0Counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Sync1_Isr(void)
|
||||
{
|
||||
Sync1WdCounter = 0;
|
||||
|
||||
if ( bEcatInputUpdateRunning
|
||||
&& (sSyncManInPar.u16SyncType == SYNCTYPE_DCSYNC1)
|
||||
&& (LatchInputSync0Value == 0)) /* Inputs are latched on Sync1 (LatchInputSync0Value == 0), if LatchInputSync0Value > 0 inputs are latched with Sync0 */
|
||||
{
|
||||
/* EtherCAT slave is at least in SAFE-OPERATIONAL, update inputs */
|
||||
PDO_InputMapping();
|
||||
}
|
||||
|
||||
/* Reset Sync0 latch counter (to start next Sync0 latch cycle) */
|
||||
LatchInputSync0Counter = 0;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function shall called within a 1ms cycle.
|
||||
Set Run and Error Led depending on the Led state
|
||||
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pObjectDictionary Pointer to application specific object dictionary.
|
||||
NULL if no specific object are available.
|
||||
\return 0 if initialization was successful
|
||||
|
||||
\brief This function initialize the EtherCAT Sample Code
|
||||
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UINT16 MainInit(void)
|
||||
{
|
||||
UINT16 Error = 0;
|
||||
/*Hardware init function need to be called from the application layer*/
|
||||
|
||||
/*ECATCHANGE_START(V5.11) EEPROM1*/
|
||||
#ifdef SET_EEPROM_PTR
|
||||
SET_EEPROM_PTR
|
||||
#endif
|
||||
/*ECATCHANGE_END(V5.11) EEPROM1*/
|
||||
|
||||
/* initialize the EtherCAT Slave Interface */
|
||||
ECAT_Init();
|
||||
/* initialize the objects */
|
||||
COE_ObjInit();
|
||||
|
||||
|
||||
/*Timer initialization*/
|
||||
u16BusCycleCntMs = 0;
|
||||
StartTimerCnt = 0;
|
||||
bCycleTimeMeasurementStarted = FALSE;
|
||||
|
||||
/*indicate that the slave stack initialization finished*/
|
||||
bInitFinished = TRUE;
|
||||
|
||||
/*Application Init need to be called from the application layer*/
|
||||
return Error;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function shall be called cyclically from main
|
||||
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MainLoop(void)
|
||||
{
|
||||
/*return if initialization not finished */
|
||||
if(bInitFinished == FALSE)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
/* FreeRun-Mode: bEscIntEnabled = FALSE, bDcSyncActive = FALSE
|
||||
Synchron-Mode: bEscIntEnabled = TRUE, bDcSyncActive = FALSE
|
||||
DC-Mode: bEscIntEnabled = TRUE, bDcSyncActive = TRUE */
|
||||
if (
|
||||
(!bEscIntEnabled || !bEcatFirstOutputsReceived) /* SM-Synchronous, but not SM-event received */
|
||||
&& !bDcSyncActive /* DC-Synchronous */
|
||||
)
|
||||
{
|
||||
/* if the application is running in ECAT Synchron Mode the function ECAT_Application is called
|
||||
from the ESC interrupt routine (in mcihw.c or spihw.c),
|
||||
in ECAT Synchron Mode it should be additionally checked, if the SM-event is received
|
||||
at least once (bEcatFirstOutputsReceived = 1), otherwise no interrupt is generated
|
||||
and the function ECAT_Application has to be called here (with interrupts disabled,
|
||||
because the SM-event could be generated while executing ECAT_Application) */
|
||||
if ( !bEscIntEnabled )
|
||||
{
|
||||
/* application is running in ECAT FreeRun Mode,
|
||||
first we have to check, if outputs were received */
|
||||
UINT16 ALEvent = HW_GetALEventRegister();
|
||||
ALEvent = SWAPWORD(ALEvent);
|
||||
|
||||
if ( ALEvent & PROCESS_OUTPUT_EVENT )
|
||||
{
|
||||
/* set the flag for the state machine behaviour */
|
||||
bEcatFirstOutputsReceived = TRUE;
|
||||
if ( bEcatOutputUpdateRunning )
|
||||
{
|
||||
/* update the outputs */
|
||||
PDO_OutputMapping();
|
||||
}
|
||||
}
|
||||
else if ( nPdOutputSize == 0 )
|
||||
{
|
||||
/* if no outputs are transmitted, the watchdog must be reset, when the inputs were read */
|
||||
if ( ALEvent & PROCESS_INPUT_EVENT )
|
||||
{
|
||||
/* Outputs were updated, set flag for watchdog monitoring */
|
||||
bEcatFirstOutputsReceived = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DISABLE_ESC_INT();
|
||||
/*ECAT_DBG(剖分:free-run 应用+输入映射块)*/
|
||||
{
|
||||
const UINT32 dbgT1 = ecatdbg_dwt();
|
||||
/*
|
||||
* free-run 主循环:**严格 1ms 节流**再跑应用并计入 busy。
|
||||
* 不要「AL 有过程事件就跑」——事件位可能连续置位,退化成
|
||||
* 每圈 MainLoop 都 Process,热路径被抬爆(8kHz 掉到 6kHz)。
|
||||
* SM 模式(bEscIntEnabled)本分支本就不进;PDO 高速走 PDI ISR,
|
||||
* 负载由 EXTI1 挂钩计入。
|
||||
*/
|
||||
static UINT32 s_freeRunAppMs = 0xFFFFFFFFu;
|
||||
const UINT32 nowMs = EcatDbg_GetTick1ms();
|
||||
if (nowMs != s_freeRunAppMs) {
|
||||
s_freeRunAppMs = nowMs;
|
||||
CpuLoad_Enter();
|
||||
ECAT_Application();
|
||||
|
||||
if ( bEcatInputUpdateRunning )
|
||||
{
|
||||
PDO_InputMapping();
|
||||
}
|
||||
CpuLoad_Leave();
|
||||
}
|
||||
g_dbg_freerunCyc += ecatdbg_dwt() - dbgT1;
|
||||
}
|
||||
ENABLE_ESC_INT();
|
||||
}
|
||||
#if !ECAT_TIMER_INT
|
||||
/* there is no interrupt routine for the hardware timer so check the timer register if the desired cycle elapsed*/
|
||||
{
|
||||
UINT32 CurTimer = (UINT32)HW_GetTimer();
|
||||
|
||||
if(CurTimer>= ECAT_TIMER_INC_P_MS)
|
||||
{
|
||||
ECAT_CheckTimer();
|
||||
|
||||
HW_ClearTimer();
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* call EtherCAT functions */
|
||||
ECAT_Main();
|
||||
|
||||
/* call lower prior application part */
|
||||
COE_Main();
|
||||
CheckIfEcatError();
|
||||
|
||||
}
|
||||
|
||||
/*The main function was moved to the application files.*/
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief ECAT_Application (prev. SSC versions "COE_Application")
|
||||
this function calculates and the physical process signals and triggers the input mapping
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
void ECAT_Application(void)
|
||||
{
|
||||
|
||||
APPL_Application();
|
||||
|
||||
/* PDO Input mapping is called from the specific trigger ISR */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** @} */
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
\addtogroup CoE CAN Application Profile over EtherCAT
|
||||
@{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file ecatcoe.c
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief Implementation
|
||||
This file contains the CoE mailbox interface
|
||||
|
||||
\version 5.11
|
||||
|
||||
<br>Changes to version V5.0:<br>
|
||||
V5.11 COE4: "change prototype of ""COE_ContinueInd()"" return <> 0 if a failure occurred"<br>
|
||||
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
|
||||
V5.11 TEST6: add test object to trigger Slave-to-Slave communication<br>
|
||||
V5.11 TEST7: add test behaviour to send an emergency on every SDO request (in SafeOP)<br>
|
||||
<br>Changes to version V4.40:<br>
|
||||
V5.0 SDO7: "SDOS_SdoInfoInd()" never return pending SDO Info service. Delete "NOERROR_INWORK" handling.<br>
|
||||
<br>Changes to version V4.08:<br>
|
||||
V4.40 SDO1: add initial value for "nSdoInfoFragmentsLeft"<br>
|
||||
V4.40 MBX6: change return value if no mailbox buffer is available<br>
|
||||
V4.08 MBX 1: If the switch MAILBOX_QUEUE was set, we have to put all SDO Info Responses in the Send Queue
|
||||
*/
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
#include "sdoserv.h"
|
||||
#define _ECATCOE_ 1
|
||||
#include "ecatcoe.h"
|
||||
#undef _ECATCOE_
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
/*remove definition of _ECATCOE_ (#ifdef is used in ecatcoe.h)*/
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ internal Types and Defines
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
#define ECATCOE 0x4300
|
||||
#define ECATCOEMAX 0x02
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ static variables
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ static functions
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ functions
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function intialize the CoE Interface.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void COE_Init(void)
|
||||
{
|
||||
pCoeSendStored = 0;
|
||||
nSdoInfoFragmentsLeft = 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pCoeMbx Pointer to the received mailbox data from the master.
|
||||
|
||||
\return result of the operation (0 (success) or mailbox error code (MBXERR_.... defined in
|
||||
mailbox.h))
|
||||
|
||||
\brief This function is called when a CoE (CAN application layer over EtherCAT) service is received from
|
||||
the master.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UINT8 COE_ServiceInd(TCOEMBX MBXMEM *pCoeMbx)
|
||||
{
|
||||
UINT8 result = 0;
|
||||
|
||||
switch ((pCoeMbx->CoeHeader & COEHEADER_COESERVICEMASK) >> COEHEADER_COESERVICESHIFT)
|
||||
{
|
||||
case COESERVICE_SDOREQUEST:
|
||||
/* SDO-Request received, call SDOS_SdoInd to process the SDO-Request
|
||||
if an existing SDO-Stack shall be used, the corresponding function
|
||||
should be called */
|
||||
result = SDOS_SdoInd( (TINITSDOMBX MBXMEM *) pCoeMbx );
|
||||
break;
|
||||
|
||||
case COESERVICE_SDOINFO:
|
||||
/* SDO-Information Request received, call SDOS_SdoInfoInd to process the SDO-Request */
|
||||
result = SDOS_SdoInfoInd( (TSDOINFORMATION MBXMEM *) pCoeMbx );
|
||||
/*NOERROR_INWORK is never returned by SDOS_SdoInfoInd() => delete return code handling*/
|
||||
break;
|
||||
|
||||
|
||||
case COESERVICE_SDORESPONSE:
|
||||
case COESERVICE_EMERGENCY:
|
||||
case COESERVICE_TXPDO:
|
||||
case COESERVICE_RXPDO:
|
||||
case COESERVICE_TXPDOREMREQ:
|
||||
case COESERVICE_RXPDOREMREQ:
|
||||
/* these CoE services are not supported yet */
|
||||
result = MBXERR_SERVICENOTSUPPORTED;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = MBXERR_INVALIDHEADER;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pMbx Pointer to the free mailbox to sent.
|
||||
|
||||
\return result of the operation (0 (success)
|
||||
|
||||
\brief This function is called when a CoE service to be sent is stored and can
|
||||
\brief be put in the send mailbox.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*ECATCHANGE_START(V5.11) COE4*/
|
||||
UINT8 COE_ContinueInd(TMBX MBXMEM * pMbx)
|
||||
/*ECATCHANGE_END(V5.11) COE4*/
|
||||
{
|
||||
if (pCoeSendStored)
|
||||
{
|
||||
/* send the stored CoE service which could not be sent before */
|
||||
MBX_MailboxSendReq(pCoeSendStored, 0);
|
||||
pCoeSendStored = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send the next fragment of the last CoE service (only for SDO-Information possible) */
|
||||
/* in mailbox queue mode pMbx is always 0, so a mailbox buffer shall be get */
|
||||
pMbx = (TMBX MBXMEM *) APPL_AllocMailboxBuffer(SIZEOF(TMBX));
|
||||
/* it shall be checked if a valid pointer was returned */
|
||||
/*ECATCHANGE_START(V5.11) COE4*/
|
||||
if (pMbx == NULL)
|
||||
{
|
||||
return MBXERR_NOMOREMEMORY;
|
||||
}
|
||||
else
|
||||
/*ECATCHANGE_END(V5.11) COE4*/
|
||||
{
|
||||
/* copy the stored SDO-Info-Header in the request */
|
||||
MBXMEMCPY(pMbx, aSdoInfoHeader, SDO_INFO_HEADER_BYTE_SIZE);
|
||||
/* call SDOS_SdoInfoInd to generate and send the next fragment */
|
||||
SDOS_SdoInfoInd( (TSDOINFORMATION MBXMEM *) pMbx );
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,906 @@
|
||||
/**
|
||||
\addtogroup Mailbox Mailbox Functions
|
||||
@{
|
||||
*/
|
||||
|
||||
/**
|
||||
\file mailbox.c
|
||||
\author EthercatSSC@beckhoff.com
|
||||
\brief Implementation
|
||||
|
||||
<br>Description of the mailbox buffer handling (MAILBOX_QUEUE = 0):<br>
|
||||
There are two mailbox buffer for sending and receiving mailbox services.<br>
|
||||
Normal operation:<br>
|
||||
When starting the mailbox handler psWriteMbx contains mailbox buffer 1,<br>
|
||||
psReadMbx, psRepeatMbx and psStoreMbx are 0.<br>
|
||||
In this state a repeat request would be ignored because there was no service sent yet.<br>
|
||||
When the first mailbox service is sent (in MBX_CopyToSendMailbox), psWriteMbx gets mailbox buffer 2<br>
|
||||
and psReadMbx gets the sent mailbox buffer 1, psRepeatMbx and psStoreMbx are still 0.<br>
|
||||
When the first mailbox service was read from the master, the sent mailbox buffer 1 is stored<br>
|
||||
in psRepeatMbx (in MBX_MailboxReadInd).<br>
|
||||
After that psReadMbx gets always the actual sent mailbox buffer, psWriteMbx is set to 0 (another<br>
|
||||
received mailbox service from the master will not be handled until the sent mailbox service was read<br>
|
||||
and MBX_MailboxReadInd was called).<br>
|
||||
When the mailbox service is read, psWriteMbx gets the Buffer of psRepeatMbx and psRepeatMbx gets the <br>
|
||||
buffer of psReadMbx.<br>
|
||||
Repeat Request from the master:<br>
|
||||
When a Repeat from the master is requested (MBX_MailboxRepeatReq), there are three different possibilities:<br>
|
||||
1. no mailbox service was sent since the mailbox handler was started (psRepeatMbx = 0): nothing to do<br>
|
||||
2. the acknowledge of the last sent mailbox service was received (in MBX_MailboxReadInd) (bSendMbxIsFull = 0):<br>
|
||||
the last sent mailbox service (psRepeatMbx) will be sent again (in MBX_CopyToSendMailbox) and stored in psReadMbx,<br>
|
||||
psRepeatMbx will be set to 0<br>
|
||||
3. the acknowledge of the last sent mailbox service was not received (psReadMbx and psRepeatMbx contain different buffers,<br>
|
||||
psReadMbx is still in the mailbox (because MBX_MailboxReadInd is not called yet, bSendMbxIsFull = 1): <br>
|
||||
psReadMbx will be deleted in the mailbox (call of DisableSyncManChannel and EnableSyncManChannel) and<br>
|
||||
stored in psStoreMbx, psRepeatMbx will be sent again (in MBX_CopyToSendMailbox) and stored in psReadMbx,<br>
|
||||
psRepeatMbx will be set to 0.<br>
|
||||
When the repeated mailbox service was sent (call of MBX_MailboxReadInd), psReadMbx will be stored in psRepeatMbx<br>
|
||||
and psStoreMbx will be sent (in MBX_CopyToSendMailbox) and stored in psReadMbx, psStoreMbx will be set to 0.<br>
|
||||
|
||||
<br>Description of the mailbox buffer handling (MAILBOX_QUEUE = 1):<br>
|
||||
There are two mailbox buffer for sending and receiving mailbox services.<br>
|
||||
Normal operation (psWriteMbx is only used for local storage):<br>
|
||||
When starting the mailbox handler psReadMbx, psRepeatMbx and psStoreMbx are 0.<br>
|
||||
In this state a repeat request would be ignored because there was no service sent yet.<br>
|
||||
When a mailbox service is received from the master (in MBX_CheckAndCopyMailbox) a mailbox buffer<br>
|
||||
will be get with APPL_AllocMailboxBuffer and the corresponding protocol service function will<br>
|
||||
be called (in MBX_WriteMailboxInd). This buffer shall be used for the protocol service response.<br>
|
||||
When the first mailbox service is sent (in MBX_CopyToSendMailbox), psReadMbx gets the sent mailbox buffer,<br>
|
||||
psRepeatMbx and psStoreMbx are still 0.<br>
|
||||
When the first mailbox service was read from the master, the sent mailbox buffer (psReadMbx) is stored<br>
|
||||
in psRepeatMbx (in MBX_MailboxReadInd).<br>
|
||||
After that psReadMbx gets always the actual sent mailbox buffer (in MBX_CopyToSendMailbox)<br>
|
||||
When the mailbox service is read, psRepeatMbx is returned (with APPL_FreeMailboxBuffer) and psRepeatMbx gets the <br>
|
||||
buffer of psReadMbx.<br>
|
||||
Repeat Request from the master:<br>
|
||||
When a Repeat from the master is requested (MBX_MailboxRepeatReq), there are three different possibilities:<br>
|
||||
1. no mailbox service was sent since the mailbox handler was started (psRepeatMbx = 0): nothing to do<br>
|
||||
2. the acknowledge of the last sent mailbox service was received (in MBX_MailboxReadInd) (bSendMbxIsFull = 0):<br>
|
||||
the last sent mailbox service (psRepeatMbx) will be sent again (in MBX_CopyToSendMailbox) and stored in psReadMbx,<br>
|
||||
psRepeatMbx will be set to 0<br>
|
||||
3. the acknowledge of the last sent mailbox service was not received (psReadMbx and psRepeatMbx contain different buffers,<br>
|
||||
psReadMbx is still in the mailbox (because MBX_MailboxReadInd is not called yet, bSendMbxIsFull = 1): <br>
|
||||
psReadMbx will be deleted in the mailbox (call of DisableSyncManChannel and EnableSyncManChannel) and<br>
|
||||
stored in psStoreMbx, psRepeatMbx will be sent again (in MBX_CopyToSendMailbox) and stored in psReadMbx,<br>
|
||||
psRepeatMbx will be set to 0.<br>
|
||||
When the repeated mailbox service was sent (call of MBX_MailboxReadInd), psReadMbx will be stored in psRepeatMbx<br>
|
||||
and psStoreMbx will be sent (in MBX_CopyToSendMailbox) and stored in psReadMbx, psStoreMbx will be set to 0.
|
||||
|
||||
\version 5.11
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ Includes
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
|
||||
|
||||
#include "ecatslv.h"
|
||||
|
||||
|
||||
#define _MAILBOX_ 1
|
||||
#include "mailbox.h"
|
||||
#undef _MAILBOX_
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
/*remove definition of _MAILBOX_ (#ifdef is used in mailbox.h)*/
|
||||
/* ECATCHANGE_END(V5.11) ECAT10*/
|
||||
|
||||
#include "ecatcoe.h"
|
||||
|
||||
#include <stdio.h> /* printf: SM 几何交叉校验 WARN(见 MBX_StartMailboxHandler) */
|
||||
|
||||
/*--------------------------------------------------------------------------------------
|
||||
------
|
||||
------ internal Types and Defines
|
||||
------
|
||||
--------------------------------------------------------------------------------------*/
|
||||
|
||||
/*--------------------------------------------------------------------------------------
|
||||
------
|
||||
------ internal Variables
|
||||
------
|
||||
--------------------------------------------------------------------------------------*/
|
||||
/*variables are declared in ecatslv.c*/
|
||||
extern VARVOLATILE UINT16 u16dummy;
|
||||
|
||||
/* ECATCHANGE_START(V5.11) MBX2*/
|
||||
BOOL bNoMbxMemoryAvailable; /**< \brief Indicates if enough dynamic memory is available to handle at least one mailbox datagram */
|
||||
/* ECATCHANGE_END(V5.11) MBX2*/
|
||||
/*--------------------------------------------------------------------------------------
|
||||
------
|
||||
------ internal functions
|
||||
------
|
||||
--------------------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------------------------------------------------
|
||||
------
|
||||
------ functions
|
||||
------
|
||||
---------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PutInMbxQueue
|
||||
//
|
||||
|
||||
UINT8 PutInMbxQueue(TMBX MBXMEM * pMbx, TMBXQUEUE MBXMEM * pQueue)
|
||||
{
|
||||
UINT16 lastInQueue;
|
||||
ENTER_MBX_CRITICAL;
|
||||
|
||||
|
||||
lastInQueue = pQueue->lastInQueue+1;
|
||||
if (lastInQueue == pQueue->maxQueueSize)
|
||||
{
|
||||
// Umbruch der Queue
|
||||
lastInQueue = 0;
|
||||
}
|
||||
|
||||
if (pQueue->firstInQueue == lastInQueue)
|
||||
{
|
||||
// Ueberlauf der Queue -> letztes Element wieder herausnehmen
|
||||
LEAVE_MBX_CRITICAL;
|
||||
return MBXERR_NOMOREMEMORY;
|
||||
}
|
||||
|
||||
pQueue->queue[pQueue->lastInQueue] = pMbx;
|
||||
pQueue->lastInQueue = lastInQueue;
|
||||
|
||||
LEAVE_MBX_CRITICAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GetOutOfMbxQueue
|
||||
//
|
||||
|
||||
TMBX MBXMEM * GetOutOfMbxQueue(TMBXQUEUE MBXMEM * pQueue)
|
||||
{
|
||||
TMBX MBXMEM * pMbx;
|
||||
ENTER_MBX_CRITICAL;
|
||||
|
||||
if (pQueue->firstInQueue != pQueue->lastInQueue)
|
||||
{
|
||||
// Queue ist nicht leer
|
||||
UINT16 firstInQueue = pQueue->firstInQueue;
|
||||
pMbx = pQueue->queue[firstInQueue];
|
||||
firstInQueue++;
|
||||
pQueue->firstInQueue = firstInQueue;
|
||||
if (pQueue->firstInQueue == pQueue->maxQueueSize)
|
||||
{
|
||||
// Umbruch der Queue
|
||||
pQueue->firstInQueue = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
pMbx = 0;
|
||||
|
||||
|
||||
LEAVE_MBX_CRITICAL;
|
||||
|
||||
return pMbx;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function intialize the Mailbox Interface.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_Init(void)
|
||||
{
|
||||
u16ReceiveMbxSize = MIN_MBX_SIZE;
|
||||
u16SendMbxSize = MAX_MBX_SIZE;
|
||||
u16EscAddrReceiveMbx = MIN_MBX_WRITE_ADDRESS;
|
||||
u16EscAddrSendMbx = MIN_MBX_READ_ADDRESS;
|
||||
|
||||
sMbxReceiveQueue.firstInQueue = 0;
|
||||
sMbxReceiveQueue.lastInQueue = 0;
|
||||
sMbxReceiveQueue.maxQueueSize = MAX_MBX_QUEUE_SIZE;
|
||||
sMbxSendQueue.firstInQueue = 0;
|
||||
sMbxSendQueue.lastInQueue = 0;
|
||||
sMbxSendQueue.maxQueueSize = MAX_MBX_QUEUE_SIZE;
|
||||
psWriteMbx = NULL;
|
||||
|
||||
|
||||
psRepeatMbx = NULL;
|
||||
psReadMbx = NULL;
|
||||
psStoreMbx = NULL;
|
||||
|
||||
bMbxRepeatToggle = FALSE;
|
||||
/*Reset Repeat acknowledge bit of SyncManager1 (0x80F bit 2)*/
|
||||
{
|
||||
UINT16 sm1Activate = 0;
|
||||
HW_EscReadWord(sm1Activate,(ESC_SYNCMAN_ACTIVE_OFFSET + SIZEOF_SM_REGISTER));
|
||||
sm1Activate &= SWAPWORD(~0x0200);
|
||||
HW_EscWriteWord(sm1Activate,(ESC_SYNCMAN_ACTIVE_OFFSET + SIZEOF_SM_REGISTER));
|
||||
}
|
||||
bMbxRunning = FALSE;
|
||||
bSendMbxIsFull = FALSE;
|
||||
bReceiveMbxIsLocked = FALSE;
|
||||
u8MailboxSendReqStored = 0;
|
||||
u8MbxWriteCounter = 0;
|
||||
u8MbxReadCounter = 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function includes the state transition from INIT to
|
||||
\brief PRE-OPERATIONAL in the EtherCAT Slave corresponding to
|
||||
\brief local management service Start Mailbox Handler
|
||||
\brief it is checked if the mailbox areas overlaps each other
|
||||
\brief and the Sync Manager channels 0 and 1 are enabled.
|
||||
\brief This function shall only be called if mailbox is supported.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
UINT16 MBX_StartMailboxHandler(void)
|
||||
{
|
||||
UINT16 result = 0;
|
||||
/* 邮箱几何构建期常量(ecat_def.h:MBX_SM0/1_START/SIZE)直达缓存,
|
||||
* 不信任 GetSyncMan/CSR 单次间接读——CSR 读降级为交叉校验:
|
||||
* 失配打 WARN(说明 ESC 寄存器被非常规改写,需查)但缓存仍取常量。
|
||||
* 运行期内存踩踏也会在每次 INIT→PREOP 被常量覆写。
|
||||
* 注意:ESC 寄存器经 PDI/CSR 读出即主机序(与 GetSyncMan 既有路径一致,勿加
|
||||
* SWAPWORD;PRAM 帧数据才是线上大端,那边交换是对的)。 */
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
TSYNCMAN ESCMEM * pSyncMan = (TSYNCMAN ESCMEM *)GetSyncMan(MAILBOX_WRITE);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
if (pSyncMan->PhysicalStartAddress != MBX_SM0_START || pSyncMan->Length != MBX_SM0_SIZE)
|
||||
{
|
||||
printf("[ECAT][MBX] WARN: SM0 reg 0x%04X/%u != build-time 0x%04X/%u -> keep build-time\r\n",
|
||||
(unsigned)pSyncMan->PhysicalStartAddress, (unsigned)pSyncMan->Length,
|
||||
(unsigned)MBX_SM0_START, (unsigned)MBX_SM0_SIZE);
|
||||
}
|
||||
/* store size of the receive mailbox */
|
||||
u16ReceiveMbxSize = MBX_SM0_SIZE;
|
||||
/* store the address of the receive mailbox */
|
||||
u16EscAddrReceiveMbx = MBX_SM0_START;
|
||||
|
||||
/* get address of the send mailbox sync manager (SM1) */
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
pSyncMan =(TSYNCMAN ESCMEM *) GetSyncMan(MAILBOX_READ);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
if (pSyncMan->PhysicalStartAddress != MBX_SM1_START || pSyncMan->Length != MBX_SM1_SIZE)
|
||||
{
|
||||
printf("[ECAT][MBX] WARN: SM1 reg 0x%04X/%u != build-time 0x%04X/%u -> keep build-time\r\n",
|
||||
(unsigned)pSyncMan->PhysicalStartAddress, (unsigned)pSyncMan->Length,
|
||||
(unsigned)MBX_SM1_START, (unsigned)MBX_SM1_SIZE);
|
||||
}
|
||||
|
||||
/* store the size of the send mailbox */
|
||||
u16SendMbxSize = MBX_SM1_SIZE;
|
||||
/* store the address of the send mailbox */
|
||||
u16EscAddrSendMbx = MBX_SM1_START;
|
||||
|
||||
// HBu 02.05.06: it should be checked if there are overlaps in the sync manager areas
|
||||
if ((u16EscAddrReceiveMbx + u16ReceiveMbxSize) > u16EscAddrSendMbx && (u16EscAddrReceiveMbx < (u16EscAddrSendMbx + u16SendMbxSize)))
|
||||
{
|
||||
return ALSTATUSCODE_INVALIDMBXCFGINPREOP;
|
||||
}
|
||||
|
||||
/* enable the receive mailbox sync manager channel */
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
EnableSyncManChannel(MAILBOX_WRITE);
|
||||
/* enable the send mailbox sync manager channel */
|
||||
EnableSyncManChannel(MAILBOX_READ);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
|
||||
/* ECATCHANGE_START(V5.11) MBX2*/
|
||||
psWriteMbx = (TMBX MBXMEM *) APPL_AllocMailboxBuffer(u16ReceiveMbxSize);
|
||||
if(psWriteMbx == NULL)
|
||||
{
|
||||
bNoMbxMemoryAvailable = TRUE;
|
||||
|
||||
//check if at least enough memory for an mailbox error is available (other wise stop the state transition)
|
||||
psWriteMbx = (TMBX MBXMEM *) APPL_AllocMailboxBuffer(10); /* a mailbox error datagram length*/
|
||||
if(psWriteMbx == NULL)
|
||||
{
|
||||
result = ALSTATUSCODE_NOMEMORY;
|
||||
}
|
||||
|
||||
APPL_FreeMailboxBuffer(psWriteMbx);
|
||||
}
|
||||
else
|
||||
{
|
||||
bNoMbxMemoryAvailable = FALSE;
|
||||
APPL_FreeMailboxBuffer(psWriteMbx);
|
||||
}
|
||||
/* ECATCHANGE_END(V5.11) MBX2*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
|
||||
\brief This function includes the state transition from
|
||||
\brief PRE-OPERATIONAL to INIT in the EtherCAT Slave corresponding to
|
||||
\brief local management service Stop Mailbox Handler
|
||||
\brief the Sync Manager channels 0 and 1 are disabled
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_StopMailboxHandler(void)
|
||||
{
|
||||
TMBX MBXMEM * pMbx;
|
||||
|
||||
/* mailbox handler is stopped */
|
||||
bMbxRunning = FALSE;
|
||||
/* disable the receive mailbox sync manager channel */
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
DisableSyncManChannel(MAILBOX_WRITE);
|
||||
/* disable the send mailbox sync manager channel */
|
||||
DisableSyncManChannel(MAILBOX_READ);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
/* initialize variables again */
|
||||
|
||||
|
||||
if (psRepeatMbx != NULL)
|
||||
APPL_FreeMailboxBuffer(psRepeatMbx);
|
||||
|
||||
if (psStoreMbx != NULL && psStoreMbx != psRepeatMbx)
|
||||
APPL_FreeMailboxBuffer(psStoreMbx);
|
||||
|
||||
if (psReadMbx != NULL && psReadMbx != psRepeatMbx && psReadMbx != psStoreMbx)
|
||||
APPL_FreeMailboxBuffer(psReadMbx);
|
||||
|
||||
|
||||
psRepeatMbx = NULL;
|
||||
psReadMbx = NULL;
|
||||
psStoreMbx = NULL;
|
||||
|
||||
bMbxRepeatToggle = FALSE;
|
||||
/*Reset Repeat acknowledge bit of SyncManager1 (0x080F bit 2)*/
|
||||
{
|
||||
UINT16 sm1Activate = 0;
|
||||
HW_EscReadWord(sm1Activate,(ESC_SYNCMAN_ACTIVE_OFFSET + SIZEOF_SM_REGISTER));
|
||||
sm1Activate &= SWAPWORD(~0x0200);
|
||||
HW_EscWriteWord(sm1Activate,(ESC_SYNCMAN_ACTIVE_OFFSET + SIZEOF_SM_REGISTER));
|
||||
}
|
||||
bSendMbxIsFull = FALSE;
|
||||
bReceiveMbxIsLocked = FALSE;
|
||||
u8MailboxSendReqStored = 0;
|
||||
u8MbxWriteCounter = 0;
|
||||
u8MbxReadCounter = 0;
|
||||
|
||||
do
|
||||
{
|
||||
pMbx = GetOutOfMbxQueue(&sMbxReceiveQueue);
|
||||
if (pMbx)
|
||||
{
|
||||
APPL_FreeMailboxBuffer(pMbx);
|
||||
pMbx = NULL;
|
||||
}
|
||||
} while (pMbx != NULL);
|
||||
do
|
||||
{
|
||||
pMbx = GetOutOfMbxQueue(&sMbxSendQueue);
|
||||
if (pMbx)
|
||||
{
|
||||
APPL_FreeMailboxBuffer(pMbx);
|
||||
pMbx = NULL;
|
||||
}
|
||||
} while (pMbx != NULL);
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pMbx Pointer to the received Mailbox command from Master.
|
||||
|
||||
\brief The function checks the mailbox header for the requested service and calls the
|
||||
\brief corresponding XXXX_ServiceInd-function
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UINT8 MailboxServiceInd(TMBX MBXMEM *pMbx)
|
||||
{
|
||||
UINT8 result;
|
||||
|
||||
|
||||
switch ( (pMbx->MbxHeader.Flags[MBX_OFFS_TYPE] & MBX_MASK_TYPE) >> MBX_SHIFT_TYPE )
|
||||
{
|
||||
case MBX_TYPE_COE:
|
||||
/* CoE datagram received */
|
||||
result = COE_ServiceInd((TCOEMBX MBXMEM *) pMbx);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
result = MBXERR_UNSUPPORTEDPROTOCOL;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pMbx Pointer to the received Mailbox command from Master.
|
||||
|
||||
\brief This function is called when the Master has written the Receive-Mailbox.
|
||||
\brief It will only be called if the send mailbox is empty, that a response for the
|
||||
\brief mailbox service could be stored.
|
||||
\brief The function checks the mailbox header for the requested service and calls the
|
||||
\brief corresponding XXXX_ServiceInd-function
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_MailboxWriteInd(TMBX MBXMEM *pMbx)
|
||||
{
|
||||
UINT8 result = 0;
|
||||
UINT8 mbxCounter = pMbx->MbxHeader.Flags[MBX_OFFS_COUNTER] >> MBX_SHIFT_COUNTER;
|
||||
UINT16 MbxLen = SWAPWORD(pMbx->MbxHeader.Length);
|
||||
|
||||
if(MbxLen > MAX_MBX_SIZE)
|
||||
{
|
||||
/* Mailbox error response: size specified in mailbox header too large*/
|
||||
pMbx->MbxHeader.Length = 4;
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_TYPE] &= ~MBX_MASK_TYPE;
|
||||
pMbx->Data[0] = SWAPWORD(MBXSERVICE_MBXERRORCMD);
|
||||
pMbx->Data[1] = SWAPWORD(MBXERR_INVALIDSIZE);
|
||||
MBX_MailboxSendReq(pMbx, 0);
|
||||
}
|
||||
else
|
||||
/* if the mailbox datagram counter (Bit 4-6 of Byte 5 of the mailbox header) is unequal zero,
|
||||
the master supports the mailbox data link layer,
|
||||
in that case a repeated mailbox write request will be detected, if the counter is unequal zero
|
||||
and unchanged */
|
||||
if ( mbxCounter == 0 || mbxCounter != u8MbxWriteCounter )
|
||||
{
|
||||
/* new mailbox service received */
|
||||
/* mbxCounter = 0: old EtherCAT master */
|
||||
/* new MBX service received, store the new mailbox counter */
|
||||
u8MbxWriteCounter = mbxCounter;
|
||||
|
||||
{
|
||||
/* check the protocol type and call the XXXX_ServiceInd-function */
|
||||
|
||||
result = PutInMbxQueue(pMbx, &sMbxReceiveQueue);
|
||||
}
|
||||
|
||||
if ( result != 0 )
|
||||
{
|
||||
/* Mailbox error response: type 0 (mailbox service protocol) */
|
||||
pMbx->MbxHeader.Length = 4;
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_TYPE] &= ~MBX_MASK_TYPE;
|
||||
pMbx->Data[0] = SWAPWORD(MBXSERVICE_MBXERRORCMD);
|
||||
pMbx->Data[1] = SWAPWORD(result);
|
||||
MBX_MailboxSendReq(pMbx, 0);
|
||||
}
|
||||
}
|
||||
// the mailbox buffer has to be freed here
|
||||
else
|
||||
{
|
||||
APPL_FreeMailboxBuffer(pMbx);
|
||||
pMbx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function is called when the Master has read the Send-Mailbox.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_MailboxReadInd(void)
|
||||
{
|
||||
bSendMbxIsFull = FALSE;
|
||||
// HBu 02.05.06: the pointer psRepeatMbx is only free if there is no stored
|
||||
// mailbox service from the last repeat
|
||||
if ( psRepeatMbx && psStoreMbx == NULL )
|
||||
{
|
||||
/* the last sent service is not stored for repeat any longer */
|
||||
APPL_FreeMailboxBuffer(psRepeatMbx);
|
||||
psRepeatMbx = NULL;
|
||||
}
|
||||
|
||||
/* the actual sent service has to be stored for repeat */
|
||||
psRepeatMbx = psReadMbx;
|
||||
|
||||
if ( psStoreMbx )
|
||||
{
|
||||
/* there was a buffer stored */
|
||||
MBX_CopyToSendMailbox(psStoreMbx);
|
||||
/* no more buffer to be stored any more */
|
||||
psStoreMbx = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
TMBX MBXMEM *pMbx = GetOutOfMbxQueue(&sMbxSendQueue);
|
||||
if (pMbx)
|
||||
{
|
||||
MBX_CopyToSendMailbox(pMbx);
|
||||
}
|
||||
}
|
||||
|
||||
if ( u8MailboxSendReqStored )
|
||||
{
|
||||
/* there are mailbox services stored to be sent */
|
||||
if ( u8MailboxSendReqStored & COE_SERVICE )
|
||||
{
|
||||
UINT8 result = 0;
|
||||
/* reset the flag indicating that CoE service to be sent was stored */
|
||||
u8MailboxSendReqStored &= ~COE_SERVICE;
|
||||
|
||||
/* call CoE function that will send the stored CoE service */
|
||||
result = COE_ContinueInd(psWriteMbx);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
/*Set the pending CoE indication is an error occurred during the continue indication*/
|
||||
u8MailboxSendReqStored |= COE_SERVICE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function is called if the Master has requested a resending of the last
|
||||
\brief sent mailbox
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_MailboxRepeatReq(void)
|
||||
{
|
||||
if (psRepeatMbx)
|
||||
{
|
||||
TMBX MBXMEM *pMbx = psRepeatMbx;
|
||||
/* send mailbox service stored for repeat */
|
||||
/* HBu 13.10.06: if a repeat request is received (again) before the previously repeated mailbox telegram
|
||||
was read from the master (psStoreMbx != NULL) the next mailbox telegram to be sent is still in the
|
||||
read mailbox so it has not to updated exchanged */
|
||||
ENTER_MBX_CRITICAL;
|
||||
|
||||
if (bSendMbxIsFull && psStoreMbx == NULL)
|
||||
{
|
||||
/* mailbox is full, take the buffer off */
|
||||
/*ECATCHANGE_START(V5.11) HW1*/
|
||||
DisableSyncManChannel(MAILBOX_READ);
|
||||
|
||||
/* store the buffer to be sent next */
|
||||
psStoreMbx = psReadMbx;
|
||||
/* enable the mailbox again */
|
||||
EnableSyncManChannel(MAILBOX_READ);
|
||||
/*ECATCHANGE_END(V5.11) HW1*/
|
||||
|
||||
/* HBu 15.02.06: flag has to be reset otherwise the mailbox service
|
||||
will not be copied by MBX_CopyToSendMailbox */
|
||||
bSendMbxIsFull = FALSE;
|
||||
}
|
||||
|
||||
|
||||
MBX_CopyToSendMailbox(pMbx);
|
||||
// HBu 17.06.06: psRepeatMbx has to be set to 0, when it was repeated, otherwise it would be returned twice
|
||||
// to the empty queue (MAILBOX_QUEUE=1) or a buffer get lost, if the the next repeat request will happen before
|
||||
// the repeated buffer was read
|
||||
psRepeatMbx = NULL;
|
||||
LEAVE_MBX_CRITICAL;
|
||||
}
|
||||
|
||||
// Repeat was finished, toggle the acknowledge bit
|
||||
bMbxRepeatToggle = !bMbxRepeatToggle;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pMbx Pointer to a Mailbox command to be sent (read by the Master)
|
||||
\param flags Bit 0-6: mailbox protocol type:
|
||||
0x01 - emergency service
|
||||
0x02 - CoE service
|
||||
0x04 - SoE service
|
||||
0x80 - EoE service
|
||||
0x10 - AoE service
|
||||
0x20 - VoE service
|
||||
Bit 7: 0 - no more fragments to be sent for the requested mailbox service
|
||||
1 - additional fragments to be sent for the mailbox service, the
|
||||
corresponding XXXX_ContinueInd-function will be called to get
|
||||
the next fragment
|
||||
|
||||
\return 0: Success - mailbox command could be stored in the send mailbox
|
||||
1: Failed - mailbox command could not be stored in the send mailbox, the
|
||||
XXXX_ContinueInd service will be called when the mailbox was
|
||||
read from the master to
|
||||
|
||||
\brief This function puts a new Mailbox service in the Send Mailbox
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UINT8 MBX_MailboxSendReq( TMBX MBXMEM * pMbx, UINT8 flags )
|
||||
{
|
||||
UINT8 result = 0;
|
||||
|
||||
/* HBu 06.02.06: in INIT-state a mailbox send request shall be refused */
|
||||
if ( (nAlStatus & STATE_MASK) == STATE_INIT )
|
||||
return ERROR_INVALIDSTATE;
|
||||
|
||||
|
||||
ENTER_MBX_CRITICAL;
|
||||
DISABLE_MBX_INT;
|
||||
|
||||
/* the counter in the mailbox header has to be incremented with every new mailbox service to be sent
|
||||
if the mailbox data link layer is supported (software switch MAILBOX_REPEAT_SUPPORTED set)*/
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_COUNTER] &= ~MBX_MASK_COUNTER;
|
||||
/* HBu 13.02.06: Repeat-Counter was incremented too much if the mailbox service could not be sent */
|
||||
/* u8MbxCounter holds the actual counter for the mailbox header, only the values
|
||||
1-7 are allowed if the mailbox data link layer is supported */
|
||||
if ( (u8MbxReadCounter & 0x07) == 0 )
|
||||
u8MbxReadCounter = 1;
|
||||
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_COUNTER] |= u8MbxReadCounter << MBX_SHIFT_COUNTER;
|
||||
|
||||
/* try to copy the mailbox command in the ESC */
|
||||
if ( MBX_CopyToSendMailbox(pMbx) != 0 )
|
||||
{
|
||||
/* no success, send mailbox was full, set flag */
|
||||
result = PutInMbxQueue(pMbx, &sMbxSendQueue);
|
||||
if (result != 0)
|
||||
flags |= FRAGMENTS_FOLLOW;
|
||||
else
|
||||
u8MbxReadCounter++;
|
||||
}
|
||||
/* HBu 13.02.06: Repeat-Counter was incremented too much if the mailbox service could not be sent */
|
||||
else
|
||||
{
|
||||
u8MbxReadCounter++;
|
||||
}
|
||||
|
||||
if ( flags & FRAGMENTS_FOLLOW )
|
||||
{
|
||||
/* store the mailbox service that the corresponding XXX_ContinueInd function will
|
||||
be called when the send mailbox will have been read by the master because there
|
||||
are mailbox commands to be sent for this service */
|
||||
u8MailboxSendReqStored |= (flags & ((UINT8) ~FRAGMENTS_FOLLOW));
|
||||
}
|
||||
|
||||
ENABLE_MBX_INT;
|
||||
LEAVE_MBX_CRITICAL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function is used to check if the received mailbox command can be processed.
|
||||
|
||||
Also the contents of the Receive Mailbox will be copied in the variable sMbx.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_CheckAndCopyMailbox( void )
|
||||
{
|
||||
UINT16 mbxLen;
|
||||
|
||||
/* get the size of the received mailbox command and acknowledge the event*/
|
||||
HW_EscReadWord(mbxLen,u16EscAddrReceiveMbx);
|
||||
|
||||
/* the size has to be swapped here, all other bytes of the mailbox service will be swapped later */
|
||||
mbxLen = SWAPWORD(mbxLen);
|
||||
|
||||
/* ECATCHANGE_START(V5.11) MBX2*/
|
||||
if(bNoMbxMemoryAvailable == TRUE)
|
||||
{
|
||||
/* Return a no memory error in case of any mailbox request*/
|
||||
TMBX MBXMEM *pMbx = (TMBX MBXMEM *) APPL_AllocMailboxBuffer(10); /* a mailbox error datagram length*/
|
||||
|
||||
if(pMbx != NULL)
|
||||
{
|
||||
HMEMSET(pMbx,0x00,10);
|
||||
|
||||
/* Mailbox error response: type 0 (mailbox service protocol) */
|
||||
pMbx->MbxHeader.Length = 4;
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_TYPE] &= ~(MBX_MASK_TYPE);
|
||||
pMbx->Data[0] = SWAPWORD(MBXSERVICE_MBXERRORCMD);
|
||||
pMbx->Data[1] = SWAPWORD(MBXERR_NOMOREMEMORY);
|
||||
MBX_MailboxSendReq(pMbx, 0);
|
||||
|
||||
APPL_FreeMailboxBuffer(pMbx);
|
||||
}
|
||||
}
|
||||
/* ECATCHANGE_END(V5.11) MBX2*/
|
||||
/* the length of the mailbox data is in the first two bytes of the mailbox,
|
||||
so the length of the mailbox header has to be added */
|
||||
mbxLen += MBX_HEADER_SIZE;
|
||||
|
||||
/* in this example there are only two mailbox buffers available in the firmware (one for processing and
|
||||
one to stored the last sent response for a possible repeat request), so a
|
||||
received mailbox service can only be processed if a free buffer is available */
|
||||
if ( ( bSendMbxIsFull ) /* a received mailbox service will not be processed
|
||||
as long as the send mailbox is still full
|
||||
(waits to be read from the master) */
|
||||
||( u8MailboxSendReqStored ) /* a mailbox service to be sent is still stored
|
||||
so the received mailbox service will not be processed
|
||||
until all stored mailbox services are sent */
|
||||
)
|
||||
{
|
||||
/* set flag that the processing of the mailbox service will be checked in the
|
||||
function MBX_Main (called from ECAT_Main) */
|
||||
bReceiveMbxIsLocked = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* received mailbox command can be processed, reset flag */
|
||||
bReceiveMbxIsLocked = FALSE;
|
||||
|
||||
/* if the read mailbox size is too big for the buffer, set the copy size to the maximum buffer size, otherwise
|
||||
memory could be overwritten,
|
||||
the evaluation of the mailbox size will be done in the mailbox protocols called from MBX_WriteMailboxInd */
|
||||
|
||||
if (mbxLen > u16ReceiveMbxSize)
|
||||
mbxLen = u16ReceiveMbxSize;
|
||||
|
||||
psWriteMbx = (TMBX MBXMEM *) APPL_AllocMailboxBuffer(u16ReceiveMbxSize);
|
||||
|
||||
/* if there is no more memory for mailbox buffer, the mailbox should not be read */
|
||||
if (psWriteMbx == NULL)
|
||||
{
|
||||
/* set flag that the processing of the mailbox service will be checked in the
|
||||
function MBX_Main (called from ECAT_Main) */
|
||||
bReceiveMbxIsLocked = TRUE;
|
||||
return;
|
||||
}
|
||||
/* copy the mailbox header and data*/
|
||||
HW_EscReadMbxMem((MEM_ADDR MBXMEM *) psWriteMbx,u16EscAddrReceiveMbx,mbxLen);
|
||||
|
||||
|
||||
{
|
||||
/*Read Control and Status of SyncManager 0 to check if the buffer is unlocked*/
|
||||
VARVOLATILE UINT16 smstate = 0x00;
|
||||
HW_EscReadWord(smstate,ESC_SYNCMAN_CONTROL_OFFSET);
|
||||
/*ECATCHANGE_START(V5.11) ECAT7*/
|
||||
smstate = SWAPWORD(smstate);
|
||||
/*ECATCHANGE_END(V5.11) ECAT7*/
|
||||
|
||||
if(smstate & SM_STATUS_MBX_BUFFER_FULL)
|
||||
{
|
||||
/*Unlock the mailbox SyncManger buffer*/
|
||||
u16dummy = 0;
|
||||
HW_EscReadWord(u16dummy,(u16EscAddrReceiveMbx + u16ReceiveMbxSize - 2));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* in MBX_MailboxWriteInd the mailbox protocol will be processed */
|
||||
MBX_MailboxWriteInd( psWriteMbx );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\param pMbx Pointer to the Mailbox command to be send to the master.
|
||||
|
||||
\brief This function copies data to the Send Mailbox.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UINT8 MBX_CopyToSendMailbox( TMBX MBXMEM *pMbx )
|
||||
{
|
||||
if ( (nAlStatus & STATE_MASK) == STATE_INIT)
|
||||
{
|
||||
/* the mailbox is disabled if the slave is in the INIT state */
|
||||
return( ERROR_INVALIDSTATE );
|
||||
}
|
||||
|
||||
|
||||
if ( !bMbxRunning )
|
||||
{
|
||||
/* the mailbox is disabled if the slave is in the INIT state */
|
||||
return( ERROR_INVALIDSTATE );
|
||||
}
|
||||
|
||||
if ( bSendMbxIsFull )
|
||||
{
|
||||
/* mailbox service cannot be sent because the send mailbox is still full */
|
||||
return MBXERR_NOMOREMEMORY;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* the variable mbxSize contains the size of the mailbox data to be sent */
|
||||
UINT16 mbxSize = pMbx->MbxHeader.Length;
|
||||
HW_EscWriteMbxMem((MEM_ADDR *)pMbx, u16EscAddrSendMbx, (mbxSize + MBX_HEADER_SIZE));
|
||||
/* ECATCHANGE_HW(V5.10) HW1*/
|
||||
|
||||
{
|
||||
/*Read Control and Status of SyncManager 1 to check if the buffer is still marked as empty*/
|
||||
VARVOLATILE UINT16 smstate = 0x00;
|
||||
HW_EscReadWord(smstate,(ESC_SYNCMAN_CONTROL_OFFSET + SIZEOF_SM_REGISTER));
|
||||
/*ECATCHANGE_START(V5.11) ECAT7*/
|
||||
smstate = SWAPWORD(smstate);
|
||||
/*ECATCHANGE_END(V5.11) ECAT7*/
|
||||
|
||||
if(!(smstate & SM_STATUS_MBX_BUFFER_FULL))
|
||||
{
|
||||
UINT8 BytesLeft = u16SendMbxSize - (mbxSize + MBX_HEADER_SIZE);
|
||||
|
||||
/*Write last Byte to trigger mailbox full flag*/
|
||||
/*Read last 2 Bytes and write them again (required if low Byte of the WORD were written before)*/
|
||||
u16dummy = 0;
|
||||
if(BytesLeft < 2)
|
||||
{
|
||||
/*The last 2Bytes are overlapping the already written buffer*/
|
||||
|
||||
/*Get the valid 16Bit address*/
|
||||
UINT32 LastDataAddress = ((mbxSize + MBX_HEADER_SIZE)/2)*2;
|
||||
|
||||
/*Copy the buffer to overwrite*/
|
||||
MEMCPY((UINT16 *)&u16dummy,(((UINT8 *)pMbx) + LastDataAddress),(2 - BytesLeft));
|
||||
}
|
||||
|
||||
HW_EscWriteWord(u16dummy,(u16EscAddrSendMbx + u16SendMbxSize - 2));
|
||||
}
|
||||
}
|
||||
|
||||
/* store last send mailbox service for a possible repeat
|
||||
one buffer includes the last send service (psRepeatMbx),
|
||||
the other one the actual service to be sent (psReadMbx),
|
||||
there is no buffer available for a mailbox receive service
|
||||
until the last sent buffer was read from the master
|
||||
the exception is after the INIT2PREOP transition, in that
|
||||
case there is no last sent service (psReadMbx = 0) */
|
||||
if ( psReadMbx )
|
||||
psWriteMbx = NULL;
|
||||
else
|
||||
{
|
||||
/* only the first time after the INIT2PREOP-transition */
|
||||
psWriteMbx = &asMbx[1];
|
||||
}
|
||||
// HBu 17.06.06: psRepeatMbx was already updated in MBX_MailboxReadInd
|
||||
// psRepeatMbx = psReadMbx;
|
||||
psReadMbx = pMbx;
|
||||
|
||||
/* set flag that send mailbox is full now */
|
||||
bSendMbxIsFull = TRUE;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
\brief This function is called cyclically to check if a received Mailbox service was
|
||||
stored.
|
||||
*////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MBX_Main(void)
|
||||
{
|
||||
TMBX MBXMEM *pMbx = NULL;
|
||||
|
||||
do
|
||||
{
|
||||
UINT8 result = 0;
|
||||
|
||||
pMbx = GetOutOfMbxQueue(&sMbxReceiveQueue);
|
||||
if ( pMbx )
|
||||
result = MailboxServiceInd(pMbx);
|
||||
|
||||
if ( result != 0 )
|
||||
{
|
||||
/* Mailbox error response: type 0 (mailbox service protocol) */
|
||||
pMbx->MbxHeader.Length = 4;
|
||||
pMbx->MbxHeader.Flags[MBX_OFFS_TYPE] &= ~(MBX_MASK_TYPE);
|
||||
pMbx->Data[0] = SWAPWORD(MBXSERVICE_MBXERRORCMD);
|
||||
pMbx->Data[1] = SWAPWORD(result);
|
||||
MBX_MailboxSendReq(pMbx, 0);
|
||||
}
|
||||
}
|
||||
while ( pMbx != NULL );
|
||||
|
||||
|
||||
|
||||
if ( bReceiveMbxIsLocked )
|
||||
{
|
||||
/* the work on the receive mailbox is locked, check if it can be unlocked (if all
|
||||
mailbox commands has been sent */
|
||||
MBX_CheckAndCopyMailbox();
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#include <math.h>
|
||||
|
||||
namespace {
|
||||
/* ---- 校验常量与辅助函数 ---- */
|
||||
constexpr float kMinValidVdda = 2.7f;
|
||||
constexpr float kMaxValidVdda = 3.6f;
|
||||
constexpr float kMinValidTemperature = -40.0f;
|
||||
@@ -24,16 +25,14 @@ static bool isFiniteFloat(float value)
|
||||
{
|
||||
return isfinite(value) != 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
/* ---- DMA 配置 ---- */
|
||||
constexpr uint32_t kDmaPeriph = DMA0;
|
||||
constexpr dma_channel_enum kDmaChannel = DMA_CH0;
|
||||
constexpr uint32_t kChannelCount = 3U;
|
||||
constexpr uint32_t kVinGpio = GPIOA;
|
||||
constexpr uint32_t kVinPin = GPIO_PIN_6;
|
||||
volatile uint16_t s_dmaBuffer[kChannelCount] = {0U, 0U, 0U};
|
||||
volatile uint8_t s_dmaReady = 0U;
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@@ -66,14 +65,51 @@ void Adc::initDma()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 ADC、DMA 与规则通道扫描配置。
|
||||
* @brief 带超时等待 ADC_CTL1 指定位清零。
|
||||
* @param[in] bit ADC_CTL1 位(RSTCLB/CLB)。
|
||||
* @param[in] timeout_ms 超时毫秒数。
|
||||
* @retval bool true=位已清零;false=超时(硬件未响应)。
|
||||
* @note 替代库函数 adc_calibration_enable 内部的无超时死等——
|
||||
* ADC 硬件异常时库函数会永久挂住 init,且无任何诊断。
|
||||
*/
|
||||
static bool waitAdcCtl1BitClear(uint32_t bit, uint32_t timeout_ms)
|
||||
{
|
||||
const uint32_t start = SysTickTimer::getMs();
|
||||
while ((ADC_CTL1(CONFIG_ADC_INSTANCE) & bit) != 0U) {
|
||||
if ((SysTickTimer::getMs() - start) >= timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带超时执行 ADC 校准(RSTCLB → CLB 两段各等清零)。
|
||||
* @retval uint8_t Adc::InitFault 故障码(0=校准完成;1=RSTCLB 超时;2=CLB 超时)。
|
||||
*/
|
||||
static uint8_t runCalibrationWithTimeout()
|
||||
{
|
||||
constexpr uint32_t kCalTimeoutMs = 10U; /* 校准实际仅数十 µs,10ms 裕量极大 */
|
||||
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_RSTCLB;
|
||||
if (!waitAdcCtl1BitClear(ADC_CTL1_RSTCLB, kCalTimeoutMs)) {
|
||||
return Adc::kFaultCalRstTimeout;
|
||||
}
|
||||
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_CLB;
|
||||
if (!waitAdcCtl1BitClear(ADC_CTL1_CLB, kCalTimeoutMs)) {
|
||||
return Adc::kFaultCalTimeout;
|
||||
}
|
||||
return Adc::kFaultOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 ADC、DMA 与规则通道扫描配置,并验证采样链有效。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @retval uint8_t Adc::InitFault 故障码(0=校准完成且 VREFINT→VDDA 换算在合理区间)。
|
||||
* @note 按 `VIN_ADC / TEMP / VREFINT` 三通道顺序启用连续扫描与 DMA 采样。
|
||||
* 参考 GD32E508 文档要求,内部温度与 VREFINT 通道使用长采样时间,以满足 ts_temp 要求。
|
||||
*/
|
||||
void Adc::init()
|
||||
uint8_t Adc::init()
|
||||
{
|
||||
rcu_periph_clock_enable(RCU_ADC0);
|
||||
rcu_periph_clock_enable(RCU_GPIOA);
|
||||
@@ -104,34 +140,17 @@ void Adc::init()
|
||||
|
||||
adc_enable(CONFIG_ADC_INSTANCE);
|
||||
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
||||
adc_calibration_enable(CONFIG_ADC_INSTANCE);
|
||||
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
||||
const uint8_t calFault = runCalibrationWithTimeout();
|
||||
if (calFault != kFaultOk) {
|
||||
return calFault;
|
||||
}
|
||||
adc_software_trigger_enable(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL);
|
||||
SysTickTimer::delayMs(1U);
|
||||
s_dmaReady = 1U;
|
||||
}
|
||||
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
|
||||
|
||||
/**
|
||||
* @brief 查询 ADC DMA 采样是否已经就绪。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示 DMA 已启动并完成初始稳定等待,false 表示尚未就绪。
|
||||
*/
|
||||
bool Adc::dmaReady()
|
||||
{
|
||||
return s_dmaReady != 0U;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 ADC DMA 原始缓冲区首地址。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval const uint16_t* 返回 DMA 原始缓冲区首地址。
|
||||
* @note 缓冲顺序固定为 `VIN_ADC / TEMP / VREFINT`。
|
||||
*/
|
||||
const uint16_t *Adc::dmaBuffer()
|
||||
{
|
||||
return (const uint16_t *)s_dmaBuffer;
|
||||
/* 验证采样链:DMA 循环搬运在跑且 VREFINT 换算 VDDA 落在合理区间。
|
||||
* calculateVdda 对非法 raw/超范围返回 0.0f。 */
|
||||
const float vdda = calculateVdda(readVrefRaw());
|
||||
return ((vdda >= kMinValidVdda) && (vdda <= kMaxValidVdda)) ? kFaultOk : kFaultVddaInvalid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1184
-384
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,8 @@
|
||||
#include "main.h"
|
||||
#include "device/timer.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/config.h"
|
||||
|
||||
/**
|
||||
@@ -48,16 +50,38 @@ void SchedulerTimer::configureNvic()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化用户调度定时器模块。
|
||||
* @brief 初始化用户调度定时器模块并验证节拍运行。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 依次完成定时器本体配置与 NVIC 中断配置。
|
||||
* @retval uint8_t SchedulerTimer::InitFault 故障码(0=回读一致且 tick_100us 已前进)。
|
||||
* @note 依次完成定时器本体配置与 NVIC 中断配置;
|
||||
* 验证覆盖"中断向量/优先级配错导致调度器静默停摆"这类纯写配置发现不了的故障。
|
||||
*/
|
||||
void SchedulerTimer::init()
|
||||
uint8_t SchedulerTimer::init()
|
||||
{
|
||||
configureTimer();
|
||||
configureNvic();
|
||||
|
||||
/* 回读:计数器已使能、PSC/CAR 与配置一致 */
|
||||
if ((TIMER_CTL0(CONFIG_SCHED_TIMER_INSTANCE) & TIMER_CTL0_CEN) == 0U) {
|
||||
return kFaultNotEnabled;
|
||||
}
|
||||
if (TIMER_PSC(CONFIG_SCHED_TIMER_INSTANCE) != CONFIG_SCHED_TIMER_PRESCALER) {
|
||||
return kFaultPscMismatch;
|
||||
}
|
||||
if (TIMER_CAR(CONFIG_SCHED_TIMER_INSTANCE) != CONFIG_SCHED_TIMER_PERIOD) {
|
||||
return kFaultCarMismatch;
|
||||
}
|
||||
|
||||
/* 有界等待 100us 中断节拍前进(50ms 预算 = 500 个 tick) */
|
||||
const uint32_t start = g_scheduler.tick_100us;
|
||||
const uint32_t deadline = SysTickTimer::getMs() + 50U;
|
||||
while (g_scheduler.tick_100us == start) {
|
||||
if ((int32_t)(SysTickTimer::getMs() - deadline) >= 0) {
|
||||
return kFaultNoTick;
|
||||
}
|
||||
}
|
||||
return kFaultOk;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,4 +106,9 @@ void SchedulerTimer::onInterrupt()
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `SchedulerTimer::onInterrupt()`。
|
||||
*/
|
||||
extern "C" void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
|
||||
extern "C" void TIMER2_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
SchedulerTimer::onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -7,14 +7,23 @@
|
||||
*/
|
||||
class SchedulerTimer {
|
||||
public:
|
||||
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
|
||||
enum InitFault : uint8_t {
|
||||
kFaultOk = 0U, /**< 成功 */
|
||||
kFaultNotEnabled = 1U, /**< CTL0.CEN 未置位(定时器未使能) */
|
||||
kFaultPscMismatch = 2U, /**< PSC 回读与配置不一致 */
|
||||
kFaultCarMismatch = 3U, /**< CAR 回读与配置不一致 */
|
||||
kFaultNoTick = 4U, /**< 100us 中断节拍未前进(NVIC/向量/优先级异常) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 初始化用户调度定时器及其中断。
|
||||
* @brief 初始化用户调度定时器及其中断,并验证节拍真实运行。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @retval uint8_t InitFault 故障码(0=寄存器回读一致且 tick_100us 中断节拍已前进)。
|
||||
* @note 当前用于建立 100us 基准节拍,驱动 TaskManager::tick100us()。
|
||||
*/
|
||||
static void init();
|
||||
static uint8_t init();
|
||||
|
||||
/**
|
||||
* @brief TIMER2 中断处理入口。
|
||||
|
||||
@@ -23,31 +23,22 @@ public:
|
||||
uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */
|
||||
};
|
||||
|
||||
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报,网页解码文字见 initial.h 注释) */
|
||||
enum InitFault : uint8_t {
|
||||
kFaultOk = 0U, /**< 成功 */
|
||||
kFaultCalRstTimeout = 1U, /**< 校准复位(RSTCLB)等待超时,ADC 硬件无响应 */
|
||||
kFaultCalTimeout = 2U, /**< 校准(CLB)等待超时 */
|
||||
kFaultVddaInvalid = 3U, /**< 校准后 VREFINT→VDDA 采样链无效(DMA/通道/供电异常) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 初始化 ADC 硬件、扫描通道与 DMA 循环缓存。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @retval uint8_t InitFault 故障码(0=校准完成且 VDDA 采样链有效)。
|
||||
* @note 完成 ADC 三通道扫描、DMA 循环搬运、PA6(VIN_ADC) 模拟输入配置以及内部通道校准。
|
||||
*/
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* @brief 查询 ADC DMA 缓存是否已完成首次有效填充。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示 DMA 循环缓存已可用,false 表示仍未准备好。
|
||||
*/
|
||||
static bool dmaReady();
|
||||
|
||||
/**
|
||||
* @brief 获取 ADC DMA 原始循环缓存只读指针。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval const uint16_t* 返回长度为 \p kChannelCount 的原始缓存指针。
|
||||
* @note 缓冲区顺序为:VIN_ADC → 温度通道 → VREF 通道。
|
||||
*/
|
||||
static const uint16_t *dmaBuffer();
|
||||
static uint8_t init();
|
||||
|
||||
/**
|
||||
* @brief 读取指定通道在 DMA 缓存中的原始 ADC 值。
|
||||
@@ -128,6 +119,4 @@ private:
|
||||
static void initDma();
|
||||
};
|
||||
|
||||
using adc_sample_t = Adc::Sample;
|
||||
|
||||
#endif /* DEVICE_ADC_H */
|
||||
+180
-35
@@ -3,17 +3,16 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief CANFD 状态报文的数据长度,单位字节。 */
|
||||
#define CANFD_STATUS_DLEN 12U
|
||||
#include "sys/config.h"
|
||||
|
||||
/** @brief 每通道反馈快照中保存的最近一帧最大数据长度,受 SPI 应答帧总长度约束。 */
|
||||
/** @brief 每通道反馈快照中保存的最近一帧最大数据长度(本地诊断用)。 */
|
||||
#define CANFD_FEEDBACK_LAST_DATA_MAX 32U
|
||||
|
||||
/** @brief 每通道不重复 ID 快照表最大条目数。 */
|
||||
#define CANFD_ID_SNAPSHOT_MAX 16U
|
||||
/** @brief 每通道不重复 ID 快照表最大条目数(CONFIG_CANFD_ID_SNAPSHOT_MAX)。 */
|
||||
#define CANFD_ID_SNAPSHOT_MAX CONFIG_CANFD_ID_SNAPSHOT_MAX
|
||||
|
||||
/** @brief 快照条目中最大数据字节数(等于 CANFD DLC 上限)。 */
|
||||
#define CANFD_ID_SNAPSHOT_DATA_MAX 64U
|
||||
/** @brief 快照条目中最大数据字节数(CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX)。 */
|
||||
#define CANFD_ID_SNAPSHOT_DATA_MAX CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX
|
||||
|
||||
/**
|
||||
* @brief CANFD 通信模块。
|
||||
@@ -31,6 +30,23 @@ public:
|
||||
kModeBridge0To12 = 2U, /**< CAN0 桥接模式:硬件过滤等同 AllPass,CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CAN 发送结果枚举,枚举值即 SPI 错误响应码,便于 handler 直接透传。
|
||||
* @note 0xE0/0xE1 保留给 handler 入口校验(payload 过短/通道非法),
|
||||
* 不经 transmitFrame 返回;0xE2~0xE5 由 transmitFrame 区分四种失败原因;
|
||||
* 0xE6=通道处于收发器 Silent(只听不发),在入口即拒绝,不计任何错误计数。
|
||||
* 0xE7=通道被电机 OTA 独占锁定(EtherCAT/SPI 跨总线排他),同样不计错误计数。
|
||||
*/
|
||||
enum class CanTxResult : uint8_t {
|
||||
kOk = 0x00U, /**< 发送成功,已进入硬件邮箱。 */
|
||||
kErrBusOff = 0xE2U, /**< 通道处于 BUS_OFF,不发送。 */
|
||||
kErrTecHigh = 0xE3U, /**< TEC > 96,暂停发送保护通道。 */
|
||||
kErrMailbox = 0xE4U, /**< 3 个硬件邮箱全 PENDING,abort+重投仍失败。 */
|
||||
kErrInvalid = 0xE5U, /**< 入口参数无效(channelIndex/dlc/data 等)。 */
|
||||
kErrSilent = 0xE6U, /**< 通道收发器处于 Silent(驱动器关断),拒绝发送。 */
|
||||
kErrOtaLocked = 0xE7U, /**< 通道被电机 OTA 独占(跨总线排他),拒绝发送,不计错误计数。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CANFD 运行诊断统计信息。
|
||||
*/
|
||||
@@ -41,7 +57,15 @@ public:
|
||||
uint32_t rx_drop_count;
|
||||
uint32_t rx_fifo_full_count;
|
||||
uint32_t rx_fifo_overfull_count;
|
||||
uint32_t tx_drop_count;
|
||||
/** 取帧期间被硬件换帧、整帧丢弃的次数(1.6.6,TS/DLC 前后不一致即判定)。
|
||||
* 这是"读到半条帧"的唯一可观测量,稳态下应当只在高负载时缓慢增长。 */
|
||||
uint32_t rx_torn_count;
|
||||
/** 释放邮箱后等待 RFD 硬件自清超时的次数(手册 26.3.4 第 3 步要求等待)。 */
|
||||
uint32_t rx_release_timeout_count;
|
||||
/** 内容级拼接帧拦截次数(1.6.11):(id, data) 结构自相矛盾(STATUS 的
|
||||
* 温度/电压出量程、CONFIG 的未使用区非 0)⇒ 整帧作废。与 rx_torn 互补:
|
||||
* torn 挡"取数期间窗口被换",这个挡"窗口没换但 FIFO 缓冲指针错位"。 */
|
||||
uint32_t rx_content_reject_count;
|
||||
uint32_t tx_abort_count;
|
||||
uint32_t last_rx_id;
|
||||
uint32_t last_tx_id;
|
||||
@@ -58,7 +82,7 @@ public:
|
||||
*/
|
||||
struct ChannelStats {
|
||||
uint32_t rx_count; /**< 该通道累计成功接收帧数。 */
|
||||
uint32_t tx_count; /**< 该通道累计成功发送帧数。 */
|
||||
uint32_t tx_count; /**< 该通道累计由硬件 MTFNERR 确认的成功发送帧数。 */
|
||||
uint32_t rx_drop_count; /**< 该通道累计软件丢帧数。 */
|
||||
uint32_t error_count; /**< 该通道累计错误计数(与发送邮箱失败合并)。 */
|
||||
uint32_t last_rx_id; /**< 该通道最近一帧报文标识符。 */
|
||||
@@ -69,7 +93,7 @@ public:
|
||||
uint8_t last_rx_data[CANFD_FEEDBACK_LAST_DATA_MAX];
|
||||
uint8_t tec; /**< 最近一次采样时硬件发送错误计数器 (0~255)。 */
|
||||
uint8_t rec; /**< 最近一次采样时硬件接收错误计数器 (0~255)。 */
|
||||
uint8_t bus_state; /**< 总线状态位图:bit0=init_mode, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning。 */
|
||||
uint8_t bus_state; /**< 总线状态位图:bit0=init_mode, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning, bit5=tx_silent(收发器 Silent,只听不发)。bit0~4 是控制器/总线侧状态,bit5 是本端 S 脚状态。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -104,18 +128,36 @@ public:
|
||||
uint8_t count; /**< 当前占用条目数。 */
|
||||
uint32_t seq; /**< 全局更新序号(每次 refresh 时递增)。 */
|
||||
};
|
||||
/**
|
||||
* @brief CANFD 状态发送载荷。
|
||||
*/
|
||||
struct StatusPayload {
|
||||
uint16_t temp_raw;
|
||||
uint16_t vref_raw;
|
||||
float vdda;
|
||||
float temperature;
|
||||
};
|
||||
|
||||
static void init();
|
||||
|
||||
/** L0 自检故障原因码(selfTestL0 输出;经 0xA3 fault_codes[] 上报,高 3 位带通道号见 initial.h) */
|
||||
enum SelfTestFault : uint8_t {
|
||||
kSelfTestOk = 0U, /**< 通过 */
|
||||
kSelfTestHwInit = 1U, /**< 控制器初始化失败(重试 5 次后仍不成功) */
|
||||
kSelfTestStuckInit = 2U, /**< 卡在初始化模式(IWS 未清零) */
|
||||
kSelfTestStbLevel = 3U, /**< 收发器 STB 输出未拉低(配置未生效) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief L0 轻量自检(不向物理总线发帧),仅校验 GD32 侧控制器自身状态。
|
||||
* @param[out] fault_code 首个失败通道的 SelfTestFault 原因码;全部通过时为 kSelfTestOk。允许空。
|
||||
* @param[out] fault_channel 首个失败通道号 0/1/2;全部通过时无意义。允许空。
|
||||
* @retval true 所有已使能通道通过;false 无使能通道或任一路失败。
|
||||
* @note 检查:硬件 init 结果、退出 init mode、收发器 STB 已置 Normal。
|
||||
* 1.5.35 移除 BUS_OFF 与 TEC/REC 检查:它们是总线侧状态,
|
||||
* 通道未接从站(RX 浮空噪声)时会误报——「没接设备」不等于错误;
|
||||
* 总线健康经 0xC1 反馈帧(bus_state/TEC/REC)运行期上报,网页 CAN 页可见。
|
||||
*/
|
||||
static bool selfTestL0(uint8_t *fault_code = nullptr, uint8_t *fault_channel = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 查询指定通道是否已使能。
|
||||
* @param[in] channelIndex 通道索引 0/1/2。
|
||||
* @retval bool true=使能,false=禁用。
|
||||
*/
|
||||
static bool isChannelEnabled(uint8_t channelIndex);
|
||||
|
||||
/**
|
||||
* @brief 切换 CANFD 接收过滤模式。
|
||||
*/
|
||||
@@ -124,14 +166,55 @@ public:
|
||||
/** @brief 查询当前 CANFD 接收过滤模式。 */
|
||||
static Mode mode();
|
||||
|
||||
static void poll();
|
||||
static void prepareStatus(const StatusPayload *status);
|
||||
static bool prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status);
|
||||
static void sendPrepared();
|
||||
static bool hasRxFrame();
|
||||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||||
static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
/**
|
||||
* @brief 直接向指定 CAN 通道发送一帧(绕过软件队列)。
|
||||
* @param allow_abort 邮箱全占时是否允许 abort 一个 PENDING 邮箱并重投一次。
|
||||
* - true(底层非阻塞顶替):适合最新单发覆盖场景;高总线负载下慎用(可能 abort 掉正在等仲裁的帧);
|
||||
* - false(突发/队列路径):邮箱满直接返回 kErrMailbox,交由 enqueueTx
|
||||
* 的软件队列缓存,由 TX 完成中断回灌——避免 abort 把同通道已装载的
|
||||
* 旧帧静默丢弃(>3 帧同通道突发只剩最后 3 帧上线的根因)。
|
||||
*/
|
||||
static CanTxResult transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false,
|
||||
bool allow_abort = true);
|
||||
|
||||
/** @brief 立即停掉指定通道所有 PENDING 发送邮箱(阻止 auto_retrans 残留重发,供停等式协议用)。 */
|
||||
static void abortPendingTx(uint8_t channelIndex);
|
||||
|
||||
/** @brief 运行时切换指定通道 CAN 自动重发(ARD 位,需 init-mode);停等式 OTA 期间关闭、结束后恢复。 */
|
||||
static void setChannelAutoRetrans(uint8_t channelIndex, bool enable);
|
||||
|
||||
/** @brief 运行时压制指定通道 TX 帧的 BRS:压制中 FD 帧数据段回退仲裁段速率。
|
||||
* 电机 BOOT 无法正确接收 BRS 高速段(见 sys/config.h CONFIG_CAN_FD_BRS_ENABLE 注释),
|
||||
* 电机 OTA relay 经 enterState 钩子与 setChannelAutoRetrans 同点切换,OTA 结束必恢复。 */
|
||||
static void setChannelBrsSuppress(uint8_t channelIndex, bool suppress);
|
||||
|
||||
/** @brief 切换指定通道收发器(TCAN1057AV)的 Silent 模式:只听不发。
|
||||
* S 脚(CAN0=PA8/CAN1=PB14/CAN2=PB9)置高 → 驱动器关断、**接收机照常工作**
|
||||
* (手册 Table 8-4:RXD 仍镜像总线),所以静默期间接收与上报完全不受影响;
|
||||
* 置低 → 回 Normal。这是硬件级禁止发送,S 脚内部上拉,MCU 复位/浮空即为 Silent。
|
||||
* @note 进 Silent 前先 abort 本通道 PENDING 邮箱:驱动器已关,残留邮箱只会让
|
||||
* 控制器因收不到 ACK 而白涨 TEC。
|
||||
* 退 Silent 时忙等收发器模式建立时间 tMODE=20µs 再返回,否则紧跟的第一帧
|
||||
* 驱动不上线。用 DWT CYCCNT 忙等,可在 CS EXTI ISR 上下文调用
|
||||
* (SysTickTimer::delayMs 在该上下文会死等)。
|
||||
* 与 canfd.cpp 内 s_chanQuiet(空通道上报节流)无关,也别混为一谈。
|
||||
* 静默不持久化:上电 init 一律回 Normal。
|
||||
* @retval true 通道合法且已生效;false 通道非法。 */
|
||||
static bool setTxSilent(uint8_t channelIndex, bool silent);
|
||||
|
||||
/** @brief 查询指定通道是否处于收发器 Silent(唯一真源 s_txSilent,0xC1 bus_state bit5 用它)。 */
|
||||
static bool isTxSilent(uint8_t channelIndex);
|
||||
|
||||
/** @brief 读取指定通道 CAN_ERR 寄存器原始值(事件时刻只读快照,不依赖 1Hz 采样)。
|
||||
* 解码:TEC=bit16-23,REC=bit24-31,ERRN=bit4-6;无效通道返回 0。仅供诊断日志,禁用于发送门控。 */
|
||||
static uint32_t readChannelErrorReg(uint8_t channelIndex);
|
||||
|
||||
/** @brief 读取并清除指定通道的 TX 错误事件(ARD=1 单发下 MTE 置位 = 帧已确认丢失)。
|
||||
* 供停等式 OTA 在丢帧后立即重发,免等 ACK 超时;仅主循环调用。 */
|
||||
static bool consumeTxError(uint8_t channelIndex);
|
||||
static const Diag *diag();
|
||||
|
||||
/**
|
||||
@@ -156,13 +239,46 @@ public:
|
||||
*/
|
||||
static bool copyIdSnapshotTable(uint8_t channelIndex, IdSnapshotTable *out);
|
||||
|
||||
static void setNormalMode();
|
||||
static void setSilentMode();
|
||||
static void onRx0Interrupt();
|
||||
static void handleRxInterrupt(uint8_t channelIndex);
|
||||
static void handleTxInterrupt(uint8_t channelIndex);
|
||||
|
||||
/**
|
||||
* @brief 突发排入软件 TX 队列(供 SPI 0xC7 burst handler / 突发场景使用)。
|
||||
*
|
||||
* 解决「3 个发送邮箱是物理上限、>3 帧突发必丢」的问题:
|
||||
* - 队列空时尝试直通 transmitFrame(不排队、零延迟)。
|
||||
* - 邮箱满时把本帧追加到软件队列,TX 完成中断里持续出队回灌,
|
||||
* 保证任意大小突发(同通道 ≤ 队列深度)都不丢帧。
|
||||
*
|
||||
* 同步:本函数可在 EXTI15 ISR 内运行(SPI burst handler),与 CAN TX ISR
|
||||
* 并发;队列 Push/Pop 通过关中段互斥,无需 LDREX/STREX。
|
||||
*
|
||||
* @param[in] channelIndex 目标通道(0/1/2)。
|
||||
* @param[in] ext_id 29 位扩展 ID。
|
||||
* @param[in] data 数据负载;dlc>0 时必填。
|
||||
* @param[in] dlc 数据长度 0..64。
|
||||
* @retval CanTxResult::kOk 已进入硬件邮箱 OR 已入软件队列(统一视为已受理)。
|
||||
* @retval CanTxResult::kErrMailbox 软件队列也满。
|
||||
* @retval CanTxResult::kErrInvalid 参数非法。
|
||||
*/
|
||||
static CanTxResult enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
/** @brief 软件 TX 队列当前积压帧数(统计/诊断用)。 */
|
||||
static uint32_t txQueueBacklog(uint8_t channelIndex);
|
||||
|
||||
/** @brief 接收队列容量深度(2 的幂;8kHz PDO 下主循环间隙短,加深缓冲防 STATUS 溢出丢帧)。 */
|
||||
static constexpr uint8_t rxQueueDepth() { return kRxQueueDepth; }
|
||||
|
||||
/** @brief 查询指定通道软件接收队列当前积压帧数(无锁 SPSC,由 tail 与 head 计算)。 */
|
||||
static uint32_t rxQueueCount(uint8_t channelIndex);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kRxQueueDepth = 16U;
|
||||
static constexpr uint8_t kRxQueueDepth = 64U; /* 16→64:1kHz STATUS×3 在 PDI 风暴下不再轻易打满 */
|
||||
static constexpr uint8_t kRxQueueMask = kRxQueueDepth - 1U;
|
||||
/** 每通道软件突发队列深度:覆盖整块突发(CAN_BURST_HARD_MAX=16 帧)中同一通道的
|
||||
* 全部超载帧(3 邮箱之外最多 13 帧 = 16-3),保证任意 DLC 的整块突发不因队列满丢帧。 */
|
||||
static constexpr uint8_t kTxQueueDepth = 13U;
|
||||
|
||||
struct RxFrame {
|
||||
uint8_t channel;
|
||||
@@ -171,13 +287,33 @@ private:
|
||||
uint8_t length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Lock-Free 单生产者单消费者 (SPSC) 环形接收队列。
|
||||
* @note 生产者为 CAN RX ISR (Preempt 0) 写入 tail,消费者为 consumeRxFrame (Thread) 推进 head。
|
||||
* 移除共享 count 变量,消除 BASEPRI=0x10 临界区对 Preempt 0 无效导致的并发撕裂与计数下溢风险。
|
||||
*/
|
||||
struct RxQueue {
|
||||
RxFrame frames[kRxQueueDepth];
|
||||
volatile uint8_t head;
|
||||
volatile uint8_t tail;
|
||||
};
|
||||
|
||||
/** 排队等待硬件邮箱空位的 CAN 帧(仅 SDO 突发用,payload ≤ 64B)。 */
|
||||
struct TxQueuedFrame {
|
||||
uint32_t id;
|
||||
uint8_t data[64];
|
||||
uint8_t dlc;
|
||||
};
|
||||
struct TxQueue {
|
||||
TxQueuedFrame frames[kTxQueueDepth];
|
||||
volatile uint8_t head;
|
||||
volatile uint8_t tail;
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** @brief CAN TX ISR 内驱动:把队列里能装的全部装进硬件邮箱。 */
|
||||
static void drainTxQueueIsr(uint8_t channelIndex);
|
||||
|
||||
static void initGpio();
|
||||
/**
|
||||
* @brief 根据当前模式为指定控制器配置接收过滤器。
|
||||
@@ -188,20 +324,29 @@ private:
|
||||
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode);
|
||||
static bool initNominalTiming(uint32_t canPeriph);
|
||||
static bool initFdMode(uint32_t canPeriph);
|
||||
static void buildStatusFrame(const StatusPayload *status);
|
||||
static bool enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status);
|
||||
|
||||
static void printClockInfo();
|
||||
static void initChannelHardware(uint8_t i);
|
||||
static void configureAllFilters();
|
||||
static void printInitResult();
|
||||
static void printErrorState();
|
||||
static void runSelfTest();
|
||||
static void runLoopbackTest();
|
||||
static void dumpRegisters();
|
||||
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
static TxQueue s_txQueues[3]; /**< CAN 突发软件队列(邮箱装不下时缓存) */
|
||||
static uint8_t s_nextConsumeChannel;
|
||||
static volatile Mode s_mode;
|
||||
static ChannelStats s_channelStats[3];
|
||||
static bool s_brsSuppress[3]; /**< TX BRS 压制(电机 OTA 期间该通道 DLC>8 帧禁 BRS) */
|
||||
static volatile uint8_t s_txSilent[3]; /**< 收发器 Silent(只听不发):1=S 脚已置高,TX 入口拒绝 */
|
||||
static ChannelFeedback s_feedback[3];
|
||||
/** 各通道硬件 init(时序+退出 init mode)是否成功 */
|
||||
static bool s_channelHwOk[3];
|
||||
static uint32_t s_feedbackSeq;
|
||||
static IdSnapshotTable s_idSnapshot[3];
|
||||
};
|
||||
|
||||
using canfd_diag_t = CanFd::Diag;
|
||||
using canfd_status_payload_t = CanFd::StatusPayload;
|
||||
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @file motor_state.cpp
|
||||
* @brief 多电机状态表实现 — CAN STATUS 帧识别、内存缓存、SPI 推送序列化。
|
||||
*
|
||||
* 本模块的函数只在主循环上下文调用(taskCanRxParse / taskHeartbeat / pushStep),
|
||||
* 但消费者不止主循环:EtherCAT 的 buildInput 跑在 PDI_Isr,会抢占 STATUS 写侧,
|
||||
* 故 STATUS 的 seq/data 由 MotorSlot.wr_seq(seqlock)保护,见 onCanFrame。
|
||||
* SPI 侧的 buildPayload 与写侧同上下文,天然无此问题。
|
||||
*/
|
||||
|
||||
#include "device/motor_state.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "main.h"
|
||||
#include <cstring>
|
||||
|
||||
/* ── 全局状态表实例 ── */
|
||||
static MotorStateTable s_motorTable = {};
|
||||
|
||||
namespace MotorState {
|
||||
|
||||
MotorStateTable &table()
|
||||
{
|
||||
return s_motorTable;
|
||||
}
|
||||
|
||||
void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
|
||||
{
|
||||
/* BOOT 心跳 0x103(固定 ID,不带节点号):电机在 Bootloader IDLE 每 ~10ms 发一次。
|
||||
* 记录最近收到时刻与通道,供网页显示独立 BOOT 状态(不归属任何 Nx)。
|
||||
* 注意:必须在 STATUS 掩码过滤之前拦截,否则 0x103 会被下面的掩码判断丢弃。 */
|
||||
if (id == kOtaBootHeartbeatId) {
|
||||
s_motorTable.boot_hb_sec = g_app_uptime_sec;
|
||||
s_motorTable.boot_channel = channel;
|
||||
s_motorTable.boot_active = 1U;
|
||||
/* 0x103 仅更新 boot_active 状态,不再串口打印(高频心跳会刷屏)。 */
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t masked = id & kMotorStatusIdMask;
|
||||
|
||||
/* STATUS 帧 0x000N0100 (DLC 48, 实时值,字节 32~35 = InitStatus) */
|
||||
if (masked == kMotorStatusIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
/* seqlock 写侧(1.6.5):奇数=本槽 seq/data 正在改。EtherCAT 的 buildInput
|
||||
* 跑在 PDI_Isr,会抢占这里;抢占落在下面那次 memcpy 中途就会发布
|
||||
* "新 seq + 半新半旧 48B"——两帧字节拼成一个 STATUS,边界若落在 float 的
|
||||
* 符号/指数字节上就是网页上的离谱值。缺的不是顺序而是让消费者能认出
|
||||
* "这一段没写完"的标记。 */
|
||||
++slot.wr_seq; /* → 奇数 */
|
||||
__DMB(); /* 普通写不得重排到奇/偶两次自增之外 */
|
||||
|
||||
slot.node_id = node;
|
||||
slot.channel = channel;
|
||||
slot.online = 1U;
|
||||
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
|
||||
slot.rx_count++;
|
||||
slot.last_rx_sec = g_app_uptime_sec;
|
||||
slot.seq = ++s_motorTable.global_seq;
|
||||
|
||||
std::memcpy(slot.data, data, slot.dlc);
|
||||
if (slot.dlc < kMotorStatusDlc) {
|
||||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||||
}
|
||||
|
||||
__DMB();
|
||||
++slot.wr_seq; /* → 偶数:本槽整体一致 */
|
||||
return;
|
||||
}
|
||||
|
||||
/* CONFIG 帧 0x000N0101 (DLC 24, 目标值回报) */
|
||||
if (masked == kMotorConfigIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
/* CONFIG 帧不更新 online 状态(仅 STATUS 帧维护在线检测) */
|
||||
slot.node_id = node; /* 确保槽位不空 */
|
||||
slot.channel = channel;
|
||||
slot.config_rx_count++;
|
||||
slot.config_seq = ++s_motorTable.global_seq;
|
||||
|
||||
const uint8_t copyLen = (len <= kMotorConfigDlc) ? len : kMotorConfigDlc;
|
||||
std::memcpy(slot.config_data, data, copyLen);
|
||||
if (copyLen < kMotorConfigDlc) {
|
||||
std::memset(&slot.config_data[copyLen], 0, kMotorConfigDlc - copyLen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void checkOffline()
|
||||
{
|
||||
const uint32_t now = g_app_uptime_sec;
|
||||
uint8_t active = 0U;
|
||||
|
||||
for (uint8_t i = 0U; i < kMotorSlotCount; ++i) {
|
||||
MotorSlot &slot = s_motorTable.slots[i];
|
||||
if (slot.node_id == 0U) {
|
||||
continue; /* 空槽 */
|
||||
}
|
||||
if ((now - slot.last_rx_sec) >= kMotorOfflineSec) {
|
||||
slot.online = 0U;
|
||||
}
|
||||
if (slot.online != 0U) {
|
||||
active++;
|
||||
}
|
||||
}
|
||||
s_motorTable.active_count = active;
|
||||
|
||||
/* BOOT 心跳超时:>kBootHeartbeatTimeoutSec 无 0x103 → 电机已退出 BOOT。 */
|
||||
if (s_motorTable.boot_active != 0U &&
|
||||
(now - s_motorTable.boot_hb_sec) >= kBootHeartbeatTimeoutSec) {
|
||||
s_motorTable.boot_active = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
|
||||
{
|
||||
if (page >= kMotorTotalPages || buf == nullptr) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/* 需要的最小空间: header + 至少 1 entry */
|
||||
const uint16_t needed = kMotorPageHeaderLen + kMotorEntrySize;
|
||||
if (maxLen < needed) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
const uint8_t startIdx = static_cast<uint8_t>(page * kMotorEntriesPerPage);
|
||||
uint8_t count = 0U;
|
||||
|
||||
/* Header (4B) */
|
||||
buf[0] = page;
|
||||
buf[1] = kMotorTotalPages;
|
||||
buf[2] = 0U; /* entry_count, 稍后回填 */
|
||||
/* bit[6:0]=在线电机数,bit7=BOOT 心跳(0x103)活跃标志(供网页显示独立 BOOT 状态)。
|
||||
* active_count 上限 9,bit7 空闲可复用,旧解析方读到会多算 128,故 ESP32 侧需掩 0x7F。 */
|
||||
buf[3] = static_cast<uint8_t>((s_motorTable.active_count & 0x7FU)
|
||||
| (s_motorTable.boot_active ? kMotorActiveBootBit : 0U));
|
||||
|
||||
uint16_t offset = kMotorPageHeaderLen;
|
||||
|
||||
for (uint8_t i = 0U; i < kMotorEntriesPerPage; ++i) {
|
||||
const uint8_t slotIdx = static_cast<uint8_t>(startIdx + i);
|
||||
if (slotIdx >= kMotorSlotCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
const MotorSlot &slot = s_motorTable.slots[slotIdx];
|
||||
if (slot.node_id == 0U) {
|
||||
continue; /* 跳过空槽 */
|
||||
}
|
||||
|
||||
/* 检查剩余空间 */
|
||||
if (offset + kMotorEntrySize > maxLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf[offset + 0U] = slot.node_id;
|
||||
buf[offset + 1U] = slot.channel;
|
||||
buf[offset + 2U] = slot.online;
|
||||
buf[offset + 3U] = slot.dlc;
|
||||
writeU32Le(&buf[offset + 4U], slot.rx_count);
|
||||
writeU32Le(&buf[offset + 8U], slot.seq);
|
||||
std::memcpy(&buf[offset + 12U], slot.data, kMotorStatusDlc);
|
||||
/* STATUS DLC 48 后,CONFIG 字段整体后移 16B */
|
||||
writeU32Le(&buf[offset + 60U], slot.config_rx_count);
|
||||
writeU32Le(&buf[offset + 64U], slot.config_seq);
|
||||
std::memcpy(&buf[offset + 68U], slot.config_data, kMotorConfigDlc);
|
||||
|
||||
offset += kMotorEntrySize;
|
||||
count++;
|
||||
}
|
||||
|
||||
buf[2] = count;
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint16_t setPushPeriodMs(uint16_t ms)
|
||||
{
|
||||
/* 2026-08 请求驱动改造:0xC5 主动推送已移除,周期设置不再生效。
|
||||
* 仅钳制后回显,保留 0xFE 子命令协议兼容(ESP32 当前不发送)。 */
|
||||
if (ms < kMotorPeriodMinMs) { ms = kMotorPeriodMinMs; }
|
||||
if (ms > kMotorPeriodMaxMs) { ms = kMotorPeriodMaxMs; }
|
||||
return ms;
|
||||
}
|
||||
|
||||
} // namespace MotorState
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @file motor_state.h
|
||||
* @brief 多电机状态表 — CAN STATUS 帧识别、内存缓存、SPI 推送序列化。
|
||||
*
|
||||
* 设计要点:
|
||||
* - 仅在主循环上下文 (taskCanRxParse / taskHeartbeat / pushStep) 中读写,
|
||||
* 无 ISR 竞态,不需要临界区。
|
||||
* - GD32 不做浮点解码,只缓存原始 32B STATUS payload,由前端按 DBC 解码。
|
||||
* - 离线判定:3s 无 STATUS 帧 → online=0。
|
||||
*/
|
||||
|
||||
#ifndef DEVICE_MOTOR_STATE_H
|
||||
#define DEVICE_MOTOR_STATE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "sys/config.h"
|
||||
|
||||
/* ── CAN ID 匹配规则 (29-bit 扩展帧) ──
|
||||
* 物理 ID = 0x000N_TT_SS
|
||||
* N = 节点号 (1-9), 位于 bits[19:16]
|
||||
* TT = 消息类型: 01=STATUS, 02=CMD, 03=CONFIG
|
||||
* SS = 子类型
|
||||
* STATUS = 0x000N0100
|
||||
*/
|
||||
static constexpr uint32_t kMotorStatusIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibble [19:16] */
|
||||
static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */
|
||||
static constexpr uint32_t kMotorConfigIdMatch = 0x00000101U; /* CONFIG 类型码 (Motor→Host, DLC 24) */
|
||||
|
||||
static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
/* 同一节点号可能同时挂在多个 CAN 通道上(两台电机同号异线),
|
||||
* 状态表按 node×channel 双键存储,各通道独立槽位互不覆盖。 */
|
||||
static constexpr uint8_t kMotorChannels = 3U;
|
||||
static constexpr uint8_t kMotorSlotCount = kMotorMaxNodes * kMotorChannels; /* 9×3 = 27 */
|
||||
static constexpr uint8_t kMotorStatusDlc = 48U; /* v0.74:STATUS 帧 DLC 48(字节 32~35 = InitStatus) */
|
||||
static constexpr uint8_t kMotorConfigDlc = 24U; /* CONFIG 帧 DLC */
|
||||
static constexpr uint16_t kMotorOfflineSec = CONFIG_MOTOR_OFFLINE_MS / 1000U;
|
||||
|
||||
/* ── 0xC5 推送周期运行时可调(ESP32 经 0xC5 子命令 0xFE 下发)── */
|
||||
static constexpr uint16_t kMotorPeriodMinMs = 50U; /* 最快 20Hz */
|
||||
static constexpr uint16_t kMotorPeriodMaxMs = 5000U; /* 最慢 0.2Hz */
|
||||
static constexpr uint16_t kMotorTicksPerMs = 10U; /* 100us/tick → 每 ms 10 tick */
|
||||
static constexpr uint8_t kMotorSubCmdSetPeriod = 0xFEU; /* 0xC5 请求 payload[0]=0xFE:设推送周期 */
|
||||
|
||||
/* ── BOOT 心跳(0x103)──
|
||||
* 电机在 Bootloader IDLE 每 ~10ms 发一次 0x00000103(固定 ID,不带节点号)。
|
||||
* 网页据此显示一个独立 BOOT 状态(不归属任何 Nx)。 */
|
||||
static constexpr uint32_t kOtaBootHeartbeatId = 0x00000103U;
|
||||
static constexpr uint32_t kBootHeartbeatTimeoutSec = 3U; /* >3s 无 0x103 → 视为退出 BOOT */
|
||||
static constexpr uint8_t kMotorActiveBootBit = 0x80U; /* 0xC5 page0 header active 字节 bit7 = boot_active */
|
||||
|
||||
/* ── 序列化常量 (0xC5 payload) ── */
|
||||
static constexpr uint8_t kMotorEntrySize = 92U; /* 1+1+1+1+4+4+48+4+4+24 (STATUS+CONFIG) */
|
||||
static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */
|
||||
static constexpr uint8_t kMotorEntriesPerPage = 2U; /* (236-4)/92 = 2.52 → 2 */
|
||||
static constexpr uint8_t kMotorTotalPages =
|
||||
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/2) = 14 */
|
||||
|
||||
/// 单电机状态槽
|
||||
struct MotorSlot {
|
||||
uint8_t node_id; ///< 1-9, 0=空槽
|
||||
uint8_t channel; ///< 最后收到帧的 CAN 通道 (0/1/2)
|
||||
uint8_t online; ///< 1=在线, 0=离线
|
||||
uint8_t dlc; ///< 最后帧 DLC (正常=48)
|
||||
uint32_t rx_count; ///< 累计收到 STATUS 帧数
|
||||
uint32_t last_rx_sec; ///< 最后收到帧时的 g_app_uptime_sec
|
||||
uint32_t seq; ///< 更新序号 (每次刷新 +1)
|
||||
uint8_t data[kMotorStatusDlc]; ///< 原始 STATUS payload
|
||||
uint32_t config_rx_count; ///< 累计收到 CONFIG 帧数
|
||||
uint32_t config_seq; ///< CONFIG 更新序号
|
||||
uint8_t config_data[kMotorConfigDlc]; ///< 原始 CONFIG payload (目标值回报)
|
||||
/* STATUS 写侧 seqlock(1.6.5):奇数=正在写本槽的 seq/data。
|
||||
* 只有主循环(onCanFrame 的 STATUS 分支)自增,ISR 只读 —— 单核下消费者是
|
||||
* ISR,它执行期间生产者被冻结,故"读到偶数 ⇒ 生产者不在这段里"成立。
|
||||
* 放结构体末尾,已有字段偏移全不变(SPI 序列化/PDO 布局照旧)。 */
|
||||
volatile uint8_t wr_seq;
|
||||
};
|
||||
|
||||
/// 全局电机状态表
|
||||
struct MotorStateTable {
|
||||
MotorSlot slots[kMotorSlotCount]; ///< 按 (node_id-1)*kMotorChannels+channel 索引
|
||||
uint32_t global_seq; ///< 全局刷新序号
|
||||
uint32_t last_push_sec; ///< 上次推送完成时间
|
||||
uint8_t active_count; ///< 当前在线电机数
|
||||
uint32_t boot_hb_sec; ///< 最后收到 0x103 BOOT 心跳时的 uptime(0=从未)
|
||||
uint8_t boot_channel; ///< 最后 0x103 所在 CAN 通道
|
||||
uint8_t boot_active; ///< 1=最近 kBootHeartbeatTimeoutSec 内有 0x103
|
||||
};
|
||||
|
||||
/// 电机状态管理命名空间
|
||||
namespace MotorState {
|
||||
|
||||
/// 获取全局状态表引用
|
||||
MotorStateTable &table();
|
||||
|
||||
/// CAN 帧到达时调用 (主循环上下文, taskCanRxParse 内)
|
||||
void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len);
|
||||
|
||||
/// 1Hz 离线检测 (taskHeartbeat 内)
|
||||
void checkOffline();
|
||||
|
||||
/// 序列化一页电机状态到 payload 缓冲区
|
||||
/// @return 实际写入字节数, 0=参数错误
|
||||
uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen);
|
||||
|
||||
/// 设置 0xC5 主动推送周期(毫秒,钳制到 [kMotorPeriodMinMs, kMotorPeriodMaxMs])。
|
||||
/// @return 实际应用的毫秒值。
|
||||
uint16_t setPushPeriodMs(uint16_t ms);
|
||||
|
||||
} // namespace MotorState
|
||||
|
||||
#endif /* DEVICE_MOTOR_STATE_H */
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @file motor_version.cpp
|
||||
* @brief 多电机版本缓存实现 — CAN 版本应答帧(0x00FF)识别、缓存、0xC6 序列化。
|
||||
*
|
||||
* 所有函数仅在主循环上下文调用(taskCanRxParse / pushStep),无 ISR 竞态。
|
||||
* 与 motor_state.cpp 不同:版本是事件驱动(收到应答才缓存+推送),无周期任务、
|
||||
* 无离线判定;pushStep 在 app_spi_cmd_motor_version.cpp(含 SPI 依赖)。
|
||||
*/
|
||||
|
||||
#include "device/motor_version.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include <cstring>
|
||||
|
||||
/* ── 全局版本表实例 ── */
|
||||
static MotorVersionTable s_verTable = {};
|
||||
|
||||
namespace MotorVersion {
|
||||
|
||||
MotorVersionTable &table()
|
||||
{
|
||||
return s_verTable;
|
||||
}
|
||||
|
||||
void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
|
||||
{
|
||||
(void)channel;
|
||||
|
||||
/* 掩码匹配版本应答 ID: 0x000N00FF */
|
||||
if ((id & kMotorVersionIdMask) != kMotorVersionIdMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorVersionMaxNodes) {
|
||||
return;
|
||||
}
|
||||
if (data == nullptr || len < 1U) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorVersionSlot &slot = s_verTable.slots[node - 1U];
|
||||
|
||||
slot.node_id = node;
|
||||
slot.seq = ++s_verTable.global_seq;
|
||||
|
||||
/* 拷贝原始 payload(不足补 0,超长截断) */
|
||||
const uint8_t n = (len <= kMotorVersionDlc) ? len : kMotorVersionDlc;
|
||||
std::memcpy(slot.version, data, n);
|
||||
if (n < kMotorVersionDlc) {
|
||||
std::memset(&slot.version[n], 0, kMotorVersionDlc - n);
|
||||
}
|
||||
|
||||
slot.dirty = 1U; /* 标记待推送 */
|
||||
}
|
||||
|
||||
bool hasPending()
|
||||
{
|
||||
for (uint8_t i = 0U; i < kMotorVersionMaxNodes; ++i) {
|
||||
if (s_verTable.slots[i].dirty != 0U) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t buildPayload(uint8_t *buf, uint16_t maxLen)
|
||||
{
|
||||
if (buf == nullptr || maxLen < kMotorVersionHeaderLen) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/* Header (2B): count 稍后回填, rsvd=0 */
|
||||
buf[0] = 0U;
|
||||
buf[1] = 0U;
|
||||
|
||||
uint16_t offset = kMotorVersionHeaderLen;
|
||||
uint8_t count = 0U;
|
||||
|
||||
for (uint8_t i = 0U; i < kMotorVersionMaxNodes; ++i) {
|
||||
MotorVersionSlot &slot = s_verTable.slots[i];
|
||||
if (slot.dirty == 0U) {
|
||||
continue;
|
||||
}
|
||||
/* 检查剩余空间;装不下则保留 dirty 待下轮(不丢) */
|
||||
if (offset + kMotorVersionEntrySize > maxLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf[offset + 0U] = slot.node_id;
|
||||
writeU32Le(&buf[offset + 1U], slot.seq);
|
||||
std::memcpy(&buf[offset + 5U], slot.version, kMotorVersionDlc);
|
||||
|
||||
offset += kMotorVersionEntrySize;
|
||||
count++;
|
||||
slot.dirty = 0U; /* 已打包进本次推送 */
|
||||
}
|
||||
|
||||
if (count == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
buf[0] = count;
|
||||
return offset;
|
||||
}
|
||||
|
||||
} // namespace MotorVersion
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file motor_version.h
|
||||
* @brief 多电机版本缓存 — CAN 版本应答帧(0x00FF)识别、内存缓存、SPI 0xC6 推送序列化。
|
||||
*
|
||||
* 设计要点(与 motor_state 同款模型):
|
||||
* - 仅在主循环上下文 (taskCanRxParse / pushStep) 中读写,无 ISR 竞态,不需要临界区。
|
||||
* - GD32 不做语义解码,只缓存原始 24B 版本 payload,由前端解码。
|
||||
* - 事件驱动:电机回报版本(0x000N00FF)时缓存并置 dirty,pushStep 经 ring 推 0xC6。
|
||||
* 版本是按需查询、低频数据,不进 250ms 周期推送(0xC5),避免占用周期带宽。
|
||||
*/
|
||||
|
||||
#ifndef DEVICE_MOTOR_VERSION_H
|
||||
#define DEVICE_MOTOR_VERSION_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "sys/config.h"
|
||||
|
||||
/* ── CAN ID 匹配规则 (29-bit 扩展帧) ──
|
||||
* 版本应答 = 0x000N00FF(DBC CANFD_GET_VERSION,发送方 Motor,与请求同 ID)。
|
||||
* 电机 APP 仅在收到 base_id=0x00FF 时回报(CANFD_VERSION_ID)。
|
||||
*/
|
||||
static constexpr uint32_t kMotorVersionIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibble [19:16] */
|
||||
static constexpr uint32_t kMotorVersionIdMatch = 0x000000FFU; /* 版本应答类型码 */
|
||||
|
||||
static constexpr uint8_t kMotorVersionMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
static constexpr uint8_t kMotorVersionDlc = 24U;
|
||||
|
||||
/* ── 序列化常量 (0xC6 payload) ──
|
||||
* Header 2B: count + rsvd;每 entry 29B: node(1)+seq(4)+version(24)。
|
||||
* (236-2)/29 = 8.06 → 单帧最多 8 个节点,版本按需查询、通常单节点,足够。 */
|
||||
static constexpr uint8_t kMotorVersionHeaderLen = 2U;
|
||||
static constexpr uint8_t kMotorVersionEntrySize = 29U; /* 1+4+24 */
|
||||
|
||||
/// 单电机版本槽
|
||||
struct MotorVersionSlot {
|
||||
uint8_t node_id; ///< 1-9, 0=空槽
|
||||
uint8_t dirty; ///< 1=已缓存待推送, 0=已推送/空
|
||||
uint8_t rsvd[2];
|
||||
uint32_t seq; ///< 更新序号 (每次收到新应答 +1)
|
||||
uint8_t version[kMotorVersionDlc]; ///< 原始版本 payload
|
||||
};
|
||||
|
||||
/// 全局电机版本表
|
||||
struct MotorVersionTable {
|
||||
MotorVersionSlot slots[kMotorVersionMaxNodes]; ///< 按 node_id-1 索引
|
||||
uint32_t global_seq; ///< 全局更新序号
|
||||
};
|
||||
|
||||
/// 电机版本管理命名空间
|
||||
namespace MotorVersion {
|
||||
|
||||
/// 获取全局版本表引用
|
||||
MotorVersionTable &table();
|
||||
|
||||
/// CAN 帧到达时调用 (主循环上下文, taskCanRxParse 内);识别 0x00FF 并缓存
|
||||
void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len);
|
||||
|
||||
/// 是否有待推送的版本更新 (任一槽 dirty)
|
||||
bool hasPending();
|
||||
|
||||
/// 序列化所有待推送版本到 payload 缓冲区,并清除已打包槽的 dirty。
|
||||
/// @return 实际写入字节数, 0=无待推送/参数错误
|
||||
uint16_t buildPayload(uint8_t *buf, uint16_t maxLen);
|
||||
|
||||
} // namespace MotorVersion
|
||||
|
||||
#endif /* DEVICE_MOTOR_VERSION_H */
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file app_spi_cmd_adc_feedback.cpp
|
||||
* @brief GD32 ADC 调试反馈(温度 / VDDA / VIN)SPI 被动查询处理器。
|
||||
*
|
||||
* TYPE = SpiDefs::kTypeAdcFeedback (0xC4)
|
||||
*
|
||||
* 总开关:sys/config.h 中 CONFIG_SPI_ADC_FEEDBACK_ENABLE
|
||||
* - 0:不注册 handler(本文件仍参与链接,函数空实现)
|
||||
* - 1:ESP32 周期查询(1s),本 handler 被动回当前 ADC 快照
|
||||
*
|
||||
* 2026-08 请求驱动改造:0xC4 不再主动按铃推送(原 1Hz pushStep 移除),
|
||||
* 查询响应 seq 每次递增,供前端判断有新快照。
|
||||
*
|
||||
* payload v1 固定 20 字节(小端):
|
||||
* [0] ver=1
|
||||
* [1] flags bit0=sample_valid
|
||||
* [2..3] temp_x100 int16 百分之一摄氏度
|
||||
* [4..5] vdda_mV uint16
|
||||
* [6..7] vin_adc_mV uint16 引脚分压后电压 mV
|
||||
* [8..9] temp_raw uint16
|
||||
* [10..11] vref_raw uint16
|
||||
* [12..15] seq uint32 快照序号(每次查询 +1)
|
||||
* [16..19] uptime_sec uint32
|
||||
*/
|
||||
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/adc.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "sys/task_runner.h"
|
||||
#include "main.h"
|
||||
#include <cstring>
|
||||
|
||||
#if (CONFIG_SPI_ADC_FEEDBACK_ENABLE != 0)
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr uint8_t kPayloadVer = 1U;
|
||||
static constexpr uint16_t kPayloadLen = 20U;
|
||||
|
||||
static uint32_t s_pushSeq = 0U;
|
||||
|
||||
/**
|
||||
* @brief 构建 0xC4 ADC 反馈 payload(20B)。
|
||||
* @param[out] out 输出缓冲
|
||||
* @param max_len 缓冲容量
|
||||
* @return 写入长度;失败 0
|
||||
*/
|
||||
static uint16_t buildAdcFeedbackPayload(uint8_t *out, uint16_t max_len)
|
||||
{
|
||||
if (out == nullptr || max_len < kPayloadLen) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
Adc::Sample s{};
|
||||
const bool have = TaskRunner::copyAdcSample(&s);
|
||||
|
||||
const int32_t temp_x100 = have
|
||||
? static_cast<int32_t>(s.temperature * 100.0f)
|
||||
: 0;
|
||||
const uint16_t vdda_mV = have
|
||||
? static_cast<uint16_t>(s.vdda * 1000.0f)
|
||||
: 0U;
|
||||
const uint16_t vin_mV = have
|
||||
? static_cast<uint16_t>(s.vin_adc_v * 1000.0f)
|
||||
: 0U;
|
||||
|
||||
std::memset(out, 0, kPayloadLen);
|
||||
out[0] = kPayloadVer;
|
||||
out[1] = (have && s.valid) ? 0x01U : 0x00U;
|
||||
/* int16 百分之一℃,按两字节小端原样写入 */
|
||||
writeU16Le(&out[2], static_cast<uint16_t>(static_cast<int16_t>(temp_x100)));
|
||||
writeU16Le(&out[4], vdda_mV);
|
||||
writeU16Le(&out[6], vin_mV);
|
||||
writeU16Le(&out[8], have ? s.temp_raw : 0U);
|
||||
writeU16Le(&out[10], have ? s.vref_raw : 0U);
|
||||
writeU32Le(&out[12], s_pushSeq);
|
||||
writeU32Le(&out[16], g_app_uptime_sec);
|
||||
return kPayloadLen;
|
||||
}
|
||||
|
||||
/** @brief SPI 0xC4 ADC 反馈:被动查询应答。 */
|
||||
class SpiAdcFeedbackHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
/** @brief 是否处理 0xC4。 */
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeAdcFeedback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 被动查询:回当前 ADC 快照。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文
|
||||
*/
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
uint8_t payload[kPayloadLen] = {};
|
||||
++s_pushSeq; /* 每次查询回一帧新序号,前端可据此判断有新快照 */
|
||||
const uint16_t n = buildAdcFeedbackPayload(payload, sizeof(payload));
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kTypeAdcFeedback, payload, n);
|
||||
}
|
||||
};
|
||||
|
||||
static SpiAdcFeedbackHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/* 2026-08 请求驱动改造:0xC4 已改 ESP32 周期查询(1s,经 handler 被动响应),
|
||||
* 主动按铃推送状态机整体移除(s_needPush/s_pushSeq 一并删除)。 */
|
||||
|
||||
/** @brief 注册 0xC4 处理器。 */
|
||||
void registerSpiAdcFeedbackCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
|
||||
#else /* CONFIG_SPI_ADC_FEEDBACK_ENABLE == 0 */
|
||||
|
||||
/** @brief 开关关闭时不注册 handler。 */
|
||||
void registerSpiAdcFeedbackCommand(AppSpiCommand & /*bus*/) {}
|
||||
|
||||
#endif
|
||||
@@ -16,17 +16,20 @@ public:
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
bus.postLog("BOOT_REQ → 进BOOT");
|
||||
bus.postLog("BOOT_REQ → 进BOOT(异步)");
|
||||
|
||||
const bool ok = OtaBootRequest::requestEnterBoot();
|
||||
uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK
|
||||
: CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR;
|
||||
/* 先取消 CAN feedback 按铃,避免 0xC1 残留帧盖住本 ACK / 干扰复位。 */
|
||||
bus.cancelRing();
|
||||
|
||||
/* 立即装标准帧 ACK:type=0xB0 payload=[0xB5],勿用通用 0x80。
|
||||
* Flash 擦写放到主循环,避免阻塞 SPI ISR。 */
|
||||
uint8_t status = CONFIG_SPI_APP_OTA_BOOT_ACK_OK;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
SpiDefs::kTypeBootReq, &status, 1U);
|
||||
|
||||
if (ok) {
|
||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||
}
|
||||
/* pending + 立即冻结 SPI 业务;并拉高优先级心跳,尽快 process() 擦页复位。 */
|
||||
OtaBootRequest::requestEnterBootAsync();
|
||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
@@ -32,12 +33,7 @@ void postCanSendLog(AppSpiCommand &bus, const char *result,
|
||||
result ? result : "UNKNOWN");
|
||||
bus.postLog(log_msg);
|
||||
#else
|
||||
(void)bus;
|
||||
(void)result;
|
||||
(void)can_ch;
|
||||
(void)can_id;
|
||||
(void)can_data;
|
||||
(void)can_dlc;
|
||||
(void)bus; (void)result; (void)can_ch; (void)can_id; (void)can_data; (void)can_dlc;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -50,7 +46,8 @@ public:
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
if (ctx.payload_len < 6U) {
|
||||
/* 最短 5 字节:ch(1)+id(4);DLC=0(如 CANFD_BOOT)合法,勿再要求 ≥6 */
|
||||
if (ctx.payload_len < 5U) {
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[AppSpiCommand::kLogBufSize] = {};
|
||||
(void)snprintf(log_msg, sizeof(log_msg),
|
||||
@@ -69,8 +66,8 @@ public:
|
||||
| (static_cast<uint32_t>(ctx.payload[2]) << 16)
|
||||
| (static_cast<uint32_t>(ctx.payload[3]) << 8)
|
||||
| static_cast<uint32_t>(ctx.payload[4]);
|
||||
const uint8_t *can_data = &ctx.payload[5];
|
||||
const uint8_t can_dlc = static_cast<uint8_t>(ctx.payload_len - 5U);
|
||||
const uint8_t *can_data = (can_dlc > 0U) ? &ctx.payload[5] : nullptr;
|
||||
|
||||
if (can_ch >= 3U) {
|
||||
postCanSendLog(bus, "ERROR(channel)", can_ch, can_id, can_data, can_dlc);
|
||||
@@ -80,15 +77,30 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
const bool ok = CanFd::transmitFrame(can_ch, can_id, can_data, can_dlc);
|
||||
if (ok) {
|
||||
/* 通道正被电机 OTA 独占(WiFi 与 EtherCAT 两条链路可能同时在跑,锁是共用的):
|
||||
* 这一帧挤进同一通道会 abort 掉在途的 0x101 固件包,单发模式下永不重传。
|
||||
* 回 0xE7,上位机据此知道「是被 OTA 挡了」而不是「邮箱满」。 */
|
||||
if (MotorOtaRelay::isChannelLocked(can_ch)) {
|
||||
postCanSendLog(bus, "LOCKED(motor-OTA)", can_ch, can_id, can_data, can_dlc);
|
||||
const uint8_t err = static_cast<uint8_t>(CanFd::CanTxResult::kErrOtaLocked);
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 0xC0 单帧发送改走 enqueueTx:
|
||||
* 1) allow_abort=false 杜绝硬件邮箱被后续发送主动 abort 抹杀;
|
||||
* 2) 电机高频上报抢占仲裁导致邮箱全占时,帧自动进入 13 深度软件队列,
|
||||
* 由 TX 中断 drainTxQueueIsr 持续回灌空闲邮箱发送,彻底消除丢帧。 */
|
||||
const CanFd::CanTxResult result = CanFd::enqueueTx(can_ch, can_id, can_data, can_dlc);
|
||||
if (result == CanFd::CanTxResult::kOk) {
|
||||
postCanSendLog(bus, "OK", can_ch, can_id, can_data, can_dlc);
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
} else {
|
||||
postCanSendLog(bus, "BUSY", can_ch, can_id, can_data, can_dlc);
|
||||
uint8_t err = 0xE2U;
|
||||
const uint8_t err = static_cast<uint8_t>(result); /* 枚举值即 SPI 错误码 */
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/* 单次突发最多帧数。SPI payload 上限 236B,DLC=32 时实际 ~7 帧;这里放宽到 16 作硬上限,
|
||||
* 约束 EXTI ISR 内背靠背 transmitFrame 的总耗时。 */
|
||||
constexpr uint8_t kBurstMaxFrames = 16U;
|
||||
|
||||
/**
|
||||
* @brief CAN 跨通道突发发送处理器(TYPE 0xC7)。
|
||||
*
|
||||
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用 CanFd::enqueueTx():
|
||||
* - 邮箱有空位:立即装入硬件邮箱。
|
||||
* - 邮箱满(>3 帧突发同通道时必然发生):进入每通道 13 帧深的软件队列
|
||||
* (禁用 transmitFrame abort+重投,否则旧帧会被静默顶替),
|
||||
* CAN TX 完成中断里自动回灌,保证整块突发(<=16 帧同通道)不丢帧。
|
||||
*
|
||||
* payload:
|
||||
* [0] count 条目数(1..kBurstMaxFrames)
|
||||
* [1] dlc 所有条目统一的数据长度(CAN FD 0..64,CMD 为 32)
|
||||
* [2..3] suffix CAN ID 低 16 位,大端(CMD=0x0200)
|
||||
* [4..] count 个条目,每条目 = chnode(1) + data[dlc]
|
||||
* chnode = (ch<<4) | node,id = (node<<16) | suffix
|
||||
*
|
||||
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=已受理;邮箱满+队列满才回 0xE4)。
|
||||
*/
|
||||
class SpiCanBurstHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeCanBurstSend;
|
||||
}
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
/* 头部至少 4 字节:count + dlc + suffix(2) */
|
||||
if (ctx.payload_len < 4U) {
|
||||
uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t count = ctx.payload[0];
|
||||
const uint8_t dlc = ctx.payload[1];
|
||||
const uint16_t suffix = static_cast<uint16_t>(
|
||||
(static_cast<uint16_t>(ctx.payload[2]) << 8) | ctx.payload[3]);
|
||||
|
||||
if (count == 0U || count > kBurstMaxFrames || dlc > 64U) {
|
||||
uint8_t err = 0xE5U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
/* payload 必须与 count*(1+dlc) 精确匹配,防截断/越界 */
|
||||
const uint16_t entry_len = static_cast<uint16_t>(1U + dlc);
|
||||
const uint16_t need = static_cast<uint16_t>(4U + count * entry_len);
|
||||
if (ctx.payload_len != need) {
|
||||
uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t resp[1U + kBurstMaxFrames];
|
||||
resp[0] = count;
|
||||
|
||||
const uint8_t *p = &ctx.payload[4];
|
||||
for (uint8_t i = 0U; i < count; ++i, p += entry_len) {
|
||||
const uint8_t chnode = p[0];
|
||||
const uint8_t ch = static_cast<uint8_t>((chnode >> 4) & 0x0FU);
|
||||
const uint8_t node = static_cast<uint8_t>(chnode & 0x0FU);
|
||||
const uint32_t id = (static_cast<uint32_t>(node) << 16) | suffix;
|
||||
|
||||
if (ch >= 3U) {
|
||||
resp[1U + i] = 0xE1U; /* 通道非法:跳过该帧,其余照常发送 */
|
||||
continue;
|
||||
}
|
||||
if (MotorOtaRelay::isChannelLocked(ch)) {
|
||||
/* 电机 OTA 独占该通道:整帧拒绝,不挤邮箱(0xE7)。 */
|
||||
resp[1U + i] = static_cast<uint8_t>(CanFd::CanTxResult::kErrOtaLocked);
|
||||
continue;
|
||||
}
|
||||
const CanFd::CanTxResult r =
|
||||
CanFd::enqueueTx(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
|
||||
resp[1U + i] = static_cast<uint8_t>(r);
|
||||
}
|
||||
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, resp, static_cast<uint16_t>(1U + count));
|
||||
}
|
||||
};
|
||||
|
||||
static SpiCanBurstHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerSpiCanBurstCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
* 请求 payload:
|
||||
* [0] channel 0/1/2
|
||||
* [1] page 页号(0 = 条目 0~7,1 = 条目 8~15)
|
||||
* 应答 payload(变长,最大 kTxBufSize - overhead = 123 字节):
|
||||
* 应答 payload(变长;上限 = AppSpiCommand::kTxBufSize - V2_OVERHEAD = 256-20 = 236):
|
||||
* [0] channel
|
||||
* [1] page
|
||||
* [2] total_count 该通道当前条目总数
|
||||
@@ -21,7 +21,7 @@
|
||||
* [21..24] error_count (LE)
|
||||
* [25] tec 硬件发送错误计数器 (0~255)
|
||||
* [26] rec 硬件接收错误计数器 (0~255)
|
||||
* [27] bus_state 总线状态位图:bit0=init,bit1=sleep,bit2=error_passive,bit3=bus_off,bit4=error_warning
|
||||
* [27] bus_state 总线状态位图:bit0=init,bit1=控制器sleep,bit2=error_passive,bit3=bus_off,bit4=error_warning,bit5=tx_silent(收发器Silent)
|
||||
* [28..] 条目逐条拼接:
|
||||
* id(4LE) + dlc(1) + flags(1) + reserved(2) + data(dlc)
|
||||
* 每条 = 8 + dlc 字节
|
||||
@@ -31,6 +31,16 @@
|
||||
* [0] mode 0=全部接收, 1=按 ID 过滤;其它值返回 kRspError。
|
||||
* 应答 payload:
|
||||
* [0] 实际生效的 mode。
|
||||
*
|
||||
* 3) kTypeCanSetSilent (0xC8):ESP32 切换指定通道收发器 Silent(只听不发)。
|
||||
* 请求 payload:
|
||||
* [0] channel 0/1/2
|
||||
* [1] silent 1=进 Silent(S 脚置高:驱动关断、接收机照常), 0=回 Normal
|
||||
* 应答 payload:
|
||||
* [0] channel [1] 实际生效的 silent(与发送门控同一个值) [2] 该通道使能位
|
||||
* ERROR:0xE0=payload 过短, 0xE1=通道非法/未使能, 0xE2=S 脚回读与意图不符(标志已回滚)。
|
||||
* 生效期间该通道所有发送入口(0xC0/0xC7/桥接/relay)回 0xE6=kErrSilent,
|
||||
* 不计错误计数;静默不持久化,上电 init 一律回 Normal。
|
||||
*/
|
||||
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
@@ -38,42 +48,139 @@
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
/* ---- 文件内共享常量与辅助函数(仅供本文件 handler 使用;原全局推送函数已随
|
||||
* 2026-08 请求驱动改造移除) ---- */
|
||||
|
||||
constexpr uint8_t kFeedbackChannelCount = 3U;
|
||||
constexpr uint16_t kFeedbackPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
|
||||
constexpr uint8_t kPageEntries = 8U;
|
||||
constexpr uint16_t kFixedHeaderLen = 28U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4)+tec(1)+rec(1)+bus_state(1) */
|
||||
static constexpr uint8_t kFeedbackChannelCount = 3U;
|
||||
/* 业务 payload 上限:APP TX 缓冲 256 - v2 帧开销 20 = 236(与 ESP32 kFeedbackPayloadLen 一致) */
|
||||
static constexpr uint16_t kFeedbackPayloadLen =
|
||||
static_cast<uint16_t>(AppSpiCommand::kTxBufSize - V2_OVERHEAD);
|
||||
static constexpr uint8_t kPageEntries = 8U;
|
||||
static constexpr uint16_t kFixedHeaderLen = 28U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4)+tec(1)+rec(1)+bus_state(1) */
|
||||
|
||||
void writeU32Le(uint8_t *dst, uint32_t value)
|
||||
/**
|
||||
* @brief 构建一页 CAN 反馈 payload(与 ESP32 canFdPollFeedback 解析格式一致)。
|
||||
* @param[in] channel 目标通道 0/1/2。
|
||||
* @param[in] page 页号 0/1。
|
||||
* @param[out] payload 输出缓冲(至少 kFeedbackPayloadLen 字节)。
|
||||
* @param[in] max_len 缓冲最大长度。
|
||||
* @return 构建完成的 payload 长度;0 表示参数错误。
|
||||
*
|
||||
* 提取自 handleFeedback(),供被动响应与主动推送复用。
|
||||
*/
|
||||
static uint16_t buildFeedbackPayload(uint8_t channel, uint8_t page,
|
||||
uint8_t *payload, uint16_t max_len)
|
||||
{
|
||||
dst[0] = static_cast<uint8_t>(value & 0xFFU);
|
||||
dst[1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
|
||||
dst[2] = static_cast<uint8_t>((value >> 16) & 0xFFU);
|
||||
dst[3] = static_cast<uint8_t>((value >> 24) & 0xFFU);
|
||||
if (channel >= kFeedbackChannelCount || page >= 2U) {
|
||||
return 0U;
|
||||
}
|
||||
if (max_len < kFeedbackPayloadLen) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/* 拷贝统计快照 */
|
||||
CanFd::ChannelFeedback snap{};
|
||||
CanFd::copyFeedbackSnapshot(channel, &snap);
|
||||
|
||||
/* 拷贝 ID 快照表 */
|
||||
CanFd::IdSnapshotTable table{};
|
||||
CanFd::copyIdSnapshotTable(channel, &table);
|
||||
|
||||
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
|
||||
if (start >= CANFD_ID_SNAPSHOT_MAX) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
/* 固定头 */
|
||||
payload[0] = channel;
|
||||
payload[1] = page;
|
||||
payload[2] = table.count;
|
||||
/* payload[3] = entry_count 稍后填 */
|
||||
payload[4] = static_cast<uint8_t>(CanFd::mode());
|
||||
writeU32Le(&payload[5], snap.snapshot_seq);
|
||||
writeU32Le(&payload[9], snap.stats.rx_count);
|
||||
writeU32Le(&payload[13], snap.stats.tx_count);
|
||||
writeU32Le(&payload[17], snap.stats.rx_drop_count);
|
||||
writeU32Le(&payload[21], snap.stats.error_count);
|
||||
payload[25] = snap.stats.tec;
|
||||
payload[26] = snap.stats.rec;
|
||||
payload[27] = snap.stats.bus_state;
|
||||
|
||||
/* 打包 ID 条目 */
|
||||
uint16_t offset = kFixedHeaderLen;
|
||||
uint8_t packed = 0U;
|
||||
|
||||
const uint8_t end = static_cast<uint8_t>(
|
||||
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
|
||||
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
|
||||
|
||||
for (uint8_t i = start; i < end; ++i) {
|
||||
const auto &e = table.entries[i];
|
||||
if (!e.occupied) continue;
|
||||
|
||||
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
||||
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
||||
const uint16_t entrySize = static_cast<uint16_t>(8U + dataLen);
|
||||
|
||||
if (offset + entrySize > kFeedbackPayloadLen) break;
|
||||
|
||||
writeU32Le(&payload[offset], e.id);
|
||||
payload[offset + 4U] = e.dlc;
|
||||
payload[offset + 5U] = static_cast<uint8_t>(
|
||||
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
|
||||
payload[offset + 6U] = 0U; /* reserved */
|
||||
payload[offset + 7U] = 0U; /* reserved */
|
||||
memcpy(&payload[offset + 8U], e.data, dataLen);
|
||||
|
||||
offset += entrySize;
|
||||
packed++;
|
||||
}
|
||||
|
||||
payload[3] = packed;
|
||||
return offset;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief SPI 0xC1 反馈查询 + 0xC2 模式切换 + 0xC8 收发器 Silent 开关处理器。 */
|
||||
class SpiCanFeedbackHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
/** @brief 是否处理 0xC1 / 0xC2 / 0xC8。 */
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return (frame_type == SpiDefs::kTypeCanFeedback)
|
||||
|| (frame_type == SpiDefs::kTypeCanSetMode);
|
||||
|| (frame_type == SpiDefs::kTypeCanSetMode)
|
||||
|| (frame_type == SpiDefs::kTypeCanSetSilent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 分发到 handleFeedback / handleSetMode / handleSetSilent。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文
|
||||
*/
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
if (ctx.frame_type == SpiDefs::kTypeCanFeedback) {
|
||||
handleFeedback(bus, ctx);
|
||||
} else if (ctx.frame_type == SpiDefs::kTypeCanSetSilent) {
|
||||
handleSetSilent(bus, ctx);
|
||||
} else {
|
||||
handleSetMode(bus, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 被动 0xC1:按 channel/page 回一页反馈。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文(payload[0]=ch, [1]=page)
|
||||
*/
|
||||
void handleFeedback(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||
{
|
||||
if (ctx.payload_len < 2U) {
|
||||
@@ -86,82 +193,24 @@ private:
|
||||
const uint8_t channel = ctx.payload[0];
|
||||
const uint8_t page = ctx.payload[1];
|
||||
|
||||
if (channel >= kFeedbackChannelCount) {
|
||||
uint8_t err = 0xE1U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 拷贝统计快照 */
|
||||
CanFd::ChannelFeedback snap{};
|
||||
CanFd::copyFeedbackSnapshot(channel, &snap);
|
||||
|
||||
/* 拷贝 ID 快照表 */
|
||||
CanFd::IdSnapshotTable table{};
|
||||
CanFd::copyIdSnapshotTable(channel, &table);
|
||||
|
||||
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
|
||||
if (start >= CANFD_ID_SNAPSHOT_MAX) {
|
||||
uint8_t payload[kFeedbackPayloadLen] = {};
|
||||
uint16_t payload_len = buildFeedbackPayload(channel, page, payload, sizeof(payload));
|
||||
if (payload_len == 0U) {
|
||||
uint8_t err = 0xE3U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t payload[kFeedbackPayloadLen] = {};
|
||||
|
||||
/* 固定头 */
|
||||
payload[0] = channel;
|
||||
payload[1] = page;
|
||||
payload[2] = table.count;
|
||||
/* payload[3] = entry_count 稍后填 */
|
||||
payload[4] = static_cast<uint8_t>(CanFd::mode());
|
||||
writeU32Le(&payload[5], snap.snapshot_seq);
|
||||
writeU32Le(&payload[9], snap.stats.rx_count);
|
||||
writeU32Le(&payload[13], snap.stats.tx_count);
|
||||
writeU32Le(&payload[17], snap.stats.rx_drop_count);
|
||||
writeU32Le(&payload[21], snap.stats.error_count);
|
||||
payload[25] = snap.stats.tec;
|
||||
payload[26] = snap.stats.rec;
|
||||
payload[27] = snap.stats.bus_state;
|
||||
|
||||
/* 打包 ID 条目 */
|
||||
uint16_t offset = kFixedHeaderLen;
|
||||
uint8_t packed = 0U;
|
||||
|
||||
const uint8_t end = static_cast<uint8_t>(
|
||||
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
|
||||
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
|
||||
|
||||
for (uint8_t i = start; i < end; ++i) {
|
||||
const auto &e = table.entries[i];
|
||||
if (!e.occupied) continue;
|
||||
|
||||
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
||||
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
||||
const uint16_t entrySize = static_cast<uint16_t>(8U + dataLen);
|
||||
|
||||
if (offset + entrySize > kFeedbackPayloadLen) break;
|
||||
|
||||
writeU32Le(&payload[offset], e.id);
|
||||
payload[offset + 4U] = e.dlc;
|
||||
payload[offset + 5U] = static_cast<uint8_t>(
|
||||
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
|
||||
payload[offset + 6U] = 0U; /* reserved */
|
||||
payload[offset + 7U] = 0U; /* reserved */
|
||||
memcpy(&payload[offset + 8U], e.data, dataLen);
|
||||
|
||||
offset += entrySize;
|
||||
packed++;
|
||||
}
|
||||
|
||||
payload[3] = packed;
|
||||
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, payload, offset);
|
||||
SpiDefs::kRspAck, payload, payload_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 0xC2:切换三路 CAN 接收过滤模式。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文(payload[0]=mode)
|
||||
*/
|
||||
void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||
{
|
||||
if (ctx.payload_len < 1U) {
|
||||
@@ -201,12 +250,71 @@ private:
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &actual, 1U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 0xC8:切换指定通道收发器 Silent(只听不发,S 脚控制)。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文(payload[0]=ch, [1]=silent 1/0)
|
||||
* @note 运行在 SPI CS EXTI ISR 上下文:只做 GPIO + 标志位;退 Silent 的 20µs
|
||||
* 收发器建立时间由 CanFd::setTxSilent 内部用 DWT 忙等,禁止 SysTick delayMs。
|
||||
*/
|
||||
void handleSetSilent(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||
{
|
||||
if (ctx.payload_len < 2U) {
|
||||
uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t channel = ctx.payload[0];
|
||||
const bool silent = (ctx.payload[1] != 0U);
|
||||
|
||||
if (!CanFd::isChannelEnabled(channel)) {
|
||||
uint8_t err = 0xE1U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool ok = CanFd::setTxSilent(channel, silent);
|
||||
const uint8_t actual = CanFd::isTxSilent(channel) ? 1U : 0U;
|
||||
|
||||
if (!ok) {
|
||||
/* S 脚回读与意图不符(被别处抢改 / GPIO 配置未生效):如实报错,不报成功 */
|
||||
uint8_t err = 0xE2U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[AppSpiCommand::kLogBufSize] = {};
|
||||
(void)snprintf(log_msg, sizeof(log_msg),
|
||||
"CAN_SILENT ch=%u set=%u -> %u",
|
||||
static_cast<unsigned>(channel),
|
||||
static_cast<unsigned>(silent ? 1U : 0U),
|
||||
static_cast<unsigned>(actual));
|
||||
bus.postLog(log_msg);
|
||||
#endif
|
||||
|
||||
const uint8_t rsp[3] = { channel, actual, 1U };
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, rsp, 3U);
|
||||
}
|
||||
};
|
||||
|
||||
static SpiCanFeedbackHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/* 2026-08 请求驱动改造:0xC1 CAN 反馈已改 ESP32 请求驱动(handleFeedback 被动
|
||||
* 响应),主动推送早已禁用;本次随 0xC5/0xC4 一并移除全部推送残留。 */
|
||||
|
||||
/**
|
||||
* @brief 注册 0xC1/0xC2 CAN 反馈与模式切换处理器。
|
||||
* @param bus 命令总线
|
||||
*/
|
||||
void registerSpiCanFeedbackCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file app_spi_cmd_exmc_diag.cpp
|
||||
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern / NRST 手动诊断 / 电源管理 / ESP32 EN。
|
||||
*
|
||||
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → 分流:
|
||||
* action 0~10 → EscExmcBus::continuityDrive() (EXMC 数据/控制线 pattern)
|
||||
* action 20~24 → EscLan9252::nrstDrive() (ECAT_NRST/PB10 拉低/拉高/浮空/回读)
|
||||
* action 30~34 → EscLan9252::pmDrive() (电源管理:读状态/进D1/D2/D3/手动唤醒)
|
||||
* action 40~44 → Esp32En::drive() (ESP32 EN/PC9 诊断备援;开关请走 EtherCAT 0x2503)
|
||||
* 立即返回 ACK,不阻塞 SPI 总线。电平保持到下次调用或 action=0(stop)。
|
||||
* NRST/ESP32_EN action 返回的 status 字节 = 实际引脚电平(0/1),非 OK 码。
|
||||
* PM action 返回的 status 字节 = bit0=READY bit1=PM_SLEEP_EN bit3:2=PM_MODE;
|
||||
* 唤醒(34)另有 1=成功 0=超时;0xEE=无效 action,0xE1=HBI 未就绪。
|
||||
*/
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "sys/esp32_en.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief SPI 0xA2 EXMC 诊断处理器。 */
|
||||
class SpiExmcDiagHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeExmcDiag;
|
||||
}
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
uint8_t status = 0xEE;
|
||||
if (ctx.payload_len >= 1) {
|
||||
const uint8_t action = ctx.payload[0];
|
||||
if (action >= 40U) {
|
||||
status = Esp32En::drive(action - 40U);
|
||||
} else if (action >= 30U) {
|
||||
status = EscLan9252::pmDrive(action - 30U);
|
||||
} else if (action >= 20U) {
|
||||
status = EscLan9252::nrstDrive(action - 20U);
|
||||
} else {
|
||||
status = EscExmcBus::continuityDrive(action);
|
||||
}
|
||||
}
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &status, 1);
|
||||
}
|
||||
};
|
||||
|
||||
static SpiExmcDiagHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/** @brief 注册 0xA2 EXMC 诊断命令处理器。 */
|
||||
void registerSpiExmcDiagCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
@@ -8,13 +8,13 @@
|
||||
* [0] channel 0/1/2
|
||||
* [1] page 页号(0 = 条目 0~7,1 = 条目 8~15)
|
||||
*
|
||||
* 应答 payload(变长,最大 kTxBufSize - 5 = 123 字节):
|
||||
* 应答 payload(变长;上限 = AppSpiCommand::kTxBufSize - V2_OVERHEAD = 256-20 = 236):
|
||||
* [0] channel
|
||||
* [1] page
|
||||
* [2] total_count 该通道当前条目总数
|
||||
* [3] entry_count 本页实际条目数
|
||||
* [4..] 条目逐条拼接:
|
||||
* id(4LE) + seq(4LE) + rx_count(4LE) + dlc(1) + flags(1) + data(dlc)
|
||||
* id(4LE) + seq(4LE) + rx_count(4LE) + dlc(1) + flags(1) + rsv(2) + data(dlc)
|
||||
* 每条 = 16 + dlc 字节
|
||||
*/
|
||||
|
||||
@@ -22,21 +22,15 @@
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/canfd.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kPageEntries = 8U;
|
||||
constexpr uint16_t kMaxPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
|
||||
|
||||
inline void writeU32Le(uint8_t *dst, uint32_t val)
|
||||
{
|
||||
dst[0] = static_cast<uint8_t>(val & 0xFFU);
|
||||
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
|
||||
dst[2] = static_cast<uint8_t>((val >> 16) & 0xFFU);
|
||||
dst[3] = static_cast<uint8_t>((val >> 24) & 0xFFU);
|
||||
}
|
||||
constexpr uint16_t kMaxPayloadLen =
|
||||
static_cast<uint16_t>(AppSpiCommand::kTxBufSize - V2_OVERHEAD);
|
||||
|
||||
class SpiIdSnapshotHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file app_spi_cmd_init_status.cpp
|
||||
* @brief SPI 0xA3 INIT_STATUS 命令:向 ESP32 上报 APP 初始化状态(1.5.34 新增,1.5.35 扩展故障码)。
|
||||
*
|
||||
* 响应 ACK 25B 布局(与 app_spi_command_defs.h kTypeInitStatus 注释一致):
|
||||
* [0..3] module_mask LE AppInitializer::ModuleBit 成功位图
|
||||
* [4..7] critical_fail_mask LE 失败的 critical 模块位图(0=无 critical 失败)
|
||||
* [8] reset_cause ResetCauseBit 复位源位图
|
||||
* [9] init_state 0=OK 1=degraded 2=critical_fail
|
||||
* [10..11] reserved (0)
|
||||
* [12..24] fault_codes[13] 每模块 1B 故障码(0=成功;编码见 sys/initial.h,
|
||||
* canfd 高 4 位为首个失败通道号)
|
||||
* 1.5.34 只回前 12B;ESP32 按 resp_len≥25 判定故障码数组是否存在,前后兼容。
|
||||
*/
|
||||
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "sys/initial.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief 0xA3 响应 payload 长度(12B 基础 + 13B 故障码)。 */
|
||||
static constexpr size_t kInitStatusLen = 12U + 13U;
|
||||
|
||||
/** @brief SPI 0xA3 INIT_STATUS 处理器:应答初始化状态 25B。 */
|
||||
class SpiInitStatusHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
/** @brief 是否处理该帧类型。 */
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeInitStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理 INIT_STATUS:填充初始化状态与模块故障码并回 0x80。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文
|
||||
*/
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
uint8_t payload[kInitStatusLen] = {0U};
|
||||
writeU32Le(&payload[0], AppInitializer::moduleMask());
|
||||
writeU32Le(&payload[4], AppInitializer::criticalFailMask());
|
||||
payload[8] = AppInitializer::resetCause();
|
||||
payload[9] = AppInitializer::initState();
|
||||
AppInitializer::faultCodes(&payload[12]);
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, payload, sizeof(payload));
|
||||
}
|
||||
};
|
||||
|
||||
static SpiInitStatusHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/** @brief 注册 0xA3 INIT_STATUS 命令处理器。 */
|
||||
void registerSpiInitStatusCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @file app_spi_cmd_motor_ota.cpp
|
||||
* @brief SPI 命令处理:电机 OTA relay(0xD0 命令 / 0xD1 数据)。
|
||||
*
|
||||
* 0xD0 payload: [cmd(1B)] + 参数
|
||||
* cmd=0 (START): [ch(1B) + fw_size(4B LE)]
|
||||
* cmd=1 (CANCEL): 无参数
|
||||
* cmd=2 (QUERY): 无参数 → 返回 12B 状态
|
||||
*
|
||||
* 0xD1 payload: [offset:u32 LE][固件明文…](总长≤236,数据≤232),
|
||||
* GD32 按绝对 offset 幂等写入环缓后本地加密发 CAN。
|
||||
*/
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
class SpiMotorOtaHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return (frame_type == SpiDefs::kTypeMotorOtaCmd ||
|
||||
frame_type == SpiDefs::kTypeMotorOtaData);
|
||||
}
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
if (ctx.frame_type == SpiDefs::kTypeMotorOtaCmd) {
|
||||
handleCmd(bus, ctx);
|
||||
} else {
|
||||
handleData(bus, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void handleCmd(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||
{
|
||||
if (ctx.payload_len < 1U) {
|
||||
const uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t cmd = ctx.payload[0];
|
||||
switch (cmd) {
|
||||
case 0U: { /* START */
|
||||
if (ctx.payload_len < 6U) {
|
||||
const uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
const uint8_t ch = ctx.payload[1];
|
||||
const uint32_t fw_size =
|
||||
static_cast<uint32_t>(ctx.payload[2])
|
||||
| (static_cast<uint32_t>(ctx.payload[3]) << 8)
|
||||
| (static_cast<uint32_t>(ctx.payload[4]) << 16)
|
||||
| (static_cast<uint32_t>(ctx.payload[5]) << 24);
|
||||
|
||||
printf("[MOTOR-OTA] SPI 0xD0 START recv ch=%u fw=%lu\r\n",
|
||||
(unsigned)ch, (unsigned long)fw_size);
|
||||
if (MotorOtaRelay::start(ch, fw_size)) {
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
} else {
|
||||
const uint8_t err = 0xE5U; /* 已在进行中或参数无效 */
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1U: { /* CANCEL */
|
||||
MotorOtaRelay::cancel();
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
break;
|
||||
}
|
||||
case 2U: { /* QUERY */
|
||||
const MotorOtaRelay::Status st = MotorOtaRelay::getStatus();
|
||||
uint8_t resp[12];
|
||||
resp[0] = static_cast<uint8_t>(st.state);
|
||||
resp[1] = st.error_code;
|
||||
resp[2] = static_cast<uint8_t>(st.packets_acked & 0xFF);
|
||||
resp[3] = static_cast<uint8_t>(st.packets_acked >> 8);
|
||||
resp[4] = static_cast<uint8_t>(st.total_packets & 0xFF);
|
||||
resp[5] = static_cast<uint8_t>(st.total_packets >> 8);
|
||||
resp[6] = static_cast<uint8_t>(st.fw_size & 0xFF);
|
||||
resp[7] = static_cast<uint8_t>((st.fw_size >> 8) & 0xFF);
|
||||
resp[8] = static_cast<uint8_t>((st.fw_size >> 16) & 0xFF);
|
||||
resp[9] = static_cast<uint8_t>((st.fw_size >> 24) & 0xFF);
|
||||
resp[10] = 0; /* reserved */
|
||||
resp[11] = 0;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, resp, 12U);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const uint8_t err = 0xE1U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleData(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||
{
|
||||
if (ctx.payload_len == 0U) {
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MotorOtaRelay::feedData(ctx.payload, ctx.payload_len)) {
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
} else {
|
||||
/* 缓冲满或状态不对:返回 WAIT,ESP32 稍后重试 */
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspWait, nullptr, 0U);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static SpiMotorOtaHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerSpiMotorOtaCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @file app_spi_cmd_motor_state.cpp
|
||||
* @brief 多电机状态查询处理器 (TYPE 0xC5)。
|
||||
*
|
||||
* 2026-08 请求驱动改造:0xC5 由 ESP32 周期查询(250ms,ESP32 can_fb 任务),
|
||||
* 本 handler 被动响应(请求 payload[0]=page,回该页快照);不再主动按铃推送。
|
||||
* - 总开关:sys/config.h 中 CONFIG_MOTOR_STATE_ENABLE
|
||||
*
|
||||
* payload 格式 (每页):
|
||||
* [0] page 页号 (0..kMotorTotalPages-1)
|
||||
* [1] total_pages 总页数 (6,27 槽 = 9 node × 3 ch)
|
||||
* [2] entry_count 本页有效电机数
|
||||
* [3] active_count 全局在线电机数
|
||||
* [4..] 每个 entry 44B:
|
||||
* [0] node_id, [1] channel, [2] online, [3] dlc,
|
||||
* [4..7] rx_count (LE), [8..11] seq (LE),
|
||||
* [12..43] data[32] (原始 STATUS payload)
|
||||
*/
|
||||
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/motor_state.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include <cstring>
|
||||
|
||||
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
|
||||
|
||||
/* ---- 被动查询 handler ---- */
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr uint16_t kMotorPayloadMax =
|
||||
static_cast<uint16_t>(AppSpiCommand::kTxBufSize - V2_OVERHEAD);
|
||||
|
||||
class SpiMotorStateHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeMotorState;
|
||||
}
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
/* 子命令:payload[0]=0xFE 且 len>=3 → 设置 0xC5 推送周期(payload[1..2]=毫秒 LE)。
|
||||
* 有效页号仅 0..kMotorTotalPages-1,0xFE 作子操作码不冲突。回 ACK 携实际应用毫秒 LE。 */
|
||||
if (ctx.payload_len >= 3U && ctx.payload[0] == kMotorSubCmdSetPeriod) {
|
||||
const uint16_t ms = static_cast<uint16_t>(ctx.payload[1] | (ctx.payload[2] << 8));
|
||||
const uint16_t applied = MotorState::setPushPeriodMs(ms);
|
||||
uint8_t ack[2] = { static_cast<uint8_t>(applied & 0xFFU),
|
||||
static_cast<uint8_t>((applied >> 8) & 0xFFU) };
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, ack, sizeof(ack));
|
||||
return;
|
||||
}
|
||||
|
||||
/* 请求 payload[0] = page (可选, 默认 0) */
|
||||
const uint8_t page = (ctx.payload_len >= 1U) ? ctx.payload[0] : 0U;
|
||||
|
||||
uint8_t payload[kMotorPayloadMax] = {};
|
||||
const uint16_t n = MotorState::buildPayload(page, payload, sizeof(payload));
|
||||
|
||||
if (n == 0U) {
|
||||
uint8_t err = 0xE1U; /* 非法页号 */
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, payload, n);
|
||||
}
|
||||
};
|
||||
|
||||
static SpiMotorStateHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/* 2026-08 请求驱动改造:0xC5 已改 ESP32 周期查询(经 handler 被动响应),
|
||||
* 主动按铃推送状态机整体移除。 */
|
||||
|
||||
void registerSpiMotorStateCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
|
||||
#else /* CONFIG_MOTOR_STATE_ENABLE == 0 */
|
||||
|
||||
void registerSpiMotorStateCommand(AppSpiCommand & /*bus*/) {}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file app_spi_cmd_motor_version.cpp
|
||||
* @brief 多电机版本 0xC6 事件驱动推送(pushStep)。
|
||||
*
|
||||
* 与 0xC1/0xC4/0xC5 同款 ring 按铃模型,但触发方式不同:
|
||||
* - 0xC5 是 250ms 周期轮次;0xC6 是事件驱动——电机回报版本(0x00FF)时
|
||||
* MotorVersion::onCanFrame() 缓存并置 dirty,本 pushStep 在主循环发现
|
||||
* 有待推送(dirty)且 ring 空闲时,打包一帧 0xC6 按铃推给 ESP32。
|
||||
* - 版本是按需查询的低频数据,推送优先级最低(runPending 中排在 0xC1/0xC5/0xC4 之后)。
|
||||
* - 总开关:sys/config.h 中 CONFIG_MOTOR_VERSION_ENABLE
|
||||
*
|
||||
* payload 格式(单帧,Header 2B + 每 entry 29B):
|
||||
* [0] count 本帧节点数
|
||||
* [1] rsvd 保留
|
||||
* [2..] 每个 entry 29B:
|
||||
* [0] node_id, [1..4] seq (LE), [5..28] version[24] (原始版本 payload)
|
||||
*
|
||||
* 仅推送(无被动查询 handler):ESP32 收到后自行缓存,Web 从 ESP32 缓存读取。
|
||||
*/
|
||||
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/motor_version.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include <cstring>
|
||||
|
||||
#if (CONFIG_MOTOR_VERSION_ENABLE != 0)
|
||||
|
||||
namespace {
|
||||
static constexpr uint16_t kVerPayloadMax =
|
||||
static_cast<uint16_t>(AppSpiCommand::kTxBufSize - V2_OVERHEAD);
|
||||
}
|
||||
|
||||
void motorVersionPushStep()
|
||||
{
|
||||
/* 无待推送 → 直接返回(事件驱动,绝大多数主循环迭代在此短路) */
|
||||
if (!MotorVersion::hasPending()) {
|
||||
return;
|
||||
}
|
||||
if (OtaBootRequest::isRebootPending()) {
|
||||
return;
|
||||
}
|
||||
/* 电机 OTA relay 活跃期间停止版本推送,避免按铃干扰 0xD1 数据流
|
||||
* (0xC5/0xC4 已改请求驱动,0xC6 保留本门控)。dirty 保留不丢,OTA 结束后继续推。 */
|
||||
if (MotorOtaRelay::isActive()) {
|
||||
return;
|
||||
}
|
||||
if (AppSpiCommandBus.isRingPending()) {
|
||||
return; /* 上一帧未被 ESP32 读走,下轮再试(dirty 保留不丢) */
|
||||
}
|
||||
|
||||
uint8_t payload[kVerPayloadMax] = {};
|
||||
const uint16_t n = MotorVersion::buildPayload(payload, sizeof(payload));
|
||||
if (n > 0U) {
|
||||
/* ring 空闲时 requestEsp32Read 必成功(单线程主循环,检查与调用之间无并发);
|
||||
* buildPayload 已清掉已打包槽的 dirty。 */
|
||||
(void)AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeMotorVersion, payload, n);
|
||||
}
|
||||
}
|
||||
|
||||
#else /* CONFIG_MOTOR_VERSION_ENABLE == 0 */
|
||||
|
||||
void motorVersionPushStep() {}
|
||||
|
||||
#endif
|
||||
@@ -2,46 +2,102 @@
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/initial.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/systick.h"
|
||||
#include "main.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 汇总 GD32 APP 编译期功能开关 → V2_FEAT_* 位图。
|
||||
*/
|
||||
static uint16_t buildAppFeatureFlags()
|
||||
{
|
||||
uint16_t feat = 0U;
|
||||
#if (CONFIG_SPI_ADC_FEEDBACK_ENABLE != 0)
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_ADC_FEEDBACK);
|
||||
#endif
|
||||
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_UART_TEMP_PRINT);
|
||||
#endif
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_SPI_APP_LOG);
|
||||
#endif
|
||||
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_BRS);
|
||||
#endif
|
||||
if (CONFIG_CAN_CH0_ENABLE) {
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH0);
|
||||
}
|
||||
if (CONFIG_CAN_CH1_ENABLE) {
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH1);
|
||||
}
|
||||
if (CONFIG_CAN_CH2_ENABLE) {
|
||||
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH2);
|
||||
}
|
||||
return feat;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 填充 APP 身份 16B(版本 + feat),供 HELLO_ACK 附加块与 PING 共用。
|
||||
* @param[out] out 至少 V2_APP_IDENTITY_LEN 字节
|
||||
*/
|
||||
void fillGd32AppIdentityPayload(uint8_t *out)
|
||||
{
|
||||
if (out == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::memset(out, 0, V2_APP_IDENTITY_LEN);
|
||||
|
||||
out[0] = (uint8_t)APP_FW_VERSION_MAJOR;
|
||||
out[1] = (uint8_t)APP_FW_VERSION_MINOR;
|
||||
out[2] = (uint8_t)APP_FW_VERSION_PATCH;
|
||||
out[3] = (uint8_t)APP_FW_VERSION_BUILD;
|
||||
|
||||
const BootVersion boot_ver = OtaBootRequest::readBootVersion();
|
||||
out[4] = boot_ver.major;
|
||||
out[5] = boot_ver.minor;
|
||||
out[6] = boot_ver.patch;
|
||||
out[7] = boot_ver.build;
|
||||
|
||||
writeU32Le(&out[8], SysTickTimer::getMs());
|
||||
|
||||
out[12] = V2_FEAT_MAP_VER;
|
||||
writeU16Le(&out[13], buildAppFeatureFlags());
|
||||
/* [15] init_state(1.5.34 起):0=OK 1=degraded 2=critical_fail。
|
||||
* 复用原 reserved 字节——旧 ESP32 忽略该字节、旧 GD32 恒 0(恰与 OK 同义),
|
||||
* 因此不 bump feat_map_ver 即前后兼容;详情经 0xA3 INIT_STATUS 查询。 */
|
||||
out[15] = AppInitializer::initState();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief SPI 0xA1 APP PING 处理器:应答身份 16B。 */
|
||||
class SpiAppPingHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
/** @brief 是否处理该帧类型。 */
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeAppPing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理 PING:填充与 HELLO 相同的身份块并回 0x80。
|
||||
* @param bus 命令总线
|
||||
* @param ctx 请求上下文
|
||||
*/
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
uint8_t alive_payload[19];
|
||||
std::memset(alive_payload, 0, sizeof(alive_payload));
|
||||
alive_payload[0] = 0xA1U; /* magic */
|
||||
alive_payload[1] = 0x55U; /* APP version tag */
|
||||
alive_payload[2] = (uint8_t)APP_FW_VERSION_MAJOR;
|
||||
alive_payload[3] = (uint8_t)APP_FW_VERSION_MINOR;
|
||||
alive_payload[4] = (uint8_t)APP_FW_VERSION_PATCH;
|
||||
alive_payload[5] = (uint8_t)APP_FW_VERSION_BUILD;
|
||||
alive_payload[6] = 0xAAU; /* BOOT version tag */
|
||||
|
||||
const volatile uint32_t *bv0 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR0;
|
||||
const volatile uint32_t *bv1 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR1;
|
||||
uint32_t bw0 = *bv0;
|
||||
uint32_t bw1 = *bv1;
|
||||
alive_payload[7] = (uint8_t)(bw0 & 0xFFU);
|
||||
alive_payload[8] = (uint8_t)((bw0 >> 16) & 0xFFU);
|
||||
alive_payload[9] = (uint8_t)(bw1 & 0xFFU);
|
||||
alive_payload[10] = (uint8_t)((bw1 >> 16) & 0xFFU);
|
||||
|
||||
alive_payload[11] = (uint8_t)(g_app_uptime_sec & 0xFFU);
|
||||
alive_payload[12] = (uint8_t)((g_app_uptime_sec >> 8) & 0xFFU);
|
||||
alive_payload[13] = (uint8_t)((g_app_uptime_sec >> 16) & 0xFFU);
|
||||
alive_payload[14] = (uint8_t)((g_app_uptime_sec >> 24) & 0xFFU);
|
||||
|
||||
/* 与 HELLO_ACK 附加块同一布局,用于存活/uptime 刷新 */
|
||||
uint8_t ping_payload[V2_APP_PING_LEN];
|
||||
fillGd32AppIdentityPayload(ping_payload);
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, alive_payload, sizeof(alive_payload));
|
||||
SpiDefs::kRspAck, ping_payload, sizeof(ping_payload));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,6 +105,7 @@ static SpiAppPingHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
/** @brief 注册 0xA1 PING 命令处理器。 */
|
||||
void registerSpiAppPingCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @file app_spi_cmd_spi_diag.cpp
|
||||
* @brief SPI 0xA4 链路全量诊断命令处理器。
|
||||
*
|
||||
* 供主机 (ESP32 / 调试脚本) 查询 GD32 侧 SPI0 从机实时运行诊断指标与异常现场快照:
|
||||
* payload[0]=sub_cmd:
|
||||
* 0 (或空载荷) -> 读取 ~88 字节全量诊断快照 (ACK)
|
||||
* 1 -> 复位清零所有诊断计数器 (ACK 1B: 0x00)
|
||||
* 2 -> 立即输出完整 ASCII 格式化报告到 GD32 USART0 串口 (ACK 1B: 0x00)
|
||||
*/
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/spi/app_spi_diag.h"
|
||||
#include "gd32e50x_gpio.h"
|
||||
#include "sys/config.h"
|
||||
#include <cstring>
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
|
||||
namespace {
|
||||
|
||||
class SpiDiagHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
{
|
||||
return frame_type == SpiDefs::kTypeSpiDiag;
|
||||
}
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
uint8_t sub_cmd = 0U;
|
||||
if (ctx.payload_len >= 1U && ctx.payload != nullptr) {
|
||||
sub_cmd = ctx.payload[0];
|
||||
}
|
||||
|
||||
if (sub_cmd == 1U) {
|
||||
// 复位清零诊断计数器
|
||||
resetSpiDiagStats();
|
||||
uint8_t status = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &status, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub_cmd == 2U) {
|
||||
// 立即向串口输出全量 ASCII 诊断报告
|
||||
dumpSpiDiag();
|
||||
uint8_t status = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &status, 1U);
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认 / sub_cmd == 0: 返回二进制全量指标快照
|
||||
const SpiDiagStats &st = getSpiDiagStats();
|
||||
uint8_t buf[88] = {};
|
||||
|
||||
buf[0] = 0x00U; // status OK
|
||||
buf[1] = bus.isV2Active() ? 1U : 0U;
|
||||
writeU32Le(&buf[2], bus.getSessionId());
|
||||
buf[6] = bus.getLocalCredit();
|
||||
buf[7] = (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == SET) ? 1U : 0U;
|
||||
buf[8] = (gpio_input_bit_get(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN) == SET) ? 1U : 0U;
|
||||
buf[9] = bus.isCsActive() ? 1U : 0U;
|
||||
buf[10] = bus.isRingPending() ? 1U : 0U;
|
||||
writeU16Le(&buf[11], st.last_spi_stat);
|
||||
|
||||
writeU32Le(&buf[13], st.total_cs_assert);
|
||||
writeU32Le(&buf[17], st.total_cs_release);
|
||||
writeU32Le(&buf[21], st.valid_v2_frames);
|
||||
writeU32Le(&buf[25], st.parse_fail_count);
|
||||
writeU32Le(&buf[29], st.err_crc_mismatch);
|
||||
|
||||
writeU16Le(&buf[33], (uint16_t)st.err_no_sof);
|
||||
writeU16Le(&buf[35], (uint16_t)st.err_sof_drift);
|
||||
writeU16Le(&buf[37], (uint16_t)st.err_bad_len);
|
||||
writeU16Le(&buf[39], (uint16_t)st.rx_overrun_count);
|
||||
writeU16Le(&buf[41], (uint16_t)st.short_rx_len_count);
|
||||
writeU16Le(&buf[43], (uint16_t)st.zero_rx_len_count);
|
||||
writeU16Le(&buf[45], (uint16_t)st.all_ff_count);
|
||||
writeU16Le(&buf[47], (uint16_t)st.all_00_count);
|
||||
writeU16Le(&buf[49], (uint16_t)st.session_mismatch);
|
||||
writeU16Le(&buf[51], (uint16_t)st.seq_jump_count);
|
||||
writeU16Le(&buf[53], (uint16_t)st.ring_timeout_count);
|
||||
writeU16Le(&buf[55], (uint16_t)st.unknown_type_count);
|
||||
writeU16Le(&buf[57], (uint16_t)st.handler_empty_count);
|
||||
|
||||
buf[59] = st.last_rx_type;
|
||||
buf[60] = st.last_tx_type;
|
||||
buf[61] = st.last_error_code;
|
||||
buf[62] = st.last_error_reason;
|
||||
writeU16Le(&buf[63], (uint16_t)st.last_rx_len);
|
||||
writeU16Le(&buf[65], (uint16_t)st.last_error_rx_len);
|
||||
memcpy(&buf[67], st.last_error_bytes, 8);
|
||||
writeU32Le(&buf[75], st.last_error_calc_crc);
|
||||
writeU32Le(&buf[79], st.last_error_expected_crc);
|
||||
writeU32Le(&buf[83], st.last_error_uptime_ms);
|
||||
buf[87] = st.last_error_sof_offset;
|
||||
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, buf, sizeof(buf));
|
||||
}
|
||||
};
|
||||
|
||||
static SpiDiagHandler s_handler;
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerSpiDiagCommand(AppSpiCommand &bus)
|
||||
{
|
||||
bus.registerHandler(&s_handler);
|
||||
}
|
||||
|
||||
#else /* CONFIG_SPI_DIAG_ENABLE == 0 */
|
||||
|
||||
void registerSpiDiagCommand(AppSpiCommand & /*bus*/) {}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
#ifndef DEVICE_APP_SPI_COMMAND_H
|
||||
#ifndef DEVICE_APP_SPI_COMMAND_H
|
||||
#define DEVICE_APP_SPI_COMMAND_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "sys/config.h"
|
||||
#include "device/spi/app_spi_diag.h"
|
||||
|
||||
class ISpiCommandHandler;
|
||||
|
||||
@@ -12,20 +14,26 @@ class ISpiCommandHandler;
|
||||
* - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI)
|
||||
* - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler
|
||||
*
|
||||
* 具体命令处理逻辑不再放在这里,而是拆到 `device/spi/app_spi_cmd_*.cpp`,
|
||||
* 通过 `registerHandler()` 注册进来,保持本模块可扩展。
|
||||
* 具体命令处理逻辑不再放在这里,而是拆到 device/spi/app_spi_cmd_*.cpp,
|
||||
* 通过 registerHandler() 注册进来,保持本模块可扩展。
|
||||
*
|
||||
* 握手架构(v2 DATA_READY,PB6<->ESP32 GPIO10 开漏单线):
|
||||
* - PB6 低 = 有待发数据(m_txLen>0 或 m_ringPending),高 = 队列空。
|
||||
* - ESP32 主动发起:CS 发请求,CS 释放后 GD32 在 onCsRelease() ISR 内
|
||||
* 完成帧处理+响应装载,由 updateDataReady() 据 m_txLen 驱动 PB6。
|
||||
* - GD32 主动按铃:requestEsp32Read() 设 m_ringPending=true 且 m_txLen>0,
|
||||
* updateDataReady() 拉低 PB6,ESP32 检测下降沿后发起读事务读走请求帧。
|
||||
* - PB6 配置为开漏输出,setSpiReady(true)=高阻释放,setSpiReady(false)=下拉。
|
||||
* - ESP32 不再操作 PB6,EXTI6 上升沿仅清标志,无业务含义。
|
||||
*/
|
||||
class AppSpiCommand {
|
||||
public:
|
||||
/** @brief 初始化 SPI0 从机命令通道并注册默认命令处理器。 */
|
||||
void init();
|
||||
|
||||
/** @brief 主循环轮询:打印延迟日志。 */
|
||||
/** @brief 主循环轮询:打印延迟日志 + GD32 主动按铃超时看护。 */
|
||||
void poll();
|
||||
|
||||
/** @brief 主循环调用:执行 ISR 中推迟的 SPI 命令解析(从 onCsRelease 移出)。 */
|
||||
void processPendingCommand();
|
||||
|
||||
/** @brief SPI0 中断处理入口。 */
|
||||
void onInterrupt();
|
||||
|
||||
@@ -44,6 +52,9 @@ public:
|
||||
/** @brief 记录一条带 1 字节参数的待输出日志。 */
|
||||
void postLogFmt(const char *fmt, uint8_t val);
|
||||
|
||||
/** @brief 记录一条带双 32 位参数的待输出日志。 */
|
||||
void postLogFmt32(const char *fmt, uint32_t v1, uint32_t v2);
|
||||
|
||||
/**
|
||||
* @brief 将应答帧构建到指定发送缓冲区。
|
||||
* @param[out] tx_buf 发送缓冲区。
|
||||
@@ -57,26 +68,87 @@ public:
|
||||
uint16_t *tx_len, uint8_t rsp_type,
|
||||
const uint8_t *payload, uint16_t payload_len);
|
||||
|
||||
static constexpr uint16_t kRxBufSize = 128U;
|
||||
static constexpr uint16_t kTxBufSize = 128U;
|
||||
static constexpr uint16_t kLogBufSize = 256U;
|
||||
/**
|
||||
* @brief GD32 主动按铃:下拉 SPIready,通知 ESP32「我有请求帧要给你读」。
|
||||
*
|
||||
* 将提供的请求帧装入内部 TX 缓冲区(供下次 ESP32 读事务通过 DMA 输出到 MISO),
|
||||
* 然后下拉 SPIready 线。ESP32 检测到下降沿后会发起一次 SPI 读事务把该帧读走。
|
||||
* 调用方负责确保 frame_type 与 payload 符合协议帧格式。
|
||||
*
|
||||
* @param[in] frame_type 请求帧类型(复用 SPI 协议帧 TYPE)。
|
||||
* @param[in] payload 请求帧载荷,可为 nullptr。
|
||||
* @param[in] payload_len 载荷长度。
|
||||
* @return true 成功下拉并装入 TX;false 当前已有未读按铃或帧构建失败。
|
||||
*
|
||||
* @note 可在主循环或 ISR 中调用。若上一次按铃尚未被 ESP32 读走(m_ringPending),
|
||||
* 则返回 false,调用方应稍后重试。按铃后启动 m_ringDeadline 超时看护,
|
||||
* 超时则放弃本次按铃(防止 ESP32 不响应时永久占用 SPIready 线)。
|
||||
*/
|
||||
bool requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len);
|
||||
|
||||
/** @brief 查询当前是否有未读按铃(ESP32 尚未来读)。 */
|
||||
bool isRingPending() const { return m_ringPending; }
|
||||
|
||||
/** @brief 查询 v2 会话是否已建立。 */
|
||||
bool isV2Active() const;
|
||||
|
||||
/** @brief 查询当前 v2 会话 ID。 */
|
||||
uint32_t getSessionId() const;
|
||||
|
||||
/** @brief 查询当前 local credit。 */
|
||||
uint8_t getLocalCredit() const;
|
||||
|
||||
/** @brief 查询 CS 是否当前处于激活(低电平)状态。 */
|
||||
bool isCsActive() const { return m_csActive; }
|
||||
|
||||
/** @brief 打印完整 ASCII SPI 链路诊断报告。 */
|
||||
void dumpDiag() const;
|
||||
|
||||
/** @brief 取消未完成的按铃(释放 SPIready、清 m_ringPending)。
|
||||
* 0xB0 进 BOOT 等路径需公开调用,避免 0xC1 残留盖住 ACK。 */
|
||||
void cancelRing();
|
||||
|
||||
/**
|
||||
* @brief ESP32 掉电/重上电后复位 SPI 链路(PC9 翻转时调用)。
|
||||
*
|
||||
* ESP32 被 EN 拉低时可能死在半笔 CS 事务里:session/序号/按铃/DMA 都会脏。
|
||||
* 重上电 HELLO 前必须清干净,否则首帧对不齐 → 永远建不起 v2 会话。
|
||||
* 清 session(HELLO 会重建)、取消按铃、re-arm DMA、释放 DATA_READY。
|
||||
*/
|
||||
void resetEsp32Link();
|
||||
|
||||
static constexpr uint16_t kRxBufSize = CONFIG_SPI_RX_BUF_SIZE;
|
||||
static constexpr uint16_t kTxBufSize = CONFIG_SPI_TX_BUF_SIZE;
|
||||
static constexpr uint16_t kLogBufSize = CONFIG_SPI_LOG_BUF_SIZE;
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kMaxCommands = 8U;
|
||||
static constexpr uint8_t kMaxCommands = CONFIG_SPI_MAX_COMMANDS;
|
||||
/** GD32 主动按铃超时(ms):见 CONFIG_SPI_RING_TIMEOUT_MS。 */
|
||||
static constexpr uint32_t kRingTimeoutMs = CONFIG_SPI_RING_TIMEOUT_MS;
|
||||
|
||||
volatile bool m_csActive = false;
|
||||
volatile uint8_t m_rxLen = 0U;
|
||||
uint8_t m_txLen = 0U;
|
||||
volatile uint8_t m_txPos = 0U;
|
||||
volatile bool m_logPending = false;
|
||||
volatile bool m_cmdPending = false; /**< ISR 置位,标记有待主循环解析的 SPI 命令。 */
|
||||
bool m_txActiveThisCs = false;
|
||||
/** 本笔 CS 实际收到的字节数(CS 上升沿时由 RX DMA CHCNT 反算得到)。 */
|
||||
volatile uint16_t m_rxLen = 0U;
|
||||
volatile uint16_t m_txLen = 0U;
|
||||
/** 日志环形缓冲区:避免高频/多事件并发时单缓冲覆盖丢日志。 */
|
||||
static constexpr uint8_t kLogSlotCount = 8U;
|
||||
static constexpr uint8_t kLogSlotLen = 128U;
|
||||
char m_logQueue[kLogSlotCount][kLogSlotLen] = {};
|
||||
volatile uint8_t m_logHead = 0U;
|
||||
volatile uint8_t m_logTail = 0U;
|
||||
/** RX DMA 接收缓冲(替代旧 RBNE 逐字节累积的 m_rxBuf)。
|
||||
* CS 上升沿时 m_rxLen 为本笔实际收到的字节数,本缓冲前 m_rxLen 字节有效。 */
|
||||
uint8_t m_rxBuf[kRxBufSize] = {};
|
||||
uint8_t m_txBuf[kTxBufSize] = {};
|
||||
char m_logBuf[kLogBufSize] = {};
|
||||
uint8_t m_commandCount = 0U;
|
||||
ISpiCommandHandler *m_commands[kMaxCommands] = {};
|
||||
|
||||
/** GD32 主动按铃状态:true 表示已下拉 SPIready 等待 ESP32 来读。 */
|
||||
volatile bool m_ringPending = false;
|
||||
/** 按铃超时截止时刻(ms,基于 SysTickTimer::getMs)。 */
|
||||
volatile uint32_t m_ringDeadline = 0U;
|
||||
|
||||
void configureGpio();
|
||||
void configureSpi0();
|
||||
void configureDma();
|
||||
@@ -84,9 +156,49 @@ private:
|
||||
void onCsAssert();
|
||||
void onCsRelease();
|
||||
void processCommand();
|
||||
|
||||
/** @brief 响应装载完成后的统一收尾:按铃 + updateDataReady。 */
|
||||
void finishResponseAndReady();
|
||||
|
||||
/** @brief 设置 m_ringPending=true 并启动超时看护截止时刻。 */
|
||||
void armRingPending();
|
||||
|
||||
/** @brief 在 CS 空闲期预装下一笔全双工 DMA(TX + RX)。
|
||||
* 必须在首个 SCK 之前完成,否则 EXTI CS 下降相对 ESP32 时钟偏晚,
|
||||
* 整笔事务会收 0 字节。对齐 BOOT SpiOta::armSpiDmasForNextCs。 */
|
||||
void armSpiDmasForNextCs();
|
||||
|
||||
/** @brief 翻转 SPI Ready 握手引脚(PB6)电平(开漏线与)。
|
||||
* @param[in] ready true=释放(高阻,靠上拉拉高), false=下拉(驱动低)。
|
||||
* @note GPIO BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。
|
||||
* 开漏模式下 setSpiReady(true) 仅写 BRR 不驱动高,由外部上拉拉高。
|
||||
*/
|
||||
void setSpiReady(bool ready);
|
||||
|
||||
/** @brief v2 DATA_READY 语义:依据待发数据驱动 PB6。
|
||||
* PB6 低 = 有待发数据(m_txLen>0 或 m_ringPending),高 = 队列空。
|
||||
* 替代旧 BUSY/READY 语义中所有 setSpiReady 调用。 */
|
||||
void updateDataReady();
|
||||
};
|
||||
|
||||
/* 2026-08 请求驱动改造:0xC1 CAN 反馈 / 0xC5 电机状态 / 0xC4 ADC 已改
|
||||
* ESP32 周期查询(本端仅被动响应 handler),主动按铃推送函数整体移除
|
||||
* (canFeedbackPushStep/canFeedbackStartRound/adcFeedbackPushStep/
|
||||
* adcFeedbackStartRound/motorStatePushStep/motorStateStartRound)。 */
|
||||
|
||||
/**
|
||||
* @brief 多电机版本(0xC6)事件驱动推送步进:有 dirty 版本且 ring 空闲时按铃推一帧。
|
||||
* @note CONFIG_MOTOR_VERSION_ENABLE=0 时为空操作。低频、事件驱动,保留推送。
|
||||
*/
|
||||
void motorVersionPushStep();
|
||||
|
||||
/** @brief 全局 SPI 命令总线实例。 */
|
||||
extern AppSpiCommand AppSpiCommandBus;
|
||||
|
||||
/**
|
||||
* @brief 填充 APP 身份 16B(版本 + 功能开关),供 HELLO_ACK 与 PING 共用。
|
||||
* @param out 至少 V2_APP_IDENTITY_LEN 字节。
|
||||
*/
|
||||
void fillGd32AppIdentityPayload(uint8_t *out);
|
||||
|
||||
#endif /* DEVICE_APP_SPI_COMMAND_H */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef DEVICE_APP_SPI_COMMAND_DEFS_H
|
||||
#ifndef DEVICE_APP_SPI_COMMAND_DEFS_H
|
||||
#define DEVICE_APP_SPI_COMMAND_DEFS_H
|
||||
|
||||
#include <cstdint>
|
||||
@@ -14,26 +14,89 @@
|
||||
|
||||
namespace SpiDefs {
|
||||
|
||||
static constexpr uint8_t kSof = 0xA5U;
|
||||
|
||||
/* 帧类型 */
|
||||
static constexpr uint8_t kTypeOtaReq = 0x01U;
|
||||
static constexpr uint8_t kTypeOtaData = 0x02U;
|
||||
static constexpr uint8_t kTypeOtaCancel = 0x03U;
|
||||
static constexpr uint8_t kTypeAppPing = 0xA1U;
|
||||
/** @brief EXMC/HBI 诊断命令(ESP32→GD32):payload[0]=action。
|
||||
* 0=stop(恢复AF_PP), 1-4=数据pattern(0xFFFF/0x0000/0xAAAA/0x5555),
|
||||
* 5=CS低, 6=RD低, 7=WR低, 8=A1高, 9=A2高。用于 LAN9252 通断测试。
|
||||
* 20~24=ECAT_NRST(PB10) 30~34=LAN9252 电源管理
|
||||
* 40~44=ESP32 EN(PC9):40/42=拉高 41=拉低保持 43=浮空 44=回读
|
||||
* (运行期开关请走 EtherCAT SDO 0x2503,本组仅 SPI 诊断备援) */
|
||||
static constexpr uint8_t kTypeExmcDiag = 0xA2U;
|
||||
/** @brief APP 初始化状态查询(ESP32→GD32,1.5.34 新增):无 payload。
|
||||
* 响应 ACK 25B(1.5.35 起;1.5.34 仅前 12B):
|
||||
* [0..3] module_mask LE(AppInitializer::ModuleBit 成功位图)
|
||||
* [4..7] critical_fail_mask LE(失败的 critical 模块位图,0=无)
|
||||
* [8] reset_cause(ResetCauseBit 位图) [9] init_state(0=OK 1=degraded 2=critical_fail)
|
||||
* [10..11] reserved
|
||||
* [12..24] fault_codes[13](每模块 1B 故障码,0=成功;编码见 sys/initial.h,
|
||||
* canfd = 原因码|(首个失败通道<<4))。位定义与 sys/initial.h 一致。 */
|
||||
static constexpr uint8_t kTypeInitStatus = 0xA3U;
|
||||
/** @brief SPI 链路全量诊断查询与控制(ESP32→GD32):
|
||||
* payload[0]=sub_cmd: 0=读取全量诊断快照 (ACK ~88B), 1=复位诊断计数器, 2=打印完整报告到 GD32 串口。
|
||||
* 若 payload 为空,默认执行 sub_cmd=0。 */
|
||||
static constexpr uint8_t kTypeSpiDiag = 0xA4U;
|
||||
static constexpr uint8_t kTypeBootReq = 0xB0U;
|
||||
static constexpr uint8_t kTypeCanSend = 0xC0U;
|
||||
static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */
|
||||
static constexpr uint8_t kTypeCanSetMode = 0xC2U; /**< ESP32 -> GD32 切换 CAN 接收过滤模式 */
|
||||
static constexpr uint8_t kTypeIdSnapshot = 0xC3U; /**< ESP32 -> GD32 拉取不重复 ID 快照表 */
|
||||
/** @brief GD32 ADC 调试反馈(温度/VDDA/VIN);1Hz 主动 EVENT 或被动查询。
|
||||
* 由 CONFIG_SPI_ADC_FEEDBACK_ENABLE 门控,调试用可随时关。 */
|
||||
static constexpr uint8_t kTypeAdcFeedback = 0xC4U;
|
||||
/** @brief 多电机状态推送;GD32 1Hz 主动 EVENT,将 CAN STATUS 帧缓存表推给 ESP32。
|
||||
* 由 CONFIG_MOTOR_STATE_ENABLE 门控。 */
|
||||
static constexpr uint8_t kTypeMotorState = 0xC5U;
|
||||
/** @brief 多电机版本推送;GD32 收到电机版本应答(0x00FF)后事件驱动 EVENT,
|
||||
* 将缓存的 24B 版本推给 ESP32(按需查询、低频,不进周期推送)。
|
||||
* 由 CONFIG_MOTOR_VERSION_ENABLE 门控。 */
|
||||
static constexpr uint8_t kTypeMotorVersion = 0xC6U;
|
||||
|
||||
/** @brief CAN 跨通道突发发送(ESP32→GD32):一次 SPI 事务背靠背入多个 CAN 邮箱,
|
||||
* 实现跨通道近同时发送(组内 <1ms)。
|
||||
* payload: count(1) dlc(1) suffix_hi(1) suffix_lo(1) 然后 count 个条目,
|
||||
* 每条目 = chnode(1) + data[dlc];chnode=(ch<<4)|node,id=(node<<16)|suffix。
|
||||
* 响应 ACK: count(1) + 每条目 1 字节状态(CanTxResult 码)。 */
|
||||
static constexpr uint8_t kTypeCanBurstSend = 0xC7U;
|
||||
|
||||
/** @brief CAN 通道收发器 Silent 开关(ESP32→GD32):只听不发(驱动关断、接收照常)。
|
||||
* 请求 payload: [0] channel 0/1/2, [1] silent 1=进 Silent / 0=回 Normal。
|
||||
* 应答 ACK: [0] channel, [1] 实际生效的 silent(与发送门控同一个值),[2] 该通道是否使能。
|
||||
* ERROR: 0xE0=payload 过短, 0xE1=通道未使能/非法, 0xE2=S 脚回读与意图不符(标志已回滚)。
|
||||
* 生效期间该通道所有发送入口(0xC0/0xC7/桥接/relay)返回 0xE6=kErrSilent,
|
||||
* 不计错误计数;静默不持久化,上电 init 一律回 Normal。 */
|
||||
static constexpr uint8_t kTypeCanSetSilent = 0xC8U;
|
||||
|
||||
/** @brief 电机 OTA relay 命令(ESP32→GD32):start / cancel / query。
|
||||
* payload[0]=cmd(0=start,1=cancel,2=query), start 时 payload[1]=ch, payload[2..5]=fw_size LE */
|
||||
static constexpr uint8_t kTypeMotorOtaCmd = 0xD0U;
|
||||
/** @brief 电机 OTA relay 数据(ESP32→GD32):固件明文块,GD32 本地加密后发 CAN。 */
|
||||
static constexpr uint8_t kTypeMotorOtaData = 0xD1U;
|
||||
/** @brief 电机 OTA relay 状态推送(GD32→ESP32 ring push):12B 进度。 */
|
||||
static constexpr uint8_t kTypeMotorOtaStatus = 0xD2U;
|
||||
|
||||
/* 响应类型 */
|
||||
static constexpr uint8_t kRspAck = 0x80U;
|
||||
static constexpr uint8_t kRspWait = 0x81U;
|
||||
static constexpr uint8_t kRspError = 0x82U;
|
||||
|
||||
static constexpr uint16_t kFrameOverhead = 5U;
|
||||
|
||||
} // namespace SpiDefs
|
||||
|
||||
/** @brief 将 16 位值按小端写入 2 字节缓冲。 */
|
||||
inline void writeU16Le(uint8_t *dst, uint16_t val) {
|
||||
dst[0] = static_cast<uint8_t>(val & 0xFFU);
|
||||
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
|
||||
}
|
||||
|
||||
/** @brief 将 32 位值按小端写入 4 字节缓冲(供 CAN 反馈/ID 快照等 payload 复用)。 */
|
||||
inline void writeU32Le(uint8_t *dst, uint32_t val) {
|
||||
dst[0] = static_cast<uint8_t>(val & 0xFFU);
|
||||
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
|
||||
dst[2] = static_cast<uint8_t>((val >> 16) & 0xFFU);
|
||||
dst[3] = static_cast<uint8_t>((val >> 24) & 0xFFU);
|
||||
}
|
||||
|
||||
#endif /* DEVICE_APP_SPI_COMMAND_DEFS_H */
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#ifndef DEVICE_APP_SPI_DIAG_H
|
||||
#define DEVICE_APP_SPI_DIAG_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "sys/config.h"
|
||||
|
||||
/**
|
||||
* @file app_spi_diag.h
|
||||
* @brief SPI 链路全量硬件与协议诊断模块声明。
|
||||
*
|
||||
* 用于快速量化与定位 SPI 通信故障根因:
|
||||
* - 物理/时序层:CS 边沿跳变、丢失下降沿、0 字节事务(SCK未走/DMA落后)、短包(<20B)、全FF/全00
|
||||
* - 硬件状态:SPI0 RXORERR (接收FIFO溢出)、FERR (格式错误)、SPI_STAT 寄存器值
|
||||
* - 协议/帧层:SOF丢失、相位偏移(SOF drift)、版本错误、超长/截断、CRC32位跳变(附带期望值与计算值)
|
||||
* - 状态机/会话:Session ID脱节、序号跳变(丢包)、序号重复(重发)、未处理命令TYPE、空响应
|
||||
* - 握手/流控:PB6 DATA_READY 引脚电平、PA15 CS 引脚电平、主动按铃超时、按铃竞争碰撞
|
||||
* - 现场快照:记录最近一次异常发生时刻、实际接收长度、前导原始字节及 CRC 对比
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief SPI 帧解析失败原因细分
|
||||
*/
|
||||
enum SpiParseResult : uint8_t {
|
||||
SPI_PARSE_OK = 0U, ///< 成功解析合法 v2 帧
|
||||
SPI_PARSE_ERR_TOO_SHORT = 1U, ///< 事务长度小于 v2 最小帧头 (rxLen < 20)
|
||||
SPI_PARSE_ERR_ALL_FF = 2U, ///< 接收数据全为 0xFF (从机浮空/DMA未就绪/MISO未连接)
|
||||
SPI_PARSE_ERR_ALL_00 = 3U, ///< 接收数据全为 0x00 (地电平短路或总线死锁)
|
||||
SPI_PARSE_ERR_NO_SOF = 4U, ///< 未扫描到 SOF 同步字 (0xA5 0x5A)
|
||||
SPI_PARSE_ERR_BAD_VERSION = 5U, ///< 找到 SOF,但 VERSION != 0x02
|
||||
SPI_PARSE_ERR_BAD_LEN = 6U, ///< 长度异常 (payload_len > 236 或帧总长超出实际收到的 rxLen)
|
||||
SPI_PARSE_ERR_CRC_MISMATCH = 7U, ///< CRC32 校验失败 (物理信号干扰/位跳变)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SPI 链路全量诊断计数器与状态快照
|
||||
*/
|
||||
struct SpiDiagStats {
|
||||
/* 1. CS 边沿与事务统计 */
|
||||
uint32_t total_cs_assert; ///< CS 下降沿次数
|
||||
uint32_t total_cs_release; ///< CS 上升沿次数
|
||||
uint32_t missed_cs_assert; ///< 丢失 CS 下降沿异常次数
|
||||
uint32_t zero_rx_len_count; ///< RX 收到 0 字节 (SCK 未时钟化或 DMA 启动严重落后)
|
||||
uint32_t short_rx_len_count; ///< RX 收到 1~19 字节 (<20B 残包)
|
||||
uint32_t all_ff_count; ///< 全 0xFF 事务数
|
||||
uint32_t all_00_count; ///< 全 0x00 事务数
|
||||
|
||||
/* 2. 硬件外设与状态 */
|
||||
uint32_t rx_overrun_count; ///< SPI0 RXORERR (接收 FIFO 溢出)
|
||||
uint32_t format_error_count; ///< SPI0 FERR (帧格式错误)
|
||||
uint16_t last_spi_stat; ///< 最近一笔事务结束时的 SPI_STAT 寄存器值
|
||||
|
||||
/* 3. 帧解析统计 */
|
||||
uint32_t valid_v2_frames; ///< 成功解析的合法 v2 帧总数
|
||||
uint32_t parse_fail_count; ///< 帧解析失败总数
|
||||
uint32_t err_no_sof; ///< 未找到 SOF0(0xA5)+SOF1(0x5A)
|
||||
uint32_t err_sof_drift; ///< SOF 偏移不在索引 0 (前导毛刺或字节时序偏移)
|
||||
uint32_t err_bad_version; ///< 版本不匹配
|
||||
uint32_t err_bad_len; ///< 长度字段错误或事务截断
|
||||
uint32_t err_crc_mismatch; ///< CRC32 校验失败
|
||||
|
||||
/* 4. 会话与序号流控 */
|
||||
uint32_t hello_count; ///< 收到 LINK_HELLO 次数
|
||||
uint32_t session_mismatch; ///< Session ID 不匹配 (对端重置或会话脱节)
|
||||
uint32_t seq_ok_count; ///< 序号严格递增正常次数
|
||||
uint32_t seq_jump_count; ///< 序号跳变 (丢帧)
|
||||
uint32_t seq_dup_count; ///< 序号重复 (重发包)
|
||||
uint16_t last_rx_len; ///< 最近收到的 rx_len (字节数)
|
||||
uint8_t last_rx_seq; ///< 最近收到的 rx_seq
|
||||
uint8_t expected_rx_seq; ///< 当前期望的 rx_seq
|
||||
uint8_t last_tx_seq; ///< 最近发送的 tx_seq
|
||||
|
||||
/* 5. 命令路由与处理 */
|
||||
uint8_t last_rx_type; ///< 最近收到的命令 TYPE
|
||||
uint8_t last_tx_type; ///< 最近构建的响应 TYPE
|
||||
uint32_t unknown_type_count; ///< 未知命令 TYPE (无 handler 注册)
|
||||
uint8_t last_unknown_type; ///< 最近未知的 TYPE 值
|
||||
uint32_t handler_empty_count; ///< Handler 执行但返回 tx_len == 0
|
||||
uint8_t last_empty_type; ///< 最近产出 0 字节的 TYPE 值
|
||||
uint32_t error_rsp_count; ///< GD32 主动返回 0x82 ERROR 响应次数
|
||||
uint8_t last_error_code; ///< 最近一次返回 0x82 的错误码 (0xE8/0xE9/0xEA/V2_ERR_*)
|
||||
|
||||
/* 6. 握手引脚与流控 */
|
||||
uint32_t ring_request_count; ///< 主动按铃请求次数
|
||||
uint32_t ring_timeout_count; ///< 按铃超时 (ESP32 未在期限内读走)
|
||||
uint32_t ring_collision_count; ///< 按铃与 ESP32 主动发帧竞争碰撞次数
|
||||
uint8_t current_local_credit; ///< 当前 local credit (0 或 1)
|
||||
|
||||
/* 7. 最近异常快照 (用于定位具体错误现场) */
|
||||
uint32_t last_error_uptime_ms; ///< 异常发生时刻 SysTick 毫秒数
|
||||
uint16_t last_error_rx_len; ///< 异常事务收到的字节数
|
||||
uint8_t last_error_bytes[8]; ///< 异常事务前 8 字节原始数据
|
||||
uint32_t last_error_calc_crc; ///< CRC 错误时: 计算出的 CRC
|
||||
uint32_t last_error_expected_crc;///< CRC 错误时: 帧内携带的 CRC
|
||||
uint8_t last_error_sof_offset; ///< SOF 所在的偏移位置 (255 表示未找到)
|
||||
uint8_t last_error_reason; ///< SpiParseResult 错误原因枚举
|
||||
};
|
||||
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
/** @brief 获取当前全局 SPI 诊断统计快照引用 */
|
||||
const SpiDiagStats& getSpiDiagStats();
|
||||
|
||||
/** @brief 复位清零所有 SPI 诊断统计计数器 */
|
||||
void resetSpiDiagStats();
|
||||
|
||||
/** @brief 将解析结果枚举转换为可读字符串 */
|
||||
const char* spiParseResultToString(SpiParseResult res);
|
||||
|
||||
/** @brief 打印完整 SPI 链路诊断报告到串口 (ASCII 表格) */
|
||||
void dumpSpiDiag();
|
||||
#else
|
||||
inline const SpiDiagStats& getSpiDiagStats()
|
||||
{
|
||||
static const SpiDiagStats s_empty{};
|
||||
return s_empty;
|
||||
}
|
||||
inline void resetSpiDiagStats() {}
|
||||
inline const char* spiParseResultToString(SpiParseResult /*res*/) { return "DISABLED"; }
|
||||
inline void dumpSpiDiag() {}
|
||||
#endif
|
||||
|
||||
#endif /* DEVICE_APP_SPI_DIAG_H */
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifndef DEVICE_SPI_COMMAND_HANDLER_H
|
||||
#ifndef DEVICE_SPI_COMMAND_HANDLER_H
|
||||
#define DEVICE_SPI_COMMAND_HANDLER_H
|
||||
|
||||
#include <cstdint>
|
||||
@@ -16,10 +16,10 @@ struct SpiCommandContext {
|
||||
|
||||
class ISpiCommandHandler {
|
||||
public:
|
||||
~ISpiCommandHandler() {}
|
||||
|
||||
virtual bool canHandle(uint8_t frame_type) const = 0;
|
||||
virtual void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) = 0;
|
||||
protected:
|
||||
~ISpiCommandHandler() = default; /* 接口基类,不允许通过基类指针 delete;裸机不退出。 */
|
||||
};
|
||||
|
||||
#endif /* DEVICE_SPI_COMMAND_HANDLER_H */
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @file temp_sensor.cpp
|
||||
* @brief 温度传感模块实现。
|
||||
*/
|
||||
|
||||
#include "device/adc.h"
|
||||
#include "device/temp_sensor.h"
|
||||
|
||||
/**
|
||||
* @brief 初始化温度传感采样依赖模块。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前内部直接复用 `Adc::init()` 完成温度与参考电压采样链路初始化。
|
||||
*/
|
||||
void TempSensor::init()
|
||||
{
|
||||
Adc::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取一组温度传感相关采样结果。
|
||||
* @param[in] data 指向输出采样结构体的指针。
|
||||
* @param[out] data 输出温度、VREF 与 VIN 相关采样结果;允许为空。
|
||||
* @retval 无返回值。
|
||||
* @note 当前内部直接转调 `Adc::collectSample()`。
|
||||
*/
|
||||
void TempSensor::sample(Data *data)
|
||||
{
|
||||
Adc::collectSample(data);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef DEVICE_TEMP_SENSOR_H
|
||||
#define DEVICE_TEMP_SENSOR_H
|
||||
|
||||
#include "device/adc.h"
|
||||
|
||||
/**
|
||||
* @brief 温度采样模块。
|
||||
* @note 当前作为 ADC 模块的轻量门面,复用 Adc::Sample 作为结果类型。
|
||||
*/
|
||||
class TempSensor {
|
||||
public:
|
||||
using Data = Adc::Sample;
|
||||
|
||||
/**
|
||||
* @brief 初始化温度采样依赖资源。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前通过调用 Adc::init() 完成内部通道准备。
|
||||
*/
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* @brief 采集一次温度相关样本。
|
||||
* @param[in] 无。
|
||||
* @param[out] data 指向输出 Data 结构体的指针。
|
||||
* @retval 无返回值。
|
||||
* @note 内部调用 Adc::collectSample() 填充完整 ADC 结果。
|
||||
*/
|
||||
static void sample(Data *data);
|
||||
};
|
||||
|
||||
using temp_sensor_data_t = TempSensor::Data;
|
||||
|
||||
#endif /* DEVICE_TEMP_SENSOR_H */
|
||||
@@ -7,11 +7,12 @@
|
||||
#include "device/usart0.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/critical.h"
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
uint8_t Usart0::s_txBuf[Usart0::kTxBufSize];
|
||||
volatile uint16_t Usart0::s_txHead = 0U;
|
||||
volatile uint16_t Usart0::s_txTail = 0U;
|
||||
volatile uint32_t Usart0::s_dropCount = 0U;
|
||||
volatile uint32_t Usart0::s_txDropped = 0U;
|
||||
|
||||
/**
|
||||
* @brief 计算发送环形缓冲区中的下一个索引。
|
||||
@@ -24,17 +25,6 @@ uint16_t Usart0::nextIndex(uint16_t idx)
|
||||
return (idx + 1U) % kTxBufSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断 USART0 发送缓冲区是否已满。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示发送缓冲区已满,false 表示仍有可写空间。
|
||||
*/
|
||||
bool Usart0::isBufferFull()
|
||||
{
|
||||
return nextIndex(s_txHead) == s_txTail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断 USART0 发送缓冲区是否为空。
|
||||
* @param[in] 无。
|
||||
@@ -47,20 +37,54 @@ bool Usart0::isBufferEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 以非阻塞方式向 USART0 发送缓冲写入一个字符。
|
||||
* @brief 在临界区内直接轮询 TBE 发出一个字节,腾出一个缓冲槽位。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功发出一字节(s_txTail 已前进),false 表示等待 TBE 超时。
|
||||
* @note 调用者必须已持有临界区:USART0 中断为 preempt=3,已被 BASEPRI 屏蔽,
|
||||
* 故此处独占 s_txTail,不会与 onInterrupt() 取到同一字节。
|
||||
* 走轮询而非等中断,因此在中断上下文里调用 printf 也不会死锁。
|
||||
*/
|
||||
bool Usart0::drainOneByteLocked()
|
||||
{
|
||||
if(isBufferEmpty()) {
|
||||
return false;
|
||||
}
|
||||
uint32_t spin = kTbeSpinMax;
|
||||
while(usart_flag_get(USART0, USART_FLAG_TBE) == RESET) {
|
||||
if(--spin == 0U) {
|
||||
return false; /* USART 未在发送(外设异常/时钟丢失),交由调用者计丢弃 */
|
||||
}
|
||||
}
|
||||
usart_data_transmit(USART0, s_txBuf[s_txTail]);
|
||||
s_txTail = nextIndex(s_txTail);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向 USART0 发送缓冲写入一个字符,缓冲满时自旋腾位而非丢弃。
|
||||
* @param[in] ch 待发送字符。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功写入发送缓冲,false 表示缓冲已满而被丢弃。
|
||||
* @note 当前统一由 TBE 中断从队列中取字节发送;此处只负责入队并使能发送中断。
|
||||
* @retval bool true 表示成功写入发送缓冲,false 表示 USART 异常导致该字节被丢弃。
|
||||
* @note 正常路径仍是"入队 + 使能 TBE 中断"的非阻塞发送。仅在缓冲写满时
|
||||
* (3M 波特率下 init 阶段诊断突发写入会写满)就地轮询发出一字节腾位,
|
||||
* 以字节级背压换取日志完整——旧版此处静默丢弃且 putChar 忽略返回值,
|
||||
* 实测使 [ECAT] 诊断出现 "bus eaabled"/"pdi_type=0x8Dexect" 等掉字,
|
||||
* 曾导致 LAN9252 故障被两次误定性,故日志完整性优先于此处的等待开销。
|
||||
* 仅当 USART 外设本身不发送时才真正丢弃并累加 s_txDropped。
|
||||
*/
|
||||
bool Usart0::putCharNonBlocking(int ch)
|
||||
{
|
||||
uint32_t save = RT_CRITICAL_SAVE();
|
||||
const uint16_t next = nextIndex(s_txHead);
|
||||
uint16_t next = nextIndex(s_txHead);
|
||||
if(next == s_txTail) {
|
||||
RT_CRITICAL_RESTORE(save);
|
||||
s_dropCount++;
|
||||
return false;
|
||||
/* 缓冲已满:就地发出最老的一字节腾出槽位,失败则说明 USART 真的没在发。 */
|
||||
if(!drainOneByteLocked()) {
|
||||
++s_txDropped;
|
||||
RT_CRITICAL_RESTORE(save);
|
||||
return false;
|
||||
}
|
||||
next = nextIndex(s_txHead);
|
||||
}
|
||||
|
||||
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
|
||||
@@ -123,10 +147,10 @@ void Usart0::configurePins()
|
||||
* @brief 初始化 USART0 外设与中断。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @retval uint8_t Usart0::InitFault 故障码(0=USART 已使能,CTL0.UEN 回读置位)。
|
||||
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 TBE 中断用于队列发送。
|
||||
*/
|
||||
void Usart0::init()
|
||||
uint8_t Usart0::init()
|
||||
{
|
||||
rcu_periph_clock_enable(RCU_USART0);
|
||||
usart_deinit(USART0);
|
||||
@@ -138,6 +162,7 @@ void Usart0::init()
|
||||
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
|
||||
usart_enable(USART0);
|
||||
nvic_irq_enable(USART0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U);
|
||||
return ((USART_CTL0(USART0) & USART_CTL0_UEN) != 0U) ? kFaultOk : kFaultNotEnabled;
|
||||
}
|
||||
|
||||
/* ----- 中断入口 / printf 重定向的 C 包装 --------------------------- */
|
||||
@@ -158,4 +183,9 @@ extern "C" int fputc(int ch, FILE *f) { return Usart0::putChar(ch, f); }
|
||||
* @retval 无返回值。
|
||||
* @note 该 C 包装函数内部转调 `Usart0::onInterrupt()`。
|
||||
*/
|
||||
extern "C" void USART0_IRQHandler(void) { Usart0::onInterrupt(); }
|
||||
extern "C" void USART0_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
Usart0::onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -19,14 +19,20 @@ public:
|
||||
*/
|
||||
static void configurePins();
|
||||
|
||||
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
|
||||
enum InitFault : uint8_t {
|
||||
kFaultOk = 0U, /**< 成功 */
|
||||
kFaultNotEnabled = 1U, /**< CTL0.UEN 回读未置位(使能未生效) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 初始化 USART0 外设与 NVIC。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @retval uint8_t InitFault 故障码(0=USART 已使能,CTL0.UEN 回读置位)。
|
||||
* @note 使用 8N1 格式,收发使能,同步配置 NVIC 以便 TXE 中断驱动发送。
|
||||
*/
|
||||
static void init();
|
||||
static uint8_t init();
|
||||
|
||||
/**
|
||||
* @brief 将字符写入 USART0 发送环形缓冲。
|
||||
@@ -56,14 +62,6 @@ private:
|
||||
*/
|
||||
static uint16_t nextIndex(uint16_t idx);
|
||||
|
||||
/**
|
||||
* @brief 判断环形发送缓冲是否已满。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示已满,false 表示可继续写入。
|
||||
*/
|
||||
static bool isBufferFull();
|
||||
|
||||
/**
|
||||
* @brief 判断环形发送缓冲是否为空。
|
||||
* @param[in] 无。
|
||||
@@ -74,11 +72,36 @@ private:
|
||||
|
||||
static bool putCharNonBlocking(int ch);
|
||||
|
||||
static constexpr uint16_t kTxBufSize = 1024U;
|
||||
static volatile uint32_t s_dropCount;
|
||||
/**
|
||||
* @brief 在临界区内直接轮询 TBE 并发出一个字节,腾出一个缓冲槽位。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功发出一字节(tail 已前进),false 表示等待 TBE 超时。
|
||||
* @note 调用者必须已持有临界区(USART0 中断 preempt=3 被 BASEPRI 屏蔽),
|
||||
* 因此这里独占 s_txTail,不会与 onInterrupt() 争用同一字节。
|
||||
* 不依赖中断投递,故在任意上下文(含 ISR 内 printf)均可安全使用。
|
||||
*/
|
||||
static bool drainOneByteLocked();
|
||||
|
||||
/** @brief 3M 波特率下 init 阶段诊断是突发写入,1024 字节会溢出致日志掉字。 */
|
||||
static constexpr uint16_t kTxBufSize = 4096U;
|
||||
/** @brief 等待 TBE 的自旋上限:3M 下单字节约 3.3us,此值远大于之,仅防 USART 失效死等。 */
|
||||
static constexpr uint32_t kTbeSpinMax = 200000U;
|
||||
static uint8_t s_txBuf[kTxBufSize];
|
||||
static volatile uint16_t s_txHead;
|
||||
static volatile uint16_t s_txTail;
|
||||
static volatile uint32_t s_txDropped;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 读取因发送缓冲溢出而被丢弃的字节数。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval uint32_t 累计丢弃字节数(0 表示日志完整可信)。
|
||||
* @note 非 0 说明串口日志有掉字,诊断结论不可全信——历史上掉字日志曾导致
|
||||
* LAN9252 故障两次误定性,故显式暴露该计数供 init 报告核对。
|
||||
*/
|
||||
static uint32_t droppedBytes() { return s_txDropped; }
|
||||
};
|
||||
|
||||
#endif /* DEVICE_USART0_H */
|
||||
@@ -11,21 +11,41 @@
|
||||
#include "sys/config.h"
|
||||
#include "main.h"
|
||||
|
||||
void Watchdog::init()
|
||||
uint8_t Watchdog::init()
|
||||
{
|
||||
/* FWDGT 的 PSC/RLD 写入经 LSI(IRC40K) 时钟域同步(PUD/RUD 由硬件清零)。
|
||||
* BOOT 只检测 FWDGT 复位标志、从不启动看门狗,冷启动时 LSI 未开——
|
||||
* 直接配置会让 PUD/RUD 永远等不到清零(库超时返回 ERROR)。
|
||||
* 必须先手动开 LSI 并等就绪,再配置。 */
|
||||
rcu_osci_on(RCU_IRC40K);
|
||||
if (rcu_osci_stab_wait(RCU_IRC40K) != SUCCESS) {
|
||||
return kFaultLsiNotReady;
|
||||
}
|
||||
|
||||
/* PSC/RLD 配置函数自带 ErrStatus(等待 PUD/RUD 清零带超时);
|
||||
* 必须检查返回值,否则写入失败会让看门狗实际未按预期运行。 */
|
||||
fwdgt_write_enable();
|
||||
fwdgt_prescaler_value_config(CONFIG_FWDGT_PSC);
|
||||
fwdgt_reload_value_config(CONFIG_FWDGT_RLD);
|
||||
uint8_t fault = kFaultOk;
|
||||
if (fwdgt_prescaler_value_config((uint16_t)CONFIG_FWDGT_PSC) != SUCCESS) {
|
||||
fault = kFaultPsc;
|
||||
}
|
||||
if (fwdgt_reload_value_config((uint16_t)CONFIG_FWDGT_RLD) != SUCCESS) {
|
||||
if (fault == kFaultOk) {
|
||||
fault = kFaultRld;
|
||||
}
|
||||
}
|
||||
fwdgt_write_disable();
|
||||
fwdgt_enable();
|
||||
/* 使能后确认无挂起更新(PUD/RUD 应已清零) */
|
||||
if ((FWDGT_STAT & (FWDGT_STAT_PUD | FWDGT_STAT_RUD)) != 0U) {
|
||||
if (fault == kFaultOk) {
|
||||
fault = kFaultPending;
|
||||
}
|
||||
}
|
||||
return fault;
|
||||
}
|
||||
|
||||
void Watchdog::feed()
|
||||
{
|
||||
fwdgt_counter_reload();
|
||||
}
|
||||
|
||||
void Watchdog::abandon()
|
||||
{
|
||||
/* 不喂狗即可,FWDGT 超时后硬件自动复位。 */
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* @file watchdog.h
|
||||
* @brief APP 独立看门狗模块(FWDGT)。
|
||||
*
|
||||
@@ -12,14 +12,21 @@
|
||||
|
||||
class Watchdog {
|
||||
public:
|
||||
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。 */
|
||||
static void init();
|
||||
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
|
||||
enum InitFault : uint8_t {
|
||||
kFaultOk = 0U, /**< 成功 */
|
||||
kFaultPsc = 1U, /**< 预分频写入未被硬件确认(PUD 超时) */
|
||||
kFaultRld = 2U, /**< 重载值写入未被硬件确认(RUD 超时) */
|
||||
kFaultPending = 3U, /**< 使能后仍有挂起的配置更新(PUD/RUD 未清零) */
|
||||
kFaultLsiNotReady = 4U, /**< LSI(IRC40K) 使能后稳定等待超时 */
|
||||
};
|
||||
|
||||
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。
|
||||
* @retval uint8_t InitFault 故障码(0=PSC/RLD 写入均经硬件确认且无挂起更新)。 */
|
||||
static uint8_t init();
|
||||
|
||||
/** @brief 喂狗(重载计数器),应在主循环中周期调用。 */
|
||||
static void feed();
|
||||
|
||||
/** @brief 停止喂狗,让看门狗自然超时触发复位跳转 BOOT。 */
|
||||
static void abandon();
|
||||
};
|
||||
|
||||
#endif /* DEVICE_WATCHDOG_H */
|
||||
|
||||
+19
-11
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "main.h"
|
||||
#include "sys/initial.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/task_runner.h"
|
||||
#include "sys/config.h"
|
||||
@@ -19,13 +20,13 @@
|
||||
*/
|
||||
void App::printBanner() const
|
||||
{
|
||||
printf("\r\n");
|
||||
printf("========================================\r\n");
|
||||
printf(" GD32E508 DCU APP started OK\r\n");
|
||||
printf(" System clock: 180 MHz\r\n");
|
||||
printf(" Build: " __DATE__ " " __TIME__ "\r\n");
|
||||
printf("========================================\r\n");
|
||||
printf("[APP] APP running, uptime_sec=0\r\n");
|
||||
APP_LOGI("\r\n");
|
||||
APP_LOGI("========================================\r\n");
|
||||
APP_LOGI(" GD32E508 DCU APP started OK\r\n");
|
||||
APP_LOGI(" System clock: 180 MHz\r\n");
|
||||
APP_LOGI(" Build: " __DATE__ " " __TIME__ "\r\n");
|
||||
APP_LOGI("========================================\r\n");
|
||||
APP_LOGI("[APP] APP running, uptime_sec=0\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,16 +38,23 @@ void App::printBanner() const
|
||||
*/
|
||||
int App::run()
|
||||
{
|
||||
printf("[APP] >>> main() entered, SystemCoreClock=%lu\r\n", (unsigned long)SystemCoreClock);
|
||||
printf("[APP] >>> calling AppInitializer::init()...\r\n");
|
||||
APP_LOGI("[APP] >>> main() entered, SystemCoreClock=%lu\r\n", (unsigned long)SystemCoreClock);
|
||||
APP_LOGI("[APP] >>> calling AppInitializer::init()...\r\n");
|
||||
AppInitializer::init();
|
||||
printf("[APP] >>> init done, calling TaskRunner::init()...\r\n");
|
||||
APP_LOGI("[APP] >>> init done, calling TaskRunner::init()...\r\n");
|
||||
TaskRunner::init();
|
||||
printf("[APP] >>> TaskRunner ready, entering main loop\r\n");
|
||||
APP_LOGI("[APP] >>> TaskRunner ready, entering main loop\r\n");
|
||||
printBanner();
|
||||
|
||||
CpuLoad::init();
|
||||
|
||||
while(1) {
|
||||
/* 负载率测量(1.5.63 定义收窄):runPending 内部按"非延迟工作"分段挂钩
|
||||
* (ISR 全量 + 任务派发 + 电机 OTA relay,见 sys/cpu_load.h);
|
||||
* 常开轮询(SSC MainLoop / 各 poll)与 NOP 空转不计入。
|
||||
* tick() 每圈无条件做 1s 窗口滚动检查——滚动不依赖本圈是否有挂钩段。 */
|
||||
TaskRunner::runPending();
|
||||
CpuLoad::tick();
|
||||
#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0)
|
||||
uint32_t save = RT_CRITICAL_SAVE();
|
||||
if(TaskManager::state().task_flags == 0U) {
|
||||
|
||||
@@ -15,16 +15,10 @@ enum system_task_t {
|
||||
TASK_ADC_VREF_SAMPLE = 0, /**< 采样 VIN/VREF 原始值任务。 */
|
||||
TASK_ADC_TEMP_SAMPLE, /**< 采样内部温度通道原始值任务。 */
|
||||
TASK_ADC_PROCESS, /**< 对 ADC 原始值进行电压与温度计算任务。 */
|
||||
TASK_CAN_RX_POLL, /**< 兼容保留的 CANFD 接收轮询任务。 */
|
||||
TASK_CAN_RX_PARSE, /**< 解析并消费已接收 CANFD 帧任务。 */
|
||||
TASK_CAN_TX_PREPARE, /**< 预留的 CANFD 发送准备任务,默认不主动置位。 */
|
||||
TASK_CAN_TX_SEND, /**< 预留的 CANFD 发送执行任务,默认不主动置位。 */
|
||||
TASK_STATUS_LED, /**< 状态灯维护任务。 */
|
||||
TASK_CAN_TX_SEND, /**< CANFD 周期发送任务,每 10ms 每通道发送一帧。 */
|
||||
TASK_HEARTBEAT, /**< 系统心跳维护任务。 */
|
||||
TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */
|
||||
TASK_SPI_LOG, /**< SPI 延迟日志打印任务。 */
|
||||
TASK_ETHERCAT_PROCESS, /**< EtherCAT 周期/事件处理任务(非实时应用层 PDO 映射)。 */
|
||||
TASK_SPI_CMD_PROCESS, /**< SPI CS release 后的命令解析任务(从 ISR 移出)。 */
|
||||
TASK_COUNT /**< 任务总数,用于边界或统计场景。 */
|
||||
};
|
||||
|
||||
@@ -46,10 +40,12 @@ struct task_scheduler_t {
|
||||
volatile uint16_t div_temp_sample;
|
||||
/** @brief 调度分频计数:CAN 接收处理。 */
|
||||
volatile uint16_t div_can_rx;
|
||||
/** @brief 调度分频计数:状态灯。 */
|
||||
/** @brief 调度分频计数:CAN 周期发送(10ms)。 */
|
||||
volatile uint16_t div_can_tx;
|
||||
/** @brief 调度分频计数:心跳与诊断打印。 */
|
||||
/** @brief 调度分频计数:心跳(1s)。 */
|
||||
volatile uint16_t div_heartbeat;
|
||||
/** @brief 调度分频计数:APP 状态/诊断打印(5s)。 */
|
||||
volatile uint16_t div_diag_print;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file ota_layout.h
|
||||
* @brief APP/BOOT 共用 OTA Flash 布局(唯一真源)。
|
||||
* @note 两侧 sys/config.h 都必须 include 本头,禁止再抄地址常量。
|
||||
* 相对路径:BOOT/APP 的 sys/config.h → ../../../shared/ota_layout.h
|
||||
*
|
||||
* 约定:0x00000000=AVAL(有效),0xFFFFFFFF=NOT_AVAL(擦除态/无效)。
|
||||
*/
|
||||
#ifndef SHARED_OTA_LAYOUT_H
|
||||
#define SHARED_OTA_LAYOUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- 整片 Flash 区段(GD32E508VE 512KB) ---- */
|
||||
#define OTA_LAYOUT_BOOT_START 0x08000000U
|
||||
#define OTA_LAYOUT_BOOT_SIZE 0x00012000U /* 72KB */
|
||||
#define OTA_LAYOUT_APP_BACK_START 0x08012000U /* 216KB 备份区 */
|
||||
#define OTA_LAYOUT_APP_MAIN_START 0x08048000U /* 216KB 主应用区 = APP VTOR */
|
||||
#define OTA_LAYOUT_APP_MAX_SIZE 0x00035FFCU /* 216KB-4B,末尾 CRC32 */
|
||||
#define OTA_LAYOUT_CONFIG_PAGE 0x0807E000U /* OTA 状态配置页 2KB */
|
||||
#define OTA_LAYOUT_SLEEP_PAGE 0x0807E800U /* 睡眠配置页 2KB */
|
||||
#define OTA_LAYOUT_PAGE_SIZE 0x00000800U /* FMC 单页 2KB */
|
||||
|
||||
/* ---- OTA 配置页 word 布局(word N = PAGE + 4*N) ---- */
|
||||
#define OTA_LAYOUT_WORD_LATEST_AVAL 0U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_AVAL 1U
|
||||
#define OTA_LAYOUT_WORD_LATEST_EN 2U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_EN 3U
|
||||
#define OTA_LAYOUT_WORD_LATEST_CHECK 4U
|
||||
#define OTA_LAYOUT_WORD_BACKUP_CHECK 5U
|
||||
#define OTA_LAYOUT_WORD_BOOT_VER0 6U /* major|minor */
|
||||
#define OTA_LAYOUT_WORD_BOOT_VER1 7U /* patch|build */
|
||||
#define OTA_LAYOUT_WORD_ECAT_ARM 8U /* 0xACA70001 才允许 ECAT SDO 刷 APP */
|
||||
#define OTA_LAYOUT_WORD_CRASH_COUNT 9U /* 连续 FWDGT 崩溃次数;APP 起板成功清 0 */
|
||||
|
||||
#define OTA_LAYOUT_ADDR_LATEST_AVAL (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_AVAL)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_AVAL (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_AVAL)
|
||||
#define OTA_LAYOUT_ADDR_LATEST_EN (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_EN)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_EN (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_EN)
|
||||
#define OTA_LAYOUT_ADDR_LATEST_CHECK (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_LATEST_CHECK)
|
||||
#define OTA_LAYOUT_ADDR_BACKUP_CHECK (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BACKUP_CHECK)
|
||||
#define OTA_LAYOUT_ADDR_BOOT_VER0 (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BOOT_VER0)
|
||||
#define OTA_LAYOUT_ADDR_BOOT_VER1 (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_BOOT_VER1)
|
||||
#define OTA_LAYOUT_ADDR_ECAT_ARM (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_ECAT_ARM)
|
||||
#define OTA_LAYOUT_ADDR_CRASH_COUNT (OTA_LAYOUT_CONFIG_PAGE + 4U * OTA_LAYOUT_WORD_CRASH_COUNT)
|
||||
|
||||
/** 配置页有效 word 数(擦页回写必须覆盖到此);页 2KB,余量充足。 */
|
||||
#define OTA_LAYOUT_WORD_COUNT 10U
|
||||
|
||||
/** ECAT 下载 arm 令牌。避开擦除态与 AVAL。 */
|
||||
#define OTA_LAYOUT_ECAT_ARM_KEY 0xACA70001U
|
||||
|
||||
/** 连续 FWDGT 达到该次数后 BOOT 停止自动跳 APP,只接受外部灌写。 */
|
||||
#define OTA_LAYOUT_CRASH_STAY_MAX 3U
|
||||
|
||||
#define OTA_LAYOUT_AVAL 0x00000000U
|
||||
#define OTA_LAYOUT_NOT_AVAL 0xFFFFFFFFU
|
||||
|
||||
#endif /* SHARED_OTA_LAYOUT_H */
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @file aes128.cpp
|
||||
* @brief AES-128 ECB 加解密实现(移植自电机 BOOT 工程 sys/aes128.cpp)。
|
||||
*/
|
||||
#include "sys/aes128.h"
|
||||
|
||||
/* S盒 */
|
||||
static const uint8_t sbox[256] = {
|
||||
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
||||
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
||||
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
||||
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
||||
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
||||
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
||||
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
||||
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
||||
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
||||
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
||||
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
||||
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
||||
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
||||
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
||||
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
||||
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
|
||||
};
|
||||
|
||||
/* 逆S盒 */
|
||||
static const uint8_t inv_sbox[256] = {
|
||||
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
|
||||
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
|
||||
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
|
||||
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
|
||||
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
|
||||
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
|
||||
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
|
||||
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
|
||||
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
|
||||
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
|
||||
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
|
||||
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
|
||||
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
|
||||
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
|
||||
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
|
||||
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
|
||||
};
|
||||
|
||||
/* 轮常量 */
|
||||
static const uint8_t rcon[10] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};
|
||||
|
||||
static uint8_t xtime(uint8_t x) { return static_cast<uint8_t>((x << 1) ^ (((x >> 7) & 1) * 0x1b)); }
|
||||
|
||||
static uint8_t gf_mul(uint8_t a, uint8_t b) {
|
||||
uint8_t p = 0;
|
||||
while (b) {
|
||||
if (b & 1) p ^= a;
|
||||
const uint8_t hi = a & 0x80;
|
||||
a <<= 1;
|
||||
if (hi) a ^= 0x1b;
|
||||
b >>= 1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void add_round_key(uint8_t *state, const uint8_t *rk) {
|
||||
for (uint8_t i = 0; i < 16; i++) state[i] ^= rk[i];
|
||||
}
|
||||
|
||||
static void sub_bytes(uint8_t *state) {
|
||||
for (uint8_t i = 0; i < 16; i++) state[i] = sbox[state[i]];
|
||||
}
|
||||
|
||||
static void inv_sub_bytes(uint8_t *state) {
|
||||
for (uint8_t i = 0; i < 16; i++) state[i] = inv_sbox[state[i]];
|
||||
}
|
||||
|
||||
static void shift_rows(uint8_t *s) {
|
||||
uint8_t t;
|
||||
t=s[1]; s[1]=s[5]; s[5]=s[9]; s[9]=s[13]; s[13]=t;
|
||||
t=s[2]; s[2]=s[10]; s[10]=t; t=s[6]; s[6]=s[14]; s[14]=t;
|
||||
t=s[3]; s[3]=s[15]; s[15]=s[11]; s[11]=s[7]; s[7]=t;
|
||||
}
|
||||
|
||||
static void inv_shift_rows(uint8_t *s) {
|
||||
uint8_t t;
|
||||
t=s[13]; s[13]=s[9]; s[9]=s[5]; s[5]=s[1]; s[1]=t;
|
||||
t=s[2]; s[2]=s[10]; s[10]=t; t=s[6]; s[6]=s[14]; s[14]=t;
|
||||
t=s[7]; s[7]=s[11]; s[11]=s[15]; s[15]=s[3]; s[3]=t;
|
||||
}
|
||||
|
||||
static void mix_columns(uint8_t *s) {
|
||||
for (uint8_t c = 0; c < 4; c++) {
|
||||
const uint8_t i = c * 4;
|
||||
const uint8_t a0=s[i], a1=s[i+1], a2=s[i+2], a3=s[i+3];
|
||||
s[i] = xtime(a0) ^ xtime(a1) ^ a1 ^ a2 ^ a3;
|
||||
s[i+1] = a0 ^ xtime(a1) ^ xtime(a2) ^ a2 ^ a3;
|
||||
s[i+2] = a0 ^ a1 ^ xtime(a2) ^ xtime(a3) ^ a3;
|
||||
s[i+3] = xtime(a0) ^ a0 ^ a1 ^ a2 ^ xtime(a3);
|
||||
}
|
||||
}
|
||||
|
||||
static void inv_mix_columns(uint8_t *s) {
|
||||
for (uint8_t c = 0; c < 4; c++) {
|
||||
const uint8_t i = c * 4;
|
||||
const uint8_t a0=s[i], a1=s[i+1], a2=s[i+2], a3=s[i+3];
|
||||
s[i] = gf_mul(0x0e,a0)^gf_mul(0x0b,a1)^gf_mul(0x0d,a2)^gf_mul(0x09,a3);
|
||||
s[i+1] = gf_mul(0x09,a0)^gf_mul(0x0e,a1)^gf_mul(0x0b,a2)^gf_mul(0x0d,a3);
|
||||
s[i+2] = gf_mul(0x0d,a0)^gf_mul(0x09,a1)^gf_mul(0x0e,a2)^gf_mul(0x0b,a3);
|
||||
s[i+3] = gf_mul(0x0b,a0)^gf_mul(0x0d,a1)^gf_mul(0x09,a2)^gf_mul(0x0e,a3);
|
||||
}
|
||||
}
|
||||
|
||||
static void key_expansion(aes128_context *ctx) {
|
||||
memcpy(ctx->round_keys[0], ctx->key, 16);
|
||||
for (uint8_t i = 1; i <= 10; i++) {
|
||||
uint8_t temp[4];
|
||||
temp[0] = ctx->round_keys[i-1][12];
|
||||
temp[1] = ctx->round_keys[i-1][13];
|
||||
temp[2] = ctx->round_keys[i-1][14];
|
||||
temp[3] = ctx->round_keys[i-1][15];
|
||||
const uint8_t t = temp[0];
|
||||
temp[0]=temp[1]; temp[1]=temp[2]; temp[2]=temp[3]; temp[3]=t;
|
||||
temp[0]=sbox[temp[0]]; temp[1]=sbox[temp[1]];
|
||||
temp[2]=sbox[temp[2]]; temp[3]=sbox[temp[3]];
|
||||
temp[0] ^= rcon[i-1];
|
||||
ctx->round_keys[i][0] = ctx->round_keys[i-1][0] ^ temp[0];
|
||||
ctx->round_keys[i][1] = ctx->round_keys[i-1][1] ^ temp[1];
|
||||
ctx->round_keys[i][2] = ctx->round_keys[i-1][2] ^ temp[2];
|
||||
ctx->round_keys[i][3] = ctx->round_keys[i-1][3] ^ temp[3];
|
||||
for (uint8_t j = 1; j < 4; j++) {
|
||||
ctx->round_keys[i][j*4+0] = ctx->round_keys[i][(j-1)*4+0] ^ ctx->round_keys[i-1][j*4+0];
|
||||
ctx->round_keys[i][j*4+1] = ctx->round_keys[i][(j-1)*4+1] ^ ctx->round_keys[i-1][j*4+1];
|
||||
ctx->round_keys[i][j*4+2] = ctx->round_keys[i][(j-1)*4+2] ^ ctx->round_keys[i-1][j*4+2];
|
||||
ctx->round_keys[i][j*4+3] = ctx->round_keys[i][(j-1)*4+3] ^ ctx->round_keys[i-1][j*4+3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void aes128_set_key(aes128_context *ctx, const uint8_t *key) {
|
||||
memcpy(ctx->key, key, AES128_KEY_LEN);
|
||||
key_expansion(ctx);
|
||||
}
|
||||
|
||||
void aes128_encrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
|
||||
memcpy(ctx->state, input, AES_BLOCK_SIZE);
|
||||
add_round_key(ctx->state, ctx->round_keys[0]);
|
||||
for (uint8_t r = 1; r <= 9; r++) {
|
||||
sub_bytes(ctx->state);
|
||||
shift_rows(ctx->state);
|
||||
mix_columns(ctx->state);
|
||||
add_round_key(ctx->state, ctx->round_keys[r]);
|
||||
}
|
||||
sub_bytes(ctx->state);
|
||||
shift_rows(ctx->state);
|
||||
add_round_key(ctx->state, ctx->round_keys[10]);
|
||||
memcpy(output, ctx->state, AES_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
void aes128_decrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
|
||||
memcpy(ctx->state, input, AES_BLOCK_SIZE);
|
||||
add_round_key(ctx->state, ctx->round_keys[10]);
|
||||
for (uint8_t r = 9; r >= 1; r--) {
|
||||
inv_shift_rows(ctx->state);
|
||||
inv_sub_bytes(ctx->state);
|
||||
add_round_key(ctx->state, ctx->round_keys[r]);
|
||||
inv_mix_columns(ctx->state);
|
||||
}
|
||||
inv_shift_rows(ctx->state);
|
||||
inv_sub_bytes(ctx->state);
|
||||
add_round_key(ctx->state, ctx->round_keys[0]);
|
||||
memcpy(output, ctx->state, AES_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
|
||||
aes128_encrypt_block(ctx, input, output);
|
||||
aes128_encrypt_block(ctx, input + 16, output + 16);
|
||||
aes128_encrypt_block(ctx, input + 32, output + 32);
|
||||
}
|
||||
|
||||
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
|
||||
aes128_decrypt_block(ctx, input, output);
|
||||
aes128_decrypt_block(ctx, input + 16, output + 16);
|
||||
aes128_decrypt_block(ctx, input + 32, output + 32);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @file aes128.h
|
||||
* @brief AES-128 ECB 加解密(纯软件实现,移植自电机 BOOT 工程)。
|
||||
* 用于电机 OTA relay 中对固件数据加密及密钥协商。
|
||||
*/
|
||||
#ifndef SYS_AES128_H
|
||||
#define SYS_AES128_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
static constexpr uint8_t AES128_KEY_LEN = 16U;
|
||||
static constexpr uint8_t AES_BLOCK_SIZE = 16U;
|
||||
|
||||
struct aes128_context {
|
||||
uint8_t key[AES128_KEY_LEN];
|
||||
uint8_t round_keys[11][16];
|
||||
uint8_t state[AES_BLOCK_SIZE];
|
||||
};
|
||||
|
||||
void aes128_set_key(aes128_context *ctx, const uint8_t *key);
|
||||
void aes128_encrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output);
|
||||
void aes128_decrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output);
|
||||
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output);
|
||||
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output);
|
||||
|
||||
#endif /* SYS_AES128_H */
|
||||
+364
-217
@@ -6,67 +6,260 @@
|
||||
#ifndef SYS_CONFIG_H
|
||||
#define SYS_CONFIG_H
|
||||
|
||||
/** @brief 应用启动横幅字符串。 */
|
||||
#define APP_BANNER_TEXT "GD32E508 DCU task scheduler start"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "gd32e50x.h"
|
||||
/* APP/BOOT 共用 OTA Flash 布局(唯一真源);相对路径见该头注释。 */
|
||||
#include "../shared/ota_layout.h"
|
||||
|
||||
/* ============================================================
|
||||
* 功能开关(调试/特性总闸 — 改这里即可开关,改完重编)
|
||||
* 0/false=关,1/true=开。需要 #if 的保留 #define。
|
||||
* ============================================================ */
|
||||
/**
|
||||
* @brief 5s 诊断任务是否输出全量 UART 摘要(task_runner)。
|
||||
* @note 任务本身始终 5s 调度:0=默认仅状态变化/错误增量;1=周期打印 ADC/CAN/ECAT 全量行。
|
||||
* 现场抓吞吐/温度时改回 1 后重编即可。
|
||||
*/
|
||||
#define CONFIG_UART_TEMP_PERIODIC_PRINT 0
|
||||
/**
|
||||
* @brief 全量摘要详细级别(仅 PERIODIC_PRINT=1 时生效;0=ADC 精简 1=含 CAN 统计)。
|
||||
* @note 默认 0;需要 can_tx/rx/drop 等列时与 PERIODIC_PRINT 一并打开。
|
||||
*/
|
||||
#define CONFIG_UART_DIAG_LEVEL 0
|
||||
/**
|
||||
* @brief SPI 调试反馈 0xC4:GD32 ADC 温度/VDDA/VIN 1Hz 主动推送。
|
||||
* @note 调试用;0=不注册 handler、不推送。与 ESP32 GD32_ADC_FEEDBACK_ENABLE 配对。
|
||||
*/
|
||||
#define CONFIG_SPI_ADC_FEEDBACK_ENABLE 1
|
||||
/**
|
||||
* @brief 多电机状态表 0xC5:CAN STATUS 帧缓存 + 250ms 主动推送。
|
||||
* @note 0=不注册 handler、不推送、不解析 STATUS 帧。与 ESP32 端 motor_state_task 配对。
|
||||
*/
|
||||
#define CONFIG_MOTOR_STATE_ENABLE 1
|
||||
/**
|
||||
* @brief 多电机版本 0xC6:CAN 版本应答帧(0x00FF)缓存 + 事件驱动推送。
|
||||
* @note 0=不识别版本帧、不推送。与 ESP32 端 motor_version 缓存配对。
|
||||
* 版本为按需查询的低频数据,独立于 0xC5 周期推送。
|
||||
*/
|
||||
#define CONFIG_MOTOR_VERSION_ENABLE 1
|
||||
/** @brief 最大电机节点数 (node ID 1-9)。 */
|
||||
constexpr uint8_t CONFIG_MOTOR_MAX_NODES = 9U;
|
||||
/** @brief 电机离线判定超时 (ms)。超过此时间未收到 STATUS 帧则标记 offline。 */
|
||||
constexpr uint16_t CONFIG_MOTOR_OFFLINE_MS = 3000U;
|
||||
/** @brief SPI 命令逐帧日志(#if)。开启后仅异常路径输出
|
||||
* (非v2帧/未知type/SEQ跳变/session不匹配),正常状态 UART 静默。
|
||||
* @note 2026-10-09 置 0:SPI 联调已结束,USART0 不再打印逐帧明细。
|
||||
* 排查时改回 1;异常现场数据仍可通过 0xA4 快照读回(check_spi_diag.py),
|
||||
* 因为 CONFIG_SPI_DIAG_ENABLE 仍为 1,计数器与现场快照照常记录。 */
|
||||
#define CONFIG_SPI_APP_LOG_ENABLE 0
|
||||
/**
|
||||
* @brief SPI 链路全量硬件与协议诊断总开关 (0=发布/关闭, 1=开启)。
|
||||
* @note 【一键发布开关】:
|
||||
* - 0 (发布模式): 彻底裁剪 0xA4 诊断指令与 Handler 注册,清空诊断内存占用,
|
||||
* 关闭中断与事务中的所有计数/现场快照记录,完全静默 USART0 诊断输出(零 CPU/RAM/Flash 开销)。
|
||||
* - 1 (调试/诊断模式): 开启 CS/时序/硬件寄存器/SOF偏移/CRC错误统计、现场快照及 0xA4 查询支持。
|
||||
* 发布固件时,将此处改为 0 即可一键关闭全部诊断功能并重新编译。
|
||||
* 当前保持 1:只静默串口输出(见下 PERIODIC_PRINT),保留 0xA4 远程查询能力。
|
||||
*/
|
||||
#define CONFIG_SPI_DIAG_ENABLE 1
|
||||
/**
|
||||
* @brief SPI 诊断 USART0 周期打印开关 (仅在 CONFIG_SPI_DIAG_ENABLE=1 时生效)。
|
||||
* @note 0=静默串口周期输出 (仍保留 0xA4 远程查询与现场快照,不刷屏);
|
||||
* 1=每 5s 周期性或异常发生时在 USART0 打印 [SPI-DIAG] 单行摘要。
|
||||
* 2026-10-09 置 0:按铃超时取消告警同样按本开关裁剪(计数器仍经 SPI_DIAG_RECORD 记账,可 0xA4 读回)。
|
||||
*/
|
||||
#define CONFIG_SPI_DIAG_PERIODIC_PRINT 0
|
||||
/** @brief CAN_SEND 成功帧详细日志(#if)。 */
|
||||
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
|
||||
/** @brief CAN-FD BRS 使能(canfd.cpp #if)。
|
||||
* 关闭 BRS: 数据段用仲裁段同速率,牢牢性更高。
|
||||
* 电机 BOOT 无法正确接收 BRS 高速段→nonce(16B FD+BRS)被拒绝→握手失败。
|
||||
* motor_ota_relay 在 OTA 期间经 CanFd::setChannelBrsSuppress 按通道压制 BRS,
|
||||
* 结束必恢复,日常吞吐不受影响。 */
|
||||
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
||||
/** @brief 启动时总线探测帧;正常固件关。 */
|
||||
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
|
||||
/** @brief 启动时内部回环自检;正常固件关。 */
|
||||
#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0
|
||||
/** @brief CAN 启动详细诊断 dump;默认关。 */
|
||||
#define CONFIG_CAN_STARTUP_DIAG_VERBOSE 0
|
||||
/**
|
||||
* @brief 周期测试发送任务(taskCanTxSend,发 0x18FF5000 64B FD 帧)。
|
||||
* @note 调试用:该 PGN 无业务接收节点 ACK。auto_retrans=ENABLE 时无 ACK 被
|
||||
* 无限重发吞进 TEC(TEC 一直涨);DISABLE 时单发失败即 MTEn→error_count++。
|
||||
* 电机拓扑下无对端应答,默认关以消除持续错误帧。需要回放测试波形时打开。
|
||||
*/
|
||||
#define CONFIG_CAN_PERIODIC_TEST_TX_ENABLE 0
|
||||
/** @brief CAN0/1/2 通道使能(**强制开关,兜底用**)。
|
||||
* 日常不再靠它区分"有没有接设备":未接设备的通道 RX 浮空会持续 STUFF、REC 冲 ~130,
|
||||
* 现由 canfd.cpp 的"空通道自动识别"在运行时判定并静默上报(只抑制打印,硬件保持
|
||||
* NORMAL 接收,插上设备有帧即自动恢复)。三路默认全开,交给自动逻辑。 */
|
||||
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
|
||||
constexpr bool CONFIG_CAN_CH1_ENABLE = true;
|
||||
constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||
/** @brief 空通道自动静默的观察窗口:连续多少个 1Hz 窗口"无有效帧且有错误"才判定为空通道。
|
||||
* 加滞回,避免偶发丢拍/慢流量(<1Hz)被误判。 */
|
||||
constexpr uint32_t CONFIG_CAN_QUIET_WINDOW_SEC = 5U;
|
||||
/** @brief 启动后多久开始允许自动静默(避开电机上电/自检期的正常错误)。 */
|
||||
constexpr uint32_t CONFIG_CAN_QUIET_AFTER_SEC = 10U;
|
||||
/** @brief 主循环 WFI(main.cpp #if)。 */
|
||||
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
|
||||
/** @brief EtherCAT SYNC0 同步模式(#if)。 */
|
||||
#define CONFIG_ETHERCAT_SYNC_MODE 0
|
||||
/** @brief MOTOR-OTA INFO 级日志(握手/进度/流控 chatter);错误与关键事件恒保留。 */
|
||||
#define CONFIG_OTA_RELAY_LOG_ENABLE 1
|
||||
/** @brief CAN 初始化/自检 INFO 级日志;初始化失败与硬件错误恒保留。 */
|
||||
#define CONFIG_CAN_INIT_LOG_ENABLE 1
|
||||
/** @brief EtherCAT/EXMC 初始化 INFO 级日志;FAIL 恒保留。 */
|
||||
#define CONFIG_ECAT_INIT_LOG_ENABLE 1
|
||||
/** @brief HBI bring-up 诊断探针(bit-bang 探活 + predrive/回显 + 20s 周期复跑)。
|
||||
* @note 硬件层已确认全通(IDX_0=0x1234、BYTE_TEST=0x87654321),默认关闭以清理上电日志,
|
||||
* 并避免每次上电/每 20s 把 EXMC 引脚切成 GPIO 手动拉一遍。换板/补焊需重新排查浮空时置 1。
|
||||
* 网页万用表通断测试 continuityDrive() 不受此开关影响,始终可用。 */
|
||||
#define CONFIG_ESC_EXMC_DIAG 0
|
||||
/** @brief [DIFF-STATS] 输入镜像差分统计(0x2000 sub13..23,量化差分写能省多少字节、
|
||||
* run 碎片有多严重、比对自身要多少钱)。临时仪表,取证完置 0 即与改造前逐字节等价。
|
||||
* @note 开启时每拍在 PDI_Isr 内多一次 600B 字粒度比对 + 一次快照拷贝(约 10µs 量级,
|
||||
* 实测见 sub23),因此 **sub9/sub10 的写侧耗时会包含这部分**,不能直接与历史
|
||||
* 不能与历史写侧基准对拍——要看差分潜力就只看 sub13..22,要看真实写侧耗时请置 0 重测。 */
|
||||
#define CONFIG_ECAT_DIFF_STATS 0
|
||||
/**
|
||||
* @brief 输入 PDO 稀疏写 ESC:按密排 count 只写「段1+段2有效区」与「段3+段5」,
|
||||
* 跳过未用到的空槽区(40+count*56 .. 543),省 HW_EscWriteIsr 的 PRAM 字节数。
|
||||
* @note 0=恒 600B 整包写;1=分段写。非密排布局(magic≠0xA5)或 count 撑满时
|
||||
* 自动回退整包。**总长/SII/主站解码均不变**;空洞区在 ESC 中为该 SM 缓冲
|
||||
* 上次残留,主站必须继续只按 count 迭代。
|
||||
*/
|
||||
#define CONFIG_ECAT_SPARSE_ESC_WRITE 1
|
||||
/**
|
||||
* @brief 输入 PDO 差分写 ESC:在稀疏写之上,按「段头(0..543)+段尾(544..599)」
|
||||
* 与上次写入影子比对;内容稳定且冲刷完成则整段跳过,省 PRAM 字节。
|
||||
* @note 3-buffer 安全:SM3 三缓冲轮转,不能只对「上一拍发布镜像」比——目标缓冲
|
||||
* 里是 2 拍前的内容。采用「变化后连续写 3 拍冲刷」协议:变化当拍写并置
|
||||
* flush=2,随后 2 拍强制写完 3 个缓冲,稳定后才允许跳过。全段跳过时强制
|
||||
* 写末 2 字节完成 SM 事件应答。0=关闭,退回稀疏写/整包。SII/主站解码不变。
|
||||
*/
|
||||
#define CONFIG_ECAT_DIFF_ESC_WRITE 1
|
||||
/** @brief 启动横幅与 main 进度打印。 */
|
||||
#define CONFIG_APP_BANNER_LOG_ENABLE 1
|
||||
|
||||
/* ============================================================
|
||||
* 日志分级宏:*_LOGI=INFO 级,由上方模块开关编译期裁剪,
|
||||
* 格式串随之被链接器回收;LOG_ERR=错误/关键事件,恒保留。
|
||||
* 使用文件需自行可见 printf 声明(<cstdio>)。
|
||||
* ============================================================ */
|
||||
#define LOG_ERR(...) printf(__VA_ARGS__)
|
||||
#if CONFIG_OTA_RELAY_LOG_ENABLE
|
||||
#define OTA_LOGI(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define OTA_LOGI(...) ((void)0)
|
||||
#endif
|
||||
#if CONFIG_CAN_INIT_LOG_ENABLE
|
||||
#define CAN_LOGI(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define CAN_LOGI(...) ((void)0)
|
||||
#endif
|
||||
#if CONFIG_ECAT_INIT_LOG_ENABLE
|
||||
#define ECAT_LOGI(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define ECAT_LOGI(...) ((void)0)
|
||||
#endif
|
||||
#if CONFIG_APP_BANNER_LOG_ENABLE
|
||||
#define APP_LOGI(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define APP_LOGI(...) ((void)0)
|
||||
#endif
|
||||
|
||||
/* ============================================================
|
||||
* APP 固件版本号
|
||||
* ============================================================ */
|
||||
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_MINOR = 7U;
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 4U; /* 1.7.4: 电机 OTA 邮箱进料块长 4096→1024——APP __HEAP_SIZE 只有 3KB,SSC 分段缓冲是唯一 malloc 用户,4096 会被从站回 abort 0x05040005 Out of memory(BOOT 堆 16KB 所以能用 4096) */
|
||||
constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
|
||||
|
||||
|
||||
/** @brief USART0 默认波特率。 */
|
||||
#define CONFIG_USART0_BAUDRATE 115200U
|
||||
constexpr uint32_t CONFIG_USART0_BAUDRATE = 3000000U; /* 3M:诊断打印高速输出,printf 阻塞极小、不拖慢 relay */
|
||||
|
||||
/** @brief ADC 外设实例。 */
|
||||
#define CONFIG_ADC_INSTANCE ADC0
|
||||
constexpr uint32_t CONFIG_ADC_INSTANCE = ADC0;
|
||||
/** @brief ADC 外部 VIN 检测通道号(原理图:PA6 -> VIN_ADC)。 */
|
||||
#define CONFIG_ADC_VIN_CHANNEL ADC_CHANNEL_6
|
||||
constexpr uint32_t CONFIG_ADC_VIN_CHANNEL = ADC_CHANNEL_6;
|
||||
/** @brief ADC 内部温度通道号。 */
|
||||
#define CONFIG_ADC_TEMP_CHANNEL ADC_CHANNEL_16
|
||||
constexpr uint32_t CONFIG_ADC_TEMP_CHANNEL = ADC_CHANNEL_16;
|
||||
/** @brief ADC 内部参考电压通道号。 */
|
||||
#define CONFIG_ADC_VREFINT_CHANNEL ADC_CHANNEL_17
|
||||
constexpr uint32_t CONFIG_ADC_VREFINT_CHANNEL = ADC_CHANNEL_17;
|
||||
/** @brief ADC 满量程计数值。 */
|
||||
#define CONFIG_ADC_MAX_VALUE 4095.0f
|
||||
constexpr float CONFIG_ADC_MAX_VALUE = 4095.0f;
|
||||
/** @brief 内部参考电压典型值,单位 V。 */
|
||||
#define CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE 1.20f
|
||||
constexpr float CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE = 1.20f;
|
||||
/** @brief 温度传感器 25℃ 对应电压,单位 V。 */
|
||||
#define CONFIG_ADC_TEMP_V25 1.43f
|
||||
constexpr float CONFIG_ADC_TEMP_V25 = 1.43f;
|
||||
/** @brief 温度传感器平均斜率,单位 V/℃。 */
|
||||
#define CONFIG_ADC_TEMP_AVG_SLOPE 0.0043f
|
||||
constexpr float CONFIG_ADC_TEMP_AVG_SLOPE = 0.0043f;
|
||||
/** @brief ADC 上电后稳定等待时间,单位 ms。 */
|
||||
#define CONFIG_ADC_STABILIZE_DELAY_MS 2U
|
||||
/** @brief ADC 规则通道采样时间配置。 */
|
||||
#define CONFIG_ADC_SAMPLE_TIME ADC_SAMPLETIME_239POINT5
|
||||
/** @brief ADC 空输入保护时默认 VDDA,单位 V。 */
|
||||
#define CONFIG_ADC_DEFAULT_VDDA 3.3f
|
||||
|
||||
constexpr uint32_t CONFIG_ADC_STABILIZE_DELAY_MS = 2U;
|
||||
/** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */
|
||||
#define CONFIG_ADC_FILTER_SIZE 6U
|
||||
constexpr uint32_t CONFIG_ADC_FILTER_SIZE = 6U;
|
||||
|
||||
/** @brief CAN 外设实例。 */
|
||||
#define CONFIG_CAN_INSTANCE CAN0
|
||||
/** @brief CAN 外设时钟使能枚举。 */
|
||||
#define CONFIG_CAN_RCU RCU_CAN0
|
||||
/** @brief CAN 状态报文标准帧 ID。 */
|
||||
#define CONFIG_CAN_STATUS_ID 0x321U
|
||||
constexpr uint32_t CONFIG_CAN_STATUS_ID = 0x321U;
|
||||
/** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */
|
||||
#define CONFIG_CAN_OTA_BOOT_REQ_ID 0x00000320U
|
||||
/** @brief 进入 Bootloader OTA 请求魔术字节 0。 */
|
||||
#define CONFIG_CAN_OTA_BOOT_MAGIC0 0x4FU
|
||||
/** @brief 进入 Bootloader OTA 请求魔术字节 1。 */
|
||||
#define CONFIG_CAN_OTA_BOOT_MAGIC1 0x54U
|
||||
/** @brief 进入 Bootloader OTA 请求魔术字节 2。 */
|
||||
#define CONFIG_CAN_OTA_BOOT_MAGIC2 0x41U
|
||||
/** @brief 进入 Bootloader OTA 请求魔术字节 3。 */
|
||||
#define CONFIG_CAN_OTA_BOOT_MAGIC3 0x21U
|
||||
constexpr uint32_t CONFIG_CAN_OTA_BOOT_REQ_ID = 0x00000320U;
|
||||
/** @brief 进入 Bootloader OTA 请求魔术字节 0~3。 */
|
||||
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC0 = 0x4FU;
|
||||
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC1 = 0x54U;
|
||||
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC2 = 0x41U;
|
||||
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC3 = 0x21U;
|
||||
/** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */
|
||||
#define CONFIG_SPI_APP_OTA_BOOT_CMD 0xB0U
|
||||
constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_CMD = 0xB0U;
|
||||
/** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */
|
||||
#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xB5U
|
||||
/** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */
|
||||
#define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U
|
||||
constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_OK = 0xB5U;
|
||||
/** @brief SPI DATA_READY 输出端口(PB6 → ESP32;v2:低=有待读,高=空闲)。 */
|
||||
constexpr uint32_t CONFIG_SPI_READY_PORT = GPIOB;
|
||||
/** @brief SPI DATA_READY 输出引脚。 */
|
||||
constexpr uint32_t CONFIG_SPI_READY_PIN = GPIO_PIN_6;
|
||||
/** @brief APP SPI DMA/软件缓冲(字节)。业务 payload 上限 = 本值 − V2_OVERHEAD(20)。 */
|
||||
constexpr uint16_t CONFIG_SPI_TX_BUF_SIZE = 256U;
|
||||
constexpr uint16_t CONFIG_SPI_RX_BUF_SIZE = 256U;
|
||||
constexpr uint16_t CONFIG_SPI_LOG_BUF_SIZE = 256U;
|
||||
/** @brief 主动按铃超时:ESP32 未在该时间内来读则 cancelRing(ms)。 */
|
||||
constexpr uint32_t CONFIG_SPI_RING_TIMEOUT_MS = 50U;
|
||||
/** @brief SPI 命令 handler 注册表容量。
|
||||
*
|
||||
* 当前 init 注册 13 个:boot_request / ota_wait / ping / init_status / can_bridge /
|
||||
* can_burst / can_feedback / id_snapshot / adc_feedback / motor_state / motor_ota /
|
||||
* exmc_diag / spi_diag。1.7.2 的 0xC8(CAN Silent)复用 can_feedback handler
|
||||
* 多加一个 frame_type,不占新槽位。
|
||||
* registerHandler 表满时静默丢弃,motor_ota 曾因容量=8 被丢弃(0xD0 回 0xEE)。
|
||||
* 新增 handler 时须确认本容量足够。 */
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
constexpr uint8_t CONFIG_SPI_MAX_COMMANDS = 16U;
|
||||
#else
|
||||
constexpr uint8_t CONFIG_SPI_MAX_COMMANDS = 14U;
|
||||
#endif
|
||||
/** @brief GD32 APP 版本(HELLO_ACK/PING 身份块;改功能后请递增便于现场核对)。 */
|
||||
/* 版本号在 app_spi_cmd_ping.cpp fillGd32AppIdentityPayload 维护 */
|
||||
/** @brief 每通道不重复 ID 快照表最大条目数(与 ESP32 CANFD_ID_SNAPSHOT_MAX 对齐)。 */
|
||||
constexpr uint8_t CONFIG_CANFD_ID_SNAPSHOT_MAX = 16U;
|
||||
/** @brief 快照条目最大数据字节数(CAN FD DLC 上限)。 */
|
||||
constexpr uint8_t CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX = 64U;
|
||||
|
||||
/** @brief ESP32 硬件复位控制端口(PC9 接 ESP32 EN/RESET,对应原理图 WLESS_EN)。
|
||||
* @note 复位后 PC9 浮空,ESP_CHIP_EN 会被 C41/U11 拉低;BOOT 也必须主动拉高。 */
|
||||
constexpr uint32_t CONFIG_ESP32_RESET_PORT = GPIOC;
|
||||
/** @brief ESP32 硬件复位控制引脚。 */
|
||||
constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9;
|
||||
|
||||
/* ============================================================
|
||||
* CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用)
|
||||
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制)
|
||||
* → APB1=/2=90MHz (CAN_CLK)
|
||||
*
|
||||
* 仲裁段目标: 1.0 Mbps = 90MHz / (9 × 10 TQ)
|
||||
* 数据段目标: 2.0 Mbps = 90MHz / (9 × 5 TQ)
|
||||
* 数据段目标: 5.0 Mbps = 90MHz / (3 × 6 TQ),采样点 83.3%
|
||||
*
|
||||
* @note RCU_MODIFY_UP 宏在 system_clock_180m_hxtal() 末尾将 AHB 从 /4
|
||||
* 临时切到 /1,这是 GigaDevice 官方行为,不可删除。
|
||||
@@ -74,237 +267,191 @@
|
||||
* ============================================================ */
|
||||
|
||||
/** @brief 仲裁段预分频值。 */
|
||||
#define CONFIG_CAN_NOMINAL_PRESCALER 9U
|
||||
/** @brief 仲裁段再同步跳跃宽度。 */
|
||||
#define CONFIG_CAN_NOMINAL_SJW CAN_BT_SJW_1TQ
|
||||
constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U;
|
||||
/** @brief 仲裁段再同步跳跃宽度。
|
||||
* 1TQ→2TQ:总线空闲期即持续 STUFF 接收错误(REC 冲 120+),OTA 数据帧被错误帧
|
||||
* 击落触发应用层重发;放宽接收再同步容差(不改波特率/采样点,上限受 BS2=2TQ 限制)。 */
|
||||
constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_2TQ;
|
||||
/** @brief 仲裁段时间段 1(PHSEG1)。 */
|
||||
#define CONFIG_CAN_NOMINAL_BS1 CAN_BT_BS1_7TQ
|
||||
constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ;
|
||||
/** @brief 仲裁段时间段 2(PHSEG2)。 */
|
||||
#define CONFIG_CAN_NOMINAL_BS2 CAN_BT_BS2_2TQ
|
||||
constexpr uint32_t CONFIG_CAN_NOMINAL_BS2 = CAN_BT_BS2_2TQ;
|
||||
|
||||
/** @brief 数据段预分频值。 */
|
||||
#define CONFIG_CAN_DATA_PRESCALER 9U
|
||||
/** @brief 数据段再同步跳跃宽度。 */
|
||||
#define CONFIG_CAN_DATA_SJW CAN_BT_SJW_1TQ
|
||||
/** @brief 数据段时间段 1。 */
|
||||
#define CONFIG_CAN_DATA_BS1 CAN_BT_BS1_3TQ
|
||||
/** @brief 数据段预分频值。
|
||||
* CAN_DBT 的 DBAUDPSC[9:0] 独立于仲裁段 BAUDPSC,数据段可自取分频。 */
|
||||
constexpr uint32_t CONFIG_CAN_DATA_PRESCALER = 3U;
|
||||
/** @brief 数据段再同步跳跃宽度。上限受 BS2=1TQ 约束,不可放宽。 */
|
||||
constexpr uint32_t CONFIG_CAN_DATA_SJW = CAN_BT_SJW_1TQ;
|
||||
/** @brief 数据段时间段 1。4TQ + 1TQ(同步) + 1TQ(BS2) = 6TQ → 5 Mbps。 */
|
||||
constexpr uint32_t CONFIG_CAN_DATA_BS1 = CAN_BT_BS1_4TQ;
|
||||
/** @brief 数据段时间段 2。 */
|
||||
#define CONFIG_CAN_DATA_BS2 CAN_BT_BS2_1TQ
|
||||
constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ;
|
||||
|
||||
/** @brief BRS(比特率切换)使能开关:1=启用数据段加速,0=关闭仅经典 CAN。 */
|
||||
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
||||
/* BRS / 启动探测 / 通道使能:见文件顶部「功能开关」 */
|
||||
|
||||
/** @brief CAN 接收过滤器目标标准帧 ID。 */
|
||||
#define CONFIG_CAN_RX_FILTER_ID CONFIG_CAN_STATUS_ID
|
||||
/** @brief CAN 接收过滤器标准帧掩码;`0x7FFU` 表示精确匹配 11-bit ID。 */
|
||||
#define CONFIG_CAN_RX_FILTER_MASK 0x7FFU
|
||||
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数,避免主循环长期被接收流量占用。 */
|
||||
#define CONFIG_CAN_RX_PARSE_BUDGET 48U
|
||||
/** @brief CAN 状态载荷中调试标识字符 0。 */
|
||||
#define CONFIG_CAN_STATUS_TAG0 0x43U
|
||||
/** @brief CAN 状态载荷中调试标识字符 1。 */
|
||||
#define CONFIG_CAN_STATUS_TAG1 0x41U
|
||||
/** @brief CAN 状态载荷中调试标识字符 2。 */
|
||||
#define CONFIG_CAN_STATUS_TAG2 0x4EU
|
||||
/** @brief CAN 状态载荷中调试标识字符 3。 */
|
||||
#define CONFIG_CAN_STATUS_TAG3 0x46U
|
||||
constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID;
|
||||
/** @brief CAN 接收过滤器标准帧掩码;0x7FF 表示精确匹配 11-bit ID。 */
|
||||
constexpr uint32_t CONFIG_CAN_RX_FILTER_MASK = 0x7FFU;
|
||||
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数。 */
|
||||
constexpr uint32_t CONFIG_CAN_RX_PARSE_BUDGET = 64U; /* 8kHz PDO 下主循环间隙短,单次多清一些 */
|
||||
|
||||
/** @brief 用户调度定时器实例。 */
|
||||
#define CONFIG_SCHED_TIMER_INSTANCE TIMER2
|
||||
constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2;
|
||||
/** @brief 用户调度定时器中断号。 */
|
||||
#define CONFIG_SCHED_TIMER_IRQn TIMER2_IRQn
|
||||
constexpr IRQn_Type CONFIG_SCHED_TIMER_IRQn = TIMER2_IRQn;
|
||||
/** @brief 用户调度定时器时钟使能宏参数。 */
|
||||
#define CONFIG_SCHED_TIMER_RCU RCU_TIMER2
|
||||
constexpr rcu_periph_enum CONFIG_SCHED_TIMER_RCU = RCU_TIMER2;
|
||||
/** @brief 用户调度定时器预分频值。
|
||||
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) → APB1=/2=90MHz → TIMER2_CLK=APB1×2=180MHz。
|
||||
* 保持 100us tick: (PSC+1)×(PER+1)=18000 → 180×100 → PSC=179, PER=99。
|
||||
*/
|
||||
#define CONFIG_SCHED_TIMER_PRESCALER 179U
|
||||
constexpr uint32_t CONFIG_SCHED_TIMER_PRESCALER = 179U;
|
||||
/** @brief 用户调度定时器周期重装值。 */
|
||||
#define CONFIG_SCHED_TIMER_PERIOD 99U
|
||||
constexpr uint32_t CONFIG_SCHED_TIMER_PERIOD = 99U;
|
||||
/** @brief 用户调度定时器 NVIC 抢占优先级。 */
|
||||
#define CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO 1U
|
||||
constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO = 1U;
|
||||
/** @brief 用户调度定时器 NVIC 子优先级。 */
|
||||
#define CONFIG_SCHED_TIMER_IRQ_SUB_PRIO 0U
|
||||
constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_SUB_PRIO = 0U;
|
||||
|
||||
/** @brief 调度基准节拍:ADC VREF 采样周期,单位 100us tick。 */
|
||||
#define CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS 5U
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U;
|
||||
/** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */
|
||||
#define CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS 10U
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U;
|
||||
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
|
||||
#define CONFIG_TASK_PERIOD_CAN_RX_TICKS 100U
|
||||
/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */
|
||||
#define CONFIG_TASK_PERIOD_CAN_TX_TICKS 1000U
|
||||
/** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */
|
||||
#define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U
|
||||
|
||||
/** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */
|
||||
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
|
||||
/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */
|
||||
#define CONFIG_UART_DIAG_LEVEL 1
|
||||
/** @brief SPI 命令逐帧日志开关:0=关闭,1=开启。高速 CAN 桥接时应关闭。 */
|
||||
#define CONFIG_SPI_APP_LOG_ENABLE 0
|
||||
/** @brief CAN_SEND 成功帧详细日志开关:0=关闭,1=开启。开启会显著影响高速传输。 */
|
||||
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U;
|
||||
/** @brief 调度基准节拍:CAN 周期发送任务周期,100 tick = 10ms。
|
||||
* @note 1ms/帧过密,10ms/帧可降低 CAN 总线负载与 GD32 邮箱压力。 */
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 100U;
|
||||
/** @brief 调度基准节拍:心跳周期,单位 100us tick。 */
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U;
|
||||
/** @brief 调度基准节拍:多电机状态 0xC5 主动推送启动周期,2500 tick = 250ms。
|
||||
* @note 独立于 1Hz heartbeat,避免连带加速 OTA Boot 轮询 / CAN 反馈快照刷新 /
|
||||
* ADC 推送 / checkOffline 离线判定(3s 阈值)。前端 age 应稳定在 ~120ms。 */
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS = 2500U;
|
||||
/** @brief 调度基准节拍:APP 状态/诊断打印周期,50000 tick = 5s。 */
|
||||
constexpr uint32_t CONFIG_TASK_PERIOD_DIAG_PRINT_TICKS = 50000U;
|
||||
|
||||
/* UART/SPI 调试开关:见文件顶部「功能开关」 */
|
||||
|
||||
/** @brief FWDGT 独立看门狗预分频值。 */
|
||||
#define CONFIG_FWDGT_PSC FWDGT_PSC_DIV64
|
||||
constexpr uint32_t CONFIG_FWDGT_PSC = FWDGT_PSC_DIV64;
|
||||
/** @brief FWDGT 独立看门狗重载值。
|
||||
* 超时 = (64 / 40000) * (RLD + 1) = 1.6ms * (RLD+1)
|
||||
* RLD=1874 → 1.6ms * 1875 ≈ 3.0s
|
||||
*/
|
||||
#define CONFIG_FWDGT_RLD 1874U
|
||||
constexpr uint32_t CONFIG_FWDGT_RLD = 1874U;
|
||||
/** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */
|
||||
#define CONFIG_FWDGT_TIMEOUT_MS 3000U
|
||||
|
||||
/* ============================================================
|
||||
* APP 固件版本号
|
||||
* ============================================================ */
|
||||
/** @brief APP 主版本号。 */
|
||||
#define APP_FW_VERSION_MAJOR 1U
|
||||
/** @brief APP 次版本号。 */
|
||||
#define APP_FW_VERSION_MINOR 2U
|
||||
/** @brief APP 补丁版本号。 */
|
||||
#define APP_FW_VERSION_PATCH 2U
|
||||
/** @brief APP 构建编号。 */
|
||||
#define APP_FW_VERSION_BUILD 0U
|
||||
constexpr uint32_t CONFIG_FWDGT_TIMEOUT_MS = 3000U;
|
||||
|
||||
/* ============================================================
|
||||
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
|
||||
* 与 BOOT 工程 sys/config.h 保持一致
|
||||
* ============================================================ */
|
||||
/** @brief OTA 配置页基地址。 */
|
||||
#define CONFIG_OTA_CONFIG_PAGE_ADDR 0x0807E000U
|
||||
/** @brief BOOT 版本存储地址(word 6,低 32 位:major|minor)。 */
|
||||
#define CONFIG_BOOT_VERSION_ADDR0 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U)
|
||||
/** @brief BOOT 版本存储地址(word 7,低 32 位:patch|build)。 */
|
||||
#define CONFIG_BOOT_VERSION_ADDR1 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU)
|
||||
/** @brief OTA 配置页总 word 数(6 个 OTA 标志 + 2 个 BOOT 版本)。 */
|
||||
#define CONFIG_OTA_CONFIG_WORD_COUNT 8U
|
||||
/**
|
||||
* @brief APP 持续喂狗满该秒数后清 BOOT 侧连续 FWDGT 崩溃计数(word9)。
|
||||
* @note 必须 > FWDGT 超时(3s)数倍;init 不清,避免 3s 内崩溃永远攒不到阈值。
|
||||
*/
|
||||
constexpr uint32_t CONFIG_CRASH_CLEAR_AFTER_SEC = 30U;
|
||||
constexpr uint32_t CONFIG_OTA_CONFIG_PAGE_ADDR = 0x0807E000U;
|
||||
constexpr uint32_t CONFIG_BOOT_VERSION_ADDR0 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U;
|
||||
constexpr uint32_t CONFIG_BOOT_VERSION_ADDR1 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU;
|
||||
constexpr uint32_t CONFIG_OTA_CONFIG_WORD_COUNT = 8U;
|
||||
|
||||
/* ============================================================
|
||||
* NVIC 优先级分组与中断优先级分层
|
||||
* (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级)
|
||||
* ============================================================ */
|
||||
/** @brief NVIC 优先级分组:4 位抢占 + 0 位子优先级。 */
|
||||
#define CONFIG_NVIC_PRIORITY_GROUP NVIC_PRIGROUP_PRE4_SUB0
|
||||
|
||||
/** @brief EtherCAT SYNC0 周期中断抢占优先级(最高,永不被阻塞)。 */
|
||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC 0U
|
||||
/** @brief 调度器定时器抢占优先级。 */
|
||||
#define CONFIG_IRQ_PREEMPT_SCHED 1U
|
||||
/** @brief CAN RX0 抢占优先级。 */
|
||||
#define CONFIG_IRQ_PREEMPT_CAN 2U
|
||||
/** @brief EtherCAT ESC 事件中断抢占优先级(可被 SYNC0 抢占)。 */
|
||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_ESC 1U
|
||||
/** @brief EtherCAT SYNC1 备用抢占优先级。 */
|
||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 1U
|
||||
/** @brief SPI / EXTI / USART 抢占优先级(最低)。 */
|
||||
#define CONFIG_IRQ_PREEMPT_SPI_USART 3U
|
||||
|
||||
constexpr uint32_t CONFIG_NVIC_PRIORITY_GROUP = NVIC_PRIGROUP_PRE4_SUB0;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC = 0U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_SCHED = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN = 2U;
|
||||
/* CAN RX 抢占级 = 0(与 EtherCAT SYNC0 同级、互不抢占)。
|
||||
* 目的:RX ISR 不被 PDI/SYNC1/SCHED 压住,FIFO 深度可压到 ≤2,
|
||||
* 多字取帧真正原子。TX 留在 2:PDI 上下文有 TX 提交,提 0 会引入
|
||||
* 提交/释放竞争。代价:SYNC0/PDI 偶发 µs 级延迟(RX ~3µs/帧×8kHz≈2.4%)。 */
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN_RX = 0U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_ESC = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U;
|
||||
/* ============================================================
|
||||
* 主循环与实时策略
|
||||
* EtherCAT LAN9252 配置(开关见顶部 CONFIG_MAIN_LOOP_WFI / ETHERCAT_SYNC_MODE)
|
||||
* ============================================================ */
|
||||
/** @brief 主循环 WFI 开关:0=关闭(忙等,适配 EtherCAT),1=开启(省电)。 */
|
||||
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
|
||||
|
||||
/* ============================================================
|
||||
* EtherCAT LAN9252 使能与配置
|
||||
* ============================================================ */
|
||||
/** @brief SYNC0 同步模式:0=EXTI 外部触发(ESC 硬件同步,默认),1=TIMER0 降级(DC 未同步时)。 */
|
||||
#define CONFIG_ETHERCAT_SYNC_MODE 0
|
||||
|
||||
/** @brief periodicIsr 最大执行时间告警阈值(µs)。实测超过此值需评估减小 PDO 大小或优化 EXMC 时序。
|
||||
* 当前阈值 5µs:基于 180MHz + 32 字节 PDO + EXMC 6 HCLK 时序的估算上限。
|
||||
* preempt=0 ISR 阻塞窗口 ≤ 此阈值时,CAN/SPI 中断需容忍。
|
||||
* @note 启用 ESC 后需根据实测 HCLK 数重新评估。 */
|
||||
#define CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD 5U
|
||||
|
||||
/** @brief 输入 PDO(TxPDO)在 PDI 内存区的起始偏移。
|
||||
* 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。
|
||||
* @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */
|
||||
#define CONFIG_ESC_PDI_INPUT_OFFSET 0x1000U
|
||||
|
||||
/** @brief 输出 PDO(RxPDO)在 PDI 内存区的起始偏移。
|
||||
* 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。
|
||||
* @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */
|
||||
#define CONFIG_ESC_PDI_OUTPUT_OFFSET 0x1200U
|
||||
/* PDO 交换由 SSC(dc_app.c APPL_*) 完成。 */
|
||||
|
||||
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
|
||||
/** @brief EXMC NOR/SRAM Region1 基址(NE1 片选)。 */
|
||||
#define CONFIG_ESC_EXMC_BASE_ADDR 0x60000000U
|
||||
/** @brief EXMC BANK 选择(BANK0 Region1)。
|
||||
* @note EXMC 库未纳入编译时回退为 0U;启用 EtherCAT 时 EXMC 组件将被引入。 */
|
||||
#ifdef EXMC_BANK0_NORSRAM_REGION1
|
||||
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1
|
||||
#else
|
||||
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 0U
|
||||
constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x60000000U;
|
||||
/* Region0 = NE0 @ 0x60000000(GD32E50x 用户手册 Rev2.2 图25-3: Bank0 四 Region 各 64MB,
|
||||
* HADDR[27:26]=00 选中 NE0)。LAN9252 CS 接 PD7=EXMC_NE0(数据手册 GD32E508xx
|
||||
* Datasheet Rev1.8 LQFP100 引脚表: PD7 Alternate2 = EXMC_NE0/EXMC_NCE1)。
|
||||
* 注意: GD32E508Vx (LQFP100) 封装只引出 NE0 一个 NOR/SRAM 片选引脚,
|
||||
* NE1~NE3 仅在 LQFP144 (Zx) 上存在;因此必须使用 Region0(SNCTL0),不能用 Region1。 */
|
||||
#ifndef EXMC_BANK0_NORSRAM_REGION0
|
||||
#define EXMC_BANK0_NORSRAM_REGION0 ((uint32_t)0x00000000U)
|
||||
#endif
|
||||
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0 EXMC_BANK0_NORSRAM_REGION0
|
||||
|
||||
/* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */
|
||||
/** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */
|
||||
#define CONFIG_ESC_SYNC0_PORT GPIOB
|
||||
#define CONFIG_ESC_SYNC0_PIN GPIO_PIN_0
|
||||
#define CONFIG_ESC_SYNC0_EXTI EXTI_0
|
||||
#define CONFIG_ESC_SYNC0_IRQn EXTI0_IRQn
|
||||
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
||||
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_0
|
||||
constexpr uint32_t CONFIG_ESC_SYNC0_PORT = GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC0_PIN = GPIO_PIN_0;
|
||||
constexpr exti_line_enum CONFIG_ESC_SYNC0_EXTI = EXTI_0;
|
||||
constexpr IRQn_Type CONFIG_ESC_SYNC0_IRQn = EXTI0_IRQn;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_0;
|
||||
|
||||
#define CONFIG_ESC_IRQ_PORT GPIOB
|
||||
#define CONFIG_ESC_IRQ_PIN GPIO_PIN_1
|
||||
#define CONFIG_ESC_IRQ_EXTI EXTI_1
|
||||
#define CONFIG_ESC_IRQ_IRQn EXTI1_IRQn
|
||||
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
||||
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_1
|
||||
constexpr uint32_t CONFIG_ESC_IRQ_PORT = GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_IRQ_PIN = GPIO_PIN_1;
|
||||
constexpr exti_line_enum CONFIG_ESC_IRQ_EXTI = EXTI_1;
|
||||
constexpr IRQn_Type CONFIG_ESC_IRQ_IRQn = EXTI1_IRQn;
|
||||
constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_1;
|
||||
|
||||
#define CONFIG_ESC_SYNC1_PORT GPIOB
|
||||
#define CONFIG_ESC_SYNC1_PIN GPIO_PIN_2
|
||||
#define CONFIG_ESC_SYNC1_EXTI EXTI_2
|
||||
#define CONFIG_ESC_SYNC1_IRQn EXTI2_IRQn
|
||||
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
||||
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_2
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_PORT = GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_PIN = GPIO_PIN_2;
|
||||
constexpr exti_line_enum CONFIG_ESC_SYNC1_EXTI = EXTI_2;
|
||||
constexpr IRQn_Type CONFIG_ESC_SYNC1_IRQn = EXTI2_IRQn;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_2;
|
||||
|
||||
/* ---------- ESC 硬件复位(PB10 → LAN9252 Pin11 RST#,SCH 第1页 Pin47) ---------- */
|
||||
/** @note 2026-08-22(1.5.25) 前固件从未初始化 PB10,RST# 仅靠 R44 10k 上拉自然释放,
|
||||
* 不满足 LAN9252 手册 18.6.3 tpurstd(电源稳定→RST#释放)≥25ms、trstia(低脉宽)≥200µs。
|
||||
* 由 EscLan9252::hardwareReset() 执行受控复位序列;释放后保持推挽输出高,
|
||||
* GD32 自身复位期间引脚回浮空,由 R44 上拉维持高,不会误复位 ESC。 */
|
||||
constexpr uint32_t CONFIG_ESC_NRST_PORT = GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_NRST_PIN = GPIO_PIN_10;
|
||||
constexpr uint32_t CONFIG_ESC_NRST_ASSERT_MS = 30U; /* 低脉宽,同时覆盖 tpurstd 25ms 与 trstia 200µs */
|
||||
constexpr uint32_t CONFIG_ESC_HBI_POLL_TIMEOUT_MS = 300U; /* RST# 释放后等待 ESC 启动/EEPROM 加载 */
|
||||
constexpr uint32_t CONFIG_ESC_HBI_POLL_STEP_MS = 5U;
|
||||
|
||||
/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */
|
||||
#define CONFIG_EXMC_D0_PORT GPIOD
|
||||
#define CONFIG_EXMC_D0_PIN GPIO_PIN_14
|
||||
#define CONFIG_EXMC_D1_PORT GPIOD
|
||||
#define CONFIG_EXMC_D1_PIN GPIO_PIN_15
|
||||
#define CONFIG_EXMC_D2_PORT GPIOD
|
||||
#define CONFIG_EXMC_D2_PIN GPIO_PIN_0
|
||||
#define CONFIG_EXMC_D3_PORT GPIOD
|
||||
#define CONFIG_EXMC_D3_PIN GPIO_PIN_1
|
||||
#define CONFIG_EXMC_D4_PORT GPIOE
|
||||
#define CONFIG_EXMC_D4_PIN GPIO_PIN_7
|
||||
#define CONFIG_EXMC_D5_PORT GPIOE
|
||||
#define CONFIG_EXMC_D5_PIN GPIO_PIN_8
|
||||
#define CONFIG_EXMC_D6_PORT GPIOE
|
||||
#define CONFIG_EXMC_D6_PIN GPIO_PIN_9
|
||||
#define CONFIG_EXMC_D7_PORT GPIOE
|
||||
#define CONFIG_EXMC_D7_PIN GPIO_PIN_10
|
||||
#define CONFIG_EXMC_D8_PORT GPIOE
|
||||
#define CONFIG_EXMC_D8_PIN GPIO_PIN_11
|
||||
#define CONFIG_EXMC_D9_PORT GPIOE
|
||||
#define CONFIG_EXMC_D9_PIN GPIO_PIN_12
|
||||
#define CONFIG_EXMC_D10_PORT GPIOE
|
||||
#define CONFIG_EXMC_D10_PIN GPIO_PIN_13
|
||||
#define CONFIG_EXMC_D11_PORT GPIOE
|
||||
#define CONFIG_EXMC_D11_PIN GPIO_PIN_14
|
||||
#define CONFIG_EXMC_D12_PORT GPIOE
|
||||
#define CONFIG_EXMC_D12_PIN GPIO_PIN_15
|
||||
#define CONFIG_EXMC_D13_PORT GPIOD
|
||||
#define CONFIG_EXMC_D13_PIN GPIO_PIN_8
|
||||
#define CONFIG_EXMC_D14_PORT GPIOD
|
||||
#define CONFIG_EXMC_D14_PIN GPIO_PIN_9
|
||||
#define CONFIG_EXMC_D15_PORT GPIOD
|
||||
#define CONFIG_EXMC_D15_PIN GPIO_PIN_10
|
||||
#define CONFIG_EXMC_A16_PORT GPIOD
|
||||
#define CONFIG_EXMC_A16_PIN GPIO_PIN_11
|
||||
#define CONFIG_EXMC_NOE_PORT GPIOD
|
||||
#define CONFIG_EXMC_NOE_PIN GPIO_PIN_4
|
||||
#define CONFIG_EXMC_NWE_PORT GPIOD
|
||||
#define CONFIG_EXMC_NWE_PIN GPIO_PIN_5
|
||||
#define CONFIG_EXMC_NE1_PORT GPIOD
|
||||
#define CONFIG_EXMC_NE1_PIN GPIO_PIN_7
|
||||
constexpr uint32_t CONFIG_EXMC_D0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D0_PIN = GPIO_PIN_14;
|
||||
constexpr uint32_t CONFIG_EXMC_D1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D1_PIN = GPIO_PIN_15;
|
||||
constexpr uint32_t CONFIG_EXMC_D2_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D2_PIN = GPIO_PIN_0;
|
||||
constexpr uint32_t CONFIG_EXMC_D3_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D3_PIN = GPIO_PIN_1;
|
||||
constexpr uint32_t CONFIG_EXMC_D4_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D4_PIN = GPIO_PIN_7;
|
||||
constexpr uint32_t CONFIG_EXMC_D5_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D5_PIN = GPIO_PIN_8;
|
||||
constexpr uint32_t CONFIG_EXMC_D6_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D6_PIN = GPIO_PIN_9;
|
||||
constexpr uint32_t CONFIG_EXMC_D7_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D7_PIN = GPIO_PIN_10;
|
||||
constexpr uint32_t CONFIG_EXMC_D8_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D8_PIN = GPIO_PIN_11;
|
||||
constexpr uint32_t CONFIG_EXMC_D9_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D9_PIN = GPIO_PIN_12;
|
||||
constexpr uint32_t CONFIG_EXMC_D10_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D10_PIN = GPIO_PIN_13;
|
||||
constexpr uint32_t CONFIG_EXMC_D11_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D11_PIN = GPIO_PIN_14;
|
||||
constexpr uint32_t CONFIG_EXMC_D12_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D12_PIN = GPIO_PIN_15;
|
||||
constexpr uint32_t CONFIG_EXMC_D13_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D13_PIN = GPIO_PIN_8;
|
||||
constexpr uint32_t CONFIG_EXMC_D14_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D14_PIN = GPIO_PIN_9;
|
||||
constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D15_PIN = GPIO_PIN_10;
|
||||
constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11;
|
||||
/* A17 (PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0 寄存器,已将 LAN9252 A2 (Pin 29) 接到 PD12。
|
||||
* 注意 LAN9252 引脚顺序:Pin25=A1, Pin26=A3, Pin27=A4, Pin28=CS, Pin29=A2。
|
||||
* A2 是 IDX/DATA 选择位,必须接到 PD12 才能访问 DATA 寄存器,EEPROM 烧录功能可用。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位(用户手册 p869):A16(PD11)=CPU 偏移 0x20000、
|
||||
* A17(PD12)=CPU 偏移 0x40000,HBI_DATA_0 访问偏移见 esc_lan9252.cpp。 */
|
||||
constexpr uint32_t CONFIG_EXMC_A17_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A17_PIN = GPIO_PIN_12;
|
||||
constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4;
|
||||
constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5;
|
||||
/* LQFP100 只有 NE0(PD7) 一个 NOR/SRAM 片选,归 Region0(0x60000000);命名沿用硬件真值 NE0。 */
|
||||
constexpr uint32_t CONFIG_EXMC_NE0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE0_PIN = GPIO_PIN_7;
|
||||
|
||||
#endif /* SYS_CONFIG_H */
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @file cpu_load.cpp
|
||||
* @brief CPU 负载率测量实现(1.5.61,度量定义与并发协议见 cpu_load.h)。
|
||||
*
|
||||
* 热路径(beginWork/endWork/isrEnter/isrExit/tick)放进 .ramfunc——它们会被包进
|
||||
* EXTI0(Sync0, preempt=0) 等延迟敏感的 ISR 包装,与 EthercatSync::onSync0Exti
|
||||
* 同款 section 属性,避免 flash 等待态给 Sync0 入口添抖动。1s 一次的窗口
|
||||
* 结算(含 64 位除法)留在普通 flash 函数里。
|
||||
*/
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
volatile uint32_t CpuLoad::s_depth = 0U;
|
||||
volatile uint32_t CpuLoad::s_stamp = 0U;
|
||||
volatile uint32_t CpuLoad::s_busy = 0U;
|
||||
volatile uint32_t CpuLoad::s_winStart = 0U;
|
||||
volatile uint16_t CpuLoad::s_loadPerm = 0U;
|
||||
volatile uint32_t CpuLoad::s_seq = 0U;
|
||||
|
||||
/** @brief 区间并集的共享进入点。0→1 者负责盖时间戳;STREX 失败方重试时深度
|
||||
* 已被抢占者修正,不会再重复盖戳。 */
|
||||
__attribute__((always_inline)) static inline void cpuLoadEnter()
|
||||
{
|
||||
uint32_t d;
|
||||
do {
|
||||
d = __LDREXW(&CpuLoad::s_depth);
|
||||
if (d == 0U) {
|
||||
CpuLoad::s_stamp = DWT->CYCCNT;
|
||||
__DMB();
|
||||
}
|
||||
} while (__STREXW(d + 1U, &CpuLoad::s_depth) != 0U);
|
||||
__DMB();
|
||||
}
|
||||
|
||||
/** @brief 区间并集的共享退出点。1→0 者把区间累进 s_busy(同样走独占访问,
|
||||
* 防"ISR 恰好插在主循环退出的读-改-写之间"丢累加)。 */
|
||||
__attribute__((always_inline)) static inline void cpuLoadLeave()
|
||||
{
|
||||
const uint32_t end = DWT->CYCCNT;
|
||||
const uint32_t start = CpuLoad::s_stamp;
|
||||
uint32_t d;
|
||||
do {
|
||||
d = __LDREXW(&CpuLoad::s_depth);
|
||||
} while (__STREXW(d - 1U, &CpuLoad::s_depth) != 0U);
|
||||
if (d == 1U) {
|
||||
const uint32_t delta = end - start;
|
||||
uint32_t b;
|
||||
do {
|
||||
b = __LDREXW(&CpuLoad::s_busy);
|
||||
} while (__STREXW(b + delta, &CpuLoad::s_busy) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
void CpuLoad::init()
|
||||
{
|
||||
/* DWT 已由 EscLan9252::init 使能;这里幂等兜底(万一 EtherCAT 初始化被裁剪)。 */
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
s_depth = 0U;
|
||||
s_busy = 0U;
|
||||
s_winStart = DWT->CYCCNT;
|
||||
s_loadPerm = 0U;
|
||||
s_seq = 0U;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::beginWork()
|
||||
{
|
||||
cpuLoadEnter();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::isrEnter()
|
||||
{
|
||||
cpuLoadEnter();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::isrExit()
|
||||
{
|
||||
cpuLoadLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 窗口结算(普通 flash 函数,1s 才进一次)。
|
||||
* @note s_busy 用独占访问换出:窗口边界上 ISR 的在途累加要么算进本窗口、
|
||||
* 要么算进下窗口,不丢。
|
||||
*/
|
||||
static void cpuLoadRollWindow(uint32_t now)
|
||||
{
|
||||
const uint32_t elapsed = now - CpuLoad::s_winStart; /* 调用方已保证 >= 1s */
|
||||
uint32_t busy;
|
||||
do {
|
||||
busy = __LDREXW(&CpuLoad::s_busy);
|
||||
} while (__STREXW(0U, &CpuLoad::s_busy) != 0U);
|
||||
CpuLoad::s_winStart = now;
|
||||
|
||||
uint32_t perm = (uint32_t)(((uint64_t)busy * 1000ULL) / (uint64_t)elapsed);
|
||||
if (perm > 1000U) {
|
||||
perm = 1000U;
|
||||
}
|
||||
CpuLoad::s_loadPerm = (uint16_t)perm;
|
||||
CpuLoad::s_seq++;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::endWork()
|
||||
{
|
||||
cpuLoadLeave();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::tick()
|
||||
{
|
||||
const uint32_t now = DWT->CYCCNT;
|
||||
if ((uint32_t)(now - s_winStart) >= SystemCoreClock) {
|
||||
cpuLoadRollWindow(now);
|
||||
}
|
||||
}
|
||||
|
||||
/* C 侧挂钩:供 ecatappl.c / dc_app.c 计入 EtherCAT 过程数据路径。
|
||||
* 与 beginWork/endWork 共用深度计数器,嵌套不重不漏。 */
|
||||
extern "C" void CpuLoad_Enter(void) { CpuLoad::beginWork(); }
|
||||
extern "C" void CpuLoad_Leave(void) { CpuLoad::endWork(); }
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @file cpu_load.h
|
||||
* @brief CPU 负载率测量(1.5.63 定义收窄,机制沿用 1.5.61)。
|
||||
*
|
||||
* 度量定义:**中断 + 非延迟处理段(任务派发 / OTA 中继等请求驱动工作)占墙钟
|
||||
* 时间的比值**,1s 滚动窗口,分辨率 0.1%(permille)。
|
||||
*
|
||||
* 为什么不计入主循环常开轮询:本架构在 EtherCAT 实时模式(CONFIG_MAIN_LOOP_WFI_ENABLE=0)
|
||||
* 下主循环空闲时是 NOP 忙等,且 runPending() 每圈无条件跑 SSC MainLoop 轮询与各
|
||||
* poll() 检查——把这部分计入"busy"的话负载率恒 ≈99%,失去参考价值(1.5.61 实机
|
||||
* 实测 99.4%,其中绝大部分是自由运转的轮询而非在处理事件)。收窄后的定义回答
|
||||
* 的问题变成"CPU 有多少时间在响应事件",随真实负载(CAN 流量、OTA 中继、
|
||||
* DC 模式 Sync0 过程数据搬运)增长;常开轮询成本与空转时间不计入。
|
||||
*
|
||||
* 计入项(全走同一深度计数器求**区间并集**,嵌套不重不漏):
|
||||
* - 全部挂钩 ISR:TIMER2(100µs 调度)、SysTick、EXTI0/1/2(EtherCAT Sync0/PDI/
|
||||
* Sync1)、TIMER0(DC 定时档)、CAN0-2 RX+TX、SPI0+EXTI10_15、USART0。
|
||||
* DC 模式下 Sync0_Isr 的 EXMC 过程数据搬运由此计入——这是大头,不能漏。
|
||||
* - 主循环非延迟段:TaskManager 派发任务、BOOT、OTA relay、新鲜度救援、
|
||||
* free-run 下 **1ms 节流** 的过程数据应用(有 CpuLoad 挂钩)。
|
||||
* - PDI/Sync0 ISR 全程(含 SM 类型下每拍 Process/映射)——负载随 PDO Hz 升。
|
||||
* 不计入:MainLoop/SSC/poll 空转。
|
||||
* 性能约束:PDI 热路径不得「凡 AL 事件就 Process/InputMapping」——8kHz 时
|
||||
* 单拍超 ~120µs 墙会掉速;输出用 12B 探针,process 兜底读带新鲜度门。
|
||||
*
|
||||
* 并发协议(无关中断、无临界区):enter/exit 用 LDREX/STREX 原子改深度,
|
||||
* 任一优先级可抢占任意上下文而不破坏计数——STREX 失败方重试时会看到已修正的
|
||||
* 深度并补上时间戳。已知残余误差:exit 在读时间戳与递减深度之间被高优先级
|
||||
* 中断插入时,该次中断时长漏计(几 cycle 窗口,对 0.1% 分辨率可忽略)。
|
||||
*
|
||||
* @note DWT 已由 EscLan9252::init 使能;init() 幂等兜底,在主循环启动前调用。
|
||||
*/
|
||||
#ifndef SYS_CPU_LOAD_H
|
||||
#define SYS_CPU_LOAD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/** C 侧挂钩(ecatappl.c / dc_app.c)。与 beginWork/endWork 同一深度计数器。 */
|
||||
void CpuLoad_Enter(void);
|
||||
void CpuLoad_Leave(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
/**
|
||||
* @brief CPU 负载率测量。
|
||||
* @note 全静态类,与 SysTickTimer 同风格。
|
||||
*/
|
||||
class CpuLoad {
|
||||
public:
|
||||
/**
|
||||
* @brief 使能 DWT CYCCNT 并复位统计状态。
|
||||
* @note 幂等;须在主循环启动前调用一次。
|
||||
*/
|
||||
static void init();
|
||||
|
||||
/** @brief 主循环圈起点:进入"busy"区间。与 endWork() 成对,每圈各一次。 */
|
||||
static void beginWork();
|
||||
|
||||
/**
|
||||
* @brief 主循环圈终点:退出"busy"区间。
|
||||
* @note 与 beginWork() 成对;窗口滚动不在这里(begin/end 可能整圈不执行),
|
||||
* 见 tick()。
|
||||
*/
|
||||
static void endWork();
|
||||
|
||||
/**
|
||||
* @brief 主循环每圈无条件调用:检查 1s 窗口是否滚动。
|
||||
* @note 只做一次 CYCCNT 读 + 比较(命中才进 flash 侧结算函数),滚动不再
|
||||
* 依赖"本圈恰好有挂钩段执行",空闲系统 seq 也会按秒推进。
|
||||
*/
|
||||
static void tick();
|
||||
|
||||
/** @brief 中断入口挂钩:与 isrExit() 成对,包住 ISR 主体。 */
|
||||
static void isrEnter();
|
||||
|
||||
/** @brief 中断出口挂钩。 */
|
||||
static void isrExit();
|
||||
|
||||
/**
|
||||
* @brief 最近一个完整窗口的负载率。
|
||||
* @return 0..1000,单位 0.1%(1000=满载);窗口未滚动过时为 0。
|
||||
*/
|
||||
static uint16_t loadPermille() { return s_loadPerm; }
|
||||
|
||||
/**
|
||||
* @brief 已完成的统计窗口数(自 init 起,单调递增回绕)。
|
||||
* @note 随负载率一起进 EtherCAT 输入 PDO 段 5,主站据此判断数据是否仍在刷新。
|
||||
*/
|
||||
static uint32_t windowSeq() { return s_seq; }
|
||||
|
||||
public:
|
||||
/* 静态成员开成 public:cpu_load.cpp 里 .ramfunc 的文件级内联热路径
|
||||
* (cpuLoadEnter/cpuLoadLeave/cpuLoadRollWindow) 直接触碰它们,
|
||||
* 经成员函数绕一道反而多一层调用。勿在模块外写这些状态。 */
|
||||
|
||||
static volatile uint32_t s_depth; /**< busy 区间嵌套深度 */
|
||||
static volatile uint32_t s_stamp; /**< 深度 0→1 时刻的 CYCCNT 快照 */
|
||||
static volatile uint32_t s_busy; /**< 当前窗口内累计 busy 周期数 */
|
||||
static volatile uint32_t s_winStart; /**< 当前窗口起点 CYCCNT */
|
||||
static volatile uint16_t s_loadPerm; /**< 最近一个完整窗口的负载率(0.1%) */
|
||||
static volatile uint32_t s_seq; /**< 完成滚动的窗口计数 */
|
||||
};
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* SYS_CPU_LOAD_H */
|
||||
@@ -59,4 +59,19 @@
|
||||
*/
|
||||
#define RT_CRITICAL_RESTORE(val) __set_BASEPRI(val)
|
||||
|
||||
/**
|
||||
* @brief Preempt-0 专用临界区:PRIMASK 全局屏蔽,用于与 preempt=0 中断共享的
|
||||
* 单字标志(读-改-写)。
|
||||
* @note 严格限定 O(1) 状态操作(置位/清位/单字赋值)。**严禁**在其中调用
|
||||
* printf/LOG_*:串口入队实测 36us,TX FIFO 满时可达 123us,会把 EtherCAT
|
||||
* SYNC0 抖动顶出 DC 容差。日志一律放临界区外。
|
||||
*/
|
||||
#define RT_CRITICAL_PREEMPT0_SAVE() \
|
||||
({ \
|
||||
uint32_t __primask_save = __get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
__primask_save; \
|
||||
})
|
||||
#define RT_CRITICAL_PREEMPT0_RESTORE(val) __set_PRIMASK(val)
|
||||
|
||||
#endif /* SYS_CRITICAL_H */
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @file esp32_en.cpp
|
||||
* @brief ESP32 EN (PC9) 驱动。PC9 拉高后延时 1.2s 复位 SPI,避开起机毛刺。
|
||||
*/
|
||||
#include "sys/esp32_en.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include <stdio.h>
|
||||
|
||||
namespace {
|
||||
|
||||
/** @brief PC9 拉高后延时多久再清 SPI(等 ESP32 ROM/APP 引脚初始化毛刺结束)。 */
|
||||
constexpr uint32_t kPostBootRelinkDelayMs = 1200U;
|
||||
|
||||
/** @brief 延时复位排期;0=无。 */
|
||||
volatile uint32_t s_relinkAtMs = 0U;
|
||||
|
||||
void drivePinLow()
|
||||
{
|
||||
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESP32_RESET_PIN);
|
||||
gpio_bit_reset(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN);
|
||||
}
|
||||
|
||||
void drivePinHigh()
|
||||
{
|
||||
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESP32_RESET_PIN);
|
||||
gpio_bit_set(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN);
|
||||
}
|
||||
|
||||
void floatPin()
|
||||
{
|
||||
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_IN_FLOATING, 0,
|
||||
CONFIG_ESP32_RESET_PIN);
|
||||
}
|
||||
|
||||
bool readPin()
|
||||
{
|
||||
return gpio_input_bit_get(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN) == SET;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8_t Esp32En::drive(uint8_t action)
|
||||
{
|
||||
switch (action) {
|
||||
case 0: /* 拉高(开 WiFi) */
|
||||
case 2:
|
||||
/* ESP32 仍死着:先清一次 SPI;起机毛刺会在 1.2s 后再清一次 */
|
||||
s_relinkAtMs = 0U;
|
||||
AppSpiCommandBus.resetEsp32Link();
|
||||
drivePinHigh();
|
||||
s_relinkAtMs = TaskManager::state().tick_1ms + kPostBootRelinkDelayMs;
|
||||
break;
|
||||
case 1: /* 拉低保持(关 WiFi) */
|
||||
s_relinkAtMs = 0U;
|
||||
drivePinLow();
|
||||
AppSpiCommandBus.resetEsp32Link();
|
||||
break;
|
||||
case 3: /* 浮空 */
|
||||
s_relinkAtMs = 0U;
|
||||
floatPin();
|
||||
AppSpiCommandBus.resetEsp32Link();
|
||||
break;
|
||||
case 4: /* 只读 */
|
||||
break;
|
||||
case 5: /* 仅复位 SPI(诊断;ESP32 空闲时调用,再让它 HELLO) */
|
||||
s_relinkAtMs = 0U;
|
||||
AppSpiCommandBus.resetEsp32Link();
|
||||
break;
|
||||
default:
|
||||
return 0xEEU;
|
||||
}
|
||||
|
||||
const bool level = readPin();
|
||||
static const char *const kName[6] = {
|
||||
"restore HIGH", "drive LOW (hold)", "drive HIGH", "float", "read-only", "spi-relink"};
|
||||
printf("[APP][ESP32_EN] %s -> pin reads %s\r\n", kName[action],
|
||||
level ? "HIGH" : "LOW");
|
||||
return level ? 1U : 0U;
|
||||
}
|
||||
|
||||
void Esp32En::poll()
|
||||
{
|
||||
if (s_relinkAtMs == 0U) {
|
||||
return;
|
||||
}
|
||||
const uint32_t now = TaskManager::state().tick_1ms;
|
||||
if ((int32_t)(now - s_relinkAtMs) < 0) {
|
||||
return;
|
||||
}
|
||||
s_relinkAtMs = 0U;
|
||||
AppSpiCommandBus.resetEsp32Link();
|
||||
printf("[APP][ESP32_EN] post-boot SPI relink (glitch window over)\r\n");
|
||||
}
|
||||
|
||||
void Esp32En::cancelRelinkFromSpi()
|
||||
{
|
||||
/* HELLO/业务帧已进来:不必再延时复位(会打断建连) */
|
||||
s_relinkAtMs = 0U;
|
||||
}
|
||||
|
||||
/* EtherCAT SDO 0x2503 回调入口(dc_app.c 为 C 侧)。 */
|
||||
extern "C" uint8_t EcatEsp32EnDrive(uint8_t action)
|
||||
{
|
||||
return Esp32En::drive(action);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user