FEZ Raptor + High Speed ADC

Hello Experts,

I am looking for solution how to connect FEZ Raptor with high speed adc. The adc has speed around 2k-3kSPS over SPI, and has “data ready” interrupt output for synchronization. I have read that the managed CLR is not advised for such a time critical / high-speed application.

I would like to ask you what is the suggested way to handle these devices, how to handle this situation? Is the RLP the only way to go?

Thank you for your answers!

Edit: What could be the estimated data rate that I can handle in managed code and RLP?
Edit: Shall I insert an arduino btw the ADC and the Raptor? The arduino handles the high speed SPI and send data packets over serial to Raptor?

Any advice or idea?

I’d be more interested to know what sample rate you need for your application and what you’re going to do with the data? If you’re just buffering the data in another micro and sending the same data at the same rate over a serial connection, you’re not doing anything different (SPI is a form of serial connection).

Being as it is possible to do realtime audio ADC using RLP on slower boards I would guess that it should be possible to do this on a RAPTOR. I assume that NETMF buffers this input at a native of even a DMA level, so you might even be ok sticking with managed code.

Please do come back at let us know if it works.

Hello,

Thank you for your feedback. Regarding the sampling rate, I think the maximum is 2kHz for one ADC.
Currently I would like to connect one ADC (8channel) to the raptor and one adc provides that 2kHz data rate. Later on I would like to increment the number of ADC…
But focusing on the present, my goal is it handle 1 adc with one raptor. The raptor do the buffering, making some basic calculation on the acquired data, handle TCP communication and so on… Shortly, the raptor has many low priority tasks.

My problem is the 2kHz data rate. As I mentioned in my first post, the adc is able signal the “data ready” with an interrupt, so my job is for every incoming interrupt to start a short (48clk length) SPI communication in order to gather the data from the adc and then waiting for the next interrupt. The task is simple. But I don’t know is raptor capable of doing this job.
Correct me if I am wrong, but we have maximum 0.5 ms delay between every interrupt and I need to do the SPI communication too.

The NETMF 1ms timebase confuses me… according to it, I doubt that it would be possible to handle this task with managed code. (And somewhere I read there is 20ms delay between the interrupt and the method that run.)

RLP: That should be the way to go, but I need some more experienced user word that should work, and RLP is fast enough to do the job in time. ( In this case, I would do the buffering in RLP level, and firing events when the buffer is ready, for example 100 sample arrived.)
Looking for some RLP - SPI implementation without any success. :frowning: A GHI posts says the NETMF porting kit is a good starting point but I have not found that code yet.

It is good to know: the data acquisition on Raptor won’t be continuous. It takes maximum 1 min and stop for a 1min and starts over again.

Thank you for your time,

what rate does your application NEED data at ? If you’re reading data to plot on a graph over a day, then 2kHz sample rate is crazy; if you need to sample at that rate only for a single second every minute then that might be appropriate.

Dear Brett,

Thank you for your reply. The sampling rate requirement of my application is 100Hz per channel. So I have 8 channels → 800 sample per channel (800Hz).

I think the key is whether you need continuous data for long periods of time and how precisely the data has to be sampled. I currently have a system with a SPI ADC from Analog Devices. It is an 8 channel device and is capable of 250,000 samples per second. I don’t need to sample that fast and neither do you so that makes our job much easier. You can use the SPI chip select (CS) line as the convert signal for most SPI ADCs. As long as you can set up your ADC to output data on the SPI MISO line every time it sees a convert signal, you shouldn’t need the data ready interrupt. Every SPI transfer toggles the CS so you get one conversion per transfer. I have a timer that kicks off my readADC method. I have to write one word over the SPI to configure the ADC and I get one sample back. I do this in a loop for each channel I need to convert and I’m done for that cycle The timer is pretty repeatable even with all the other stuff I have going on in my app. You should be able to get timing accuracy of +/- a couple of milliseconds.

One complication is my understanding is you don’t want to spend a lot of time in your event handlers, like a timer handler. All my timer handler does is stick a message to sample the ADC on a queue I have in implemented in my program. When the method that handles the queue sees the ADC message, it does the ADC read.

This approach works and is really simple if you don’t need long blocks of precisely timed samples. I’ve done that in the past but I’m a little hazy on how I did it. Seems like all did was set up my SPI transfer to read multiple bytes for as many samples as I needed. My recollection is the whole SPI transfer happened without significant jitter in the ADC conversion pulse timing.

If you tell me what ADC you’re using I can take a quick look and see if it should work this way.

