Problem between BLE eventHandler and TC / TCC interrupt

Hello folks,

this is my first post here. Till now I have always found a solution in these boards or other forums, but this time I can't find a solution.

Couple years ago I had made a code I used on the Adafruit Feather M0 Bluefruit. I tried to port it to the Nano 33 IoT, however, I notice that I can't make the Bluetooth eventHandler work. In the Serialport I can see, that BLE.poll() is called, however, I can't connect my phone via ble to it when the switch is set to ProgState = ='O'. If I remove the code for ProgState 'O' or change the start condition to something else like ProgState == 'R'. The eventHandler is working.

Can you please help me?

The purpose of this code is to sample signals from a electret microphone and then do a FFT and turn it into a color spectrum on a SK9822 RGB Strip. For this I am using FASTLED.

// Audio Spectrum Display
// Copyright 2021 Dave Sircar
// Used hardware: Arduino Nano 33 IoT + Adafruit MAX9814 AGC Electret Microphone with in-built Amplifier 


#define ARM_MATH_CM0PLUS
#include <arm_math.h>
#include "arm_const_structs.h"
#include <FastLED.h>
#include <ArduinoBLE.h>
#define TIMER_PRESCALER_DIV 1024

#define SK9822_PIXEL_COUNT 23		// Number of Sk9822 pixels
#define BRIGHTNESS  127
#define LED_TYPE    SK9822
#define COLOR_ORDER BGR



////////////////////////////////////////////////////////////////////////////////
// CONIFIGURATION 
// These values can be changed to alter the behavior of the spectrum display.
////////////////////////////////////////////////////////////////////////////////

int SAMPLE_RATE_HZ = 9600;             // Sample rate of the audio in hertz.
float SPECTRUM_MIN_DB = 30.0;          // Audio intensity (in decibels) that maps to low LED brightness.
float SPECTRUM_MAX_DB = 60.0;          // Audio intensity (in decibels) that maps to high LED brightness.
int LEDS_ENABLED = 1;                  // Control if the LED's should display the spectrum or not.  1 is true, 0 is false.
                                       // Useful for turning the LED display on and off with commands from the serial port.
const int FFT_SIZE = 256;              // Size of the FFT.  Realistically can only be at most 256 
                                       // without running out of memory for buffers and other state.
const int AUDIO_INPUT_PIN = 18;        // Input ADC pin for audio data 18=A4
const int POWER_LED_PIN = 13;          // Output pin for power LED (pin 13 to use Teensy 3.0's onboard LED).  
uint32_t refIndex = 255, testIndex = 0;// needed by the arm_cmsis_cfft code


////////////////////////////////////////////////////////////////////////////////
// INTERNAL STATE
// These shouldn't be modified unless you know what you're doing.
////////////////////////////////////////////////////////////////////////////////

float samples[FFT_SIZE*2];
float magnitudes[FFT_SIZE];
int sampleCounter = 0;
//#define SPI_DATA 11	//This value is here as a reminder, that I made some manual correction in the fastpin_arm_d21.h file of FastLED!!!
//#define SPI_CLOCK 13
CRGB leds[SK9822_PIXEL_COUNT];
float frequencyWindow[SK9822_PIXEL_COUNT+1];
float hues[SK9822_PIXEL_COUNT];


CRGBPalette16 				currentPalette;
TBlendType    				currentBlending;


char ProgState = 'O'; // O is spectrum analyser state
int redL,blueL,greenL;


BLEService ledService("19B16860-E8F2-537E-4F6C-D104768A1214"); // create service BLEService

