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.

[参考译文] TMS570LC4357:SCI DMA 挂起寄存器+中断延迟

Guru**** 2455560 points


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

https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1191289/tms570lc4357-sci-dma-pending-register-interrupt-delay

器件型号:TMS570LC4357

您好!

在过去几个月中、我们开始在 SCI2端口上使用 DMA、仅用于传输操作。  另一位同事还在单独的项目中使用 DMA 在 SCI1端口上执行发送操作、这两种 DMA 实现方式看起来都很稳定。 我们现在正处于将这两种实现相结合的阶段、以便在 SCI1和 SCI2上使用 DMA 仅用于传输操作。 根据技术参考手册、这应该是可行的、因为它表明 DMA 支持最多两个通道上的并发操作。

开启两个 SCI 端口的 DMA 后、我开始注意到奇怪的行为;具体而言、SCI1 DMA 似乎在~1分钟的操作后停止工作、而 SCI2 DMA 将在5-10分钟左右后停止。 根据经验、通过监控连接到这些端口的终端器件(SCI1的 PC 端 COM 端口应用和 SCI2上的第二个 MCU)来观察到这一点。  为了确定导致此行为的确切原因、我实现了一些基本的 DMA 测试函数、并看到了意外结果。  

我看到的最奇怪的事情之一是 DMA 待定寄存器设置中的延迟。 我有代码 通过配置控制数据包并写入 SCI SETINT 寄存器的相应位来启动 DMA 传输、然后进入一个循环、等待 DMA 挂起寄存器指示通道已被服务。 此逻辑遵循 TRM 中的以下模板:

如果是第一个通道、DMA 控制器无法识别同一通道上的两个软件请求
软件请求仍在等待处理中。 如果发生此类请求、DMA 将丢弃此请求。 因此、
在发出新的软件请求之前、用户软件应该检查挂起的寄存器。

奇怪的是、循环似乎仅在一次寄存器检查后退出。 循环中没有实现延迟或睡眠功能、这表示在开始传输后(微秒内)将立即清除挂起寄存器。 缓冲区大小+波特率配置为大约需要~350微秒进行传输、因此这表明挂起寄存器没有被正确设置。 传输完成时、BTC 中断也会设置一个标志(全局声明且为易失性)、并且在跳过挂起循环后也不会设置该标志。 最奇怪的是传输实际上完成了! 我可以观察到字节是使用我的 PC COM 读取器发送的。 在 Launchpad 上使用 JTAG 调试器时、我始终观察到以下事件序列:

  1. DMA 传输函数被调用,它配置控制数据包并调用  dmaSetCtrlPacket() 和  dmaSetChEnable() HAL 函数。  dmaSetChEnable()被调用以在硬件请求时触发
  2. 在 SCI SETINT 寄存器位16被置位的代码行上触发一个 SW 断点、这使得 SCI 传输发生。 此时、COM 读取器不显示任何数据
  3. 我允许代码继续运行、并在挂起寄存器检查循环之后的代码行上触发一个 SW 断点。 循环计数器的值为"1"、表示循环已几乎立即退出。 BTC 中断完成标志尚未置位、表示中断尚未执行。 但是、此时我看到使用 COM 读取器传输的数据、这表明传输已结束
  4. 在代码执行中看似任意的点、BTC 中断函数中会出现断点、现在该标志已置1
  5. 在单步执行代码时、对挂起寄存器的额外检查现在表明传输正在挂起。 这在 BTC 中断发生后发生、 该中断应该是传输的结束

代码的水印版本如下所示:

void unit_test(void)
{
   //Initialize test variables, other function calls
   //...
   
   /*
    * Start a transfer on UART0. Loop until the DMA is no longer
    * pending and verify that the callback flag is incremented.
    */
   dma_ut_uart0_complete = 0U;
   dma_tf_rtn = DMA_start_transfer(DMA_UT_CH_UART0,
                                   DMA_UT_DATA_UART0,
                                   DMA_UT_DATA_UART0_LEN,
                                   DMA_UT_DATA_UART0_ADDR);
   if(dma_tf_rtn != DMA_STARTED)
   {
      dma_unit_rtn = FAILURE;
   }
   else
   {
      /* Enable interrupt to kick-off transfer */
      DMA_unit_test_enable_int_uart0();
   }

   dma_pend_ctr = 0U;
   while(DMA_is_pending(DMA_UT_CH_UART0) == DMA_PENDING)
   {
      dma_pend_ctr++;
      if(dma_pend_ctr > DMA_UT_PENDING_CTR_THRESHOLD)
      {
         dma_unit_rtn = FAILURE;
         break;
      }
   }
   
   //At this point, the value of dma_pend_ctr is either 0 or 1 (not consistent)

   /* Verify that callback complete flag has incremented */
   if(dma_ut_uart0_complete != 1U)
   {
      dma_unit_rtn = FAILURE;
   }
   
   /*
    * Start another transfer and immediately attempt a third. Verify
    * that the function call returns FAILURE as the DMA is busy. Verify
    * that the transfer ultimately completes.
    */
   dma_ut_uart0_complete = 0U;
   
   //--- NOTE!!! ---
   //The BTC interrupt breakpoint occurs while stepping through
   //the following function. Also within this function is a call
   //to DMA_is_pending() which now shows that the DMA channel
   //is pending - even though the interrupt has indicated that it
   //has completed!
   dma_tf_rtn = DMA_start_transfer(DMA_UT_CH_UART0,
                                   DMA_UT_DATA_UART0,
                                   DMA_UT_DATA_UART0_LEN,
                                   DMA_UT_DATA_UART0_ADDR);
   if(dma_tf_rtn != DMA_STARTED)
   {
      dma_unit_rtn = FAILURE;
   }
   else
   {
      /* Enable interrupt to kick-off transfer */
      DMA_unit_test_enable_int_uart0();
   }
   
} //End unit test

//DMA_is_pending()
DMA_Pending_Status_t DMA_is_pending(uint8_t dma_pend_channel)
{
   DMA_Pending_Status_t dma_pending = DMA_INVALID_ARGS;
   uint32_t dma_pend_ch = 0U;

   /* Sanity check */
   if(dma_pend_channel < DMA_NUM_CHANNELS)
   {
      dma_pend_ch = (dmaREG->PEND) & (uint32_t)(1U << dma_pend_channel);

      if(dma_pend_ch > 0U)
      {
         dma_pending = DMA_PENDING;
      }
      else
      {
         dma_pending = DMA_NOT_PENDING;
      }
   }

   return dma_pending;
}
   
