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!
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.
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!
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 = ®_desc[0];
char *ra = ®_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);
}