Jobs Tags Users Badges Ask Up vote 0 Down vote Raspberry stroing accelerometer data sd card

I am using a raspberry pi4 with an IMU 6050 sensor and would like to save the data in a csv file with a frequency of 200 saples / s. I use a button to close the file and open a new one. However, the sampling rate is unstable and lower than 200 samples / s. Can you help me? Thanks in advance

import smbus            #import SMBus module of I2C
from time import sleep          #import
import datetime
import time
import numpy as np
import csv
import sys
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)

GPIO.setup(25, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#some MPU6050 Registers and their Address
PWR_MGMT_1   = 0x6B
SMPLRT_DIV   = 0x19
CONFIG       = 0x1A
GYRO_CONFIG  = 0x1B
INT_ENABLE   = 0x38
ACCEL_XOUT_H = 0x3B
ACCEL_YOUT_H = 0x3D
ACCEL_ZOUT_H = 0x3F
GYRO_XOUT_H  = 0x43
GYRO_YOUT_H  = 0x45
GYRO_ZOUT_H  = 0x47
ACCEL_CONFIG = 0x1C
GYRO_CONFIG  = 0x1B

# Scale Modifiers
ACCEL_SCALE_MODIFIER_2G = 16384.0
ACCEL_SCALE_MODIFIER_4G = 8192.0
ACCEL_SCALE_MODIFIER_8G = 4096.0
ACCEL_SCALE_MODIFIER_16G = 2048.0

GYRO_SCALE_MODIFIER_250DEG = 131.0
GYRO_SCALE_MODIFIER_500DEG = 65.5
GYRO_SCALE_MODIFIER_1000DEG = 32.8
GYRO_SCALE_MODIFIER_2000DEG = 16.4

# Pre-defined ranges
ACCEL_RANGE_2G = 0x00
ACCEL_RANGE_4G = 0x08
ACCEL_RANGE_8G = 0x10
ACCEL_RANGE_16G = 0x18

GYRO_RANGE_250DEG = 0x00
GYRO_RANGE_500DEG = 0x08
GYRO_RANGE_1000DEG = 0x10
GYRO_RANGE_2000DEG = 0x18



f = open(time.strftime("%Y%m%d-%H%M%S")+'_Datalog.csv','w')
f.write('Time(ms),Ax(g),Ay(g),Az(g),Gx(°/s),Gy(°s),Gz(°/s),"\n')

delta = 0
start_time = (time.time()*1000)

def MPU_Init():
    #write to sample rate register
    bus.write_byte_data(Device_Address, SMPLRT_DIV, 7)
    
    #Write to power management register
    bus.write_byte_data(Device_Address, PWR_MGMT_1, 1)
    
    #Write to Configuration register
    bus.write_byte_data(Device_Address, CONFIG, 0)
    
    #Write to Gyro configuration register
    bus.write_byte_data(Device_Address, GYRO_CONFIG, 24)
    
    #Write to interrupt enable register
    bus.write_byte_data(Device_Address, INT_ENABLE, 1)
    
    #Write accelerometer range
    
    bus.write_byte_data(Device_Address,ACCEL_CONFIG ,0x00)
    bus.write_byte_data(Device_Address,ACCEL_CONFIG ,ACCEL_RANGE_2G)
    #Write Gyro range
    bus.write_byte_data(Device_Address,GYRO_CONFIG ,0x00)
    bus.write_byte_data(Device_Address,GYRO_CONFIG ,GYRO_RANGE_250DEG)

def read_raw_data(addr):
    #Accelero and Gyro value are 16-bit
        high = bus.read_byte_data(Device_Address, addr)
        low = bus.read_byte_data(Device_Address, addr+1)
    
        #concatenate higher and lower value
        value = ((high << 8) | low)
        
        #to get signed value from mpu6050
        if(value > 32768):
                value = value - 65536
        return value

bus = smbus.SMBus(1)    # or bus = smbus.SMBus(0) for older version boards
Device_Address = 0x68   # MPU6050 device address

MPU_Init()

print (" Reading Data of Gyroscope and Accelerometer")

while True:
        
       
 millis = (time.time()*1000)
 
 delta  = np.uint32(millis - start_time)
 #Read Accelerometer raw value
 acc_x = read_raw_data(ACCEL_XOUT_H)
 acc_y = read_raw_data(ACCEL_YOUT_H)
 acc_z = read_raw_data(ACCEL_ZOUT_H)
        
 #Read Gyroscope raw value
 gyro_x = read_raw_data(GYRO_XOUT_H)
 gyro_y = read_raw_data(GYRO_YOUT_H)
 gyro_z = read_raw_data(GYRO_ZOUT_H)
        
 #Full scale range +/- 250 degree/C as per sensitivity scale factor
 #Full scale range +/- 250 degree/C as per sensitivity scale factor
 Ay = -acc_x/ACCEL_SCALE_MODIFIER_2G
 Ax = acc_y/ACCEL_SCALE_MODIFIER_2G
 Az = -acc_z/ACCEL_SCALE_MODIFIER_2G
     
 Gy = gyro_x/GYRO_SCALE_MODIFIER_250DEG
 Gx = -gyro_y/GYRO_SCALE_MODIFIER_250DEG
 Gz = gyro_z/GYRO_SCALE_MODIFIER_250DEG
 

 f.write(str(delta) + ',' + str(Ax) + ',' + str(Ay) + ',' + str(Az) + ',' + str(Gx) + ',' + str(Gy) + ',' + str(Gz) +'\n')
    
       
        
 sleep(0.005)
 input_state      = GPIO.input(25)       
        #n_packet = n_packet +1;

 if input_state == False:

   sleep(1)
   print("Done")
   
    #pickle.dump(record,f)
   f.close()
   
   SystemExit
   input_state == True
   f = open(time.strftime("%Y%m%d-%H%M%S")+'_Datalog.csv','w')
   start_time = (time.time()*1000)

I see one problem in your code: You do sleep(0.005) between measurements. That ignores the time that the measurements, output, and loop logic take, so you will definitely get less than 200 measurements per second. Additionally the time.sleep() documentation says:

The actual suspension time may be less than that requested because any caught signal will terminate the sleep() following execution of that signal’s catching routine. Also, the suspension time may be longer than requested by an arbitrary amount because of the scheduling of other activity in the system.

In C I’d use nanosleep() so I could give a fixed time to sleep until instead of a duration. You could try to use that via ctypes, but I really doubt it’ll give you the precision you want. Simply put, as much as I love Python :snake:, it isn’t well suited for real time applications. A garbage collector run could be enough to mess up your timing.

I see two options to avoid those issues:

  1. Do the measurements in a program that’s written in a language that’s suitable for real-time and supports nanosleep (e.g. C, Rust, or Go). That program should just do the measurements and write them out, so it’s as simple and has as little risk of unexpected delays as possible. You could then read and use the data from Python. Depending on whether you want to use the data live a Unix socket or a file may be more suited. This is similar to what I did in my graduation thesis (except that was about network timing).
  2. If you’re okay with using CPython only for your program you could look into writing an extension module in C, that could do the measurements in a C thread and pass the data to Python.