User avatar
Pekka
Posts: 20
Joined: Mon Nov 19, 2012 4:11 pm

Sampling from ADC on SPI

Fri Aug 19, 2016 8:53 am

Hello,

I'm doing sampling from a MCP3208 ADC on SPI using the bcm2835 library to communicate with the chip.
The target is to have the time interval of 4 ms to sample all 8 channels. I use a Raspberry Pi model B with the PREEMPT RT patched kernel.
The process execution priority is set in the code like this:

Code: Select all

struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = sched_get_priority_max(SCHED_RR);
sched_setscheduler(0, SCHED_RR, &sp);
setpriority(PRIO_PROCESS, 0, -20);
mlockall(MCL_CURRENT | MCL_FUTURE);
which yields this in top when run:

Code: Select all

PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2010 root      rt -20   20396   3612   3200 S  0.3  0.7   0:00.15 test
The fuction to read the ADC values from SPI is the following:

Code: Select all

int readadc(char adc_channel){
    if(adc_channel>7||adc_channel<0){
        printf("ERROR: Invalid Channel. Valid Channels are %d through %d\n", 0, 7); return -1;
    }
    //mcp3208
    char b1 = 4+(adc_channel>>2);
    char b2 = (adc_channel&3)<<6;

    char buf[] = { b1, b2, 0 };
    bcm2835_spi_transfern(buf, sizeof(buf));
    int adcout = (((buf[1]&15) << 8) + buf[2]);
    return adcout;
}
In the main loop of the c++ program I simply do the following:

Code: Select all

timestamp_msec = ((long long int) timer_msec.time) * 1000ll + (long long int) timer_msec.millitm-inittime_msec;
if (timestamp_msec >= next_timestamp_msec) { // don't write if interval not reached yet
    fprintf(fi, "%llu", timestamp_msec);
    for (a2dChannel=0; a2dChannel<=noChannels; a2dChannel++) {
        adcval = readadc(a2dChannel);
        VIn = (double) (((adcval*5)/4096.0)-offset[a2dChannel]) * MULTIPLIER[a2dChannel];
        fprintf(fi,"\t%1.3f",VIn);
    }
    next_timestamp_msec = timestamp_msec + sampling_interval;
}
(sampling_interval is set to 4).
The thing works almost perfectly (evenly spaced 4 ms intervals) except for an occasional "lock", where the program stops sampling and writing to file for a random time that can be from 50 to up to 600 ms. This sometimes happens only after several tens of seconds after sampling start but sometimes also almost immediately after start. If I use the stock kernel there seems to be more such occurences but the lock times are shorter (up to 50 ms) and I also loose the evenly spaced 4 ms intervals.
At first I thought the delay is caused by writing to SD card, but this seems not to be the cause since if I comment out the ADC reading the problem goes away.
I would appreciate an explanation about what is causing this (and possibly how to make it go away).
Thanks.

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

Re: Sampling from ADC on SPI

Fri Aug 19, 2016 9:39 am

Presumably Linux needs to do something else periodically.

If you do need strict timing you will need to bit bang using DMA. See http://abyz.co.uk/rpi/pigpio/examples.h ... wMCP3008_c for an example. This assumes you are familiar with SPI and C.

T3am5hark
Posts: 62
Joined: Wed Jul 17, 2013 5:37 pm

Re: Sampling from ADC on SPI

Fri Aug 19, 2016 11:19 pm

How are you populating the timer struct? I assume it's using ftime?

Is it fair to assume that timestamp_msec and next_timestamp_msec are both declared as long long ints?

It looks like a pure "busy wait" polling approach - given that you only have to worry about 4ms time windows, you can probably put a small (say 50us) nanosleep in your loop and cut down significantly on the CPU from continuous polling which may help.

User avatar
Pekka
Posts: 20
Joined: Mon Nov 19, 2012 4:11 pm

Re: Sampling from ADC on SPI

Mon Aug 22, 2016 12:24 pm

How are you populating the timer struct? I assume it's using ftime?
Is it fair to assume that timestamp_msec and next_timestamp_msec are both declared as long long ints?
Yes, both assumptions are correct.
It looks like a pure "busy wait" polling approach - given that you only have to worry about 4ms time windows, you can probably put a small (say 50us) nanosleep in your loop and cut down significantly on the CPU from continuous polling which may help.
I inserted the nanosleep as suggested, but it did not itself solve the problem (although it did bring the CPU load down to some 60% compared to 97% without it, which also helps a lot).
I might have found the culprit for the delay, though: before starting to sample and write to file I open the file and write the header lines to it like this:

Code: Select all

        // Compose file name from current time
        time_t rawtime;
        struct tm * timeinfo;
        char buffer [80];
        time (&rawtime);
        timeinfo = localtime (&rawtime);
        strftime (buffer,80,"ib_%Y%m%d-%H%M%S.txt",timeinfo);
        char *fname = buffer;

        // Open file for writing
        FILE *fi = fopen(fname, "w");
        if (fi == NULL) {
            printf("Error opening file for writing\n");
            exit(1);
        }

        // Write file header
        time_t now;
        time(&now);
        fprintf (fi, "File %s started on %s\n", fname, ctime(&now));
        fprintf (fi, "time [ms]");
        for (int i=0; i<=noChannels; i+=1) {
            fprintf(fi, "\tACH%d", i);
        }
        fprintf(fi, "\n");
It turns out that if I flush the file buffer afterwards (and wait another 600 ms for good measure) like this:

Code: Select all

        fflush(fi);
        bcm2835_delay(600);
the delay does not seem to occur any longer. This needs a bit more testing, though, but I have just sampled several runs (including a 30 min long one) on all 8 channels and the delay was not present in any of them.

Return to “C/C++”