Teensy 4.0 GPIO Manipulation -- Code Help

Status
Not open for further replies.

Setanko

Member
I am trying to implement the DShot protocol, which is summarized pretty well at this link: https://www.speedgoat.com/help/slrt/page/io_main/refentry_dshot_usage_notes. This protocol is pretty similar to Teensy would communicate with the WS2811 LED driver chip, and for this reason I have decided to adapt Paul's OctoWS2811 library for this task. Another benefit of his library is that it uses DMA and thereby frees up the processor, which is another plus for my application (a quadcopter). The general approach I use to generate these signals is explained pretty well at this video (relevant part begins around 12:07 and goes to the end): https://www.youtube.com/watch?v=fNLxHWd0Bvg. To be very brief, the code uses a similar timing setup to the OctoWS2811 to trigger DMA transfers to the Teensy 3.6 GPIO set and clear registers (in my case, since I am on Teensy 4.0, I am using GPIO9_DR_SET and GPIO9_DR_CLEAR).

Getting back to my code. I have read up on the datasheet and made every effort to actually understand Paul's code, so i am not blindly copy-pasting and as such I think I have a pretty decent grasp of what is going on in his code. My main file looks like this (the delayMicroseconds is there to simulate flight controller loop time, as it would be occupied with other things for at least that long):

Code:
#include <Arduino.h>
#include "dshotDMA.h"

void setup() {
  Serial.begin(115200);

  setupDshotDMA();
}

void loop() {
 
 uint16_t escValues[4] = {1500, 1500, 1500, 1500};
 fillDshotBuffer(escValues);
 writeDshot();

 delayMicroseconds(150);
}

What happens in this code (as I figured out through my own debugging) is that the function writeDshot() runs once, but then on the second loop it hangs on the following line forever:

Code:
while((!b1DMAChannel.complete()));

I suspect this is something to do with the fact that I manually set b1DMAChannel as complete on line 88 in the setup function:

Code:
b1DMAChannel.TCD->CSR = DMA_TCD_CSR_DREQ | DMA_TCD_CSR_DONE;

However, it should keep looping infinitely, as the OctoWS2811 code does. Can anybody offer me clues as to what is going wrong with my code? This is for a highschool senior capstone project that is due early next week, and since I have spent the better part of a week struggling with this issue to no avail, I thought I would come here for guidance.

Many thanks,

Michael

P.S. I have included the files dshotDMA.h and dshotDMA.cpp, which contain all relevant methods. The middle function fillDshotBuffer shouldn't be an issue, since it doesn't interfere with DMA and I checked that its output is what it should be, and the last function in the file xbar_connect is a copy of the function Paul wrote for pwm.c in the Teensy 4 core files.

View attachment dshotDMA.cpp View attachment dshotDMA.h
 
Sorry, I have played a reasonable amount with DMA stuff and some timer stuff..

But this is not something that is easy to debug, without a full setup and a lot of hair scratching.

But in any case like this, the things I would do include:
When I setup the DMACHannels, and XBar and Timers... I would dump out the states of many of these things. You will see in several of the libraries that I have played with, I have left a lot of the debug stuff in the code under #ifdef. Example the ILI9488_t3 code, you will see functions like:
Code:
#ifdef DEBUG_ASYNC_UPDATE
void dumpDMA_TCD(DMABaseClass *dmabc)
{
	Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD);

	Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR,
		dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR, 
		dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
}
#endif

void ILI9488_t3::dumpDMASettings() {
#ifdef DEBUG_ASYNC_UPDATE
#if defined(__MK66FX1M0__) 
	dumpDMA_TCD(&_dmatx);
	dumpDMA_TCD(&_dmasettings[0]);
	dumpDMA_TCD(&_dmasettings[1]);
	dumpDMA_TCD(&_dmasettings[2]);
#elif defined(__MK64FX512__)
	dumpDMA_TCD(&_dmatx);
//	dumpDMA_TCD(&_dmarx);
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)  // Teensy 4.x
	// Serial.printf("DMA dump TCDs %d\n", _dmatx.channel);
	dumpDMA_TCD(&_dmatx);
	dumpDMA_TCD(&_dmasettings[0]);
	dumpDMA_TCD(&_dmasettings[1]);
#else
#endif	
#endif

}
Also in cases like this I would not leave home without my Logic Analyzer hooked up to it, to get an idea of what is happening. i.e. is something actually being sent out?

So sorry to say I am probably not much help here.
 
Thanks for the ideas KurtE. I do have one problem with print debugging on the Teensy 4.0, though, which is that nothing shows up on my serial monitor if I don't print repeatedly and include delays between the prints, like so:

Code:
for(uint8_t i = 0; i < 10; i++){

     Serial.println(variable);
     delay(100);
}

However, this repetition and delay in the code makes debugging something like DMA challenging, since it goes on "full speed" in the background and I can therefore only print out variables once or twice before the program freezes up at the line I specified. I don't think I am the only person to have ever run across this issue with Serial printing, as on the PJRC website (https://www.pjrc.com/teensy/td_serial.html) it is noted that "Unlike a standard Arduino, the Teensy Serial object always communicates at 12 Mbit/sec USB speed. Many computes, especially older Macs, can not update the serial monitor window if there is no delay to limit the speed!" It would be helpful if I could print more stuff out without all this repetition and delay, so I could have better "resolution" time-wise and really understand whats going on. Any tips on how to do this? I am using the PlatformIO extension for VS Code to program the board, so I don't know if that is the source of the issue...

Thanks again for the advice.
 
Update: I looked at what is going on with b1DMAChannel.TCD->CITER during the while loop and the value keeps going from 16 down to 1 and back up to 16, instead of stopping when the last transfer finishes (note that it never reaches 0). It does this infinitely, hence the program getting hung up on the line
Code:
while((!b1DMAChannel.complete()));
. I thought
Code:
b1DMAChannel.disableOnCompletion()
would achieve this desired behavior (stopping once the last transfer finishes) -- am I wrong? Also, I hooked up an oscilloscope to the GPIO, and the line stays low, which is strange seeing as how CITER is decreasing (suggesting that transfers are actually going on). The DMA source and destination register addresses seem OK, as I confirmed by looking at the SADDR and DADDR registers. Thoughts?
 
quick note on : "Unlike a standard Arduino, the Teensy Serial object always communicates at 12 Mbit/sec USB speed."

@Paul - not sure if that "12 Mbit/sec USB " has been updated on PJRC.com - if it is worthy.

That applies to Teensy before 4.0. The 1062 processor is a High Speed 480 Mbps device ... and it outputs currently 10-20 times faster than a T_3.6 depending on the PC and SerMon in use to capture that data.

Not sure what the Serial Monitor in use is capable of - but the IDE gets a Teensy SerMon installed that works well on a PC up to maybe 200-500,000 lines of 32 bytes per second. The other good tool is TyCommander that has similar performance.

@KurtE above usually it seems resorts to toggling status and indicator pins on func() entry/exit and hooking up a logic analyzer to monitor the process and failure point.
 
Yes as @defragster mentioned, I do a lot IO pins toggle or HIGH/LOW setting around functions and watch that pin on the logic analyzer.

Also If I think something is going to go boom! And I print out information before it hangs/crashes/resets... I will sometimes put in Serial.flush();
Which will tell the system to wait for the USB output before continuing.

And as I mentioned in first posting, just before I start up the DMA operation, I would try to print out everything that I think might be involved.
Things like DMASettings as I showed that above. Also XBar if involved, probably IO registers associated with the GPIO pins. Registers associated with Timers...

Again everything I think that is involved, such that when it appears to not work, I can double check to see if it looks correct.

Sometimes it turns out to be something as simple as trying to give it a count > of entries that can work, example if you are using some form of linking, than the count can only be something like up to 512, if not max of 32767. Ran into issues earlier where I tried > 32767 and ELINK was set top bits were used for something else... So again I would print out that structure and verify everything, like what is my output?
 
OK defragster and KurtE, I will follow this advice to try debugging further. Using this advice this afternoon and evening, I found that the CITER value for the DMA channels starts at 16 (which I set it to), then goes back down to 1, and then back up to 16, without ever reaching 0. Thus, the "done" flag is never set and the program stays in the while(!b1DMAChannel.complete()) loop forever. I also checked that DADDR is correct (i.e. actually contains the value for GPIO9_DR_SET), which it is. However, when I check the value of GPIO9_DR or GPIO9_DR_SET, they are always unchanged despite the CITER value decreasing. Moreover, when I check the value of the desired output pin with an oscilloscope, the value stays low. To me, it almost seems as if the DMA channel "thinks" it is sending information to the GPIO9_DR_SET register, but the information never gets there. However, this still doesn't explain the infinite looping behavior of CITER, as well as why the counter never reaches 0. Have either of you (or anyone else) encountered this problem in the past? Thanks for all the help so far.
 
@KurtE - is there an easy way to pause DMA xfers? Suppose that is per channel - not just like nointerrupts()?
 
Status
Not open for further replies.
Back
Top