3

I am using the RPi to continuously acquire voltage data from an external ADC. I am using MCP3201 ADC (12 bit, 10k SPS) in a bit banging approach to get voltage data to Raspberry Pi and write it to a USB disk. Unfortunately the data acquired has unaccounted gaps in it.

Gaps in acquired data (Voltage(V) vs Time(microseconds)

I am unable to figure out the reasons for this. Any ideas? We need to have a continuous data acquisition to happen.

The power supplied to the RPi is through a 230V AC to 5V DC adapter with decent filtering.

Here's the code script used to get the data

#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <fcntl.h>     // open
#include <inttypes.h>  // uint8_t, etc
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <linux/i2c-dev.h>
#include <asm-generic/errno-base.h>
#include <wiringPi.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

#define GPSET0 7
#define GPSET1 8

#define GPCLR0 10
#define GPCLR1 11

#define GPLEV0 13
#define GPLEV1 14

#define GPPUD     37
#define GPPUDCLK0 38
#define GPPUDCLK1 39

unsigned piModel;
unsigned piRev;

static volatile uint32_t  *gpioReg = MAP_FAILED;

#define PI_BANK (gpio>>5)
#define PI_BIT  (1<<(gpio&0x1F))

/* gpio modes. */

#define PI_INPUT  0
#define PI_OUTPUT 1
#define PI_ALT0   4
#define PI_ALT1   5
#define PI_ALT2   6
#define PI_ALT3   7
#define PI_ALT4   3
#define PI_ALT5   2

void gpioSetMode(unsigned gpio, unsigned mode)
{
   int reg, shift;

   reg   =  gpio/10;
   shift = (gpio%10) * 3;

   gpioReg[reg] = (gpioReg[reg] & ~(7<<shift)) | (mode<<shift);
}

int gpioGetMode(unsigned gpio)
{
   int reg, shift;

   reg   =  gpio/10;
   shift = (gpio%10) * 3;

   return (*(gpioReg + reg) >> shift) & 7;
}

/* Values for pull-ups/downs off, pull-down and pull-up. */

#define PI_PUD_OFF  0
#define PI_PUD_DOWN 1
#define PI_PUD_UP   2

void gpioSetPullUpDown(unsigned gpio, unsigned pud)
{
   *(gpioReg + GPPUD) = pud;

   usleep(20);

   *(gpioReg + GPPUDCLK0 + PI_BANK) = PI_BIT;

   usleep(20);

   *(gpioReg + GPPUD) = 0;

   *(gpioReg + GPPUDCLK0 + PI_BANK) = 0;
}

int gpioRead(unsigned gpio)
{
   if ((*(gpioReg + GPLEV0 + PI_BANK) & PI_BIT) != 0) return 1;
   else                                         return 0;
}

void gpioWrite(unsigned gpio, unsigned level)
{
   if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
}

void gpioTrigger(unsigned gpio, unsigned pulseLen, unsigned level)
{
   if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;

   usleep(pulseLen);

   if (level != 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
}

/* Bit (1<<x) will be set if gpio x is high. */

uint32_t gpioReadBank1(void) { return (*(gpioReg + GPLEV0)); }
uint32_t gpioReadBank2(void) { return (*(gpioReg + GPLEV1)); }

/* To clear gpio x bit or in (1<<x). */

void gpioClearBank1(uint32_t bits) { *(gpioReg + GPCLR0) = bits; }
void gpioClearBank2(uint32_t bits) { *(gpioReg + GPCLR1) = bits; }

/* To set gpio x bit or in (1<<x). */

void gpioSetBank1(uint32_t bits) { *(gpioReg + GPSET0) = bits; }
void gpioSetBank2(uint32_t bits) { *(gpioReg + GPSET1) = bits; }

unsigned gpioHardwareRevision(void)
{
   static unsigned rev = 0;

   FILE * filp;
   char buf[512];
   char term;
   int chars=4; /* number of chars in revision string */

   if (rev) return rev;

   piModel = 0;

   filp = fopen ("/proc/cpuinfo", "r");

   if (filp != NULL)
   {
      while (fgets(buf, sizeof(buf), filp) != NULL)
      {
         if (piModel == 0)
         {
            if (!strncasecmp("model name", buf, 10))
            {
               if (strstr (buf, "ARMv6") != NULL)
               {
                  piModel = 1;
                  chars = 4;
               }
               else if (strstr (buf, "ARMv7") != NULL)
               {
                  piModel = 2;
                  chars = 6;
               }
            }
         }

         if (!strncasecmp("revision", buf, 8))
         {
            if (sscanf(buf+strlen(buf)-(chars+1),
               "%x%c", &rev, &term) == 2)
            {
               if (term != '\n') rev = 0;
            }
         }
      }

      fclose(filp);
   }
   return rev;
}

int gpioInitialise(void)
{
   int fd;

   piRev = gpioHardwareRevision(); /* sets piModel and piRev */

   fd = open("/dev/gpiomem", O_RDWR | O_SYNC) ;

   if (fd < 0)
   {
      fprintf(stderr, "failed to open /dev/gpiomem\n");
      return -1;
   }

   gpioReg = (uint32_t *)mmap(NULL, 0xB4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

   close(fd);

   if (gpioReg == MAP_FAILED)
   {
      fprintf(stderr, "Bad, mmap failed\n");
      return -1;
   }
   return 0;
}


int main()
{
    FILE * DataFile;
    FILE * TESTDEVFILE1;
    uint32_t    TimerValUSec = micros();        // Store the time elapsed (in u sec) from the beginning of the program execution. Max Val of 71 min

    time_t rawtime;
    struct tm * timeinfo;

    int16_t val;                                // Stores the 16 bit value of our ADC conversion

    int i;
    int j;
    int fd;
    int USBAvl;
    int USBRem;
    int StatSDA1;
    int StatSDB1;
    int StatSDC1;
    int I2CFile;
    int Idx = 0;
    int Count = 0;
    int ADS_address = 0x48;             // Address of our device on the I2C bus 
    int ADCAmpCoded;
    int ADCVoltCoded;
    char SrcPath[100];
    char FileName[100];
    char DestPath[100];
    char FileCpyStr[100];
    char FileDelStr[100];

    if (gpioInitialise() < 0) return 1;

    /* Pin connection for interfacing (GPIO numbers are in BCM format)
    # Clock             : GPIO4     : Output
    # Data              : GPIO17    : Input
    # Chip select (V)   : GPIO27    : Output
    # Chip select (I)   : GPIO22    : Output
    # Red LED           : GPIO23    : Output
    # Green LED         : GPIO24    : Output
    */

    /* Configure the output pins */
    gpioSetMode( 4, 1); 
    gpioSetMode(27, 1); 
    gpioSetMode(22, 1); 
    // gpioSetMode(18, 1);
    gpioSetMode(23, 1);
    gpioSetMode(24, 1);

    /* Configure the input pins */
    gpioSetMode(17, 0);

    gpioWrite(27, 1); // Set CS to HIGH to disable the ADC for voltage
    gpioWrite(22, 1); // Set CS to HIGH to disable the ADC for current
    gpioWrite( 4, 1); // Set the clock pin to HIGH

    strcpy (SrcPath, "cp --no-preserve=mode,ownership ");
    strcpy (DestPath," /mnt/usb1/");            // Location to mount the detected USB device

    while (1)
    {
        Idx = 0;

        USBAvl = 0;
        USBRem = 0;

        printf("\n Searching for device ");
        printf("...");
        while (USBAvl == 0)
        {   
            /* Search for SDA1, SDB1 and SDC1 in root/dev. If found then exit this loop */
            /* Check if SDA1 file is detected in device directory */
            fd = open("/dev/sda1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            if (fd < 0)
            {
                if (errno == EEXIST) 
                {   system("sudo mount -t vfat -o uid=pi,gid=pi /dev/sda1 /mnt/usb1/"); USBAvl = 1;}
            }

            /* Check if SDB1 file is detected in device directory */
            fd = open("/dev/sdb1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            if (fd < 0)
            {
                if (errno == EEXIST) 
                {   system("sudo mount -t vfat -o uid=pi,gid=pi /dev/sdb1 /mnt/usb1/"); USBAvl = 1;}
            }

            /* Check if SDC1 file is detected in device directory */
            fd = open("/dev/sdc1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            if (fd < 0)
            {
                if (errno == EEXIST) 
                {   system("sudo mount -t vfat -o uid=pi,gid=pi /dev/sdc1 /mnt/usb1/"); USBAvl = 1;}
            }
        }

        // New USB Installed
        /* ########################## */
        gpioWrite(23, 1);   /* Set RED LED to indicate writing to USB has begun */
        gpioWrite(24, 0);   /* Reset GREEN LED to indicate writing to USB has begun */

        while (Idx < 150) //Number of files
        {

            time(&rawtime);
            timeinfo = localtime(&rawtime);
            strftime(FileName,100 ,"/home/pi/C_Code/CurrentDataFile%Y%b%d_%H%M%S.txt",timeinfo);
            strcpy(FileCpyStr, SrcPath);
            strcat(FileCpyStr, FileName);
            strcat(FileCpyStr, DestPath);
            strcpy(FileDelStr, "rm ");
            strcat(FileDelStr, FileName);

            DataFile = fopen(FileName,"w");     // Open the data file which will store the data

            Count = 0;

            while(Count++ < 1000000) //Number of samples per file
            {
                gpioWrite(27, 0); // Set CS to LOW to enable the voltage ADC

                ADCVoltCoded = 0;

                for (i=0; i<15; i++)
                {
                    gpioWrite(4, 0);    // Bring clock to LOW
                    for (j=0;j<10;j++);  // Delay to stabilize the charging value
                    gpioWrite(4, 1);    // Bring clock to HIGH
                    for (j=0;j<10;j++);  // Delay to stabilize the charging value
                    ADCVoltCoded = (ADCVoltCoded<<1)|(gpioRead(17));
                }

                gpioWrite(27, 1); // Set CS to HIGH to disable the voltage ADC
                for (j=0;j<10;j++); // Delay to stabilize the charging value
                gpioWrite(22, 0); // Set CS to LOW to enable the current ADC

                ADCAmpCoded = 0;

                for (i=0; i<15; i++)
                {
                    gpioWrite(4, 0);    // Bring clock to LOW
                    for (j=0;j<10;j++);  // Delay to stabilize the charging value
                    gpioWrite(4, 1);    // Bring clock to HIGH
                    for (j=0;j<10;j++);  // Delay to stabilize the charging value
                    ADCAmpCoded = (ADCAmpCoded<<1)|(gpioRead(17));
                }

                gpioWrite(22, 1); // Set CS to HIGH to disable the current ADC

                ADCVoltCoded = ADCVoltCoded & 0xFFF;
                ADCAmpCoded  = ADCAmpCoded  & 0xFFF;

                //printf ("\n Time : %d \t Voltage : %d \t Current : %d",TimerValUSec, ADCVoltCoded, ADCAmpCoded);

                TimerValUSec = micros();

                fprintf(DataFile, "\n %u , %d, %d", TimerValUSec, ADCVoltCoded, ADCAmpCoded);

                for (j=0;j<6000;j++);  //Dummy Loop to control samples per second which can be changed
            }
            fclose(DataFile);
            system(FileCpyStr);
            system(FileDelStr);
            Idx++;
        }

        system(" sudo umount /mnt/usb1/");

        gpioWrite(23, 0);           // Switch Off RED : USB Writing Done
        gpioWrite(24, 1);           // Switch On Green : USB Full

        // wait until usb is removed
        printf("\n Device Full, please remove it...");
        printf("\n");
        while (USBRem == 0)
        {
            /* Check if SDA1 is present in device directory */
            fd = open("/dev/sda1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            StatSDA1 = ((fd < 0) && (errno == EEXIST)) ? 1 : 0;

            /* Check if SDB1 is present in device directory */
            fd = open("/dev/sdb1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            StatSDB1 = ((fd < 0) && (errno == EEXIST)) ? 1 : 0;

            /* Check if SDC1 is present in device directory */
            fd = open("/dev/sdc1", O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
            StatSDC1 = ((fd < 0) && (errno == EEXIST)) ? 1 : 0;

            /* If none of SDA1, SDB1 or SDC1 is found, then exit the loop */
            if ((StatSDA1 == 0) && (StatSDB1 == 0) && (StatSDC1 == 0))
            {
                USBRem = 1;
                printf("\n Device removed successfully...");
            }
        }
        printf("\n CONFIRMED"); 

    }

    return 0;

}
8
  • You give no scale so I have no idea if the gaps are years, weeks, day, or microseconds. – joan Sep 27 '16 at 17:07
  • @joan Sorry, my mistake. The scale is in microseconds. – Krishna R Sep 27 '16 at 17:10
  • 2
    If the scale is microseconds the maximum gap would appear to be 2 µs. That doesn't make sense as the sample rate would only be about 10ksps, so a regular gap would be 50 or so µs. – joan Sep 27 '16 at 17:17
  • 1
    Well, as much information as you want to provide (within reason) -- you should consider what you would think if you read this. What's to say about it besides, "oh well"? I guess people could make random guesses like "EM interference!" (to be silly), but I would not expect someone to sit down and spend a few hours detailing how they would do it from scratch, but if you provided relevant code, they might glance through it and have suggestions. – goldilocks Sep 27 '16 at 17:22
  • 1
    Just out of curiosity, can you try to to adjust the process priority to near real time using nice and see if that reduces such gaps. – Shreyas Murali Sep 27 '16 at 17:24
6

Cursory look over your code makes me think there might be a performance problem with the way you have interspersed your file writing logic with your acquisition loop. I am referring the fprintf statement in the acquisiton loop. My hunch is OS provided file buffers are getting filled due to the repeated fprintf and at some point fprintf blocks until the buffers are flushed and all writes are done. The write ares subject to the limits of the port speeds and the drive's write speed. this could explain the gaps in your acqusition.

For a quick fix, I'd recommend using a binary write rather than a printf so that less cpu and memory will be used. if required you can write a binary to text converter that works offline

From a larger design perspective I'd recommend trying the following changes

create a struct like so to represent a sample

struct sample
{
    uint32_t time_stamp;        // 4 bytes
    int      adc_amp_coded;     // 4 bytes
    int      adc_volt_coded;    // 4 bytes
} __attribute__((packed));      // make sure no wasted space was we will be allocating large buffers

create 2 buffers. each buffer capable of storing 1000000 samples

#define NUM_SAMPLES (1000000)

sample * buffer1 = new sample[NUM_SAMPLES]; // 12 * 1000000 = 12000000 bytes ~= 12 MB
sample * buffer2 = new sample[NUM_SAMPLES]; // 12 * 1000000 = 12000000 bytes ~= 12 MB

sample * adc_buffer = buffer1, file_buffer = buffer2;

Use 2 threads, 1 for acquisition, 1 for file writing using pthreads once NUM_SAMPLES samples have been acquired, swap the 2 buffers - adc_buffer and file_buffer and let the acquisition continue with the other buffer

In theory, using 2 threads will allow the file write thread to block without affecting the acquisition thread though the real underlying problem is the different subsystems (GPIO, USB, RAM, CPU) work at different speeds and some kind of tradeoff definitely required.

there are probably many other tweaks worth pursuing but I will stop here :)

1

the data acquired has unaccounted gaps in it

I think "accounting" for them is simple enough, it is arbitrary latency. If the units there are seconds, then that is a cause for consternation.

If they are centiseconds or smaller, that's the nature of the beast on a multi-tasking OS. You're not going to be able to guarantee yourself zero-latency, although you may in practice be able to achieve close enough to it.

You've clarified they're microseconds, in which case I'd say you should probably count yourself lucky as is.

We need to have a continuous data acquisition to happen.

Use a parallel in, serial out shift register on the UART; you should be able to get decent sub-millisecond resolution that way (anecdotally I'd say it is reliable up to 1 Mbps).

3
  • With the UART approach, would it be possible to get 10k Samples per second consistently without these gaps? – Krishna R Sep 27 '16 at 17:21
  • I can't promise, but at 12 bits per sample, assuming you have to round that up to 16 (I think anything that's not an even byte size chunk will present problems for the UART interface), that's 160 Kbaud; you'd probably also have to fit that into a standard rate, I think the closest higher one would be 230.4 Kbaud. This is not something I've tried, it's just an idea/suggestion, so again, no promises... – goldilocks Sep 27 '16 at 17:29
  • Our larger sibling site, Electrical Engineering, might be a better place to ask about the general potential for that kind of approach; if it is not sane they will tell you. – goldilocks Sep 27 '16 at 17:31
1

Using DMA bit banging it is possible to get accurately timed gap-less samples from the MCP3202. The same technique should work for the MCP3201 with the obvious change to the SPI command to request the data.

The MCP3202 (12-bit ADC 2 channels) command SPI tranfer is

1  2  3  4  5  6   7   8  9  10 11 12 13 14 15 16 17
SB SD OS MS NA B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0

SB  1  1
SD  1  0=differential 1=single
OS  0  0=ch0, 1=ch1 (in single mode)
MS  0  0=tx lsb first after tx msb first

With DMA the maximum bit rate is 500kbps. The above 17 bits take 34µs. If a transfer is scheduled every 40µs that provides 25ksps.

See example code at http://abyz.me.uk/rpi/pigpio/examples.html#C_rawMCP3202_c

0

You need two buffers at least, maybe more. I am not that familiar with the PI or the adc, but the input of the data should be an event or interrupt driven. The input interrupt service program should run at a higher priority than the output program. This would keep you from missing input data unless you run out of buffer room. This is also what might be happening. Two threads with the input running at a higher priority than the output, while passing buffers between the two would accomplish this. Emptying buffers fast enough is another problem that you must address. Calculation of the buffer size and how many is up to you. You should try using a fast hard disk instead of a USB disk and then copy the data to the USB disk.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.