diff --git a/PSL/Peripherals.py b/PSL/Peripherals.py index fe1f500e..35c18330 100644 --- a/PSL/Peripherals.py +++ b/PSL/Peripherals.py @@ -1,5 +1,5 @@ from __future__ import print_function -import PSL.commands_proto as CP +import commands_proto as CP import numpy as np import time, inspect @@ -33,7 +33,11 @@ class I2C(): >>> print (x,y,z) """ - + samples = 0 + total_bytes=0 + channels = 0 + tg=100 + MAX_SAMPLES = 10000 def __init__(self, H): self.H = H from PSL import sensorlist @@ -375,6 +379,123 @@ def scan(self, frequency=100000, verbose=False): self.stop() return addrs + def __captureStart__(self,address,location,sample_length,total_samples,tg): + """ + Blocking call that starts fetching data from I2C sensors like an oscilloscope fetches voltage readings + You will then have to call `__retrievebuffer__` to fetch this data, and `__dataProcessor` to process and return separate channels + refer to `capture` if you want a one-stop solution. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + ================== ============================================================================================ + **Arguments** + ================== ============================================================================================ + address Address of the I2C sensor + location Address of the register to read from + sample_length Each sample can be made up of multiple bytes startng from . such as 3-axis data + total_samples Total samples to acquire. Total bytes fetched = total_samples*sample_length + tg timegap between samples (in uS) + ================== ============================================================================================ + + :return: Arrays X(timestamps),Y1,Y2 ... + + """ + if(tg<20):tg=20 + total_bytes = total_samples*sample_length + print ('total bytes calculated : ',total_bytes) + if(total_bytes>self.MAX_SAMPLES*2): + print ('Sample limit exceeded. 10,000 int / 20000 bytes total') + total_bytes = self.MAX_SAMPLES*2 + total_samples = total_bytes/sample_length #2* because sample array is in Integers, and we're using it to store bytes + + + print ('length of each channel : ',sample_length) + self.total_bytes = total_bytes + self.channels = sample_length + self.samples = total_samples + self.tg = tg + + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_START_SCOPE) + self.H.__sendByte__(address) + self.H.__sendByte__(location) + self.H.__sendByte__(sample_length) + self.H.__sendInt__(total_samples) #total number of samples to record + self.H.__sendInt__(tg) #Timegap between samples. 1MHz timer clock + self.H.__get_ack__() + return 1e-6*self.samples*self.tg+.01 + + def __retrievebuffer__(self): + ''' + Fetch data acquired by the I2C scope. refer to :func:`__captureStart__` + + ''' + total_int_samples = self.total_bytes/2 + DATA_SPLITTING = 500 + print ('fetchin samples : ',total_int_samples,' split',DATA_SPLITTING) + data=b'' + for i in range(int(total_int_samples/DATA_SPLITTING)): + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) #starts with A0 on PIC + self.H.__sendInt__(DATA_SPLITTING) + self.H.__sendInt__(i*DATA_SPLITTING) + rem = DATA_SPLITTING*2+1 + for a in range(200): + partial = self.H.fd.read(rem) #reading int by int sometimes causes a communication error. this works better. + rem -=len(partial) + data+=partial + #print ('partial: ',len(partial), end=",") + if rem<=0: + break + data=data[:-1] + #print ('Pass : len=',len(data), ' i = ',i) + + if total_int_samples%DATA_SPLITTING: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) #starts with A0 on PIC + self.H.__sendInt__(total_int_samples%DATA_SPLITTING) + self.H.__sendInt__(total_int_samples-total_int_samples%DATA_SPLITTING) + rem = 2*(total_int_samples%DATA_SPLITTING)+1 + for a in range(20): + partial = self.H.fd.read(rem) #reading int by int sometimes causes a communication error. this works better. + rem -=len(partial) + data+=partial + #print ('partial: ',len(partial), end="") + if rem<=0: + break + data=data[:-1] + print ('Final Pass : len=',len(data)) + return data + + def __dataProcessor__(self,data,*args): + ''' + Interpret data acquired by the I2C scope. refer to :func:`__retrievebuffer__` to fetch data + + ================== ============================================================================================ + **Arguments** + ================== ============================================================================================ + data byte array returned by :func:`__retrievebuffer__` + *args supply optional argument 'int' if consecutive bytes must be combined to form short integers + ================== ============================================================================================ + + ''' + + try: + data = [ord(a) for a in data] + if('int' in args): + for a in range(self.channels*self.samples/2): self.buff[a] = np.int16((data[a*2]<<8)|data[a*2+1]) + else: + for a in range(self.channels*self.samples): self.buff[a] = data[a] + + yield np.linspace(0,self.tg*(self.samples-1),self.samples) + for a in range(int(self.channels/2)): + yield self.buff[a:self.samples*self.channels/2][::self.channels/2] + except Exception as ex: + msg = "Incorrect number of bytes received",ex + raise RuntimeError(msg) + + def capture(self, address, location, sample_length, total_samples, tg, *args): """ Blocking call that fetches data from I2C sensors like an oscilloscope fetches voltage readings diff --git a/PSL/SENSORS/BH1750.py b/PSL/SENSORS/BH1750.py index 2cf7fc0c..d7a0ac24 100644 --- a/PSL/SENSORS/BH1750.py +++ b/PSL/SENSORS/BH1750.py @@ -23,7 +23,7 @@ class BRIDGE(): # for changing various options of this sensor # It's a dictionary of the string representations of functions matched with an array # of options that each one can accept - params = {'init': ['Now'], + params = {'init': None, 'setRange': gain_literal_choices, } @@ -35,9 +35,9 @@ class BRIDGE(): def __init__(self, I2C, **args): self.I2C = I2C self.ADDRESS = args.get('address', 0x23) - self.init('') + self.init() - def init(self, dummy_variable_to_circumvent_framework_limitation): # I know how to fix this now. remind me. + def init(self): self.I2C.writeBulk(self.ADDRESS, [self.RES_500mLx]) def setRange(self, g): diff --git a/PSL/SENSORS/HMC5883L.py b/PSL/SENSORS/HMC5883L.py index cc0291ee..212cfee1 100644 --- a/PSL/SENSORS/HMC5883L.py +++ b/PSL/SENSORS/HMC5883L.py @@ -30,7 +30,7 @@ class HMC5883L(): # for changing various options of this sensor # It's a dictionary of the string representations of functions matched with an array # of options that each one can accept - params = {'init': ['Now'], + params = {'init': None, 'setSamplesToAverage': samplesToAverage_choices, 'setDataOutputRate': dataOutputRate_choices, 'setGain': gain_choices, @@ -51,9 +51,9 @@ def __init__(self, I2C, **args): except: print 'FAILED TO CHANGE BAUD RATE' ''' - self.init('') + self.init() - def init(self, dummy_variable_to_circumvent_framework_limitation): # I know how to fix this now. remind me. + def init(self): self.__writeCONFA__() self.__writeCONFB__() self.I2C.writeBulk(self.ADDRESS, [self.MODE, 0]) # enable continuous measurement mode diff --git a/PSL/SENSORS/MLX90614.py b/PSL/SENSORS/MLX90614.py index 7a93000a..725eab78 100644 --- a/PSL/SENSORS/MLX90614.py +++ b/PSL/SENSORS/MLX90614.py @@ -21,7 +21,7 @@ def __init__(self, I2C, **args): self.source = self.OBJADDR self.name = 'Passive IR temperature sensor' - self.params = {'readReg': range(0x20), + self.params = {'readReg': {'dataType':'integer','min':0,'max':0x20,'prefix':'Addr: '} , 'select_source': ['object temperature', 'ambient temperature']} try: diff --git a/PSL/SENSORS/MPU6050.py b/PSL/SENSORS/MPU6050.py index b2f4d3c3..7e87c877 100644 --- a/PSL/SENSORS/MPU6050.py +++ b/PSL/SENSORS/MPU6050.py @@ -33,8 +33,8 @@ def __init__(self, I2C, **args): self.I2C = I2C self.ADDRESS = args.get('address', self.ADDRESS) self.name = 'Accel/gyro' - self.params = {'powerUp': ['Go'], 'setGyroRange': [250, 500, 1000, 2000], 'setAccelRange': [2, 4, 8, 16], - 'KalmanFilter': [.01, .1, 1, 10, 100, 1000, 10000, 'OFF']} + self.params = {'powerUp': None, 'setGyroRange': [250, 500, 1000, 2000], 'setAccelRange' : [2, 4, 8, 16], + 'KalmanFilter': {'dataType':'double','min':0,'max':1000,'prefix':'value: '} } self.setGyroRange(2000) self.setAccelRange(16) ''' @@ -43,11 +43,11 @@ def __init__(self, I2C, **args): except: pass ''' - self.powerUp(True) + self.powerUp() self.K = None def KalmanFilter(self, opt): - if opt == 'OFF': + if opt == 0: self.K = None return noise = [[]] * self.NUMPLOTS @@ -64,7 +64,7 @@ def getVals(self, addr, bytes): vals = self.I2C.readBulk(self.ADDRESS, addr, bytes) return vals - def powerUp(self, x): + def powerUp(self): self.I2C.writeBulk(self.ADDRESS, [0x6B, 0]) def setGyroRange(self, rs): diff --git a/PSL/SENSORS/MPU925x.py b/PSL/SENSORS/MPU925x.py new file mode 100644 index 00000000..1322c435 --- /dev/null +++ b/PSL/SENSORS/MPU925x.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8; mode: python; indent-tabs-mode: t; tab-width:4 -*- +from numpy import int16,std +from Kalman import KalmanFilter + +def connect(route,**args): + return MPU925x(route,**args) + +class MPU925x(): + ''' + Mandatory members: + GetRaw : Function called by Graphical apps. Must return values stored in a list + NUMPLOTS : length of list returned by GetRaw. Even single datapoints need to be stored in a list before returning + PLOTNAMES : a list of strings describing each element in the list returned by GetRaw. len(PLOTNAMES) = NUMPLOTS + name : the name of the sensor shown to the user + params: + A dictionary of function calls(single arguments only) paired with list of valid argument values. (Primitive. I know.) + These calls can be used for one time configuration settings + + ''' + INT_PIN_CFG = 0x37 + GYRO_CONFIG = 0x1B + ACCEL_CONFIG = 0x1C + GYRO_SCALING= [131,65.5,32.8,16.4] + ACCEL_SCALING=[16384,8192,4096,2048] + AR=3 + GR=3 + NUMPLOTS=7 + PLOTNAMES = ['Ax','Ay','Az','Temp','Gx','Gy','Gz'] + ADDRESS = 0x68 + AK8963_ADDRESS =0x0C + AK8963_CNTL = 0x0A + name = 'Accel/gyro' + def __init__(self,I2C,**args): + self.I2C=I2C + self.ADDRESS = args.get('address',self.ADDRESS) + self.name = 'Accel/gyro' + self.params={'powerUp':None,'setGyroRange':[250,500,1000,2000],'setAccelRange':[2,4,8,16],'KalmanFilter':[.01,.1,1,10,100,1000,10000,'OFF']} + self.setGyroRange(2000) + self.setAccelRange(16) + ''' + try: + self.I2C.configI2C(400e3) + except: + pass + ''' + self.powerUp() + self.K=None + + + + def KalmanFilter(self,opt): + if opt=='OFF': + self.K=None + return + noise=[[]]*self.NUMPLOTS + for a in range(500): + vals=self.getRaw() + for b in range(self.NUMPLOTS):noise[b].append(vals[b]) + + self.K=[None]*7 + for a in range(self.NUMPLOTS): + sd = std(noise[a]) + self.K[a] = KalmanFilter(1./opt, sd**2) + + def getVals(self,addr,bytes): + vals = self.I2C.readBulk(self.ADDRESS,addr,bytes) + return vals + + def powerUp(self): + self.I2C.writeBulk(self.ADDRESS,[0x6B,0]) + + def setGyroRange(self,rs): + self.GR=self.params['setGyroRange'].index(rs) + self.I2C.writeBulk(self.ADDRESS,[self.GYRO_CONFIG,self.GR<<3]) + + def setAccelRange(self,rs): + self.AR=self.params['setAccelRange'].index(rs) + self.I2C.writeBulk(self.ADDRESS,[self.ACCEL_CONFIG,self.AR<<3]) + + def getRaw(self): + ''' + This method must be defined if you want GUIs to use this class to generate + plots on the fly. + It must return a set of different values read from the sensor. such as X,Y,Z acceleration. + The length of this list must not change, and must be defined in the variable NUMPLOTS. + + GUIs will generate as many plots, and the data returned from this method will be appended appropriately + ''' + vals=self.getVals(0x3B,14) + if vals: + if len(vals)==14: + raw=[0]*7 + for a in range(3):raw[a] = 1.*int16(vals[a*2]<<8|vals[a*2+1])/self.ACCEL_SCALING[self.AR] + for a in range(4,7):raw[a] = 1.*int16(vals[a*2]<<8|vals[a*2+1])/self.GYRO_SCALING[self.GR] + raw[3] = int16(vals[6]<<8|vals[7])/340. + 36.53 + if not self.K: + return raw + else: + for b in range(self.NUMPLOTS): + self.K[b].input_latest_noisy_measurement(raw[b]) + raw[b]=self.K[b].get_latest_estimated_measurement() + return raw + + else: + return False + else: + return False + + def getAccel(self): + ''' + Return a list of 3 values for acceleration vector + + ''' + vals=self.getVals(0x3B,6) + ax=int16(vals[0]<<8|vals[1]) + ay=int16(vals[2]<<8|vals[3]) + az=int16(vals[4]<<8|vals[5]) + return [ax/65535.,ay/65535.,az/65535.] + + def getTemp(self): + ''' + Return temperature + ''' + vals=self.getVals(0x41,6) + t=int16(vals[0]<<8|vals[1]) + return t/65535. + + def getGyro(self): + ''' + Return a list of 3 values for angular velocity vector + + ''' + vals=self.getVals(0x43,6) + ax=int16(vals[0]<<8|vals[1]) + ay=int16(vals[2]<<8|vals[3]) + az=int16(vals[4]<<8|vals[5]) + return [ax/65535.,ay/65535.,az/65535.] + + def getMag(self): + ''' + Return a list of 3 values for magnetic field vector + + ''' + vals=self.I2C.readBulk(self.AK8963_ADDRESS,0x03,7) #6+1 . 1(ST2) should not have bit 4 (0x8) true. It's ideally 16 . overflow bit + ax=int16(vals[0]<<8|vals[1]) + ay=int16(vals[2]<<8|vals[3]) + az=int16(vals[4]<<8|vals[5]) + if not vals[6]&0x08:return [ax/65535.,ay/65535.,az/65535.] + else: return None + + + def WhoAmI(self): + ''' + Returns the ID . + It is 71 for MPU9250 . + ''' + v = self.I2C.readBulk(self.ADDRESS,0x75,1)[0] + if v not in [0x71,0x73]:return 'Error %s'%hex(v) + + + if v==0x73:return 'MPU9255 %s'%hex(v) + elif v==0x71:return 'MPU9250 %s'%hex(v) + + + def WhoAmI_AK8963(self): + ''' + Returns the ID fo magnetometer AK8963 if found. + It should be 0x48. + ''' + self.initMagnetometer() + v= self.I2C.readBulk(self.AK8963_ADDRESS,0,1) [0] + if v==0x48:return 'AK8963 at %s'%hex(v) + else: return 'AK8963 not found. returned :%s'%hex(v) + + def initMagnetometer(self): + ''' + For MPU925x with integrated magnetometer. + It's called a 10 DoF sensor, but technically speaking , + the 3-axis Accel , 3-Axis Gyro, temperature sensor are integrated in one IC, and the 3-axis magnetometer is implemented in a + separate IC which can be accessed via an I2C passthrough. + + Therefore , in order to detect the magnetometer via an I2C scan, the passthrough must first be enabled on IC#1 (Accel,gyro,temp) + ''' + self.I2C.writeBulk(self.ADDRESS,[self.INT_PIN_CFG,0x22]) #I2C passthrough + self.I2C.writeBulk(self.AK8963_ADDRESS,[self.AK8963_CNTL,0]) #power down mag + self.I2C.writeBulk(self.AK8963_ADDRESS,[self.AK8963_CNTL,(1<<4)|6]) #mode (0=14bits,1=16bits) <<4 | (2=8Hz , 6=100Hz) + + + +if __name__ == "__main__": + from PSL import sciencelab + I = sciencelab.connect() + A = connect(I.I2C) + t,x,y,z = I.I2C.capture(A.ADDRESS,0x43,6,5000,1000,'int') + #print (t,x,y,z) + from pylab import * + plot(t,x) + plot(t,y) + plot(t,z) + show() diff --git a/PSL/SENSORS/SHT21.py b/PSL/SENSORS/SHT21.py index 67ba575b..c86f60db 100644 --- a/PSL/SENSORS/SHT21.py +++ b/PSL/SENSORS/SHT21.py @@ -31,10 +31,10 @@ def __init__(self, I2C, **args): except: print ('FAILED TO CHANGE BAUD RATE') ''' - self.params = {'selectParameter': ['temperature', 'humidity']} - self.init('') + self.params = {'selectParameter': ['temperature', 'humidity'],'init':None} + self.init() - def init(self, x): + def init(self): self.I2C.writeBulk(self.ADDRESS, [self.RESET]) # soft reset time.sleep(0.1) diff --git a/PSL/SENSORS/TSL2561.py b/PSL/SENSORS/TSL2561.py index 6c9cfb6a..f4a878f4 100644 --- a/PSL/SENSORS/TSL2561.py +++ b/PSL/SENSORS/TSL2561.py @@ -5,11 +5,9 @@ from __future__ import print_function import time - def connect(route, **args): return TSL2561(route, **args) - class TSL2561: VISIBLE = 2 # channel 0 - channel 1 INFRARED = 1 # channel 1 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy