Ticker/Interrupt issue

04 Jun 2011

Hello,

I'am building an acceleration data logger. (ADXL345 through 4-wire SPI, to USB mass storage). My sample rate is 800Hz which seems to be too high frequency to write it continuously to the USB. So at the moment I am missing some samples time to time. I accepted this and want to rebuild the missing section of my acceleration signal with some array filling/filtering method. To do this I will need to add a sequence number front of each set of samples.

Firstly I tried to use the InterruptIn interrupt triggered from the DATA_READY interrupt of the ADXL345, but it seems the InterruptIn has no priority over the other lines of the code.

My second attempt is to use the Ticker to increase the value of a variable, which then can be then used as the data sequence number. I set the Ticker to 1250us but somehow the output txt file I'm recording has repeatedly the same value (ticker did't call the func?) and then some missing (not after the data reaching the 512byte block size to transfer the USB how would expect it). /media/uploads/levissz/code_secion.txt

I would appreciate any suggestion how I could modify my code or hardware to achieve a correct sequence number generation, preferably from the data ready interrupt of the ADXL345.

Thanks Levente

04 Jun 2011

Best thing you could do, is to place the sampled values into an array (if you can take a short int type, you can go up to 15.000 samples). And when you're done, then write the values to de mass storage device. In my project i sample at 10.000 samples/sec (10 Khz) using a ADXL 320 in combination with an external 16 bit ADC.

06 Jun 2011

I tried a simple program (below) that simply writes the value of a timer to a file on a USB stick. Depending on the stick, I can write 4000 - 6000 lines (18 bytes) a second. So in general, logging at 800Hz can easilly be done. However, the disk write can take quite long sometimes. That will give gaps. So these are your options:

1) As marcel suggested, log to memory first and write to disk afterwards. 2) Use a timer (like in my code below) to add a timestamp to each sample. In this way you will know where the gaps are. 3) Move the entire SPI code into an interrupt, so you can keep on reading the ADXL while the filesystem is busy. Your SPI code will get a bit more complex and you'll need a buffer in memory to store the results untill the filesystem is ready for more data. 4) Send the data directly from the mbed to a PC using a serial port (or the mbed's virtual com port). Let the PC do the disk writing (teraterm can do this). 115k2 sustained isn't a problem, but the wire may be a show stopper for you.

  Timer tm;
  pc.baud(4*115200);  
  
  FILE *f;
  f = fopen("/usb/test.dat", "wb");
  pc.printf("FOPEN result = %p\n", f);
  
  tm.start();
  float dt, t, prevt, maxDt = 0.0f;
  uint32_t loops =0;
  prevt = tm.read();
  for (t=tm.read(); t<10.0; t=tm.read())
  {
    loops++;
    dt = t - prevt;
    if(dt > maxDt) 
        maxDt = dt;
    fprintf(f, "%f %f\n", t, dt);
    prevt = t;
  }
  pc.printf("Written = %d values. Largest hickup: %f seconds\n", loops, maxDt);
  fclose(f);

Stick A: Written = 40961 values. Largest hickup: 0.493180 seconds

Stick B: Written = 66504 values. Largest hickup: 0.130630 seconds

04 Jun 2011

Thanks for the replies! The problem is that the device has to measure acceleration on a bicycle so portability is a have to. I was thinking of using time stamps but I would need at least milliseconds to see something useful. SPI to interrupt function isn't gonna work since interrupt doesn't have priority over the rest of the code. Looks like my only option is to fill an array in local memory then flush it to USB stick. I was just hoping I can get a continuous acceleration signal.

05 Jun 2011

Hi, thanks for the application info. That makes choosing the right solution a lot easier. The continuous logging must be possible.

I think the idea to use an interrupt on DATA_READY is a good start. In this interrupt, you have to read the values using the SPI. However, you cannot store them to USB from the interrupt because the whole interrupt will take to long and you will miss the next samples.

So you will have to use a ring buffer to communicate between the interrupt and the main loop. Each interrupt, you store a new sample in the ring buffer. In the main loop, you save everything that arrived in the buffer since the last call. This way, while the USB call is busy, your interrupt handler keeps on adding samples to the buffer.

I saw somebody already made a ringbuffer library which you can use: http://mbed.org/users/okini3939/libraries/RingBuffer/lreh5b

I hope this helps you, regards, Jeroen

