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.

[参考译文] MSP430FR5994:在从属设备上实施具有 I2C 的 DMA

Guru**** 2390755 points


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

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1071877/msp430fr5994-implementing-dma-with-i2c-on-a-slave

部件号:MSP430FR5994

我有一个应用程序,其中 MSP430充当 I2C 总线上的从属设备。 主控制器将以同步方式请求状态字节的数组,因为从控制器可能正在休眠或收集数据。 这在很大程度上是有效的,但偶尔会发生碰撞,导致时钟过度拉伸和总线冻结。 我的解决方法是使用超时中断重置总线,但我认为使用 DMA 进行数据传输是一种更好的方法。 我正在使用 UCTXIFG0 ISR 实现此目的,但文档表明,如果设置了中断位 UCB0IE |= UCB0TXIE,eUSCI_B0上的 DMA 传输将不起作用。 我的想法是在 UCSTIFG 内部调解 DMA 传输,而不是 UCTXIFG0,但我想在尝试开发之前获得一些指导。 我的应用程序是否可以使用 DMA,如果可以,最佳方法是什么? 我目前的工作代码如下:

#pragma vector = EUSCI_B0_VECTOR
    __interrupt void USCI_B0_ISR(void)
     {
        switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
         {
         case USCI_I2C_UCTXIFG0:     // Vector 26: TXIFG0
                             if (TxCount == SBYTES) 
                             {
                                 PStatus = Status_array; // Point PStatus to start of Status_Array
                                 Tflag = 1; //Alert main program that interrupt occurred
                                 if (TimerFlag == 1) Tstore = TB0R; //Store the timer count
                             }
                             if (TxCount)      // Check TX byte counter not empty
                             {
                                 UCB0TXBUF = *PStatus++; // Load TX buffer
                                 TxCount--;  // Decrement TX byte counter
                             }
                             else
                             {
                                 UCB0IFG &= ~UCTXIFG0; // Clear USCI_B0 TX int flag
                             }
                             break;
    }
}

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

    我只使用了 I2C 主控制器的 DMA,该主控制器知道事务将在何时开始以及将移动多少数据。 从属功能通常不太可预测。

    我看到的一个可能的危险是[Ref User Guide (SLAU367P) Fig32-9] UCSTIFG 和 UCTXIFG 同时偏高,因此当代码看到 STTIFG 时,TXIFG 上升边缘(DMA 正在寻找)已经发生。 SPI DMA 也会发生类似的情况,在这里使用 DMALEVEL=1也会有所帮助。

    代码片段表明,从属设备看到的唯一操作是从固定“位置”发出固定长度的从属发射器请求。 这是正确的吗? 如果是这样,您可能会因为 DMA 请求一直处于待处理状态而离开,当 DMA 请求在两次事务之间用完(大概)时重新启动它。 唯一的危险是,如果主中继器(出于任何原因)提前结束事务,DMA 将会半生半生半生。

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

    正确,有一个固定的字节数组,主中继器可以随时访问该数组。 当数据请求进入时,从属设备通常忙于执行其他操作,因此,当 ISR 得到服务时,我必须保存计时器计数。 它可以工作,但非常尴尬,这就是 DMA 方法看起来很有吸引力的原因。 需要中断,但它不能是 DMA 中断,该中断仅在 DMaxSZ 达到零时才会触发。 我无法在 EUSCI_B0矢量内使用 TXIFG,否则 DMA 将无法正常工作。 这就是为什么我在考虑 STTIFG。 根据 UCTR 位的不同,我似乎需要在 STTIFG 内部分支,但我怀疑时间很紧。 我还应该提到,I2C 总线被主中继器用来(很少)与从中继器通信,所以我也在访问 UCRXIFG0中断。  

    如果我理解正确,我可能能够消除上面所示的 UCTXIFG0 ISR,设置 DMaxSAL =&Status_array[0],DMaxDAL =&UCB0TXBUF,使用 UCTXIFG0触发 DMA,然后使用 DMA 中断重置 DMAXSZ =0?

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

    我试图对 Bruce 的建议进行编码。 下面是我所提出的内容:

    /*  DMA Ch 1 triggers on UCB0TXIFG0; UCB0IE &= ~UCTXIE0
    	Single byte transfer, increment source, enable interrupt, enable DMA */
    	
    	DMACTL0 = DMA1TSEL_19; 
        DMA1CTL = DMADT_0 + DMASRCINCR_3 + DMADSTINCR_0 + DMADSTBYTE + DMASRCBYTE + DMAIE + DMAEN;
        DMA1SZ = 1; 
    
    while(1)
    {
    	... fill Status_array ...
    
     	DMA1SAL = &Status_array[0]; 
        DMA1DAL = &UCB0TXBUF; 
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
      switch(__even_in_range(DMAIV,16))
      {
        case 4: // DMA1IFG = DMA Channel 1
            P2OUT |= BIT0; //Flash LED when interrupt fires
            __delay_cycles(1000);
            P2OUT &= ~BIT0;
            if (TxCount == SBYTES)
            {
                DMA1SAL = &Status_array[0]; 
                DMA1DAL = &UCB0TXBUF; 
            }
            if (TxCount)      // Check TX byte counter not empty
            {
                TxCount--;  // Decrement TX byte counter
            }
            DMA1CTL |= DMAEN;
            DMA1SZ = 1;
            break;
        }
    }

    主中继器不接收任何字节。 DMA 中断不会触发。 已尝试使用边缘和水平触发器。

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

    > DMA1SZ = 1;

    我建议您在一个 DMA 操作中完成整个事务:

    > DMA1SZ = SBYTES;

    在 DMA ISR 中,不要统计字节,只需将 DMAEN 设置为重新启动它。

    ---

    > DMA1SAL =&Status_array[0];
    > DMA1DAL =&UCB0TXBUF;

    您只需要执行一次(永久)操作,因为 DMA 单元会在内部复制这些内容。 此外,出于您的目的,您只需要设置一次 DMA1SZ,因为完成后 DMA 单元将恢复它。  

    在另一条线程中,似乎存在一些问题,即分配 DMA1SAL/dal 是否有效(与 data20_write_long 相比)。 这是否得到了解决?

    --

    我不是很清楚这里的流量是如何工作的。 主教练何时(在你的代码中)提出请求?

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

    在我的现有应用程序中,主中继器异步查询在从属设备上运行的定时循环。 从属方停止正在执行的操作,通过 I2C 发送字节阵列,然后恢复。 这是通过 USCI_B0中断向量以通常方式完成的。 但是,如果使用 DMA 在后台执行此操作,则会更好。 我经常遇到随机死机,需要在从属设备上重置 I2C。

    按照 ADC 的另一个线程中的建议配置 DMAxSAL 和 DMAxDAL 会在 CCS 中生成警告,但否则这将非常有效。

    我尝试对我认为您建议的内容进行编码,但主机上仍然看不到任何字节,DMA 中断也不会触发。 已尝试作为单字节和重复的单字节传输。 此处的代码片段:

    // Also tried single byte transfer DMADT_0
    DMA1CTL = DMADT_4 + DMASRCINCR_3 + DMADSTINCR_0 + DMADSTBYTE + DMASRCBYTE + DMAIE + DMAEN; 
    DMA1SZ = SBYTES;
    DMA1SAL = &Status_array[0]; //Ignore the 515-D warnings
    DMA1DAL = &UCB0TXBUF; //Ignore the 515-D warnings
    
    while(1) //Timed loop
    {
    	... fill Status_array ...
    
     	... master requests this array asynchronously ..
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
      switch(__even_in_range(DMAIV,16))
      {
        case 4:                     // DMA1IFG = DMA Channel 1
            P2OUT |= BIT0; //Flash LED when interrupt fires
            __delay_cycles(1000);
            P2OUT &= ~BIT0;
            DMA1CTL |= DMAEN;
            break;
        }
    }
    

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

    我以前没有尝试过这种方法,所以我对自己的版本进行编码,并使用“环回”脚手架(同一 MCU 上的主从)对其进行测试。

    我没有看到你描述的行为——我每次都能获得数据——但我确实发现了一个微妙的点。

    如果您对《用户指南》(SLAU367P)图32-9 (第一行)感到不满意,将在装入 TXBUF 后不久发布(新) TXIFG。 这仅显示第一个字节,但可能会显示每个字节的情况。 (这也符合 EUSCI 的典型行为。) DMA 在加载最终字节后即完成,但如果您重新启动 DMA,则太早了,额外的 TXIFG 将(重新启动) DMA 计数抛出。

    我通过从 UCSTPIAG 触发 DMA 重启来避免这种情况;到那时,停止已发生,额外的 TXIFG 已被清除。 (这种情况可能与 SPI 中的清除/CS 类似。) 我认为这也避免了师父过早退出的危险。

    无论如何,这是我的版本。 也许这里有一些有用的东西。

    ///
    //  i2cs-static.c
    //  Provide I2C access to a static array (hands-free via DMA)
    //  No support, no warranty. I may not even exist.
    //
    #include <msp430.h>
    #include <stdint.h>
    //#include "sbytes.h"
    #define SBYTES  16
    //#include "i2cs-static.h"
    #define I2CS_ADDR   0x40            // I just made that up
    extern void i2cs_init(void);
    
    uint8_t i2cs_data[SBYTES];
    void
    i2cs_init(void)
    {
        uint16_t i;
        for (i = 0 ; i < SBYTES ; ++i)
            i2cs_data[i] = (0xFF-i); // Fill with nonsense
    
        P5OUT  |= (BIT0|BIT1);          // Cheat on the
        P5REN  |= (BIT0|BIT1);          //  pullups
        P5SEL0 |= (BIT0|BIT1);          // P5.0/1 as UCB1SDA/SCL per SLASE54C Table 6-31
    
        UCB1CTLW0 = UCMODE_3 | UCSYNC | (0*UCMST)|(0*UCSSEL) | UCSWRST;
        UCB1I2COA0 = I2CS_ADDR | UCOAEN;
        UCB1CTLW0 &= ~UCSWRST;
        UCB1IFG = 0;
        UCB1IE = UCSTPIE;               // This happens at the right time
    
        DMACTL1 |= DMA3TSEL_19;         // UCB1 TXIFG0 per SLASE54C Table 6-11
        __data20_write_long((uintptr_t)&DMA3DA, (uintptr_t)&UCB1TXBUF);
        __data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)&i2cs_data);
        DMA3SZ = SBYTES;
        DMA3CTL = DMADT_0 | DMASRCINCR_3 | DMASRCBYTE | DMADSTBYTE; // no DMAIE
        DMA3CTL |= DMAEN;
    
        //  Everything happens in the DMA
        return;
    }
    
    ///
    //  This takes the place of the DMA_VECTOR since the DMA
    //  completion happens too early.
    #pragma vector=USCI_B1_VECTOR   // UCSTPIFG
    __interrupt void
    DMA_ISR(void)
    {
        UCB1IFG = 0;                // Clear UCSTPIFG
        DMA3CTL &= ~DMAEN;          // In case master quit early
        DMA3CTL |=  DMAEN;
        ++i2cs_data[0];             // So we can see progress
        return;
    }
    

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

    如上所示,我可以使用停止中断获得一些工作,但它需要一些我不理解的干预。 下面是我看到的内容:

    当主中继器请求从中继器发送数据时,SCLK 一直处于低位,UCSTPIF 将不会触发。 我使用 UCB0IE 实施时钟拉伸超时 UCB0CTLW1 |= UCCLTO_3 (34毫秒)|= UCCLTOIE 并将重置操作置于此处:

    https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/869750/msp430fr2433-i2c-clock-low-timeout-interrupt

    进入超时 ISR。 从机仍不传输任何数据。 但是,如果主中继器使用 EUSCI_B0 UCRXIFG0 ISR 异步向从属设备发送几个字节,则总线将解锁并通过 DMA 将整个字节阵列从从属设备传输到主中继器。 在下一次迭代中,SCL 再次保持在低位,但在此期间,ISR 将解锁超时,并进行 DMA 传输。 此序列将无限期地继续:SCL 超时,ISR 中的 I2C 自动重置,发生 DMA 传输,UCSTPIG ISR 重置 DMAEN 并重复。 主中继器只需要发送一次数据才能移动,但每次从属设备尝试通过 DMA 向主中继器发送数据时,都需要进行 I2C 重置。

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

    更新:将 DMADD_0替换为 DMADD_4后,无需在每次迭代中重置 I2C 总线。 几乎就在那里! 最后一个问题似乎是了解为什么需要将某些任意数据从主数据移动到从数据才能解锁 DMA。

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

    更新:需要向从属设备写入数据以触发 UCSTPIG ISR,该 ISR 执行 DMAxCTL &=~DMAEN 的序列,然后是 DMA1CTL |= DMAEN。 我决定在输入无限 while 循环(不使用中断)之前放置相同的清晰序列,这就正确地启用了从读的 DMA (停止 ISR 保持不变)。 为什么这样做是一个谜,因为在程序早期配置信道时,可能会设置 DMAEN 位。 请注意,清除该位并设置它是非常重要的,只是重置它将不起作用。 我不知道发生了什么,但现在一切都好了,所以我将把它标记为一个已解决的线程。 感谢所有的帮助。

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

    我看到的症状是数据是在主控收到的,但第二次读取的数据是从 i2cs_data[1]开始的,第三次是在[2]开始的,依此类推。

    通过这种安排,巴士停靠的一种方法是让师父要求的数据超过 SBYTES。 您可以尝试设置 DMAxSZ=(SBYTES+1)以查看是否正在发生这种情况。 在这种情况下,STPIG 代码(通过切换 DMAEN 以重置 DMA)应修复。

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

    更新:在 USCI_Bx_Vector 中,DMA 可以将数据从从属传输到主中继器,而无需使用任何 ISR。 主中继器的异步触发完全可以通过 DMA ISR 来完成。 黑客攻击涉及在进入主程序循环之前清除和设置 DMAEN。 我不知道正在发生什么,但它是有效的。 下面是使用 DMA 通道1的一些示例代码:

    // Ch 1 Repeated single byte transfers from Status_array[SBYTES]. Increment the source only. 
    DMACTL0 = DMA1TSEL_19; // Trigger on UCB0TXIFG0 (see 5994 data sheet)
    DMA1CTL = DMADT_4 + DMASRCINCR_3 + DMADSTINCR_0 + DMADSTBYTE + DMASRCBYTE + DMAEN + DMAIE;
    DMA1SZ = SBYTES;
    DMA1SAL = &Status_array[0]; //Ignore the 515-D warnings
    DMA1DAL = &UCB0TXBUF; //Ignore the 515-D warnings
    
    //Essential to clear and set DMAEN before entering loop
    DMA1CTL &= ~DMAEN; 
    DMA1CTL |= DMAEN;
    
    while(1)
    {
          /* Perform the various slave tasks (including sleep) and wait 
            for asynchronous data request from master */
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
      switch(__even_in_range(DMAIV,16))
      {
        case 4: // DMA1IFG = DMA Channel 1
                while(UCB0STATW & UCBBUSY); //Give last byte time to transfer
                DMA1CTL &= ~DMAEN; 
                DMA1CTL |= DMAEN;
                break;
        default: break;
      }
    }

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

    我怀疑正是这种情况,而不是额外的 DMAEN=0,这样做的目的是:

    > While (UCB0STATW 和 UCBBUSY);//给出最后一个字节的传输时间

    这与 STPIFG 上的触发作用相同(尽管使用 STPIFG 不需要旋转环路)。

    我很高兴你能让它发挥作用。

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

    再想一想,我相信您的 STPIG ISR 方法会更好。 如果我的理解正确,轮询 UCBBUSY 需要 CPU 干预,这就失去了 DMA 的一个关键优势,即不涉及 CPU 的数据传输。 再次感谢他们的帮助。