Audio results are not being available at low clock speeds

YasarYY

Member
Hi,

Audio result availabilities like peak1.available() and fft256_1.available() are working as expected from normal 600 MHz to 300 MHz clock speeds, as I set via set_arm_clock(), for power reduction purposes. But below 300 MHz (i.e. 240 MHz and below), these available() methods always return false. If I call peak1.read(), it returns 1.0 as it is.

Source codes like AudioStream uses F_CPU_ACTUAL, so they theoretically adapt, but to a degree I guess. May be something about ADC timing is happening.

I saw some posts saying things work at low clock speeds, but I cannot figure if they're using audio library at all.

Is there any method to have these audio components work at low clock speeds, or is this the case no matter what?

p.s. My aim is to listen environment at a low clock speed/low power consumption, and when a recording is necessary, accelerate back to 600 MHz.
 
Try building it at 150 MHz and running it there first.

Have an audio sketch here playing back SD WAV files that works doing that and bar charts on an SPI display that works as well as it does at 600 MHz.

It may be there is something that doesn't like the clock change - where most parts can dynamically adjust. Building at 150 MHz will clarify that perhaps.

If it fails at 150 then there is some other issue at hand not showing in the sketch in use here
 
BTW: Example in use is :: // Part 3-3: Add a TFT Display
which at 150 MHz has this loop() code updating the display bars:
Code:
  if (msecs > 15) {
    if (peak1.available() && peak2.available()) {
      msecs = 0;
      float leftNumber = peak1.read();
      float rightNumber = peak2.read();
 
Ok, while testing I've come to a weird point. I made clock speed being adjusted by a pot, and swept down from 600 MHz to 150 MHz, with 50 MHz steps. Code lights leds when available() methods return true, so that I can see.

When the speed input to set_arm_clock and the speed read back from F_CPU_ACTUAL are the same, ADC-related peak and FFT work, but when not the same, they don't 🤔 So, among sweep speeds, only 600, 450 and 300 MHz works, while I sweep up and down.

As a workaround trial, if I add an exception for setting 150 MHz, to set speed to 151200000 (which is F_CPU_ACTUAL reading when set_arm_clock with 150 MHz), peak and FFT still don't work, even at this case set_arm_clock input and F_CPU_ACTUAL are the same.

Playing wav from SD card through MQS always works. And, as @defragster stated, peak works and has nothing to do with this, since it just cannot get data from ADC. So, this issue relates to AudioInputAnalog I guess. I can live with finding the lowest possible speed that AudioInputAnalog works, but will dig this a bit deeper, out of curiosity.

Compiling with 150 MHz selected from the IDE as CPU speed, and without even calling set_arm_clock once, ADC-related results don't work as above 🤔🤔 And while sweeping up from 150 MHz to 600 MHz, they continue never giving results, even at steps where set_arm_clock input and F_CPU_ACTUAL reading are the same.

Below is the output from sweeping down, with code compiled with 600 MHz selected from the IDE as CPU speed, writing gear no (by dividing pot range to 10 🙄), set_arm_clock input and F_CPU_ACTUAL reading. Peak and FFT works at gears 9, 6 and 3 only.

Code:
setup OK
9
600000000
600000000
8
550000000
552000000
7
500000000
498000000
6
450000000
450000000
5
400000000
402000000
4
350000000
348000000
3
300000000
300000000
2
250000000
252000000
1
200000000
201000000
0
150000000
151200000

Here is the code for completeness, with aforementioned exception for 150 MHz (gear 0) is commented out:

C++:
#include <Bounce.h>

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySdWav playSdWav1;    //xy=588,233
AudioInputAnalog adc1;        //xy=629,374
AudioAmplifier amp1;          //xy=764,232
AudioAnalyzePeak peak1;       //xy=852,358
AudioAnalyzeFFT256 fft256_1;  //xy=853,413
AudioOutputMQS mqs1;          //xy=939,230
AudioConnection patchCord1(playSdWav1, 0, amp1, 0);
AudioConnection patchCord2(adc1, peak1);
AudioConnection patchCord3(adc1, fft256_1);
AudioConnection patchCord4(amp1, 0, mqs1, 0);
// GUItool: end automatically generated code

// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11  // not actually used
#define SDCARD_SCK_PIN 13   // not actually used

// pin assignments
// AudioInputAnalog uses pin A2 by default
const uint8_t pinButton = 0, pinYellow = 41, pinRed = 40, pinGreen = 39, pinPot = A4;
// button debounce time in ms, increase if button output still chatters
const int debounceTime = 20;
// buttons
Bounce button = Bounce(pinButton, debounceTime);

// clock speed entities
extern "C" uint32_t set_arm_clock(uint32_t frequency);
// frequency of the basis crystal for the system clock is 24 MHz
const uint32_t minSpeed = 150000000;
int currentGear;

void setup() {
  Serial.begin(9600);
  AudioMemory(8);
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(1000);
    }
  }
  // generate lesser (344 / 8 = 43) new output per second by averaging
  fft256_1.averageTogether(8);
  pinMode(pinButton, INPUT_PULLUP);
  currentGear = 9;
  Serial.println("setup OK");
  delay(1000);
}