05 Jun 2011

Hi Jeroen,

thanks it does help and I was thinking something similar. The problem I have is with the InterruptIn function of the mbed. I thought whenever some perviously set event happens (DATA_READY rise in my case) the mbed jumps to the specified function and executes it before jumping back to the part it was busy before. Unfortunately it doesn't happening like that so even i set up a ringbuffer to get filled at every rise on DATA_READY whenever USB write will work this interrupt-function won't execute.

05 Jun 2011

Remember, unless you alter interrupt priorities, then all interrupts are equal and will not preempt each other (See http://mbed.org/users/AjK/notebook/regarding-interrupts-use-and-blocking/). I'm not sure if that's your issue, haven't looked closely at what you are doing, but it may affect what you are doing and what your are expecting to happen.

Regarding Mbed's InterruptIn library, you might like to have a read of this thread.

05 Jun 2011

Hi Andy,

I read the two links but I think non of them is solution to my problem. I'm including my code, which by my understanding should simply count the samples the accelerometer generates regardless I get them from the sensor or not. When I tried this code the value of 'dataSequence' variable always stayed 1, like the interrupt would be never called.

#include "mbed.h"
#include "MSCFileSystem.h"

#define FSNAME "msc"
MSCFileSystem msc(FSNAME);

SPI spi(p11, p12, p13); // mosi, miso, sclk
DigitalOut ac(p15);
DigitalOut recLed(p14); 
DigitalOut myled1(LED1);
DigitalOut myled2(LED2);
DigitalIn recPush(p9);
DigitalIn stopPush(p10);
DigitalIn int1(p16);        //WATERMARK
DigitalIn int2(p17);        //DATA_READY

InterruptIn int2(p17);      //DATA_READY  pin19 and pin20 can NOT used for interruptIn


unsigned long dataSequence=1;

void count()
{
    dataSequence = dataSequence + 1;
}

int main()
{

    recLed=0;
    char test=1;
    char rec=0;

    int2.rise(&count);      //counting sample numbers
   
    spi.format(8,3);
    spi.frequency(600000);      //SPI link's clock to 600kHz
    ac=1;
    wait_ms(0.01);            
         
    ac=0;                   //set standby mode
    spi.write(0x2d);
    spi.write(0x00);
    wait_ms(0.01);
    ac=1;
    wait_ms(0.01);
    
    ac=0;                   //set FIFO
    spi.write(0x38);        // 0x00 for bypass
    spi.write(0xbf);        //0x92 for STREAM mode size 18 samples  0x93 for 19     0xbf for full size 
    wait_ms(0.01);          //0xd2 for TRIGGER mode size 18 samples  0xd3 for 19
    ac=1;
    wait_ms(0.01);
       
    ac=0;                   //set interrupt WATERMARK to pin: INT1 and DATAREADY to pin: INT2 0xfd
    spi.write(0x2f);        
    spi.write(0xfd);       
    wait_ms(0.01);
    ac=1;
    wait_ms(0.01);
            
    ac=0;                   //set sample rate to:
    spi.write(0x2c);                    //           800 Hz   0d
    spi.write(0x0d);                    //          1.6 KHz   0e
    wait_ms(0.01);                      //          400 Hz    0c  
    ac=1;
    wait_ms(0.01);
   
    ac=0;                   //read device ID should be E5
    spi.write(0x80);        //first bit of address byte 0-write 1-read 
    char l = spi.write(0x00);
    wait_ms(0.01);
    ac=1;
    wait_ms(0.01);
            
            
    ac=0;                   //set SPI 4-wire, range 4g, full resolution and right-justified data
    spi.write(0x31);
    spi.write(0x0f);        //0x0d for left-justified 4g
    wait_ms(0.01);          //0x0f for left-justified 16g
    ac=1;
    wait_ms(0.01);
    
     ac=0;                   //set interrupt DATA_READY 0x80
    spi.write(0x2e);        //               WATERMARK 0x02
    spi.write(0x82);        // 0x82 for both       
    wait_ms(0.01);
    ac=1;
    wait_ms(0.01);
            
    ac=0;                   //set measurement mode
    spi.write(0x2d);
    spi.write(0x08);
    wait_ms(0.01);
    ac=1;
    
      FILE *fp = fopen( "/" FSNAME "/sdtest.txt", "w");
      if(fp == NULL) {
                   error("ERROR\n");
         }
      fprintf(fp, "ADXL345 test\n");
      fprintf(fp, "Device ID: %X\n", l);
      fprintf(fp, "g-values\n");
      fprintf(fp, "\tx-axis  \ty-axis  \tz-axis  \n\n");
      fclose(fp);

    while(test==1)
    { 
         if (recPush==1)
        {
        rec=1;
        dataSequence=1;
        
         FILE *fp = fopen( "/" FSNAME "/sdtest.txt", "a");       //"a" to carry on writing at the end of the file
            if ( fp == NULL )
            {
             error("ERROR\n");
            } 
            myled1=1;
      	    }
       
        while(rec==1)
        {
            recLed=1;
            
         if(int2 == 1){         //if data is ready
          
         wait_ms(0.1);
            ac=0;                       //fetch x,y,z-data
            spi.write(0xf2);            
          int x0 = spi.write(0x00);
          int x1 = spi.write(0x00);
          int y0 = spi.write(0x00);
          int y1 = spi.write(0x00);
          int z0 = spi.write(0x00);
          int z1 = spi.write(0x00);
          wait_ms(0.01);
            ac=1;                                                        
           
            if (stopPush==1)
                rec=0;
            
            signed int x = ((((x1 << 8) | x0) << 16)/524288);          //combine data bytes
            signed int y = ((((y1 << 8) | y0) << 16)/524288);
            signed int z = ((((z1 << 8) | z0) << 16)/524288);            
                       
            float xg = (float(x))/256;
            float yg = (float(y))/256;
            float zg = (float(z))/256;
              
           fprintf(fp, "%u\t%f\t%f\t%f\n", dataSequence, xg, yg, zg);
                     
           
           }         // int1 if statement end         
 
            if (stopPush==1)
                rec=0;
    
        }//while REC close
        fclose(fp);
        recLed=0;  
    }       //while test==1 end
}

My code should be something very simple but still doesn't want to work how I would expect it to. Hope including my code makes it easier to spot what I'm doing wrong.

Thanks Levente

05 Jun 2011

First up, try changing:-

unsigned long dataSequence=1;

to

volatile unsigned long dataSequence=1;
05 Jun 2011

I changed it and now it does count, but it has some weird behaviour. Some lines in my output file have the same numbering, which couldn't really happened unless some cases the InterruptIn missies. Will try to confirm tomorrow by checking the DATA_READY pin with scope.

Thanks in the meantime Levente

05 Jun 2011

DigitalIn int2(p17);
InterruptIn int2(p17);

Why do you have two object types, DigitialIn and InterruptIn with the same name, int2?

IIRC, there should be no need for DigitalIn int2(p17); as you can read an InterruptIn in the same way as a DigitalIn. It's not in the manual but reading http://mbed.org/projects/libraries/svn/mbed/trunk/InterruptIn.h it appears to have both read() and operator int()

06 Jun 2011

Hi Andy,

I had chance today to check the DATA_READY interrupt of the accelerometer with a scope. It turned out that it stays high until the master device accesses at least one of the data registers. This means collecting data by the FIFO won't change it's state, so I can not use this interrupt to count a sequence. Also noticed that the InterruptIn function might not be called since the DATA_READY state changes to quick like it would never rise. I might be wrong about this, since the oscilloscope I used can sense up to 60MHz and on this I couldn't get the a proper spike just some kind of noise at every 1.25ms(800Hz sampling rate).

Other approach what I was working on today is to use the Overrun interrupt from sensor, which should set high whenever loss of a samples occurs. Using this pin I wanted to use the InterruptIn function to set the same number for each set of continuos data but somehow this pin doesn't want to call the function. Although this pins stays high for 200ms every 1s. The declaration of the variable is happens just how u suggested.

Your suggestion using the int2 to trigger the interrupt function and also a data fetching from the sensor would work if the sensor would rise and then fall the state of the DATA_READY pin for every new samples even if never gets collected.

Thanks again for your help!

All the best Levente

06 Jun 2011

You could try using this instead of the sequence number:

Add this:

int main()
{
  Timer tm;
  tm.start();

And change your logger code to:

fprintf(fp, "%f\t%f\t%f\t%f\n", tm.read(), xg, yg, zg);

In the output file, this will give you a timestamp in seconds with each sample. It has a resolution of 0.000001 second, so you can easily see where you are missing samples.

06 Jun 2011

Levente,

There is actually a solution to your problem. From your initial post you stated you are running at 800Hz and at this frequency you are dropping samples. To me that's somewhat surprising, 800Hz is actually pretty slow and if you are dropping samples then somewhere you are getting IO bound. So I decided to go and have a read of the datasheet and think about it over a cup of tea.

The solution I came up with is probably more complex but when you think about it, it'll make sense. The first thing that the datasheet showed was that the device supports a FIFO mode called streaming. The FIFO is 32samples in depth and provides a "watermark" interrupt. The trick here is simple when you know but not obvious and it's use the GPDMA to move the data out of the device into a buffer area. However, getting all this married together isn't simple. So let me describe how I would do it:-

First, we need two buffers. Each buffer is made up of blocks, the block size memory area should match the number of samples * the watermark. So, lets imagine this for a second. Say we set the watermark to 16samples. Since each axis is two bytes of data our block needs to be 32bytes in size. Now, if we say we have 64 blocks in one buffer, that's just 2k for a buffer. Two buffers therefore use 4k.

Why two buffers? Well, here's how it works.

When the watermark IRQ fires, we setup the GPDMA/SPI to stream read the ADC345 registers using one GPDMA channel. That DMA channel is setup to point at a const memory buffer that merely describes the ADC read operation. Then another channel is used to read the SPI coming back into a block. Each time a watermark IRQ fires GPDMA transfers the data the next block, so on and so on. Until a buffer is full. When the buffer (2k) is full, you switch to the second 2k buffer and start all over.

While the second buffer is filling in DMA stream mode, you now have plenty of time to write the first buffer via USB to the memory stick. The whole operation just "round robins" swapping back and forth between the two buffers.

By using the GPDMA to manage the IO portion of the data transfers it should be able to keep up quite easily. Your CPU should have time left over to try and calculate a new decimal place of PI ;)

The tricky part is arranging the GPDMA transfers at each watermark IRQ. Also, the datasheet notes you'll have to run the SPI below 1.6MHz to ensure the transfers from FIFO to data registers.

Anyway, something for you to chew on! Have fun!

Btw, I have a GPDMA library, MODDMA, that makes handling the GPDMA setup in real simple ways using configurations. You could setup a configuration on a per block basis.

Andy

[edit: I should add I have used this technique before, just not with an ADC345 and it works quite well.]

06 Jun 2011

Thanks very much for both of your help! In the short time left till my deadline I think won't be able to master the use of GPDMA, but doing this project definitely made me interested in the process. I will try the timer() and if it works I just go with that. Then in the future I will learn the use of your library! ;)

Thanks again, all the best wishes to both of you.

Regards Levente

06 Jun 2011

Levente,

Ok, no probs. If you want help in the future with a DMA solution give me a shout. But here's a simpler solution using the timer method. First up, setup the timer as suggested:-

  Timer tm;
  tm.start();

This ensures the Mbed library starts up TIMER3 to make it's measurements. Once TIMER3 is running then you can simply "bypass" a call to tm.read() (and it's associated code overhead) and just do:-

  fprintf(fp, "%lu\t%f\t%f\t%f\n", LPC_TIM3->TC, xg, yg, zg);

