Arduino Nano 33 IoT Timer SAMD21

Hi

I am in a project where I need to save data with a time stamp with a precission of 1 mili second, I already have used the millis() function. However I have high drifts in the time estimation. I was wondering how can I program de SAMD timer counter to achive less drift than millis().
I already read some information of the time counters in the SAMD, but I find it really complex.

Could you please describe in more detail what you would like to do? A time stamp is what you want it to be, it can have any value.

It could also be useful to post your code to explain what you are trying to do and what you mean with drift. Drift is a general term and could mean different parts of your system e.g. the clock signal drifting because of temperature or your software not catching an event on time.

I am saving data of a IMU sensor in an SD with the millis() function, however with this function I get an error of 5s for recordings of 220s. I already have tested the sample rate of the IMU and it works fine. To reduce this error, I thought that using directly the time counters of the SAMD might reduce it, however I find really dificult to understant the timer setings of SAMD processor.

Can you post your code?

Did you look at how writing to an SD card affects your application?

Have a look at the following post reply #4. I did an experiment with an Arduino Nano 33 BLE and there was a large delay from time to time when the SD card was written. Maybe that is your real issue.

https://forum.arduino.cc/t/ble-33-writing-to-sd-card-spi-stuck-every-15ms/693375/4

Hi

Thanks for the replies here is my code:

int buttom = 2;//asignamos al boton guardar el pin digital 2
int volatile interrup = 0;// para la interrupcion con un boton debemos definir una variable que esta cambiara para que active el guardado de datos en la SD
int volatile accAvailable=0;
int volatile gyrAvailable=0;
//----------------------------------------------------
//BIBLIOTECAS Y VARIABLES TARJETA SD
//----------------------------------------------------
#include <SPI.h>
#include <SD.h>
File file;
#define SSpin 10
//----------------------------------------------------
//BIBLIOTECAS Y VARIABLES SENSOR IMU
//----------------------------------------------------
#include "SparkFunLSM6DS3_Personal.h"
#include "Wire.h"
//Create a instance of class LSM6DS3
LSM6DS3 myIMU( I2C_MODE, 0x6A );  //I2C device address 0x6A
//----------------------------------------------------
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  attachInterrupt( digitalPinToInterrupt(buttom), save, CHANGE);//desiganos la interrupción que activara la funcion guardar al boton_guardar
  //----------------------------------------------------
  //setup SD
  //----------------------------------------------------
  // Open serial communications and wait for port to open:
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Initializing SD card...");
  if (!SD.begin(SSpin)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
  file = SD.open("IMU.txt", FILE_WRITE);
  //----------------------------------------------------
  //setup IMU
  //----------------------------------------------------
  /* Initialise the sensor */
  //Call .begin() to configure the IMUs
  if( myIMU.begin() != 0 )
  {
    Serial.println("Device error");
  }
  else  
  {
    Serial.println("Device OK!");
  }
  myIMU.settings.gyroEnabled = 1;  //Can be 0 or 1
  myIMU.settings.gyroRange = 2000;   //Max deg/s.  Can be: 125, 245, 500, 1000, 2000
  myIMU.settings.gyroSampleRate = 104;   //Hz.  Can be: 13, 26, 52, 104, 208, 416, 833, 1666

  myIMU.settings.accelEnabled = 1;
  myIMU.settings.accelRange = 16;      //Max G force readable.  Can be: 2, 4, 8, 16
  myIMU.settings.accelSampleRate = 104;  //Hz.  Can be: 13, 26, 52, 104, 208, 416, 833, 1666, 3332, 6664, 13330 
}
void save(){
  interrup=1;//asignamos a la variable interrup el valor de uno, de manera que active el código para guardar los datos de la SD
}
void loop() {
  //------------------------------------------------------------------------------
  //Escribimos los valores del IMU en la SD
  //------------------------------------------------------------------------------
  float ax, ay, az, gx, gy, gz;
  if (file) {
    if (interrup==0) {
      if (myIMU.accelerationAvailable()==true 
      && myIMU.gyroscopeAvailable()==true 
      && myIMU.readFloatAccelX() 
      && myIMU.readFloatAccelY() 
      && myIMU.readFloatAccelZ() 
      && myIMU.readFloatGyroX() 
      && myIMU.readFloatGyroY() 
      && myIMU.readFloatGyroZ()){
        //El orden de las componentes depende de la orientación en la que dispondremos el IMU
        //Accelerometer (g)
        archivo.print(myIMU.readFloatAccelX(), 4);archivo.print(",");
        archivo.print(myIMU.readFloatAccelY(), 4);archivo.print(",");
        archivo.print(myIMU.readFloatAccelZ(), 4);archivo.print(",");
        //Gyroscope (º/s)
        archivo.print(myIMU.readFloatGyroX(), 4);archivo.print(",");
        archivo.print(myIMU.readFloatGyroY(), 4);archivo.print(",");    
        archivo.print(myIMU.readFloatGyroZ(), 4);archivo.print(",");
        //Tiempo (ms)
        archivo.println(millis());      
      }
    } else {
      archivo.close();
    }
  } else {
    Serial.println("Error en la apertura de IMU.txt");
  }
}

