This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP430F5326:SPI 从器件时序问题

Guru**** 2386600 points
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1135595/msp430f5326-spi-slave-timing-issue

器件型号:MSP430F5326

编译器:IAR 4.21.6

SPI 设置为从器件。  主时钟为312.5kHz

观察结果:

MISO 线路不与时钟输入的上升沿同步。

这种情况很少发生在上电复位后。 在软件的较长运行时间(几个小时)内也很少发生这种情况。

大多数情况下、一切都按照 SPI 规范正常工作、没有任何问题。

逻辑分析仪记录:

如果发生上述错误、可以在时钟线的上升沿和数据线的变化(MISO)之间观察到延迟。

这个延迟在运行期间增加、直到 MISO 线路的数据变化和时钟线路的下降边沿同时发生。

发生这种情况时、主器件将无法再正确解释传输的数据。

复位后的通信:

在复位后、可以观察到~750ns 的延迟。 MISO 线路应在上升沿改变其输出。

MISO 时序偏移:

复位300ms 后、数据线会进一步移向时钟的下降沿。

上次“成功”通信:

复位后的1.04秒、数据线变化几乎与时钟线的下降边沿一致。

处理器的 SPI 单元怎么可能以这种方式工作?

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Benjamin、

    只需回答几个问题即可缩小源代码范围。

    您能否共享初始化 SPI 外设和 SPI ISR 时使用的代码段? 这将让我验证外设是否已正确初始化。

    您在 SPI 线路上是否有无源器件、或者它是两个器件之间的直接连接?

    您是否在 SPI 中使用 DMA?

    此致、

    Luke

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Luke、

    感谢您的快速回复。

    DMA 未被使用。 它完全由字节级的中断例程处理。

    硬件设置如下:
    连接到主器件的 MISO 线路上为100欧姆。
    uClamp0504A 从 CS、MOSI、MISO 和 Clk 到 GND。

    SPI 外设初始化代码:

    void SPI_InitUsciA0asSpi(void)
    {
        UCA0CTL1 = UCSWRST;
        UCA0CTL0 = UCMSB + UCMODE1 + UCSYNC; // MSB first, 8Bit, 4 Pin SPI active low, Slave, Output at rising edge
    
        P3SEL |= (BIT2 + BIT3 + BIT4);  // SPI functionality
        P3DIR |= BIT4;
        P3DIR &= ~BIT2;
        P3DIR &= ~BIT3;
    
        P2SEL |= BIT7;  // P2.7 with SPI functionality
        P2DIR &= ~BIT7;
    
        UCA0CTL1 &= ~UCSWRST;   // Clear Software reset bit (enables USCI_A0)
        UCA0IE = UCRXIE;  // Recieve interrupt enable
    }

    SPI 中断处理代码:

    #define HW_SetCpuToFullSpeedMode()         UCSCTL5 &= 0xFFF8
    #define HW_SetCpuToLowSpeedMode()          {UCSCTL5 &= 0xFFF8;UCSCTL5 |= 0x0002;}
    
    // Clear Interrupt Flags and enable Transmit interrupt
    inline void SPI_EnableTransmitInterrupt(void)
    {
      UCA0IFG = 0;  // Clear Interrupt Flags
      UCA0IE = UCTXIE;  // Transmit interrupt enable
    }
    
    
    // Read Transmit buffer
    inline unsigned char SPI_ReadTransmitBuffer(void)
    {
      return UCA0TXBUF;
    }
    
    #pragma vector=USCI_A0_VECTOR
    __interrupt void ISR_UsciA0 (void)
    {
      timeoutInt = 0;
      SPI_SpiCommStateMachine(UCA0RXBUF);   // erstes byte 5us bei 20Mhz
    }
    
    // SPI Comunication State Machine
    #pragma inline=forced
    inline void SPI_SpiCommStateMachine(unsigned char recByte)
    {
      static unsigned char checkSum = 0;
      static unsigned char checkSumError = 0;
      
      switch(commState)
      {
        case 0:   // Command Byte
    
          if (P2IFG&BIT2)  
            HW_SetCpuToFullSpeedMode();
    
          SPI_SpiCommInterpreter(recByte);
          HW_SetNewTimerA0cc1Value();         // for timeout start timer
          TA0CCTL1_bit.CCIFG = 0;             
          HW_EnableTimerA0IcOc1_Interrupt();  
          checkSum = recByte;
          break;
    
        case 1:  // Receive Bytes from Master
          *spiRecievePtr = recByte;
          spiRecievePtr++;
          checkSum += recByte;
          spiRecieveDataLenght--;
          if(spiRecieveDataLenght == 0)
          {
            commState = 2;
          }
          break;
    
        case 2: // Get Checksum from Master and send 1st Byte
          SPI_EnableTransmitInterrupt();  // Change to transmit interrupt
          recByte = ~recByte;
          if(checkSum != recByte) // Checksum Error (Invertierte Checksum)
          {
            checkSumError = 1;
            spiStatus.bit.commandRecieved = 0;
          }
          else
          {
            checkSumError = 0;
          }
          checkSum = SPI_ReadTransmitBuffer();
          if(spiTransmitDataLenght == 0)
          {
            commState = 4;
          }
          else
          {
            commState = 3;
          }
          break;
    
        case 3: // Send bytes to Master
          SPI_WriteTransmitBuffer(*spiTransmitPtr);
          checkSum += *spiTransmitPtr;
          spiTransmitPtr++;
          spiTransmitDataLenght--;
          if(spiTransmitDataLenght == 0)
          {
            commState = 4;
          }
          break;
    
        case 4: // Send Checksum to Master (Invertierte Checksum)
          SPI_WriteTransmitBuffer(~(checkSum + checkSumError));
          commState = 5;
          break;
    
        case 5: // Letztes Byte
          SPI_WriteTransmitBuffer(0);
          SPI_EnableRecieveInterrupt();
          commState = 0xFF;
          break;
    
        case 10: //undefined command wait until end
          SPI_EnableTransmitInterrupt();  // Change to transmit interrupt
          SPI_WriteTransmitBuffer(0);
          // wait for timeout 6ms
          break;
    
        default:    // Letztes Byte wurde Empfangen
          HW_DisableTimerA0IcOc1_Interrupt(); // Timeout Counter deaktivieren
          SPI_WriteTransmitBuffer(0);
          UCA0IFG = 0;  // Clear Interrupt Flags
          commState = 0;
          if(spiStatus.bit.inertDataSending)
          {
            spiStatus.bit.inertDataSending = 0;
            spiStatus.bit.inertDataSent = 1;
            errorState.bit.WdgResetOccured = 0; // Clear Watchdog Error
          }
          if(checkSumError)
          {
            ResetRequired = 0;
            spiStatus.bit.chkSumErr = 1;
            checkSumError = 0;
          }
          else if(spiStatus.bit.commandRecieved)
          {
            spiStatus.bit.commandPending = 1;
          }
          spiStatus.bit.commandRecieved = 0;
          if(clrCommErrors)
          {
            SPI_ResetCommErrors();
            errorState.bit.WdgResetOccured = 0; // Clear Watchdog Error
            clrCommErrors = 0;
          }
          if(cpuLowFreqMode)
          {
            HW_SetCpuToLowSpeedMode();
          }
          break;
      }
    }

    此致、

     Benjamin

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    钳位二极管看起来非常无用、因为它们钳位到的电压大于 MSP430内部钳位二极管导通时的电压。 它们还会增加相当大的电容。

    您的 SPI 代码具有竞态条件。 接收到校验和字节后、它将启用发送中断。 由于 TXIFG 被置位、这会排队等待一个立即中断。 ISR 是否会返回、下一个 ISR 是否会在需要前将数据放入 TXBUF 中? 这似乎不太可能 、因为最坏的情况下、您只有半个 SPI 时钟周期来完成所有这些工作。 这意味着传输的第一个字节将是 TXBUF 中挂起的任何字节。

    但不会影响数据输出时序。 您显示了数据输出时序、该时序从上升沿缓慢地移动到下降沿。 它会停在那里吗? 这是否随 SPI 时钟频率而变化? 换言之、移位是否在下降沿停止或延迟是否持续增加?

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    感谢有关钳位二极管的评论。 我将向电子产品团队介绍此类二极管的必要性。
    然而、所有斜坡的上升和下降时间看起来都很好。

    我知道这种比赛情况。 这不是很好、但转换的第一个字节对主器件没有意义。

    在 MOSI 线路输出的数据到达 CLK 的下降边沿后、再发送的所有数据为零。
    在我的小齿轮中、这是由于开关外壳的默认部分或外壳10而发生的。
    从机似乎不再能够理解输入命令、因此会以零做出反应。
    因此、我不再能够观察边沿、因为零不包含边沿。

    从器件不会从该状态中恢复。 通信仅以零继续。

    我还尝试与半波特率通信。 这也不能解决问题。
    不幸的是,我没有关于半波特率试验的记录。

    如果我错了、请纠正我的错误、但 SPI 从器件通过主器件通过 CLK 计时的移位寄存器在内部实现。
    MISO 边沿随时间变化如何在硬件方面实现呢?

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Benjamin、

    硬软件不会有任何原因导致该延迟一直传播、直到它到达下降沿。

    我确实看到一些可能导致此延迟的代码。

    1. ISR 是非常通用的、它正在接收任何 SPI 中断、因此不依赖于 TX 或 RX 标志
    2. ISR 中有很多代码
      1. 这可能会导致延迟、因为您正在进行特别快速的通信(尤其是默认部分)内的更改
    3. 我不知道你是如何脱离状态10的、也许这是有意的、但我会做一些检查、以便你可以看到你何时处于该状态。
    4. 请参阅外设(从器件)端的 SPI 示例。  SPI 示例

    此致、

    Luke

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    第1点和第2点:
    我 得到了答案背后的想法、但我 不明白这 会导致这样的问题。
    如果中断处理时间过长、唯一可能发生的事情就是丢失了整个字节。
    我 不明白这会如何改变由外部 CLK 源控制的位时序。

    第3点:
    状态10通过将 SPI 复位到其初始状态的硬件计时器保留。

    如果我能够更好地了解采购员 SPI 单元的内部结构、我可能会缩小问题的根源。

    是否有任何类型的文档可以更详细地了解 SPI 单元?

    此致、
      Benjamin

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    要更详细地了解 SPI 端口的操作、需要详细描述硬件。 比如硬件设计的 VHDL 源。 但这种情况永远不会发生。 在任何情况下、我都不能想到会导致它从上升沿逐渐改变其运行状态的任何东西。

    在从模式下使用硬件将会变得不太常见、因此您可能在 MSP430中发现了问题。 但这是不可能的。

    您的 ISR 仍然存在严重缺陷。 我一开始没有意识到这一点、因为它会混淆一些东西。 我没有注意到您在启用发送中断之前清除了中断标志。 这真的很糟糕。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Ben、

    除了 数据表 用户指南之外 、还不会提供有关 SPI 外设的其他信息。  

    在第2点、ISR 中仍有 SPI 传输、因此即使时序关闭、SPI 中断也会排队等待发送、我认为最终会发生的情况是您脱离了正确的相位和极性对齐。

    是否可以从 ISR 中取出大部分代码并将其放入 main 中、检查 main 中的状态、然后发送 SPI 传输事件并对其进行处理、与我们的示例类似?

    此致、

    Luke

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我不认为写入 TXBUF 的时序会对位时序产生很大影响。 这当然取决于从 TXBUF 到发送移位寄存器的数据传输的时序。 在从机模式下、所有可用的都是外部应用的 SPI 时钟。

    我假设 SPI 端口内可能有一些未记录的状态机、这些状态机依靠内部时钟运行。 在这种情况下、将 UCSSEL 设置为保留设置以外的值可能会有所帮助。