//DMA_start_transfer()
DMA_Transfer_Return_t DMA_start_transfer(uint8_t dma_tf_channel,
                                         uint8_t* dma_tf_data,
                                         uint16_t dma_tf_len,
                                         uint32_t dma_tf_dest)
{
   DMA_Transfer_Return_t dma_start_rtn = DMA_ARG_ERROR;
   DMA_Pending_Status_t dma_pend = DMA_PENDING;

   uint16_t dma_append_rtn = 0U;

   uint8_t* dma_dat_ptr = dma_tf_data;
   uint16_t dma_len = 0U;

   /*
    * Sanity check:
    * - Channel is valid
    * - Channel is enabled
    * - Destination memory location is valid
    */
   if((dma_tf_channel < DMA_NUM_CHANNELS) &&
      (dma_channels[dma_tf_channel].dma_ch_en > 0U) &&
      (dma_tf_dest != 0U))
   {
      /* Check DMA pending status */
      dma_pend = DMA_is_pending(dma_tf_channel);

      /*
       * If buffering is enabled:
       * 1) Append data if the pointer is not NULL and length > 0
       * 2) Check to see if the DMA is busy
       * 3) Delete old + Fetch new data for transfer if not busy
       * 4) If fetch return is > 0, send to DMA
       *
       * If the pointer/length are NULL/0, the DMA will be
       * primed without buffering any data.
       */
      if((dma_channels[dma_tf_channel].dma_ch_buff_en > 0U) &&
         (dma_channels[dma_tf_channel].dma_ch_buff_ptr != NULL))
      {
         /* 1) */
         if((dma_tf_data != NULL) && (dma_tf_len > 0U))
         {
            dma_append_rtn = DBUF_append(dma_channels[dma_tf_channel].dma_ch_buff_ptr,
                                         &dma_dat_ptr,
                                         dma_tf_len);

            /* Return value > 0 indicates that not all data was appended */
            if(dma_append_rtn > 0U)
            {
               /* TODO Generate WCAN? Anything to flag? */
            }
         }

         /* 2) */
         if(dma_pend == DMA_NOT_PENDING)
         {
            /* 3) */
            if(dma_channels[dma_tf_channel].dma_ch_buff_tf_len > 0U)
            {
               DBUF_delete(dma_channels[dma_tf_channel].dma_ch_buff_ptr,
                           dma_channels[dma_tf_channel].dma_ch_buff_tf_len);
            }

            /*
             * After call to Fetch, dma_dat_ptr will point to the "tail"
             * of the data in the buffer, e.g. the data to transmit.
             */
            dma_len = DBUF_fetch(dma_channels[dma_tf_channel].dma_ch_buff_ptr,
                                 &dma_dat_ptr,
                                 dma_channels[dma_tf_channel].dma_ch_buff_ptr->dbuf_size);

            /* Store length for deletion next cycle */
            dma_channels[dma_tf_channel].dma_ch_buff_tf_len = dma_len;

            /* Check fetch return to set function return */
            if(dma_len == 0U)
            {
               dma_start_rtn = DMA_BUFFER_EMPTY;
            }
         }
         else
         {
            /* DMA is busy - set flag to do nothing */
            dma_len = 0U;
            dma_start_rtn = DMA_BUSY_BUFFERED;
         }
      }
      /*
       * If buffering is not enabled, verify that the data pointer
       * and length are valid.
       */
      else if((dma_tf_data != NULL) && (dma_tf_len > 0U))
      {
         /* If the DMA is free, set length of transfer */
         if(dma_pend == DMA_NOT_PENDING)
         {
            dma_len = dma_tf_len;
         }
         /* If the DMA is busy, set length to 0 to prevent transfer */
         else
         {
            dma_len = 0U;
            dma_start_rtn = DMA_BUSY_NO_BUFFER;
         }
      }
   }

   /*
    * If the above checks have passed, start the transfer
    */
   if(dma_len > 0U)
   {
      /*
       * Populate DMA control packet
       *
       * CHCTRL - Channel Control
       * ELCNT - Element count
       * ELDOFFSET - Element Destination Offset
       * ELSOFFSET - Element Source Offset
       * FRDOFFSET - Frame Destination Offset
       * FRSOFFSET - Frame Source Offset
       * PORTASGN - Port Assignment
       * RDSIZE - Read Size
       * WRSIZE - Write Size
       * TTYPE - Transfer Type
       * ADDMODERD - Address Mode Read
       * ADDMODEWR - Address Mode Write
       * AUTOINIT - Auto Initialization Mode
       */
      dma_channels[dma_tf_channel].dma_ctrl.CHCTRL = 0U;
      dma_channels[dma_tf_channel].dma_ctrl.ELCNT = 1U;
      dma_channels[dma_tf_channel].dma_ctrl.ELDOFFSET = 0U;
      dma_channels[dma_tf_channel].dma_ctrl.ELSOFFSET = 0U;
      dma_channels[dma_tf_channel].dma_ctrl.FRDOFFSET = 0U;
      dma_channels[dma_tf_channel].dma_ctrl.FRSOFFSET = 0U;
      dma_channels[dma_tf_channel].dma_ctrl.PORTASGN = PORTA_READ_PORTB_WRITE;
      dma_channels[dma_tf_channel].dma_ctrl.RDSIZE = ACCESS_8_BIT;
      dma_channels[dma_tf_channel].dma_ctrl.WRSIZE = ACCESS_8_BIT;
      dma_channels[dma_tf_channel].dma_ctrl.TTYPE = FRAME_TRANSFER;
      dma_channels[dma_tf_channel].dma_ctrl.ADDMODERD = ADDR_INC1;
      dma_channels[dma_tf_channel].dma_ctrl.ADDMODEWR = ADDR_FIXED;
      dma_channels[dma_tf_channel].dma_ctrl.AUTOINIT = AUTOINIT_OFF;
      dma_channels[dma_tf_channel].dma_ctrl.SADD = (uint32_t)dma_dat_ptr;
      dma_channels[dma_tf_channel].dma_ctrl.DADD = dma_tf_dest + DMA_DEST_ADDR_OFFSET;
      dma_channels[dma_tf_channel].dma_ctrl.FRCNT = (uint32_t)dma_len;

      /* Set DMA control packet */
      dmaSetCtrlPacket((dmaChannel_t)dma_tf_channel,
                       dma_channels[dma_tf_channel].dma_ctrl);

      /* Set the DMA channel to trigger on HW request */
      dmaSetChEnable((dmaChannel_t)dma_tf_channel, DMA_HW);

      /* Set return */
      dma_start_rtn = DMA_STARTED;
   }

   return dma_start_rtn;
}

