aethersis
Posts: 3
Joined: Wed Mar 07, 2018 9:30 pm

The most efficient way to transfer SPI data

Wed Mar 07, 2018 9:41 pm

I'm currently building a robot which will be able to localize source of sound and move towards it. In order to achieve it I am using an array of 4 microphones and sample rate of 250kHz per microphone (1 MSample/s in total) with 12 bit resolution. A single read from the ADC124S101 chip takes 16 bits in total, which means I'd need at least 16 Mbit bandwidth out of SPI in rPi 3B somehow. So far, with WiringPi library I got up to 50kSample/s which is pretty horrible and the sample rate seems to fluctuate/stutter which is completely unacceptable for the DSP algorithms. The library seems a bit fishy as there is no option to setup SPI mode (clock polarity/phase). Thankfully the default settings work.
So in short - what is the absolutely fastest way to read from SPI and is the bandwidth I desire possible at all? If not, what is the highest achievable SPI bandwidth for rPI?

PS: If there's no better way I can always stick to STM32F4 microcontroller for DSP and rPi for even more complex tasks, but I'd prefer to avoid it.

aethersis
Posts: 3
Joined: Wed Mar 07, 2018 9:30 pm

Re: The most efficient way to transfer SPI data

Thu Mar 08, 2018 9:46 pm

I managed to write a benchmark program which measures the speed of GPIO and my previous error was that I was not buffering the reads. I am still using the WiringPi library. It can be observed that the more packets are being sent at once, the better bitrate is achieved, however this seems to plateau at certain value. I managed to get up to around 2MB/s (16Mbit/s) which is sufficient for my needs. However I still wonder if better speed is possible? Here's a plot I got at 32MHz clock (increasing the clock rate didn't produce any better results):
plot.png
plot.png (23.53 KiB) Viewed 1143 times
The benchmark code is as follows:

Code: Select all

static bool echoNBytes(const int bytesCount, unsigned char fillValue)
{
    unsigned char spiData[bytesCount];
    memset(spiData, fillValue, bytesCount);

    wiringPiSPIDataRW(0, spiData, bytesCount) ;

    return std::all_of(spiData, spiData + bytesCount, [&](const unsigned char &a){return a==fillValue;});
}

bool validateReceived(bool* received, int packetCount, unsigned char fillValue)
{
    return std::all_of(received, received + packetCount, [&](const unsigned char &a){return a==fillValue;});
}

void benchmark()
{
    using std::chrono::high_resolution_clock;
    int packetSizeB;
    const int clockSpeed = 16000000;
    const int packetCount = 100000;
    const unsigned char fillValue = 0xb01010101;
    bool received[packetCount];

    if(wiringPiSPISetup(0, clockSpeed) > 0)
    {
        for(packetSizeB = 1; packetSizeB <= 64; ++packetSizeB)
        {
            high_resolution_clock::time_point t1 = high_resolution_clock::now();
            for(int i=0; i<packetCount; ++i)
            {
                received[i] = echoNBytes(packetSizeB, fillValue);
            }
            high_resolution_clock::time_point t2 = high_resolution_clock::now();

            bool allValid = validateReceived(received, packetCount, fillValue);
            auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( t2 - t1 ).count()/1000.f;

            std::cout << packetSizeB << ", " << packetCount*packetSizeB/duration << ", " << allValid << "\n" << std::flush;
        }
    }
}
I test it using a jumper between MISO and MOSI pins. In the test I ensure that there is no data corruption - or in other words that all the packets sent are the same as packets received. I profiled the code and the checks have negligible influence on the measured time.

User avatar
joan
Posts: 13540
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: The most efficient way to transfer SPI data

Thu Mar 08, 2018 10:01 pm

Was that a Pi3B? The other Pis seem to top out at about 20k samples per second with the Linux driver.

There will be nothing you can do about the sampling stutter under Linux, you will have to cope with that or find another solution.

My benchmarks at http://abyz.me.uk/rpi/pigpio/faq.html#How_fast_is_SPI

My hosting site is having problems with false virus reports on my C benchmark code.

aethersis
Posts: 3
Joined: Wed Mar 07, 2018 9:30 pm

Re: The most efficient way to transfer SPI data

Thu Mar 08, 2018 10:42 pm

Yes, I forgot to add, it was Raspberry PI 3B. Perhaps a realtime kernel would help? I found a really promising tutorial here: http://www.frank-durr.de/?p=203

User avatar
joan
Posts: 13540
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: The most efficient way to transfer SPI data

Thu Mar 08, 2018 11:03 pm

I don't know if a RT kernel will help or not. I can see it being more stable if you ask for a reading every x microseconds for some largish value of x. I'm not sure it will be of much use if you are in fact busy spinning SPI reads.

Re RT kernel also see viewtopic.php?t=206750

chaosjug
Posts: 18
Joined: Wed Nov 05, 2014 8:24 pm

Re: The most efficient way to transfer SPI data

Tue Mar 13, 2018 8:13 pm

The ADC124S101 has a maximum clock speed of 16Mhz, so if you want 1MSample/s the SPI bus needs to continuously transfer data. If it is possible at all, you need to use DMA. The idea is that the SPI runs independent of the CPU reading and writing to a memory buffer via DMA (note: you need SPI0 for this as SPI1 and SPI2 don't support DMA). Your program then only evaluates the data which is written to memory. The bigger the buffer, the less likely that your program falls too much behind and misses data. As Linux is no real-time OS there is no guarantee that your program gets often enough CPU time to process the data.
I don't know if it would be easier to do with a STM32F4. Does it have DMA? If yes, then it should be possible and if only your code runs on, preventing buffer under-runs is easier :)

User avatar
bitbank
Posts: 248
Joined: Sat Nov 07, 2015 8:01 am
Location: Sarasota, Florida
Contact: Website

Re: The most efficient way to transfer SPI data

Wed Mar 14, 2018 4:12 pm

Sounds like you need to get a capable MCU like a Cortex-M to manage the data reading and then pass it on to the RPI in asynchronous chunks. Even with Joan's great SPI code, there will be timing hiccups on any Linux system reading a real-time stream. There will be gaps between each block read from the device due to the DMA hardware limitations and possibly the default SPI block size (4K). A microcontroller can manage the SPI hardware (even polling) without being interrupted.
The fastest code is none at all :)

Return to “C/C++”

Who is online

Users browsing this forum: No registered users and 7 guests