Learning the Peripheral Register Programming Model of the Teensy 4.1

Steve_AU

Member
Guys,
I'm looking at the support code for the Teensy in the Arduino libraries, and I wanted to use the ADC to set up a DMA into a buffer at a fixed acquisition rate, say 10kHz to start with, at 12-bit resolution. Nothing too demanding.

For example, when I try to decipher the programming model for the DMA controller, it's hard to determine what has to be done to make it work. There is the standard DMA, and there is the eDMA. Which should I use and why? Getting the DMA.source() to compile was a trick because it referenced the ADC result register, but which one should be used when I hardware trigger the ADC, and how do I get the pesky warning to go away? First, as intimated, the casting did not work as I may have expected, and then I wasn't even sure that this source register relationship was correct for DMA use. (it didn't work, but was it because of the ADC configuration or the DMA?) What was I missing?

Copilot-derived code samples either wouldn't compile because of missing constants in header files, or samples from the forums (here) failed for me because there was too much "other code" (dependencies) to get a straightforward example up and running. Otherwise, both Copilot and ChatGPT were less than useless; very cooperative and apologetic - yes - but useless! :D They were useless even in getting a simple memory-to-memory 'buffer-to-buffer' DMA example working. Obvious configuration omissions or missing constants were apologised for, and an alternative code example was supplied each time I complained, but none worked. This has taken nearly a week of patience.

So, I decided to take a much closer look. My initial thought was to examine the Teensy register model, starting with the ADC1+2 pair. I wrote a 'register reader' where I could send a register address to a command, have it read the value and then return the register contents to the PC host. Using the IMXRT1060 Reference manual, I found the list of ADC registers and read all their contents, both at power in reset and after an example of initial configuration, to see which register fields were updated and understand the function of each bit.
Now, we are getting somewhere.

So far, I have repeated the process and deduced the existence of the IOMUX_GPR Registers (36 of these), the GPIO Registers (100 of these) and the ADC Registers (47 of these).

I'm a little tired of the guessing game that hunting and pecking for code gives, and I'm prepared for some solid study (the RM is 3500+ pages). The Teensy (iMXRT1062) is a highly refined and capable processor, and I'm sure I will not use its whole compass (the cryptographic functions are probably for MUCH later, for example). However, I do want to know what I'm doing rather than "cutting and pasting" and borrowing and guessing.

The humble ask of the forum members:
I would like a guide that includes programming these peripherals (DMA with ADC), starting very simply and then using the more advanced capabilities. Does anyone know of such a programmer's reference?

Of course, I'd like to understand the other registers, such as those supporting SPI and such (although the code examples for SPI work OOTB, there is much more there, I'm sure).

Meanwhile, I'd be happy to begin with a solid appreciation of the ADC/DMA registers and simple steps to goodness, and if there is such a guide, I'd be happy to buy and download it.
Steve
======================
Below is a sample of how I read the registers after passing an address to the function over the serial port, in an XML marked-up command, selected by the constant "GET_IMXRT1062_REGISTER". I'm not worried about this part. Code like this has worked for a number of years. And yes, I have used the old C-style unsafe functions. Also, yes - I have used static global allocation for the buffer. None of these are my worry for now.

