Files
cbb 5fd986299c feat(comm): 1.6.51 CAN 空通道自动静默 + PDO 诊断 + OTA 布局共用头
变更点:
1. CAN-FD(device/CANFD.cpp、sys/config.h)
   - 发送统一 FD+BRS(DLC≤8 不再回退经典 CAN);OTA 期仍按通道压制 BRS
   - 空通道自动识别:连续 5 个 1Hz 窗口无有效帧且有错误 → 静默上报;有帧即恢复
   - 三路 CAN 默认全开;RX parse budget 48→64
2. EtherCAT 桥 / PDO
   - 段1 增 txtxid/rx00ff;版本应答 0x00FF 粘滞发布,避免被 CONFIG 冲掉
   - STATUS 禁止写入 xrx 快照;processSeq 新鲜度救援
3. OTA / 配置
   - sys/config.h include 仓库内 shared/ota_layout.h(与 BOOT 共用,不再依赖仓外路径)
   - ota_boot_request 对齐配置页 word 布局
4. docs/audit_ecat_spi_wifi_ota_compatibility.md:ECAT/SPI/WiFi/OTA 兼容性审计
5. .gitignore:忽略 .trae/、docs/traework-import、评审过程稿
2026-09-20 22:58:09 +08:00

350 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file initial.cpp
* @brief 应用启动初始化:模块成功置位,结束统一报告结果。
*
* 顺序:
* 复位源采集 → NVIC → SysTick → ESP32_EN 保持 → USART0 → AppSpi(尽早 HELLO 探活)→
* ADC → CANFD → OtaBootRequest → TaskManager → Scheduler →
* EtherCAT(+Sync 可选) → Watchdog → report
*
* 各模块 init 带检查(寄存器回读 / 节拍验证 / 校准超时),
* 失败不置位;复位源/模块位图/critical 失败位图经 getter 导出供 SPI 0xA3 上报。
*/
#include "sys/initial.h"
#include "device/canfd.h"
#include "device/usart0.h"
#include "device/timer.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/spi/app_spi_command.h"
#include "sys/systick.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/config.h"
#include "comm/esc_lan9252.h"
#include "comm/ethercat_slave.h"
#include "comm/ethercat_sync.h"
extern "C" {
uint16_t MainInit(void);
void MainLoop(void);
}
uint32_t AppInitializer::s_mask = 0U;
uint8_t AppInitializer::s_initState = AppInitializer::kInitOk;
uint8_t AppInitializer::s_resetCause = 0U;
uint8_t AppInitializer::s_faultCodes[AppInitializer::kModuleCount] = {0U};
/* CoE 0x100A「Software version」写入口,实现在 comm/ssc/src/coeappl.c(C 链接)。
* 主站总线读此对象可自证「新 APP 真起来了」。 */
extern "C" void AppSetSoftwareVersion(const char *version);
namespace {
struct InitEntry {
uint32_t bit;
const char *name;
bool critical;
};
static const InitEntry kEntries[] = {
{ AppInitializer::kNvic, "nvic", true },
{ AppInitializer::kSysTick, "systick", true },
{ AppInitializer::kEsp32Hold, "esp32_hold", true },
{ AppInitializer::kUsart0, "usart0", true },
{ AppInitializer::kAppSpi, "app_spi", true },
{ AppInitializer::kAdc, "adc", true },
{ AppInitializer::kCanFd, "canfd", true },
{ AppInitializer::kOtaBootReq, "ota_boot_req", true },
{ AppInitializer::kTaskManager, "task_manager", true },
{ AppInitializer::kScheduler, "scheduler", true },
{ AppInitializer::kEthercat, "ethercat", false },
{ AppInitializer::kEthercatSync, "ethercat_sync", false },
{ AppInitializer::kWatchdog, "watchdog", true },
};
} // namespace
void AppInitializer::mark(ModuleBit b)
{
s_mask |= static_cast<uint32_t>(b);
}
/** @brief ModuleBit(1<<N) → 数组索引 N。 */
static uint8_t moduleBitIndex(uint32_t bit)
{
uint8_t idx = 0U;
while (bit > 1U) {
bit >>= 1U;
++idx;
}
return idx;
}
void AppInitializer::setFault(ModuleBit b, uint8_t code)
{
const uint8_t idx = moduleBitIndex(static_cast<uint32_t>(b));
if (idx < kModuleCount) {
s_faultCodes[idx] = code;
}
}
uint8_t AppInitializer::faultCode(uint8_t module_index)
{
return (module_index < kModuleCount) ? s_faultCodes[module_index] : 0U;
}
uint8_t AppInitializer::faultCodes(uint8_t *out)
{
if (out == nullptr) {
return 0U;
}
for (uint8_t i = 0U; i < kModuleCount; ++i) {
out[i] = s_faultCodes[i];
}
return kModuleCount;
}
uint32_t AppInitializer::criticalFailMask()
{
uint32_t fail = 0U;
for (size_t i = 0; i < sizeof(kEntries) / sizeof(kEntries[0]); ++i) {
const InitEntry &e = kEntries[i];
if (e.critical && (s_mask & e.bit) == 0U) {
fail |= e.bit;
}
}
return fail;
}
/**
* @brief 采集 RCU_RSTSCK 复位源位图并清硬件标志。
* @note 必须在 init 最开头调用(早于任何可能读标志的模块);
* 清除不影响 BOOT 的 FWDGT 停留 OTA 判定——新的看门狗复位会由硬件重新置位。
*/
void AppInitializer::captureResetCause()
{
uint8_t cause = 0U;
if (rcu_flag_get(RCU_FLAG_PORRST) != RESET) cause |= kRstPor;
if (rcu_flag_get(RCU_FLAG_EPRST) != RESET) cause |= kRstEpr;
if (rcu_flag_get(RCU_FLAG_FWDGTRST) != RESET) cause |= kRstFwdgt;
if (rcu_flag_get(RCU_FLAG_WWDGTRST) != RESET) cause |= kRstWwdgt;
if (rcu_flag_get(RCU_FLAG_SWRST) != RESET) cause |= kRstSw;
if (rcu_flag_get(RCU_FLAG_LPRST) != RESET) cause |= kRstLp;
rcu_all_reset_flag_clear();
s_resetCause = cause;
}
void AppInitializer::report()
{
bool critical_ok = true;
bool all_ok = true;
char fail_list[160] = {0};
size_t fail_len = 0U;
for (size_t i = 0; i < sizeof(kEntries) / sizeof(kEntries[0]); ++i) {
const InitEntry &e = kEntries[i];
const bool ok = (s_mask & e.bit) != 0U;
if (ok) {
continue;
}
all_ok = false;
if (e.critical) {
critical_ok = false;
}
if (fail_len + 2U < sizeof(fail_list)) {
if (fail_len > 0U) {
fail_list[fail_len++] = ',';
}
const char *p = e.name;
while (*p != '\0' && fail_len + 1U < sizeof(fail_list)) {
fail_list[fail_len++] = *p++;
}
fail_list[fail_len] = '\0';
}
/* 故障码后缀 #XX(16 进制;canfd 高 4 位为通道号),0=成功不追加。
* 码值含义见 initial.h 注释与各模块头文件;网页按同一编码解码中文原因。 */
const uint8_t fc = s_faultCodes[moduleBitIndex(e.bit)];
if (fc != 0U && fail_len + 3U < sizeof(fail_list)) {
const char *hex = "0123456789ABCDEF";
fail_list[fail_len++] = '#';
fail_list[fail_len++] = hex[(fc >> 4) & 0xFU];
fail_list[fail_len++] = hex[fc & 0xFU];
fail_list[fail_len] = '\0';
}
}
/* 复位源随 init 报告输出一次(位图多位置位属正常,如 POR 常伴随 EPRST/SWRST) */
printf("[APP] reset_cause: 0x%02X (por=%d epr=%d fwdgt=%d wwdgt=%d sw=%d lp=%d)\r\n",
(unsigned)s_resetCause,
(s_resetCause & kRstPor) ? 1 : 0,
(s_resetCause & kRstEpr) ? 1 : 0,
(s_resetCause & kRstFwdgt) ? 1 : 0,
(s_resetCause & kRstWwdgt) ? 1 : 0,
(s_resetCause & kRstSw) ? 1 : 0,
(s_resetCause & kRstLp) ? 1 : 0);
if (critical_ok && all_ok) {
s_initState = kInitOk;
printf("[APP] init: OK (all modules)\r\n");
} else if (critical_ok) {
s_initState = kInitDegraded;
printf("[APP] init: OK (critical), skipped/failed: %s\r\n",
fail_list[0] != '\0' ? fail_list : "?");
} else {
s_initState = kInitCriticalFail;
printf("[APP] init: FAIL, modules: %s\r\n",
fail_list[0] != '\0' ? fail_list : "?");
}
/* 日志完整性自证:串口 TX 缓冲溢出会静默吞字节。
* 缓冲满时自旋腾位,仅 USART 外设异常才丢弃;非 0 即代表上面的日志不可全信。 */
const uint32_t dropped = Usart0::droppedBytes();
if (dropped != 0U) {
printf("[APP] WARN: uart dropped %u bytes, 以上日志可能不完整\r\n",
(unsigned)dropped);
}
}
void AppInitializer::init()
{
s_mask = 0U;
s_initState = kInitOk;
for (uint8_t i = 0U; i < kModuleCount; ++i) {
s_faultCodes[i] = 0U;
}
captureResetCause();
/* NVIC 优先级分组:写入后回读 AIRCR.PRIGROUP 验证(纯寄存器写,回读确认分组生效) */
nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP);
if ((SCB->AIRCR & (0x7UL << 8U)) == (CONFIG_NVIC_PRIORITY_GROUP & (0x7UL << 8U))) {
mark(kNvic);
} else {
setFault(kNvic, kFaultNvicPrigroupMismatch);
}
/* SysTick:内部验证节拍真实前进;失败不返回(FATAL 停机) */
if (SysTickTimer::initMs()) {
mark(kSysTick);
}
rcu_periph_clock_enable(RCU_GPIOC);
gpio_bit_set(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN);
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_ESP32_RESET_PIN);
/* ESP32 EN 自检:回读为 1 才算成功(被外部钳低属硬件异常,标记失败但不阻塞其余模块) */
const bool esp32_released =
(gpio_input_bit_get(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN) == SET);
if (esp32_released) {
mark(kEsp32Hold);
} else {
setFault(kEsp32Hold, kFaultEsp32HoldClamped);
}
APP_LOGI("[APP] ESP32_EN PC9=%d (1=released)\r\n", esp32_released ? 1 : 0);
Usart0::configurePins();
const uint8_t usartFault = Usart0::init();
if (usartFault == Usart0::kFaultOk) {
mark(kUsart0);
} else {
setFault(kUsart0, usartFault);
}
/* SPI 尽早就绪:OTA FINISH 跳转后 ESP32 用 HELLO 探活,不能等 CAN 上电延时。
* 从机无硬件自证手段(配置即生效),探活依赖 ESP32 侧 HELLO/PING(运行期)。 */
AppSpiCommandBus.init();
mark(kAppSpi);
/* ADC:带超时校准 + VREFINT→VDDA 采样链验证 */
const uint8_t adcFault = Adc::init();
if (adcFault == Adc::kFaultOk) {
mark(kAdc);
} else {
setFault(kAdc, adcFault);
LOG_ERR("[APP] ADC init FAIL fault=%u (1=RSTCLB timeout 2=CLB timeout 3=VDDA invalid)\r\n",
(unsigned)adcFault);
}
CanFd::init();
/* L0 通过才置 canfd 位;故障码 = 原因 | (首个失败通道 << 4)。
* 无使能通道(canFault==Ok)属合法配置而非故障,照样置位。 */
uint8_t canFault = CanFd::kSelfTestOk;
uint8_t canFaultCh = 0U;
(void)CanFd::selfTestL0(&canFault, &canFaultCh);
if (canFault != CanFd::kSelfTestOk) {
setFault(kCanFd, (uint8_t)(canFault | (uint8_t)(canFaultCh << 4U)));
} else {
mark(kCanFd);
}
OtaBootRequest::init();
mark(kOtaBootReq);
/* 版本号写进 CoE 0x100A:唯一真源是 sys/config.h 的 APP_FW_VERSION_*,
* 不在别处再抄一遍字符串。 */
{
char ver[20];
snprintf(ver, sizeof ver, "%u.%u.%u",
(unsigned)APP_FW_VERSION_MAJOR, (unsigned)APP_FW_VERSION_MINOR,
(unsigned)APP_FW_VERSION_PATCH);
AppSetSoftwareVersion(ver);
}
TaskManager::init();
mark(kTaskManager);
/* 调度定时器:回读 + 100us 中断节拍前进验证 */
const uint8_t schedFault = SchedulerTimer::init();
if (schedFault == SchedulerTimer::kFaultOk) {
mark(kScheduler);
} else {
setFault(kScheduler, schedFault);
LOG_ERR("[APP] SchedulerTimer init FAIL fault=%u (1=CEN 2=PSC 3=CAR 4=no tick)\r\n",
(unsigned)schedFault);
}
/* ---- EtherCAT: EXMC 总线探活 → SSC 协议栈初始化 → SYNC0/IRQ 中断使能 ----
* 门控用 EscLan9252::isBusReady()(BYTE_TEST 通过)而非 isEscReady():
* 冷启动时 AL=INIT,SSC 必须照常启动并等待主站配置。
* 另需 isCoreReady()——EscLan9252::init() 已等待 EEPROM(SII)
* 加载完成(DL_STATUS bit0);未加载时 EtherCAT 核心寄存器写静默丢弃,
* SSC 的 AL Event Mask/邮箱 SM 配置会全部落空(主站恒读 AL=INIT),
* 此时跳过 SSC 并以独立故障码上报。
* EEPROM 校验/烧录延迟到主循环执行,避免阻塞 init(烧录需 ~20s)。 */
EthercatSlave::init();
if (EscLan9252::isBusReady() && EscLan9252::isCoreReady()) {
const uint16_t sscResult = MainInit();
if (sscResult == 0U) {
mark(kEthercat);
EthercatSync::init();
mark(kEthercatSync);
printf("[ECAT] SSC MainInit OK, sync ISR enabled\r\n");
} else {
setFault(kEthercat, kFaultEthercatSscFail);
printf("[ECAT] SSC MainInit FAIL (code=0x%04X)\r\n", (unsigned)sscResult);
}
} else if (EscLan9252::isBusReady()) {
setFault(kEthercat, kFaultEthercatCoreNotReady);
printf("[ECAT] EtherCAT core not ready (EEPROM not loaded), SSC skipped\r\n");
} else {
setFault(kEthercat, kFaultEthercatBusNotReady);
printf("[ECAT] EXMC bus not ready, SSC skipped\r\n");
}
/* 看门狗放最后:PSC/RLD 写入经硬件确认才置位。
* 崩溃计数不在这里清:须喂狗稳定满 CONFIG_CRASH_CLEAR_AFTER_SEC
* (task_runner 每圈 maybeClearCrashCount),否则主循环崩溃攒不到阈值。 */
const uint8_t wdgFault = Watchdog::init();
if (wdgFault == Watchdog::kFaultOk) {
mark(kWatchdog);
} else {
setFault(kWatchdog, wdgFault);
LOG_ERR("[APP] Watchdog init FAIL fault=%u (1=PSC 2=RLD 3=pending 4=LSI not ready)\r\n",
(unsigned)wdgFault);
}
report();
}