How Can I Get A Stable And Accurate Sample Rate?

Status
Not open for further replies.

ethelena

New member
Hi I'm new to teensy and I'm using a teensy3.2 and I want to get the data from teensy to my laptop with a stable and accurate sample rate(I don't need very high, higher than 16k is enough).

I have tried the function adc->setSamplingSpeed(SPEED) in the ADC library to set the sample speed,but the result I got is not good. I don't know the actual sample rate and I got about 272000 data for two seconds, and got 460650 data for four seconds when I set the same sample rate.

So is there any simple way for me to set an accurate sample rate? Thanks for your help!
 
How are you measuring the sample rate? If it is by counting the number of bytes transferred to your PC, that is not a very reliable method. The data transfer over a PC's USB port is interrupted by all sorts of things and Windows is not a real-time operating system. The Teensy is much less complicated because it doesn't have a scheduler or a task-switching OS (unless you implement one) so it is much more likely to have a stable sample rate on its own, apart from transferring data via USB.

One reliable method to test for a stable sample rate is to sample a known stable frequency, for example a 1 kHz sine wave, and then take the fourier transform of that data. The deviation of the peak from 1.0 kHz will show you the average sample rate offset, and the width and any sidelobes around the peak shows the timing jitter.
 
An interesting topic. Two questions spring to mind:

1) Does the ADC library take its timing from one of the FTM timers (or PITs)? These will run independant of any code running inside Teensy, and likely to be very stable from the Teensy 16 MHz crystal (which is used to generate the timer clock). The only delay then would be in the ISR which I presume stores the sample.
2) How would I practically take the Fourier Transform of a data set? Is there some simple "toolset" that I could use (having never undertaken such an operation previously).
 
I've used the programmable delay block to trigger ADC conversions, and can attest to the consistency of the resulting sample rate. I have also used the DMA controller to transfer the collected ADC readings to memory, and know that combination works well too. Our sample rate is 25 kHz with 16 bit precision, setting the ADC to use hardware averaging of 4 ADC samples per reading.

The project in which we are using Kinetis microcontrollers (we use a 150 MHz version) depends on accurate, consistent measurement and delivery of high volume, high value data in a physically demanding environment. Having been in continuous testing for many months now, I cannot cite a single example of the hardware missing a reading. The results are examined very closely for adherence to these specs... :)

Setting up the PDB, ADC and DMA appear daunting at first glance. Some patience managing the details is required but once the setup is built, the results are wonderful. This apparently high performance data acquisition stuff is actually the easiest part of our project.

Edit: Fix a "the the" typo.
 
You can use the PDB to trigger ADC conversions at a fixed rate with the ADC library.
Then you can use the DMAChannel library (it's part of the core library) to transfer the ADC readings into a buffer.
 
What references would you recommend I read to learn to do this? And, are there simple example codes that show how it works? In my application I want to run the DAC simultaneously with two ADCs to generate and measure response to a waveform, and then tear it all down and resume using normal single sample i/o calls. Will that be feasible? Thank you
 
The basic reference to learn “all this” is the 1400 page reference manual for the KINETIS K20 processor which can be downloaded on the PJRC website. Less theoretical and closer to practice is studying the source code of the audio and adc libraries which come both included in the Teensyduino installation.
 
I was hoping for a programming guide or at least a focused list of topics. A reference manual and sources for high level libraries is usually the long way around to ramp up on a chip.

Nonetheless, on first perusal I see the PDB, the DMA, some other timing devices and clocks, an eDMA, a DMAMUX, the ADC, a CMP, and so forth, Its not obvious yet, what can work with what, and how, but we'll figure it out eventually.

In the source codes, I see references to registers and bitmasks, but I haven't yet found where they are defined nor a comprehensive list of what is defined. Also the codes are in cpp, while my installation of ardiuino and teensy creates ino files. How these work together is another mystery.

Is there a guide to documentation that would help me ramp up more quickly?
 
The registers and bitmasks defines which you see in the libraries’ source files are defined in the Teensyduino core files. The names and designators have been chosen to closely match the names used in the reference manual, so that their use should quickly become intuitive, once you are familiar with the register and bitfield naming conventions used in the reference manual.
 
Thank you. I found a closely relevant example in Pedro's code tree, ringbufferDMA.ino. I am planning to insert the DAC output in one of the ISR's, perhaps coding it at the register level if need-be. Then, at the end of the waveform measurement, I'll have to shut it all down. Hopefully the connect to interrupts and dma, can be done and undone in the main loop and not just in setup.
 
DrM -
>> The .INO file is a naming convention for the CPP code used by Arduino. The IDE preprocesses the .INO to allow some C/cpp requirements to be supplied for the user to simplify the usage, for instance prototypes are optional as it creates them. Integrating C/cpp files is otherwise done with the installed gcc toolchain from compile to link.

>> a look in : ...\hardware\teensy\avr\libraries\ADC\ADC.cpp shows these that should shut it all down ::
Code:
// Disable ADC DMA request
void ADC::disableDMA(int8_t adc_num) {

     // … and

// Disable interrupts
void ADC::disableInterrupts(int8_t adc_num) {
 
That works. To restart it, you need a third step:

adc->enableDMA(ADC_0);
adc->enableInterrupts(ADC_0);
dmaBuffer->start(&dmaBuffer_isr);
 
Cool! Looking at the sample : ...\hardware\teensy\avr\libraries\ADC\examples\ringBufferDMA\ringBufferDMA.ino

It does all those things in Setup and then the 's' start command to get it going.

Might be nice to extend the example for 'd': disable that would have demonstrated your use case.

Wondering if these commands [ from : ~RingBufferDMA() ] would be a cleaner solution if it is going to stop and start?
Code:
    dmaChannel->detachInterrupt();
    dmaChannel->disable();

Then just calling the dmaBuffer->start(&dmaBuffer_isr); would not require the enable calls?
 
From the names of the calls, it would seem you need to attach to the interrupt again, and enable again. Generally, as a preference, I like to disable interrupts before I detach the isr,
 
2) How would I practically take the Fourier Transform of a data set? Is there some simple "toolset" that I could use (having never undertaken such an operation previously).
I realize this is hopelessly late, but it is very simple to work with fourier transforms (in practice, FFT) on a PC, using tools like Matlab. Here's an example http://de.mathworks.com/help/matlab/ref/fft.html;jsessionid=1892e6802587f293ca2dd5210cc8

Matlab is expensive. However there is a free alternative, GNU Octave that runs much of the same code, including the FFT example above. Available from https://www.gnu.org/software/octave/
 
From the names of the calls, it would seem you need to attach to the interrupt again, and enable again. Generally, as a preference, I like to disable interrupts before I detach the isr,

Thus my note … that is exactly what the dmaBuffer->start(&dmaBuffer_isr); does :: ...\hardware\teensy\avr\libraries\ADC\RingBufferDMA.cpp
Code:
	// ...
	dmaChannel->enable();
	dmaChannel->attachInterrupt(call_dma_isr);

So doing that would be a simpler and safer change - if it works - than the prior post code as used.

Indeed there may be a preferred order - that detach ordering is copied from destructor code.
 
Thank you. Is that using DMA? On first perusal, it seems there is quite a lot of functionality, I am not sure how to quickly find the internal adc sujpport
 
That (dmaCannel->enable()) gives an compilation error, 'dmaChannel' was not declared in this scope.
 
Thank you. Is that using DMA? On first perusal, it seems there is quite a lot of functionality, I am not sure how to quickly find the internal adc sujpport
It uses Paul's audio library that uses DMA.
Best, use Audio Design GUI and simply try it out. DMA is Always build in.
 
i was unable to construct my system from the available documentation. i want to output a short waveform while simultaneously reading two channels of analog input, at a clock rate as fast as the simple can run.
 
Status
Not open for further replies.
Back
Top