BLEByteCharacteristic switchCharacteristic("19B16861-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); // create switch characteristic and allow remote device to read and write

////////////////////////////////////////////////////////////////////////////////
// MAIN SKETCH FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void setup() {
  // Set up serial port.
  Serial.begin(115200);
  while (!Serial);
  
  //Begin BLE
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  // set the local name peripheral advertises
  BLE.setLocalName("TheName");
  // set the UUID for the service this peripheral advertises
  BLE.setAdvertisedService(ledService);

  // add the characteristic to the service
  ledService.addCharacteristic(switchCharacteristic);

  // add service
  BLE.addService(ledService);

  // assign event handlers for connected, disconnected to peripheral
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // assign event handlers for characteristic
  switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten);
  // set an initial value for the characteristic
  switchCharacteristic.setValue(0);

  // start advertising
  BLE.advertise();

  Serial.println(("Bluetooth device active, waiting for connections..."));

   
  // Set up ADC and audio input.
  pinMode(AUDIO_INPUT_PIN, INPUT);
  
  // Turn on the power indicator LED.
  pinMode(POWER_LED_PIN, OUTPUT);
  digitalWrite(POWER_LED_PIN, HIGH);
  
  // Initialize FastLED library and turn off the LEDs
  FastLED.addLeds<LED_TYPE, COLOR_ORDER>(leds, SK9822_PIXEL_COUNT).setCorrection( TypicalLEDStrip );
  
  for(int dot = 0; dot < SK9822_PIXEL_COUNT; dot++){
    leds[dot] = CRGB::Black;
    FastLED.show();
  } 

  
  // Initialize spectrum display
  spectrumSetup();
  
  tcConfigure(SAMPLE_RATE_HZ); //configure the timer to run at <SAMPLE_RATE_HZ>
  
  // Begin sampling audio
  samplingBegin();
}

void loop() {

  switch (ProgState){
	case 'O': // Spectrum Analyser Mode
		
		// Calculate FFT if a full sample is available.
		if (samplingIsDone()) {
			
			// Run FFT on sample data.
			arm_status status;
			float32_t maxValue;
			
			status = ARM_MATH_SUCCESS;
			
			/* Process the data through the CFFT/CIFFT module */
			arm_cfft_f32(&arm_cfft_sR_f32_len256, samples, 0, 1);
			/* Process the data through the Complex Magnitude Module for
			calculating the magnitude at each bin */
			arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE);
			/* Calculates maxValue and returns corresponding BIN value */
			arm_max_f32(magnitudes, FFT_SIZE, &maxValue, &testIndex);
			
			if(testIndex !=  refIndex){
				status = ARM_MATH_TEST_FAILURE;
			}
		  
			if (LEDS_ENABLED == 1)
			{
			  spectrumLoop();
			}
		  
			// Restart audio sampling.
			samplingBegin();
			Serial.print("SamplingBegin() ");
			Serial.println(micros());
		}
		break;
	case 'C': //Const Colour shown as inputed by bluetooth
		FastLED.show();
		break;
	case 'F':
		static uint8_t var_k = 0;
		fill_solid(leds, SK9822_PIXEL_COUNT, CHSV(var_k, 255, 51) );

		FastLED.show();
		var_k++;
		FastLED.delay(200);
		break;
	case 'R': //Theater chase in Rainbow Palette Colours
	
		currentPalette = RainbowColors_p; //IMPLEMENT OTHER COLORPALETTES!!!!!!!!!!!!!!!!!!!!!
    	currentBlending = LINEARBLEND;
		
		static uint8_t startIndex = 0;
		EVERY_N_MILLISECONDS(200) {
    		startIndex = startIndex + 1; /* motion speed */
		
    		FillLEDsFromPaletteColors( startIndex);
    	   	FastLED.show();
    	}
		break;
		
  }


	// poll for BLE events
	BLE.poll();
	Serial.print("\t Marker BLE.poll: ");
	Serial.println(micros());
	
	
	
}



////////////////////////////////////////////////////////////////////////////////
//This function gets called by the interrupt at <SAMPLE_RATE_HZ>
////////////////////////////////////////////////////////////////////////////////

void TC4_Handler (void) {
  //YOUR CODE HERE 
  samplingCallback();
  // END OF YOUR CODE
  TC4->COUNT16.INTFLAG.bit.MC0 = 1; //don't change this, it's part of the timer code
}


////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

// Compute the average magnitude of a target frequency window vs. all other frequencies.
void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) {
    *windowMean = 0;
    *otherMean = 0;
    // Notice the first magnitude bin is skipped because it represents the
    // average power of the signal.
    for (int i = 1; i < FFT_SIZE/2; ++i) {
      if (i >= lowBin && i <= highBin) {
        *windowMean += magnitudes[i];
      }
      else {
        *otherMean += magnitudes[i];
      }
    }
    *windowMean /= (highBin - lowBin) + 1;
    *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin));
}

// Convert a frequency to the appropriate FFT bin it will fall within.
int frequencyToBin(float frequency) { //Actually "frequency" should be named "frequencyWindow", however, not done as this variable already exists
  float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE);
  return int(frequency / binFrequency);
}


////////////////////////////////////////////////////////////////////////////////
// SPECTRUM DISPLAY FUNCTIONS
///////////////////////////////////////////////////////////////////////////////

void spectrumSetup() {
  // Set the frequency window values by evenly dividing the possible frequency
  // spectrum across the number of SK9822 pixels.

  //float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(SK9822_PIXEL_COUNT/2); //For centered LEDs useful
  //for (int i = 0; i < SK9822_PIXEL_COUNT/2+1; ++i) {

  float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(SK9822_PIXEL_COUNT); 
  
  for (int i = 0; i < SK9822_PIXEL_COUNT+1; ++i) {
    frequencyWindow[i] = i*windowSize; //upper window frequencies are calculated
  }
  
  // Evenly spread hues across all pixels.
  for (int i = 0; i < SK9822_PIXEL_COUNT+1; ++i) {
	leds[i] = CHSV(255 * (float(i)/float(SK9822_PIXEL_COUNT)), 255, 51);
  }
  FastLED.show();
}

void spectrumLoop() {
  // Update each LED based on the intensity of the audio 
  // in the associated frequency window.
  float intensity, otherMean;
  for (int i = 0; i < SK9822_PIXEL_COUNT; ++i) {
    windowMean(magnitudes, 
               frequencyToBin(frequencyWindow[i]),
               frequencyToBin(frequencyWindow[i+1]),
               &intensity,
               &otherMean);
    // Convert intensity to decibels.
    intensity = 20.0*log10(intensity);
	// Scale the intensity and clamp between 0 and 1.0.
    intensity -= SPECTRUM_MIN_DB;
    intensity = intensity < 0.0 ? 0.0 : intensity;
    intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB);
    intensity = intensity > 1.0 ? 1.0 : intensity;
	
	leds[i] = CHSV(240-intensity*240, 255, 127*intensity); //The stonger the frequencies, the more the colour changes from dark blue to bright red
  }
  FastLED.show();
}

void FillLEDsFromPaletteColors( uint8_t colorIndex){
    uint8_t brightness = 255;
    
    for( int i = 0; i < SK9822_PIXEL_COUNT; ++i) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}


////////////////////////////////////////////////////////////////////////////////
// SAMPLING FUNCTIONS
////////////////////////////////////////////////////////////////////////////////

void samplingCallback() {
  // Read from the ADC and store the sample data
  samples[sampleCounter] = (float32_t)analogRead(AUDIO_INPUT_PIN);
  // Complex FFT functions require a coefficient for the imaginary part of the input.
  // Since we only have real data, set this coefficient to zero.
  samples[sampleCounter+1] = 0.0;
  // Update sample buffer position and stop after the buffer is filled
  sampleCounter += 2;

  if (sampleCounter >= FFT_SIZE*2) {
	tcDisable();
	//tcReset();
  }
}

void samplingBegin() {
  // Reset sample buffer position and start callback at necessary rate.
  sampleCounter = 0;
  tcStartCounter(); //starts the timer
}

bool samplingIsDone(){
  return sampleCounter >= FFT_SIZE*2;
}


//////////////////////////////////////////////////////////////////////////////// 
//  TIMER SPECIFIC FUNCTIONS FOLLOW
//  you shouldn't change these unless you know what you're doing
////////////////////////////////////////////////////////////////////////////////

//Configures the TC to generate output events at the sample frequency.
//Configures the TC in Frequency Generation mode, with an event output once
//each time the audio sample frequency period expires.
 void tcConfigure(int SAMPLE_RATE_HZ){
	// Enable GCLK for TCC2 and TC4 (timer counter input clock)
	GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
	while (GCLK->STATUS.bit.SYNCBUSY);

	tcReset(); //reset TC4

	// Set Timer counter Mode to 16 bits
	TC4->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
	// Set TC4 mode as match frequency
	TC4->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
	//set prescaler and enable TC4
	TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE;
	//set TC4 timer counter based off of the system clock and the user defined sample rate or waveform
	TC4->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / (TIMER_PRESCALER_DIV*SAMPLE_RATE_HZ) - 1);
	while (tcIsSyncing());
	 
	 // Configure interrupt request
	 NVIC_DisableIRQ(TC4_IRQn);
	 NVIC_ClearPendingIRQ(TC4_IRQn);
	 NVIC_SetPriority(TC4_IRQn, 0);
	 NVIC_EnableIRQ(TC4_IRQn);

	 // Enable the TC4 interrupt request
	 TC4->COUNT16.INTENSET.bit.MC0 = 1;
	 while (tcIsSyncing()); //wait until TC4 is done syncing 
} 