LPC_TIM3->TC is just a uint32_t that increments at 1us intervals, thus providing you with the timestamp you need. Just remember a uint32_t overflows at 2^32, that's approx once every 71 minutes or so. You'll just have to look out for that overflow when reading the data off the memory stick.

06 Jun 2011

And one more thing when looking for speed. Why do this:-

            signed int x = ((((x1 << 8) | x0) << 16)/524288);          //combine data bytes
            signed int y = ((((y1 << 8) | y0) << 16)/524288);
            signed int z = ((((z1 << 8) | z0) << 16)/524288);            
                       
            float xg = (float(x))/256;
            float yg = (float(y))/256;
            float zg = (float(z))/256;

Why not just store the raw data into the file and move all that to the program that's reading the data off the memory stick. That program is, I presume, on a PC somewhere and it has all the time in the world to post-process that raw data into any format you need.

It's always better to capture data in it's raw format and post-process it later if you can.

06 Jun 2011

Thanks for the offer Andy and about the bit shift and friends: it was just something find more simple to do in this code then LabVIEW, the time I wrote it had no idea about the file writing issue. For further work I will definitely move it from the code. :)

06 Jun 2011

I usually prefer to write data in a convenient format like csv. This way, you don't need to write a special tool on the PC side. Matlab or even excel will do fine.

