1. 项目概述与FSI核心价值
在工业电机驱动、数字电源或者多轴运动控制这类实时性要求极高的嵌入式系统里,处理器之间的通信链路就像是整个系统的“神经”。这条“神经”不仅要跑得快,更要稳如磐石,不能有丝毫的延迟或数据错乱。传统的SPI、I2C虽然通用,但在面对多节点、长距离、强干扰的复杂工业现场时,有时会显得力不从心——时钟同步要求苛刻、软件开销大、容错机制弱等问题会逐渐暴露。
这时,像TI C2000系列TMS320F28003x这类实时微控制器(MCU)内置的FSI(Fast Serial Interface)模块,其价值就凸显出来了。我这些年做伺服驱动器开发,深有体会:主控DSP和多个从属的智能功率模块(IPM)或者传感器之间,通信的可靠性和实时性直接决定了系统的性能上限和稳定性下限。FSI并非一个全新的协议,你可以把它理解为一个“强化版”的同步串行接口。它在硬件层面集成了帧结构封装、自动CRC校验、硬件看门狗(Ping/Frame Watchdog)以及灵活的数据触发机制。这意味着,一旦链路建立,数据的完整性和时效性由硬件保障,CPU可以更专注于核心控制算法,而不是疲于处理通信的细枝末节。
简单来说,FSI解决的核心痛点就是:在恶劣的电气环境下,实现确定性的、高可靠的、低软件开销的芯片间数据交换。它特别适合用于构建主从式或菊花链式的多设备系统,例如一个主控MCU连接多个从属的ADC采样芯片、栅极驱动器或者另一个协处理器MCU的场景。
本文将以TMS320F28003x的FSI模块为例,抛开官方手册的平铺直叙,结合我实际调试中的踩坑经验,深入剖析通信链路建立的完整流程与关键寄存器的配置心法。我会重点讲清楚两个部分:一是主从设备间如何通过“握手”(Ping帧交换)建立起可靠的物理链路;二是那些带保护机制的寄存器(如TX_MASTER_CTRL)到底该怎么安全、正确地配置。无论你是刚开始接触C2000的新手,还是想优化现有通信架构的老手,相信这些从实战中总结的细节都能让你少走弯路。
2. FSI通信链路建立全流程拆解
建立FSI通信链路,本质上是一个主从设备间相互确认、同步状态的过程。官方手册给出了标准的初始化序列,但直接照搬常常会遇到链路无法建立、数据收不到等诡异问题。下面我结合代码和时序图,把每一步背后的“为什么”和“怎么做”掰开揉碎讲清楚。
2.1 主设备主动建链流程详解
主设备(Master)在通信中通常扮演发起者和调度者的角色。它的建链流程是一个“主动探测-等待回应-确认同步”的过程。
第一步:模块复位与基础配置这是所有操作的基石,绝对不能出错。上电或初始化时,必须先将FSI发送(TX)和接收(RX)核心置于复位状态,然后再解除复位。这确保了所有内部状态机、计数器和缓冲区都从一个已知的、干净的状态开始。
// 假设使用TI的DriverLib库,以下为示例代码 #include “driverlib.h” void FSI_Master_Init(void) { // 1. 断言核心复位 FSITX_disableModule(FSITXA_BASE); // 内部会设置CORE_RST位 FSIRX_disableModule(FSIRXA_BASE); // 2. 解除复位,并配置基本操作参数 FSITX_enableModule(FSITXA_BASE); FSIRX_enableModule(FSIRXA_BASE); // 配置发射器:选择时钟源、分频、数据宽度(单/双通道)、帧类型等 FSITX_setClockSource(FSITXA_BASE, FSITX_CLOCK_SOURCE_SYS_CLK); FSITX_setPrescalerValue(FSITXA_BASE, 99); // 假设系统时钟100MHz,目标FSI时钟~1MHz FSITX_setDataWidth(FSITXA_BASE, FSITX_DATA_WIDTH_2_LANE); // 使用双通道提高数据率 FSITX_setFrameType(FSITXA_BASE, FSITX_FRAME_TYPE_DATA_N_WORD); // 配置接收器:匹配发射器的数据宽度、使能中断等 FSIRX_setDataWidth(FSIRXA_BASE, FSIRX_DATA_WIDTH_2_LANE); FSIRX_enableInterrupt(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); // 使能帧接收中断 }关键点:
FSITX_setPrescalerValue这个分频值需要仔细计算。FSI的最终传输时钟TXCLK = (Input Clock) / (PRESCALE_VAL) / 2(FSI模式)。如果你的系统时钟是100MHz,PRESCALE_VAL设为99,那么TXCLKIN = 100MHz / (99+1) = 1MHz,最终的TXCLK = 1MHz / 2 = 500kHz。这个频率需要与接收端严格匹配,并且要考虑PCB走线的长度和信号完整性。
第二步:Ping循环握手——链路建立的灵魂这是建立链路的核心握手协议。主设备通过发送特定的Ping帧(一种特殊的、短小的控制帧)来探测从设备是否存在并准备就绪。
bool FSI_Master_EstablishLink(void) { uint16_t timeout = 0; const uint16_t MAX_PING_ATTEMPTS = 10; // 最大尝试次数,防止死循环 bool link_established = false; // 3. 设置接收中断,用于检测来自从设备的Ping回应 // (通常在上一步的FSIRX_enableInterrupt中已完成) // 4. 开始Ping循环 for (int attempt = 0; attempt < MAX_PING_ATTEMPTS; attempt++) { // a. 发送Flush序列 FSITX_sendFlushSequence(FSITXA_BASE); // 这会置位TX_MASTER_CTRL.FLUSH位 // 重要:必须等待Flush序列完成,手册要求至少5个TXCLK周期 DEVICE_DELAY_US(10); // 简单延时,更可靠的做法是查询状态或使用定时器 // b. 发送一个标签为0000的Ping帧 FSITX_setFrameTag(FSITXA_BASE, 0x0); // 设置帧标签 FSITX_startFrameTransmission(FSITXA_BASE, FSITX_FRAME_TYPE_PING); // c. 等待一段时间(由应用决定),让从设备有机会回复 // 这个等待时间必须大于从设备的处理时间+信号传播时间 DEVICE_DELAY_US(100); // 示例:等待100us // d. 检查接收器是否收到了有效的Ping帧 if (FSIRX_getInterruptStatus(FSIRXA_BASE) & FSIRX_INT_FRAME_RECEIVED) { FSIRX_clearInterruptStatus(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); // e. 检查收到的Ping帧标签是否为0001 uint16_t received_tag = FSIRX_getReceivedFrameTag(FSIRXA_BASE); if (received_tag == 0x1) { // 收到正确的应答!跳出循环 link_established = true; break; } else { // 收到帧,但标签不对,可能是干扰或从设备状态异常,继续循环 continue; } } // 如果超时未收到任何回复,直接进行下一次循环尝试 } if (link_established) { // 5. 发送标签为0001的Ping帧(确认帧) FSITX_setFrameTag(FSITXA_BASE, 0x1); FSITX_startFrameTransmission(FSITXA_BASE, FSITX_FRAME_TYPE_PING); DEBUG_PRINT(“Master: Communication link established successfully.\n”); } else { DEBUG_PRINT(“Master: Failed to establish link after %d attempts.\n”, MAX_PING_ATTEMPTS); } return link_established; }避坑指南:
- Flush序列的等待时间:手册说“wait for five TXCLK cycles”。在代码中直接用
DEVICE_DELAY_US()是一个简单方法,但不精确。更可靠的做法是在发送Flush后,用一个短循环查询某个状态位(如果有),或者根据配置的TXCLK频率计算精确的微秒数进行延时。TXCLK=500kHz时,一个周期是2us,5个周期就是10us。- Ping超时与重试机制:循环中的等待时间(示例中的100us)和最大尝试次数(
MAX_PING_ATTEMPTS)需要根据实际系统调整。在长线缆或高噪声环境中��这个时间可能需要加长,尝试次数也需要增加。- 中断处理:确保FSI接收中断服务程序(ISR)被正确安装和使能,并且能够快速响应、清除标志位。中断处理延迟过长可能导致主循环误判为未收到响应。
2.2 从设备被动建链流程详解
从设备(Slave)的流程是一个“等待-验证-回应”的被动过程。它需要持续监听线路,并对主设备的探测做出正确响应。
bool FSI_Slave_EstablishLink(void) { bool link_established = false; uint16_t received_tag; // 1. & 2. 复位与基础配置(与主设备类似,确保参数匹配) FSIRX_disableModule(FSIRXA_BASE); FSITX_disableModule(FSITXA_BASE); FSIRX_enableModule(FSIRXA_BASE); FSITX_enableModule(FSITXA_BASE); // 注意:从设备的时钟分频、数据宽度等必须与主设备严格一致! FSIRX_setDataWidth(FSIRXA_BASE, FSIRX_DATA_WIDTH_2_LANE); FSITX_setDataWidth(FSITXA_BASE, FSITX_DATA_WIDTH_2_LANE); FSIRX_enableInterrupt(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); // 3. 设置接收中断(已完成) // 4. 等待接收中断(进入一个监听循环) while(!link_established) { // 5. 检查是否收到有效的Ping帧 if (FSIRX_getInterruptStatus(FSIRXA_BASE) & FSIRX_INT_FRAME_RECEIVED) { FSIRX_clearInterruptStatus(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); // 6. 检查收到的帧标签是否为0000 received_tag = FSIRX_getReceivedFrameTag(FSIRXA_BASE); if (received_tag == 0x0) { // 收到主设备的首次探测! // 7. 发送Flush序列(清空自己的发送线路,准备回应) FSITX_sendFlushSequence(FSITXA_BASE); DEVICE_DELAY_US(10); // 等待Flush完成 // 8. 发送一个标签为0001的Ping帧作为应答 FSITX_setFrameTag(FSITXA_BASE, 0x1); FSITX_startFrameTransmission(FSITXA_BASE, FSITX_FRAME_TYPE_PING); // 9. 再次等待接收中断,期待主设备的确认帧(标签0001) // 这里需要添加一个超时机制,防止无限等待 uint32_t wait_timeout = 0; while(wait_timeout < 100000) { // 简单计数超时 if (FSIRX_getInterruptStatus(FSIRXA_BASE) & FSIRX_INT_FRAME_RECEIVED) { FSIRX_clearInterruptStatus(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); received_tag = FSIRX_getReceivedFrameTag(FSIRXA_BASE); // 10. & 11. 检查收到的确认帧标签 if (received_tag == 0x1) { link_established = true; DEBUG_PRINT(“Slave: Link established with master.\n”); break; // 跳出内层等待循环 } else if (received_tag == 0x0) { // 手册提到的情况:可能在步骤8的回应发出前,主设备第二个Ping帧已在路上 // 忽略它,继续等待标签为0001的确认帧 continue; } } wait_timeout++; } if (!link_established) { DEBUG_PRINT(“Slave: Timeout waiting for master confirmation.\n”); // 可以选择返回失败,或重新开始监听 return false; } } else { // 收到非0000标签的帧,丢弃(可能是噪声或其它设备) continue; } } // 主循环中可能还需要处理其他任务或进入低功耗模式 } return link_established; }核心难点与对策:
- “标签0000帧已在中途”的竞态条件:这是手册特别指出的一个关键场景。当从设备收到第一个标签0000的Ping并开始回应时,主设备可能已经发出了第二个标签0000的Ping(因为主设备在等待超时后会重试)。从设备的代码(步骤11)必须能处理这种情况:如果收到第二个0000标签,它应该丢弃并继续等待真正的确认帧(标签0001),而不是错误地重置状态机。
- 严格的参数同步:从设备的
TX_CLK_CTRL.PRESCALE_VAL、TX_OPER_CTRL_LO.DATA_WIDTH等所有通信参数,必须与主设备完全一致。任何细微差别都会导致时钟或数据相位错位,通信必然失败。最好的实践是在软件中用相同的配置函数和宏定义。- 中断与轮询的权衡:上述示例使用了轮询检查中断标志位。在实际系统中,更高效的做法是真正启用中断,在中断服务程序(ISR)中设置标志位,主循环检查这些标志位。这能解放CPU,尤其在从设备还需要执行其他任务时。
2.3 链路建立后的常规通信
一旦握手成功,双方都确认了对方的存在和同步状态,TX_EVT_STS.FRAME_DONE和RX_EVT_STS.FRAME_RECEIVED这类标志位会正常运作。此时,应用程序可以:
- 配置周期性Ping帧:通过设置
TX_PING_CTRL.TIMER_EN和TX_PING_TO_REF,让硬件自动定时发送Ping帧,作为链路保持活跃(Keep-Alive)的心跳信号。 - 初始化看门狗:使能接收端的Ping看门狗和帧看门狗。如果超过预定时间未收到任何帧或Ping帧,看门狗超时事件会触发中断,通知应用程序链路可能已断开。
- 开始应用数据通信:通过配置
TX_FRAME_CTRL(设置帧类型、数据字数)和TX_BUF_BASE_y(指向数据缓冲区),启动正式的数据帧传输。数据传输可以由软件写寄存器触发,也可以由外部事件(如ePWM、CPU定时器)或DMA自动触发。
3. 关键寄存器配置深度解析与实战技巧
FSI的寄存器不少都带有写保护,配置不当轻则配置不生效,重则导致难以排查的通信故障。下面我挑几个最核心、最容易踩坑的寄存器,结合二进制操作和DriverLib函数,讲透它们的配置方法。
3.1 受保护的寄存器与安全写入机制
FSI模块的稳定性很大程度上得益于其严格的寄存器保护机制,主要包括三类:
1. EALLOW保护这是C2000芯片全局的机制。像TX_MASTER_CTRL、TX_CLK_CTRL、TX_OPER_CTRL_LO/HI等关键控制寄存器都受EALLOW保护。在修改它们之前,必须解除“写保护锁”。
// 正确做法:成对使用EALLOW和EDIS EALLOW; // 解除保护 FSITX_setClockSource(FSITXA_BASE, FSITX_CLOCK_SOURCE_SYS_CLK); FSITX_setPrescalerValue(FSITXA_BASE, 99); EDIS; // 重新上锁 // 错误做法:忘记EDIS,可能导致后续对其他受保护寄存器的意外写入 EALLOW; SysCtl_setClock(…); // 配置系统时钟 // 忘记了 EDIS; // … 后续代码如果意外修改了受EALLOW保护的FSI寄存器,可能不会报错但行为异常经验之谈:我习惯在配置某个外设模块的初始化函数开头
EALLOW,在函数末尾EDIS。并且用#ifdef DEBUG包裹,在调试阶段加入检查,确保没有嵌套的EALLOW/EDIS错配。
2. 寄存器密钥(KEY)保护这是FSI特有的、更细粒度的保护。例如,对TX_MASTER_CTRL寄存器的任何写操作,都必须同时向其高字节(KEY字段)写入特定的密钥0xA5。
// 直接操作寄存器的方式: // 假设我们要启动Flush序列并解除核心复位 uint16_t regValue = 0xA500; // 高字节=0xA5 (KEY), 低字节=0x00 regValue |= (1 << 1); // 置位FLUSH位 (bit1) regValue |= (0 << 0); // 清除CORE_RST位 (bit0), 0表示解除复位 HWREGH(FSITXA_BASE + FSITX_O_TX_MASTER_CTRL) = regValue; // 一次性写入 // 使用DriverLib库函数(库函数内部已经处理了KEY): FSITX_enableModule(FSITXA_BASE); // 这个函数会写CORE_RST=0,并自动处理KEY FSITX_sendFlushSequence(FSITXA_BASE); // 这个函数会写FLUSH=1,并自动处理KEY关键点:密钥必须和要设置的值在同一个16位写操作中完成。不能先写KEY,再写控制位。每次写操作都需要携带KEY。
3. 控制寄存器锁(LOCK)这是最终的安全锁,位于TX_LOCK_CTRL和RX_LOCK_CTRL寄存器。一旦设置(写入0xA501),所有受锁保护的寄存器(参考手册列表)将无法被修改,直到发生系统复位(SYSRSn)或对外设的软复位。这个锁通常用于产品最终发布时,防止应用程序跑飞后意外修改关键通信参数,导致系统死锁。在开发和调试阶段,一般不去动它。
3.2 核心功能寄存器配置示例
我们以一个常见的点对点(P2P)全双工通信场景为例,看看如何配置一套完整的参数。
场景设定:主从设备间需要传输16个16位字(即32字节)的数据块,使用双通道模式以提高吞吐量,FSI通信时钟目标为1MHz,使用系统时钟(100MHz)作为源,由硬件Ping定时器每10ms发送一次心跳。
void FSI_Complete_Config(void) { // ===== 第一部分:全局与时钟配置 ===== EALLOW; // 1. 配置TX_MASTER_CTRL:解除核心复位 // DriverLib: FSITX_enableModule 内部已实现 FSITX_enableModule(FSITXA_BASE); // 2. 配置TX_CLK_CTRL:设置时钟分频 // 目标TXCLK = 1MHz。公式:TXCLK = SYSCLK / (PRESCALE_VAL+1) / 2 // 代入:1MHz = 100MHz / (PRESCALE_VAL+1) / 2 => PRESCALE_VAL = (100 / (1*2)) -1 = 49 FSITX_setPrescalerValue(FSITXA_BASE, 49); // PRESCALE_VAL = 49 FSITX_setClockSource(FSITXA_BASE, FSITX_CLOCK_SOURCE_SYS_CLK); // 注意:CLK_EN和CLK_RST通常在enableModule中处理,这里无需单独设置 // ===== 第二部分:操作模式控制 ===== // 3. 配置TX_OPER_CTRL_LO FSITX_setDataWidth(FSITXA_BASE, FSITX_DATA_WIDTH_2_LANE); // DATA_WIDTH = 1 (双通道) FSITX_disableSPIMode(FSITXA_BASE); // SPI_MODE = 0 (FSI模式) FSITX_setStartMode(FSITXA_BASE, FSITX_START_MODE_SOFTWARE); // START_MODE = 0 (软件启动) FSITX_setCRCSelect(FSITXA_BASE, FSITX_CRC_SELECT_HARDWARE); // SW_CRC = 0 (硬件CRC) // PING_TO_MODE, SEL_PLLCLK等根据需求保持默认或设置 // 4. 配置TX_OPER_CTRL_HI (如果需要外部触发) // FSITX_setExternalTriggerSource(FSITXA_BASE, 5); // 例如选择触发源5 // 本例使用软件触发,暂不配置。 // ===== 第三部分:帧结构配置 ===== // 5. 配置TX_FRAME_CTRL (在每次发送前动态配置) // 对于DATA_N_WORD类型,N_WORDS字段 = 字数 - 1。16个字则设置为15 (0xF)。 // 这个通常在发送函数中设置,例如: // FSITX_setFrameType(FSITXA_BASE, FSITX_FRAME_TYPE_DATA_N_WORD); // FSITX_setNumOfWords(FSITXA_BASE, 15); // 传输16个字 // 6. 配置TX_FRAME_TAG_UDATA (用户数据和标签) FSITX_setUserData(FSITXA_BASE, 0xAA); // 设置用户数据,可用于标识数据源 // 帧标签通常在每次发送前根据协议设置,例如:FSITX_setFrameTag(FSITXA_BASE, 2); // ===== 第四部分:Ping与看门狗配置 ===== // 7. 配置TX_PING_CTRL, TX_PING_TAG, TX_PING_TO_REF FSITX_setPingTimerEnable(FSITXA_BASE, true); // 使能硬件Ping定时器 FSITX_setPingTag(FSITXA_BASE, 0x0); // 设置硬件自动发送的Ping帧标签,例如0 // 设置Ping超时参考值。假设SYSCLK=100MHz,PRESCALE_VAL=49,TXCLKIN=2MHz。 // Ping定时器以SYSCLK计数。要每10ms发一次Ping: // 计数值 = 10ms * 100MHz = 1,000,000 FSITX_setPingTimeoutRef(FSITXA_BASE, 1000000UL); // ===== 第五部分:中断与DMA事件配置 ===== // 8. 配置TX_INT_CTRL FSITX_enableInterrupt(FSITXA_BASE, FSITX_INT_FRAME_DONE); // 使能帧发送完成中断 FSITX_enableInterrupt(FSITXA_BASE, FSITX_INT_PING_TIMEOUT); // 使能Ping超时中断(可选,用于监控) // 缓冲区溢出/下溢中断对于调试很有用,生产环境可关闭以降低中断负载 FSITX_disableInterrupt(FSITXA_BASE, FSITX_INT_BUF_OVERRUN); FSITX_disableInterrupt(FSITXA_BASE, FSITX_INT_BUF_UNDERRUN); // 9. 配置TX_DMA_CTRL (如果使用DMA) // FSITX_enableDMAEvent(FSITXA_BASE); // 使能DMA事件,每完成一帧数据触发一次DMA // ===== 第六部分:数据缓冲区配置 ===== // 10. 配置TX_BUF_BASE_y (y=0~15) // 将16个缓冲区基地址指向全局或静态数组 extern uint16_t fsi_tx_buffer[16]; // 假设是16个16位字的数组 for (int i = 0; i < 16; i++) { FSITX_setBufferBaseAddress(FSITXA_BASE, i, (uint32_t)&fsi_tx_buffer[i]); } EDIS; // 结束EALLOW保护区域 // ===== 第七部分:接收端对称配置 ===== // 接收端的配置必须与发送端严格匹配:时钟分频、数据宽度、帧类型(N_WORDS)等。 // 使能所需的中断(帧接收、看门狗超时等)。 FSIRX_enableModule(FSIRXA_BASE); FSIRX_setDataWidth(FSIRXA_BASE, FSIRX_DATA_WIDTH_2_LANE); FSIRX_setNumOfWords(FSIRXA_BASE, 15); // 必须与发送端N_WORDS一致! FSIRX_enableInterrupt(FSIRXA_BASE, FSIRX_INT_FRAME_RECEIVED); // 配置RX看门狗超时参考值(如果需要) // FSIRX_setPingTimeoutRef(FSIRXA_BASE, 1500000UL); // 比发送间隔稍长 // FSIRX_enablePingWatchdog(FSIRXA_BASE); }3.3 配置参数计算与选择依据
PRESCALE_VAL计算:这是最容易算错的地方。公式必须记牢:
TXCLK = Input_Clock / (PRESCALE_VAL + 1) / 2。Input_Clock是TX_CLK_CTRL.SEL_PLLCLK选择的源时钟(SYSCLK或PLLRAWCLK)。PRESCALE_VAL是一个8位值,范围1-255(写0是保留的)。计算时,先根据需要的TXCLK反推出TXCLKIN = TXCLK * 2,再计算PRESCALE_VAL = (Input_Clock / TXCLKIN) - 1。务必确保计算结果为整数,否则实际时钟会有偏差。N_WORDS与缓冲区:
TX_FRAME_CTRL.N_WORDS定义的是“字数-1”。对于16位宽的数据,一个字就是16位。如果你要发送8个16位数据,N_WORDS应设置为7。对应的,TX_BUF_BASE_y需要配置至少8个缓冲区地址(y=0~7)。数据会从BASE_ADDRESS_0指向的内存开始,按顺序发送。Ping超时值(TX_PING_TO_REF):这个32位寄存器决定了硬件自动发送Ping帧的间隔。它以模块输入时钟(
TXCLKIN)为计数基准,而不是TXCLK。例如,SYSCLK=100MHz,PRESCALE_VAL=49,则TXCLKIN = 100MHz / (49+1) = 2MHz。如果要每20ms发一次Ping,则TO_REF = 20ms * 2MHz = 40000。设置过小会增加总线开销,设置过大则链路中断检测不灵敏。
4. 典型问题排查与调试心得
即使按照手册一步步配置,在实际硬件上调试FSI也难免遇到问题。下面是我总结的几个常见故障现象和排查思路。
4.1 链路无法建立(Ping无响应)
这是最常见的问题。排查可以按照信号流的方向进行:
物理层检查:
- 引脚映射:首先确认
FSITX_CLK/TX0/TX1和FSIRX_CLK/RX0/RX1是否正确映射到了具体的GPIO引脚(通过GPIO复用寄存器配置)。一个低级错误是TX和RX的引脚交叉接错或者时钟线没接。 - 电平与波形:用示波器测量
TXCLK和TXD0/TXD1引脚。在发送Flush序列或Ping帧时,应该能看到规整的方波。检查时钟频率是否符合预期,数据线是否有活动。 - 终端匹配:如果通信距离较长(超过十几厘米),可能需要考虑在传输线末端添加适当的端接电阻,以减少信号反射。
- 引脚映射:首先确认
配置一致性检查:
- 时钟分频:这是重中之重。用示波器实测主从双方的
TXCLK频率,必须一致。检查双方PRESCALE_VAL和SEL_PLLCLK的设置。 - 数据宽度:主设备设了双通道 (
DATA_WIDTH=1),从设备也必须设双通道。单双通道不匹配会导致数据完全错位。 - 工作模式:确保双方都在FSI模式 (
SPI_MODE=0),而不是一个FSI一个SPI模式。
- 时钟分频:这是重中之重。用示波器实测主从双方的
软件逻辑检查:
- 中断是否使能并响应:在从设备的中断服务函数入口加一个GPIO翻转,用示波器看是否有脉冲,确认中断确实被触发。
- 状态寄存器查询:在关键步骤后,读取
TX_EVT_STS和RX_EVT_STS寄存器。例如,主设备发送Ping后,是否置起了FRAME_DONE?从设备是否置起了FRAME_RECEIVED或PING_RECEIVED? - Flush序列等待时间:如前所述,确保在设置
FLUSH位后,等待了足够的时间(>5个TXCLK周期)再清除它。可以适当增加这个延时进行测试。
4.2 数据帧传输错误(CRC错误、数据错乱)
链路建立后,传数据出错。
缓冲区管理:
- 指针错位:
TX_BUF_PTR_STS.CURR_BUF_PTR和CURR_WORD_CNT可以帮你诊断。在非传输期间,CURR_WORD_CNT应等于你配置的缓冲区剩余字数。如果指针乱了,可能是缓冲区溢出/下溢,或者在传输过程中错误地写了TX_BUF_PTR_LOAD。 - DMA同步:如果使用DMA向FSI缓冲区搬运数据,务必确保DMA传输完成在FSI帧传输开始之前。否则FSI可能发送出错误或旧的数据。可以利用FSI的
FRAME_DONE中断或DMA完成中断来同步。
- 指针错位:
CRC错误:
- 如果使用硬件CRC (
SW_CRC=0),通常很可靠。如果频繁出错,首先怀疑物理链路噪声或时钟抖动。 - 如果使用软件CRC (
SW_CRC=1),需要确保在每次发送前,正确计算并写入TX_USER_CRC寄存器。计算算法必须与接收端校验算法一致。
- 如果使用硬件CRC (
时序问题:
- 外部触发抖动:如果使用ePWM等外部触发,确保触发信号稳定,且脉宽满足FSI模块的要求。
- CPU负载:在高优先级中断频繁打断的系统中,如果FSI的中断优先级设置过低,可能导致数据搬运不及时,引发缓冲区下溢。适当调整中断优先级。
4.3 调试工具与技巧
- 寄存器查看:熟练使用CCS的寄存器查看窗口,实时监控
TX_EVT_STS、RX_EVT_STS、TX_BUF_PTR_STS等关键状态寄存器。这是定位问题的第一手资料。 - GPIO辅助调试:在代码关键位置(如进入中断、开始发送、收到特定标签)控制一个空闲的GPIO引脚输出高/低电平。用逻辑分析仪或示波器同时抓取这个GPIO和FSI信号,可以非常直观地看到软件执行流程与硬件信号之间的时序关系。
- 利用
TX_EVT_FRC/RX_EVT_FRC:这两个“强制事件”寄存器是强大的调试工具。你可以在不实际发送/接收帧的情况下,手动置起FRAME_DONE或FRAME_RECEIVED标志,来测试你的中断服务程序是否能正确响应。这有助于将硬件问题和软件逻辑问题分离开。 - 从简单开始:先让通信跑起来,再考虑优化。最初的测试可以:
- 使用内部回环模式(如果硬件支持),排除外部硬件问题。
- 使用单通道模式,简化信号。
- 使用最小的数据长度(如1个字)。
- 先使用软件触发,避免外部触发源的复杂性。
- 暂时关闭所有中断,用轮询方式检查状态标志,确保基本的数据流是通的。
5. 不同应用场景下的配置策略
FSI的灵活性体现在它能适应多种拓扑和触发方式。下面结合官方示例代码,谈谈不同场景下的配置要点。
5.1 点对点(P2P)全双工通信
这就是前面详细讨论的标准模式。关键在于主从设备参数的绝对对称。除了之前提到的,还有:
- 看门狗策略:主设备启用硬件Ping定时器 (
TX_PING_CTRL.TIMER_EN) 作为心跳。从设备启用Ping看门狗 (RX_WD_CTRL.PING_WD_EN) 和帧看门狗 (RX_WD_CTRL.FRAME_WD_EN),并设置合理的超时值(应略大于主设备Ping间隔)。这样,任何一端的故障都能被另一端检测到。 - 中断分配:将
FRAME_DONE(发送完成)和FRAME_RECEIVED(接收完成)中断分配到不同的CPU中断线(INT1和INT2),或者合理设置优先级,避免高频率数据通信时中断相互阻塞。
5.2 菊花链(Daisy-Chain)拓扑
在菊花链中,每个设备既是上一个设备的接收方,又是下一个设备的发送方。数据像接力棒一样传递。
- 核心挑战:数据转发延迟。设备收到数据后,需要时间处理并转发给下一级。这个延迟必须小于FSI的帧间隔或看门狗超时时间,否则链路会断裂。
- 配置要点:
- 使用DMA:这是强烈推荐的方式。配置FSI RX事件触发DMA,将接收缓冲区数据直接搬运到FSI TX的发送缓冲区。这几乎消除了CPU干预带来的延迟。参考示例
fsi_ex16_daisy_handshake_node.c中的DMA配置部分。 - 优化缓冲区:确保DMA的源/目标地址包装(Wrap)大小和突发(Burst)大小与FSI配置的“软件帧大小”(Software Frame Size)匹配。
- 链头设备:作为发起者,其配置与P2P主设备类似。
- 链中设备:需要同时初始化和使能FSI的接收和发送模块。其握手流程更复杂,需要正确处理“透传”的Ping帧。示例代码
fsi_ex16_daisy_handshake_node.c展示了节点设备如何参与握手并转发数据。
- 使用DMA:这是强烈推荐的方式。配置FSI RX事件触发DMA,将接收缓冲区数据直接搬运到FSI TX的发送缓冲区。这几乎消除了CPU干预带来的延迟。参考示例
5.3 与SPI设备通信(兼容模式)
FSI可以模拟SPI主设备或从设备,与标准SPI器件通信。这是FSI一个非常实用的功能。
- 模式设置:将
TX_OPER_CTRL_LO.SPI_MODE置1。 - 时钟变化:在SPI模式下,
TXCLK = TXCLKIN,不再除以2。计算PRESCALE_VAL时要注意。 - 信号映射:
- FSI作为SPI主设备(TX):
FSITX_CLK连接SPICLK,FSITX_TX0连接SPISIMO(主机输出),FSITX_TX1可能不用或连接片选(根据SPI模式)。 - FSI作为SPI从设备(RX):
FSIRX_CLK连接SPICLK,FSIRX_RX0连接SPISOMI(从机输出)。特别注意:FSI RX在SPI模式下不需要片选信号,它会响应任何时钟边沿。
- FSI作为SPI主设备(TX):
- 数据格式:需要了解对端SPI设备的时钟极性(CPOL)和相位(CPHA)。FSI的SPI模式通常固定为一种格式(通常是CPOL=0, CPHA=0,即模式0),你需要确保SPI设备配置与之匹配。如果不匹配,需要在SPI设备端或通过外部逻辑进行调整。
5.4 由事件触发的传输
FSI支持由ePWM、CPU定时器甚至另一个外设的信号来触发帧传输,这非常适合同步于控制周期的应用,比如每个PWM周期发送一次最新的电流采样值。
- 配置
TX_OPER_CTRL_LO.START_MODE:设置为01b(外部触发)或10b(软件或外部触发均可)。 - 选择触发源:在
TX_OPER_CTRL_HI.EXT_TRIG_SEL中选择具体哪个触发输入(共64个)。这些触发输入需要通过X-BAR(交叉开关)将ePWM、定时器等信号路由过来。 - Ping帧的触发:除了数据帧,Ping帧也可以由外部触发(
TX_PING_CTRL.EXT_TRIG_EN)。这允许你用同一个定时事件来同步心跳和数据。 - 优势:实现了数据发送与控制系统时钟的硬同步,减少了由软件任务调度带来的时间抖动(Jitter),对于高性能的实时控制环路至关重要。
最后,关于寄存器保护,我想再强调一次:在开发阶段,可以暂时不启用最终的LOCK保护,但EALLOW和KEY保护机制务必正确使用。养成“配置前解除保护,配置后立即恢复保护”的习惯,能避免无数个因为寄存器被意外改写而导致的灵异故障���当你准备将代码固化发布时,再考虑在初始化序列的最后,写入TX_LOCK_CTRL和RX_LOCK_CTRL为0xA501,给关键的通信配置加上一把硬件锁,让系统在复杂的现场环境中更加坚不可摧。