I have already tested the IMU frequency with the SD and its about 103.5-104Hz, which is okey. However I get the error I told you before in millis().

Thanks for the code.
I do not believe millis() is your main issue. I suspect you changed the LSM6DS3 library so I am not sure what happens there but is likely fine. Here is what I noted about your code.

  • you read the IMU values in your if () statement and in your print statements. The IMU is connected externally where all accesses cost a lot of time. I recommend you read the value only once when new data is available. Store all values in variables first.
  • you read millis() at the end of your code inside the print statement. I recommend you read millis() before you write anything to the SC card.
  • I recommend you separate the acceleration and gyroscope code. The values might be ready at different times. There is no need to wait for the other values to become available. You can still write them to the SD card at the same time. Just read the values set a flag and store them when both flags are set.

In case millis() affects your timing, I wrote a simple example on how to setup the SAMD21 TC4 as 32-bit timer and how to read the value. The timer will run without interruption. The timer runs slow in the example with ~47 increments per ms (48MHz / 1000 (ms) / 1024 (prescaler)). It is still faster than millis(). You can change the prescaler to get even more increments per ms but then the timer will overflow faster. The example will run for a bit over one day before overflow.

Here is the example:

SAMD21 Simple Timer example. Click to view.
/*
  This example shows how to configure SAMD21 timer.
  - TC4 and TC5 are used as one 32-bit timer.
  - TC5 registers are ignored when in 32-bit mode.
  - Timer runs from the main clock.
  - Timer is read trough software.
  - Serial Plotter can be used to see the interval or COUNT value.
  
  The circuit:
  - Arduino Nano 33 IoT.

  This example code is in the public domain.
*/

#include <samd.h>

void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );

  TC4Init();
}

void loop()
{
#define PRINT_INTERVAL 10

  static uint32_t previousMillis = 0;
  static uint32_t previousTC4Count = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= PRINT_INTERVAL )
  {
    previousMillis = currentMillis;

    uint32_t currentTC4Count = TC4Read();
    uint32_t TC4Interval = currentTC4Count - previousTC4Count;
    previousTC4Count = currentTC4Count;
    Serial.print( TC4Interval );
//    Serial.println( currentTC4Count );
  }
}

// For register naming structs and unions see
// .\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\
   ATMEL\samd21\include\component\tc.h

void TC4Init()
{
  // Enable and configure clock source for timer TC4/5
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5;
  while ( GCLK->STATUS.bit.SYNCBUSY );

  // Reset timer registers ( just to be sure )
  TC4->COUNT32.CTRLA.bit.SWRST = 1;
  while ( TC4->COUNT32.STATUS.bit.SYNCBUSY );

  // Set mode to 32 bit
  TC4->COUNT32.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT32_Val;

  // Set prescaler
  TC4->COUNT32.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1024_Val;

  // Enable read synchronization of the timer COUNT register ( Offset: 0x10 )
  TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR( 0x10 );
  while ( TC4->COUNT32.STATUS.bit.SYNCBUSY );

  // Enable timer
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;
  while ( TC4->COUNT32.STATUS.bit.SYNCBUSY );
}

uint32_t TC4Read()
{
  return TC4->COUNT32.COUNT.reg;
}

Thanks for your Comments.
I will implement them, however I need Gyroscope and Accelerometer syncronised. As I said I have no issues with the IMU frequency and data output is coming out at 104Hz.

How does reading the data at the same time keep them synchronized? Unless the data creation is synchronized internally the the two sensor values will always be separated a little bit (max +-0.5 * sampling interval).

Well I dont know how the sensor does to scycronise data but when yo access de STATUS_REG, you get the bites of accelerometer and gyroscope available at the same time.

So, there is no need for you to have all these calls in the if() statement. I recommend you optimize your code to reduce the number of external accesses. They take a lot of time. For instance, myIMU.accelerationAvailable() and myIMU.gyroscopeAvailable() access a bit in the same register. Just read the STATUS_REG once and compare both bits at the same time.

Also remove all myIMU.readFloatAccel() from the if() statement. Read the values into a variable once and then print, store ... whatever you need to do using the variables.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.