And i'm sure it is possible to write this program without something complex like DMA. The performace of the mbed show for example in the madplayer project on this site. It decodes mp3 files from USB. Audio is send to the DAC with a timer IRQ at 44.1 Khz. So writing some samples at 800Hz must be possible.

06 Jun 2011

Jeroen, I agree with you, that's why I said I was "surprised". 800Hz really is "slow". I think it's all that conversion math followed by the friends time sink of a printf() family member (using %f too!) that's just getting in the way. Anyway, there's plenty of ways to do it. I just have a tendency to demo the best solutions for speed, hence DMA. I like to demo where possible use of all the bits of the LPC17xx that normally just remain switched off.

It may not be needed for Levente but it may spark an interest in someone else reading the thread with a similar problem :)

06 Jun 2011

I wasn't expecting any issue with this low frequency data logging either, hence the bitwise operation in my code. Using txt file was the most simplest choice for me since the data analysing software I use (LabVIEW) had a very convenient vi to read it. I also tried to write only raw data (without any friends) to the USB-stick but the only different was the separation in time between the data collecting hold ups. It just took a bit longer for the mbed to build the 512byte block to transfer. And in my opinion learning the best solution is always a good thing to do. :)

07 Jun 2011

I left out the file system (see code below) and ran a test: The SPI transaction, floating point math and %f stuff takes at most 0.19ms. This amounts to 5000Hz sample rate. So I expect the file system causes the loss of samples.

