您好!
在过去几个月中、我们开始在 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 调试器时、我始终观察到以下事件序列:
- DMA 传输函数被调用,它配置控制数据包并调用 dmaSetCtrlPacket() 和 dmaSetChEnable() HAL 函数。 dmaSetChEnable()被调用以在硬件请求时触发
- 在 SCI SETINT 寄存器位16被置位的代码行上触发一个 SW 断点、这使得 SCI 传输发生。 此时、COM 读取器不显示任何数据
- 我允许代码继续运行、并在挂起寄存器检查循环之后的代码行上触发一个 SW 断点。 循环计数器的值为"1"、表示循环已几乎立即退出。 BTC 中断完成标志尚未置位、表示中断尚未执行。 但是、此时我看到使用 COM 读取器传输的数据、这表明传输已结束
- 在代码执行中看似任意的点、BTC 中断函数中会出现断点、现在该标志已置1
- 在单步执行代码时、对挂起寄存器的额外检查现在表明传输正在挂起。 这在 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 内产生中断的时间可能会有一些延迟、因此我可以理解这会比预期的晚。 不过、我担心的是、挂起寄存器并不表示信道在启动传输后正在等待处理、而只是似乎表示在某种延迟后等待处理。 总而言之、我的问题基本上是:
- 为什么我在为通道设置的 DMA 挂起寄存器中观察到一个延迟?
- 使用 DMA 挂起寄存器来检查通道是否不可用是一种可靠的方法?
- 为什么 DMA 挂起寄存器继续指示该通道在 BTC 中断发生后挂起?
- JTAG 的使用是否以任何方式干扰了这个测试? 例如、避免中断和/或计时混乱
任何建议或智慧都是值得赞赏的。 如果我误解了 DMA 的操作或挂起寄存器的使用、也请纠正我的理解。
最棒的
James