void loop() {
  // read the pot position and set the CPU clock
  int gear = (int)((float)analogRead(pinPot) / 102.4f);  // is between 0~9
  if (currentGear != gear) {
    // change the CPU clock
    uint32_t speed = minSpeed + gear * 50000000;
    //if (gear == 0) speed = 151200000;
    set_arm_clock(speed);
    currentGear = gear;
    Serial.println(currentGear);
    Serial.println(speed);
    Serial.println(F_CPU_ACTUAL);
  }

  // check user input
  button.update();
  if (button.fallingEdge()) {
    playSdWav1.play("SDTEST4s.wav");
    delay(10);  // wait for library to parse WAV info
  }

  // check peak
  if (peak1.available()) {
    digitalWrite(pinGreen, HIGH);
  } else {
    digitalWrite(pinGreen, LOW);
  }

  // check FFT
  if (fft256_1.available()) {
    digitalWrite(pinYellow, HIGH);
  } else {
    digitalWrite(pinYellow, LOW);
  }

  // wait for audio processes or a button click
  delay(2 * debounceTime);
}
 
sample code not matching I/O here and adjusting not getting audio or much output beyond the SPeed and index changing as shown p#4
 
After studying set_arm_clock() at clockspeed.c file, clock trees and timers at IMXRT1060 manual, I see my lack of knowledge and some numeric coincidences had misled me. Now I have a solution, which is not nice but enough for my case.

Teensy 4 starts with 600 MHz arm clock (armF) and 150 MHz bus clock (busF=F_BUS_ACTUAL, via clocking params mult, div_arm, div_ahb, div_ipg).

AudioInputAnalog adc1; line executes its constructor, which calls init(), which configures a timer to trigger ADC. During this configuration, init() calculates timer's timeout value (comp1) based on F_BUS_ACTUAL. So at startup, config is:

armF = 600000000, busF = 150000000, mult = 100, div_arm = 2, div_ahb = 1, div_ipg = 4, comp1 = 425

When set_arm_clock() is called, it recalculates an exact or a very close arm clock and a bus clock via adjusting clocking params. But AudioInputAnalog's timer still uses the same timeout value, which is not correct anymore. 600, 450 and 300 MHz arm clocks calculate the same bus frequency and the same timeout value, that's why ADC input works at these frequencies:

armF = 600000000, busF = 150000000, mult = 100, div_arm = 2, div_ahb = 1, div_ipg = 4, comp1 = 425 armF = 552000000, busF = 138000000, mult = 92, div_arm = 2, div_ahb = 1, div_ipg = 4, comp1 = 391 armF = 498000000, busF = 124500000, mult = 83, div_arm = 2, div_ahb = 1, div_ipg = 4, comp1 = 353 armF = 450000000, busF = 150000000, mult = 75, div_arm = 2, div_ahb = 1, div_ipg = 3, comp1 = 425 armF = 402000000, busF = 134000000, mult = 67, div_arm = 2, div_ahb = 1, div_ipg = 3, comp1 = 380 armF = 348000000, busF = 116000000, mult = 58, div_arm = 2, div_ahb = 1, div_ipg = 3, comp1 = 329 armF = 300000000, busF = 150000000, mult = 75, div_arm = 3, div_ahb = 1, div_ipg = 2, comp1 = 425 armF = 252000000, busF = 126000000, mult = 63, div_arm = 3, div_ahb = 1, div_ipg = 2, comp1 = 357

So I added reconfigureTimer() method to AudioInputAnalog, which recalculates and sets comp1:

C++:
// input_adc.h:
public:
    void reconfigureTimer();

// input_adc.cpp:
void AudioInputAnalog::reconfigureTimer()
{
    // reconfigure timer's comparator load register, so that when next compare event occurs,
    // the new compare value in the comparator load register is written to the compare register.
    // sample rate should be very close to 4X AUDIO_SAMPLE_RATE_EXACT
    const int comp1 = ((float)F_BUS_ACTUAL) / (AUDIO_SAMPLE_RATE_EXACT * 4.0f) / 2.0f + 0.5f;
    TMR4_CMPLD13 = comp1;
}

// main code:
    set_arm_clock(frequency);
    adc1.reconfigureTimer();

This makes ADC work on majority of arm frequencies. But running a 600 MHz to 24 MHz test sweep shows that, ADC still doesn't work at 200-150 MHz range and below 100 MHz. Looking sweep output, at these and only these ranges, bus clocks are below 100 MHz (and comp1 values are below 285).

