/** * @file config.h * @brief 应用级集中配置项定义。 * @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。 */ #ifndef SYS_CONFIG_H #define SYS_CONFIG_H #include #include #include "gd32e50x.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)。 */ #define CONFIG_SPI_APP_LOG_ENABLE 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)被拒绝→握手失败。 * 1.6.1 起:保持 1U 也可——motor_ota_relay 在 OTA 期间经 CanFd::setChannelBrsSuppress * 按通道压制 BRS(与 ARD 切换同一 enterState 钩子,结束必恢复),日常吞吐不受影响。 */ #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 通道使能。CH1/CH2 未接设备,关闭避免持续 CAN 错误 + 占 SPI 带宽。 */ constexpr bool CONFIG_CAN_CH0_ENABLE = true; constexpr bool CONFIG_CAN_CH1_ENABLE = true; constexpr bool CONFIG_CAN_CH2_ENABLE = true; /** @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 启动横幅与 main 进度打印。 */ #define CONFIG_APP_BANNER_LOG_ENABLE 1 /* ============================================================ * 日志分级宏:*_LOGI=INFO 级,由上方模块开关编译期裁剪, * 格式串随之被链接器回收;LOG_ERR=错误/关键事件,恒保留。 * 使用文件需自行可见 printf 声明()。 * ============================================================ */ #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 = 6U; constexpr uint32_t APP_FW_VERSION_PATCH = 13U; /* 1.6.13:根治 —— 1.6.12 的 PDI 入口预抽实测(60s 正弦指令负载,链路活性已断言):overfull 50/s→0 但指纹违规仍 ~92/s、torn 在无溢出阶段 1-7/s 持续涨 —— 判决:溢出只是共犯之一,**357µs 的 PDI 阻塞窗口本身**才是病理母体(窗口内 FIFO 满溢毁窗口 + 被冻结的取帧路径留着不依赖溢出的换帧路径,MI/MP 只抓到少数)。本版釜底抽薪:CAN RX ISR 提到 preempt=0(CONFIG_IRQ_PREEMPT_CAN_RX,与 SYNC0 同级互不抢占)—— RX 不再被 PDI/SYNC1/SCHED 压住,FIFO 深度 ≤2、溢出与冻结同时消失,receiveFrameChecked 的多字取数自此真正原子;TX ISR 留在 2(PDI 上下文有 TX 提交,提 0 会引入提交/释放竞争)。代价:SYNC0/PDI 偶发 µs 级延迟(RX ISR ~3µs/帧×8kHz≈2.4% 占空),相对 1ms 周期是零头。PDI 预抽 pumpAllChannels 随之撤除(失去意义且引入 PDI/ISR 双消费者风险)。保留 1.6.12 的诚实管道(校验只计不丢)与诊断布局(overfull@490、relto@492、seq u16@488)作为验证仪表:修好 = reject/torn/overfull 恒 0。 */ /* 1.6.12:源头修复 —— 用户裁定"过滤是遮羞布,修采集源头,前端只许诚实反馈"。1.6.11 上线后实测(repro_cmdload 正弦负载)reject@494 仍以 ~384/s 增长:RFOD=DISABLE 挡不住溢出毁窗口(本 IP 满溢时 DISCARD 语义存疑,或毁帧发生在满态下的读并发路径),内容校验丢弃只是把坏数据拦在计数前。本版两处:(1) **源头**:CanFd::pumpAllChannels() 挂进 onEscIrqExti 的 PDI_Isr 入口 —— 在 buildInput 357µs 长阻塞(CAN ISR preempt=2 全程被压)开始之前把三路 FIFO 抽干,阻塞窗口起点深度=0、窗口内到达 ≤2-3 帧,3 深度 FIFO 从"必然打满"变成"必然不满";preempt=1 + SYNC0 已关的上下文保证与 CAN ISR 取帧互斥,与主循环批量队列生产零交集。(2) **诚实管道**:内容校验改为只计不丢(reject@494 恒 0 = 源头修好;还在涨 = 源头未愈且页面如实显示),诊断区加挂判决性测量:seq 488 拆 u16、overfull(RFO0)@490、release_timeout@492(原重入计数位,恒 0 让出)。reject 与 overfull 是否同涨 = "残余拼接是否仍源自溢出"的判决。 */ /* 1.6.11:根治残余拼接 —— 1.6.10 的 settle 延迟实测无效(正弦指令负载下拼接率不变),节点指纹实验(两电机反相正弦+速度限 200/150 打指纹)实锤拼接是**跨节点**的:node3 的 STATUS 帧头 24B = node2 的完整 CONFIG(速度指纹 200.0、角度 1142° 均属 node2 轨迹),且 CONFIG↔CONFIG 同型拼接结构校验抓不到(两边都合法)—— 电机 TX 不可能发出别的电机的字节 ⇒ 混合点在 GD32 RX 侧。真根因:can_init 的 rec_fifo_overwrite=ENABLE —— FIFO 满时覆盖最旧邮箱,而最旧邮箱正是 CPU 正在读的输出窗口;buildInput 在 PDI_Isr 占 ~357µs 压住 CAN ISR,发指令时总线 ~8kHz、阻塞窗口内 ~3 帧打满 FIFO 深度 → 覆盖进行中被读走 = 拼接(MI/MP 回读挡不住:MI 是覆盖序列最后更新的一环;settle 延迟也挡不住:非时间竞态而是溢出策略)。本版:(1) rec_fifo_overwrite=DISABLE —— 溢出丢新帧置 RFO0(rx_fifo_overfull_count 已统计),周期量丢一拍下拍即补,与软件队列"满丢新不覆旧"同则;(2) 撤 settle 延迟(无效且白烧 CPU);(3) 保留 1.6.10 的重取循环删除与边界修正,并新增入队前内容校验(STATUS 温度/电压量程+帧间斜率、CONFIG 未使用区/模式)丢结构矛盾帧、计数段5@494 —— 作为溢出之外的纵深防御(实测能挡跨型拼接,挡不住同型 CONFIG↔CONFIG,故必须配合 (1))。验证:正弦指令负载 60s,指纹违规 40→0。 */ /* 1.6.10:根治拼接帧 —— 1.6.9 的"队列覆写"定性被自测推翻为**次要**:段 2 电机槽(MotorState 路径,与批量环形队列完全独立)出现同签名拼接(node2 槽头 = node3 CONFIG 前缀 03c12200,解出 mcuTemp=-161.25),且 rx_torn/pdo_reentry 自启动恒 0 —— 两条独立下游同时中招 ⇒ 混合点在共同上游 CAN RX ISR;校验恒不报 ⇒ 拼接发生在 MI/MP 判据盲区。真根因=1.6.7 的"校验失败原地重取":RFIFOMDATA0 读指针随读自动前进且不因换帧复位,attempt1 失败后 attempt2 从错位指针开读,MI/MP 比对的是窗口身份、管不住指针错位,于是"校验通过、内容为上一帧尾巴拼下一帧开头"。实测(自测脚本 scripts/selftest_torn.py,按拍去重+灵敏度自证):空闲 0%(35k 条),电机活跃时 9.155%(56835/620838,STATUS 错标为 CONFIG 占绝对多数)。本版:(1) 去掉重取循环,校验失败即 release+丢帧+rx_torn 计数(1.6.6 语义,STATUS/CONFIG 均 1kHz 周期量,丢一拍下拍即补);(2) fillRxBatch 边界改判段 5 起点 ECAT_DIAG_OFF_LOAD(484) 而非 PDO 全长(496)——潜伏越界写诊断区,当前容量下触不到,容量放宽即咬人;(3) releaseFifo0 自清后补 settle 延迟 —— 烧(1)(2)后实测活跃流量下仍残余 0.11% 跨节点拼接(node2 槽头=node3 CONFIG 前缀,且 rx_torn 已正常计数 146,证明窗口换帧路径已被挡住),定性为 RFD 自清只代表 dequeue 握手完成、下一帧 MDATA 还在逐字搬运(MI/MP 先落数据区后落),while 连排的第二次读紧跟 release 即拿到陈旧头+新鲜尾,MI/MP 回读对这种陈旧全盲;空闲每 ISR 单帧故 0%、活跃连排才中招,与实测分布吻合。 */ /* 1.6.9:拿到实证并定位真根因 —— 给主站加了同拍 raw+parsed 的只读诊断路由(/api/motor/rxraw)后抓到:**坏帧在原始 PDO 字节里就已经是错的**(如 off=72 处条目声称 id=0x20101/dlc=24,数据却是合法 STATUS:mcuTemp=35.92 motTemp=33.55 pwrV=23.972),而主站 parsed 与 raw 逐字节一致 —— 坏在从站写入侧,与主站解析无关。根因:批量接收环形队列长期饱和下,生产者覆写了消费者正在读的槽。实测证据:CAN 侧约 4000 帧/s 而批量区每拍最多 3 条(=3000 帧/s),净积压使 pending 恒 8/12、lost 几乎每拍都有、count 恒取满 3;消费者 fillRxBatch 又攒到循环末才发布 s_rxRingTail,生产者判满用的正是这个滞后值,于是把新帧写进消费者刚读过但未发布的槽,拷进 PDO 的 id/dlc/data 遂来自不同帧(错标率 1.77%)。两处修复:(1) onCanFrame 入队**过滤 STATUS 帧** —— 完整 STATUS 本就由段 2 按槽上报,主站 drainRx() 拿到 STATUS 也直接 continue 丢掉,让它占队列纯属重复,正是它把队列压爆;(2) fillRxBatch 改为整条拷贝到局部副本再用,并逐条发布 tail,使生产者判满始终基于真实进度。 1.6.8:曾判定为"输入 PDO 填充跨上下文重入"并加了双缓冲发布 + 重入计数(段5@492)。**该假说事后被证伪**:SM-Synchron 稳态下 MainLoop 分支的门控 (!bEscIntEnabled||!bEcatFirstOutputsReceived)&&!bDcSyncActive 恒为假不执行,Sync0 因 free-run 无 DC 脉冲不触发,Sync1 与 PDI_Isr 同为 preempt=1 不能抢占 —— buildInput 实际只在 PDI_Isr 单上下文跑。双缓冲发布本身是正确加固,予以保留。 1.6.7:接着 1.6.6 治 CAN 取帧拼接。1.6.6 的校验只读 MP 的 TS/DLENC,TS 是 16 位计数器、相邻帧可能同值,DLENC 更常同为 48,于是"ID 报 STATUS、数据却是 CONFIG 帧"这种错配从校验漏了过去。本版三处改动:(1) 回读校验再加上 MI(完整 ID/帧格式),ID 变了就一定是换了帧,没有巧合空间;(2) 校验失败改为原地重取最多 3 次而不是直接丢弃 —— 失败时窗口里躺着的正是完整的新帧,重读一次通常就能拿全;(3) 整段取数放进 BASEPRI 临界区,挡住 preempt>=1 的 EtherCAT 中断。注:事后(1.6.8)证明本版方向不是主因,但这三条本身仍是正确的加固,予以保留。 1.6.6:根治 ECAT/CAN 偶发乱码 —— 取帧不再用厂商库 can_message_receive。一条 48B CANFD 报文要对同一地址 CAN_RFIFOMDATA0 连读 12 次才取完,整个过程不原子;而厂商库置 RFD0 后既不等硬件自清也不校验,取数过程会横跨 dequeue、读出两帧拼接。现改为本地 receiveFrameChecked():取数前后各读一次 MP,比对 TS[31:16] 与 DLENC,不一致即判定邮箱窗口被换帧,整帧丢弃并计入 Diag::rx_torn_count(经输入 PDO 段 5 偏移 486 原保留位上报,PDO 长度不变、EEPROM 无需重刷);releaseFifo0() 置位后轮询等自清,且改用直接赋值 —— 原来的 |= 是读改写,会把期间硬件置起的 RFF0/RFO0(rc_w1) 顺手清掉,FIFO 溢出证据就是这么丢的。另修 ISR 里 frame.id/stats.last_rx_id 取自三路共享的 s_diag.last_rx_id(而 data 取自局部 rx_message,会串出"A 通道 id 配 B 通道数据"),一律改用本帧局部 rxId。 1.6.5:段 2 电机槽补 seqlock(MotorSlot.wr_seq)。buildInput 跑在 PDI_Isr,会抢占主循环的 MotorState::onCanFrame,而那里是"先 seq=++global_seq、后 memcpy(slot.data,48)"——抢占落在 memcpy 中途就会发布"新 seq + 半新半旧 48B",主站只按 seq 判新鲜度分辨不出,混合边界若落在 float 的符号/指数字节(B16/B20/B24)就是网页上的偶发离谱值。现消费者见 wr_seq 为奇数即跳过本槽(该 56B 留零,主站按 node_id==0 跳过、下拍自愈),win_count 改报窗口跨度而非填充数以保持按 i 定位。CONFIG 路径不套锁:它只写 config_* 与恒等的 node_id/channel,buildInput 不同读这些。槽布局不变 ⇒ 输入 PDO 仍 496B,EEPROM/SII 无需重刷。 1.6.4:电机槽 40B→56B(8B 头 + v0.74 的 48B STATUS),每拍槽数 7→5(段 2 的 280B 正好 5×56)。修掉两件事:buildInput 按 48B 拷贝时溢出 16B 盖住段 3 长帧快照;主站侧 STATUS 被截到 32B 使 InitStatus(B32-35) 永远拿不到、网页把"无数据"显示成 NORMAL。槽数×槽长不变 ⇒ 输入 PDO 仍 496B,EEPROM/SII 无需重刷;主站需同步 MOT_SLOT_SIZE / MOT_SLOTS_PER_PDO / MOT_STATUS_DLC。 1.6.3:processOutput 不再从 out 回读 WIN@8——1.6.0 起 out 只在 seq 沿那拍新鲜,无沿周期里它是上一次全量读的残值,于是每拍用开机首拍的 0 盖掉 peekOutput 刚取到的新值,电机状态扫描窗口被钉死在基址 0,槽位 7..26(node3 及以上)永不上报,只有主站真发一帧才复位。WIN 的每拍刷新改由 peekOutput 独占。判据:不发任何帧,从站回显 win_base 也应等于主站请求值。 */ constexpr uint32_t APP_FW_VERSION_BUILD = 0U; /** @brief USART0 默认波特率。 */ constexpr uint32_t CONFIG_USART0_BAUDRATE = 3000000U; /* 3M:诊断打印高速输出,printf 阻塞极小、不拖慢 relay */ /** @brief ADC 外设实例。 */ constexpr uint32_t CONFIG_ADC_INSTANCE = ADC0; /** @brief ADC 外部 VIN 检测通道号(原理图:PA6 -> VIN_ADC)。 */ constexpr uint32_t CONFIG_ADC_VIN_CHANNEL = ADC_CHANNEL_6; /** @brief ADC 内部温度通道号。 */ constexpr uint32_t CONFIG_ADC_TEMP_CHANNEL = ADC_CHANNEL_16; /** @brief ADC 内部参考电压通道号。 */ constexpr uint32_t CONFIG_ADC_VREFINT_CHANNEL = ADC_CHANNEL_17; /** @brief ADC 满量程计数值。 */ constexpr float CONFIG_ADC_MAX_VALUE = 4095.0f; /** @brief 内部参考电压典型值,单位 V。 */ constexpr float CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE = 1.20f; /** @brief 温度传感器 25℃ 对应电压,单位 V。 */ constexpr float CONFIG_ADC_TEMP_V25 = 1.43f; /** @brief 温度传感器平均斜率,单位 V/℃。 */ constexpr float CONFIG_ADC_TEMP_AVG_SLOPE = 0.0043f; /** @brief ADC 上电后稳定等待时间,单位 ms。 */ constexpr uint32_t CONFIG_ADC_STABILIZE_DELAY_MS = 2U; /** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */ constexpr uint32_t CONFIG_ADC_FILTER_SIZE = 6U; /** @brief CAN 状态报文标准帧 ID。 */ constexpr uint32_t CONFIG_CAN_STATUS_ID = 0x321U; /** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */ 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 命令。 */ constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_CMD = 0xB0U; /** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */ 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 注册 12 个:boot_request / ota_wait / ping / init_status / can_bridge / * can_burst / can_feedback / id_snapshot / adc_feedback / motor_state / motor_ota / exmc_diag。 * registerHandler 表满时静默丢弃,motor_ota 曾因容量=8 被丢弃(0xD0 回 0xEE)。 * 取 14 留余量;新增 handler 时须确认本容量足够。 */ constexpr uint8_t CONFIG_SPI_MAX_COMMANDS = 14U; /** @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) * * @note RCU_MODIFY_UP 宏在 system_clock_180m_hxtal() 末尾将 AHB 从 /4 * 临时切到 /1,这是 GigaDevice 官方行为,不可删除。 * 最终 AHB=180MHz, APB1=90MHz, APB2=180MHz, TIMER2_CLK=180MHz。 * ============================================================ */ /** @brief 仲裁段预分频值。 */ 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)。 */ constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ; /** @brief 仲裁段时间段 2(PHSEG2)。 */ constexpr uint32_t CONFIG_CAN_NOMINAL_BS2 = CAN_BT_BS2_2TQ; /** @brief 数据段预分频值。 */ constexpr uint32_t CONFIG_CAN_DATA_PRESCALER = 9U; /** @brief 数据段再同步跳跃宽度。 */ constexpr uint32_t CONFIG_CAN_DATA_SJW = CAN_BT_SJW_1TQ; /** @brief 数据段时间段 1。 */ constexpr uint32_t CONFIG_CAN_DATA_BS1 = CAN_BT_BS1_3TQ; /** @brief 数据段时间段 2。 */ constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ; /* BRS / 启动探测 / 通道使能:见文件顶部「功能开关」 */ /** @brief CAN 接收过滤器目标标准帧 ID。 */ 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 = 48U; /** @brief 用户调度定时器实例。 */ constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2; /** @brief 用户调度定时器中断号。 */ constexpr IRQn_Type CONFIG_SCHED_TIMER_IRQn = TIMER2_IRQn; /** @brief 用户调度定时器时钟使能宏参数。 */ 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。 */ constexpr uint32_t CONFIG_SCHED_TIMER_PRESCALER = 179U; /** @brief 用户调度定时器周期重装值。 */ constexpr uint32_t CONFIG_SCHED_TIMER_PERIOD = 99U; /** @brief 用户调度定时器 NVIC 抢占优先级。 */ constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO = 1U; /** @brief 用户调度定时器 NVIC 子优先级。 */ constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_SUB_PRIO = 0U; /** @brief 调度基准节拍:ADC VREF 采样周期,单位 100us tick。 */ constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U; /** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */ constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U; /** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */ 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 独立看门狗预分频值。 */ 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 */ constexpr uint32_t CONFIG_FWDGT_RLD = 1874U; /** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */ constexpr uint32_t CONFIG_FWDGT_TIMEOUT_MS = 3000U; /* ============================================================ * BOOT 版本在 OTA 配置页中的 Flash 存储地址 * 与 BOOT 工程 sys/config.h 保持一致 * ============================================================ */ 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 位抢占优先级) * ============================================================ */ 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; /* 1.6.13:CAN RX 提到 preempt=0(与 EtherCAT SYNC0 同级)。 * 1.6.12 的 PDI 入口预抽实测:溢出确实大降(overfull 50/s→0)但拼接照旧 * (指纹违规 ~92/s,且 torn 在无溢出阶段仍 1-7/s 在涨)—— 357µs 的 PDI * 阻塞窗口本身就是病理:窗口内 FIFO 满溢毁窗口的同时,被冻结的取帧路径 * 还留着不依赖溢出的换帧路径(MI/MP 只抓到少数)。preempt=0 一并掐断: * RX ISR 不再被 PDI/SYNC1/SCHED 压住(FIFO 深度 ≤2,溢出与冻结同时消失), * 且与 SYNC0 同级互不抢占 —— receiveFrameChecked 的多字取数自此真正原子。 * TX ISR 留在 2:PDI 上下文有 TX 提交,提到 0 会引入提交/释放竞争。 * 代价:SYNC0/PDI 偶发 µs 级延迟(RX ISR ~3µs/帧 × ~8kHz ≈ 2.4% 占空), * 相对 1ms 周期与 165µs 总线时间是零头。 */ 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) * ============================================================ */ /* 1.5.27:CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD / CONFIG_ESC_PDI_INPUT_OFFSET / * CONFIG_ESC_PDI_OUTPUT_OFFSET / CONFIG_ETHERCAT_PDO_SIZE 随死代码 EthercatSlave:: * periodicIsr 三缓冲一并删除;PDO 交换由 SSC(dc_app.c APPL_*, 4×64bit=32B) 完成。 */ /* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */ 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)。 */ 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; 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; 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 标准映射) ---------- */ 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 */