/*
 * ---- ISR call sequence
 */
void dmaBTCAInterrupt(void)
{
   /*
    * MISRA-C:2004 11.3/A Exception:
    *    The TI HAL code defines structures to represent the peripheral
    * registers. The physical memory address of the peripheral registers
    * is cast as a pointer to the defined HAL structure, and this pointer
    * is used to access the registers. De-referencing the pointer therefore
    * provides access to the register value.
    */
   uint32_t dma_int_channel = dmaREG->BTCAOFFSET;

   if(dma_int_channel != 0U)
   {
      /*
       * Offset register is 0 if there are no interrupts pending, and
       * (channel + 1) otherwise. Decrement the offset by 1 to get the
       * interrupt channel
       */
      dmaGroupANotification(BTC, dma_int_channel - 1U);
   }
}

void dmaGroupANotification(dmaInterrupt_t inttype,
                           uint32_t channel)
{
   /* Execute channel callback based on type */
   if(channel < DMA_NUM_CHANNELS)
   {
      switch(inttype)
      {
         case FTC:
         {
            if(dma_channels[channel].dma_cbs.dma_ch_ftc_cb != NULL)
            {
               (*dma_channels[channel].dma_cbs.dma_ch_ftc_cb)();
            }
            break;
         }
         case LFS:
         {
            if(dma_channels[channel].dma_cbs.dma_ch_lfs_cb != NULL)
            {
               (*dma_channels[channel].dma_cbs.dma_ch_lfs_cb)();
            }
            break;
         }
         case HBC:
         {
            if(dma_channels[channel].dma_cbs.dma_ch_hbc_cb != NULL)
            {
               (*dma_channels[channel].dma_cbs.dma_ch_hbc_cb)();
            }
            break;
         }
         case BTC:
         {
            if(dma_channels[channel].dma_cbs.dma_ch_btc_cb != NULL)
            {
               //This callback is set during the unit test for
               //the test channel (UART0)
               (*dma_channels[channel].dma_cbs.dma_ch_btc_cb)();
            }
            break;
         }
         default:
         {
            /* Should never reach - do nothing */
            break;
         }
      } /* End switch() */
   } /* End if() */
}

static void DMA_unit_test_btc_callback_uart0(void)
{
   sciREG1->CLEARINT = (1U << DMA_UT_UART_DMA_BIT);
   dma_ut_uart0_complete++;
   dma_ut_uart0_cend = get_CPU_cycles();
}

我知道 VIM 内产生中断的时间可能会有一些延迟、因此我可以理解这会比预期的晚。 不过、我担心的是、挂起寄存器并不表示信道在启动传输后正在等待处理、而只是似乎表示在某种延迟后等待处理。 总而言之、我的问题基本上是:

  1. 为什么我在为通道设置的 DMA 挂起寄存器中观察到一个延迟?
  2. 使用 DMA 挂起寄存器来检查通道是否不可用是一种可靠的方法?
  3. 为什么 DMA 挂起寄存器继续指示该通道在 BTC 中断发生后挂起?
  4. JTAG 的使用是否以任何方式干扰了这个测试? 例如、避免中断和/或计时混乱

