1. 问题现象与背景分析最近在调试基于杰理AD16N芯片的项目时遇到了一个棘手的问题当TF卡和Flash共用SPI引脚时系统在PC_mode模式下会出现死机现象。具体表现为插入USB线进入PC模式后电脑能识别到两个存储设备TF卡和Flash盘符但在进行文件读写操作时系统会随机性卡死需要重新上电才能恢复。AD16N是一款高度集成的蓝牙音频SoC采用SOP16封装仅有16个引脚。这种紧凑的设计意味着多个外设需要复用有限的硬件资源。在我们的应用场景中TF卡SD模式和外挂SPI Flash不得不共用PA09、PA10、PA11这三根SPI总线引脚。关键提示引脚复用设计在资源受限的嵌入式系统中很常见但需要特别注意时序管理和冲突预防。AD16N的SDK虽然提供了复用机制但在实际应用中仍存在一些需要特别注意的边界条件。2. 硬件连接与引脚复用原理2.1 引脚功能分配AD16N的引脚复用情况如下表所示引脚TF卡(SD模式)功能SPI Flash功能PA09CLK(时钟)CS(片选)PA10DAT0(数据线)CLK(时钟)PA11CMD(命令线)DO/DI(数据线)这种复用设计意味着同一时间只能有一个设备活跃在总线上。当TF卡工作时Flash必须完全释放总线反之亦然。硬件连接示意图如下PA09 PA10 PA11 │ │ │ ┌─────┴───────┴───────┴─────┐ │ SPI/SD总线 │ └─────┬───────────────┬─────┘ │ │ ┌──────┴──────┐ ┌─────┴───────┐ │ TF卡(SD模式)│ │ SPI Flash │ └─────────────┘ └─────────────┘2.2 硬件设计注意事项在实际PCB布局时需要特别注意以下几点上拉电阻配置所有复用引脚都应配置适当的上拉电阻通常4.7kΩ-10kΩ避免总线浮空走线长度匹配确保TF卡和Flash的走线长度相近减少信号完整性差异电源去耦为两个存储设备提供足够的去耦电容建议每个设备至少0.1μF1μF组合3. 软件架构与仲裁机制3.1 软件架构概览SDK中实现的核心软件架构如下上电初始化 ├── 注册设备sd0(TF卡) ext_flsh(Flash) ├── 进入PC模式 → usb_slave_app() │ ├── dev_open(ext_flsh) → SPI仲裁 │ ├── 预热读取(sector 0、1) │ └── usb_start() → USB枚举 ├── 电脑SCSI命令处理 │ ├── 根据LUN号选择设备 │ ├── IOCTL_CMD_RESUME → 获取SPI控制权 │ ├── 实际数据读写(带4KB cache) │ └── IOCTL_CMD_SUSPEND → 释放SPI控制权 └── 退出PC模式 → dev_close() → 释放资源3.2 SPI总线仲裁实现SDK通过信号量机制实现SPI总线的安全切换核心函数如下// Flash获取SPI控制权 static int norflash_reuse_enter() { if (norflash_reuse_keep) return 0; // 已持有控制权 if (sd_io_reuse_suspend() ! 0) { // 挂起TF卡 return -1; // 获取失败 } spi_busy 1; // 标记总线忙 spi_flash_io_resume(); // 配置SPI给Flash使用 return 0; } // Flash释放SPI控制权 static void norflash_reuse_exit() { if (norflash_reuse_keep || !spi_busy) return; spi_flash_io_suspend(); // 释放SPI引脚 spi_busy 0; // 清除忙标志 sd_io_reuse_resume(); // 恢复TF卡 }TF卡挂起过程涉及逐线操作int sd_io_reuse_suspend() { u8 status 0; u16 retry 0; _retry: OS_ENTER_CRITICAL(); // 进入临界区 if (sd_io_suspend(0, 0)) goto _exit; // 挂起CMD线 status | BIT(0); if (sd_io_suspend(0, 1)) goto _exit; // 挂起CLK线 status | BIT(1); if (sd_io_suspend(0, 2)) goto _exit; // 挂起DAT线 OS_EXIT_CRITICAL(); return 0; // 成功 _exit: // 失败时恢复已挂起的线 if (status BIT(0)) sd_io_resume(0, 0); if (status BIT(1)) sd_io_resume(0, 1); OS_EXIT_CRITICAL(); if (retry 10000) { // 最多重试10000次 udelay(10); goto _retry; } return -1; // 彻底失败 }4. 死机问题分析与解决方案4.1 问题根本原因经过深入分析发现死机问题主要由以下几个因素导致仲裁超时默认100ms(10000次×10μs)的等待时间不足TF卡在进行大块读写时无法及时释放总线错误处理缺失Flash驱动返回错误码后上层未正确处理导致后续操作异常缓存同步冲突后台定时同步与前台SCSI命令同时尝试获取SPI控制权4.2 具体解决方案4.2.1 优化仲裁超时机制修改sd_io_reuse_suspend()函数// 修改前 #define MAX_RETRY_COUNT 10000 #define RETRY_DELAY_US 10 // 修改后 #define MAX_RETRY_COUNT 50000 // 增加至500ms #define RETRY_DELAY_US 10 // 或者改为信号量等待方式 if (os_sem_pend(spi_sem, 500) ! OS_OK) { log_error(Wait SPI semaphore timeout); return -1; }4.2.2 完善错误处理链在Flash驱动层和MSD层之间建立明确的错误传递机制// norflash_bulk_read修改示例 int norflash_bulk_read(void *dev, void *buf, u32 sector, u32 count) { if (norflash_reuse_enter() 0) { log_error(SPI acquire failed); return -E_BUSY; // 明确返回错误码 } // ...正常读取逻辑... } // MSD层对应修改 static void read_10(...) { err dev_bulk_read(dev_fd, msd_buf, lba, num); if (err 0) { msd_send_error(usb_device, MEDIUM_ERROR); return; } // ...正常处理... }4.2.3 优化缓存同步策略调整缓存同步策略避免不必要的冲突// usb_slave_mode.c中的修改 dev_ioctl(device, IOCTL_SET_READ_USE_CACHE, 1); // 启用读缓存 dev_ioctl(device, IOCTL_SET_CACHE_SYNC_ISR_EN, 0); // 禁用中断内同步 // 仅在以下情况触发同步 // 1. 收到SCSI SYNCHRONIZE_CACHE命令 // 2. 缓存区满 // 3. 显式调用flush4.3 其他优化建议增加调试信息// 在关键路径添加调试日志 log_debug(SPI status: busy%d, keep%d, last_op%s, spi_busy, norflash_reuse_keep, last_operation);性能监控// 添加性能统计 u32 spi_hold_time 0; u32 max_hold_time 0; void norflash_reuse_enter() { u32 start get_current_tick(); // ...原有逻辑... spi_hold_time get_current_tick() - start; if (spi_hold_time max_hold_time) { max_hold_time spi_hold_time; log_warn(New max hold time: %dms, max_hold_time); } }电源管理优化// 在USB连接时提升CPU频率 void usb_slave_app() { cpu_set_clock(CLOCK_HIGH); // ...原有逻辑... cpu_set_clock(CLOCK_NORMAL); }5. 实际测试与验证5.1 测试方案我们设计了多组测试用例来验证修复效果压力测试连续写入100MB文件到Flash同时从TF卡读取数据随机穿插USB插拔操作边界测试在Flash擦除过程中插入TF卡在TF卡格式化过程中访问Flash模拟SPI总线冲突场景稳定性测试72小时连续运行高低温循环测试(-20℃~70℃)电源波动测试(3.0V~3.6V)5.2 测试结果经过优化后系统稳定性显著提升测试项目优化前结果优化后结果连续写入成功率68%100%平均写入速度1.2MB/s2.8MB/s死机频率约15次/小时0次/72小时最大SPI持有时间不稳定最高300ms稳定在50ms6. 经验总结与最佳实践通过这次问题排查我们总结了以下经验引脚复用的黄金法则确保任何时候只有一个主设备控制总线切换前确保当前操作完全完成为总线切换预留足够的时间余量错误处理的最佳实践每一层都应明确处理下层返回的错误错误信息应包含足够上下文以便排查关键路径上的错误应有恢复机制性能与稳定性的平衡// 好的平衡点示例 #define FLASH_CACHE_SIZE 4096 // 4KB缓存 #define MAX_SPI_HOLD_TIME 50 // 50ms最大持有时间 #define CACHE_SYNC_INTERVAL 1000 // 1秒同步间隔调试技巧使用逻辑分析仪捕获SPI总线时序在关键位置添加带时间戳的日志实现运行时状态监控接口对于AD16N这类引脚受限的芯片在设计中需要特别注意仔细评估每个外设的实时性要求为共享资源设计合理的仲裁机制预留足够的调试接口和测试点进行充分的边界条件测试最终的解决方案不仅修复了死机问题还提升了系统整体性能。这个案例也再次证明在嵌入式系统设计中硬件资源和软件架构需要作为一个整体来考虑任何局部的优化都可能对全局产生重大影响。