stm32 sample frequency

this code works on an Arduino uno, a guitar string frequency detector,
but the output values are approx halved when I use stm32f411,
anyone know why, could it be the sample frequency

#define LENGTH 512

byte rawData[LENGTH];
int count;

// Sample Frequency in kHz
const float sample_freq = 8919;

int len = sizeof(rawData);
int i,k;
long sum, sum_old;
int thresh = 0;
float freq_per = 0;
byte pd_state = 0;

void setup(){
  //analogReference(EXTERNAL);   // Connect to 3.3V
  analogRead(A0);
  Serial.begin(115200);
  count = 0;
}


void loop(){
  
  if (count < LENGTH) {
    count++;
    rawData[count] = analogRead(A0)>>2;
  }
  else {
    sum = 0;
    pd_state = 0;
    int period = 0;
    for(i=0; i < len; i++)
    {
      // Autocorrelation
      sum_old = sum;
      sum = 0;
      for(k=0; k < len-i; k++) sum += (rawData[k]-128)*(rawData[k+i]-128)/256;
      // Serial.println(sum);
      
      // Peak Detect State Machine
      if (pd_state == 2 && (sum-sum_old) <=0) 
      {
        period = i;
        pd_state = 3;
      }
      if (pd_state == 1 && (sum > thresh) && (sum-sum_old) > 0) pd_state = 2;
      if (!i) {
        thresh = sum * 0.5;
        pd_state = 1;
      }
    }
    // for(i=0; i < len; i++) Serial.println(rawData[i]);
    // Frequency identified in Hz
    if (thresh >100) {
      freq_per = sample_freq/period;
      Serial.println(freq_per);
    }
    count = 0;
  }
}

Tuner_aRead.ino (1.2 KB)

this code works on an Arduino uno, a guitar string frequency detector,
but the output values are approx halved when I use stm32f411,
anyone know why, could it be the sample frequency

what determines the sampling rate in the following code? it looks like it's collecting samples as fast as it can and other interrupt processing (e.g. millis()) will affect it

  if (count < LENGTH) {
    count++;
    rawData[count] = analogRead(A0)>>2;
  }

I don’t know, I just know it works on uno, but gives wrong values using stm32

Using "analogRead()" on an Arduino Uno and sampling as fast as possible, one gets a sampling rate of about 8919 samples per second.

On the stm32f411 the sampling rate is going to be something else. One would either have to figure out what that sampling rate is, for instance, by calibrating your program to a known pitch source, or by explicitly controlling the sampling rate. One way to do the latter would be to use "micros()" to get the current time and doing "analogRead()" when the time advances some amount of time from the previous sample. E.g. analogRead() each time micros() advances 100 microseconds would give 10000 samples per second.

gcjr:
what determines the sampling rate in the following code? it looks like it's collecting samples as fast as it can and other interrupt processing (e.g. millis()) will affect it

  if (count < LENGTH) {

count++;
   rawData[count] = analogRead(A0)>>2;
 }

Yes, it has a hard coded constant that can be changed for different processors. It just needs to be changed for the STM32F4.

// Sample Frequency in kHz
const float sample_freq = 8919;

changed sample freq to 18460
this correctly gives me 82.41 Hz for a low E string

but incorrectly gives me

109.88 Hz for A string (should be 110.00 Hz)
146.51 Hz for D string (should be 146.83 Hz)
194.32 Hz for G string (should be 196.00 Hz)
246.13 Hz for B string (should be 246.94 Hz)
323.86 Hz for high E string (should be 329.63 Hz)

Did you write this code? You're mixing integers and floating point math.

long sum, sum_old;
int thresh = 0;
...
        thresh = sum * 0.5;

There are no inline comments to explain how it is supposed to work.

I found the sketch online, it works correctly on an arduino uno

Porting real time code between different processors is often an expert level job. Have you consulted the STM32 core and chip documentation?

Code from here:

Is the tutorial there helpful? Also how did you connect AREF? I assume you are using the LM358 circuit? Perhaps you should show us some images of your hardware connections. These may need some changes for the STM32F4.

Have you tried measuring/using different input levels on the UNO and the STM32?

I started at the blog, nothing there to help me figure this out

aarg:
Porting real time code between different processors is often an expert level job. Have you consulted the STM32 core and chip documentation?

Haven't seen an answer to this question yet. For something like this, always start with the datasheet.

With code you "found online", you usually get what you pay for. Especially if you don't understand how it works or interacts with the hardware.

Though I would prefer to set a fixed sample frequency using timers you can probably get away with using millis() to time the collection loop and then, rather than use a const for sample_freq, calculate the value of sample_freq . Simple calculation since you know the number of samples being taken.

Yes I consulted the documentation, bit pointless as I don’t know what a sample frequency is, or a timer, or millis

Also don’t understand what a period count is from the blog:

I measured the sample frequency to be 8919Hz. So for a low E (E2) you’d get a period count of 108 or 109 which gives a frequency difference of 0.7Hz but at high E (E4) the period would be 27 or 28 which is a frequency difference of 12Hz, so it won’t be as easy to get fine tunings on higher strings.

There are secondary effect but the effective frequency resolution one can obtain from an observation length of T is 1/T Hz. In the original code run on the Uno the observation period is 512 / 8919 seconds which means the frequency resolution is 8919 / 512 which equals 17.4 Hz . Your timeing measurement of the STM32 code indicates that it takes the 512 samples in roughly half the time of that of the Uno so the resoltion is roughly doubled. i.e.about 34 Hz. The resoution is substantially independent of the frequency one is trying to measure.

The only way you can improve the resolution is to increase the observation period. To my mind you have three ways you can implement this. There are probably more then 3 but ...

  1. Setup a timer to interrupt at your desired sample points. In the interrupt handler you take a sample and put it into the data array. When you have read all the samples you need you signal to the loop() that the data needs to be processed.

  2. You can increase the number of samples you are taking from 512 to a value that will give you your desired resolution.

  3. You can put a small delay in your collection loop so that you get back to a sample rate of 8191.

The only method I would consider is 1) but then I am biased since it is the way I have been doing it for nearly 50 years.

Not familiar with the STM32. Does it have timers with input capture capability (there's that old read the datasheet thing again)? If so, use them. Also, check out these two PJRC libraries. They don't claim to support STM32, but you can check out the source code to see how they work:
https://www.pjrc.com/teensy/td_libs_FreqCount.html
https://www.pjrc.com/teensy/td_libs_FreqMeasure.html

The STM32F1 has a plethora of ADC operation modes. From the sheet:

The events generated by the general-purpose timers (TIMx) and the advanced-control timer (TIM1) can be internally connected to the ADC start trigger, injection trigger, and DMA trigger respectively, to allow the application to synchronize A/D conversion and timers.

The STM32F4 will have that and more. I'm just short of the data sheet right now.