//Function that is used to check if TC4 is done syncing
//returns true when it is done syncing
bool tcIsSyncing(){
	return TC4->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY;
}

//This function enables TC4 and waits for it to be ready
void tcStartCounter(){
	TC4->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //set the CTRLA register
	while (tcIsSyncing()); //wait until snyc'd
}

//Reset TC4 
void tcReset(){
  TC4->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  while (tcIsSyncing());
  while (TC4->COUNT16.CTRLA.bit.SWRST);
}

//disable TC4
void tcDisable(){
  TC4->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (tcIsSyncing());
}



////////////////////////////////////////////////////////////////////////////////
// Functons and variables for the BLE module
////////////////////////////////////////////////////////////////////////////////


void blePeripheralConnectHandler(BLEDevice central) {
  // central connected event handler
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  // central disconnected event handler
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
}

void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) {
  // central wrote new value to characteristic
  Serial.print("Characteristic event, written: ");

  if (switchCharacteristic.value()) {
    Serial.println("LED on");
    digitalWrite(POWER_LED_PIN, HIGH);
  } else {
    Serial.println("LED off");
    digitalWrite(POWER_LED_PIN, LOW);
  }
 
	//MORE CODE 
}```

I tried out this code by replacing TC4 by TCC1 and the appropiate input pin, however, this doesn't change anything.

How often is BLE.poll called in your application when you are in each mode/ProgState? e.g., how many loops per second do you get?

BLE.poll should be called often to allow the stack to react to events running on the module. The fact you print every time you call poll lets me suspect your loop runs fairly slow. (Just a guess, maybe you enjoy super fast printing in the Serial Monitor.)

How often is BLE.poll called in your application when you are in each mode/ProgState? e.g., how many loops per second do you get?

According to the micros() output at 112,200 Baud. One loop takes 0.303 seconds. Is that too slow?

Yes, during that time BLE can exchange a lot of packets. Most of the BLE stack runs in the module and poll tells the part of the BLE library running on the SAMD21 what needs to be done.

In my examples I usually use 10ms poll interval using millis (calling too often wastes time, this function uses an external interface). That seems to make BLE apps respond without noticeable delay.

I checked today, why the cycle time is so long.
It turns out, that the AnalogRead function in the Callback function takes about 10x as long as it should. (almos 1ms instead of 0.1ms). I am now assuming, that the AnalogRead function gobbles up precious cycle time.
I am wondering if I could set up the ADC in such a fashion, that it would take less time as described in this link.
Does the SAM d21 provide a way to measure multiple times and store the values in an array?

That should hopefully also reduce the polling time something acceptable.

Yes, the AnalogRead function is not ideal for speed. It is made to be very robust against user errors by initializing everything for every call of AnalogRead. Have a look at the source code in the wiring_analog.c file.

The link you posted seems to be useful for writing something faster. You will find some of this in the file mentioned above. Get yourself a copy of the datasheet for the SAMD21.

The SAMD21 has an event system that allows you to control peripherals from other peripherals. You should be able to get a timer to start the ADC automatically with a fixed interval. Then you could pick up the data using interrupts or DMA.

Direct register programming is a bit harder but it is worth the effort if you need performance.

Get yourself a copy of the datasheet for the SAMD21

That's how I came up with that weird set up in the first place couple years back.
As to the solution with the ADC: It has a free running mode I can set up with slightly more cycles per second than wanted (8.5% more to be precise).
I've to figure out now whether I can keep it running and only need to flush the results register when I need a measurement or if I need to disable and enable and then trigger it every time.

I'll keep you posted on my finding.

The code seems to be working, however, I have not tested it with the led strip attached to it. I got another problem with the Ble.
As I need to transfer the values of the fft to two other Nano 33 IoTs, I tried out this solution for simultaneous central and peripheral role.
Unfortunately this is not working right for me.

Worst of all is that the BLE.setEventHandler(BLEDiscovered, bleCentralDiscoverHandler); works in unexpected ways and even when BLE.poll() is commented out from the entire sketch.

How could I solve this problem? Any suggestions?