Here’s my method

using System;
using Microsoft.SPOT;

using System.Threading;
using Microsoft.SPOT.Hardware;

//using GHI.Hardware.EMX;
using GHI.Hardware.G400;

namespace miniCPF
{
    class CN0254
    {
//        private static SPI.Configuration CN2054Config = new SPI.Configuration((Cpu.Pin)GHI.Hardware.EMX.Pin.IO18, false, 100, 100, false, true, 1000, SPI.SPI_module.SPI1);
        private static SPI.Configuration CN0254Config = new SPI.Configuration(Pin.PC29, false, 0, 0, false, true, 100, SPI.SPI_module.SPI2);
        private static SPI CN2054spi = new SPI(CN0254Config);

        private static ushort[] txData = new ushort[1];
        private static ushort[] rxData = new ushort[1];

        private static ushort[] configWord = new ushort[8];

        private static double[] voltage = new double[8];
        private static int[] rxShort = new int[8];

        private static double voltAccum = 0.0;
        private static int shortAccum = 0;

        //It take 2 samples before config word takes effect, see AD7689 data sheet timing diagrams
        private static int numBogusChannels = 2;
        private static int numAverages = 10;     //seems to be a problem with averaging on the Raptor
        private static int numChannels = 8;
        private static int startCh = 0;

        public static double[] scanAll()
        {
            double volts = 0.0;

            //txData[0] = 0x3FFF;     //Default
            configWord[0] = 0xF004;     //sequencer disabled, ch0 only. 1/4 BW filter
            configWord[1] = 0xF204;     //sequencer disabled, ch1 only. 1/4 BW filter
            configWord[2] = 0xF404;     //sequencer disabled, ch2 only. 1/4 BW filter
            configWord[3] = 0xF604;     //sequencer disabled, ch3 only. 1/4 BW filter
            configWord[4] = 0xF804;     //sequencer disabled, ch4 only. 1/4 BW filter
            configWord[5] = 0xFA04;     //sequencer disabled, ch5 only. 1/4 BW filter
            configWord[6] = 0xFC04;     //sequencer disabled, ch6 only. 1/4 BW filter
            configWord[7] = 0xFE04;     //sequencer disabled, ch7 only. 1/4 BW filter

            for (int chNum = startCh; chNum < (startCh + numChannels); chNum++)
            {
                volts = 0.0;
                voltAccum = 0.0;
                shortAccum = 0;
                txData[0] = configWord[chNum];
                for (int sampleNum = 0; sampleNum < numAverages + numBogusChannels; sampleNum++)
                {
                    CN2054spi.WriteRead(txData, rxData);

                    if (sampleNum > (numBogusChannels - 1))
                    {
                        //This converts the ADC output to voltage at the input to the ADC
                        volts = (3.81476E-05 * rxData[0]) + -6.35783E-06;
                        //This converts the voltage at the input to the ADC to the voltage at the input to the ADC buffer
                        volts = (volts - 1.251586) * -4.99;
                        
                        voltAccum = voltAccum + volts;
                        shortAccum = shortAccum + rxData[0];
                    }
                }
                voltage[chNum] = voltAccum / numAverages;
                rxShort[chNum] = shortAccum / numAverages;

                //Debug.Print("CH " + chNum.ToString() + " " + rxShort[chNum].ToString() + "   " + voltage[chNum].ToString("F3"));
                //Debug.Print(" ");
            }
            return (voltage);
        }
    }
}
1 Like

Hey Gene,

Wow, thank you very much for your reply.
As I understand your solution you are doing software timing on your G400. Here I have TI ADS1258 ADC with Pulse Convert Command. Using this cmd I think I can achieve similar result like yours.

You mean, the maximum sample that I can acquire from the ADC is limited only execution speed of the for loop in [em]scanAll()[/em] ? Decreasing the loop iteration time results higher sample rate.
In my case the precise timing is not really required so this should work.
[em]Yes, you are awesome! [/em]

[quote]stick a message to sample the ADC on a queue I have in implemented in my program. When the method that handles the queue sees the ADC message, it does the ADC read.[/quote] - you mean, a producer - consumer design patter? This part of your reply is not totally clear.

Thank you for your time and the example code!

Yes, the timing between ADC conversions is controlled by my software timer and the rate at which my scan loop runs. My timer calls the scanAll method at whatever rate I set, usually a couple of seconds. The scanAll method has an inner and outer loop. The inner loop reads each channel sequentially as fast as it can. The outer loop runs for as many samples as I want to average. It also runs as fast as the processor can go. Since I’m worried about accuracy more than timing jitter, I average samples to get a more accurate reading. Note that averaging in my case is the same as just getting a block of samples. Whether you average them or not is up to you.

