SmittyHalibut
Member
tl,dr: When I declare an `AudioInputI2S` or `AudioOutputI2S` object and upload the code, my Teensy 4.1 won't boot: no USB devices (host computer doesn't even see it), no GPIO activity, nothing. It doesn't even get to `setup()`. Comment out the `AudioInputI2S` declaration, and it boots right up. Because I have no serial console, I'm not sure how to debug this.
Details:
* It happens with either Input or Output I2S objects.
* It does NOT happen with other `AudioStream` objects, like `AudioInputUSB` or `AudioSynthWaveformSine`
* It happens even if the instantiation COULD happen, but doesn't. If I wrap the `new AudioInputI2S()` with a conditional the compiler can't optimize out (eg: `if (digitalRead(PIN_TIED_TO_GROUND) == HIGH)`), it won't boot. Even though the `new AudioInputI2S()` never gets called.
The fact that it happens even when the `new AudioInputI2S()` never gets called, and the fact that it's happening before `setup()` runs, tells me that it's something that's happening with `static` code/constructors. The fact that it's NOT happening with other `AudioStream` objects, tells me it's not in `AudioStream` parent-class code, it's in the `AudioInputI2S` child-class code.
So looking in `class AudioInputI2S` for static code, I see:
I'm willing to bet that defining some static variables, all set to 0 or NULL, isn't doing this, nor is defining (but not calling) a static function (ISR.) So the most likely candidate is `static DMAChannel dma;` which will have a constructor
It's initialized as:
Which will call this constructor:
Which doesn't even do anything!
`DMAChannel` is a child of `DMABaseClass`, which only has this constructor:
Again, does nothing.
Going back to `DMAChannel()` constructor: If for some reason it DID get to the point where it called `begin()`, then there's some code in `DMAChannel::begin()` that looks like it might be problematic, but looks good:
I see a `while(1)` and messing with IRQs. Those are the kinds of things that could lead to lock-ups, but in my brief review of the code, I don't see any obvious problems. Never mind the fact that it doesn't look like it gets called.
So, I'm stumped. Any ideas? Am I even looking in the right place?
Here's the real kick-to-the-shins: While writing up this post, I pulled out all my Audio code to put in a dummy program to reproduce the problem AND THE SILLY THING WORKS. IT DOESN'T FAIL. I'm happy to accept that this is a problem in my code, but I can't for the life of me figure out what I'm doing that would conflict with any of this static code in `AudioInputI2S` or `DMAChannel`. Where to even look for that?
Here's my dummy code anyway. This is from Platform.IO. `SOARAudio.h` and `.cpp` are copied directly from my larger project, `main.cpp` just does what it needs to call it. Hopefully someone will see something in my code that's a race condition? Or a resource consumption that when stripped down has plenty of resources? Or something...
I appreciate any help you can provide, or suggests on where to look next. Thank you!
73 de N6MTS
-Mark
main.cpp:
SOARAudio.h:
SOARAudio.cpp:
Details:
* It happens with either Input or Output I2S objects.
* It does NOT happen with other `AudioStream` objects, like `AudioInputUSB` or `AudioSynthWaveformSine`
* It happens even if the instantiation COULD happen, but doesn't. If I wrap the `new AudioInputI2S()` with a conditional the compiler can't optimize out (eg: `if (digitalRead(PIN_TIED_TO_GROUND) == HIGH)`), it won't boot. Even though the `new AudioInputI2S()` never gets called.
The fact that it happens even when the `new AudioInputI2S()` never gets called, and the fact that it's happening before `setup()` runs, tells me that it's something that's happening with `static` code/constructors. The fact that it's NOT happening with other `AudioStream` objects, tells me it's not in `AudioStream` parent-class code, it's in the `AudioInputI2S` child-class code.
So looking in `class AudioInputI2S` for static code, I see:
Code:
protected:
static bool update_responsibility;
static DMAChannel dma;
static void isr(void);
private:
static audio_block_t *block_left;
static audio_block_t *block_right;
static uint16_t block_offset;
I'm willing to bet that defining some static variables, all set to 0 or NULL, isn't doing this, nor is defining (but not calling) a static function (ISR.) So the most likely candidate is `static DMAChannel dma;` which will have a constructor
It's initialized as:
Code:
DMAChannel AudioInputI2S::dma(false);
Which will call this constructor:
Code:
DMAChannel(bool allocate) {
if (allocate) begin();
}
`DMAChannel` is a child of `DMABaseClass`, which only has this constructor:
Code:
DMABaseClass() {}
Going back to `DMAChannel()` constructor: If for some reason it DID get to the point where it called `begin()`, then there's some code in `DMAChannel::begin()` that looks like it might be problematic, but looks good:
Code:
void DMAChannel::begin(bool force_initialization)
{
uint32_t ch = 0;
__disable_irq();
if (!force_initialization && TCD && channel < DMA_MAX_CHANNELS
&& (dma_channel_allocated_mask & (1 << channel))
&& (uint32_t)TCD == (uint32_t)(0x400E9000 + channel * 32)) {
// DMA channel already allocated
__enable_irq();
return;
}
while (1) {
if (!(dma_channel_allocated_mask & (1 << ch))) {
dma_channel_allocated_mask |= (1 << ch);
__enable_irq();
break;
}
if (++ch >= DMA_MAX_CHANNELS) {
__enable_irq();
TCD = (TCD_t *)0;
channel = DMA_MAX_CHANNELS;
return; // no more channels available
// attempts to use this object will hardfault
}
}
channel = ch;
[...other stuff...]
So, I'm stumped. Any ideas? Am I even looking in the right place?
Here's the real kick-to-the-shins: While writing up this post, I pulled out all my Audio code to put in a dummy program to reproduce the problem AND THE SILLY THING WORKS. IT DOESN'T FAIL. I'm happy to accept that this is a problem in my code, but I can't for the life of me figure out what I'm doing that would conflict with any of this static code in `AudioInputI2S` or `DMAChannel`. Where to even look for that?
Here's my dummy code anyway. This is from Platform.IO. `SOARAudio.h` and `.cpp` are copied directly from my larger project, `main.cpp` just does what it needs to call it. Hopefully someone will see something in my code that's a race condition? Or a resource consumption that when stripped down has plenty of resources? Or something...
I appreciate any help you can provide, or suggests on where to look next. Thank you!
73 de N6MTS
-Mark
main.cpp:
Code:
#include <Arduino.h>
#include "SOARAudio.h"
SOARAudio *audio;
void setup() {
Serial.begin(115200);
while (!Serial) delay(500);
Serial.println("Getting started, waiting 2 seconds for console.");
delay(2000);
pinMode(31, INPUT_PULLUP);
if (digitalRead(31) == LOW) {
audio = new SOARAudio();
}
}
void loop() {
Serial.println("Loop top");
delay(1000);
}
SOARAudio.h:
Code:
#ifndef SOAR_AUDIO_H
#define SOAR_AUDIO_H
#include <Audio.h>
#include <Wire.h>
#include <map>
class SOARAudio {
public:
SOARAudio();
const static uint8_t num_audio_buffers = 12;
std::map<String, AudioStream *> Streams;
std::map<String, AudioConnection *> Connections;
std::map<String, AudioControl *> Controllers;
// Needs to be public so the Audio library can see it.
static DMAMEM audio_block_t data[num_audio_buffers];
protected:
void begin_v2_2b();
};
#endif // SOAR_AUDIO_H
SOARAudio.cpp:
Code:
#include "SOARAudio.h"
DMAMEM audio_block_t SOARAudio::data[num_audio_buffers];
SOARAudio::SOARAudio() {
Serial.printf("Setting up audio for v2.2b hardrware.");
begin_v2_2b();
}
void SOARAudio::begin_v2_2b() {
/*
AudioSynthWaveformSine TestTone440Hz; //xy=621,364
AudioInputUSB USB_in; //xy=643,416
AudioInputI2S CODEC_in; //xy=702,555
AudioMixer4 mixer2; //xy=814,457
AudioMixer4 mixer1; //xy=815,381
AudioOutputUSB USB_out; //xy=903,554
AudioOutputI2S CODEC_out; //xy=957,420
AudioConnection patchCord1(TestTone440Hz, 0, mixer1, 0);
AudioConnection patchCord2(TestTone440Hz, 0, mixer2, 3);
AudioConnection patchCord3(USB_in, 0, mixer1, 3);
AudioConnection patchCord4(USB_in, 1, mixer2, 0);
AudioConnection patchCord5(CODEC_in, 0, USB_out, 0);
AudioConnection patchCord6(CODEC_in, 1, USB_out, 1);
AudioConnection patchCord7(mixer2, 0, CODEC_out, 1);
AudioConnection patchCord8(mixer1, 0, CODEC_out, 0);
AudioControlWM8731 wm8731; //xy=825,645
*/
bool ret;
// Streams
AudioSynthWaveformSine *testTone = new AudioSynthWaveformSine();
testTone->amplitude(0.0);
testTone->frequency(440.0);
testTone->phase(0.0);
Streams["TestTone440Hz"] = testTone;
Streams["USB_in"] = new AudioInputUSB();
Streams["USB_out"] = new AudioOutputUSB();
Serial.println("Allocated USB Streams.");
Streams["I2S_in"] = new AudioInputI2S();
Streams["I2S_out"] = new AudioOutputI2S();
Serial.println("Allocated I2S Streams.");
Streams["MixerUplink"] = new AudioMixer4();
Streams["MixerDownlink"] = new AudioMixer4();
Serial.println("Allocated Mixers.");
// Connections
Connections["TestToneToUplink"] = new AudioConnection(*Streams["TestTone440Hz"], 0, *Streams["MixerUplink"], 0);
Connections["TestToneToDownlink"] = new AudioConnection(*Streams["TestTone440Hz"], 0, *Streams["MixerDownlink"], 0);
Connections["USBToUplink"] = new AudioConnection(*Streams["USB_in"], 0, *Streams["MixerUplink"], 1);
Connections["USBToDownlink"] = new AudioConnection(*Streams["USB_in"], 1, *Streams["MixerDownlink"], 1);
Connections["I2SToUSBUp"] = new AudioConnection(*Streams["I2S_in"], 0, *Streams["USB_out"], 0);
Connections["I2SToUSBDown"] = new AudioConnection(*Streams["I2S_in"], 1, *Streams["USB_out"], 1);
Connections["MixerUplinkToI2S"] = new AudioConnection(*Streams["MixerUplink"], 0, *Streams["I2S_out"], 0);
Connections["MixerDownlinkToI2S"] = new AudioConnection(*Streams["MixerDownlink"], 1, *Streams["I2S_out"], 1);
// Controllers
AudioControlWM8731 *wm8731 = new AudioControlWM8731();
ret = wm8731->enable();
Serial.printf("wm8731.enable(): %s\n", ret ? "True" : "False");
ret = wm8731->volume(0.0); // This is the headphone amp, which we aren't using.
Serial.printf("wm8731.volume(): %s\n", ret ? "True" : "False");
ret = wm8731->inputLevel(1.0);
Serial.printf("wm8731.inputLevel(): %s\n", ret ? "True" : "False");
ret = wm8731->inputSelect(AUDIO_INPUT_LINEIN);
Serial.printf("wm8731.inputSelect(): %s\n", ret ? "True" : "False");
Controllers["CODEC"] = wm8731;
// This is a #define that expects to be run outside of a class. We have to do this
// differently to make sure `data` doesn't leave scope.
//AudioMemory(12);
// expands to:
//{ static DMAMEM audio_block_t data[12]; AudioStream::initialize_memory(data, 12); }
AudioStream::initialize_memory(data, num_audio_buffers);
}