C:
#include "xml_parser.h"
#include "message.h"
#include "xml_parser.h"
extern T_xml_message xml_message;
extern T_xml_errors xml_errors;
char buff[768];  //
float tell_tale = 0;
extern volatile uint16_t v;
//--------------------------------------------------------------------------------
int parse_xml_command(void) {
  int command;
  int hash_val1;
  int hash_val2;
  int seq;
  char reg_desc[32];
  char reg_addr[32];
  char *rd = &reg_desc[0];
  char *ra = &reg_addr[0];
  uint32_t register_address;

  hash_val1 = atoi(xml_message.hash);               // convert the hash value into an integer
  sprintf(buff, "<msg>%s</msg>", xml_message.msg);  // re-combine the message (minus the hash value)
  hash_val2 = gen_hash(buff);                       // re-calculate the hash value
  seq = atoi(xml_message.seq);            // random sequence number associated with this message. We will add 1 to it on return so the PC host knows which it is.
  if (hash_val1 == hash_val2) {                                // compare the re-calculated hash value with the original value and proceed if they match.
    command = atoi(xml_message.cmd);                           // now convert the 'cmd' value into an integer
    switch (command) {                                         // select the command with its optional data pulled in from the original message.
      case SET_SOFTWARE_RESET: SCB_AIRCR = 0x05FA0004; break;  // cause a full processor reset
      case GET_EXEC_GCODE:
        {
          // unpack_gcode_block(xml_message.data);       // do a CNC G-Code updload to the machine. Todo: include this function and debug.(later)
        }
        break;
      case GET_MACHINE_POSITION_XYZ:  // this function reads the state of the xyz axes from the machine state.
        {
          sprintf(buff, "<xyz>%d,Teensy,4.1</xyz>", v);  // for now, just getting the integer value 'v' to return to the host with some strings to display. Runs every 60mS
          host_reply(command, seq, buff);
        }
        break;
      case GET_IMXRT1062_REGISTER:                                                         // generic register read. Note - we use the register notation in IMXRT1060RM Rev. 3, 07/2021.
        {                                                                                  // <reg>ADC1_HS,0x1F</reg>);
          strcpy_xml_string("reg", xml_message.data, rd);                                  // decode the register mnemonic (the register descriptor) example "ADC1_RA"
          strcpy_xml_string("addr", xml_message.data, ra);                                 // decode the register address
          register_address = atoi(ra);                                                     // convert the register address string into a 32 bit integer
          sprintf(buff, "<reg>%s=%lx</reg>", rd, *(volatile uint32_t *)register_address);  // access the register directly and print the contents as hex. All registers are 32 bits wide.
          host_reply(command, seq, buff); // return a formatted reply to the PC host
        }
        break;

      default: host_reply(command, seq, "Unknown command"); break;
    }
  } else {
    xml_errors.hash_mismatch = 1;                          // integrity check failed - set the 'xml_errors.hash_mismatch = 1'
    host_reply(atoi(xml_message.cmd), seq, "hash error");  //
    return (false);
  }
  return (true);
}
 
For accessing the peripherals at register level, NXP's 3522 page reference manual really is the best info. As far as a I know, nobody has written a more friendly guide.

I can point you to the ADC with DMA code that's inside the audio library. Maybe a working example might help?

Just starting with the simple analogRead source code might be a better first step? No DMA, just simply tell the ADC to start a measurement, wait for it to complete, and read the result. Also look at the analog_init() function which turns on the ADC hardware and starts its self calibration process. Hopefully this simple code and reading chapter 66 can help explain how it works.

On the reference manual, if you got it from NXP's website, get the copy from the PJRC Teensy 4.1 page (first link in "Technical Information near the end). That copy has annotations for Teensy which make reading it a bit easier.

Regarding the GPIO and pin mux registers, you generally just configure those so the digital circuitry doesn't interfere. Inside the chip the pins directly connect to the ADC's analog inputs, so you don't really do anything with the digital GPIO stuff other than keep it from messing everything up, since it's also physically connected to the pin.

On your DMA vs eDMA question, they're the same thing. But to be technically correct, most of the really high speed peripherals (USB, Ethernet, SDIO, etc) have their own specialized bus master DMA built in. So the term "DMA" can be kind of generic to mean either. But for all the peripherals listed in Table 4-3 starting at the bottom of page 52, they all use the "eDMA" controller described by chapter 6... which implements a basic idea of memory-to-memory copy where each hardware trigger causes the minor loop to run.

Something you won't see in the reference manual (but I really ought to add more annotations to explain it) is DMAChannel.h in the core library. Even if you intend to directly control the DMA, you should create a DMAChannel instance to gain access to 1 of the DMA controller's channels. You still get a pointer to the TCD so you can control everything. If you do it this way, your code will (probably) work together with the many other DMA-based libraries (Audio, OctoWS2811, SmartMatrix, etc) because they all let DMAChannel allocate which channel to use. If you just hard code specific DMA channel registers, then your code is likely to conflict if ever used together with libraries that use DMA channels.
 
Thanks so much, Paul.
The power of this thing enthuses me. It's enormous. I have spent a few nights agog at the detailed reference manual and its details (from your site).

As ever, these things seem daunting at the start, so it's going to be a case of chewing it and looking and chewing some more. There's no easy way.

I'll follow your links and have another go at it, more enlightened but careful. One day the penny will drop, and I'll have a feel for it. It has happened before, but this processor is streets ahead of anything I have played with (STM32 series, black pill etc). Somewhere in there will be gold nuggets, and I'm close, but not that close! :)

Over the past year or so, I have bought several 4.1s as a backstop for further projects, and I probably have 30 of them by now. Your boards are such a good start and help immensely with blinking light projects, sound processing and a bunch of other interesting ideas, and I wanted to thank you for these insights and for your effort in helping me bootstrap into this stuff (still a way to go, but it's appreciated!). When I had a spare couple of hundred $ over the last years or so, I'd grab half a dozen of them at a time (now a bit of a collection).

My only problem is that I feel daunted when I use the 'easy way', Arduino introductory abstracted code that gets me started, where I don't really understand the hidden detail (and I want to). There will come a time (I'm sure) when I don't want to reinvent the wheel, and I will feel comfortable using said abstract code because I will understand what it does and the principles upon which it is created. Until then, it's a process of discovery and familiarity. There are no real shortcuts.

Thanks again,
Steve
 
@Steve_AU: Have you seen <this> posted write-up on using DMA on the Teensy 4 ?? The post is from a few years back, but should still be pertinent. You may want to browse the entire thread for more details as well.

Mark J Culross
KD5RXT
 
Hi all,
Your examples and Paul's guidance are great stuff. I have a solid weekend of review now. Thanks so much. I'll get stuck into it and share any success (less loquaciously). This processor is worth the effort and time investment. I'm going to start with Paul's suggestion to kick off the ADC using direct register access and the simple analogRead source code. I need to unveil the mystery, and there is only one way to do it. This is a great start as I have at least a 'passing' friendship with the ADC registers so that will be excellent. The DMA-related material will follow soon after.
Steve
@Steve_AU: Have you seen <this> posted write-up on using DMA on the Teensy 4 ?? The post is from a few years back, but should still be pertinent. You may want to browse the entire thread for more details as well.

Mark J Culross
KD5RXT
Will do Mark!!
On howto “use the ADC to set up a DMA into a buffer at a fixed acquisition rate, say 10kHz to start with, at 12-bit resolution. “

Yes, too demanding for ChatGPT etc…

But here’s an example that lets you do 12 bits ADC, 1, 2 or 4 channels, up to 1Ms/s. timer triggered, to DMA.

Thanks Sicco This will come later, but yes, I will look at this stuff. Thanks!
 
@Steve_AU: Have you seen <this> posted write-up on using DMA on the Teensy 4 ?? The post is from a few years back, but should still be pertinent. You may want to browse the entire thread for more details as well.

Mark J Culross
KD5RXT
Mark, this is GOLD. I have had a browse, and it has all the things I was wondering about examined and unpacked. The GPIO MUX registers and how that relates to pin access, etc., how to deal with DMA, and how to imagine it put together - are all here. This feels great. I really appreciate it. The chap who wrote it echoes my own sense of bewilderment and carefully goes through it all in relatable English. I hope to return the contribution one day.
Steve
 
Hi Paul,
I have been looking at the "IMXRT.h" file in detail, and I think I may have understood some of the clever things you have done here.

This is simply a query to see if I 'have it' at some elementary level.

Firstly - you start by listing all the base addresses of the register-groups, starting with
#define IMXRT_CMP1_ADDRESS 0x40094000
. . .
. . . and ending with
#define IMXRT_XTAL0SC24M_ADDRESS 0x400D8000

Secondly, you define a structure as a 'type' for the different 32-bit, 16-bit, and 8-bit address separations, each of which supports the various offset distances, baked into each register array by the size of the variable types.
Some structure types I saw included:
  • IMX_REGISTER32_t (32-bit address offsets)
  • IMX_REGISTER16_t (16-bit address offsets)
  • IMX_REGISTER8_t (8-bit address offsets)
Thirdly - to enable a pointer to increment by the correct register offset, you named each register address within that structure so the 'variable name' refers to its own offset value in the list. It then resolves the pointer address cast (arithmetic) to the right (array) address because of its position in the structure. (nice!)

As an example, the "#define ADC1_HC3" will resolve to an address formed by the "IMXRT_ADC1_ADDRESS" (0x400C4000), plus the "HC3" offset (fourth in the list - 0x00C). Then, the pointer resolves to "IMXRT_ADC1" plus "HC3" (offset006).

So, when determined by the pointer-address-plus-struct-notation (IMXRT_ADC1.HC3), you get the address your label 'said' it would. Ergo 0x400C400C. This makes sense if I have the gist. So 'offset006' is also 'HC3' or '0x0C'.

There are other constructs where you also offset to a 'bit address' within a register. In some cases, the macro also takes an argument (that looks like an array index thing to me) and then performs a shift left-count to point to a particular bit in the addressed register - to either AND it to 0 or write it to a 1. (I think DMAEN bit address uses that idea.) Anyway, the register addressing scheme looks palpable and understandable.

My next worry is determining why we set which bit to what and when (the logic part). I'm following the adcRead() code and consulting the Ref Manual next.

Thanks again,
Steve
 
Guys,
I'm looking at the support code for the Teensy in the Arduino libraries, and I wanted to use the ADC to set up a DMA into a buffer at a fixed acquisition rate, say 10kHz to start with, at 12-bit resolution. Nothing too demanding.

For example, when I try to decipher the programming model for the DMA controller, it's hard to determine what has to be done to make it work. There is the standard DMA, and there is the eDMA. Which should I use and why? Getting the DMA.source() to compile was a trick because it referenced the ADC result register, but which one should be used when I hardware trigger the ADC, and how do I get the pesky warning to go away? First, as intimated, the casting did not work as I may have expected, and then I wasn't even sure that this source register relationship was correct for DMA use. (it didn't work, but was it because of the ADC configuration or the DMA?) What was I missing?

Copilot-derived code samples either wouldn't compile because of missing constants in header files, or samples from the forums (here) failed for me because there was too much "other code" (dependencies) to get a straightforward example up and running. Otherwise, both Copilot and ChatGPT were less than useless; very cooperative and apologetic - yes - but useless! :D They were useless even in getting a simple memory-to-memory 'buffer-to-buffer' DMA example working. Obvious configuration omissions or missing constants were apologised for, and an alternative code example was supplied each time I complained, but none worked. This has taken nearly a week of patience.

So, I decided to take a much closer look. My initial thought was to examine the Teensy register model, starting with the ADC1+2 pair. I wrote a 'register reader' where I could send a register address to a command, have it read the value and then return the register contents to the PC host. Using the IMXRT1060 Reference manual, I found the list of ADC registers and read all their contents, both at power in reset and after an example of initial configuration, to see which register fields were updated and understand the function of each bit.
Now, we are getting somewhere.

So far, I have repeated the process and deduced the existence of the IOMUX_GPR Registers (36 of these), the GPIO Registers (100 of these) and the ADC Registers (47 of these).

I'm a little tired of the guessing game that hunting and pecking for code gives, and I'm prepared for some solid study (the RM is 3500+ pages). The Teensy (iMXRT1062) is a highly refined and capable processor, and I'm sure I will not use its whole compass (the cryptographic functions are probably for MUCH later, for example). However, I do want to know what I'm doing rather than "cutting and pasting" and borrowing and guessing.

The humble ask of the forum members:
I would like a guide that includes programming these peripherals (DMA with ADC), starting very simply and then using the more advanced capabilities. Does anyone know of such a programmer's reference?

Of course, I'd like to understand the other registers, such as those supporting SPI and such (although the code examples for SPI work OOTB, there is much more there, I'm sure).

Meanwhile, I'd be happy to begin with a solid appreciation of the ADC/DMA registers and simple steps to goodness, and if there is such a guide, I'd be happy to buy and download it.
Steve
======================
Below is a sample of how I read the registers after passing an address to the function over the serial port, in an XML marked-up command, selected by the constant "GET_IMXRT1062_REGISTER". I'm not worried about this part. Code like this has worked for a number of years. And yes, I have used the old C-style unsafe functions. Also, yes - I have used static global allocation for the buffer. None of these are my worry for now.

C:
#include "xml_parser.h"
#include "message.h"
#include "xml_parser.h"
extern T_xml_message xml_message;
extern T_xml_errors xml_errors;
char buff[768];  //
float tell_tale = 0;
extern volatile uint16_t v;
//--------------------------------------------------------------------------------
int parse_xml_command(void) {
  int command;
  int hash_val1;
  int hash_val2;
  int seq;
  char reg_desc[32];
  char reg_addr[32];
  char *rd = &reg_desc[0];
  char *ra = &reg_addr[0];
  uint32_t register_address;

  hash_val1 = atoi(xml_message.hash);               // convert the hash value into an integer
  sprintf(buff, "<msg>%s</msg>", xml_message.msg);  // re-combine the message (minus the hash value)
  hash_val2 = gen_hash(buff);                       // re-calculate the hash value
  seq = atoi(xml_message.seq);            // random sequence number associated with this message. We will add 1 to it on return so the PC host knows which it is.
  if (hash_val1 == hash_val2) {                                // compare the re-calculated hash value with the original value and proceed if they match.
    command = atoi(xml_message.cmd);                           // now convert the 'cmd' value into an integer
    switch (command) {                                         // select the command with its optional data pulled in from the original message.
      case SET_SOFTWARE_RESET: SCB_AIRCR = 0x05FA0004; break;  // cause a full processor reset
      case GET_EXEC_GCODE:
        {
          // unpack_gcode_block(xml_message.data);       // do a CNC G-Code updload to the machine. Todo: include this function and debug.(later)
        }
        break;
      case GET_MACHINE_POSITION_XYZ:  // this function reads the state of the xyz axes from the machine state.
        {
          sprintf(buff, "<xyz>%d,Teensy,4.1</xyz>", v);  // for now, just getting the integer value 'v' to return to the host with some strings to display. Runs every 60mS
          host_reply(command, seq, buff);
        }
        break;
      case GET_IMXRT1062_REGISTER:                                                         // generic register read. Note - we use the register notation in IMXRT1060RM Rev. 3, 07/2021.
        {                                                                                  // <reg>ADC1_HS,0x1F</reg>);
          strcpy_xml_string("reg", xml_message.data, rd);                                  // decode the register mnemonic (the register descriptor) example "ADC1_RA"
          strcpy_xml_string("addr", xml_message.data, ra);                                 // decode the register address
          register_address = atoi(ra);                                                     // convert the register address string into a 32 bit integer
          sprintf(buff, "<reg>%s=%lx</reg>", rd, *(volatile uint32_t *)register_address);  // access the register directly and print the contents as hex. All registers are 32 bits wide.
          host_reply(command, seq, buff); // return a formatted reply to the PC host
        }
        break;

      default: host_reply(command, seq, "Unknown command"); break;
    }
  } else {
    xml_errors.hash_mismatch = 1;                          // integrity check failed - set the 'xml_errors.hash_mismatch = 1'
    host_reply(atoi(xml_message.cmd), seq, "hash error");  //
    return (false);
  }
  return (true);
}
If you're working with the IMXRT1062 at this level, you might want to consider purchasing an NXP development kit and setting up the MCUXpresso environment for checking it out. It's not a Teensy with Teensyduino of course. But it's a powerful way to become familiar with the chip and its features, which seems to be what you're looking for before applying in the Teensy environment.

I used MCUXpresso on a project 5 years ago, though for a different processor and dev kit (LPC54628). More recently, I worked with a similar dev kit and environment for STM32Cube processors. MCUXpresso and STM32CubeIDE are both based on an Eclipse model, and MCUXpresso also now includes a version for Visual Studio Code. There's definitely a learning curve for working with these dev kits and environments, but it seems you're already on that path.

Working in a tool such as MCUXpresso provides a powerful development environment, with the ability to set breakpoints, look at registers, and all the other features one expects; and lots of example code to demonstrate various features of the chip. Though I'm not familiar with the configuration options for the chip available in MCUXpresso, I made extensive use of the tools in STM32IDE which allow you to configure all sorts of processor options (port assignments, clock routing, etc.) and then generate the base startup code to configure the chip. A similar configuration tool is provided in the MCUXpresso environment.

I know this doesn't relate specifically to Teensy, but it might provide a much better understanding of the underlying features and usage of the IMXRT1062 you're examining. You'll need to spend a bit of $ (the dev kit is around $120, but includes a touchscreen and all sorts of IO). Not sure how far you want to go with this, or if my suggestions are helpful, but I hope it's of some use.

MCUXpresso IDE:

MCUXpresso Config Tools: Pins, Clocks and Peripherals:

i.MX RT1060 Evaluation Kit:

Digikey part #:
568-MIMXRT1060-EVKB-ND ($121.17)
 
Last edited:
Hi @Neurofrantic - this is very sensible advice, and $120 USD is a small amount to pay for this. I have worked with the Eclipse IDE and the STM32 framework over the last few years, and I liked the insight the 'MX' tool gave to programming peripherals, where the necessary setting to make some stuff work was pretty much instant and plonked directly into the code. (ADC to DMA, for example! :) ) Most examples worked out of the box. The Eclipse IDE was a little cumbersome at first. However, separating modules like a proper IDE was so nice. Arduino IDE can do that now, which is great, of course.

The support from the Teensy programming environment that people here have written is invaluable, but you have to consult the manual quite extensively for any 'whys and wherefores' at the lower levels (trying to infer the logic of what they have written). I don't want to be stuck at that 'introductory level' for too long. Even though much work has been done here, when I understand the background better, using the pre-canned code base will be much more comfortable (less guesswork). Paul S is a "gun", and what he has done here is legion, given the immensity of the IMXRT1062 and the need (vision) to democratise the NXP chip(s). The NXP processor for Teensy 4.x is in a league of its own. I came for the 600 megahertz clock speed and stayed for the extensive sophistication of this machine :D My next step will be to move back to the Eclipse IDE with the MCUXpresso tools. Great suggestion. I had not realised that an analogue like this existed.
Kind regards,
Steve
-----
Update - I just bought the MCUXpresso and IMRT1060 evaluation board from NXP. You were right, this is the way to goodness. They want to know what you're using it for and when you're going to go to production - me? Production? I had to lie, of course. However I do want to make a CNC controller using the Teensy board as the backbone, but that's a fair way away. I have made such controllers since the days of the Z80 and the 8051 (back in the 9'0's) with a detour into PIC32, STM32, and now Teensy 4.x. It's not my day job. I just like the elegance of these things and to find out what makes them tick.
 
Last edited:
You move fast, Steve_AU! Pleased to hear that you will be checking out the evaluation board. I look forward to hearing more about your experience with the setup, and hope you find it helpful. I agree that these devices in the current 600MHz+ range are quite impressive. Like you, I have a very long history of working with chips of all sorts. I've done contract work on embedded systems for many years in support of my efforts in developing SW/HW for music and arts related projects for myself and other artists. But these newer generation MCUs just blow me away with their design and capabilities. A 600MHz Teensy 4.1 is really capable of doing things one needed a full-scale desktop or laptop 20-25 years ago (last project on the STM32 series was doing 60Hz screen updates, running on a 200MHz clock, with plenty of BW to handle the actual work of the system it was running on, which was an industrial meter and controller).

