Paul's Spark Interval library - collect data at 62.5 microsecond intervals

G’day all,

I was going to send this to @peekay123 directly but suspect there may be some value to others in the answer.

For a project I am working on I would like to sample the DAC line at 16000 Khz (Speech recog quality sampling).

I will sample 1/2 second at a time which means using Pauls excellent library I would (if my maths are correct) sample at about every 62.5 microseconds. (I will obviously need a 32k buffer to hold the data.

The code would capture from DAC and put the value into an array - then increment the array index. If index exceeds array size would stop capturing (and signal via a volatile that data had been collected).

The actual question is am I barking up the wrong tree and this data rate cannot be done on the photon?

Stan.

Also FWIW - I plan on using a couple of 4mbit frams (SPI) to store each 1/2 second ‘chunk’ . Once the big DHL bird delivers them I plan on writing a library that will implement the read/write etc to the SPIfram object(s).
(The reason I am getting two frams is;

  1. I may ‘break’ one - I have a small amount of klutz gene
  2. The real reason is that I was going to store the raw waveform in one, then read out the data and construct html into the other.

)

@Stan, you cannot “sample” DAC as it can only be used as a Digital to Analog Output or a digital I/O pin.

DAC 12-bit Digital-to-Analog (D/A) output (0-4095), and also a digital GPIO. DAC is used as DAC or DAC1 in software, and A3 is a second DAC output used as DAC2 in software.

So assuming you will be using an ADC line instead, you can certainly use SparkIntervalTimer to sample at a 16KHz interval though you may need to disable WiFi and Cloud connectivity during sampling to ensure nothing interrupts the process or affects the timing.

Storing data to FRAM via SPI can work if the FRAM has an auto-increment address mode so you can “stream” contiguous data to it without having to specify an address on each write. You could capture to RAM and then use SPI DMA to transfer the data to FRAM though I have not tried that myself.

Bottom line is careful planning of your code, especially around the ISR will be important. If you search the forum, you will find examples of data being captured and streamed (to a local server) at rates exceeding those you propose. :smiley:

2 Likes

Thanks Paul for the response.

Re DAC = what can I say but 'Doh!'
The Frams I am getting do have an autoincriment and will store data at full SPI speed (40Mhz) - So I will be using DMA!

I did get the code to work sampling at 16Khz - your library makes it extremely easy.

Essentially I capture a second then turn off the timer. - Currently I just dump it to the console - but I do (currently) have a sd card in circuit so I may write it to that.

Slowly getting there…

Stan

@Stan, great! If you use an SD, I highly recommend using the SdFat Library running in configuration zero (uses DMA). In that mode it will write at 3-4MB/s!

As for the FRAM, you will need to serialize your data in a byte array that you can DMA out. That should be easy to do using a join within a data struct so you store your captured data in a uint16_t array (0-4095) but DMA it out as a (union) array of uint8_t.

Have used the SdFat library successfully - this is an interim test until the FRAM arrives - however using DMA it may be fast enough to not even need the FRAM :frowning: (Doesn’t matter - If I end up with a couple of FRAM chips giving me 1Mb plus a library that I can contribute, I am sure it will be usefull down the line).

Re the FRAM - yes I will be researching the join/union - but correct me if I am wrong, but for DMA I just need a pointer to where I am starting and the length in bytes - so the pointer does not care really what it is pointing to be it an array of shorts or an array of bytes…

For the moment the logic of the main loop is;

  1. Init array of shorts to all zero.
  2. Init a volatile to be TRUE (collecting)
  3. Init array index to zero.
  4. Init a timer on TIMER7 with interval of 62 microseconds.
  5. while collecting is TRUE
    particle.process() - Note that I was sure there was a yield function - but for the life of me I cannot find a reference to it
  6. End timer
  7. Do something with the array of data.

The timer function is as follows

if array index = array size
set collecting = FALSE
if (collecting)
increment array index
analogread into [int]
array[array_index]=[int]

As I said, all of this is working

Ultimately I will collect say 10 second of audio (and perhaps stop process when a period of silence is encountered), send it to speech to text engine and process the text - if certain words are found certain ‘ACTIONS’ will occur!

Hi (again) Paul.

Re the DAC pin for sampling;

I was just checking the photon pinout and found this;

A0~A7 12-bit Analog-to-Digital (A/D) inputs (0-4095), and also digital GPIOs. A6 and A7 are code convenience mappings, which means pins are not actually labeled as such but you may use code like analogRead(A7). A6 maps to the DAC pin and A7 maps to the WKP pin. A4,A5,A7 may also be used as a PWM output.

SO (correct me if I am wrong) according to this DAC can be used as an a/d input?

Stan

@Stan, an interesting catch! I’ll check with the Particle folks to get the low down. :wink:

@Stan, I just verified and YES, DAC and be used as an ADC input. Geesh!

That’s alright Paul - happens to the best of us. Just the other day I thought I had made an error but turns out I was mistaken :grinning:

1 Like

@Stan

May I make a suggestion? Instead of trying to process an interrupt every 62.5 uS. You can use some of the hardware available in the STM32F205. What might work for you would be to

  1. Setup your ADC to use DMA.
  2. setup your timer to instead of interrupting you, to tell the ADC to sample. and when it is done to tell the DMA that a sample is finished.
  3. setup a buffer that is twice the size you want to deal with. The size will determine how much real time you have from the time you detect the first half of the buffer is full, untill you have to have cleared it (untill the second half of the buffer is full).
    4 Set the DMA to interrupt you at the half-full mark and the full mark and to wrap around at the end of the buffer.

so what all this gets you is this. You can sample your signal at very high rates with very little CPU overhead. You will only have to deal with a buffer every time it gets half full. This let’s you sample in your case at 16000 and if your buffer is 2048 samples you will get an interrupt every 64 mS. in that 64mS you must deal with the data in the half of the buffer that was just captured. Then you have what is left of the 64mS to do whatever before the next half buffer full and you need to deal again.

I am using this technique to capture audio at 8000 and run FFT on it to get the level in the bins and then display it on a LED matrix. (looks cool). The limit for me is the time it takes to do FFT on the M3 core. But it seems that your speed would be limited by the size of the buffer. (I also ran out of RAM, the FFT needs some arrays.).

The one comment I will make about the technique of getting an interrupt every 62.5 uS on a system like the Particle which is running a small OS. Your code may not run a deterministic time after the actual interrupt due to higher priority interrupts and the system disabling interrupts when it needs to do things, This results in jitter in your sample. This technique is much better suited to an bare-metal system where you have control of the priorities of interrupts and when they get disabled or not.

The technique I described does suffer from this issue. In fact what I did in the interrupt was to set a flag that the loop code checked and when set the loop code would process that half of the buffer that was ready. Then it would clear the flag. If the interrupt ever got a buffer full interrupt and the next buffer flag wasn’t clear it marked an overrun.

anyway I have some test code that does exactly what I described, that I can put up somewhere if you would like to see it.

Michael

2 Likes

@mishafarms, great suggestions! Posting your code would be great. Do you have a repo on github?

I just put this code up at https://github.com/mishafarms/Photon_audio.git

One small comment. This code was written a little while ago and I did not know or the technique for overloading the interrupts was not available so the taking over the interrupt was not done in a clean way. If I were updating this code, I would change the way I dealt with this.

Michael

1 Like

I did the sampling in hardware using the ADC, hardware timer and DMA, storing the samples in RAM at precise intervals without using the main CPU, as well. It’s very efficient and my example works at a 32000 Hz sample rate with 16-bit samples and streams the data out by TCP in real time to a node.js server, which writes out wav files.

I mentioned this in another thread, but I thought I’d mentioned it again in case someone finds this thread in search instead.

1 Like

@rickkas7 - Hi Rick - I did have a look at your code and whilst I cannot follow all of it (particularly the processor specific stuff (primarily because I am not so familiar with the ports/registers etc on the STM)) It looks great - studying the STM reference manual is in volume 7 of my TO-Do list! I did have one question. Currently if I Issue an analogRead I can get a 12 bit value back - your code however seems to get a 16 bit value - I am interested (if you know) why the difference between the photon implementation and what the hardware is capable of?

@mishafarms - Hi Michael, I will take great pleasure in looking at your code - thanks for that!

Stan

No, the hardware only supports 12-bits of ADC. The hardware writes the 12-bit samples into 16-bit words. I configured mine to be left-justified, so the 4 least significant bits are 0.

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;

You can also configure it as ADC_DataAlign_Right, where each 16-bit value will be 0-4095 instead.

2 Likes

@rickkas7 - Ahh - so that is the issue - I cannot read properly :frowning:

Thanks for clearing up my confusion.