任何建议或智慧都是值得赞赏的。 如果我误解了 DMA 的操作或挂起寄存器的使用、也请纠正我的理解。

最棒的

James

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

    尊敬的 James:

    [引用 userid="395253" URL"~/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1191289/tms570lc4357-sci-dma-pending-register-interrupt-delay ]DMA 控制器无法识别同一信道上的两个软件请求(如果是第一个)
    软件请求仍在等待处理中。[/QUERP]

    当我们为两个 SCI 实例使用单通道时、我希望我们为两个实例使用两个不同的通道、并为两个实例使用两个不同的请求。

    --

    谢谢、此致、
    Jagadish。

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

    Jagadish 您好、

    感谢你的答复。 在我们的代码中、我们将通道和请求映射如下:

    • UART 0 TX --通道0 --请求29
    • UART 1 TX --通道1 --请求41

    根据 TRM、这些是最高优先级的通道(通道0在发生所需仲裁时占据优先级)、这些是我们系统中唯一启用的两个 DMA 通道、因此我假设竞争没有问题。 我通过观察数据输出和引用 TRM 来确认这些请求是正确的。

    我的困惑在于 、在我看来、开始传输和为通道设置 DMA 挂起寄存器之间存在延迟。 在 COM 端口上观察数据和代码内 BTC 回调之间的似乎延迟也让我感到困惑。 我可能不了解内部 DMA 机制或 JTAG 调试如何影响行为(这是我尝试和单步执行测试代码时使用的方法)。

    关于这一混乱问题的任何资料都将不胜感激。

    最棒的

    James

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

    尊敬的 James:

    我怀疑的一件事是该缓存。

    是否可以检查高速缓存是否已启用?

    如果已启用、 您可以尝试禁用它吗?

    --

    谢谢、此致、
    Jagadish。  

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

    Jagadish 您好、

    在我的构建中确实启用了缓存。 但是、禁用高速缓存对我看到的内容没有任何影响。

    实际上、我能够通过在软件中设置和清除标志来解决我的所有问题、以指示 DMA 正忙、而不是使用挂起寄存器。 这强烈地向我表明、我对 DMA 有一些机制或交互不了解、或者 TRM 在解释系统如何工作时可能不清楚。  

    我们现在将继续使用此软件解决方案、但如果您对该挂起寄存器的行为有任何见解、请告诉我。 接下来、如果可能、我们希望从硬件接收这些指示。 也许有一个更好的寄存器来轮询状态而不是挂起?

    此致、

    James

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

    尊敬的 James:

    DMA 挂起寄存器用于跟踪直接存储器访问(DMA)事务的状态。 它是一个硬件寄存器、用于存储正在进行的 DMA 传输的状态、从而使系统能够监视和管理数据流。 DMA 挂起寄存器中的位代表系统中有挂起 DMA 请求的通道。 通过读取该寄存器的内容、系统可以确定哪些通道请求了对系统存储器的访问、并对其进行相应的优先级排序。

    只要触发发生、相应的通道挂起位就会被置位、并且只有在帧结束或块传输结束后才会被清零、具体取决于通道的配置方式。

    例如:

    如果您看到以下固定优先级方案

    --

    谢谢、此致、
    Jagadish。

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

    Jagadish 您好、

    感谢您的回答。 这就是我在阅读技术参考手册之后对该寄存器的使用的理解。 不幸的是、我在软件中观察到的行为似乎与此描述不符、这导致我认为我可能以某种方式错误配置了我的项目。  

    我在上面所描述的软件解决方案对于我们的项目来说已经足够了、我们将继续努力。 也许我们将来会再讨论这个问题,但我现在很高兴结束这个问题。

    谢谢、

    James