I've never been a big fan of open-source, finding coding often of poor quality and absent even basic documentation or comments. But I've now been using Teensy for several years, and though I still find it frustrating, as do you, when attempting to understand or analyze code in the libraries, or from other sources, I've come to have great respect for the efforts so many have put in and made freely available to others. In the end, the fact that they actually work (in most cases) so that you can focus instead on the task at hand, is the overriding consideration. So I have to give my heartfelt thanks and respect to all those whose contributions over the years in support of the community have been so essential and important, and especially Paul S., whose work has enabled so many of us to succeed in our own individual efforts.

Still, in the end, one sometimes requires something a bit more, and I hope you'll find what you need in working with the dev kit.
 
Hi Neurofrantic,
I'm still waiting for the board. Meanwhile, I have been looking at the stuff Paul linked above.

Your sentiments are well appreciated here:-

"So I have to give my heartfelt thanks and respect to all those whose contributions over the years in support of the community have been so essential and important, and especially Paul S., whose work has enabled so many of us to succeed in our own individual efforts."

There is no doubt about this score. Paul is the most obliging individual, and I have scanned his and other's GitHub code repositories. There is plenty of gold to see there. My only worry is the detail on the 'why' questions since there is always a context that eludes me on the first few passes.

My approach to 'ADC with DMA' starts with hardware (interrupt event) ADC triggers and has a few steps.

First, let's start with a timer interrupt that calls 'adcConvert()' within the interrupt body and waits for the conversion.

Code:
void timerFunc() {
  digitalToggle(6); // see the timer event on CRO
  v = analogRead(A17);
}

That works! I can read the value 'v' anywhere outside the interrupt. The "poor design problem" is that the interrupt will always wait for the conversion, whereas interrupts ought to be as short as possible. (trigger and go). Hunting the code sequence in analogRead() has revealed a few gems about how the analogue pin and ADC are selected, calibrated, and initialised (for example), which are hidden if you simply call analogRead().

Next, I'd like to start the ADC conversion from the timer interrupt and pick up the conversion somewhere else when it has finished. Immediately, I'm in ETC and GPIO_MUX territory, and so the configuration complexity from a call to analogRead() has unfolded at least 5 times over. I'm not complaining, as these options are beneficial/powerful; however, reading code that has already been written and debugged at the register level doesn't always show me the reasoning or dependencies behind it. There is a story within that is missing to me. If Paul or the other contributors had to write sufficiently detailed comments along these lines, they would be time-prohibitive and possibly confusing, so it's a complex problem for all concerned.

As mentioned above, I have realised that I need to understand at least the GPIO_MUX logic and the ETC logic to map the right (timer event) trigger to the ADC and then attach the right interrupts to the ADC COCO-derived flag-triggered event (trigger an interrupt or a DMA event).

In that case, I'm 'almost ready' to attempt to write the code from scratch; however, as I go, I see what Paul or pedvide has done, and it makes better sense little by little. It just takes time. After a few more passes, I'll get more familiar with it. Hopefully, the MCUXpresso material will outline the patterns of use, and I can jump into it with the development board next week. Thanks again for that suggestion!!

Stevo
 
Hi Sicco,
On howto “use the ADC to set up a DMA into a buffer at a fixed acquisition rate, say 10kHz to start with, at 12-bit resolution. “

Yes, too demanding for ChatGPT etc…

But here’s an example that lets you do 12 bits ADC, 1, 2 or 4 channels, up to 1Ms/s. timer triggered, to DMA.

I just copied and pasted the whole module into the Arduino environment, and it compiled and ran on the first try!! There are crucial configurations in there that I wanted to extract and try by themselves, particularly the ETC mechanism to trigger the ADC from a timer interrupt and then trigger an interrupt to push the result to a global variable for later reading. (This is step-by-step to where your linked code is up to for full DMA function.)
I'll unpack this gem for its insights. Thanks for this material. It's great and very much appreciated.

Stevo
 
Back
Top