Flexitimer: can we go from 250 Hz to 2kHz?

Why would we need a higher sampling rate? Our BME expert’s understanding is that a higher rate would make his signal processing less computationally intensive (although more memory resources would be called in.)

The second question is, can we do that?

It takes about 100 microseconds (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second.

[analogread]

And, we’re sampling 5 channels. So it could be possible to sample at 2 kHz. But can we do that, and get readings over I2C, and communicate over serial?

The answer depends on:

  • How much data we want to transmit between each sampling event (packet size and baud rate)
    • a 15 byte packet at 115200 baud rate seems to take about 1/850 seconds.
    • I2C reads seem to be very fast compared to serial (kHz)
  • What range of arguments FlexiTimer2 supports
    • 1 unit of 1/1000 seconds seems to fail. 2, 3 and 4 ms units do work.
  • How timers on the Arduino work
    • Potential workaround, not using FlexiTimer at all.

The easiest thing to do is to just sending 15 byte packets over serial, with interrupts every 2 ms (487.427 Hz), and serial baud rate set to 115200.

Here are some scope captures to visualize what’s happening. In the first set, the yellow channel is driven high whenever the Uno reads accelerometer data over I2C. The blue channel goes high over the execution of the function called by FlexiTimer’s interrupts. Over different captures, I change the timing between interrupts from 4 ms, to 3, to 2, to 1 ms. I also switch the baud rates between 57600 and 115200.

In the next set of captures, I change the interrupt timing from 4 ms to 2 ms while showing the data packets sent over Tx. It is clear that for the 2 ms interrupts, we need to bump up the baud rate to make sure that all of our 15 bytes get sent in time!

The final set of captures for the day (night) showing different packets sizes, and what bytes look like on serial:

  



/////////////////////////////////////////////
// Team UMAR-VARS                          //
// Arduino code for grabbing               //
// - ECG/EMG data (A0, A1)                 //
// - PPG data     (A2)                     //
// - acc data     (A5, A6) - I2C - raw     //
////////////////////////////////////////////////////////////////////////////////////////////
// Links:
// - User manual
// https://www.olimex.com/Products/Duino/Shields/SHIELD-EKG-EMG/resources/SHIELD-EKG-EMG.pdf
// - ECG code from Stan12's code (which is adapted from Olimex's sample code)
// https://www.olimex.com/forum/index.php?topic=572.0
// - MPU-6050 code from JohnChi's example sketch
////////////////////////////////////////////*BEGIN*/////////////////////////////////////////

  #include<FlexiTimer2.h>  //http://playground.arduino.cc/Main/FlexiTimer2 for interrupts (timing)
  //#include <compat/deprecated.h> //seems to be outdated
  #include<Wire.h>                 //for I2C

  // MPU-6050:
      const int MPU=0x68;  // I2C address of the MPU-6050. Max 7 bits.
      //int16_t AcX,AcY,AcZ; //,Tmp,GyX,GyY,GyZ;  
  // ECG shield:
      volatile unsigned char CurrentCh = 0;       // Current channel being sampled.
      volatile unsigned int  ADC_Value = 0;	  // ADC current value, 2 byte value (16 bits total)
      volatile unsigned char TXBuf[1+1+2*3+2*3];  // sync + packetNum + heartData + accData
      volatile unsigned char TXIndex;             // Next byte to write in the transmission packet.

  void setup() {
      pinMode(11, OUTPUT);
      pinMode(10, OUTPUT);
      
  // MPU-6050:   
      //do NOT place noInterrupts() here. Seems to affect I2C?
      Wire.begin();                               // as master
      Wire.beginTransmission(MPU);                // "queue bytes for transmission with write(), transmit by calling endTransmission()"
      Wire.write(0x6B);  // PWR_MGMT_1 register
      Wire.write(0);     // set to zero (wakes up the MPU-6050)
      Wire.endTransmission(true);
      
  // ECG shield:
      noInterrupts();  // Disable all interrupts before initialization
   
      //Packet | units?
      TXBuf[0] = 0xFF;    //Sync 0 -- could help with parsing -- unchanged for legacy
      TXBuf[1] = 0x0;     //Packet counter -- helps to see what we might miss
      TXBuf[2] = 0x02;    //ECG-1 | A0 High Byte
      TXBuf[3] = 0x00;    //ECG-1 | A0 Low Byte
      TXBuf[4] = 0x02;    //ECG-2 | A1 High Byte
      TXBuf[5] = 0x00;    //ECG-2 | A1 Low Byte
      TXBuf[6] = 0x02;    //PPG   | A2 High Byte
      TXBuf[7] = 0x00;    //PPG   | A2 Low Byte
      TXBuf[8] = 0x02;    //AccX  | I2C High Byte
      TXBuf[9] = 0x00;    //AccX  | I2C Low Byte
      TXBuf[10] = 0x02;   //AccY  | I2C High Byte
      TXBuf[11] = 0x00;   //AccY  | I2C Low Byte
      TXBuf[12] = 0x02;   //AccZ  | I2C High Byte
      TXBuf[13] = 0x00;   //AccZ  | I2C Low Byte
      TXBuf[14] = 0x5a;   //end packet
      
      FlexiTimer2::set(2, 1/1000, Timer2_Overflow_ISR); //4 units of 1 ms resolution => sampling at FS
      // FlexiTimer2::set(unsigned long units, double resolution, void (*f)())
      // E.g. units=1, resolution = 1.0/3000 will call f 3000 times per second, 
      // whereas it would be called only 1500 times per second when units=2.

      //serial prints seem to freeze for certain rates. Data might still be coming through.

      FlexiTimer2::start();
      //enables the interrupt.
 
      // Serial Port
      Serial.begin(115200);       //why this baud rate? too slow vs too many errors

      interrupts();              // Enable all interrupts after initialization has been completed
  }

  void Timer2_Overflow_ISR() {
        digitalWrite(11, HIGH);

    // Serial.println("Hey!");   //use for experimenting with interrupt frequency
  
  // ECG/PPG:
    //Read the ADC channels (ECG-1, ECG-2, PPG)
    for(CurrentCh=0; CurrentCh<3; CurrentCh++) {
      ADC_Value = analogRead(CurrentCh); //first two channels reserved for I2C
      TXBuf[2*CurrentCh + 2] = ((unsigned char)((ADC_Value & 0xFF00) >> 8));// Write High Byte
      TXBuf[2*CurrentCh + 3] = ((unsigned char) (ADC_Value & 0x00FF));	    // Write Low Byte
    }
    // http://arduino.cc/en/Reference/analogRead
    // 10-bit ADC, mapping input voltages 0-5V -> 0-1023 => resolution = 4.9 mV
    // The input range and resolution can be changed using analogReference().
    // It takes about (0.0001s to read an analog input => max rate is about 10,000 Hz.
 
  // MPU-6050 (I2C routine):
    //couldn't get it to work here. Moved to void loop().
    //MPU_I2C_read();
    //delay(100);
    
    //Serial.print(TXBuf[1], HEX);
    // Send Packet
    
    
    for(TXIndex=0;TXIndex<15;TXIndex++){//14
      Serial.write(TXBuf[TXIndex]);
      //delay(100);
    }
    
    //Serial.println("");


    //Serial.println(TXBuf[1], HEX);
    TXBuf[1]++;	                         // Increment the packet counter
    digitalWrite(11, LOW);

  }
/*
  void MPU_I2C_read(){
    //digitalWrite(13, HIGH);    
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);                    // starting with register 0x3B (ACCEL_XOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU,6,true);        // request 6 (consecutive?) registers
    
    int i = 0;
    while(Wire.available() && i<6) {     // Reads the following 6 registers:
      TXBuf[8+i]=Wire.read();            // 0x3B (ACCEL_XOUT_H), 0x3C (ACCEL_XOUT_L)
      i++;                               // 0x3D (ACCEL_YOUT_H), 0x3E (ACCEL_YOUT_L)
    }                                    // 0x3F (ACCEL_ZOUT_H), 0x40 (ACCEL_ZOUT_L)
    //digitalWrite(13, LOW);
  }
 */ 
  void loop() {
    Wire.beginTransmission(MPU);
    Wire.write(0x3B);                    // starting with register 0x3B (ACCEL_XOUT_H)
    Wire.endTransmission(false);
    Wire.requestFrom(MPU,6,true);        // request 6 (consecutive?) registers
    
    digitalWrite(10, HIGH);
    int i = 0;
    while(Wire.available() && i<6) {     // Reads the following 6 registers:
      TXBuf[8+i]=Wire.read();            // 0x3B (ACCEL_XOUT_H), 0x3C (ACCEL_XOUT_L)
      i++;                               // 0x3D (ACCEL_YOUT_H), 0x3E (ACCEL_YOUT_L)
    } 
    digitalWrite(10, LOW);
    //__asm__ __volatile__ ("sleep");
  }

Advertisements

One thought on “Flexitimer: can we go from 250 Hz to 2kHz?

  1. – I2C reads and the routine called by the interrupt never really overlap (unless they occur really close in time), but, the serial communication continues to go on even after the interrupt function has exited!
    – It takes the interrupt function longer to execute at with interrupts every 2 ms, than it takes with interrupts every 3 ms. Now, I would expect the execution time to be the same, so I don’t know what’s going on.
    – Byte communication over serial looks weird. I have to read up on how these bytes (symbols) are encoded.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s