timing duration of single cycle waveforms for tuning a synthesizer

mrdave45

Member
Hi,

Im currently working on an analog polysynth and one of the most crucial things is tuning of vcos. They will need quickly tweaing as the thing gets used and teperature changes. As good as some of these vco chips are, theyre not digital!

I have managed to use freqMeasure to successfully tune the various octaves of my voltage controlled oscilators but I have to use many cycles and I have had to filter out some odd readings. I think these are due to interupt issues. Regardless, for a deep tune this method works, however, for a quick tune, its far too long. By the time ive checked 8 voices with 2 vcos and 11 octaves itll take a while, probably several minutes.
The prophet 5 takes less than 10 seconds to tune.
I have done more digging and these machines and others had been using 8253 programmable interval timers and measuring just one cycle from the oscillator and they seem to be good enough and super quick. I couldn't get freqMeasure to work properly on just one cycle. freqCount is also going to bring its own problems with trying to get fast freq reads and is also not suitable for a quicktune algorithm.

Im sure with a modern mcu i shouldnt need to resort to something like a 8253 and today, having read round many forums i tried a new approach using ccnt cycle counter on a teensy 4.1 However ive had some rather unexpected results.

The plan is to wait until pin 22 goes high, log cycles count. wait for pin to go low, then when the pin goes high again, log the cycle counter again.
Measure the difference.
Eventually calculate the frequency of my input waveform based on the frequency of the cycle counter.

Heres the critical code with

setup(){
ARM_DEMCR |=ARM_DEMCR_TRCENA;
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
}
------------------------------------------------
loop(){
int timeStampStart = 0;
int timeStampEnd = 0;
int cycles = 0;

noInterrupts();

while (digitalReadFast(tunerSignal)==0);

timeStampStart = ARM_DWT_CYCCNT;

while (digitalReadFast(tunerSignal)==1);

while (digitalReadFast(tunerSignal)==0);

timeStampEnd = ARM_DWT_CYCCNT;

cycles = timeStampEnd - timeStampStart;

Serial.println(cycles);
interrupts();
}
--------------------------------------------------
This works to a point, however, this is the data im getting. (ive been testing the routine with my siglent sig gen so Im pretty sure the source freq is ok).
As you can see, certain frequencies seem to measure brilliantly and I can calculate the frequency of the cycle counter. for 1046hz and 16744hz the predicted cycle counter frequency comes out at 600mhz. Which is fab.However, you can see from the other figures, that the predicted cycles is all over the shop which means i cant get to my measured frequency by assuming that the cycle count is being performed at 600mhz.

I cant see why it would work perfectly at 1046hz and 16744hz but not say 523hz or 2093hz.

I half expected there to be a gradual drift in one direction, probably as the measured frequency got higher due to my digital reads fasts and while loops but as you can see its very unpredictable ( although for any given frequency its actually really consistant). I have double checked my results and as yet I cant find experimental error.
Any ideas?
Thanks

Screenshot 2022-09-14 at 20.21.42.jpg
 
Here is a test I did with FreqMeasure from 1 kHz to 30 kHz. I got good results by throwing out the first two readings at each frequency and using just the 3rd reading. I used PWM on pin 23 to generate the frequencies, so keep in mind that when you request PWM at, say, 7000 Hz, you may get something slightly different depending on clock divisors, etc. All 30 frequencies can be measured in less than 1 second. Also, note that library FreqMeasureMulti gives you the option of using pins other than pin 22.

Code:
 f_pwm     f_meas  period[0]  period[1]  period[2]
  1000    1000.00      58217     150000     150000
  2000    2000.00      90888      82319      75000
  3000    3000.00      69270      50000      50000
  4000    4000.00      43370      37500      37500
  5000    5000.00      53006      30000      30000
  6000    6000.00      45142      25000      25000
  7000    6999.86      25879      21429      21429
  8000    8000.00      64088      18750      18750
  9000    8999.82      30837      16667      16667
 10000   10000.00     124184 4294931622      15000
 11000   11000.29      32734      26508      13636
 12000   12000.00       1881      24269      12500
 13000   13000.52      33094      22206      11538
 14000   14000.37     126856 4294922162      10714
 15000   15000.00      21603      18683      10000
 16000   16000.00      44873      17460       9375
 17000   16999.09        670      16453       8824
 18000   18000.72      20331      15385       8333
 19000   18999.37      38510      14489       7895
 20000   20000.00      55326      13635       7500
 21000   20999.58       5415      12855       7143
 22000   22000.59      19841      12165       6818
 23000   22999.08      33360      11536       6522
 24000   24000.00      45987      10875       6250
 25000   25000.00      57703      10331       6000
 26000   26001.04       2986       9893       5769
 27000   26997.84      13062       9703       5556
 28000   28000.75      22833       9033       5357
 29000   29002.32      31706       8598       5172
 30000   30000.00      39975       8345       5000

Code:
#include <FreqMeasure.h>

// jumper pin 23 (PWM) to pin 22 (FreqMeasure)

void setup() {
  Serial.begin(57600);
  while (!Serial) {}
  FreqMeasure.begin();
}

void loop() {
  uint32_t freq, period[3], count;
  // print data header
  Serial.printf( " f_pwm     f_meas  period[0]  period[1]  period[2]\n" );
  for (freq = 1000; freq <= 30000; freq += 1000) {
    FreqMeasure.end();
    analogWriteFrequency( 23, freq );  // 2-kHz PWM  
    analogWrite( 23, 128 );   // 50% duty cycle
    delay( 10 );
    count = 0;
    FreqMeasure.begin();    
    while (count < 3) {
      if (FreqMeasure.available()) {
        period[count++] = FreqMeasure.read();
      }
    }
    // compute frequency based on just the 3rd reading
    float frequency = FreqMeasure.countToFrequency( period[2] );
    Serial.printf( "%6lu %10.2f %10lu %10lu %10lu\n",
      freq, frequency, period[0], period[1], period[2] );
  }
}
 
Here is a test I did with FreqMeasure from 1 kHz to 30 kHz. I got good results by throwing out the first two readings at each frequency and using just the 3rd reading. I used PWM on pin 23 to generate the frequencies, so keep in mind that when you request PWM at, say, 7000 Hz, you may get something slightly different depending on clock divisors, etc. All 30 frequencies can be measured in less than 1 second. Also, note that library FreqMeasureMulti gives you the option of using pins other than pin 22.

Code:
 f_pwm     f_meas  period[0]  period[1]  period[2]
  1000    1000.00      58217     150000     150000
  2000    2000.00      90888      82319      75000
  3000    3000.00      69270      50000      50000
  4000    4000.00      43370      37500      37500
  5000    5000.00      53006      30000      30000
  6000    6000.00      45142      25000      25000
  7000    6999.86      25879      21429      21429
  8000    8000.00      64088      18750      18750
  9000    8999.82      30837      16667      16667
 10000   10000.00     124184 4294931622      15000
 11000   11000.29      32734      26508      13636
 12000   12000.00       1881      24269      12500
 13000   13000.52      33094      22206      11538
 14000   14000.37     126856 4294922162      10714
 15000   15000.00      21603      18683      10000
 16000   16000.00      44873      17460       9375
 17000   16999.09        670      16453       8824
 18000   18000.72      20331      15385       8333
 19000   18999.37      38510      14489       7895
 20000   20000.00      55326      13635       7500
 21000   20999.58       5415      12855       7143
 22000   22000.59      19841      12165       6818
 23000   22999.08      33360      11536       6522
 24000   24000.00      45987      10875       6250
 25000   25000.00      57703      10331       6000
 26000   26001.04       2986       9893       5769
 27000   26997.84      13062       9703       5556
 28000   28000.75      22833       9033       5357
 29000   29002.32      31706       8598       5172
 30000   30000.00      39975       8345       5000

Code:
#include <FreqMeasure.h>

// jumper pin 23 (PWM) to pin 22 (FreqMeasure)

void setup() {
  Serial.begin(57600);
  while (!Serial) {}
  FreqMeasure.begin();
}

void loop() {
  uint32_t freq, period[3], count;
  // print data header
  Serial.printf( " f_pwm     f_meas  period[0]  period[1]  period[2]\n" );
  for (freq = 1000; freq <= 30000; freq += 1000) {
    FreqMeasure.end();
    analogWriteFrequency( 23, freq );  // 2-kHz PWM  
    analogWrite( 23, 128 );   // 50% duty cycle
    delay( 10 );
    count = 0;
    FreqMeasure.begin();    
    while (count < 3) {
      if (FreqMeasure.available()) {
        period[count++] = FreqMeasure.read();
      }
    }
    // compute frequency based on just the 3rd reading
    float frequency = FreqMeasure.countToFrequency( period[2] );
    Serial.printf( "%6lu %10.2f %10lu %10lu %10lu\n",
      freq, frequency, period[0], period[1], period[2] );
  }
}

hmm, thats interesting. I couldnt get anywhere close to that. That would be more than good enough.

Ill revisit freqMeasure and give that ago.

Does this example basically stay with the measurement loop until its done and then continue?

I think when i tried freqMeasure, my code (not the code listed in p#1) was probably jumping about all over with interrupts and thats what was causing the problems. When i needed to take multiple cycles i needed to update my dacs (heavily multiplexed to give lots of control voltages) or the sample and hold circuits sag. But wierdly, just a few freqMeasure cycles didnt hold water once i got past a few hundred hz. Your code snippet looks very promising. If i cant get it to work like this the problems solved.

Thanks.
 
actually, looking more closely at your results, have you any idea why the first 2 seem to be just largely junk but the third seems consistently spot on?
 
actually, looking more closely at your results, have you any idea why the first 2 seem to be just largely junk but the third seems consistently spot on?

FreqMeasure configures a timer to do input capture, and if it's not throwing away the first capture, that would explain why the first value is no good. I can't explain why the second value is not good, but I'm guessing it's something similar. I actually recommend using FreqMeasureMulti instead of FreqMeasure, even if you only need to measure on one pin. Not only can you read on multiple pins, but there are also options for capture on falling edges, etc., and the code is much easier to read because it's just for Teensy boards. Here are the results and code for the same program using FreqMeasureMulti instead of FreqMeasure. You can see that the 2nd reading is good, as it should be.

Code:
 f_pwm     f_meas  period[0]  period[1]
  1000    1000.00     189289     150000
  2000    2000.00     133206      75000
  3000    3000.00      83207      50000
  4000    4000.00      70708      37500
  5000    5000.00     131244      30000
  6000    6000.00      63208      25000
  7000    6999.86      61809      21429
  8000    8000.00      60887      18750
  9000    8999.82      60321      16667
 10000   10000.00      59875      15000
 11000   11000.29      73168      13636
 12000   12000.00      71844      12500
 13000   13000.52      70648      11538
 14000   14000.37      69706      10714
 15000   15000.00      68922      10000
 16000   16000.00      68208       9375
 17000   16999.09      67663       8824
 18000   18000.72      66972       8333
 19000   18999.37      66591       7895
 20000   20000.00      66103       7500
 21000   20999.58      65738       7143
 22000   22000.59      65311       6818
 23000   22999.08      65086       6522
 24000   24000.00     130266       6250
 25000   25000.00      64458       6000
 26000   26001.04      64148       5769
 27000   26997.84      64097       5556
 28000   28000.75      63724       5357
 29000   29002.32      63445       5172
 30000   30000.00      63380       5000

Code:
#include <FreqMeasureMulti.h>
FreqMeasureMulti fmm1;

void setup() {
  Serial.begin(57600);
  while (!Serial) {}
  fmm1.begin(22);
}

void loop() {
  uint32_t freq, period[2], count;
  // print data header
  Serial.printf( " f_pwm     f_meas  period[0]  period[1]\n" );
  for (freq = 1000; freq <= 30000; freq += 1000) {
    fmm1.end();
    analogWriteFrequency( 23, freq );  // 2-kHz PWM  
    analogWrite( 23, 128 );   // 50% duty cycle
    delay( 10 );
    count = 0;
    fmm1.begin(22);    
    while (count < 2) {
      if (fmm1.available()) {
        period[count++] = fmm1.read();
      }
    }
    // compute frequency based on just the 3rd reading
    float frequency = fmm1.countToFrequency( period[1] );
    Serial.printf( "%6lu %10.2f %10lu %10lu\n",
      freq, frequency, period[0], period[1] );
  }
}
 
Well, had mixed success with your suggestions.
I've been having a nightmare with serial dropping. Everything seemed to work well until a Windows update and now it'll work great for a bit, then I have to start resetting the teensy and reprogramming, then sometimes restarting serial monitor. Then it'll work for a bit. Then stop and tell me access is denied.
Then I tried it on my mac. Same thing... Anyway. Rant over...

What I did get working properly (I think) was my code at the top.
I found putting another
while (digitalReadFast(tunerSignal)==1)
So it looks for a whole cycle before trying to do the time stamp sorted it out. It then worked at all frequencys.i think there must have been some wierd aliasing effect going on.

Sadly the timing measurements have highlighted my oscillators aren't settling to the new value as quick as I had hoped/need. Whether this is the dac/sample and hold or something else putting a lag on it I don't know, but I hadn't noticed this on my scope. But checking on a slow signal on the scope has confirmed that the wave is doing what the measurement routine has suggested.
 
Well, had mixed success with your suggestions. What I did get working properly (I think) was my code at the top. I found putting another while (digitalReadFast(tunerSignal)==1) So it looks for a whole cycle before trying to do the time stamp sorted it out. It then worked at all frequencys.i think there must have been some wierd aliasing effect going on.

Sadly the timing measurements have highlighted my oscillators aren't settling to the new value as quick as I had hoped/need. Whether this is the dac/sample and hold or something else putting a lag on it I don't know, but I hadn't noticed this on my scope. But checking on a slow signal on the scope has confirmed that the wave is doing what the measurement routine has suggested.

The nice things about using FreqMeasureMulti to measure your signal period (frequency) are that the input capture is independent of the software, so even if there is a (short) delay in responding to the interrupt, the measurement accuracy is not affected, and that each measurement goes into a FIFO that you access via the read() function. Those two things give you a lot of flexibility as you develop whatever logic is necessary to properly interpret your input signal. For example, you can read from the FIFO until the measurement stabilizes, with most of the work being done by the timer and very little by the CPU. If you use the digitalReadFast() method, you have to dedicate the CPU to that measurement, and any variability in interrupt handling will show up as an error in your frequency measurement.
 
Lol. I had actually been trying to get a routine that was specifically not doing any Interupt stuff and just waiting so I could be sure that odd results weren't due to wierd Interupt stuff I had no hope of tracking down.
As it happens, for this particular part of the program there's probably not much else that the processor can be doing. Theres only the one pin left for tuning as all the other pins have been used for other things.
However, once I've sussed out the dac issue I'll have another go with this as it's obviously a better practice than what I'm currently doing. But at least I've tracked down some of the odd behaviour and cause of unexpected measurements.
I'm really the hardware guy in our endeavour but as I know much more how the mobo and voice card work (and have more test kit) it made sense for me to write this aspect of the code.
I started off with the Mike predko book (123 experiments for the evil genius) but could you recommend another good book for programming microcontrollers?
 
I started off with the Mike predko book (123 experiments for the evil genius) but could you recommend another good book for programming microcontrollers?

Wow, that's hard to say. There are lots of common capabilities among microcontrollers, but once you decide you actually want to do something as complex as what you're doing, you need to get into the details of the specific platform you've chosen. To that end, you should read the T4.x info on the PJRC website. Paul has done an amazing job of providing access to the capabilities on each Teensy model. I also think it's a good idea to look through the table of contents of the IMXRT reference manual so that you get an appreciation of the full set of peripherals that are available. For each peripheral of interest, you can then dig into the TeensyDuino and third-party libraries, work through example programs, and use this forum.
 
Back
Top