I cannot figure why these values cause ADC to stop working, but got around by going to conditional check at line 466 of input_adc.cpp (if (new_samples < AUDIO_BLOCK_SAMPLES*4 - 3) {), and commenting out the code inside that scope, which releases memory and returns from update(). Now ADC is working at every single arm frequency, and peak results are still correct.

If anyone knows a better approach for the last case, which is far from a proper solution, please let me know. Otherwise this solution is enough for me.

Last but not least, current draw while ADC listening at 24 MHz is 56 mA when USB is on, and is 50 mA when USB is off.
 
So, I did some more trials regarding to this issue, and wanted to report my observation here, as short as possible, in case someone has an explanation.

It seems, when bus frequency is set below 100 MHz (arm frequency is at 200-150 MHz range and below 100 MHz), AudioInputAnalog gets insufficient samples, and dropping them, leading results like peak and FFT never become available. I played with clock params, timer params, compile clock speed (Tools -> CPU Speed), but the issue persists. Enough samples being 4 x AUDIO_SAMPLE_RATE_EXACT - 3 = 509 samples, as in source:

As I set calculated timer value, comp1, to a lower value to increase trigger frequency, ADC collects more samples, but to a max limit, being never enough, even when comp1 is close to or is zero. Then I noticed a pattern between the ratio of max sample limit to enough sample count and the bus frequency.

armF = 192000000, busF = 96000000 // 483 / 509 samples, 95% of enough armF = 168000000, busF = 84000000 // 423 / 509 samples, 83% of enough armF = 72000000, busF = 72000000 // 362 / 509 samples, 71% of enough armF = 48000000, busF = 48000000 // 241 / 509 samples, 47% of enough armF = 24000000, busF = 24000000 // 120 / 509 samples, 24% of enough

Notice the bus frequency and the percentage similarity. It's like there is a hardcoded relation to the bus frequency being at least 100 MHz.

What's the catch about the bus frequency being somehow related to 100 MHz? I cannot find any document or info, or cannot relate to DMA. I'm just curious about what's going on here 🤔

A minimum example code, which never prints any line:

C++:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputAnalog adc1;   //xy=629,374
AudioAnalyzePeak peak1;  //xy=852,358
AudioConnection patchCord2(adc1, peak1);
// GUItool: end automatically generated code

// clock speed setter
extern "C" uint32_t set_arm_clock(uint32_t frequency);

void setup() {
  AudioMemory(8);
  //set_arm_clock(24000000); // 24 MHz
  set_arm_clock(150000000);  // 150 MHz
}

void loop() {
  delay(50);
  if (peak1.available()) Serial.println(peak1.read());
}
 
At last I got this issue working, and for completeness I'm summarizing my solution here. If anyone needs the details, let me know and I'll explain.

Issue's relation to bus frequency being below 100 MHz at my previous post, was actually a relation to ADC's internal clock being so slow. I studied ADC section at the chip's manual, the internal clock tree, conversion time calculation, etc. And I managed to make ADC input work at 24 MHz, by adjusting its clock tree and hardware averaging, w.r.t. a conversion time target.

Adjusting chip's clock tree was not enough, because ADC has its own clock tree which divides its source clock (IPG) by 4, as set by Audio Library source code. At low ARM frequencies (24 MHz), this makes ADC's internal clock (ADCK) even lower (6 MHz), and in turn makes conversion cycles take more time. More time that, internal sampling frequency goes below the required minimum of 176.4 kHz (44.1*4, 4 times more sample is being used by low-pass/noise filtering, and is separate from ADC's hardware averaging). This causes DMA cannot collect a sample every time it's triggered by the timer.

To make AudioInputAnalog work at 24 MHz, I set ADC's internal clock tree divider to 1, and disabled hardware averaging to increase internal sampling frequency, and made a minor modification to timer's comp1 value's offset as well. These make ADC's internal clock speed 24 MHz and make internal sampling frequency above 176.4 kHz, so DMA collects a sample every time it's triggered. These are done in below method, no need to change AudioInputAnalog source code.

As audio analyze testing, AudioAnalyzePeak and AudioAnalyzeFFT256 work correctly. I tested FFT output by feeding a sine-wave sound generated at a certain frequency, and they match.

Here is the method that switches between 600 MHz and 24 MHz:

C++:
void ChangeCpuSpeed(bool toHigh) {
  set_arm_clock(toHigh ? 600000000 : 24000000);
  // reconfigure timer's comparator load register, so that when next compare event occurs,
  // the new compare value in the comparator load register is written to the compare register.
  // sample rate should be very close to 4X AUDIO_SAMPLE_RATE_EXACT
  const float comp1base = ((float)F_BUS_ACTUAL) / (AUDIO_SAMPLE_RATE_EXACT * 4.0f) / 2.0f;
  if (toHigh) {
    // set timer's comparator with original offset
    const int comp1 = comp1base + 0.5f;
    TMR4_CMPLD13 = comp1;
    // use averaging only when running at 600 MHz
    ADC2_GC |= ADC_GC_AVGE;
    // clear clock divide ratio
    ADC2_CFG &= ~ADC_CFG_ADIV(3);
    // set clock divide ratio to 1/4 (0:div1, 1=div2, 2=div4, 3=div8)
    ADC2_CFG |= ADC_CFG_ADIV(2);
  } else {
    // set timer's comparator with modified offset
    const int comp1 = comp1base - 1.f;
    TMR4_CMPLD13 = comp1;
    // single sample, no averaging
    ADC2_GC &= ~ADC_GC_AVGE;
    // clear clock divide ratio
    ADC2_CFG &= ~ADC_CFG_ADIV(3);
    // set clock divide ratio to 1/1 (0:div1, 1=div2, 2=div4, 3=div8)
    ADC2_CFG |= ADC_CFG_ADIV(0);
  }
}

I did a lot of timing calculations and testing at some variety of frequencies, to fully grasp the underlying mechanics. I also thought about writing a general ChangeCpuSpeed method that covers all frequencies, but I guess there is no need for this 😅
 
Back
Top