If I could do this in an 8-bit chip, I probably would, since I know how they work and how to use them: just bit-bang a handful of registers, and the peripherals start doing what I want. Data "magically" shows up in a specific register; and I can get an interrupt for that or poll the flag, depending on priority; and whatever I write to that register goes out on the wire. But that's apparently not how the 32-bit world works, if I'm to believe the complexity of everyone's SDK's even for basic things like bit-banging GPIO.
Using a Teensy 4.1:
How do I get from my 8-bit mentality to a working boilerplate for this project?
I have some pretty tight requirements that I think are possible in principle, but probably not with the standard libraries as they are.
The primary purpose is to undo the acoustic lowpass through a physical barrier that can't be removed. So there's a mic and a speaker with not much distance between them, but a fair amount of attenuation that is also frequency-dependent. I figure that the entire system needs to have about 200us (microseconds) of latency, analog-to-analog, including the DSP. To make that work, I think I have to:
The USB connection does not require low latency. It's mostly to record some useful taps in the DSP chain to analyze later, and to play back a signal to various points for better testing. Nothing live there. So USB can operate normally, except that it needs more bits, more samples, and more channels than the Teensy Audio Library provides. And it needs to interface with the single-sample-NOW, DSP code.
The obvious follow-on effect (to me at least) is that absolutely everything takes a back seat to the DSP code, including USB. I would think that TDM would just work in the background as a hardware function - set it up to run, and then the DSP code reads and writes directly to its data registers. (8-bit mentality) No need for DMA because there's no buffer. The code literally grabs each individual sample as it comes, and delivers each result "just in time". And of course, everything else needs to work around this top-priority ISR that fires constantly at 96kHz, and potentially takes a significant amount of time to finish.
About that "significant amount of time to finish", I'm also thinking to use the Teensy's on-board LED as a "DSP load indicator". Turn it off at the start of the ISR and on and the end, so if it goes out completely, it's trying to do too much. If it's still visible, it's probably okay. And an oscilloscope can use it to verify the actual sample rate.
I wouldn't necessarily have to use the graphical editor or any of the existing Audio Library to build my audio chain. I'm perfectly okay with doing something like this, using my own functions:
Using a Teensy 4.1:
- I've looked at the Arduino framework and IDE, with the idea of modifying it to fit what I need, but I see a lot of moving parts that all have to work together and I'm having a hard time keeping track of them all.
- I've looked at PlatformIO in VS Code, with its own version of the Arduino framework, and it's not much better. It does give me the main() function at least, so that's a plus!
- I've looked at NXP's CMSIS, without Arduino at all, but their license makes me wonder who really owns my project.
How do I get from my 8-bit mentality to a working boilerplate for this project?
I have some pretty tight requirements that I think are possible in principle, but probably not with the standard libraries as they are.
Code:
+-------------------------------------------+
| UART |
+-----+ | - Debug messages during early development |
| PC |<--->| - 115200 8N1 |
| | +-------------------------------------------+
| |
| | +----------------------------------------+
| |<--->| USB |
+-----+ | +---------------------------+ | +-----------------+
| | Serial |<-------------->| Other functions |
| | - Debug messages | | +-----------------+
| | - Manage DSP coefficients | |
| | - Control other functions |<-------------------+
| +---------------------------+ | |
| | V
| +----------------------------------+ | +------------------------------------------------+ +--------------------------------+
| | Audio | | | DSP | | TDM Audio CODEC |
| | - 8 channels each direction |<------->| - No buffer: single sample all the way through |<--->| - 8 channels each direction |
| | - 32-bit integer or float, 96kHz | | | - 32-bit fixed-point or float, 96kHz | | - 24- or 32-bit integer, 96kHz |
| +----------------------------------+ | +------------------------------------------------+ +--------------------------------+
| |
+----------------------------------------+
The primary purpose is to undo the acoustic lowpass through a physical barrier that can't be removed. So there's a mic and a speaker with not much distance between them, but a fair amount of attenuation that is also frequency-dependent. I figure that the entire system needs to have about 200us (microseconds) of latency, analog-to-analog, including the DSP. To make that work, I think I have to:
- Run the CODEC at 96kHz or faster, both to have less time between samples and to allow a less aggressive anti-aliasing filter that itself has fewer samples of latency.
- Run the newest single sample all the way through the DSP, and send it out immediately. No buffer. This essentially creates a 3-sample pipeline, ignoring a handful-of-samples delay as part of the DSP chain, to fine-tune the 200us target:
Code:
In | 0 | 1 | 2 | 3 | 4 | 5 | ...
DSP | - | 0 | 1 | 2 | 3 | 4 | ...
Out | - | - | 0 | 1 | 2 | 3 | ...
The obvious follow-on effect (to me at least) is that absolutely everything takes a back seat to the DSP code, including USB. I would think that TDM would just work in the background as a hardware function - set it up to run, and then the DSP code reads and writes directly to its data registers. (8-bit mentality) No need for DMA because there's no buffer. The code literally grabs each individual sample as it comes, and delivers each result "just in time". And of course, everything else needs to work around this top-priority ISR that fires constantly at 96kHz, and potentially takes a significant amount of time to finish.
About that "significant amount of time to finish", I'm also thinking to use the Teensy's on-board LED as a "DSP load indicator". Turn it off at the start of the ISR and on and the end, so if it goes out completely, it's trying to do too much. If it's still visible, it's probably okay. And an oscilloscope can use it to verify the actual sample rate.
I wouldn't necessarily have to use the graphical editor or any of the existing Audio Library to build my audio chain. I'm perfectly okay with doing something like this, using my own functions:
Code:
void DSP_ISR()
{
LED_OFF();
if (new_coefficients_available())
{
get_new_coefficients();
}
if (coefficients_requested())
{
send_coefficients();
}
//magic numbers are channel numbers
float sample = from_codec(3);
sample = process1(sample, process1_state, process1_coefficients);
to_usb(sample, 2);
sample += from_usb(5);
sample = process3(process2(sample,
process2_state, process2_coefficients),
process3_state, process3_coefficients);
to_codec(sample, 1);
LED_ON();
}