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.
Hello,
Over the past few months we have begun using DMA on the SCI2 port for transmit operations only. Another coworker has also been using DMA for transmit operations on the SCI1 port in a separate project, and both of these DMA implementations have been seemingly stable. We are now at the stage of combining both implementations in order to use DMA on both SCI1 and SCI2 for transmit operations only. This should be possible according to the technical reference manual, as it indicates that the DMA supports concurrent operations on up to two channels.
After turning on the DMA for both SCI ports I began to notice strange behavior; specifically, the SCI1 DMA seemed to stop working after ~1 minute of operation, and the SCI2 DMA would stop after 5-10 minutes or so. This was empirically observed by monitoring the end devices connected to these ports (a PC-side COM port application for SCI1, and a second MCU on SCI2). In an effort to determine what exactly was causing this behavior, I implemented some basic DMA test functions and am seeing unexpected results.
One of the most bizarre things that I have been seeing is a delay in the DMA pending register being set. I have code which initiates the DMA transfer by configuring the control packet and writing the appropriate bit of the SCI SETINT register, and following this enters a loop which waits for the DMA PEND register to indicate that the channel has been serviced. This logic follows the following blurb from the TRM:
The DMA controller cannot recognize two software requests on the same channel if the first
software request is still pending. If such a request occurs, the DMA will discard it. Therefore,
the user software should check the pending register before issuing a new software request.
Strangely, the loop seems to exit after only a single register check. There is no implemented delay or sleep function within the loop, which indicates that the pending register is cleared nearly immediately after starting the transfer (within microseconds). The buffer size + baud rate are configured to take roughly ~350 microseconds to transfer, so this indicates that the pending register is not being properly set. I have the BTC interrupt set a flag (declared globally and volatile) as well upon transfer completion, and this flag is also not set after the pending loop is skipped. What is perhaps most strange is that the transfer actually completes! I can observe that the bytes are sent using my PC COM reader. Using the JTAG debugger on the Launchpad, I have consistently observed the following sequence of events:
A watered-down version of the code is presented below:
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(); }
I understand that there is likely some delay within the VIM in generating the interrupt, so I can understand that this fires later than expected. What concerns me though is that the PEND register does not indicate that the channel is pending service after initiating the transfer, and only seems to indicate that it is pending after some sort of delay. To summarize, my questions are essentially:
Any advice or wisdom is much appreciated. If I have misunderstood the operation of the DMA or use of the PEND register, please correct my understanding as well.
Best,
James
Hi James,
The DMA controller cannot recognize two software requests on the same channel if the first
software request is still pending.
This condition is when we used single channel for both the SCI instances, In our case i wish we are using two different channels for both the instances and two different requests for both the instances.
--
Thanks & Regards,
Jagadish.
Hello Jagadish,
Thank you for the reply. In our code, we have mapped the channels and requests as follows:
According to the TRM, these are the highest priority channels (with Channel 0 taking priority in the event of required arbitration) and these are the only two enabled DMA channels in our system, so I assume that there is no issue with contention. I have confirmed that these requests are correct, both by observing the data output and by referencing the TRM.
My confusion lies with what seems to me is a delay between beginning the transfer and the DMA PEND register being set for the channel. The seeming delay between observing data on the COM port and BTC callback within the code is confusing to me as well. There is likely something that I'm not understanding about the internal DMA mechanisms or about how JTAG debugging may be impacting the behavior (which is what I have been using to try and step through my test code).
Any information about this topic of confusion would be greatly appreciated.
Best,
James
Hi James,
One thing i am suspecting is that cache.
Can you please check whether cache is enabled or not?
If it is enabled, can you please try by disabling it?
--
Thanks & Regards,
Jagadish.
Hello Jagadish,
The cache was indeed enabled in my build. However, disabling the cache did not have any impact on what I have been seeing.
I actually was able to resolve all of my issues by setting and clearing flags in software to indicate that the DMA is busy, rather than using the PEND register. This very strongly indicates to me that there is some mechanism or interaction that I do not understand about the DMA, or possibly that the TRM is unclear in its explanation of how the system works.
We will move forward with this software solution for now, but if there is any insight you can share on the behavior of this PEND register please let me know. Going forward we would prefer to receive these indications from the hardware if possible. Perhaps there is a better register to poll for status rather than PEND?
Best regards,
James
Hi James,
DMA pending register is used to track status of Direct Memory Access (DMA) transactions. It is a hardware register that stores the status of ongoing DMA transfers, allowing the system to monitor and manage the flow of data. The bits in the DMA pending register represent the channels in the system that have pending DMA requests. By reading the contents of this register, the system can determine which channels have requested access to the system's memory and prioritize them accordingly.
It is simple whenever a trigger comes the corresponding channel pending bit will get set and it is cleared only after end of frame or block transfer depending upon the how channel is configured.
For example:
If you see below fixed priority scheme
--
Thanks & Regards,
Jagadish.
Hello Jagadish,
Thank you for the response. This is how I understood the use of this register as well after reading the technical reference manual. The behavior that I had observed in software did not seem to align to this description, unfortunately, which led me to believe that I may have somehow misconfigured my project.
The software solution that I had described above is sufficient for our project, and we will move forward with that. Perhaps in the future we will revisit this issue, but I am good to close this topic for now.
Thanks,
James