Configuring DMA crashes CPU?

Kuba0040

Well-known member
Hello,
I am trying to get the DMA working on a Teensy 4.0. To do so I’m currently in the process of reverse-engineering the DMAChannel.cpp and .h file, along with the datasheet's help. However, I have encountered a peculiar problem. When I try to configure the DMA, writes to some registers immediately crash the CPU.

Here is an example:
Code:
void setupDMAChannel_0_Main(int32_t BytesToTransfer, int32_t HowManyTransfers, int32_t MinorLoopOffset)
{
  DMA_CR = DMA_CR_GRP0PRI | DMA_CR_EMLM | DMA_CR_EDBG;
  /*
   * A GROUP PRIORITY MUST ALWAYS BE SET!
   * GRP1PRI - Group 1 Priority. Group 1 gets set as the most important. 
   * GRP0PRI - Group 0 Priority. Same thing as group 1
   * 
   * EMLM - Enable Minor Loop Mapping
   * Minor loops allow us to have offsets to the Source and Destination adress after each minor loop completes.
   * 
   * Minor loops are individual requests. So if I request to copy X bytes from A to B that action occurs inside a Minor Loop.
   * However, If I'd want to have that copy operation start with a diffrenet offset each time a request fires then that Copy Minor Loop will start working inside a larger Major Loop.
   * The Major Loop isn't a loop really, it's just waits uintil CITER reaches 0. At which point it finishes by applying it's own Major Loop offset.
   * 
   * EDBG - Enable Debug Mode. The DMA stalls when a debugger is plugged in
   */
  
  
  DMA_TCD0_BITER = HowManyTransfers;
  DMA_TCD0_CITER = HowManyTransfers;
  /*
   * CITER - Current Major Loop Interation Cout. It's a counter that starts at the number of transfers we want to do (BITER - Beggining Iteration Count)
   * and then decrements down until 0. When it reaches 0, we know that a transfer has finished and we can fire a INTMAJOR
   * interrupt for example.
   * 
   */
  
  //Minor Loop Offset Enabled, we pretty much always want to use it. And if not, we can juts set it to 0.
  DMA_TCD0_NBYTES_MLOFFYES = BytesToTransfer | DMA_TCD_NBYTES_MLOFFYES_MLOFF(MinorLoopOffset);                               //<---- Crashes here!
  /*
   * NBYTES - How many bytes in total are we transfering per request? Think of it as the i<64 part in a for loop
   * MLOFF - As described before, each time a Minor Loop finishes (a DMA request finishes) we add a offset to the Source and Destination adress  
   * ONLY IF the enable bits are set
   */

  //DMA_TCD0_CSR = DMA_TCD_CSR_INTMAJOR(MajorInterrupt);
  /*
   * BWC - Bandwith Control
   * If we want to slow the DMA down we can stall it every 4 or 8 CPU cycles. Writing a 0 here will have the DMA run at full speed.
   * 
   * INTMAJOR - Setting this to 1 will couse the DMA to fire a interrupt when it finishes the transfer
   */
}

This is one function I am working on to setup the DMA. The clock for the DMA in the CCM_CCGR5 register has already been activated like so: CCM_CCGR5 |= CCM_CCGR5_DMA(CCM_CCGR_ON) before this function. The CPU executes the DMA_CR, DMA_TCD0_BITER, DMA_TCD0_CITER lines just fine, however crashes instantly on the DMA_TCD0_NBYTES_MLOFFYES line. And I can’t figure out why?

What am I missing? I couldn't find (or missed) any mention of a seperate action I have to perform before I can use this register.
Thank You for the help.
 
Which version of Teensyduino are you using? In Arduino, click Help > About to check.

A bug involving direct access to DMA registers was fixed in 1.56. If you have older, install 1.56.

It went undiscovered for so long because pretty much nobody uses DMA this way, and you probably shouldn't either. DMAChannel gives you direct access to the TCD registers, so you do have full access to the hardware capability. If you create code which uses DMA directly, your choice of which channels to use will (probably) conflict with every other DMA-based library. All Teensy libraries using DMA get their channel allocation from DMAChannel, so you can use them together in any program without conflicting channel assignments.
 
Thank You,
While it makes no sense, I think I'll keep using the DMA how I am now in most cases. I feel like I'm learning more this way. However thank you for the suggestion.
 
Hello,
I've updated both the Arduino IDE and Teensyduino as well as cleaned the Temp folder after instalation (Windows 10). Indeed I had the old version however the issue still presists.
 
Update:

I’ve figured out what was wrong. The DMA_TCDx_NBYTES_MLOFFYES register isn’t just a normal register like DMA_TCDx_CR for example. Where each bit always controls the same thing. Here, what individual bits do in the DMA_TCDx_NBYTES register changes based on your other settings. If you have Minor Loop mapping turned off (EMLM bit in the CR register) the entirety of DMA_TCDx_NBYTES will be just the NBYTES value.

However, if you enable Minor Loop Mapping now this register gains two extra special bits. The Source and Destination Minor Loop Offset Enable bits (SMLOE, DMLOE). Because of this, the register now has a new name: DMA_TCDx_NBYTES_MLOFF. And now if you try to write to the old DMA_TCDx_NBYTES register, it will just crash the CPU, because this old register’s bits now define different things. Same thing happens if you now try to write to the DMA_TCDx_NBYTES_MLOFFYES register, because it's special bits and regions haven't been activated yet.

To activate it, you need to set either of the SMLOE or DMLOE bits to 1. The DMA_TCDx_NBYTES_MLOFF register will transform yet again now into the DMA_TCDx_NBYTES_MLOFFYES register. And writing to it will finally work.

Note: I noticed that for the SMLOE/ DMLOE bits to work and not crash the CPU, the Source/Destination addresses must already be set beforehand.
So, if you want to jump into this DMA rabbit hole, I suggest you write your code to operate on registers in this order:
1. Enable the DMA Clock
2. Set stuff in the Control Register
3. Set the total number of transfers (CITER, BITER registers)
4. Set the Source Adress and its other stuff (Attribute, Offset and Last Adress Adjustment registers)
5. Set the Destination Adress (Same here)
6. Set the Minor Loop stuff -> so what I talked about here.

I hope this helps someone with a similar problem.
 
Note: I have normally used the stuff in DMAChannel.h (in both cores\teensy4 and teensy3).

The macros makes it a lot easier to setup a consistent set of a DMAChannel and chains of settings...
 
Back
Top