Look at Figure 58 in the ADS1258 data sheet. I think that is the mode you want to use.

Yes, I’m using a producer consumer design pattern. I have lots of serial ports, a SPI ADC and a couple of I2C devices that all use the queue to allow their asynchronous events to work in a moderately real time application.

However, I think you have a slightly more complicated problem. The ADS1258 is a 24 bit device and the SPI port only supports 8 and 16 bit transfers. Luckily, I used the single channel version of the ADS1258 a long time ago for the high speed application I mentioned in my previous post. For a 24 bit ADC, I think you have to set up the SPI to do 8 bit transfers and do 3 reads for each conversion. I dug this code up which I can’t vouch for since it is pretty old but it may help

using System.Threading;
using System.Text;
using System.Collections;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace readMTSContinuous
{
    public class Program
    {
        public static void Main()
        {
            byte[] txData = new byte[3];
            byte[] rxData = new byte[3];

            int MTSPositionInt = 0;
            int loopCounter = 0;

            double MTSPosition = 0.0;

            string logString = "";

            BlueToothPort.initConsole("COMX");

            SPI.Configuration MTSConfig = new SPI.Configuration(Cpu.Pin.GPIO_Pin1, false, 0, 0, true, false, 1000, SPI.SPI_module.SPI2);
            SPI MTS_SPI = new SPI(MTSConfig);
            ArrayList MTSLog = new ArrayList();

            bool test = true;

            while (test)
            {
                MTS_SPI.WriteRead(txData, rxData);
                MTSPositionInt = (((rxData[0] & 0x7F) << 16) | ((rxData[1] & 0xFF) << 8) | (rxData[2] & 0xFF)) * 2;
                MTSPosition = 0.0005 * MTSPositionInt; //in mm
                logString = "Loop Counter = " + loopCounter.ToString() + "  MTS Position = " + MTSPosition.ToString("F4") + " mm" + "\r\n";

                MTSLog.Add(logString);
                Debug.Print(logString + "\r\n");
                loopCounter++;
                Thread.Sleep(1000);
            }

            for (int i = 0; i < MTSLog.Count; i++)
                BlueToothPort.sendLine((string)MTSLog[i]);
        }
    }
}

I do a lot of data acq that needs to be pretty accurate. Unless you’re doing sonar (which I do sometimes) or audio (which I don’t think you are given your sample rate) you can make your life easier using 16 bit converters and just average successive samples. Both TI and Analog Devices have tons of devices and really good evaluation modules that are really helpful.

Here’s what I’m using in my current project ( http://www.analog.com/en/circuits-from-the-lab/cn0254/vc.html )

1 Like

Perfect!
I have already implemented most of the features of ADC, only the optimization of reading left for me when realized there could be a timing issue on Raptor.
Thank you very much for all your detailed answers and example codes, help me a lot.
Using your advice I will do the same and hoping the best :slight_smile:

My last question for you, if you don’t mind: I know it was a long time ago, but do you remember how fast sampling rate could you achieve with this solution? (Independently of the adc settings.) An estimation is also fine, but I would like to know what should I expect from my Raptor.

I can’t tell you for a fact how fast you can sample but here’s a guess. We’re pretty sure you can do a 3 byte (24 bit) conversion with one 3 byte SPI ReadWrite. If you set the SPI clock rate for 1 Mhz that is 24 uSeconds. I remember there is a slight time delay between each of the SPI single byte reads. Call it 10 clocks as a conservative guess. So we’re up to 44 useconds, about 23 kHz.

You can run the SPI faster, I just don’t know how fast. I think once you get the single sample conversion going you can do a simple test. Instead of doing a 3 byte transfer, do a 30 byte transfer and see if you get 10 successive samples. Then you can fiddle with the SPI clock rate and see how fast you can go. If you input a 1 kHz of so sine wave to the ADC and plot your ADC readings, you’ll get a quick idea how well you’re doing.

Most importantly, the 3 byte transfer worked with my 24 bit MTS device which isn’t strictly a SPI device and had somewhat funny timing. Hopefully you’ll be able to put a scope on your SPI signals so you can see exactly what is happening between the SPI CS/Convert signal, SPI_CLK and your ADC.

Please let me know how this all turns out for you.

1 Like