By the way: In the original code are calls to wait_ms(0.1). These won't work, as wait_ms(int delay) expects an integer. As a result 0.1 gets truncated to 0. If you really need the delay, use wait(0.0001) or wait_us(100).

#include "mbed.h"

SPI spi(p11, p12, p13); // mosi, miso, sclk

// Serial port for debugging.
Serial pc(USBTX, USBRX);

char dummy[1024];

int main()
{
    spi.format(8,3);
    spi.frequency(600000);      //SPI link's clock to 600kHz
    
    pc.baud(4*115200);
    
    uint32_t loops = 0;
    Timer tm;
    
    tm.start();
    float time, prevTime = tm.read();
    float dmax = 0;
    
    while(tm.read() < 1.0)
    {
 
        loops++;
          wait_ms(0.1);
          spi.write(0xf2);            
          int x0 = spi.write(0x00);
          int x1 = spi.write(0x00);
          int y0 = spi.write(0x00);
          int y1 = spi.write(0x00);
          int z0 = spi.write(0x00);
          int z1 = spi.write(0x00);
          wait_ms(0.01);
            
          signed int x = ((((x1 << 8) | x0) << 16)/524288);          //combine data bytes
          signed int y = ((((y1 << 8) | y0) << 16)/524288);
          signed int z = ((((z1 << 8) | z0) << 16)/524288);            
                       
          float xg = (float(x))/256;
          float yg = (float(y))/256;
          float zg = (float(z))/256;
              
           sprintf(dummy, "%u\t%f\t%f\t%f\n", loops, xg, yg, zg);
          time = tm.read();
          if(time - prevTime > dmax)
            dmax = time-prevTime;
          prevTime = time;
      }       
      pc.printf("Maximum time=%f. Loops=%d.\n", dmax, loops);
}

Result: Maximum time=0.000192. Loops=6762.

07 Jun 2011

Thanks for your effort Jeroen! I also tried it a bit different way. Disabling the file writing line in the code ( fprintf(); ) and probed the Data_ready output of the accelerometer. This case I had a perfect signal of 800Hz spikes, so I knew the issue is with the file writing process. Will look into the buffering methods and hopefully I can achieve the required data sample recording rate.

Levente

07 Jun 2012

The write to USB Flash Drive of about 20 to 30 bytes takes less than 0.2 ms usually. About each 500 bytes, it takes about 2 to 3 ms. Then, occasionally it takes 600 to 700 ms. This big delay requires a large circular buffer of samples, to continue sampling at... Maybe 1000 samples per second.

Is there any way to reduce the large delay in the file writing? Thanks, Gary