diff --git a/.gitignore b/.gitignore index dc328d23..4d22ef99 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __PYCACHE__ build _* PSL.egg-info +.idea/ diff --git a/.travis.yml b/.travis.yml index f4be74f6..071809de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,21 @@ language: python + python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - # - "3.5" - #- "3.5-dev" # 3.5 development branch - #- "nightly" # currently points to 3.6-dev -# command to install dependencies -#install: "pip install -r requirements.txt" -# command to run tests -script: nosetests + - "3.5" + - "3.6" + - "3.7" + - "3.8" + +before_install: + - sudo apt-get -qq update + - sudo mkdir -p /builds + - sudo chmod a+rw /builds -notifications: - slack: fossasia:bqOzo4C9y6oI6dTF8kO8zdxp +install: + - pip3 install flake8 -r requirements.txt + - flake8 . --select=E9,F63,F7,F82 --show-source --statistics + - sudo env "PATH=$PATH" make clean + - make all + - sudo env "PATH=$PATH" make install + +script: nosetests diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 97751bd4..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -recursive-include PSL/docs/docs * -recursive-include PSL/helpfiles/ * - diff --git a/Makefile b/Makefile index c80dc071..cd699712 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,35 @@ DESTDIR = + +# Find library installation path +INSTALL_PATH = $(patsubst Location:,,$(shell python3 -m pip show PSL | grep Location)) +INSTALL_PATH_LEN = $(shell echo $(INSTALL_PATH) | wc -c) + all: - #make -C docs html - #make -C docs/misc all - # make in subdirectory PSLab-apps-master if it is there - [ ! -d PSLab-apps-master ] || make -C PSLab-apps-master $@ DESTDIR=$(DESTDIR) - python setup.py build python3 setup.py build -clean: - rm -rf docs/_* - # make in subdirectory PSLab-apps-master if it is there - [ ! -d PSLab-apps-master ] || make -C PSLab-apps-master $@ DESTDIR=$(DESTDIR) - rm -rf PSL.egg-info build - find . -name "*~" -o -name "*.pyc" -o -name "__pycache__" | xargs rm -rf +fullcleanup: verifyFiles + # Removes every PSL instance in system. Be careful and check if the following list got all files inside a python folder or related to PSLab + find /usr/* -name "PSL*" -type d | xargs rm -rf + find /usr/* -name "pslab*" -type d | xargs rm -rf + find /opt/* -name "pslab-*" -type d | xargs rm -rf + find /usr/* -name "Experiments" -type f | xargs rm -rf + @echo "All selected files are deleted.." -IMAGEDIR=$(DESTDIR)/usr/share/doc/pslab-common/images +verifyFiles: + @find /usr/* -name "PSL*" -type d + @find /usr/* -name "pslab*" -type d + @find /opt/* -name "pslab-*" -type d + @find /usr/* -name "Experiments" -type f + @echo -n "Confirm if you want to remove all these files.. [Y/N] " && read ans && [ $${ans:-N} = Y ] + +clean: + # Remove build files + @rm -rf docs/_* + @rm -rf PSL.egg-info build + @find . -name "*~" -o -name "*.pyc" -o -name "__pycache__" | xargs rm -rf + if [ ${INSTALL_PATH_LEN} -gt 2 ]; then sudo rm -rf $(INSTALL_PATH)/PSL $(INSTALL_PATH)/PSL-1* ; fi install: - # make in subdirectory PSLab-apps-master if it is there - [ ! -d PSLab-apps-master ] || make -C PSLab-apps-master $@ DESTDIR=$(DESTDIR) - # install documents - install -d $(DESTDIR)/usr/share/doc/pslab - #cp -a docs/_build/html $(DESTDIR)/usr/share/doc/pslab - #cp docs/misc/build/*.html $(DESTDIR)/usr/share/doc/pslab/html - # create ditributions for Python2 and Python3 - python setup.py install --install-layout=deb \ - --root=$(DESTDIR)/ --prefix=/usr - python3 setup.py install --install-layout=deb \ - --root=$(DESTDIR)/ --prefix=/usr - # rules for udev + python3 setup.py install mkdir -p $(DESTDIR)/lib/udev/rules.d install -m 644 99-pslab.rules $(DESTDIR)/lib/udev/rules.d/99-pslab - # fix a few permissions - #find $(DESTDIR)/usr/share/pslab/psl_res -name auto.sh -exec chmod -x {} \; diff --git a/PSL/Peripherals.py b/PSL/Peripherals.py index c0023a0f..3751fce4 100644 --- a/PSL/Peripherals.py +++ b/PSL/Peripherals.py @@ -1,1538 +1,1586 @@ from __future__ import print_function import PSL.commands_proto as CP -import numpy as np -import time,inspect +import numpy as np +import time, inspect + - class I2C(): - """ - Methods to interact with the I2C port. An instance of Labtools.Packet_Handler must be passed to the init function - - - Example:: Read Values from an HMC5883L 3-axis Magnetometer(compass) [GY-273 sensor] connected to the I2C port - >>> ADDRESS = 0x1E - >>> from PSL import sciencelab - >>> I = sciencelab.connect() - #Alternately, you may skip using I2C as a child instance of Interface, - #and instead use I2C=PSL.Peripherals.I2C(PSL.packet_handler.Handler()) - - # writing to 0x1E, set gain(0x01) to smallest(0 : 1x) - >>> I.I2C.bulkWrite(ADDRESS,[0x01,0]) - - # writing to 0x1E, set mode conf(0x02), continuous measurement(0) - >>> I.I2C.bulkWrite(ADDRESS,[0x02,0]) - - # read 6 bytes from addr register on I2C device located at ADDRESS - >>> vals = I.I2C.bulkRead(ADDRESS,addr,6) - - >>> from numpy import int16 - #conversion to signed datatype - >>> x=int16((vals[0]<<8)|vals[1]) - >>> y=int16((vals[2]<<8)|vals[3]) - >>> z=int16((vals[4]<<8)|vals[5]) - >>> print (x,y,z) - - """ - - def __init__(self,H): - self.H = H - from PSL import sensorlist - self.SENSORS=sensorlist.sensors - self.buff=np.zeros(10000) - - def init(self): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_INIT) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def enable_smbus(self): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_ENABLE_SMBUS) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def pullSCLLow(self,uS): - """ - Hold SCL pin at 0V for a specified time period. Used by certain sensors such - as MLX90316 PIR for initializing. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - uS Time(in uS) to hold SCL output at 0 Volts - ================ ============================================================================================ - - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_PULLDOWN_SCL) - self.H.__sendInt__(uS) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - def config(self,freq,verbose=True): - """ - Sets frequency for I2C transactions - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - freq I2C frequency - ================ ============================================================================================ - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_CONFIG) - #freq=1/((BRGVAL+1.0)/64e6+1.0/1e7) - BRGVAL=int( (1./freq-1./1e7)*64e6-1 ) - if BRGVAL>511: - BRGVAL=511 - if verbose:print ('Frequency too low. Setting to :',1/((BRGVAL+1.0)/64e6+1.0/1e7)) - self.H.__sendInt__(BRGVAL) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def start(self,address,rw): - """ - Initiates I2C transfer to address via the I2C port - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - address I2C slave address\n - rw Read/write. - - 0 for writing - - 1 for reading. - ================ ============================================================================================ - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_START) - self.H.__sendByte__(((address<<1)|rw)&0xFF) # address - return self.H.__get_ack__()>>4 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def stop(self): - """ - stops I2C transfer - - :return: Nothing - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_STOP) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def wait(self): - """ - wait for I2C - - :return: Nothing - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_WAIT) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def send(self,data): - """ - SENDS data over I2C. - The I2C bus needs to be initialized and set to the correct slave address first. - Use I2C.start(address) for this. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - data Sends data byte over I2C bus - ================ ============================================================================================ - - :return: Nothing - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_SEND) - self.H.__sendByte__(data) #data byte - return self.H.__get_ack__()>>4 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def send_burst(self,data): - """ - SENDS data over I2C. The function does not wait for the I2C to finish before returning. - It is used for sending large packets quickly. - The I2C bus needs to be initialized and set to the correct slave address first. - Use start(address) for this. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - data Sends data byte over I2C bus - ================ ============================================================================================ - - :return: Nothing - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_SEND_BURST) - self.H.__sendByte__(data) #data byte - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - #No handshake. for the sake of speed. e.g. loading a frame buffer onto an I2C display such as ssd1306 - - def restart(self,address,rw): - """ - Initiates I2C transfer to address - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - address I2C slave address - rw Read/write. - * 0 for writing - * 1 for reading. - ================ ============================================================================================ - - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_RESTART) - self.H.__sendByte__(((address<<1)|rw)&0xFF) # address - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return self.H.__get_ack__()>>4 - - def simpleRead(self,addr,numbytes): - """ - Read bytes from I2C slave without first transmitting the read location. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - addr Address of I2C slave - numbytes Total Bytes to read - ================ ============================================================================================ - """ - self.start(addr,1) - vals=self.read(numbytes) - return vals - - def read(self,length): - """ - Reads a fixed number of data bytes from I2C device. Fetches length-1 bytes with acknowledge bits for each, +1 byte - with Nack. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - length number of bytes to read from I2C bus - ================ ============================================================================================ - """ - data=[] - try: - for a in range(length-1): - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_READ_MORE) - data.append(self.H.__getByte__()) - self.H.__get_ack__() - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_READ_END) - data.append(self.H.__getByte__()) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return data - - def read_repeat(self): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_READ_MORE) - val=self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return val - - def read_end(self): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_READ_END) - val=self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return val - - - def read_status(self): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_STATUS) - val=self.H.__getInt__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return val - - - def readBulk(self,device_address,register_address,bytes_to_read): - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_READ_BULK) - self.H.__sendByte__(device_address) - self.H.__sendByte__(register_address) - self.H.__sendByte__(bytes_to_read) - data=self.H.fd.read(bytes_to_read) - self.H.__get_ack__() - try: - return [ord(a) for a in data] - except: - print ('Transaction failed') - return False - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def writeBulk(self,device_address,bytestream): - """ - write bytes to I2C slave - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - device_address Address of I2C slave - bytestream List of bytes to write - ================ ============================================================================================ - """ - try: - self.H.__sendByte__(CP.I2C_HEADER) - self.H.__sendByte__(CP.I2C_WRITE_BULK) - self.H.__sendByte__(device_address) - self.H.__sendByte__(len(bytestream)) - for a in bytestream: - self.H.__sendByte__(a) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def scan(self,frequency = 100000,verbose=False): - """ - Scan I2C port for connected devices - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - Frequency I2C clock frequency - ================ ============================================================================================ - - :return: Array of addresses of connected I2C slave devices - - """ - - self.config(frequency,verbose) - addrs=[] - n=0 - if verbose: - print ('Scanning addresses 0-127...') - print ('Address','\t','Possible Devices') - for a in range(0,128): - x = self.start(a,0) - if x&1 == 0: #ACK received - addrs.append(a) - if verbose: print (hex(a),'\t\t',self.SENSORS.get(a,'None')) - n+=1 - self.stop() - return addrs - - - 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 - - .. 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) - ================== ============================================================================================ - - Example - - >>> from pylab import * - >>> I=sciencelab.ScienceLab() - >>> x,y1,y2,y3,y4 = I.capture_multiple(800,1.75,'CH1','CH2','MIC','SEN') - >>> plot(x,y1) - >>> plot(x,y2) - >>> plot(x,y3) - >>> plot(x,y4) - >>> show() - - :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>MAX_SAMPLES*2): - print ('Sample limit exceeded. 10,000 int / 20000 bytes total') - total_samples = MAX_SAMPLES*2/sample_length #2* because sample array is in Integers, and we're using it to store bytes - total_bytes = MAX_SAMPLES*2 - - if('int' in args): - total_chans = sample_length/2 - channel_length = total_bytes/sample_length/2 - else: - total_chans = sample_length - channel_length = total_bytes/sample_length - - print ('total channels calculated : ',total_chans) - print ('length of each channel : ',channel_length) - try: - 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__() - print ( 'done', total_chans, channel_length) - - print ('sleeping for : ',1e-6*total_samples*tg+.01) - - time.sleep(1e-6*total_samples*tg+0.5) - data=b'' - total_int_samples = total_bytes/2 - - 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(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 ('Final Pass : len=',len(data)) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - try: - data = [ord(a) for a in data] - if('int' in args): - for a in range(total_chans*channel_length): self.buff[a] = np.int16((data[a*2]<<8)|data[a*2+1]) - else: - for a in range(total_chans*channel_length): self.buff[a] = data[a] - - #print (self.buff, 'geer') - - yield np.linspace(0,tg*(channel_length-1),channel_length) - for a in range(int(total_chans)): - yield self.buff[a:channel_length*total_chans][::total_chans] - except Exception as ex: - msg = "Incorrect number of bytes received" - raise RuntimeError(msg) + """ + Methods to interact with the I2C port. An instance of Labtools.Packet_Handler must be passed to the init function + + + Example:: Read Values from an HMC5883L 3-axis Magnetometer(compass) [GY-273 sensor] connected to the I2C port + >>> ADDRESS = 0x1E + >>> from PSL import sciencelab + >>> I = sciencelab.connect() + #Alternately, you may skip using I2C as a child instance of Interface, + #and instead use I2C=PSL.Peripherals.I2C(PSL.packet_handler.Handler()) + + # writing to 0x1E, set gain(0x01) to smallest(0 : 1x) + >>> I.I2C.bulkWrite(ADDRESS,[0x01,0]) + + # writing to 0x1E, set mode conf(0x02), continuous measurement(0) + >>> I.I2C.bulkWrite(ADDRESS,[0x02,0]) + + # read 6 bytes from addr register on I2C device located at ADDRESS + >>> vals = I.I2C.bulkRead(ADDRESS,addr,6) + + >>> from numpy import int16 + #conversion to signed datatype + >>> x=int16((vals[0]<<8)|vals[1]) + >>> y=int16((vals[2]<<8)|vals[3]) + >>> z=int16((vals[4]<<8)|vals[5]) + >>> 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 + self.SENSORS = sensorlist.sensors + self.buff = np.zeros(10000) + + def init(self): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_INIT) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def enable_smbus(self): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_ENABLE_SMBUS) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def pullSCLLow(self, uS): + """ + Hold SCL pin at 0V for a specified time period. Used by certain sensors such + as MLX90316 PIR for initializing. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + uS Time(in uS) to hold SCL output at 0 Volts + ================ ============================================================================================ + + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_PULLDOWN_SCL) + self.H.__sendInt__(uS) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def config(self, freq, verbose=True): + """ + Sets frequency for I2C transactions + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + freq I2C frequency + ================ ============================================================================================ + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_CONFIG) + # freq=1/((BRGVAL+1.0)/64e6+1.0/1e7) + BRGVAL = int((1. / freq - 1. / 1e7) * 64e6 - 1) + if BRGVAL > 511: + BRGVAL = 511 + if verbose: print('Frequency too low. Setting to :', 1 / ((BRGVAL + 1.0) / 64e6 + 1.0 / 1e7)) + self.H.__sendInt__(BRGVAL) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start(self, address, rw): + """ + Initiates I2C transfer to address via the I2C port + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + address I2C slave address\n + rw Read/write. + - 0 for writing + - 1 for reading. + ================ ============================================================================================ + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_START) + self.H.__sendByte__(((address << 1) | rw) & 0xFF) # address + return self.H.__get_ack__() >> 4 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def stop(self): + """ + stops I2C transfer + + :return: Nothing + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_STOP) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def wait(self): + """ + wait for I2C + + :return: Nothing + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_WAIT) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def send(self, data): + """ + SENDS data over I2C. + The I2C bus needs to be initialized and set to the correct slave address first. + Use I2C.start(address) for this. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + data Sends data byte over I2C bus + ================ ============================================================================================ + + :return: Nothing + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_SEND) + self.H.__sendByte__(data) # data byte + return self.H.__get_ack__() >> 4 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def send_burst(self, data): + """ + SENDS data over I2C. The function does not wait for the I2C to finish before returning. + It is used for sending large packets quickly. + The I2C bus needs to be initialized and set to the correct slave address first. + Use start(address) for this. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + data Sends data byte over I2C bus + ================ ============================================================================================ + + :return: Nothing + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_SEND_BURST) + self.H.__sendByte__(data) # data byte + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + # No handshake. for the sake of speed. e.g. loading a frame buffer onto an I2C display such as ssd1306 + + def restart(self, address, rw): + """ + Initiates I2C transfer to address + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + address I2C slave address + rw Read/write. + * 0 for writing + * 1 for reading. + ================ ============================================================================================ + + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_RESTART) + self.H.__sendByte__(((address << 1) | rw) & 0xFF) # address + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + return self.H.__get_ack__() >> 4 + + def simpleRead(self, addr, numbytes): + """ + Read bytes from I2C slave without first transmitting the read location. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + addr Address of I2C slave + numbytes Total Bytes to read + ================ ============================================================================================ + """ + self.start(addr, 1) + vals = self.read(numbytes) + return vals + + def read(self, length): + """ + Reads a fixed number of data bytes from I2C device. Fetches length-1 bytes with acknowledge bits for each, +1 byte + with Nack. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + length number of bytes to read from I2C bus + ================ ============================================================================================ + """ + data = [] + try: + for a in range(length - 1): + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_READ_MORE) + data.append(self.H.__getByte__()) + self.H.__get_ack__() + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_READ_END) + data.append(self.H.__getByte__()) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + return data + + def read_repeat(self): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_READ_MORE) + val = self.H.__getByte__() + self.H.__get_ack__() + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def read_end(self): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_READ_END) + val = self.H.__getByte__() + self.H.__get_ack__() + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def read_status(self): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_STATUS) + val = self.H.__getInt__() + self.H.__get_ack__() + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def readBulk(self, device_address, register_address, bytes_to_read): + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_READ_BULK) + self.H.__sendByte__(device_address) + self.H.__sendByte__(register_address) + self.H.__sendByte__(bytes_to_read) + data = self.H.fd.read(bytes_to_read) + self.H.__get_ack__() + try: + return list(data) + except: + print('Transaction failed') + return False + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def writeBulk(self, device_address, bytestream): + """ + write bytes to I2C slave + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + device_address Address of I2C slave + bytestream List of bytes to write + ================ ============================================================================================ + """ + try: + self.H.__sendByte__(CP.I2C_HEADER) + self.H.__sendByte__(CP.I2C_WRITE_BULK) + self.H.__sendByte__(device_address) + self.H.__sendByte__(len(bytestream)) + for a in bytestream: + self.H.__sendByte__(a) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def scan(self, frequency=100000, verbose=False): + """ + Scan I2C port for connected devices + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + Frequency I2C clock frequency + ================ ============================================================================================ + + :return: Array of addresses of connected I2C slave devices + + """ + + self.config(frequency, verbose) + addrs = [] + n = 0 + if verbose: + print('Scanning addresses 0-127...') + print('Address', '\t', 'Possible Devices') + for a in range(0, 128): + x = self.start(a, 0) + if x & 1 == 0: # ACK received + addrs.append(a) + if verbose: print(hex(a), '\t\t', self.SENSORS.get(a, 'None')) + n += 1 + 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 _ 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 _ 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 + + .. 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) + ================== ============================================================================================ + + Example + + >>> from pylab import * + >>> I=sciencelab.ScienceLab() + >>> x,y1,y2,y3,y4 = I.capture_multiple(800,1.75,'CH1','CH2','MIC','SEN') + >>> plot(x,y1) + >>> plot(x,y2) + >>> plot(x,y3) + >>> plot(x,y4) + >>> show() + + :return: Arrays X(timestamps),Y1,Y2 ... + + """ + t = self.__captureStart__(address, location, sample_length, total_samples, tg) + time.sleep(t) + data = self.__retrievebuffer__() + return self.__dataProcessor__(data, *args) + class SPI(): - """ - Methods to interact with the SPI port. An instance of Packet_Handler must be passed to the init function - - """ - def __init__(self,H): - self.H = H - - def set_parameters(self,primary_prescaler=0,secondary_prescaler=2,CKE=1,CKP=0,SMP=1): - """ - sets SPI parameters. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - primary_pres Primary Prescaler(0,1,2,3) for 64MHz clock->(64:1,16:1,4:1,1:1) - secondary_pres Secondary prescaler(0,1,..7)->(8:1,7:1,..1:1) - CKE CKE 0 or 1. - CKP CKP 0 or 1. - ================ ============================================================================================ - - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.SET_SPI_PARAMETERS) - #0Bhgfedcba - > : modebit CKP,: modebit CKE, :primary pre,:secondary pre - self.H.__sendByte__(secondary_prescaler|(primary_prescaler<<3)|(CKE<<5)|(CKP<<6)|(SMP<<7)) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def start(self,channel): - """ - selects SPI channel to enable. - Basically lowers the relevant chip select pin . - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - channel 1-7 ->[PGA1 connected to CH1,PGA2,PGA3,PGA4,PGA5,external chip select 1,external chip select 2] - 8 -> sine1 - 9 -> sine2 - ================ ============================================================================================ - - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.START_SPI) - self.H.__sendByte__(channel) #value byte - #self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def set_cs(self,channel,state): - """ - Enable or disable a chip select - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - channel 'CS1','CS2' - state 1 for HIGH, 0 for LOW - ================ ============================================================================================ - - """ - try: - channel = channel.upper() - if channel in ['CS1','CS2']: - csnum=['CS1','CS2'].index(channel)+9 #chip select number 9=CSOUT1,10=CSOUT2 - self.H.__sendByte__(CP.SPI_HEADER) - if state:self.H.__sendByte__(CP.STOP_SPI) - else:self.H.__sendByte__(CP.START_SPI) - self.H.__sendByte__(csnum) - else: print('Channel does not exist') - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def stop(self,channel): - """ - selects SPI channel to disable. - Sets the relevant chip select pin to HIGH. - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - channel 1-7 ->[PGA1 connected to CH1,PGA2,PGA3,PGA4,PGA5,external chip select 1,external chip select 2] - ================ ============================================================================================ - - - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.STOP_SPI) - self.H.__sendByte__(channel) #value byte - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - #self.H.__get_ack__() - - def send8(self,value): - """ - SENDS 8-bit data over SPI - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - value value to transmit - ================ ============================================================================================ - - :return: value returned by slave device - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.SEND_SPI8) - self.H.__sendByte__(value) #value byte - v=self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return v - - def send16(self,value): - """ - SENDS 16-bit data over SPI - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - value value to transmit - ================ ============================================================================================ - - :return: value returned by slave device - :rtype: int - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.SEND_SPI16) - self.H.__sendInt__(value) #value byte - v=self.H.__getInt__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return v - - def send8_burst(self,value): - """ - SENDS 8-bit data over SPI - No acknowledge/return value - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ================ ============================================================================================ - **Arguments** - ================ ============================================================================================ - value value to transmit - ================ ============================================================================================ - - :return: Nothing - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.SEND_SPI8_BURST) - self.H.__sendByte__(value) #value byte - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def send16_burst(self,value): - """ - SENDS 16-bit data over SPI - no acknowledge/return value - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ============== ============================================================================================ - **Arguments** - ============== ============================================================================================ - value value to transmit - ============== ============================================================================================ - - :return: nothing - """ - try: - self.H.__sendByte__(CP.SPI_HEADER) - self.H.__sendByte__(CP.SEND_SPI16_BURST) - self.H.__sendInt__(value) #value byte - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + """ + Methods to interact with the SPI port. An instance of Packet_Handler must be passed to the init function + + """ + + def __init__(self, H): + self.H = H + + def set_parameters(self, primary_prescaler=0, secondary_prescaler=2, CKE=1, CKP=0, SMP=1): + """ + sets SPI parameters. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + primary_pres Primary Prescaler(0,1,2,3) for 64MHz clock->(64:1,16:1,4:1,1:1) + secondary_pres Secondary prescaler(0,1,..7)->(8:1,7:1,..1:1) + CKE CKE 0 or 1. + CKP CKP 0 or 1. + ================ ============================================================================================ + + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.SET_SPI_PARAMETERS) + # 0Bhgfedcba - > : modebit CKP,: modebit CKE, :primary pre,:secondary pre + self.H.__sendByte__(secondary_prescaler | (primary_prescaler << 3) | (CKE << 5) | (CKP << 6) | (SMP << 7)) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start(self, channel): + """ + selects SPI channel to enable. + Basically lowers the relevant chip select pin . + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + channel 1-7 ->[PGA1 connected to CH1,PGA2,PGA3,PGA4,PGA5,external chip select 1,external chip select 2] + 8 -> sine1 + 9 -> sine2 + ================ ============================================================================================ + + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.START_SPI) + self.H.__sendByte__(channel) # value byte + # self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def set_cs(self, channel, state): + """ + Enable or disable a chip select + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + channel 'CS1','CS2' + state 1 for HIGH, 0 for LOW + ================ ============================================================================================ + + """ + try: + channel = channel.upper() + if channel in ['CS1', 'CS2']: + csnum = ['CS1', 'CS2'].index(channel) + 9 # chip select number 9=CSOUT1,10=CSOUT2 + self.H.__sendByte__(CP.SPI_HEADER) + if state: + self.H.__sendByte__(CP.STOP_SPI) + else: + self.H.__sendByte__(CP.START_SPI) + self.H.__sendByte__(csnum) + else: + print('Channel does not exist') + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def stop(self, channel): + """ + selects SPI channel to disable. + Sets the relevant chip select pin to HIGH. + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + channel 1-7 ->[PGA1 connected to CH1,PGA2,PGA3,PGA4,PGA5,external chip select 1,external chip select 2] + ================ ============================================================================================ + + + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.STOP_SPI) + self.H.__sendByte__(channel) # value byte + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + # self.H.__get_ack__() + + def send8(self, value): + """ + SENDS 8-bit data over SPI + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + value value to transmit + ================ ============================================================================================ + + :return: value returned by slave device + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.SEND_SPI8) + self.H.__sendByte__(value) # value byte + v = self.H.__getByte__() + self.H.__get_ack__() + return v + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def send16(self, value): + """ + SENDS 16-bit data over SPI + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + value value to transmit + ================ ============================================================================================ + + :return: value returned by slave device + :rtype: int + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.SEND_SPI16) + self.H.__sendInt__(value) # value byte + v = self.H.__getInt__() + self.H.__get_ack__() + return v + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def send8_burst(self, value): + """ + SENDS 8-bit data over SPI + No acknowledge/return value + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ================ ============================================================================================ + **Arguments** + ================ ============================================================================================ + value value to transmit + ================ ============================================================================================ + + :return: Nothing + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.SEND_SPI8_BURST) + self.H.__sendByte__(value) # value byte + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def send16_burst(self, value): + """ + SENDS 16-bit data over SPI + no acknowledge/return value + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ============== ============================================================================================ + **Arguments** + ============== ============================================================================================ + value value to transmit + ============== ============================================================================================ + + :return: nothing + """ + try: + self.H.__sendByte__(CP.SPI_HEADER) + self.H.__sendByte__(CP.SEND_SPI16_BURST) + self.H.__sendInt__(value) # value byte + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def xfer(self, chan, data): + self.start(chan) + reply = [] + for a in data: + reply.append(self.send8(a)) + self.stop(chan) + return reply + class DACCHAN: - def __init__(self,name,span,channum,**kwargs): - self.name = name - self.channum=channum - self.VREF = kwargs.get('VREF',0) - self.SwitchedOff = kwargs.get('STATE',0) - self.range = span - slope = (span[1]-span[0]) - intercept = span[0] - self.VToCode = np.poly1d([4095./slope,-4095.*intercept/slope ]) - self.CodeToV = np.poly1d([slope/4095.,intercept ]) - self.calibration_enabled = False - self.calibration_table = [] - self.slope=1 - self.offset=0 - - def load_calibration_table(self,table): - self.calibration_enabled='table' - self.calibration_table = table - - def load_calibration_twopoint(self,slope,offset): - self.calibration_enabled='twopoint' - self.slope = slope - self.offset = offset - #print('########################',slope,offset) - - - def apply_calibration(self,v): - if self.calibration_enabled=='table': #Each point is individually calibrated - return int(np.clip(v+self.calibration_table[v] ,0,4095)) - elif self.calibration_enabled=='twopoint': #Overall slope and offset correction is applied - #print (self.slope,self.offset,v) - return int(np.clip(v*self.slope+self.offset,0,4095) ) - else: - return v - + def __init__(self, name, span, channum, **kwargs): + self.name = name + self.channum = channum + self.VREF = kwargs.get('VREF', 0) + self.SwitchedOff = kwargs.get('STATE', 0) + self.range = span + slope = (span[1] - span[0]) + intercept = span[0] + self.VToCode = np.poly1d([4095. / slope, -4095. * intercept / slope]) + self.CodeToV = np.poly1d([slope / 4095., intercept]) + self.calibration_enabled = False + self.calibration_table = [] + self.slope = 1 + self.offset = 0 + + def load_calibration_table(self, table): + self.calibration_enabled = 'table' + self.calibration_table = table + + def load_calibration_twopoint(self, slope, offset): + self.calibration_enabled = 'twopoint' + self.slope = slope + self.offset = offset + + # print('########################',slope,offset) + + def apply_calibration(self, v): + if self.calibration_enabled == 'table': # Each point is individually calibrated + return int(np.clip(v + self.calibration_table[v], 0, 4095)) + elif self.calibration_enabled == 'twopoint': # Overall slope and offset correction is applied + # print (self.slope,self.offset,v) + return int(np.clip(v * self.slope + self.offset, 0, 4095)) + else: + return v + class MCP4728: - defaultVDD =3300 - RESET =6 - WAKEUP =9 - UPDATE =8 - WRITEALL =64 - WRITEONE =88 - SEQWRITE =80 - VREFWRITE =128 - GAINWRITE =192 - POWERDOWNWRITE =160 - GENERALCALL =0 - #def __init__(self,I2C,vref=3.3,devid=0): - def __init__(self,H,vref=3.3,devid=0): - self.devid = devid - self.addr = 0x60|self.devid #0x60 is the base address - self.H=H - self.I2C = I2C(self.H) - self.SWITCHEDOFF=[0,0,0,0] - self.VREFS=[0,0,0,0] #0=Vdd,1=Internal reference - self.CHANS = {'PCS':DACCHAN('PCS',[0,3.3e-3],0),'PV3':DACCHAN('PV3',[0,3.3],1),'PV2':DACCHAN('PV2',[-3.3,3.3],2),'PV1':DACCHAN('PV1',[-5.,5.],3)} - self.CHANNEL_MAP={0:'PCS',1:'PV3',2:'PV2',3:'PV1'} - - - - def __ignoreCalibration__(self,name): - self.CHANS[name].calibration_enabled=False - - def setVoltage(self,name,v): - chan = self.CHANS[name] - v = int(round(chan.VToCode(v))) - return self.__setRawVoltage__(name,v) - - def getVoltage(self,name): - return self.values[name] - - def setCurrent(self,v): - chan = self.CHANS['PCS'] - v = int(round(chan.VToCode(v))) - return self.__setRawVoltage__('PCS',v) - - def __setRawVoltage__(self,name,v): - v=int(np.clip(v,0,4095)) - CHAN = self.CHANS[name] - ''' - self.H.__sendByte__(CP.DAC) #DAC write coming through.(MCP4728) - self.H.__sendByte__(CP.SET_DAC) - self.H.__sendByte__(self.addr<<1) #I2C address - self.H.__sendByte__(CHAN.channum) #DAC channel - if self.calibration_enabled[name]: - val = v+self.calibration_tables[name][v] - #print (val,v,self.calibration_tables[name][v]) - self.H.__sendInt__((CHAN.VREF << 15) | (CHAN.SwitchedOff << 13) | (0 << 12) | (val) ) - else: - self.H.__sendInt__((CHAN.VREF << 15) | (CHAN.SwitchedOff << 13) | (0 << 12) | v ) - - self.H.__get_ack__() - ''' - val = self.CHANS[name].apply_calibration(v) - self.I2C.writeBulk(self.addr,[64|(CHAN.channum<<1),(val>>8)&0x0F,val&0xFF]) - return CHAN.CodeToV(v) - - - def __writeall__(self,v1,v2,v3,v4): - self.I2C.start(self.addr,0) - self.I2C.send((v1>>8)&0xF ) - self.I2C.send(v1&0xFF) - self.I2C.send((v2>>8)&0xF ) - self.I2C.send(v2&0xFF) - self.I2C.send((v3>>8)&0xF ) - self.I2C.send(v3&0xFF) - self.I2C.send((v4>>8)&0xF ) - self.I2C.send(v4&0xFF) - self.I2C.stop() - - def stat(self): - self.I2C.start(self.addr,0) - self.I2C.send(0x0) #read raw values starting from address - self.I2C.restart(self.addr,1) - vals=self.I2C.read(24) - self.I2C.stop() - print (vals) + defaultVDD = 3300 + RESET = 6 + WAKEUP = 9 + UPDATE = 8 + WRITEALL = 64 + WRITEONE = 88 + SEQWRITE = 80 + VREFWRITE = 128 + GAINWRITE = 192 + POWERDOWNWRITE = 160 + GENERALCALL = 0 + + # def __init__(self,I2C,vref=3.3,devid=0): + def __init__(self, H, vref=3.3, devid=0): + self.devid = devid + self.addr = 0x60 | self.devid # 0x60 is the base address + self.H = H + self.I2C = I2C(self.H) + self.SWITCHEDOFF = [0, 0, 0, 0] + self.VREFS = [0, 0, 0, 0] # 0=Vdd,1=Internal reference + self.CHANS = {'PCS': DACCHAN('PCS', [0, 3.3e-3], 0), 'PV3': DACCHAN('PV3', [0, 3.3], 1), + 'PV2': DACCHAN('PV2', [-3.3, 3.3], 2), 'PV1': DACCHAN('PV1', [-5., 5.], 3)} + self.CHANNEL_MAP = {0: 'PCS', 1: 'PV3', 2: 'PV2', 3: 'PV1'} + self.values = {'PV1': 0, 'PV2': 0, 'PV3': 0, 'PCS': 0} + + def __ignoreCalibration__(self, name): + self.CHANS[name].calibration_enabled = False + + def setVoltage(self, name, v): + chan = self.CHANS[name] + v = int(round(chan.VToCode(v))) + return self.__setRawVoltage__(name, v) + + def getVoltage(self, name): + return self.values[name] + + def setCurrent(self, v): + chan = self.CHANS['PCS'] + v = int(round(chan.VToCode(v))) + return self.__setRawVoltage__('PCS', v) + + def __setRawVoltage__(self, name, v): + v = int(np.clip(v, 0, 4095)) + CHAN = self.CHANS[name] + ''' + self.H.__sendByte__(CP.DAC) #DAC write coming through.(MCP4728) + self.H.__sendByte__(CP.SET_DAC) + self.H.__sendByte__(self.addr<<1) #I2C address + self.H.__sendByte__(CHAN.channum) #DAC channel + if self.calibration_enabled[name]: + val = v+self.calibration_tables[name][v] + #print (val,v,self.calibration_tables[name][v]) + self.H.__sendInt__((CHAN.VREF << 15) | (CHAN.SwitchedOff << 13) | (0 << 12) | (val) ) + else: + self.H.__sendInt__((CHAN.VREF << 15) | (CHAN.SwitchedOff << 13) | (0 << 12) | v ) + + self.H.__get_ack__() + ''' + val = self.CHANS[name].apply_calibration(v) + self.I2C.writeBulk(self.addr, [64 | (CHAN.channum << 1), (val >> 8) & 0x0F, val & 0xFF]) + self.values[name] = CHAN.CodeToV(v) + return self.values[name] + + def __writeall__(self, v1, v2, v3, v4): + self.I2C.start(self.addr, 0) + self.I2C.send((v1 >> 8) & 0xF) + self.I2C.send(v1 & 0xFF) + self.I2C.send((v2 >> 8) & 0xF) + self.I2C.send(v2 & 0xFF) + self.I2C.send((v3 >> 8) & 0xF) + self.I2C.send(v3 & 0xFF) + self.I2C.send((v4 >> 8) & 0xF) + self.I2C.send(v4 & 0xFF) + self.I2C.stop() + + def stat(self): + self.I2C.start(self.addr, 0) + self.I2C.send(0x0) # read raw values starting from address + self.I2C.restart(self.addr, 1) + vals = self.I2C.read(24) + self.I2C.stop() + print(vals) + class NRF24L01(): - #Commands - R_REG = 0x00 - W_REG = 0x20 - RX_PAYLOAD = 0x61 - TX_PAYLOAD = 0xA0 - ACK_PAYLOAD = 0xA8 - FLUSH_TX = 0xE1 - FLUSH_RX = 0xE2 - ACTIVATE = 0x50 - R_STATUS = 0xFF - - #Registers - NRF_CONFIG = 0x00 - EN_AA = 0x01 - EN_RXADDR = 0x02 - SETUP_AW = 0x03 - SETUP_RETR = 0x04 - RF_CH = 0x05 - RF_SETUP = 0x06 - NRF_STATUS = 0x07 - OBSERVE_TX = 0x08 - CD = 0x09 - RX_ADDR_P0 = 0x0A - RX_ADDR_P1 = 0x0B - RX_ADDR_P2 = 0x0C - RX_ADDR_P3 = 0x0D - RX_ADDR_P4 = 0x0E - RX_ADDR_P5 = 0x0F - TX_ADDR = 0x10 - RX_PW_P0 = 0x11 - RX_PW_P1 = 0x12 - RX_PW_P2 = 0x13 - RX_PW_P3 = 0x14 - RX_PW_P4 = 0x15 - RX_PW_P5 = 0x16 - R_RX_PL_WID = 0x60 - FIFO_STATUS = 0x17 - DYNPD = 0x1C - FEATURE = 0x1D - PAYLOAD_SIZE = 0 - ACK_PAYLOAD_SIZE =0 - READ_PAYLOAD_SIZE =0 - - ADC_COMMANDS =1 - READ_ADC =0<<4 - - I2C_COMMANDS =2 - I2C_TRANSACTION =0<<4 - I2C_WRITE =1<<4 - SCAN_I2C =2<<4 - PULL_SCL_LOW = 3<<4 - I2C_CONFIG = 4<<4 - I2C_READ = 5<<4 - - NRF_COMMANDS = 3 - NRF_READ_REGISTER =0 - NRF_WRITE_REGISTER =1<<4 - - - CURRENT_ADDRESS=0xAAAA01 - nodelist={} - nodepos=0 - NODELIST_MAXLENGTH=15 - connected=False - def __init__(self,H): - self.H = H - self.ready=False - self.sigs={self.CURRENT_ADDRESS:1} - if self.H.connected: - self.connected=self.init() - - """ - routines for the NRFL01 radio - """ - def init(self): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_SETUP) - self.H.__get_ack__() - time.sleep(0.015) #15 mS settling time - stat = self.get_status() - if stat &0x80: - print ("Radio transceiver not installed/not found") - return False - else: - self.ready=True - self.selectAddress(self.CURRENT_ADDRESS) - #self.write_register(self.RF_SETUP,0x06) - self.rxmode() - time.sleep(0.1) - self.flush() - return True - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def rxmode(self): - ''' - Puts the radio into listening mode. - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_RXMODE) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def txmode(self): - ''' - Puts the radio into transmit mode. - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_TXMODE) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def triggerAll(self,val): - self.txmode() - self.selectAddress(0x111111) - self.write_register(self.EN_AA,0x00) - self.write_payload([val],True) - self.write_register(self.EN_AA,0x01) - - - def power_down(self): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_POWER_DOWN) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def rxchar(self): - ''' - Receives a 1 Byte payload - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_RXCHAR) - value = self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return value - - def txchar(self,char): - ''' - Transmits a single character - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_TXCHAR) - self.H.__sendByte__(char) - return self.H.__get_ack__()>>4 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def hasData(self): - ''' - Check if the RX FIFO contains data - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_HASDATA) - value = self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return value - - def flush(self): - ''' - Flushes the TX and RX FIFOs - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_FLUSH) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_register(self,address,value): - ''' - write a byte to any of the configuration registers on the Radio. - address byte can either be located in the NRF24L01+ manual, or chosen - from some of the constants defined in this module. - ''' - #print ('writing',address,value) - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITEREG) - self.H.__sendByte__(address) - self.H.__sendByte__(value) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def read_register(self,address): - ''' - Read the value of any of the configuration registers on the radio module. - - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_READREG) - self.H.__sendByte__(address) - val=self.H.__getByte__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return val - - def get_status(self): - ''' - Returns a byte representing the STATUS register on the radio. - Refer to NRF24L01+ documentation for further details - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_GETSTATUS) - val=self.H.__getByte__() - self.H.__get_ack__() - return val - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_command(self,cmd): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITECOMMAND) - self.H.__sendByte__(cmd) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_address(self,register,address): - ''' - register can be TX_ADDR, RX_ADDR_P0 -> RX_ADDR_P5 - 3 byte address. eg 0xFFABXX . XX cannot be FF - if RX_ADDR_P1 needs to be used along with any of the pipes - from P2 to P5, then RX_ADDR_P1 must be updated last. - Addresses from P1-P5 must share the first two bytes. - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITEADDRESS) - self.H.__sendByte__(register) - self.H.__sendByte__(address&0xFF);self.H.__sendByte__((address>>8)&0xFF); - self.H.__sendByte__((address>>16)&0xFF); - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def selectAddress(self,address): - ''' - Sets RX_ADDR_P0 and TX_ADDR to the specified address. - - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITEADDRESSES) - self.H.__sendByte__(address&0xFF);self.H.__sendByte__((address>>8)&0xFF); - self.H.__sendByte__((address>>16)&0xFF); - self.H.__get_ack__() - self.CURRENT_ADDRESS=address - if address not in self.sigs: - self.sigs[address]=1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def read_payload(self,numbytes): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_READPAYLOAD) - self.H.__sendByte__(numbytes) - data=self.H.fd.read(numbytes) - self.H.__get_ack__() - return [ord(a) for a in data] - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_payload(self,data,verbose=False,**args): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITEPAYLOAD) - numbytes=len(data)|0x80 #0x80 implies transmit immediately. Otherwise it will simply load the TX FIFO ( used by ACK_payload) - if(args.get('rxmode',False)):numbytes|=0x40 - self.H.__sendByte__(numbytes) - self.H.__sendByte__(self.TX_PAYLOAD) - for a in data: - self.H.__sendByte__(a) - val=self.H.__get_ack__()>>4 - if(verbose): - if val&0x2: print (' NRF radio not found. Connect one to the add-on port') - elif val&0x1: print (' Node probably dead/out of range. It failed to acknowledge') - return - return val - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def I2C_scan(self): - ''' - Scans the I2C bus and returns a list of live addresses - ''' - x = self.transaction([self.I2C_COMMANDS|self.I2C_SCAN|0x80],timeout=500) - if not x:return [] - if not sum(x):return [] - addrs=[] - for a in range(16): - if(x[a]^255): - for b in range(8): - if x[a]&(0x80>>b)==0: - addr = 8*a+b - addrs.append(addr) - return addrs - - def GuessingScan(self): - ''' - Scans the I2C bus and also prints the possible devices associated with each found address - ''' - import sensorlist - print ('Scanning addresses 0-127...') - x = self.transaction([self.I2C_COMMANDS|self.I2C_SCAN|0x80],timeout=500) - if not x:return [] - if not sum(x):return [] - addrs=[] - print ('Address','\t','Possible Devices') - - for a in range(16): - if(x[a]^255): - for b in range(8): - if x[a]&(0x80>>b)==0: - addr = 8*a+b - addrs.append(addr) - print (hex(addr),'\t\t',sensorlist.sensors.get(addr,'None')) - - return addrs - - def transaction(self,data,**args): - st = time.time() - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_TRANSACTION) - self.H.__sendByte__(len(data)) #total Data bytes coming through - if 'listen' not in args:args['listen']=True - if args.get('listen',False):data[0]|=0x80 # You need this if hardware must wait for a reply - timeout = args.get('timeout',200) - verbose = args.get('verbose',False) - self.H.__sendInt__(timeout) #timeout. - for a in data: - self.H.__sendByte__(a) - - #print ('dt send',time.time()-st,timeout,data[0]&0x80,data) - numbytes=self.H.__getByte__() - #print ('byte 1 in',time.time()-st) - if numbytes: data = self.H.fd.read(numbytes) - else: data=[] - val=self.H.__get_ack__()>>4 - if(verbose): - if val&0x1: print (time.time(),'%s Err. Node not found'%(hex(self.CURRENT_ADDRESS))) - if val&0x2: print (time.time(),'%s Err. NRF on-board transmitter not found'%(hex(self.CURRENT_ADDRESS))) - if val&0x4 and args['listen']: print (time.time(),'%s Err. Node received command but did not reply'%(hex(self.CURRENT_ADDRESS))) - if val&0x7: #Something didn't go right. - self.flush() - self.sigs[self.CURRENT_ADDRESS] = self.sigs[self.CURRENT_ADDRESS]*50/51. - return False - - self.sigs[self.CURRENT_ADDRESS] = (self.sigs[self.CURRENT_ADDRESS]*50+1)/51. - return [ord(a) for a in data] - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def transactionWithRetries(self,data,**args): - retries = args.get('retries',5) - reply=False - while retries>0: - reply = self.transaction(data,verbose=(retries==1),**args) - if reply: - break - retries-=1 - return reply - - def write_ack_payload(self,data,pipe): - if(len(data)!=self.ACK_PAYLOAD_SIZE): - self.ACK_PAYLOAD_SIZE=len(data) - if self.ACK_PAYLOAD_SIZE>15: - print ('too large. truncating.') - self.ACK_PAYLOAD_SIZE=15 - data=data[:15] - else: - print ('ack payload size:',self.ACK_PAYLOAD_SIZE) - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_WRITEPAYLOAD) - self.H.__sendByte__(len(data)) - self.H.__sendByte__(self.ACK_PAYLOAD|pipe) - for a in data: - self.H.__sendByte__(a) - return self.H.__get_ack__()>>4 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - - def start_token_manager(self): - ''' - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_START_TOKEN_MANAGER) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def stop_token_manager(self): - ''' - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_STOP_TOKEN_MANAGER) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def total_tokens(self): - ''' - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_TOTAL_TOKENS) - x = self.H.__getByte__() - self.H.__get_ack__() - return x - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def fetch_report(self,num): - ''' - ''' - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_REPORTS) - self.H.__sendByte__(num) - data = [self.H.__getByte__() for a in range(20)] - self.H.__get_ack__() - return data - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - - def __decode_I2C_list__(self,data): - lst=[] - if sum(data)==0: - return lst - for a in range(len(data)): - if(data[a]^255): - for b in range(8): - if data[a]&(0x80>>b)==0: - addr = 8*a+b - lst.append(addr) - return lst - - def get_nodelist(self): - ''' - Refer to the variable 'nodelist' if you simply want a list of nodes that either registered while your code was - running , or were loaded from the firmware buffer(max 15 entries) - - If you plan to use more than 15 nodes, and wish to register their addresses without having to feed them manually, - then this function must be called each time before the buffer resets. - - The dictionary object returned by this function [addresses paired with arrays containing their registered sensors] - is filtered by checking with each node if they are alive. - - ''' - - total = self.total_tokens() - if self.nodepos!=total: - for nm in range(self.NODELIST_MAXLENGTH): - dat = self.fetch_report(nm) - txrx=(dat[0])|(dat[1]<<8)|(dat[2]<<16) - if not txrx:continue - self.nodelist[txrx]=self.__decode_I2C_list__(dat[3:19]) - self.nodepos=total - #else: - # self.__delete_registered_node__(nm) - - filtered_lst={} - for a in self.nodelist: - if self.isAlive(a): filtered_lst[a]=self.nodelist[a] - - return filtered_lst - - def __delete_registered_node__(self,num): - try: - self.H.__sendByte__(CP.NRFL01) - self.H.__sendByte__(CP.NRF_DELETE_REPORT_ROW) - self.H.__sendByte__(num) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __delete_all_registered_nodes__(self): - while self.total_tokens(): - print ('-') - self.__delete_registered_node__(0) - - def isAlive(self,addr): - self.selectAddress(addr) - return self.transaction([self.NRF_COMMANDS|self.NRF_READ_REGISTER]+[self.R_STATUS],timeout=100,verbose=False) - - def init_shockburst_transmitter(self,**args): - ''' - Puts the radio into transmit mode. - Dynamic Payload with auto acknowledge is enabled. - upto 5 retransmits with 1ms delay between each in case a node doesn't respond in time - Receivers must acknowledge payloads - ''' - self.PAYLOAD_SIZE=args.get('PAYLOAD_SIZE',self.PAYLOAD_SIZE) - myaddr=args.get('myaddr',0xAAAA01) - sendaddr=args.get('sendaddr',0xAAAA01) - - self.init() - #shockburst - self.write_address(self.RX_ADDR_P0,myaddr) #transmitter's address - self.write_address(self.TX_ADDR,sendaddr) #send to node with this address - self.write_register(self.RX_PW_P0,self.PAYLOAD_SIZE) - self.rxmode() - time.sleep(0.1) - self.flush() - - def init_shockburst_receiver(self,**args): - ''' - Puts the radio into receive mode. - Dynamic Payload with auto acknowledge is enabled. - ''' - self.PAYLOAD_SIZE=args.get('PAYLOAD_SIZE',self.PAYLOAD_SIZE) - if 'myaddr0' not in args: - args['myaddr0']=0xA523B5 - #if 'sendaddr' non in args: - # args['sendaddr']=0xA523B5 - print (args) - self.init() - self.write_register(self.RF_SETUP,0x26) #2MBPS speed - - #self.write_address(self.TX_ADDR,sendaddr) #send to node with this address - #self.write_address(self.RX_ADDR_P0,myaddr) #will receive the ACK Payload from that node - enabled_pipes = 0 #pipes to be enabled - for a in range(0,6): - x=args.get('myaddr'+str(a),None) - if x: - print (hex(x),hex(self.RX_ADDR_P0+a)) - enabled_pipes|= (1<> 4 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def hasData(self): + ''' + Check if the RX FIFO contains data + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_HASDATA) + value = self.H.__getByte__() + self.H.__get_ack__() + return value + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def flush(self): + ''' + Flushes the TX and RX FIFOs + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_FLUSH) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_register(self, address, value): + ''' + write a byte to any of the configuration registers on the Radio. + address byte can either be located in the NRF24L01+ manual, or chosen + from some of the constants defined in this module. + ''' + # print ('writing',address,value) + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITEREG) + self.H.__sendByte__(address) + self.H.__sendByte__(value) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def read_register(self, address): + ''' + Read the value of any of the configuration registers on the radio module. + + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_READREG) + self.H.__sendByte__(address) + val = self.H.__getByte__() + self.H.__get_ack__() + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_status(self): + ''' + Returns a byte representing the STATUS register on the radio. + Refer to NRF24L01+ documentation for further details + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_GETSTATUS) + val = self.H.__getByte__() + self.H.__get_ack__() + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_command(self, cmd): + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITECOMMAND) + self.H.__sendByte__(cmd) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_address(self, register, address): + ''' + register can be TX_ADDR, RX_ADDR_P0 -> RX_ADDR_P5 + 3 byte address. eg 0xFFABXX . XX cannot be FF + if RX_ADDR_P1 needs to be used along with any of the pipes + from P2 to P5, then RX_ADDR_P1 must be updated last. + Addresses from P1-P5 must share the first two bytes. + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITEADDRESS) + self.H.__sendByte__(register) + self.H.__sendByte__(address & 0xFF) + self.H.__sendByte__((address >> 8) & 0xFF) + self.H.__sendByte__((address >> 16) & 0xFF) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def selectAddress(self, address): + ''' + Sets RX_ADDR_P0 and TX_ADDR to the specified address. + + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITEADDRESSES) + self.H.__sendByte__(address & 0xFF) + self.H.__sendByte__((address >> 8) & 0xFF) + self.H.__sendByte__((address >> 16) & 0xFF) + self.H.__get_ack__() + self.CURRENT_ADDRESS = address + if address not in self.sigs: + self.sigs[address] = 1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def read_payload(self, numbytes): + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_READPAYLOAD) + self.H.__sendByte__(numbytes) + data = self.H.fd.read(numbytes) + self.H.__get_ack__() + return [ord(a) for a in data] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_payload(self, data, verbose=False, **args): + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITEPAYLOAD) + numbytes = len( + data) | 0x80 # 0x80 implies transmit immediately. Otherwise it will simply load the TX FIFO ( used by ACK_payload) + if (args.get('rxmode', False)): numbytes |= 0x40 + self.H.__sendByte__(numbytes) + self.H.__sendByte__(self.TX_PAYLOAD) + for a in data: + self.H.__sendByte__(a) + val = self.H.__get_ack__() >> 4 + if (verbose): + if val & 0x2: + print(' NRF radio not found. Connect one to the add-on port') + elif val & 0x1: + print(' Node probably dead/out of range. It failed to acknowledge') + return + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def I2C_scan(self): + ''' + Scans the I2C bus and returns a list of live addresses + ''' + x = self.transaction([self.I2C_COMMANDS | self.I2C_SCAN | 0x80], timeout=500) + if not x: return [] + if not sum(x): return [] + addrs = [] + for a in range(16): + if (x[a] ^ 255): + for b in range(8): + if x[a] & (0x80 >> b) == 0: + addr = 8 * a + b + addrs.append(addr) + return addrs + + def GuessingScan(self): + ''' + Scans the I2C bus and also prints the possible devices associated with each found address + ''' + from PSL import sensorlist + print('Scanning addresses 0-127...') + x = self.transaction([self.I2C_COMMANDS | self.I2C_SCAN | 0x80], timeout=500) + if not x: return [] + if not sum(x): return [] + addrs = [] + print('Address', '\t', 'Possible Devices') + + for a in range(16): + if (x[a] ^ 255): + for b in range(8): + if x[a] & (0x80 >> b) == 0: + addr = 8 * a + b + addrs.append(addr) + print(hex(addr), '\t\t', sensorlist.sensors.get(addr, 'None')) + + return addrs + + def transaction(self, data, **args): + st = time.time() + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_TRANSACTION) + self.H.__sendByte__(len(data)) # total Data bytes coming through + if 'listen' not in args: args['listen'] = True + if args.get('listen', False): data[0] |= 0x80 # You need this if hardware must wait for a reply + timeout = args.get('timeout', 200) + verbose = args.get('verbose', False) + self.H.__sendInt__(timeout) # timeout. + for a in data: + self.H.__sendByte__(a) + + # print ('dt send',time.time()-st,timeout,data[0]&0x80,data) + numbytes = self.H.__getByte__() + # print ('byte 1 in',time.time()-st) + if numbytes: + data = self.H.fd.read(numbytes) + else: + data = [] + val = self.H.__get_ack__() >> 4 + if (verbose): + if val & 0x1: print(time.time(), '%s Err. Node not found' % (hex(self.CURRENT_ADDRESS))) + if val & 0x2: print(time.time(), + '%s Err. NRF on-board transmitter not found' % (hex(self.CURRENT_ADDRESS))) + if val & 0x4 and args['listen']: print(time.time(), + '%s Err. Node received command but did not reply' % ( + hex(self.CURRENT_ADDRESS))) + if val & 0x7: # Something didn't go right. + self.flush() + self.sigs[self.CURRENT_ADDRESS] = self.sigs[self.CURRENT_ADDRESS] * 50 / 51. + return False + + self.sigs[self.CURRENT_ADDRESS] = (self.sigs[self.CURRENT_ADDRESS] * 50 + 1) / 51. + return [ord(a) for a in data] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def transactionWithRetries(self, data, **args): + retries = args.get('retries', 5) + reply = False + while retries > 0: + reply = self.transaction(data, verbose=(retries == 1), **args) + if reply: + break + retries -= 1 + return reply + + def write_ack_payload(self, data, pipe): + if (len(data) != self.ACK_PAYLOAD_SIZE): + self.ACK_PAYLOAD_SIZE = len(data) + if self.ACK_PAYLOAD_SIZE > 15: + print('too large. truncating.') + self.ACK_PAYLOAD_SIZE = 15 + data = data[:15] + else: + print('ack payload size:', self.ACK_PAYLOAD_SIZE) + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_WRITEPAYLOAD) + self.H.__sendByte__(len(data)) + self.H.__sendByte__(self.ACK_PAYLOAD | pipe) + for a in data: + self.H.__sendByte__(a) + return self.H.__get_ack__() >> 4 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start_token_manager(self): + ''' + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_START_TOKEN_MANAGER) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def stop_token_manager(self): + ''' + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_STOP_TOKEN_MANAGER) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def total_tokens(self): + ''' + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_TOTAL_TOKENS) + x = self.H.__getByte__() + self.H.__get_ack__() + return x + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def fetch_report(self, num): + ''' + ''' + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_REPORTS) + self.H.__sendByte__(num) + data = [self.H.__getByte__() for a in range(20)] + self.H.__get_ack__() + return data + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __decode_I2C_list__(self, data): + lst = [] + if sum(data) == 0: + return lst + for a in range(len(data)): + if (data[a] ^ 255): + for b in range(8): + if data[a] & (0x80 >> b) == 0: + addr = 8 * a + b + lst.append(addr) + return lst + + def get_nodelist(self): + ''' + Refer to the variable 'nodelist' if you simply want a list of nodes that either registered while your code was + running , or were loaded from the firmware buffer(max 15 entries) + + If you plan to use more than 15 nodes, and wish to register their addresses without having to feed them manually, + then this function must be called each time before the buffer resets. + + The dictionary object returned by this function [addresses paired with arrays containing their registered sensors] + is filtered by checking with each node if they are alive. + + ''' + + total = self.total_tokens() + if self.nodepos != total: + for nm in range(self.NODELIST_MAXLENGTH): + dat = self.fetch_report(nm) + txrx = (dat[0]) | (dat[1] << 8) | (dat[2] << 16) + if not txrx: continue + self.nodelist[txrx] = self.__decode_I2C_list__(dat[3:19]) + self.nodepos = total + # else: + # self.__delete_registered_node__(nm) + + filtered_lst = {} + for a in self.nodelist: + if self.isAlive(a): filtered_lst[a] = self.nodelist[a] + + return filtered_lst + + def __delete_registered_node__(self, num): + try: + self.H.__sendByte__(CP.NRFL01) + self.H.__sendByte__(CP.NRF_DELETE_REPORT_ROW) + self.H.__sendByte__(num) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __delete_all_registered_nodes__(self): + while self.total_tokens(): + print('-') + self.__delete_registered_node__(0) + + def isAlive(self, addr): + self.selectAddress(addr) + return self.transaction([self.NRF_COMMANDS | self.NRF_READ_REGISTER] + [self.R_STATUS], timeout=100, + verbose=False) + + def init_shockburst_transmitter(self, **args): + ''' + Puts the radio into transmit mode. + Dynamic Payload with auto acknowledge is enabled. + upto 5 retransmits with 1ms delay between each in case a node doesn't respond in time + Receivers must acknowledge payloads + ''' + self.PAYLOAD_SIZE = args.get('PAYLOAD_SIZE', self.PAYLOAD_SIZE) + myaddr = args.get('myaddr', 0xAAAA01) + sendaddr = args.get('sendaddr', 0xAAAA01) + + self.init() + # shockburst + self.write_address(self.RX_ADDR_P0, myaddr) # transmitter's address + self.write_address(self.TX_ADDR, sendaddr) # send to node with this address + self.write_register(self.RX_PW_P0, self.PAYLOAD_SIZE) + self.rxmode() + time.sleep(0.1) + self.flush() + + def init_shockburst_receiver(self, **args): + ''' + Puts the radio into receive mode. + Dynamic Payload with auto acknowledge is enabled. + ''' + self.PAYLOAD_SIZE = args.get('PAYLOAD_SIZE', self.PAYLOAD_SIZE) + if 'myaddr0' not in args: + args['myaddr0'] = 0xA523B5 + # if 'sendaddr' non in args: + # args['sendaddr']=0xA523B5 + print(args) + self.init() + self.write_register(self.RF_SETUP, 0x26) # 2MBPS speed + + # self.write_address(self.TX_ADDR,sendaddr) #send to node with this address + # self.write_address(self.RX_ADDR_P0,myaddr) #will receive the ACK Payload from that node + enabled_pipes = 0 # pipes to be enabled + for a in range(0, 6): + x = args.get('myaddr' + str(a), None) + if x: + print(hex(x), hex(self.RX_ADDR_P0 + a)) + enabled_pipes |= (1 << a) + self.write_address(self.RX_ADDR_P0 + a, x) + P15_base_address = args.get('myaddr1', None) + if P15_base_address: self.write_address(self.RX_ADDR_P1, P15_base_address) + + self.write_register(self.EN_RXADDR, enabled_pipes) # enable pipes + self.write_register(self.EN_AA, enabled_pipes) # enable auto Acknowledge on all pipes + self.write_register(self.DYNPD, enabled_pipes) # enable dynamic payload on Data pipes + self.write_register(self.FEATURE, 0x06) # enable dynamic payload length + # self.write_register(self.RX_PW_P0,self.PAYLOAD_SIZE) + + self.rxmode() + time.sleep(0.1) + self.flush() -class RadioLink(): - ADC_COMMANDS =1 - READ_ADC =0<<4 - - I2C_COMMANDS =2 - I2C_TRANSACTION =0<<4 - I2C_WRITE =1<<4 - SCAN_I2C =2<<4 - PULL_SCL_LOW = 3<<4 - I2C_CONFIG = 4<<4 - I2C_READ = 5<<4 - - NRF_COMMANDS = 3 - NRF_READ_REGISTER =0<<4 - NRF_WRITE_REGISTER =1<<4 - - MISC_COMMANDS = 4 - WS2812B_CMD = 0<<4 - - def __init__(self,NRF,**args): - self.NRF = NRF - if 'address' in args: - self.ADDRESS = args.get('address',False) - else: - print ('Address not specified. Add "address=0x....." argument while instantiating') - self.ADDRESS=0x010101 - - - def __selectMe__(self): - if self.NRF.CURRENT_ADDRESS!=self.ADDRESS: - self.NRF.selectAddress(self.ADDRESS) - - - def I2C_scan(self): - self.__selectMe__() - import sensorlist - print ('Scanning addresses 0-127...') - x = self.NRF.transaction([self.I2C_COMMANDS|self.SCAN_I2C|0x80],timeout=500) - if not x:return [] - if not sum(x):return [] - addrs=[] - print ('Address','\t','Possible Devices') - - for a in range(16): - if(x[a]^255): - for b in range(8): - if x[a]&(0x80>>b)==0: - addr = 8*a+b - addrs.append(addr) - print (hex(addr),'\t\t',sensorlist.sensors.get(addr,'None')) - - return addrs - - - def __decode_I2C_list__(self,data): - lst=[] - if sum(data)==0: - return lst - for a in range(len(data)): - if(data[a]^255): - for b in range(8): - if data[a]&(0x80>>b)==0: - addr = 8*a+b - lst.append(addr) - return lst - - def writeI2C(self,I2C_addr,regaddress,bytes): - self.__selectMe__() - return self.NRF.transaction([self.I2C_COMMANDS|self.I2C_WRITE]+[I2C_addr]+[regaddress]+bytes) - - def readI2C(self,I2C_addr,regaddress,numbytes): - self.__selectMe__() - return self.NRF.transaction([self.I2C_COMMANDS|self.I2C_TRANSACTION]+[I2C_addr]+[regaddress]+[numbytes]) - - def writeBulk(self,I2C_addr,bytes): - self.__selectMe__() - return self.NRF.transaction([self.I2C_COMMANDS|self.I2C_WRITE]+[I2C_addr]+bytes) - - def readBulk(self,I2C_addr,regaddress,numbytes): - self.__selectMe__() - return self.NRF.transactionWithRetries([self.I2C_COMMANDS|self.I2C_TRANSACTION]+[I2C_addr]+[regaddress]+[numbytes]) - - def simpleRead(self,I2C_addr,numbytes): - self.__selectMe__() - return self.NRF.transactionWithRetries([self.I2C_COMMANDS|self.I2C_READ]+[I2C_addr]+[numbytes]) - - - def readADC(self,channel): - self.__selectMe__() - return self.NRF.transaction([self.ADC_COMMANDS|self.READ_ADC]+[channel]) - - def pullSCLLow(self,t_ms): - self.__selectMe__() - dat=self.NRF.transaction([self.I2C_COMMANDS|self.PULL_SCL_LOW]+[t_ms]) - if dat: - return self.__decode_I2C_list__(dat) - else: - return [] - - def configI2C(self,freq): - self.__selectMe__() - brgval=int(32e6/freq/4 - 1) - print (brgval) - return self.NRF.transaction([self.I2C_COMMANDS|self.I2C_CONFIG]+[brgval],listen=False) - - def write_register(self,reg,val): - self.__selectMe__() - #print ('writing to ',reg,val) - return self.NRF.transaction([self.NRF_COMMANDS|self.NRF_WRITE_REGISTER]+[reg,val],listen=False) - - - - def WS2812B(self,cols): - """ - set shade of WS2182 LED on CS1/RC0 - - .. tabularcolumns:: |p{3cm}|p{11cm}| - - ============== ============================================================================================ - **Arguments** - ============== ============================================================================================ - cols 2Darray [[R,G,B],[R2,G2,B2],[R3,G3,B3]...] - brightness of R,G,B ( 0-255 ) - ============== ============================================================================================ - - example:: - - >>> WS2812B([[10,0,0],[0,10,10],[10,0,10]]) - #sets red, cyan, magenta to three daisy chained LEDs - - """ - self.__selectMe__() - colarray=[] - for a in cols: - colarray.append(int('{:08b}'.format(int(a[1]))[::-1], 2)) - colarray.append(int('{:08b}'.format(int(a[0]))[::-1], 2)) - colarray.append(int('{:08b}'.format(int(a[2]))[::-1], 2)) - - - res = self.NRF.transaction([self.MISC_COMMANDS|self.WS2812B_CMD]+colarray,listen=False) - return res - - - def read_register(self,reg): - self.__selectMe__() - x=self.NRF.transaction([self.NRF_COMMANDS|self.NRF_READ_REGISTER]+[reg]) - if x: - return x[0] - else: - return False - +class RadioLink(): + ADC_COMMANDS = 1 + READ_ADC = 0 << 4 + + I2C_COMMANDS = 2 + I2C_TRANSACTION = 0 << 4 + I2C_WRITE = 1 << 4 + SCAN_I2C = 2 << 4 + PULL_SCL_LOW = 3 << 4 + I2C_CONFIG = 4 << 4 + I2C_READ = 5 << 4 + + NRF_COMMANDS = 3 + NRF_READ_REGISTER = 0 << 4 + NRF_WRITE_REGISTER = 1 << 4 + + MISC_COMMANDS = 4 + WS2812B_CMD = 0 << 4 + + def __init__(self, NRF, **args): + self.NRF = NRF + if 'address' in args: + self.ADDRESS = args.get('address', False) + else: + print('Address not specified. Add "address=0x....." argument while instantiating') + self.ADDRESS = 0x010101 + + def __selectMe__(self): + if self.NRF.CURRENT_ADDRESS != self.ADDRESS: + self.NRF.selectAddress(self.ADDRESS) + + def I2C_scan(self): + self.__selectMe__() + from PSL import sensorlist + print('Scanning addresses 0-127...') + x = self.NRF.transaction([self.I2C_COMMANDS | self.SCAN_I2C | 0x80], timeout=500) + if not x: return [] + if not sum(x): return [] + addrs = [] + print('Address', '\t', 'Possible Devices') + + for a in range(16): + if (x[a] ^ 255): + for b in range(8): + if x[a] & (0x80 >> b) == 0: + addr = 8 * a + b + addrs.append(addr) + print(hex(addr), '\t\t', sensorlist.sensors.get(addr, 'None')) + + return addrs + + def __decode_I2C_list__(self, data): + lst = [] + if sum(data) == 0: + return lst + for a in range(len(data)): + if (data[a] ^ 255): + for b in range(8): + if data[a] & (0x80 >> b) == 0: + addr = 8 * a + b + lst.append(addr) + return lst + + def writeI2C(self, I2C_addr, regaddress, bytes): + self.__selectMe__() + return self.NRF.transaction([self.I2C_COMMANDS | self.I2C_WRITE] + [I2C_addr] + [regaddress] + bytes) + + def readI2C(self, I2C_addr, regaddress, numbytes): + self.__selectMe__() + return self.NRF.transaction([self.I2C_COMMANDS | self.I2C_TRANSACTION] + [I2C_addr] + [regaddress] + [numbytes]) + + def writeBulk(self, I2C_addr, bytes): + self.__selectMe__() + return self.NRF.transaction([self.I2C_COMMANDS | self.I2C_WRITE] + [I2C_addr] + bytes) + + def readBulk(self, I2C_addr, regaddress, numbytes): + self.__selectMe__() + return self.NRF.transactionWithRetries( + [self.I2C_COMMANDS | self.I2C_TRANSACTION] + [I2C_addr] + [regaddress] + [numbytes]) + + def simpleRead(self, I2C_addr, numbytes): + self.__selectMe__() + return self.NRF.transactionWithRetries([self.I2C_COMMANDS | self.I2C_READ] + [I2C_addr] + [numbytes]) + + def readADC(self, channel): + self.__selectMe__() + return self.NRF.transaction([self.ADC_COMMANDS | self.READ_ADC] + [channel]) + + def pullSCLLow(self, t_ms): + self.__selectMe__() + dat = self.NRF.transaction([self.I2C_COMMANDS | self.PULL_SCL_LOW] + [t_ms]) + if dat: + return self.__decode_I2C_list__(dat) + else: + return [] + + def configI2C(self, freq): + self.__selectMe__() + brgval = int(32e6 / freq / 4 - 1) + print(brgval) + return self.NRF.transaction([self.I2C_COMMANDS | self.I2C_CONFIG] + [brgval], listen=False) + + def write_register(self, reg, val): + self.__selectMe__() + # print ('writing to ',reg,val) + return self.NRF.transaction([self.NRF_COMMANDS | self.NRF_WRITE_REGISTER] + [reg, val], listen=False) + + def WS2812B(self, cols): + """ + set shade of WS2182 LED on CS1/RC0 + + .. tabularcolumns:: |p{3cm}|p{11cm}| + + ============== ============================================================================================ + **Arguments** + ============== ============================================================================================ + cols 2Darray [[R,G,B],[R2,G2,B2],[R3,G3,B3]...] + brightness of R,G,B ( 0-255 ) + ============== ============================================================================================ + + example:: + + >>> WS2812B([[10,0,0],[0,10,10],[10,0,10]]) + #sets red, cyan, magenta to three daisy chained LEDs + + """ + self.__selectMe__() + colarray = [] + for a in cols: + colarray.append(int('{:08b}'.format(int(a[1]))[::-1], 2)) + colarray.append(int('{:08b}'.format(int(a[0]))[::-1], 2)) + colarray.append(int('{:08b}'.format(int(a[2]))[::-1], 2)) + + res = self.NRF.transaction([self.MISC_COMMANDS | self.WS2812B_CMD] + colarray, listen=False) + return res + + def read_register(self, reg): + self.__selectMe__() + x = self.NRF.transaction([self.NRF_COMMANDS | self.NRF_READ_REGISTER] + [reg]) + if x: + return x[0] + else: + return False diff --git a/PSL/README.md b/PSL/README.md new file mode 100644 index 00000000..5fef3170 --- /dev/null +++ b/PSL/README.md @@ -0,0 +1,1306 @@ +### Documentation of back-end functions used in PSLab, taken from [sciencelab.py](https://github.com/fossasia/pslab-python/blob/development/PSL/sciencelab.py) + +Sciencelab class contains methods that can be used to interact with the FOSSASIA PSLab. Run the following code to connect: + + >>> from PSL import sciencelab + >>> I = sciencelab.connect() + >>> self.__print__(I) + + +Once you have initiated this class, its various methods will allow access to all the features built into the device. + +
+get_resistance(self)
+
+ +
+get_version(self) + ++ Returns the version string of the device, format: LTS-...... + +
+ +
+getRadioLinks(self)
+
+ +
+newRadioLink(self, **args) + ++ Arguments + + \*\*Kwargs: Keyword Arguments + + address: Address of the node. a 24 bit number. Printed on the nodes. Can also be retrieved using :py:meth:`~NRF24L01_class.NRF24L01.get_nodelist` ++ Return: :py:meth:`~NRF_NODE.RadioLink` + +
+ +## ANALOG SECTION + +This section has commands related to analog measurement and control. These include the oscilloscope routines, voltmeters, ammeters, and Programmable voltage sources. + +
+reconnect(self, **kwargs) + ++ Attempts to reconnect to the device in case of a commmunication error or accidental disconnect. + +
+ +
+capture1(self, ch, ns, tg, *args, **kwargs) + ++ Blocking call that fetches an oscilloscope trace from the specified input channel ++ Arguments + + ch: Channel to select as input. ['CH1'..'CH3','SEN'] + + ns: Number of samples to fetch. Maximum 10000 + + tg: Timegap between samples in microseconds ++ Return: Arrays X(timestamps),Y(Corresponding Voltage values) + +``` +>>> from pylab import * +>>> from PSL import sciencelab +>>> I = sciencelab.connect() +>>> x,y = I.capture1('CH1',3200,1) +>>> plot(x,y) +>>> show() +``` + +
+ +
+capture2(self, ns, tg, TraceOneRemap = 'CH1') + ++ Blocking call that fetches oscilloscope traces from CH1,CH2 ++ Arguments + + ns: Number of samples to fetch. Maximum 5000 + + tg: Timegap between samples in microseconds + + TraceOneRemap: Choose the analog input for channel 1. It is connected to CH1 by default. Channel 2 always reads CH2. ++ Return: Arrays X(timestamps),Y1(Voltage at CH1),Y2(Voltage at CH2) + +``` +>>> from pylab import * +>>> from PSL import sciencelab +>>> I = sciencelab.connect() +>>> x,y1,y2 = I.capture2(1600,2,'MIC') #Chan1 remapped to MIC. Chan2 reads CH2 +>>> plot(x,y1) #Plot of analog input MIC +>>> plot(x,y2) #plot of analog input CH2 +>>> show() +``` + +
+ +
+capture4(self, ns, tg, TraceOneRemap = 'CH1') + ++ Blocking call that fetches oscilloscope traces from CH1,CH2 ++ Arguments + + ns: Number of samples to fetch. Maximum 2500 + + tg: Timegap between samples in microseconds. Minimum 1.75uS + + TraceOneRemap: Choose the analog input for channel 1. It is connected to CH1 by default. Channel 2 always reads CH2. ++ Return: Arrays X(timestamps),Y1(Voltage at CH1),Y2(Voltage at CH2),Y3(Voltage at CH3),Y4(Voltage at CH4) + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> x,y1,y2,y3,y4 = I.capture4(800,1.75) +>>> plot(x,y1) +>>> plot(x,y2) +>>> plot(x,y3) +>>> plot(x,y4) +>>> show() +``` + +
+ +
+capture_multiple(self, samples, tg, *args) + ++ Blocking call that fetches oscilloscope traces from a set of specified channels ++ Arguments + + samples: Number of samples to fetch. Maximum 10000/(total specified channels) + + tg: Timegap between samples in microseconds. + + \*args: Channel names ++ Return: Arrays X(timestamps),Y1,Y2 ... + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> x,y1,y2,y3,y4 = I.capture_multiple(800,1.75,'CH1','CH2','MIC','SEN') +>>> plot(x,y1) +>>> plot(x,y2) +>>> plot(x,y3) +>>> plot(x,y4) +>>> show() +``` + +
+ +
+capture_fullspeed(self, chan, samples, tg, *args, **kwargs) + ++ Blocking call that fetches oscilloscope traces from a single oscilloscope channel at a maximum speed of 2MSPS + ++ Arguments + + chan: Channel name 'CH1' / 'CH2' ... 'SEN' + + samples: Number of samples to fetch. Maximum 10000/(total specified channels) + + \*args: Specify if SQR1 must be toggled right before capturing. + + 'SET_LOW': Set SQR1 to 0V + + 'SET_HIGH': Set SQR1 to 1V + + 'FIRE_PULSES': output a preset frequency on SQR1 for a given interval (keyword arg 'interval' must be specified or it will default to 1000uS) before acquiring data. This is used for measuring speed of sound using piezos if no arguments are specified, a regular capture will be executed. + + \*\*kwargs + + interval: Units: uS. Necessary if 'FIRE_PULSES' argument was supplied. Default 1000uS ++ Return: timestamp array ,voltage_value array + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> x,y = I.capture_fullspeed('CH1',2000,1) +>>> plot(x,y) +>>> show() +``` + +``` +>>> x,y = I.capture_fullspeed('CH1',2000,1,'SET_LOW') +>>> plot(x,y) +>>> show() +``` + +``` +>>> I.sqr1(40e3 , 50, True) # Prepare a 40KHz, 50% square wave. Do not output it yet +>>> x,y = I.capture_fullspeed('CH1',2000,1,'FIRE_PULSES',interval = 250) #Output the prepared 40KHz(25uS) wave for 250uS(10 cycles) before acquisition +>>> plot(x,y) +>>> show() +``` + +
+ +
+capture_fullspeed_hr(self, chan, samples, tg, *args) +
+ +
+capture_traces(self, num, samples, tg, channel_one_input = 'CH1', CH123SA = 0, *kwargs) + ++ Instruct the ADC to start sampling. use fetch_trace to retrieve the data + ++ Arguments + + num: Channels to acquire. 1/2/4 + + samples: Total points to store per channel. Maximum 3200 total. + + tg: Timegap between two successive samples (in uSec) + + channel_one_input: Map channel 1 to 'CH1' ... 'CH9' + + \*\*kwargs + + \*trigger: Whether or not to trigger the oscilloscope based on the voltage level set by :func:`configure_trigger` ++ Return: nothing + +The following example demonstrates how to use this function to record active events. + ++ Connect a capacitor and an Inductor in series. ++ Connect CH1 to the spare leg of the inductor. Also Connect OD1 to this point ++ Connect CH2 to the junction between the capacitor and the inductor ++ Connect the spare leg of the capacitor to GND( ground ) ++ Set OD1 initially high using set_state(SQR1 = 1) + +``` +>>> I.set_state(OD1 = 1) #Turn on OD1 +#Arbitrary delay to wait for stabilization +>>> time.sleep(0.5) +#Start acquiring data (2 channels,800 samples, 2microsecond intervals) +>>> I.capture_traces(2,800,2,trigger = False) +#Turn off OD1. This must occur immediately after the previous line was executed. +>>> I.set_state(OD1 = 0) +#Minimum interval to wait for completion of data acquisition. +#samples*timegap*(convert to Seconds) +>>> time.sleep(800*2*1e-6) +>>> x,CH1 = I.fetch_trace(1) +>>> x,CH2 = I.fetch_trace(2) +>>> plot(x,CH1-CH2) #Voltage across the inductor +>>> plot(x,CH2) ##Voltage across the capacitor +>>> show() +``` + +The following events take place when the above snippet runs + ++ The oscilloscope starts storing voltages present at CH1 and CH2 every 2 microseconds ++ The output OD1 was enabled, and this causes the voltage between the L and C to approach OD1 voltage. (It may or may not oscillate) ++ The data from CH1 and CH2 was read into x,CH1,CH2 ++ Both traces were plotted in order to visualize the Transient response of series LC + +
+ +
+capture_highres_traces(self, channel, samples, tg, **kwargs) + ++ Instruct the ADC to start sampling. Use fetch_trace to retrieve the data + ++ Arguments + + channel: Channel to acquire data from 'CH1' ... 'CH9' + + samples: Total points to store per channel. Maximum 3200 total. + + tg : Timegap between two successive samples (in uSec) + + \*\*kwargs + + \*trigger : Whether or not to trigger the oscilloscope based on the voltage level set by :func:`configure_trigger` ++ Return: nothing + +
+ +
+fetch_trace(self, channel_number) + ++ Fetches a channel(1-4) captured by :func:`capture_traces` called prior to this, and returns xaxis,yaxis + ++ Arguments + + channel_number: Any of the maximum of four channels that the oscilloscope captured. 1/2/3/4 ++ Return: time array,voltage array + +
+ +
+oscilloscope_progress(self) + ++ Returns the number of samples acquired by the capture routines, and the conversion_done status ++ Return: conversion done(bool) ,samples acquired (number) + +``` +>>> I.start_capture(1,3200,2) +>>> self.__print__(I.oscilloscope_progress()) +(0,46) +>>> time.sleep(3200*2e-6) +>>> self.__print__(I.oscilloscope_progress()) +(1,3200) +``` + +
+ +
+configure_trigger(self, chan, name, voltage, resolution = 10, **kwargs) + ++ Configure trigger parameters for 10-bit capture commands ++ The capture routines will wait till a rising edge of the input signal crosses the specified level. ++ The trigger will timeout within 8mS, and capture routines will start regardless. ++ These settings will not be used if the trigger option in the capture routines are set to False ++ Arguments + + chan: Channel 0,1,2,3. Corresponding to the channels being recorded by the capture routine(not the analog inputs) + + name: Name of the channel. 'CH1'... 'V+' + + voltage: The voltage level that should trigger the capture sequence(in Volts) ++ Return: Nothing + +``` +>>> I.configure_trigger(0,'CH1',1.1) +>>> I.capture_traces(4,800,2) +#Unless a timeout occured, the first point of this channel will be close to 1.1Volts +>>> I.fetch_trace(1) +#This channel was acquired simultaneously with channel 1, +#So it's triggered along with the first +>>> I.fetch_trace(2) +``` + +
+ +
+set_gain(self, channel, gain, Force = False) + ++ Set the gain of the selected PGA ++ Arguments + + channel: 'CH1','CH2' + + gain: (0-8) -> (1x,2x,4x,5x,8x,10x,16x,32x,1/11x) + + Force: If True, the amplifier gain will be set even if it was previously set to the same value. + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> I.set_gain('CH1',7) #gain set to 32x on CH1 +``` + +**Note**: The gain value applied to a channel will result in better resolution for small amplitude signals. However, values read using functions like :func:`get_average_voltage` or func:`capture_traces` will not be 2x, or 4x times the input signal. These are calibrated to return accurate values of the original input signal. In case the gain specified is 8 (1/11x) , an external 10MOhm resistor must be connected in series with the device. The input range will be +/-160 Volts + +
+ +
+select_range(self, channel, voltage_range) + ++ set the gain of the selected PGA ++ Arguments + + channel: 'CH1','CH2' + + voltage_range: Choose from [16,8,4,3,2,1.5,1,.5,160] + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> I.select_range('CH1',8) #gain set to 2x on CH1. Voltage range +/-8V +``` + +**Note**: Setting the right voltage range will result in better resolution. In case the range specified is 160 , an external 10MOhm resistor must be connected in series with the device. This function internally calls `set_gain` with the appropriate gain value + +
+ +
+get_voltage(self, channel_name, **kwargs)
+
+ +
+voltmeter_autorange(self, channel_name)
+
+ +
+get_average_voltage(self, channel_name, **kwargs) + ++ Return the voltage on the selected channel ++ Arguments + + channel_name : 'CH1','CH2','CH3', 'MIC','IN1','SEN','V+' + + sleep: Read voltage in CPU sleep mode. not particularly useful. Also, Buggy. + + \*\*kwargs: Samples to average can be specified. Eg, samples=100 will average a hundred readings + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> self.__print__(I.get_average_voltage('CH4')) +1.002 +``` + +
+ +
+fetch_buffer(self, starting_position = 0, total_points = 100) + ++ Fetches a section of the ADC hardware buffer + +
+ +
+clear_buffer(self, starting_position, total_points) + ++ Clears a section of the ADC hardware buffer + +
+ +
+fill_buffer(slef, starting_position, poin_array) + ++ Fill a section of the ADC hardware buffer with data + +
+ +
+start_streaming(self, tg, channel = 'CH1') + ++ Instruct the ADC to start streaming 8-bit data. use stop_streaming to stop. ++ Arguments + + tg: timegap. 250KHz clock + + channel: channel 'CH1'... 'CH9','IN1','SEN' + +
+ +
+stop_streaming(self) + ++ Instruct the ADC to stop streaming data + +
+ +## DIGITAL SECTION + +This section has commands related to digital measurement and control. These include the Logic Analyzer, frequency measurement calls, timing routines, digital outputs etc. + +
+get_high_freq(self, pin) + ++ Retrieves the frequency of the signal connected to ID1. For frequencies > 1MHz ++ Also good for lower frequencies, but avoid using it since the oscilloscope cannot be used simultaneously due to hardware limitations. ++ The input frequency is fed to a 32 bit counter for a period of 100mS. ++ The value of the counter at the end of 100mS is used to calculate the frequency. ++ Arguments + + pin: The input pin to measure frequency from : ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] ++ Return: frequency + +
+ +
+get_freq(self, channel = 'CNTR', timeout = 2) + ++ Frequency measurement on IDx. ++ Measures time taken for 16 rising edges of input signal. ++ Returns the frequency in Hertz ++ Arguments + + channel: The input to measure frequency from. ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + timeout: This is a blocking call which will wait for one full wavelength before returning the calculated frequency. Use the timeout option if you're unsure of the input signal. Returns 0 if timed out ++ Return: float: frequency + +Connect SQR1 to ID1 + +``` +>>> I.sqr1(4000,25) +>>> self.__print__(I.get_freq('ID1')) +4000.0 +>>> self.__print__(I.r2r_time('ID1')) +#time between successive rising edges +0.00025 +>>> self.__print__(I.f2f_time('ID1')) +#time between successive falling edges +0.00025 +>>> self.__print__(I.pulse_time('ID1')) +#may detect a low pulse, or a high pulse. Whichever comes first +6.25e-05 +>>> I.duty_cycle('ID1') +#returns wavelength, high time +(0.00025,6.25e-05) +``` + +
+ +
+r2r_time(self, channel, skip_cycle = 0, timeout = 5) + ++ Return a list of rising edges that occured within the timeout period. ++ Arguments + + channel: The input to measure time between two rising edges.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + skip_cycle: Number of points to skip. eg. Pendulums pass through light barriers twice every cycle. SO 1 must be skipped + + timeout: Number of seconds to wait for datapoints. (Maximum 60 seconds) ++ Return: list: Array of points + +
+ +
+f2f_time(self,channel, skip_cycle = 0, timeout = 5) + ++ Return a list of falling edges that occured within the timeout period. ++ Arguments + + channel: The input to measure time between two falling edges.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + skip_cycle: Number of points to skip. eg. Pendulums pass through light barriers twice every cycle. SO 1 must be skipped + + timeout: Number of seconds to wait for datapoints. (Maximum 60 seconds) ++ Return: list: Array of points + +
+ +
+MeasureInterval(self, channel1, channel2, edge1, edge2, timeout = 0.1) + ++ Measures time intervals between two logic level changes on any two digital inputs(both can be the same) and returns the calculated time. ++ For example, one can measure the time interval between the occurence of a rising edge on ID1, and a falling edge on ID3. ++ If the returned time is negative, it simply means that the event corresponding to channel2 occurred first. ++ Arguments + + channel1: The input pin to measure first logic level change + + channel2: The input pin to measure second logic level change -['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + edge1: The type of level change to detect in order to start the timer + + 'rising' + + 'falling' + + 'four rising edges' + + edge1: The type of level change to detect in order to stop the timer + + 'rising' + + 'falling' + + 'four rising edges' + + timeout: Use the timeout option if you're unsure of the input signal time period. + Returns -1 if timed out ++ Return: time + +
+ +
+DutyCycle(self, channel = 'ID1', timeout = 1.) + ++ Duty cycle measurement on channel. Returns wavelength(seconds), and length of first half of pulse(high time) ++ Low time = (wavelength - high time) ++ Arguments + + channel: The input pin to measure wavelength and high time.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + timeout: Use the timeout option if you're unsure of the input signal time period. Returns 0 if timed out ++ Return: wavelength, duty cycle + +
+ +
+PulseTime(self, channel = 'ID1', PulseType = 'LOW', timeout = 0.1) + ++ Duty cycle measurement on channel. Returns wavelength(seconds), and length of first half of pulse(high time) ++ Low time = (wavelength - high time) ++ Arguments + + channel: The input pin to measure wavelength and high time.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + PulseType: Type of pulse to detect. May be 'HIGH' or 'LOW' + + timeout: Use the timeout option if you're unsure of the input signal time period. + Returns 0 if timed out ++ Return: pulse width + +
+ +
+MeasureMultipleDigitalEdges(self, channel1, channel2, edgeType1, edgeType2, points1, points2, timeout=0.1, **kwargs) + ++ Measures a set of timestamped logic level changes(Type can be selected) from two different digital inputs. ++ Arguments + + channel1: The input pin to measure first logic level change + + channel2: The input pin to measure second logic level change + -['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + edgeType1: The type of level change that should be recorded + + 'rising' + + 'falling' + + 'four rising edges' [default] + + edgeType2: The type of level change that should be recorded + + 'rising' + + 'falling' + + 'four rising edges' + + points1: Number of data points to obtain for input 1 (Max 4) + + points2: Number of data points to obtain for input 2 (Max 4) + + timeout: Use the timeout option if you're unsure of the input signal time period. + returns -1 if timed out + + **kwargs + + SQ1: Set the state of SQR1 output(LOW or HIGH) and then start the timer. eg. SQR1 = 'LOW' + + zero: subtract the timestamp of the first point from all the others before returning. Default: True ++ Return: time ++ Example, Aim : Calculate value of gravity using time of flight. The setup involves a small metal nut attached to an electromagnet powered via SQ1. When SQ1 is turned off, the set up is designed to make the nut fall through two different light barriers(LED,detector pairs that show a logic change when an object gets in the middle) placed at known distances from the initial position. One can measure the timestamps for rising edges on ID1 ,and ID2 to determine the speed, and then obtain value of g. + +
+ +
+capture_edges1(self, waiting_time = 1., **args) + ++ Log timestamps of rising/falling edges on one digital input ++ Arguments + + waiting_time: Total time to allow the logic analyzer to collect data. This is implemented using a simple sleep routine, so if large delays will be involved, refer to :func:`start_one_channel_LA` to start the acquisition, and :func:`fetch_LA_channels` to retrieve data from the hardware after adequate time. The retrieved data is stored in the array self.dchans[0].timestamps. + + keyword arguments + + channel: 'ID1',...,'ID4' + + trigger_channel: 'ID1',...,'ID4' + + channel_mode: acquisition mode, default value: 3 + + EVERY_SIXTEENTH_RISING_EDGE = 5 + + EVERY_FOURTH_RISING_EDGE = 4 + + EVERY_RISING_EDGE = 3 + + EVERY_FALLING_EDGE = 2 + + EVERY_EDGE = 1 + + DISABLED = 0 + + trigger_mode: same as channel_mode. default_value : 3 ++ Return: timestamp array in Seconds + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> I.capture_edges(0.2,channel = 'ID1',trigger_channel = 'ID1',channel_mode = 3,trigger_mode = 3) +#captures rising edges only. with rising edge trigger on ID1 +``` + +
+ +
+start_one_channel_LA_backup__(self, trigger = 1, channel = 'ID1', maximum_time = 67, **args) + ++ Start logging timestamps of rising/falling edges on ID1 ++ Arguments + + trigger: Bool . Enable edge trigger on ID1. use keyword argument edge = 'rising' or 'falling' + + channel: ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + maximum_time: Total time to sample. If total time exceeds 67 seconds, a prescaler will be used in the reference clock. + + kwargs + + triggger_channels: array of digital input names that can trigger the acquisition. Eg, trigger = ['ID1','ID2','ID3'] will triggger when a logic change specified by the keyword argument 'edge' occurs on either or the three specified trigger inputs. + + edge: 'rising' or 'falling' . trigger edge type for trigger_channels. ++ Return: Nothing + +
+ +
+start_one_channel_LA(self, **args) + ++ Start logging timestamps of rising/falling edges on ID1 ++ Arguments + + channel: ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + channel_mode: Acquisition mode, default value: 1 + + EVERY_SIXTEENTH_RISING_EDGE = 5 + + EVERY_FOURTH_RISING_EDGE = 4 + + EVERY_RISING_EDGE = 3 + + EVERY_FALLING_EDGE = 2 + + EVERY_EDGE = 1 + + DISABLED = 0 ++ Return: Nothing + +
+ +
+start_two_channel_LA(self, **args) + ++ Start logging timestamps of rising/falling edges on ID1, AD2 ++ Arguments + + trigger: Bool. Enable rising edge trigger on ID1 + + \*\*args + + chans: Channels to acquire data from . default ['ID1','ID2'] + + mode: modes for each channel. Array, default value: [1,1] + + EVERY_SIXTEENTH_RISING_EDGE = 5 + + EVERY_FOURTH_RISING_EDGE = 4 + + EVERY_RISING_EDGE = 3 + + EVERY_FALLING_EDGE = 2 + + EVERY_EDGE = 1 + + DISABLED = 0 + + maximum_time: Total time to sample. If total time exceeds 67 seconds, a prescaler will be used in the reference clock ++ Return: Nothing + +
+ +
+start_three_channel_LA(self, **args) + ++ Start logging timestamps of rising/falling edges on ID1, ID2, ID3 ++ Arguments + + \*\*args + + trigger_channel: ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + + mode: modes for each channel. Array, default value: [1,1,1] + + EVERY_SIXTEENTH_RISING_EDGE = 5 + + EVERY_FOURTH_RISING_EDGE = 4 + + EVERY_RISING_EDGE = 3 + + EVERY_FALLING_EDGE = 2 + + EVERY_EDGE = 1 + + DISABLED = 0 + + trigger_mode: Same as modes(previously documented keyword argument) + default_value : 3 ++ Return: Nothing + +
+ +
+start_four_channel_LA(self, trigger = 1, maximum_time = 0.001, mode = [1, 1, 1, 1], **args) + ++ Four channel Logic Analyzer. Start logging timestamps from a 64MHz counter to record level changes on ID1,ID2,ID3,ID4. ++ Arguments + + trigger: Bool . Enable rising edge trigger on ID1 + + maximum_time: Maximum delay expected between two logic level changes.
+ If total time exceeds 1 mS, a prescaler will be used in the reference clock. However, this only refers to the maximum time between two successive level changes. If a delay larger than .26 S occurs, it will be truncated by modulo .26 S.
+If you need to record large intervals, try single channel/two channel modes which use 32 bit counters capable of time interval up to 67 seconds. + + mode: modes for each channel. Array, default value: [1,1,1] + + EVERY_SIXTEENTH_RISING_EDGE = 5 + + EVERY_FOURTH_RISING_EDGE = 4 + + EVERY_RISING_EDGE = 3 + + EVERY_FALLING_EDGE = 2 + + EVERY_EDGE = 1 + + DISABLED = 0 + + trigger_mode: Same as modes(previously documented keyword argument) + default_value : 3 ++ Return: Nothing ++ See also: Use :func:`fetch_long_data_from_LA` (points to read,x) to get data acquired from channel x.The read data can be accessed from :class:`~ScienceLab.dchans` [x-1] + +
+ +
+get_LA_initial_states(self) + ++ Fetches the initial states of digital inputs that were recorded right before the Logic analyzer was started, and the total points each channel recorded ++ Returns: chan1 progress,chan2 progress,chan3 progress,chan4 progress,[ID1,ID2,ID3,ID4]. eg. [1,0,1,1] + +
+ +
+stop_LA(self) + ++ Stop any running logic analyzer function + +
+ +
+fetch_int_data_from_LA(self, bytes, chan = 1) + ++ Fetches the data stored by DMA. integer address increments ++ Arguments + + bytes: Number of readings(integers) to fetch + + chan: Channel number (1-4) + +
+ +
+fetch_LA_channels(self) + ++ Fetches the data stored by DMA. Integer address increments ++ Arguments + + bytes: Number of readings(integers) to fetch + + chan: Channel number (1-4) + +
+ +
+fetch_long_data_from_LA(self, bytes, chan = 1) + ++ Fetches the data stored by DMA. long address increments ++ Arguments + + bytes: Number of readings(long integers) to fetch + + chan: Channel number (1-2) + +
+ +
+fetch_LA_channels(self) + ++ Reads and stores the channels in self.dchans. + +
+ +
+get_states(self) + ++ Gets the state of the digital inputs. ++ Returns: dictionary with keys 'ID1','ID2','ID3','ID4' + +
+ +
+get_state(self, input_id) + ++ Returns the logic level on the specified input (ID1,ID2,ID3, or ID4) ++ Arguments + + input_id: the input channel + + 'ID1' -> state of ID1 + + 'ID4' -> state of ID4 ++ Return: boolean + +``` +>>> from pylab import * +>>> I = sciencelab.ScienceLab() +>>> self.__print__(I.get_state(I.ID1)) + False +``` + +
+ +
+set_state(self, **kwargs) + ++ Set the logic level on digital outputs SQR1,SQR2,SQR3,SQR4 ++ Arguments + + \*\*kwargs: SQR1,SQR2,SQR3,SQR4
+ states(0 or 1) +``` +>>> I.set_state(SQR1 = 1,SQR2 = 0) +#Sets SQR1 HIGH, SQR2 LOw, but leave SQR3,SQR4 untouched. +``` + +
+ +
+countPulses(self, channel = 'SEN') + ++ Count pulses on a digital input. Retrieve total pulses using readPulseCount ++ Arguments + + channel: The input pin to measure rising edges on : ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + +
+ +
+readPulseCount(self) + ++ Read pulses counted using a digital input. Call countPulses before using this. + +
+ +
+capacitance_via_RC_discharge(self)
+
+ +
+get_capacitor_range(self) + ++ Charges a capacitor connected to IN1 via a 20K resistor from a 3.3V source for a fixed interval ++ This function allows an estimation of the parameters to be used with the :func:`get_capacitance` function. ++ Returns: Capacitance calculated using the formula Vc = Vs(1-exp(-t/RC)) + +
+ +
+get_capacitance(self) + ++ Measures capacitance of component connected between CAP and ground ++ Returns: Capacitance (F) + +
+ +
+get_temperature(self) + ++ Return the processor's temperature ++ Returns: Chip Temperature in degree Celcius + +
+ +
+get_ctmu_voltage(self, channel, Crange, tgen = 1) + ++ get_ctmu_voltage(5,2) will activate a constant current source of 5.5uA on IN1 and then measure the voltage at the output. ++ If a diode is used to connect IN1 to ground, the forward voltage drop of the diode will be returned. e.g. .6V for a 4148diode. ++ Returns: Voltage ++ Channel = 5 for IN1 + +| CRange | Implies | +|----------|:-------------:| +| 0 | 550uA | +| 1 | 0.55uA | +| 2 | 5.5uA | +| 3 | 55uA | + +
+ +
+resetHardware(self) + ++ Resets the device, and standalone mode will be enabled if an OLED is connected to the I2C port + +
+ +
+read_flash(self, page, location) + ++ Reads 16 BYTES from the specified location ++ Arguments + + page: page number. 20 pages with 2KBytes each + + location: The flash location(0 to 63) to read from. ++ Return: String of 16 characters read from the location + +
+ +
+read_bulk_flash(self, page, numbytes) + ++ Reads BYTES from the specified location ++ Arguments + + page: Block number. 0-20. each block is 2kB. + + numbytes: Total bytes to read ++ Return: String of 16 characters read from the locationamps),Y1(Voltage at CH1),Y2(Voltage at CH2),Y3(Voltage at CH3),Y4(Voltage at CH4) + +
+ +
+write_flash(self, page, location, string_to_write) + ++ Write a 16 BYTE string to the selected location (0-63) ++ Arguments + + page: page number. 20 pages with 2KBytes each + + location: The flash location(0 to 63) to write to. + + string_to_write: String of 16 characters can be written to each location ++ Note: DO NOT USE THIS UNLESS YOU'RE ABSOLUTELY SURE KNOW THIS! + YOU MAY END UP OVERWRITING THE CALIBRATION DATA, AND WILL HAVE + TO GO THROUGH THE TROUBLE OF GETTING IT FROM THE MANUFACTURER AND + REFLASHING IT. + +
+ +
+write_bulk_flash(self, location, data) + ++ Write a byte array to the entire flash page. Erases any other data ++ Arguments + + location: Block number. 0-20. each block is 2kB. + + bytearray: Array to dump onto flash. Max size 2048 bytes ++ Note: DO NOT USE THIS UNLESS YOU'RE ABSOLUTELY SURE KNOW THIS! + YOU MAY END UP OVERWRITING THE CALIBRATION DATA, AND WILL HAVE + TO GO THROUGH THE TROUBLE OF GETTING IT FROM THE MANUFACTURER AND + REFLASHING IT. + +
+ +## WAVEGEN SECTION + +This section has commands related to waveform generators W1, W2, PWM outputs, servo motor control etc. + +
+set_wave(self, chan, freq) + ++ Set the frequency of wavegen ++ Arguments + + chan: Channel to set frequency for. W1 or W2 + + frequency: Frequency to set on wave generator ++ Returns: frequency + +
+ +
+set_sine1(self, freq) + ++ Set the frequency of wavegen 1 after setting its waveform type to sinusoidal ++ Arguments + + frequency: Frequency to set on wave generator 1. ++ Returns: frequency + +
+ +
+set_sine2(self, freq) + ++ Set the frequency of wavegen 2 after setting its waveform type to sinusoidal ++ Arguments + + frequency: Frequency to set on wave generator 1. ++ Returns: frequency + +
+ +
+set_w1(self, freq, waveType = None) + ++ Set the frequency of wavegen 1 ++ Arguments + + frequency: Frequency to set on wave generator 1. + + waveType: 'sine','tria' . Default : Do not reload table, and use last set table ++ Returns: frequency + +
+ +
+set_w2(self, freq, waveType = None) + ++ Set the frequency of wavegen 2 ++ Arguments + + frequency: Frequency to set on wave generator 2. ++ Returns: frequency + +
+ +
+readbackWavefrom(self, chan) + ++ Set the frequency of wavegen 1 ++ Arguments + + chan: Any of W1,W2,SQR1,SQR2,SQR3,SQR4 ++ Returns: frequency + +
+ +
+set_waves(self, freq, phase, f2 = None) + ++ Set the frequency of wavegen ++ Arguments + + frequency: Frequency to set on both wave generators + + phase: Phase difference between the two. 0-360 degrees + + f2: Only specify if you require two separate frequencies to be set ++ Returns: frequency + +
+ +
+load_equation(self, chan, function, span = None, **kwargs) + ++ Load an arbitrary waveform to the waveform generators ++ Arguments + + chan: The waveform generator to alter. W1 or W2 + + function: A function that will be used to generate the datapoints + + span: The range of values in which to evaluate the given function + +``` +fn = lambda x:abs(x-50) #Triangular waveform +self.I.load_waveform('W1',fn,[0,100]) +#Load triangular wave to wavegen 1 +#Load sinusoidal wave to wavegen 2 +self.I.load_waveform('W2',np.sin,[0,2*np.pi]) +``` + +
+ +
+load_table(self, chan, points, mode = 'arbit', **kwargs) + ++ Load an arbitrary waveform table to the waveform generators ++ Arguments + + chan: The waveform generator to alter. 'W1' or 'W2' + + points: A list of 512 datapoints exactly + + mode: Optional argument. Type of waveform. default value 'arbit'. accepts 'sine', 'tria' + +``` +>>> self.I.load_waveform_table(1,range(512)) +#Load sawtooth wave to wavegen 1 +``` + +
+ +
+sqr1(self, freq, duty_cycle = 50, onlyPrepare = False) + ++ Set the frequency of sqr1 ++ Arguments + + frequency: Frequency + + duty_cycle: Percentage of high time + +
+ +
+sqr1_pattern(self, timing_array) + ++ output a preset sqr1 frequency in fixed intervals. Can be used for sending IR signals that are packets of 38KHz pulses. ++ Arguments + + timing_array: A list of on & off times in uS units + +``` +>>> I.sqr1(38e3 , 50, True ) # Prepare a 38KHz, 50% square wave. Do not output it yet +>>> I.sqr1_pattern([1000,1000,1000,1000,1000]) #On:1mS (38KHz packet), Off:1mS, On:1mS (38KHz packet), Off:1mS, On:1mS (38KHz packet), Off: indefinitely.. +``` + +
+ +
+sqr2(self, freq, duty_cycle) + ++ Set the frequency of sqr2 ++ Arguments + + frequency: Frequency + + duty_cycle: Percentage of high time + +
+ +
+set_sqrs(self, wavelength, phase, high_time1, high_time2, prescaler = 1) + ++ Set the frequency of sqr1,sqr2, with phase shift ++ Arguments + + wavelength: Number of 64Mhz/prescaler clock cycles per wave + + phase: Clock cycles between rising edges of SQR1 and SQR2 + + high time1: Clock cycles for which SQR1 must be HIGH + + high time2: Clock cycles for which SQR2 must be HIGH + + prescaler: 0,1,2. Divides the 64Mhz clock by 8,64, or 256 + +
+ +
+sqrPWM(self, freq, h0, p1, h1, p2, h2, p3, h3, **kwargs) + ++ Initialize phase correlated square waves on SQR1,SQR2,SQR3,SQR4 ++ Arguments + + freq: Frequency in Hertz + + h0: Duty Cycle for SQR1 (0-1) + + p1: Phase shift for SQR2 (0-1) + + h1: Duty Cycle for SQR2 (0-1) + + p2: Phase shift for OD1 (0-1) + + h2: Duty Cycle for OD1 (0-1) + + p3: Phase shift for OD2 (0-1) + + h3: Duty Cycle for OD2 (0-1) + +
+ +
+map_reference_clock(self, scaler, *args) + ++ Map the internal oscillator output to SQR1,SQR2,SQR3,SQR4 or WAVEGEN ++ The output frequency is 128/(1< 128MHz +* 1 -> 64MHz +* 2 -> 32MHz +* 3 -> 16MHz +* . +* . +* 15 ->128./32768 MHz + +``` +>>> I.map_reference_clock(2,'SQR1','SQR2') +``` +Outputs 32 MHz on SQR1, SQR2 pins + ++Note: If you change the reference clock for 'wavegen' , the external waveform generator(AD9833) resolution and range will also change. Default frequency for 'wavegen' is 16MHz. Setting to 1MHz will give you 16 times better resolution, but a usable range of 0Hz to about 100KHz instead of the original 2MHz. + +
+ +## ANALOG OUTPUTS +This section has commands related to current and voltage sources PV1,PV2,PV3,PCS + +
+set_pv1(self, val) + ++ Set the voltage on PV1 ++ 12-bit DAC... -5V to 5V ++ Arguments + + val: Output voltage on PV1. -5V to 5V + +
+ +
+set_pv2(self, val) + ++ Set the voltage on PV2 ++ 12-bit DAC... 0-3.3V ++ Arguments + + val: Output voltage on PV2. 0-3.3V ++ Return: Actual value set on pv2 + +
+ +
+set_pv3(self, val) + ++ Set the voltage on PV3 ++ Arguments + + val: Output voltage on PV3. 0V to 3.3V ++ Return: Actual value set on pv3 + +
+ +
+set_pcs(self, val) + ++ Set programmable current source ++ Arguments + + val: Output current on PCS. 0 to 3.3mA. Subject to load resistance. Read voltage on PCS to check. ++ Return: value attempted to set on pcs + +
+ +
+get_pv1(self) + ++ Get the last set voltage on PV1 ++ 12-bit DAC... -5V to 5V + +
+ +
+get_pv2(self) + ++ Get the last set voltage on PV2 + +
+ +
+get_pv3(self) + ++ Get the last set voltage on PV3 + +
+ +
+get_pcs(self) + ++ Get the last set voltage on PCS + +
+ +
+WS2812B(self, cols, output = 'CS1') + ++ Set shade of WS2182 LED on SQR1 ++ Arguments + + cols: 2Darray [[R,G,B],[R2,G2,B2],[R3,G3,B3]...]
+ brightness of R,G,B ( 0-255 ) + +``` +>>> I.WS2812B([[10,0,0],[0,10,10],[10,0,10]]) +#sets red, cyan, magenta to three daisy chained LEDs +``` + +
+ +## READ PROGRAM AND DATA ADDRESSES +Direct access to RAM and FLASH + +
+read_program_address(self, address) + ++ Reads and returns the value stored at the specified address in program memory ++ Arguments + + address: Address to read from. Refer to PIC24EP64GP204 programming manual + +
+ +
+device_id(self)
+
+ +
+read_data_address(self, address) + ++ Reads and returns the value stored at the specified address in RAM ++ Arguments + + address: Address to read from. Refer to PIC24EP64GP204 programming manual + +
+ +## MOTOR SIGNALLING +Set servo motor angles via SQ1-4. Control one stepper motor using SQ1-4 + +
+stepForward(self, steps, delay) + ++ Control stepper motors using SQR1-4 ++ Take a fixed number of steps in the forward direction with a certain delay( in milliseconds ) between each step. + +
+ +
+stepBackward(self, steps, delay) + ++ Control stepper motors using SQR1-4 ++ Take a fixed number of steps in the backward direction with a certain delay( in milliseconds ) between each step. + +
+ +
+servo(self, angle, chan = 'SQR1') + ++ Output A PWM waveform on SQR1/SQR2 corresponding to the angle specified in the arguments. ++ This is used to operate servo motors. Tested with 9G SG-90 Servo motor. ++ Arguments + + angle: 0-180. Angle corresponding to which the PWM waveform is generated. + + chan: 'SQR1' or 'SQR2'. Whether to use SQ1 or SQ2 to output the PWM waveform used by the servo + +
+ +
+servo4(self, a1, a2, a3, a4) + ++ Operate Four servo motors independently using SQR1, SQR2, SQR3, SQR4. ++ tested with SG-90 9G servos. ++ For high current servos, please use a different power source, and a level convertor for the PWm output signals(if needed) ++ Arguments + + a1: Angle to set on Servo which uses SQR1 as PWM input. [0-180] + + a2: Angle to set on Servo which uses SQR2 as PWM input. [0-180] + + a3: Angle to set on Servo which uses SQR3 as PWM input. [0-180] + + a4: Angle to set on Servo which uses SQR4 as PWM input. [0-180] + +
+ +
+enableUartPassthrough(self, baudrate, persist = False) + ++ All data received by the device is relayed to an external port(SCL[TX],SDA[RX]) after this function is called. ++ If a period > .5 seconds elapses between two transmit/receive events, the device resets and resumes normal mode. This timeout feature has been implemented in lieu of a hard reset option. ++ Can be used to load programs into secondary microcontrollers with bootloaders such ATMEGA, and ESP8266 ++ Arguments + + baudrate: BAUDRATE to use + + persist: If set to True, the device will stay in passthrough mode until the next power cycle.
+ Otherwise(default scenario), the device will return to normal operation if no data is sent/received for a period greater than one second at a time. + +
+ +
+estimateDistance(self) + ++ Read data from ultrasonic distance sensor HC-SR04/HC-SR05. Sensors must have separate trigger and output pins. ++ First a 10uS pulse is output on SQR1. SQR1 must be connected to the TRIG pin on the sensor prior to use. ++ Upon receiving this pulse, the sensor emits a sequence of sound pulses, and the logic level of its output pin(which we will monitor via ID1) is also set high. The logic level goes LOW when the sound packet returns to the sensor, or when a timeout occurs. ++ The ultrasound sensor outputs a series of 8 sound pulses at 40KHz which corresponds to a time period of 25uS per pulse. These pulses reflect off of the nearest object in front of the sensor, and return to it. The time between sending and receiving of the pulse packet is used to estimate the distance. If the reflecting object is either too far away or absorbs sound, less than 8 pulses may be received, and this can cause a measurement error of 25uS which corresponds to 8mm. ++ Ensure 5V supply. You may set SQR2 to HIGH [ I.set_state(SQR2 = True) ] , and use that as the power supply. ++ Return: 0 upon timeout + +
+ +
+opticalArray(self, SS, delay, channel = 'CH3', **kwargs) + ++ Read from 3648 element optical sensor array TCD3648P from Toshiba. Experimental feature. Neither Sine waves will be available. ++ Connect SQR1 to MS , SQR2 to MS , A0 to CHannel , and CS1(on the expansion slot) to ICG ++ delay : ICG low duration ++ tp : clock wavelength = tp*15nS, SS = clock/4 + +
+ +
+setUARTBAUD(self, BAUD)
+
+ +
+writeUART(self, character)
+
+ +
+readUART(self)
+
+ +
+readUARTStatus(self) + ++ Return: available bytes in UART buffer + +
+ +
+readLog(self) + ++ Read hardware debug log. + +
+ +
+raiseException(self, ex, msg)
+
diff --git a/PSL/SENSORS/AD7718_class.py b/PSL/SENSORS/AD7718_class.py index a79a518c..4adec5bb 100644 --- a/PSL/SENSORS/AD7718_class.py +++ b/PSL/SENSORS/AD7718_class.py @@ -1,5 +1,7 @@ from __future__ import print_function -import time,importlib + +import time + import numpy as np ''' @@ -12,248 +14,242 @@ ''' -import struct def _bv(x): - return 1<4:data = (data-3.3/2)*4 - return self.caldata[chan](data) - else: - time.sleep(0.1) - print ('increase delay') - return False - - def readVoltage(self,chan): - if not self.__startRead__(chan): - return False - time.sleep(0.15) - return self.__fetchData__(chan) - - - def __fetchRawData__(self,chan): - while True: - stat=self.readRegister(self.STATUS) - if stat&0x80: - data =float(self.readData()) - return self.convert_unipolar(data) - else: - time.sleep(0.01) - print ('increase delay') - return False - - def readRawVoltage(self,chan): - if not self.__startRead__(chan): - return False - time.sleep(0.15) - return self.__fetchRawData__(chan) +class AD7718: + VREF = 3.3 + + STATUS = 0 + MODE = 1 + ADCCON = 2 + FILTER = 3 + ADCDATA = 4 + ADCOFFSET = 5 + ADCGAIN = 6 + IOCON = 7 + TEST1 = 12 + TEST2 = 13 + ID = 15 + # bit definitions + MODE_PD = 0 + MODE_IDLE = 1 + MODE_SINGLE = 2 + MODE_CONT = 3 + MODE_INT_ZEROCAL = 4 + MODE_INT_FULLCAL = 5 + MODE_SYST_ZEROCAL = 6 + MODE_SYST_FULLCAL = 7 + + MODE_OSCPD = _bv(3) + MODE_CHCON = _bv(4) + MODE_REFSEL = _bv(5) + MODE_NEGBUF = _bv(6) + MODE_NOCHOP = _bv(7) + + CON_AIN1AINCOM = 0 << 4 + CON_AIN2AINCOM = 1 << 4 + CON_AIN3AINCOM = 2 << 4 + CON_AIN4AINCOM = 3 << 4 + CON_AIN5AINCOM = 4 << 4 + CON_AIN6AINCOM = 5 << 4 + CON_AIN7AINCOM = 6 << 4 + CON_AIN8AINCOM = 7 << 4 + CON_AIN1AIN2 = 8 << 4 + CON_AIN3AIN4 = 9 << 4 + CON_AIN5AIN6 = 10 << 4 + CON_AIN7AIN8 = 11 << 4 + CON_AIN2AIN2 = 12 << 4 + CON_AINCOMAINCOM = 13 << 4 + CON_REFINREFIN = 14 << 4 + CON_OPEN = 15 << 4 + CON_UNIPOLAR = _bv(3) + + CON_RANGE0 = 0 # +-20mV + CON_RANGE1 = 1 # +-40mV + CON_RANGE2 = 2 # +-80mV + CON_RANGE3 = 3 # +-160mV + CON_RANGE4 = 4 # +-320mV + CON_RANGE5 = 5 # +-640mV + CON_RANGE6 = 6 # +-1280mV + CON_RANGE7 = 7 # +-2560mV + gain = 1 + CHAN_NAMES = ['AIN1AINCOM', 'AIN2AINCOM', 'AIN3AINCOM', 'AIN4AINCOM', 'AIN5AINCOM', + 'AIN6AINCOM', 'AIN7AINCOM', 'AIN8AINCOM'] + + def __init__(self, I, calibs): + self.cs = 'CS1' + self.I = I + self.calibs = calibs + self.I.SPI.set_parameters(2, 1, 0, 1) + self.writeRegister(self.FILTER, 20) + self.writeRegister(self.MODE, self.MODE_SINGLE | self.MODE_CHCON | self.MODE_REFSEL) + self.caldata = {} + for a in calibs.keys(): + self.caldata[a] = np.poly1d(calibs[a]) + print('Loaded calibration', self.caldata) + + def start(self): + self.I.SPI.start(self.cs) + + def stop(self): + self.I.SPI.stop(self.cs) + + def send8(self, val): + return self.I.SPI.send8(val) + + def send16(self, val): + return self.I.SPI.send16(val) + + def write(self, regname, value): + pass + + def readRegister(self, regname): + self.start() + val = self.send16(0x4000 | (regname << 8)) + self.stop() + # print (regname,val) + val &= 0x00FF + return val + + def readData(self): + self.start() + val = self.send16(0x4000 | (self.ADCDATA << 8)) + val &= 0xFF + val <<= 16 + val |= self.send16(0x0000) + self.stop() + return val + + def writeRegister(self, regname, value): + self.start() + val = self.send16(0x0000 | (regname << 8) | value) + self.stop() + return val + + def internalCalibration(self, chan=1): + self.start() + val = self.send16(0x0000 | (self.ADCCON << 8) | (chan << 4) | 7) # range=7 + + start_time = time.time() + caldone = False + val = self.send16(0x0000 | (self.MODE << 8) | 4) + while caldone != 1: + time.sleep(0.5) + caldone = self.send16(0x4000 | (self.MODE << 8)) & 7 + print('waiting for zero scale calibration... %.2f S, %d' % (time.time() - start_time, caldone)) + + print('\n') + caldone = False + val = self.send16(0x0000 | (self.MODE << 8) | 5) + while caldone != 1: + time.sleep(0.5) + caldone = self.send16(0x4000 | (self.MODE << 8)) & 7 + print('waiting for full scale calibration... %.2f S %d' % (time.time() - start_time, caldone)) + + print('\n') + + self.stop() + + def readCalibration(self): + self.start() + off = self.send16(0x4000 | (self.ADCOFFSET << 8)) + off &= 0xFF + off <<= 16 + off |= self.send16(0x0000) + + gn = self.send16(0x4000 | (self.ADCGAIN << 8)) + gn &= 0xFF + gn <<= 16 + gn |= self.send16(0x0000) + self.stop() + return off, gn + + def configADC(self, adccon): + self.writeRegister(self.ADCCON, adccon) # unipolar channels , range + self.gain = 2 ** (7 - adccon & 3) + + def printstat(self): + stat = self.readRegister(self.STATUS) + P = ['PLL LOCKED', 'RES', 'RES', 'ADC ERROR', 'RES', 'CAL DONE', 'RES', 'READY'] + N = ['PLL ERROR', 'RES', 'RES', 'ADC OKAY', 'RES', 'CAL LOW', 'RES', 'NOT READY'] + s = '' + for a in range(8): + if stat & (1 << a): + s += '\t' + P[a] + else: + s += '\t' + N[a] + print(stat, s) + + def convert_unipolar(self, x): + return (1.024 * self.VREF * x) / (self.gain * 2 ** 24) + + def convert_bipolar(self, x): + return ((x / 2 ** 24) - 1) * (1.024 * self.VREF) / (self.gain) + + def __startRead__(self, chan): + if chan not in self.CHAN_NAMES: + print('invalid channel name. try AIN1AINCOM') + return False + chanid = self.CHAN_NAMES.index(chan) + self.configADC(self.CON_RANGE7 | self.CON_UNIPOLAR | (chanid << 4)) + self.writeRegister(self.MODE, self.MODE_SINGLE | self.MODE_CHCON | self.MODE_REFSEL) + return True + + def __fetchData__(self, chan): + while True: + stat = self.readRegister(self.STATUS) + if stat & 0x80: + data = float(self.readData()) + data = self.convert_unipolar(data) + if int(chan[3]) > 4: data = (data - 3.3 / 2) * 4 + return self.caldata[chan](data) + else: + time.sleep(0.1) + print('increase delay') + return False + + def readVoltage(self, chan): + if not self.__startRead__(chan): + return False + time.sleep(0.15) + return self.__fetchData__(chan) + + def __fetchRawData__(self, chan): + while True: + stat = self.readRegister(self.STATUS) + if stat & 0x80: + data = float(self.readData()) + return self.convert_unipolar(data) + else: + time.sleep(0.01) + print('increase delay') + return False + + def readRawVoltage(self, chan): + if not self.__startRead__(chan): + return False + time.sleep(0.15) + return self.__fetchRawData__(chan) if __name__ == "__main__": - from PSL import sciencelab - I= sciencelab.connect() - calibs={ - 'AIN6AINCOM':[6.993123e-07,-1.563294e-06,9.994211e-01,-4.596018e-03], - 'AIN7AINCOM':[3.911521e-07,-1.706405e-06,1.002294e+00,-1.286302e-02], - 'AIN3AINCOM':[-3.455831e-06,2.861689e-05,1.000195e+00,3.802349e-04], - 'AIN1AINCOM':[8.220199e-05,-4.587100e-04,1.001015e+00,-1.684517e-04], - 'AIN5AINCOM':[-1.250787e-07,-9.203838e-07,1.000299e+00,-1.262684e-03], - 'AIN2AINCOM':[5.459186e-06,-1.749624e-05,1.000268e+00,1.907896e-04], - 'AIN9AINCOM':[7.652808e+00,1.479229e+00,2.832601e-01,4.495232e-02], - 'AIN8AINCOM':[8.290843e-07,-7.129532e-07,9.993159e-01,3.307947e-03], - 'AIN4AINCOM':[4.135213e-06,-1.973478e-05,1.000277e+00,2.115374e-04], } - A = AD7718(I,calibs) - for a in range(10): - print (A.readRawVoltage('AIN1AINCOM')) - time.sleep(0.3) - - + from PSL import sciencelab + + I = sciencelab.connect() + calibs = { + 'AIN6AINCOM': [6.993123e-07, -1.563294e-06, 9.994211e-01, -4.596018e-03], + 'AIN7AINCOM': [3.911521e-07, -1.706405e-06, 1.002294e+00, -1.286302e-02], + 'AIN3AINCOM': [-3.455831e-06, 2.861689e-05, 1.000195e+00, 3.802349e-04], + 'AIN1AINCOM': [8.220199e-05, -4.587100e-04, 1.001015e+00, -1.684517e-04], + 'AIN5AINCOM': [-1.250787e-07, -9.203838e-07, 1.000299e+00, -1.262684e-03], + 'AIN2AINCOM': [5.459186e-06, -1.749624e-05, 1.000268e+00, 1.907896e-04], + 'AIN9AINCOM': [7.652808e+00, 1.479229e+00, 2.832601e-01, 4.495232e-02], + 'AIN8AINCOM': [8.290843e-07, -7.129532e-07, 9.993159e-01, 3.307947e-03], + 'AIN4AINCOM': [4.135213e-06, -1.973478e-05, 1.000277e+00, 2.115374e-04], } + A = AD7718(I, calibs) + for a in range(10): + print(A.readRawVoltage('AIN1AINCOM')) + time.sleep(0.3) diff --git a/PSL/SENSORS/AD9833.py b/PSL/SENSORS/AD9833.py index dde710c9..be8556c2 100644 --- a/PSL/SENSORS/AD9833.py +++ b/PSL/SENSORS/AD9833.py @@ -1,95 +1,94 @@ -import time,sys -import numpy as np +import sys + class AD9833: - if sys.version.major==3: - DDS_MAX_FREQ = 0xFFFFFFF-1 #24 bit resolution + if sys.version.major == 3: + DDS_MAX_FREQ = 0xFFFFFFF - 1 # 24 bit resolution else: - DDS_MAX_FREQ = eval("0xFFFFFFFL-1") #24 bit resolution - #control bytes + DDS_MAX_FREQ = eval("0xFFFFFFFL-1") # 24 bit resolution + # control bytes DDS_B28 = 13 DDS_HLB = 12 DDS_FSELECT = 11 DDS_PSELECT = 10 DDS_RESET = 8 - DDS_SLEEP1 =7 - DDS_SLEEP12 =6 - DDS_OPBITEN =5 - DDS_DIV2 =3 - DDS_MODE =1 - - DDS_FSYNC =9 - - DDS_SINE =(0) - DDS_TRIANGLE =(1<>14)&0x3FFF))&0xFFFF ) #MSB - phase = args.get('phase',0) - self.write( 0xc000|phase) #Phase - self.write(modebits) #finished loading data + self.write((1 << self.DDS_RESET) | modebits) # Ready to load DATA + self.write((regsel | (freq_setting & 0x3FFF)) & 0xFFFF) # LSB + self.write((regsel | ((freq_setting >> 14) & 0x3FFF)) & 0xFFFF) # MSB + phase = args.get('phase', 0) + self.write(0xc000 | phase) # Phase + self.write(modebits) # finished loading data - def set_voltage(self,v): - self.waveform_mode=self.DDS_TRIANGLE - self.set_frequency(0,0,phase = v)#0xfff*v/.6) + def set_voltage(self, v): + self.waveform_mode = self.DDS_TRIANGLE + self.set_frequency(0, 0, phase=v) # 0xfff*v/.6) - def select_frequency_register(self,register): + def select_frequency_register(self, register): self.active_channel = register modebits = self.waveform_mode - if register: modebits|=(1<> 8) & 0xFF, value & 0xFF]) + + def setGain(self, gain): + ''' + options : 'GAIN_TWOTHIRDS','GAIN_ONE','GAIN_TWO','GAIN_FOUR','GAIN_EIGHT','GAIN_SIXTEEN' + ''' + self.gain = gain + + def setChannel(self, channel): + ''' + options 'UNI_0','UNI_1','UNI_2','UNI_3','DIFF_01','DIFF_23' + ''' + self.channel = channel + + def setDataRate(self, rate): + ''' + data rate options 8,16,32,64,128,250,475,860 SPS + ''' + self.rate = rate + + def readADC_SingleEnded(self, chan): + if chan > 3: return None + # start with default values + config = (self.REG_CONFIG_CQUE_NONE # Disable the comparator (default val) + | self.REG_CONFIG_CLAT_NONLAT # Non-latching (default val) + | self.REG_CONFIG_CPOL_ACTVLOW # Alert/Rdy active low (default val) + | self.REG_CONFIG_CMODE_TRAD # Traditional comparator (default val) + | self.sdr_selection[self.rate] # 1600 samples per second (default) + | self.REG_CONFIG_MODE_SINGLE) # Single-shot mode (default) + + # Set PGA/voltage range + config |= self.gains[self.gain] + + if chan == 0: + config |= self.REG_CONFIG_MUX_SINGLE_0 + elif chan == 1: + config |= self.REG_CONFIG_MUX_SINGLE_1 + elif chan == 2: + config |= self.REG_CONFIG_MUX_SINGLE_2 + elif chan == 3: + config |= self.REG_CONFIG_MUX_SINGLE_3 + # Set 'start single-conversion' bit + config |= self.REG_CONFIG_OS_SINGLE + self.writeRegister(self.REG_POINTER_CONFIG, config); + time.sleep(1. / self.rate + .002) # convert to mS to S + return self.readRegister(self.REG_POINTER_CONVERT) * self.gain_scaling[self.gain] + + def readADC_Differential(self, chan='01'): + # start with default values + config = (self.REG_CONFIG_CQUE_NONE # Disable the comparator (default val) + | self.REG_CONFIG_CLAT_NONLAT # Non-latching (default val) + | self.REG_CONFIG_CPOL_ACTVLOW # Alert/Rdy active low (default val) + | self.REG_CONFIG_CMODE_TRAD # Traditional comparator (default val) + | self.sdr_selection[self.rate] # samples per second + | self.REG_CONFIG_MODE_SINGLE) # Single-shot mode (default) + + # Set PGA/voltage range + config |= self.gains[self.gain] + if chan == '01': + config |= self.REG_CONFIG_MUX_DIFF_0_1 + elif chan == '23': + config |= self.REG_CONFIG_MUX_DIFF_2_3 + # Set 'start single-conversion' bit + config |= self.REG_CONFIG_OS_SINGLE + self.writeRegister(self.REG_POINTER_CONFIG, config); + time.sleep(1. / self.rate + .002) # convert to mS to S + return int16(self.readRegister(self.REG_POINTER_CONVERT)) * self.gain_scaling[self.gain] + + def getLastResults(self): + return int16(self.readRegister(self.REG_POINTER_CONVERT)) * self.gain_scaling[self.gain] + + def getRaw(self): + ''' + return values in mV + ''' + chan = self.type_selection[self.channel] + if self.channel[:3] == 'UNI': + return [self.readADC_SingleEnded(chan)] + elif self.channel[:3] == 'DIF': + return [self.readADC_Differential(chan)] diff --git a/PSL/SENSORS/BH1750.py b/PSL/SENSORS/BH1750.py index 6964fd71..2d14a748 100644 --- a/PSL/SENSORS/BH1750.py +++ b/PSL/SENSORS/BH1750.py @@ -1,63 +1,66 @@ -from __future__ import print_function -from numpy import int16 -def connect(route,**args): - return BRIDGE(route,**args) +from __future__ import print_function + + +def connect(route, **args): + return BRIDGE(route, **args) class BRIDGE(): - POWER_ON =0x01 - RESET =0x07 - RES_1000mLx =0x10 - RES_500mLx =0x11 - RES_4000mLx =0x13 - - gain_choices=[RES_500mLx,RES_1000mLx,RES_4000mLx] - gain_literal_choices=['500mLx','1000mLx','4000mLx'] - gain = 0 - scaling=[2,1,.25] - - #--------------Parameters-------------------- - #This must be defined in order to let GUIs automatically create menus - #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'], - 'setRange':gain_literal_choices, - } - - NUMPLOTS=1 - PLOTNAMES = ['Lux'] - ADDRESS = 0x23 - name = 'Luminosity' - def __init__(self,I2C,**args): - self.I2C=I2C - self.ADDRESS = args.get('address',0x23) - self.init('') - - def init(self,dummy_variable_to_circumvent_framework_limitation): # I know how to fix this now. remind me. - self.I2C.writeBulk(self.ADDRESS,[self.RES_500mLx]) - - def setRange(self,g): - self.gain = self.gain_literal_choices.index(g) - self.I2C.writeBulk(self.ADDRESS,[self.gain_choices[self.gain]]) - - def getVals(self,numbytes): - vals = self.I2C.simpleRead(self.ADDRESS,numbytes) - return vals - - def getRaw(self): - vals=self.getVals(2) - if vals: - if len(vals)==2: - return [(vals[0]<<8|vals[1])/1.2] #/self.scaling[self.gain] - else: - return False - else: - return False - + POWER_ON = 0x01 + RESET = 0x07 + RES_1000mLx = 0x10 + RES_500mLx = 0x11 + RES_4000mLx = 0x13 + + gain_choices = [RES_500mLx, RES_1000mLx, RES_4000mLx] + gain_literal_choices = ['500mLx', '1000mLx', '4000mLx'] + gain = 0 + scaling = [2, 1, .25] + + # --------------Parameters-------------------- + # This must be defined in order to let GUIs automatically create menus + # 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': None, + 'setRange': gain_literal_choices, + } + + NUMPLOTS = 1 + PLOTNAMES = ['Lux'] + ADDRESS = 0x23 + name = 'Luminosity' + + def __init__(self, I2C, **args): + self.I2C = I2C + self.ADDRESS = args.get('address', 0x23) + self.init() + + def init(self): + self.I2C.writeBulk(self.ADDRESS, [self.RES_500mLx]) + + def setRange(self, g): + self.gain = self.gain_literal_choices.index(g) + self.I2C.writeBulk(self.ADDRESS, [self.gain_choices[self.gain]]) + + def getVals(self, numbytes): + vals = self.I2C.simpleRead(self.ADDRESS, numbytes) + return vals + + def getRaw(self): + vals = self.getVals(2) + if vals: + if len(vals) == 2: + return [(vals[0] << 8 | vals[1]) / 1.2] # /self.scaling[self.gain] + else: + return False + else: + return False + if __name__ == "__main__": - from PSL import sciencelab - I= sciencelab.connect() - A = connect(I.I2C) - print (A.getRaw()) + from PSL import sciencelab + + I = sciencelab.connect() + A = connect(I.I2C) + print(A.getRaw()) diff --git a/PSL/SENSORS/BMP180.py b/PSL/SENSORS/BMP180.py index 5b7b4732..45a8bb53 100644 --- a/PSL/SENSORS/BMP180.py +++ b/PSL/SENSORS/BMP180.py @@ -1,113 +1,118 @@ from __future__ import print_function -from numpy import int16 + import time -def connect(route,**args): - return BMP180(route,**args) +from numpy import int16 + + +def connect(route, **args): + return BMP180(route, **args) + class BMP180: - ADDRESS = 0x77 - REG_CONTROL = 0xF4 - REG_RESULT = 0xF6 - CMD_TEMP = 0x2E - CMD_P0 = 0x34 - CMD_P1 = 0x74 - CMD_P2 = 0xB4 - CMD_P3 = 0xF4 - oversampling=0 - NUMPLOTS=3 - PLOTNAMES = ['Temperature','Pressure','Altitude'] - name = 'Altimeter BMP180' - def __init__(self,I2C,**args): - self.ADDRESS = args.get('address',self.ADDRESS) - - self.I2C = I2C - - self.MB = self.__readInt__(0xBA) - - self.c3 = 160.0 * pow(2,-15) * self.__readInt__(0xAE) - self.c4 = pow(10,-3) * pow(2,-15) * self.__readUInt__(0xB0) - self.b1 = pow(160,2) * pow(2,-30) * self.__readInt__(0xB6) - self.c5 = (pow(2,-15) / 160) * self.__readUInt__(0xB2) - self.c6 = self.__readUInt__(0xB4) - self.mc = (pow(2,11) / pow(160,2)) * self.__readInt__(0xBC) - self.md = self.__readInt__(0xBE) / 160.0 - self.x0 = self.__readInt__(0xAA) - self.x1 = 160.0 * pow(2,-13) * self.__readInt__(0xAC) - self.x2 = pow(160,2) * pow(2,-25) * self.__readInt__(0xB8) - self.y0 = self.c4 * pow(2,15) - self.y1 = self.c4 * self.c3 - self.y2 = self.c4 * self.b1 - self.p0 = (3791.0 - 8.0) / 1600.0 - self.p1 = 1.0 - 7357.0 * pow(2,-20) - self.p2 = 3038.0 * 100.0 * pow(2,-36) - self.T=25 - print ('calib:',self.c3,self.c4,self.b1,self.c5,self.c6,self.mc,self.md,self.x0,self.x1,self.x2,self.y0,self.y1,self.p0,self.p1,self.p2) - self.params={'setOversampling':[0,1,2,3]} - self.name='BMP180 Altimeter' - self.initTemperature() - self.readTemperature() - self.initPressure() - self.baseline=self.readPressure() - - def __readInt__(self,addr): - return int16(self.__readUInt__(addr)) - - def __readUInt__(self,addr): - vals = self.I2C.readBulk(self.ADDRESS,addr,2) - v=1.*((vals[0]<<8)|vals[1]) - return v - - def initTemperature(self): - self.I2C.writeBulk(self.ADDRESS,[self.REG_CONTROL,self.CMD_TEMP]) - time.sleep(0.005) - - def readTemperature(self): - vals = self.I2C.readBulk(self.ADDRESS,self.REG_RESULT,2) - if len(vals)==2: - T = (vals[0] <<8 ) + vals[1] - a = self.c5 * (T - self.c6) - self.T = a + (self.mc / (a + self.md)) - return self.T - else: - return False - - def setOversampling(self,num): - self.oversampling=num - - def initPressure(self): - os = [0x34,0x74,0xb4,0xf4] - delays=[0.005,0.008,0.014,0.026] - self.I2C.writeBulk(self.ADDRESS,[self.REG_CONTROL,os[self.oversampling] ]) - time.sleep(delays[self.oversampling]) - - def readPressure(self): - vals = self.I2C.readBulk(self.ADDRESS,self.REG_RESULT,3) - if len(vals)==3: - P = 1.*(vals[0] <<8) + vals[1] + (vals[2]/256.0) - s = self.T - 25.0 - x = (self.x2 * pow(s,2)) + (self.x1 * s) + self.x0 - y = (self.y2 * pow(s,2)) + (self.y1 * s) + self.y0 - z = (P - x) / y - self.P = (self.p2 * pow(z,2)) + (self.p1 * z) + self.p0 - return self.P - else: - return False - - def altitude(self): - #baseline pressure needs to be provided - return (44330.0*(1-pow(self.P/self.baseline,1/5.255))) - - def sealevel(self,P,A): - ''' - given a calculated pressure and altitude, return the sealevel - ''' - return(P/pow(1-(A/44330.0),5.255)) - - def getRaw(self): - self.initTemperature() - self.readTemperature() - self.initPressure() - self.readPressure() - return [self.T,self.P,self.altitude()] - + ADDRESS = 0x77 + REG_CONTROL = 0xF4 + REG_RESULT = 0xF6 + CMD_TEMP = 0x2E + CMD_P0 = 0x34 + CMD_P1 = 0x74 + CMD_P2 = 0xB4 + CMD_P3 = 0xF4 + oversampling = 0 + NUMPLOTS = 3 + PLOTNAMES = ['Temperature', 'Pressure', 'Altitude'] + name = 'Altimeter BMP180' + + def __init__(self, I2C, **args): + self.ADDRESS = args.get('address', self.ADDRESS) + + self.I2C = I2C + + self.MB = self.__readInt__(0xBA) + + self.c3 = 160.0 * pow(2, -15) * self.__readInt__(0xAE) + self.c4 = pow(10, -3) * pow(2, -15) * self.__readUInt__(0xB0) + self.b1 = pow(160, 2) * pow(2, -30) * self.__readInt__(0xB6) + self.c5 = (pow(2, -15) / 160) * self.__readUInt__(0xB2) + self.c6 = self.__readUInt__(0xB4) + self.mc = (pow(2, 11) / pow(160, 2)) * self.__readInt__(0xBC) + self.md = self.__readInt__(0xBE) / 160.0 + self.x0 = self.__readInt__(0xAA) + self.x1 = 160.0 * pow(2, -13) * self.__readInt__(0xAC) + self.x2 = pow(160, 2) * pow(2, -25) * self.__readInt__(0xB8) + self.y0 = self.c4 * pow(2, 15) + self.y1 = self.c4 * self.c3 + self.y2 = self.c4 * self.b1 + self.p0 = (3791.0 - 8.0) / 1600.0 + self.p1 = 1.0 - 7357.0 * pow(2, -20) + self.p2 = 3038.0 * 100.0 * pow(2, -36) + self.T = 25 + print('calib:', self.c3, self.c4, self.b1, self.c5, self.c6, self.mc, self.md, self.x0, self.x1, self.x2, + self.y0, self.y1, self.p0, self.p1, self.p2) + self.params = {'setOversampling': [0, 1, 2, 3]} + self.name = 'BMP180 Altimeter' + self.initTemperature() + self.readTemperature() + self.initPressure() + self.baseline = self.readPressure() + + def __readInt__(self, addr): + return int16(self.__readUInt__(addr)) + + def __readUInt__(self, addr): + vals = self.I2C.readBulk(self.ADDRESS, addr, 2) + v = 1. * ((vals[0] << 8) | vals[1]) + return v + + def initTemperature(self): + self.I2C.writeBulk(self.ADDRESS, [self.REG_CONTROL, self.CMD_TEMP]) + time.sleep(0.005) + + def readTemperature(self): + vals = self.I2C.readBulk(self.ADDRESS, self.REG_RESULT, 2) + if len(vals) == 2: + T = (vals[0] << 8) + vals[1] + a = self.c5 * (T - self.c6) + self.T = a + (self.mc / (a + self.md)) + return self.T + else: + return False + + def setOversampling(self, num): + self.oversampling = num + + def initPressure(self): + os = [0x34, 0x74, 0xb4, 0xf4] + delays = [0.005, 0.008, 0.014, 0.026] + self.I2C.writeBulk(self.ADDRESS, [self.REG_CONTROL, os[self.oversampling]]) + time.sleep(delays[self.oversampling]) + + def readPressure(self): + vals = self.I2C.readBulk(self.ADDRESS, self.REG_RESULT, 3) + if len(vals) == 3: + P = 1. * (vals[0] << 8) + vals[1] + (vals[2] / 256.0) + s = self.T - 25.0 + x = (self.x2 * pow(s, 2)) + (self.x1 * s) + self.x0 + y = (self.y2 * pow(s, 2)) + (self.y1 * s) + self.y0 + z = (P - x) / y + self.P = (self.p2 * pow(z, 2)) + (self.p1 * z) + self.p0 + return self.P + else: + return False + + def altitude(self): + # baseline pressure needs to be provided + return (44330.0 * (1 - pow(self.P / self.baseline, 1 / 5.255))) + + def sealevel(self, P, A): + ''' + given a calculated pressure and altitude, return the sealevel + ''' + return (P / pow(1 - (A / 44330.0), 5.255)) + + def getRaw(self): + self.initTemperature() + self.readTemperature() + self.initPressure() + self.readPressure() + return [self.T, self.P, self.altitude()] diff --git a/PSL/SENSORS/ComplementaryFilter.py b/PSL/SENSORS/ComplementaryFilter.py index 01e416ab..5e092b02 100644 --- a/PSL/SENSORS/ComplementaryFilter.py +++ b/PSL/SENSORS/ComplementaryFilter.py @@ -1,17 +1,20 @@ +import numpy as np + + class ComplementaryFilter: - def __init__(self,): - self.pitch=0 - self.roll=0 - self.dt=0.001 + def __init__(self, ): + self.pitch = 0 + self.roll = 0 + self.dt = 0.001 - def addData(self,accData,gyrData): - self.pitch += (gyrData[0]) * self.dt # Angle around the X-axis - self.roll -= (gyrData[1]) * self.dt #Angle around the Y-axis - forceMagnitudeApprox = abs(accData[0]) + abs(accData[1]) + abs(accData[2]); - pitchAcc = np.arctan2(accData[1], accData[2]) * 180 / np.pi - self.pitch = self.pitch * 0.98 + pitchAcc * 0.02 - rollAcc = np.arctan2(accData[0], accData[2]) * 180 / np.pi - self.roll = self.roll * 0.98 + rollAcc * 0.02 + def addData(self, accData, gyrData): + self.pitch += (gyrData[0]) * self.dt # Angle around the X-axis + self.roll -= (gyrData[1]) * self.dt # Angle around the Y-axis + forceMagnitudeApprox = abs(accData[0]) + abs(accData[1]) + abs(accData[2]); + pitchAcc = np.arctan2(accData[1], accData[2]) * 180 / np.pi + self.pitch = self.pitch * 0.98 + pitchAcc * 0.02 + rollAcc = np.arctan2(accData[0], accData[2]) * 180 / np.pi + self.roll = self.roll * 0.98 + rollAcc * 0.02 - def getData(self): - return self.roll,self.pitch + def getData(self): + return self.roll, self.pitch diff --git a/PSL/SENSORS/HMC5883L.py b/PSL/SENSORS/HMC5883L.py index 4b2b2edc..7f299291 100644 --- a/PSL/SENSORS/HMC5883L.py +++ b/PSL/SENSORS/HMC5883L.py @@ -1,103 +1,106 @@ -from numpy import int16 -def connect(route,**args): - return HMC5883L(route,**args) +def connect(route, **args): + return HMC5883L(route, **args) class HMC5883L(): - CONFA=0x00 - CONFB=0x01 - MODE=0x02 - STATUS=0x09 - - #--------CONFA register bits. 0x00----------- - samplesToAverage=0 - samplesToAverage_choices=[1,2,4,8] - - dataOutputRate=6 - dataOutputRate_choices=[0.75,1.5,3,7.5,15,30,75] - - measurementConf=0 - - #--------CONFB register bits. 0x01----------- - gainValue = 7 #least sensitive - gain_choices = [8,7,6,5,4,3,2,1] - scaling=[1370.,1090.,820.,660.,440.,390.,330.,230.] - - #--------------Parameters-------------------- - #This must be defined in order to let GUIs automatically create menus - #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'], - 'setSamplesToAverage':samplesToAverage_choices, - 'setDataOutputRate':dataOutputRate_choices, - 'setGain':gain_choices, - } - ADDRESS = 0x1E - name = 'Magnetometer' - NUMPLOTS=3 - PLOTNAMES = ['Bx','By','Bz'] - def __init__(self,I2C,**args): - self.I2C=I2C - self.ADDRESS = args.get('address',self.ADDRESS) - self.name = 'Magnetometer' - ''' - try: - print 'switching baud to 400k' - self.I2C.configI2C(400e3) - except: - print 'FAILED TO CHANGE BAUD RATE' - ''' - self.init('') - - def init(self,dummy_variable_to_circumvent_framework_limitation): # I know how to fix this now. remind me. - self.__writeCONFA__() - self.__writeCONFB__() - self.I2C.writeBulk(self.ADDRESS,[self.MODE,0]) #enable continuous measurement mode - - def __writeCONFB__(self): - self.I2C.writeBulk(self.ADDRESS,[self.CONFB,self.gainValue<<5]) #set gain - - def __writeCONFA__(self): - self.I2C.writeBulk(self.ADDRESS,[self.CONFA,(self.dataOutputRate<<2)|(self.samplesToAverage<<5)|(self.measurementConf)]) - - def setSamplesToAverage(self,num): - self.samplesToAverage=self.samplesToAverage_choices.index(num) - self.__writeCONFA__() - - def setDataOutputRate(self,rate): - self.dataOutputRate=self.dataOutputRate_choices.index(rate) - self.__writeCONFA__() - - def setGain(self,gain): - self.gainValue = self.gain_choices.index(gain) - self.__writeCONFB__() - - def getVals(self,addr,bytes): - vals = self.I2C.readBulk(self.ADDRESS,addr,bytes) - return vals - - def getRaw(self): - vals=self.getVals(0x03,6) - if vals: - if len(vals)==6: - return [int16(vals[a*2]<<8|vals[a*2+1])/self.scaling[self.gainValue] for a in range(3)] - else: - return False - else: - return False - + CONFA = 0x00 + CONFB = 0x01 + MODE = 0x02 + STATUS = 0x09 + + # --------CONFA register bits. 0x00----------- + samplesToAverage = 0 + samplesToAverage_choices = [1, 2, 4, 8] + + dataOutputRate = 6 + dataOutputRate_choices = [0.75, 1.5, 3, 7.5, 15, 30, 75] + + measurementConf = 0 + + # --------CONFB register bits. 0x01----------- + gainValue = 7 # least sensitive + gain_choices = [8, 7, 6, 5, 4, 3, 2, 1] + scaling = [1370., 1090., 820., 660., 440., 390., 330., 230.] + + # --------------Parameters-------------------- + # This must be defined in order to let GUIs automatically create menus + # 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': None, + 'setSamplesToAverage': samplesToAverage_choices, + 'setDataOutputRate': dataOutputRate_choices, + 'setGain': gain_choices, + } + ADDRESS = 0x1E + name = 'Magnetometer' + NUMPLOTS = 3 + PLOTNAMES = ['Bx', 'By', 'Bz'] + + def __init__(self, I2C, **args): + self.I2C = I2C + self.ADDRESS = args.get('address', self.ADDRESS) + self.name = 'Magnetometer' + ''' + try: + print 'switching baud to 400k' + self.I2C.configI2C(400e3) + except: + print 'FAILED TO CHANGE BAUD RATE' + ''' + self.init() + + def init(self): + self.__writeCONFA__() + self.__writeCONFB__() + self.I2C.writeBulk(self.ADDRESS, [self.MODE, 0]) # enable continuous measurement mode + + def __writeCONFB__(self): + self.I2C.writeBulk(self.ADDRESS, [self.CONFB, self.gainValue << 5]) # set gain + + def __writeCONFA__(self): + self.I2C.writeBulk(self.ADDRESS, [self.CONFA, (self.dataOutputRate << 2) | (self.samplesToAverage << 5) | ( + self.measurementConf)]) + + def setSamplesToAverage(self, num): + self.samplesToAverage = self.samplesToAverage_choices.index(num) + self.__writeCONFA__() + + def setDataOutputRate(self, rate): + self.dataOutputRate = self.dataOutputRate_choices.index(rate) + self.__writeCONFA__() + + def setGain(self, gain): + self.gainValue = self.gain_choices.index(gain) + self.__writeCONFB__() + + def getVals(self, addr, numbytes): + vals = self.I2C.readBulk(self.ADDRESS, addr, numbytes) + return vals + + def getRaw(self): + vals = self.getVals(0x03, 6) + if vals: + if len(vals) == 6: + return [int16(vals[a * 2] << 8 | vals[a * 2 + 1]) / self.scaling[self.gainValue] for a in range(3)] + else: + return False + else: + return False + if __name__ == "__main__": - from PSL import sciencelab - I= sciencelab.connect() - I.set_sine1(.5) - A = connect(I.I2C) - A.setGain(2) - t,x,y,z = I.I2C.capture(A.ADDRESS,0x03,6,400,10000,'int') - #print (t,x,y,z) - from pylab import * - plot(t,x) - plot(t,y) - plot(t,z) - show() + from PSL import sciencelab + + I = sciencelab.connect() + I.set_sine1(.5) + A = connect(I.I2C) + A.setGain(2) + t, x, y, z = I.I2C.capture(A.ADDRESS, 0x03, 6, 400, 10000, 'int') + # print (t,x,y,z) + from pylab import * + + plot(t, x) + plot(t, y) + plot(t, z) + show() diff --git a/PSL/SENSORS/Kalman.py b/PSL/SENSORS/Kalman.py index 9b034419..ec136a87 100644 --- a/PSL/SENSORS/Kalman.py +++ b/PSL/SENSORS/Kalman.py @@ -1,20 +1,21 @@ class KalmanFilter(object): - ''' - Credits:http://scottlobdell.me/2014/08/kalman-filtering-python-reading-sensor-input/ - ''' - def __init__(self, process_variance, estimated_measurement_variance): - self.process_variance = process_variance - self.estimated_measurement_variance = estimated_measurement_variance - self.posteri_estimate = 0.0 - self.posteri_error_estimate = 1.0 + ''' + Credits:http://scottlobdell.me/2014/08/kalman-filtering-python-reading-sensor-input/ + ''' - def input_latest_noisy_measurement(self, measurement): - priori_estimate = self.posteri_estimate - priori_error_estimate = self.posteri_error_estimate + self.process_variance + def __init__(self, process_variance, estimated_measurement_variance): + self.process_variance = process_variance + self.estimated_measurement_variance = estimated_measurement_variance + self.posteri_estimate = 0.0 + self.posteri_error_estimate = 1.0 - blending_factor = priori_error_estimate / (priori_error_estimate + self.estimated_measurement_variance) - self.posteri_estimate = priori_estimate + blending_factor * (measurement - priori_estimate) - self.posteri_error_estimate = (1 - blending_factor) * priori_error_estimate + def input_latest_noisy_measurement(self, measurement): + priori_estimate = self.posteri_estimate + priori_error_estimate = self.posteri_error_estimate + self.process_variance - def get_latest_estimated_measurement(self): - return self.posteri_estimate + blending_factor = priori_error_estimate / (priori_error_estimate + self.estimated_measurement_variance) + self.posteri_estimate = priori_estimate + blending_factor * (measurement - priori_estimate) + self.posteri_error_estimate = (1 - blending_factor) * priori_error_estimate + + def get_latest_estimated_measurement(self): + return self.posteri_estimate diff --git a/PSL/SENSORS/MF522.py b/PSL/SENSORS/MF522.py index f200c312..1b304547 100644 --- a/PSL/SENSORS/MF522.py +++ b/PSL/SENSORS/MF522.py @@ -1,155 +1,148 @@ # -*- coding: utf-8 -*- # MF522 - Software stack to access the MF522 RFID reader via FOSSASIA PSLab # +from __future__ import print_function +def connect(I, cs): + return MF522(I, cs) -from __future__ import print_function -from PSL import sciencelab -import time -def connect(I,cs): - return MF522(I,cs) class MF522: - #Constants from https://github.com/miguelbalboa/rfid ( Open source License : UNLICENSE) - CommandReg = 0x01 << 1 # starts and stops command execution - ComIEnReg = 0x02 << 1 # enable and disable interrupt request control bits - DivIEnReg = 0x03 << 1 # enable and disable interrupt request control bits - ComIrqReg = 0x04 << 1 # interrupt request bits - DivIrqReg = 0x05 << 1 # interrupt request bits - ErrorReg = 0x06 << 1 # error bits showing the error status of the last command executed - Status1Reg = 0x07 << 1 # communication status bits - Status2Reg = 0x08 << 1 # receiver and transmitter status bits - FIFODataReg = 0x09 << 1 # input and output of 64 byte FIFO buffer - FIFOLevelReg = 0x0A << 1 # number of bytes stored in the FIFO buffer - WaterLevelReg = 0x0B << 1 # level for FIFO underflow and overflow warning - ControlReg = 0x0C << 1 # miscellaneous control registers - BitFramingReg = 0x0D << 1 # adjustments for bit-oriented frames - CollReg = 0x0E << 1 # bit position of the first bit-collision detected on the RF sciencelab - - ModeReg = 0x11 << 1 # defines general modes for transmitting and receiving - TxModeReg = 0x12 << 1 # defines transmission data rate and framing - RxModeReg = 0x13 << 1 # defines reception data rate and framing - TxControlReg = 0x14 << 1 # controls the logical behavior of the antenna driver pins TX1 and TX2 - TxASKReg = 0x15 << 1 # controls the setting of the transmission modulation - TxSelReg = 0x16 << 1 # selects the internal sources for the antenna driver - RxSelReg = 0x17 << 1 # selects internal receiver settings - RxThresholdReg = 0x18 << 1 # selects thresholds for the bit decoder - DemodReg = 0x19 << 1 # defines demodulator settings - MfTxReg = 0x1C << 1 # controls some MIFARE communication transmit parameters - MfRxReg = 0x1D << 1 # controls some MIFARE communication receive parameters - SerialSpeedReg = 0x1F << 1 # selects the speed of the serial UART sciencelab - - CRCResultRegH = 0x21 << 1 # shows the MSB and LSB values of the CRC calculation - CRCResultRegL = 0x22 << 1 - ModWidthReg = 0x24 << 1 # controls the ModWidth setting? - RFCfgReg = 0x26 << 1 # configures the receiver gain - GsNReg = 0x27 << 1 # selects the conductance of the antenna driver pins TX1 and TX2 for modulation - CWGsPReg = 0x28 << 1 # defines the conductance of the p-driver output during periods of no modulation - ModGsPReg = 0x29 << 1 # defines the conductance of the p-driver output during periods of modulation - TModeReg = 0x2A << 1 # defines settings for the internal timer - TPrescalerReg = 0x2B << 1 # the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. - TReloadRegH = 0x2C << 1 # defines the 16-bit timer reload value - TReloadRegL = 0x2D << 1 - TCounterValueRegH = 0x2E << 1 # shows the 16-bit timer value - TCounterValueRegL = 0x2F << 1 - - TestSel1Reg = 0x31 << 1 # general test signal configuration - TestSel2Reg = 0x32 << 1 # general test signal configuration - TestPinEnReg = 0x33 << 1 # enables pin output driver on pins D1 to D7 - TestPinValueReg = 0x34 << 1 # defines the values for D1 to D7 when it is used as an I/O bus - TestBusReg = 0x35 << 1 # shows the status of the internal test bus - AutoTestReg = 0x36 << 1 # controls the digital self test - VersionReg = 0x37 << 1 # shows the software version - AnalogTestReg = 0x38 << 1 # controls the pins AUX1 and AUX2 - TestDAC1Reg = 0x39 << 1 # defines the test value for TestDAC1 - TestDAC2Reg = 0x3A << 1 # defines the test value for TestDAC2 - TestADCReg = 0x3B << 1 # shows the value of ADC I and Q channels + # Constants from https://github.com/miguelbalboa/rfid ( Open source License : UNLICENSE) + CommandReg = 0x01 << 1 # starts and stops command execution + ComIEnReg = 0x02 << 1 # enable and disable interrupt request control bits + DivIEnReg = 0x03 << 1 # enable and disable interrupt request control bits + ComIrqReg = 0x04 << 1 # interrupt request bits + DivIrqReg = 0x05 << 1 # interrupt request bits + ErrorReg = 0x06 << 1 # error bits showing the error status of the last command executed + Status1Reg = 0x07 << 1 # communication status bits + Status2Reg = 0x08 << 1 # receiver and transmitter status bits + FIFODataReg = 0x09 << 1 # input and output of 64 byte FIFO buffer + FIFOLevelReg = 0x0A << 1 # number of bytes stored in the FIFO buffer + WaterLevelReg = 0x0B << 1 # level for FIFO underflow and overflow warning + ControlReg = 0x0C << 1 # miscellaneous control registers + BitFramingReg = 0x0D << 1 # adjustments for bit-oriented frames + CollReg = 0x0E << 1 # bit position of the first bit-collision detected on the RF sciencelab + + ModeReg = 0x11 << 1 # defines general modes for transmitting and receiving + TxModeReg = 0x12 << 1 # defines transmission data rate and framing + RxModeReg = 0x13 << 1 # defines reception data rate and framing + TxControlReg = 0x14 << 1 # controls the logical behavior of the antenna driver pins TX1 and TX2 + TxASKReg = 0x15 << 1 # controls the setting of the transmission modulation + TxSelReg = 0x16 << 1 # selects the internal sources for the antenna driver + RxSelReg = 0x17 << 1 # selects internal receiver settings + RxThresholdReg = 0x18 << 1 # selects thresholds for the bit decoder + DemodReg = 0x19 << 1 # defines demodulator settings + MfTxReg = 0x1C << 1 # controls some MIFARE communication transmit parameters + MfRxReg = 0x1D << 1 # controls some MIFARE communication receive parameters + SerialSpeedReg = 0x1F << 1 # selects the speed of the serial UART sciencelab + + CRCResultRegH = 0x21 << 1 # shows the MSB and LSB values of the CRC calculation + CRCResultRegL = 0x22 << 1 + ModWidthReg = 0x24 << 1 # controls the ModWidth setting? + RFCfgReg = 0x26 << 1 # configures the receiver gain + GsNReg = 0x27 << 1 # selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CWGsPReg = 0x28 << 1 # defines the conductance of the p-driver output during periods of no modulation + ModGsPReg = 0x29 << 1 # defines the conductance of the p-driver output during periods of modulation + TModeReg = 0x2A << 1 # defines settings for the internal timer + TPrescalerReg = 0x2B << 1 # the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + TReloadRegH = 0x2C << 1 # defines the 16-bit timer reload value + TReloadRegL = 0x2D << 1 + TCounterValueRegH = 0x2E << 1 # shows the 16-bit timer value + TCounterValueRegL = 0x2F << 1 + + TestSel1Reg = 0x31 << 1 # general test signal configuration + TestSel2Reg = 0x32 << 1 # general test signal configuration + TestPinEnReg = 0x33 << 1 # enables pin output driver on pins D1 to D7 + TestPinValueReg = 0x34 << 1 # defines the values for D1 to D7 when it is used as an I/O bus + TestBusReg = 0x35 << 1 # shows the status of the internal test bus + AutoTestReg = 0x36 << 1 # controls the digital self test + VersionReg = 0x37 << 1 # shows the software version + AnalogTestReg = 0x38 << 1 # controls the pins AUX1 and AUX2 + TestDAC1Reg = 0x39 << 1 # defines the test value for TestDAC1 + TestDAC2Reg = 0x3A << 1 # defines the test value for TestDAC2 + TestADCReg = 0x3B << 1 # shows the value of ADC I and Q channels # MFRC522 commands. Described in chapter 10 of the datasheet. - PCD_Idle = 0x00 #no action, cancels current command execution - PCD_Mem = 0x01 #stores 25 bytes into the internal buffer - PCD_GenerateRandomID = 0x02 #generates a 10-byte random ID number - PCD_CalcCRC = 0x03 #activates the CRC coprocessor or performs a self test - PCD_Transmit = 0x04 # transmits data from the FIFO buffer - PCD_NoCmdChange = 0x07 - PCD_Receive = 0x08 #activates the receiver circuits - PCD_Transceive = 0x0C #transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission - PCD_MFAuthent = 0x0E #performs the MIFARE standard authentication as a reader - PCD_SoftReset = 0x0F #resets the MFRC522 - - RxGain_18dB = 0x00 << 4 # 000b - 18 dB, minimum - RxGain_23dB = 0x01 << 4 # 001b - 23 dB - RxGain_18dB_2 = 0x02 << 4 # 010b - 18 dB, it seems 010b is a duplicate for 000b - RxGain_23dB_2 = 0x03 << 4 # 011b - 23 dB, it seems 011b is a duplicate for 001b - RxGain_33dB = 0x04 << 4 # 100b - 33 dB, average, and typical default - RxGain_38dB = 0x05 << 4 # 101b - 38 dB - RxGain_43dB = 0x06 << 4 # 110b - 43 dB - RxGain_48dB = 0x07 << 4 # 111b - 48 dB, maximum - RxGain_min = 0x00 << 4 # 000b - 18 dB, minimum, convenience for RxGain_18dB - RxGain_avg = 0x04 << 4 # 100b - 33 dB, average, convenience for RxGain_33dB - RxGain_max = 0x07 << 4 # 111b - 48 dB, maximum, convenience for RxGain_48dB + PCD_Idle = 0x00 # no action, cancels current command execution + PCD_Mem = 0x01 # stores 25 bytes into the internal buffer + PCD_GenerateRandomID = 0x02 # generates a 10-byte random ID number + PCD_CalcCRC = 0x03 # activates the CRC coprocessor or performs a self test + PCD_Transmit = 0x04 # transmits data from the FIFO buffer + PCD_NoCmdChange = 0x07 + PCD_Receive = 0x08 # activates the receiver circuits + PCD_Transceive = 0x0C # transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MFAuthent = 0x0E # performs the MIFARE standard authentication as a reader + PCD_SoftReset = 0x0F # resets the MFRC522 + + RxGain_18dB = 0x00 << 4 # 000b - 18 dB, minimum + RxGain_23dB = 0x01 << 4 # 001b - 23 dB + RxGain_18dB_2 = 0x02 << 4 # 010b - 18 dB, it seems 010b is a duplicate for 000b + RxGain_23dB_2 = 0x03 << 4 # 011b - 23 dB, it seems 011b is a duplicate for 001b + RxGain_33dB = 0x04 << 4 # 100b - 33 dB, average, and typical default + RxGain_38dB = 0x05 << 4 # 101b - 38 dB + RxGain_43dB = 0x06 << 4 # 110b - 43 dB + RxGain_48dB = 0x07 << 4 # 111b - 48 dB, maximum + RxGain_min = 0x00 << 4 # 000b - 18 dB, minimum, convenience for RxGain_18dB + RxGain_avg = 0x04 << 4 # 100b - 33 dB, average, convenience for RxGain_33dB + RxGain_max = 0x07 << 4 # 111b - 48 dB, maximum, convenience for RxGain_48dB # The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) - PICC_CMD_REQA = 0x26 # REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection - PICC_CMD_WUPA = 0x52 # Wake-UP command, prepare for anticollision or selection. 7 bit frame. - PICC_CMD_CT = 0x88 # Cascade Tag. Not really a command, but used during anti collision. - PICC_CMD_SEL_CL1 = 0x93 # Anti collision/Select, Cascade Level 1 - PICC_CMD_SEL_CL2 = 0x95 # Anti collision/Select, Cascade Level 2 - PICC_CMD_SEL_CL3 = 0x97 # Anti collision/Select, Cascade Level 3 - PICC_CMD_HLTA = 0x50 # HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + PICC_CMD_REQA = 0x26 # REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection + PICC_CMD_WUPA = 0x52 # Wake-UP command, prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88 # Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93 # Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95 # Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97 # Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50 # HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. # The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) # Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. # The read/write commands can also be used for MIFARE Ultralight. - PICC_CMD_MF_AUTH_KEY_A = 0x60 # Perform authentication with Key A - PICC_CMD_MF_AUTH_KEY_B = 0x61 # Perform authentication with Key B - PICC_CMD_MF_READ = 0x30 # Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. - PICC_CMD_MF_WRITE = 0xA0 # Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. - PICC_CMD_MF_DECREMENT = 0xC0 # Decrements the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_INCREMENT = 0xC1 # Increments the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_RESTORE = 0xC2 # Reads the contents of a block into the internal data register. - PICC_CMD_MF_TRANSFER = 0xB0 # Writes the contents of the internal data register to a block. - - + PICC_CMD_MF_AUTH_KEY_A = 0x60 # Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61 # Perform authentication with Key B + PICC_CMD_MF_READ = 0x30 # Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0 # Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = 0xC0 # Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = 0xC1 # Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2 # Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0 # Writes the contents of the internal data register to a block. NRSTPD = 22 MAX_LEN = 16 - MI_OK = 0 + MI_OK = 0 MI_NOTAGERR = 1 - MI_ERR = 2 + MI_ERR = 2 - PCD_CALCCRC = 0x03 + PCD_CALCCRC = 0x03 - PICC_REQIDL = 0x26 - PICC_REQALL = 0x52 - PICC_ANTICOLL = 0x93 + PICC_REQIDL = 0x26 + PICC_REQALL = 0x52 + PICC_ANTICOLL = 0x93 PICC_SElECTTAG = 0x93 PICC_AUTHENT1A = 0x60 PICC_AUTHENT1B = 0x61 - PICC_READ = 0x30 - PICC_WRITE = 0xA0 + PICC_READ = 0x30 + PICC_WRITE = 0xA0 PICC_DECREMENT = 0xC0 PICC_INCREMENT = 0xC1 - PICC_RESTORE = 0xC2 - PICC_TRANSFER = 0xB0 - PICC_HALT = 0x50 - - + PICC_RESTORE = 0xC2 + PICC_TRANSFER = 0xB0 + PICC_HALT = 0x50 # The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) # The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. - PICC_CMD_UL_WRITE = 0xA2 #Writes one 4 byte page to the PICC. - - MF_ACK = 0xA # The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. - MF_KEY_SIZE = 6 # A Mifare Crypto1 key is 6 bytes. + PICC_CMD_UL_WRITE = 0xA2 # Writes one 4 byte page to the PICC. + MF_ACK = 0xA # The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. + MF_KEY_SIZE = 6 # A Mifare Crypto1 key is 6 bytes. - def __init__(self,I,cs='CS1'): - self.cs=cs + def __init__(self, I, cs='CS1'): + self.cs = cs self.I = I - self.I.SPI.set_parameters(2,1,1,0) + self.I.SPI.set_parameters(2, 1, 1, 0) if not self.reset(): - self.connected=False + self.connected = False return None self.write(self.TModeReg, 0x80) self.write(self.TPrescalerReg, 0xA9) @@ -159,72 +152,63 @@ def __init__(self,I,cs='CS1'): self.write(self.TxASKReg, 0x40) self.write(self.ModeReg, 0x3D) - #Enable the antenna + # Enable the antenna self.enableAntenna() self.connected = True - def MFRC522_Init(self): - GPIO.output(self.NRSTPD, 1) - self.MFRC522_Reset(); - self.write(self.TModeReg, 0x8D) - self.write(self.TPrescalerReg, 0x3E) - self.write(self.TReloadRegL, 30) - self.write(self.TReloadRegH, 0) - - self.write(self.TxAutoReg, 0x40) - self.write(self.ModeReg, 0x3D) - self.AntennaOn() - def enableAntenna(self): val = self.read(self.TxControlReg); if ((val & 0x03) != 0x03): self.write(self.TxControlReg, val | 0x03); def reset(self): - self.write(self.CommandReg,self.PCD_SoftReset) - s=time.time() - while (self.read(self.CommandReg) & (1<<4)): - print ('wait') + self.write(self.CommandReg, self.PCD_SoftReset) + s = time.time() + while (self.read(self.CommandReg) & (1 << 4)): + print('wait') time.sleep(0.1) if time.time() - s > .5: return False return True - def write(self,register,val): - self.I.SPI.set_cs(self.cs,0) - ret = self.I.SPI.send16(((register&0x7F)<<8)|val) - self.I.SPI.set_cs(self.cs,1) - return ret&0xFF + def write(self, register, val): + self.I.SPI.set_cs(self.cs, 0) + ret = self.I.SPI.send16(((register & 0x7F) << 8) | val) + self.I.SPI.set_cs(self.cs, 1) + return ret & 0xFF - def read(self,register): - self.I.SPI.set_cs(self.cs,0) - ret = self.I.SPI.send16((register|0x80)<<8) - self.I.SPI.set_cs(self.cs,1) - return ret&0xFF + def read(self, register): + self.I.SPI.set_cs(self.cs, 0) + ret = self.I.SPI.send16((register | 0x80) << 8) + self.I.SPI.set_cs(self.cs, 1) + return ret & 0xFF - def readMany(self,register,total): - self.I.SPI.set_cs(self.cs,0) + def readMany(self, register, total): + self.I.SPI.set_cs(self.cs, 0) self.I.SPI.send8(register) vals = [] - for a in range(total-1): - vals.append( I.SPI.send8(register) ) - vals.append( I.SPI.send8(0) ) - self.I.SPI.set_cs(self.cs,1) + for a in range(total - 1): + vals.append(I.SPI.send8(register)) + vals.append(I.SPI.send8(0)) + self.I.SPI.set_cs(self.cs, 1) return vals - def getStatus(self): return self.read(self.Status1Reg) def getVersion(self): ver = self.read(self.VersionReg) - if ver==0x88: print ('Cloned version: Fudan Semiconductors') - elif ver==0x90: print ('version 1.0') - elif ver==0x91: print ('version 1.0') - elif ver==0x92: print ('version 2.0') - else: print ('Unknown version ',ver) + if ver == 0x88: + print('Cloned version: Fudan Semiconductors') + elif ver == 0x90: + print('version 1.0') + elif ver == 0x91: + print('version 1.0') + elif ver == 0x92: + print('version 2.0') + else: + print('Unknown version ', ver) return ver - def SetBitMask(self, reg, mask): tmp = self.read(reg) self.write(reg, tmp | mask) @@ -233,7 +217,7 @@ def ClearBitMask(self, reg, mask): tmp = self.read(reg); self.write(reg, tmp & (~mask)) - def MFRC522_ToCard(self,command,sendData): + def MFRC522_ToCard(self, command, sendData): returnedData = [] backLen = 0 status = self.MI_ERR @@ -250,7 +234,7 @@ def MFRC522_ToCard(self,command,sendData): irqEn = 0x77 waitIRq = 0x30 - self.write(self.ComIEnReg, irqEn|0x80) + self.write(self.ComIEnReg, irqEn | 0x80) self.ClearBitMask(self.ComIrqReg, 0x80) self.SetBitMask(self.FIFOLevelReg, 0x80) @@ -267,39 +251,38 @@ def MFRC522_ToCard(self,command,sendData): while True: n = self.read(self.ComIrqReg) i = i - 1 - if ~((i!=0) and ~(n&0x01) and ~(n&waitIRq)): + if ~((i != 0) and ~(n & 0x01) and ~(n & waitIRq)): break self.ClearBitMask(self.BitFramingReg, 0x80) if i != 0: - if (self.read(self.ErrorReg) & 0x1B)==0x00: + if (self.read(self.ErrorReg) & 0x1B) == 0x00: status = self.MI_OK if n & irqEn & 0x01: - status = self.MI_NOTAGERR + status = self.MI_NOTAGERR if command == self.PCD_Transceive: - n = self.read(self.FIFOLevelReg) - lastBits = self.read(self.ControlReg) & 0x07 - if lastBits != 0: - backLen = (n-1)*8 + lastBits - else: - backLen = n*8 - - if n == 0: - n = 1 - if n > self.MAX_LEN: - n = self.MAX_LEN - - i = 0 - while i self.MAX_LEN: + n = self.MAX_LEN + + i = 0 + while i < n: + returnedData.append(self.read(self.FIFODataReg)) + i = i + 1; else: status = self.MI_ERR - return (status,returnedData,backLen) - + return (status, returnedData, backLen) def MFRC522_Request(self, reqMode): status = None @@ -309,11 +292,11 @@ def MFRC522_Request(self, reqMode): self.write(self.BitFramingReg, 0x07) TagType.append(reqMode); - (status,returnedData,backBits) = self.MFRC522_ToCard(self.PCD_Transceive, TagType) + (status, returnedData, backBits) = self.MFRC522_ToCard(self.PCD_Transceive, TagType) if ((status != self.MI_OK) | (backBits != 0x10)): status = self.MI_ERR - return (status,backBits) + return (status, backBits) def MFRC522_Anticoll(self): returnedData = [] @@ -326,12 +309,12 @@ def MFRC522_Anticoll(self): serNum.append(self.PICC_ANTICOLL) serNum.append(0x20) - (status,returnedData,backBits) = self.MFRC522_ToCard(self.PCD_Transceive,serNum) + (status, returnedData, backBits) = self.MFRC522_ToCard(self.PCD_Transceive, serNum) - if(status == self.MI_OK): + if (status == self.MI_OK): i = 0 - if len(returnedData)==5: - while i<4: + if len(returnedData) == 5: + while i < 4: serNumCheck = serNumCheck ^ returnedData[i] i = i + 1 if serNumCheck != returnedData[i]: @@ -339,8 +322,8 @@ def MFRC522_Anticoll(self): else: status = self.MI_ERR - return (status,returnedData) - + return (status, returnedData) + def CalulateCRC(self, pIndata): self.ClearBitMask(self.DivIrqReg, 0x04) self.SetBitMask(self.FIFOLevelReg, 0x80); @@ -349,20 +332,20 @@ def CalulateCRC(self, pIndata): self.write(self.CommandReg, self.PCD_CALCCRC) for i in range(0xFF): n = self.read(self.DivIrqReg) - if (n&0x04): + if (n & 0x04): break pOutData = [] pOutData.append(self.read(self.CRCResultRegL)) pOutData.append(self.read(self.CRCResultRegH)) return pOutData - + def MFRC522_SelectTag(self, serNum): returnedData = [] buf = [] buf.append(self.PICC_SElECTTAG) buf.append(0x70) i = 0 - while i<5: + while i < 5: buf.append(serNum[i]) i = i + 1 pOut = self.CalulateCRC(buf) @@ -374,7 +357,7 @@ def MFRC522_SelectTag(self, serNum): return returnedData[0] else: return 0 - + def MFRC522_Auth(self, authMode, BlockAddr, Sectorkey, serNum): buff = [] # First byte should be the authMode (A or B) @@ -383,24 +366,24 @@ def MFRC522_Auth(self, authMode, BlockAddr, Sectorkey, serNum): buff.append(BlockAddr) # Now we need to append the authKey which usually is 6 bytes of 0xFF i = 0 - while(i < len(Sectorkey)): + while (i < len(Sectorkey)): buff.append(Sectorkey[i]) i = i + 1 i = 0 # Next we append the first 4 bytes of the UID - while(i < 4): + while (i < 4): buff.append(serNum[i]) - i = i +1 + i = i + 1 # Now we start the authentication itself - (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_MFAuthent,buff) + (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_MFAuthent, buff) # Check if an error occurred - if not(status == self.MI_OK): - print ("AUTH ERROR!!") + if not (status == self.MI_OK): + print("AUTH ERROR!!") if not (self.read(self.Status2Reg) & 0x08) != 0: - print ("AUTH ERROR(status2reg & 0x08) != 0") + print("AUTH ERROR(status2reg & 0x08) != 0") # Return the status return status @@ -417,8 +400,8 @@ def MFRC522_Read(self, blockAddr): recvData.append(pOut[0]) recvData.append(pOut[1]) (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_Transceive, recvData) - if not(status == self.MI_OK): - print ("Error while reading!") + if not (status == self.MI_OK): + print("Error while reading!") i = 0 return returnedData @@ -430,10 +413,10 @@ def MFRC522_Write(self, blockAddr, writeData): buff.append(crc[0]) buff.append(crc[1]) (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_Transceive, buff) - if not(status == self.MI_OK) or not(backLen == 4) or not((returnedData[0] & 0x0F) == 0x0A): + if not (status == self.MI_OK) or not (backLen == 4) or not ((returnedData[0] & 0x0F) == 0x0A): status = self.MI_ERR - print (str(backLen)+" returnedData &0x0F == 0x0A "+str(returnedData[0]&0x0F)) + print(str(backLen) + " returnedData &0x0F == 0x0A " + str(returnedData[0] & 0x0F)) if status == self.MI_OK: i = 0 buf = [] @@ -443,11 +426,11 @@ def MFRC522_Write(self, blockAddr, writeData): crc = self.CalulateCRC(buf) buf.append(crc[0]) buf.append(crc[1]) - (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_Transceive,buf) - if not(status == self.MI_OK) or not(backLen == 4) or not((returnedData[0] & 0x0F) == 0x0A): - print ("Error while writing") + (status, returnedData, backLen) = self.MFRC522_ToCard(self.PCD_Transceive, buf) + if not (status == self.MI_OK) or not (backLen == 4) or not ((returnedData[0] & 0x0F) == 0x0A): + print("Error while writing") if status == self.MI_OK: - print ("Data written") + print("Data written") def MFRC522_DumpClassic1K(self, key, uid): i = 0 @@ -457,33 +440,34 @@ def MFRC522_DumpClassic1K(self, key, uid): if status == self.MI_OK: self.MFRC522_Read(i) else: - print ("Authentication error") - i = i+1 - + print("Authentication error") + i = i + 1 if __name__ == "__main__": from PSL import sciencelab - I= sciencelab.connect() - A = MF522(I,'CS1') - ret = A.getStatus() - print (ret,hex(ret),bin(ret)) + + I = sciencelab.connect() + A = MF522(I, 'CS1') + ret = A.getStatus() + print(ret, hex(ret), bin(ret)) A.getVersion() import time + while 1: - (status,TagType) = A.MFRC522_Request(A.PICC_CMD_REQA) + (status, TagType) = A.MFRC522_Request(A.PICC_CMD_REQA) if status == A.MI_OK: - print ("Card detected") - (status,uid) = A.MFRC522_Anticoll() + print("Card detected") + (status, uid) = A.MFRC522_Anticoll() if status == A.MI_OK: - print ("Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3])) - key = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] + print("Card read UID: " + str(uid[0]) + "," + str(uid[1]) + "," + str(uid[2]) + "," + str(uid[3])) + key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] A.MFRC522_SelectTag(uid) status = A.MFRC522_Auth(A.PICC_AUTHENT1A, 8, key, uid) if status == A.MI_OK: - A.MFRC522_Read(8) - # Variable for the data to write + print(A.MFRC522_Read(8)) ''' + # Variable for the data to write data = [] # Fill the data with 0xFF for x in range(0,16): @@ -503,10 +487,8 @@ def MFRC522_DumpClassic1K(self, key, uid): ''' A.MFRC522_StopCrypto1() else: - print ("Authentication error") + print("Authentication error") else: - print ('not detected') - -#A.reset() - + print('not detected') +# A.reset() diff --git a/PSL/SENSORS/MLX90614.py b/PSL/SENSORS/MLX90614.py index 1d9bfa57..b19b6f43 100644 --- a/PSL/SENSORS/MLX90614.py +++ b/PSL/SENSORS/MLX90614.py @@ -1,70 +1,70 @@ from __future__ import print_function -from numpy import int16 -def connect(route,**args): - return MLX90614(route,**args) + + +def connect(route, **args): + return MLX90614(route, **args) + class MLX90614(): - NUMPLOTS=1 - PLOTNAMES=['Temp'] - ADDRESS = 0x5A - name = 'PIR temperature' - def __init__(self,I2C,**args): - self.I2C=I2C - self.ADDRESS = args.get('address',self.ADDRESS) - self.OBJADDR=0x07 - self.AMBADDR=0x06 + NUMPLOTS = 1 + PLOTNAMES = ['Temp'] + ADDRESS = 0x5A + name = 'PIR temperature' - self.source=self.OBJADDR - - self.name = 'Passive IR temperature sensor' - self.params={'readReg':range(0x20), - 'select_source':['object temperature','ambient temperature']} - - try: - print ('switching baud to 100k') - self.I2C.configI2C(100e3) - except: - print ('FAILED TO CHANGE BAUD RATE') + def __init__(self, I2C, **args): + self.I2C = I2C + self.ADDRESS = args.get('address', self.ADDRESS) + self.OBJADDR = 0x07 + self.AMBADDR = 0x06 + self.source = self.OBJADDR - def select_source(self,source): - if source=='object temperature': - self.source = self.OBJADDR - elif source=='ambient temperature': - self.source = self.AMBADDR + self.name = 'Passive IR temperature sensor' + self.params = {'readReg': {'dataType': 'integer', 'min': 0, 'max': 0x20, 'prefix': 'Addr: '}, + 'select_source': ['object temperature', 'ambient temperature']} + try: + print('switching baud to 100k') + self.I2C.configI2C(100e3) + except Exception as e: + print('FAILED TO CHANGE BAUD RATE', e.message) - def readReg(self,addr): - x= self.getVals(addr,2) - print (hex(addr),hex(x[0]|(x[1]<<8))) + def select_source(self, source): + if source == 'object temperature': + self.source = self.OBJADDR + elif source == 'ambient temperature': + self.source = self.AMBADDR + def readReg(self, addr): + x = self.getVals(addr, 2) + print(hex(addr), hex(x[0] | (x[1] << 8))) - def getVals(self,addr,bytes): - vals = self.I2C.readBulk(self.ADDRESS,addr,bytes) - return vals + def getVals(self, addr, numbytes): + vals = self.I2C.readBulk(self.ADDRESS, addr, numbytes) + return vals - def getRaw(self): - vals=self.getVals(self.source,3) - if vals: - if len(vals)==3: - return [((((vals[1]&0x007f)<<8)+vals[0])*0.02)-0.01 - 273.15] - else: - return False - else: - return False + def getRaw(self): + vals = self.getVals(self.source, 3) + if vals: + if len(vals) == 3: + return [((((vals[1] & 0x007f) << 8) + vals[0]) * 0.02) - 0.01 - 273.15] + else: + return False + else: + return False - def getObjectTemperature(self): - self.source = self.OBJADDR - val=self.getRaw() - if val: - return val[0] - else: - return False + def getObjectTemperature(self): + self.source = self.OBJADDR + val = self.getRaw() + if val: + return val[0] + else: + return False - def getAmbientTemperature(self): - self.source = self.AMBADDR - val=self.getRaw() - if val: - return val[0] - else: - return False + def getAmbientTemperature(self): + self.source = self.AMBADDR + val = self.getRaw() + if val: + return val[0] + else: + return False diff --git a/PSL/SENSORS/MPU6050.py b/PSL/SENSORS/MPU6050.py index e9313173..bf6beb76 100644 --- a/PSL/SENSORS/MPU6050.py +++ b/PSL/SENSORS/MPU6050.py @@ -1,134 +1,134 @@ -from numpy import int16,std +from numpy import int16, std from PSL.SENSORS.Kalman import KalmanFilter -def connect(route,**args): - return MPU6050(route,**args) + +def connect(route, **args): + return MPU6050(route, **args) + class MPU6050(): - ''' - 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 - - ''' - 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 - 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':['Go'],'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(True) - 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,x): - 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): - 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): - vals=self.getVals(0x41,6) - t=int16(vals[0]<<8|vals[1]) - return t/65535. - - def getGyro(self): - 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.] - + ''' + 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 + + ''' + 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 + 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': {'dataType': 'double', 'min': 0, 'max': 1000, 'prefix': 'value: '}} + self.setGyroRange(2000) + self.setAccelRange(16) + self.powerUp() + self.K = None + + def KalmanFilter(self, opt): + if opt == 0: + 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, numbytes): + vals = self.I2C.readBulk(self.ADDRESS, addr, numbytes) + 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): + 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): + vals = self.getVals(0x41, 6) + t = int16(vals[0] << 8 | vals[1]) + return t / 65535. + + def getGyro(self): + 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.] + + 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() + 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/MPU925x.py b/PSL/SENSORS/MPU925x.py new file mode 100644 index 00000000..93cf4ff3 --- /dev/null +++ b/PSL/SENSORS/MPU925x.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8; mode: python; indent-tabs-mode: t; tab-width:4 -*- +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) + 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, numbytes): + return self.I2C.readBulk(self.ADDRESS, addr, numbytes) + + 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 1d1c4974..c514e662 100644 --- a/PSL/SENSORS/SHT21.py +++ b/PSL/SENSORS/SHT21.py @@ -1,90 +1,101 @@ from __future__ import print_function -from numpy import int16 import time -def connect(route,**args): - ''' - route can either be I.I2C , or a radioLink instance - ''' - return SHT21(route,**args) -class SHT21(): - RESET = 0xFE - TEMP_ADDRESS = 0xF3 - HUMIDITY_ADDRESS = 0xF5 - selected=0xF3 - NUMPLOTS=1 - PLOTNAMES = ['Data'] - ADDRESS = 0x40 - name = 'Humidity/Temperature' - def __init__(self,I2C,**args): - self.I2C=I2C - self.ADDRESS = args.get('address',self.ADDRESS) - self.name = 'Humidity/Temperature' - ''' - try: - print ('switching baud to 400k') - self.I2C.configI2C(400e3) - except: - print ('FAILED TO CHANGE BAUD RATE') - ''' - self.params={'selectParameter':['temperature','humidity']} - self.init('') +def connect(route, **args): + ''' + route can either be I.I2C , or a radioLink instance + ''' + return SHT21(route, **args) - def init(self,x): - self.I2C.writeBulk(self.ADDRESS,[self.RESET]) #soft reset - time.sleep(0.1) - - def rawToTemp(self,vals): - if vals: - if len(vals): - v = (vals[0]<<8)|(vals[1]&0xFC) #make integer & remove status bits - v*=175.72; v/= (1<<16); v-=46.85 - return [v] - return False +def rawToTemp(vals): + if vals: + if len(vals): + v = (vals[0] << 8) | (vals[1] & 0xFC) # make integer & remove status bits + v *= 175.72 + v /= (1 << 16) + v -= 46.85 + return [v] + return False - def rawToRH(self,vals): - if vals: - if len(vals): - v = (vals[0]<<8)|(vals[1]&0xFC) #make integer & remove status bits - v*=125.; v/= (1<<16); v-=6 - return [v] - return False - @staticmethod - def _calculate_checksum(data, number_of_bytes): - """5.7 CRC Checksum using the polynomial given in the datasheet - Credits: https://github.com/jaques/sht21_python/blob/master/sht21.py - """ - # CRC - POLYNOMIAL = 0x131 # //P(x)=x^8+x^5+x^4+1 = 100110001 - crc = 0 - # calculates 8-Bit checksum with given polynomial - for byteCtr in range(number_of_bytes): - crc ^= (data[byteCtr]) - for bit in range(8, 0, -1): - if crc & 0x80: - crc = (crc << 1) ^ POLYNOMIAL - else: - crc = (crc << 1) - return crc +def rawToRH(vals): + if vals: + if len(vals): + v = (vals[0] << 8) | (vals[1] & 0xFC) # make integer & remove status bits + v *= 125. + v /= (1 << 16) + v -= 6 + return [v] + return False - def selectParameter(self,param): - if param=='temperature':self.selected=self.TEMP_ADDRESS - elif param=='humidity':self.selected=self.HUMIDITY_ADDRESS +class SHT21(): + RESET = 0xFE + TEMP_ADDRESS = 0xF3 + HUMIDITY_ADDRESS = 0xF5 + selected = 0xF3 + NUMPLOTS = 1 + PLOTNAMES = ['Data'] + ADDRESS = 0x40 + name = 'Humidity/Temperature' - def getRaw(self): - self.I2C.writeBulk(self.ADDRESS,[self.selected]) - if self.selected==self.TEMP_ADDRESS:time.sleep(0.1) - elif self.selected==self.HUMIDITY_ADDRESS:time.sleep(0.05) + def __init__(self, I2C, **args): + self.I2C = I2C + self.ADDRESS = args.get('address', self.ADDRESS) + self.name = 'Humidity/Temperature' + ''' + try: + print ('switching baud to 400k') + self.I2C.configI2C(400e3) + except: + print ('FAILED TO CHANGE BAUD RATE') + ''' + self.params = {'selectParameter': ['temperature', 'humidity'], 'init': None} + self.init() - vals = self.I2C.simpleRead(self.ADDRESS,3) - if vals: - if self._calculate_checksum(vals,2)!=vals[2]: - return False - print (vals) - if self.selected==self.TEMP_ADDRESS:return self.rawToTemp(vals) - elif self.selected==self.HUMIDITY_ADDRESS:return self.rawToRH(vals) - + def init(self): + self.I2C.writeBulk(self.ADDRESS, [self.RESET]) # soft reset + time.sleep(0.1) + + @staticmethod + def _calculate_checksum(data, number_of_bytes): + """5.7 CRC Checksum using the polynomial given in the datasheet + Credits: https://github.com/jaques/sht21_python/blob/master/sht21.py + """ + # CRC + POLYNOMIAL = 0x131 # //P(x)=x^8+x^5+x^4+1 = 100110001 + crc = 0 + # calculates 8-Bit checksum with given polynomial + for byteCtr in range(number_of_bytes): + crc ^= (data[byteCtr]) + for _ in range(8, 0, -1): + if crc & 0x80: + crc = (crc << 1) ^ POLYNOMIAL + else: + crc = (crc << 1) + return crc + + def selectParameter(self, param): + if param == 'temperature': + self.selected = self.TEMP_ADDRESS + elif param == 'humidity': + self.selected = self.HUMIDITY_ADDRESS + + def getRaw(self): + self.I2C.writeBulk(self.ADDRESS, [self.selected]) + if self.selected == self.TEMP_ADDRESS: + time.sleep(0.1) + elif self.selected == self.HUMIDITY_ADDRESS: + time.sleep(0.05) + + vals = self.I2C.simpleRead(self.ADDRESS, 3) + if vals: + if self._calculate_checksum(vals, 2) != vals[2]: + print(vals) + return False + if self.selected == self.TEMP_ADDRESS: + return rawToTemp(vals) + elif self.selected == self.HUMIDITY_ADDRESS: + return rawToRH(vals) diff --git a/PSL/SENSORS/SSD1306.py b/PSL/SENSORS/SSD1306.py index f85c3d9f..9c40ff5e 100644 --- a/PSL/SENSORS/SSD1306.py +++ b/PSL/SENSORS/SSD1306.py @@ -34,420 +34,467 @@ from numpy import int16 import time -def connect(route,**args): - return SSD1306(route,**args) -class SSD1306(): - ADDRESS = 0x3C - - #--------------Parameters-------------------- - #This must be defined in order to let GUIs automatically create menus - #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={'load':['logo'], - 'scroll':['left','right','topright','topleft','bottomleft','bottomright','stop'] - } - - NUMPLOTS=0 - PLOTNAMES = [''] - name = 'OLED Display' - _width=128;WIDTH = 128; - _height=64;HEIGHT = 64; - - rotation = 0 - cursor_y = 0 - cursor_x = 0 - textsize = 1 - textcolor =1 - textbgcolor = 0 - wrap = True - - - - SSD1306_128_64 =1 - SSD1306_128_32 =2 - SSD1306_96_16 =3 - - # -----------------------------------------------------------------------*/ - DISPLAY_TYPE = SSD1306_96_16 - ## self.SSD1306_128_32 - #/*=========================================================================*/ - - SSD1306_LCDWIDTH = 128 - SSD1306_LCDHEIGHT = 64 - - - SSD1306_SETCONTRAST =0x81 - SSD1306_DISPLAYALLON_RESUME =0xA4 - SSD1306_DISPLAYALLON =0xA5 - SSD1306_NORMALDISPLAY =0xA6 - SSD1306_INVERTDISPLAY =0xA7 - SSD1306_DISPLAYOFF= 0xAE - SSD1306_DISPLAYON= 0xAF - - SSD1306_SETDISPLAYOFFSET= 0xD3 - SSD1306_SETCOMPINS= 0xDA - - SSD1306_SETVCOMDETECT= 0xDB - - SSD1306_SETDISPLAYCLOCKDIV =0xD5 - SSD1306_SETPRECHARGE= 0xD9 - - SSD1306_SETMULTIPLEX= 0xA8 - - SSD1306_SETLOWCOLUMN =0x00 - SSD1306_SETHIGHCOLUMN =0x10 - - SSD1306_SETSTARTLINE= 0x40 - - SSD1306_MEMORYMODE= 0x20 - - SSD1306_COMSCANINC =0xC0 - SSD1306_COMSCANDEC= 0xC8 - - SSD1306_SEGREMAP =0xA0 - - SSD1306_CHARGEPUMP= 0x8D - - SSD1306_EXTERNALVCC= 0x1 - SSD1306_SWITCHCAPVCC =0x2 - - - logobuff = [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 127, 63, 63, 159, 159, 223, 223, 207, 207, 207, 239, 239, 47, 47, 39, 39, 7, 7, 67, 67, 83, 131, 135, 7, 7, 15, 15, 31, 191, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 31, 15, 199, 99, 17, 25, 12, 4, 2, 3, 7, 63, 255, 255, 255, 255, 255, 255, 255, 255, 254, 252, 240, 224, 224, 224, 192, 192, 128, 128, 128, 128, 129, 128, 0, 0, 0, 0, 0, 3, 3, 7, 31, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 15, 3, 192, 120, 134, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 252, 252, 249, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 143, 0, 0, 124, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 240, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 240, 128, 0, 7, 56, 96, 128, 0, 0, 0, 0, 0, 0, 0, 12, 63, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 31, 7, 227, 243, 249, 249, 249, 249, 249, 249, 243, 255, 255, 199, 131, 49, 57, 57, 57, 121, 115, 255, 255, 255, 255, 15, 15, 159, 207, 207, 207, 143, 31, 63, 255, 255, 159, 207, 207, 207, 143, 31, 63, 255, 255, 255, 15, 15, 159, 207, 207, 207, 255, 255, 0, 0, 255, 127, 63, 159, 207, 239, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 248, 240, 224, 129, 2, 4, 8, 16, 32, 96, 64, 128, 128, 135, 30, 115, 207, 159, 255, 255, 255, 255, 127, 63, 31, 31, 31, 31, 31, 31, 31, 7, 7, 7, 127, 127, 127, 127, 127, 127, 255, 255, 255, 255, 252, 240, 227, 231, 207, 207, 207, 207, 207, 207, 231, 255, 255, 231, 207, 207, 207, 207, 207, 198, 224, 240, 255, 255, 255, 0, 0, 231, 207, 207, 207, 199, 224, 240, 255, 225, 193, 204, 204, 204, 228, 192, 192, 255, 255, 255, 192, 192, 255, 255, 255, 255, 255, 255, 192, 192, 252, 248, 243, 231, 207, 223, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 252, 248, 248, 240, 240, 224, 225, 225, 193, 193, 195, 195, 195, 195, 195, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 62, 62, 62, 62, 62, 255, 243, 3, 3, 51, 51, 51, 19, 135, 239, 255, 255, 63, 63, 159, 159, 159, 159, 63, 127, 255, 255, 255, 63, 31, 159, 159, 159, 31, 252, 252, 255, 63, 63, 159, 159, 159, 159, 63, 127, 255, 255, 255, 223, 159, 159, 159, 31, 127, 255, 255, 255, 255, 223, 31, 31, 191, 159, 159, 159, 255, 255, 127, 63, 159, 159, 159, 159, 31, 31, 255, 255, 247, 3, 7, 159, 159, 159, 31, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 252, 252, 252, 252, 252, 252, 252, 252, 224, 224, 224, 255, 255, 255, 255, 255, 255, 255, 243, 240, 240, 247, 255, 254, 252, 248, 243, 255, 255, 248, 248, 242, 242, 242, 242, 242, 250, 255, 255, 255, 241, 242, 242, 242, 242, 248, 253, 255, 255, 248, 248, 242, 242, 242, 242, 242, 250, 255, 255, 249, 240, 242, 242, 242, 240, 240, 255, 255, 255, 255, 243, 240, 240, 243, 243, 255, 255, 255, 255, 252, 248, 243, 243, 243, 243, 243, 255, 255, 255, 247, 240, 240, 247, 255, 247, 240, 240, 247, 255] - font = [0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, - 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, - 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, 0x18, 0x3C, 0x18, 0x00, 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, - 0x00, 0x18, 0x24, 0x18, 0x00, 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E, - 0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, 0x40, 0x7F, 0x05, 0x25, 0x3F, - 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, - 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, - 0x00, 0x66, 0x89, 0x95, 0x6A, 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94, - 0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, 0x08, 0x08, 0x2A, 0x1C, 0x08, - 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, - 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, - 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49, 0x56, 0x20, 0x50, - 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, - 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x80, 0x70, 0x30, 0x00, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, - 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x72, 0x49, 0x49, 0x49, 0x46, - 0x21, 0x41, 0x49, 0x4D, 0x33, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, - 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07, 0x36, 0x49, 0x49, 0x49, 0x36, - 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00, - 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, - 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, - 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, - 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73, - 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, - 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, - 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 0x06, - 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32, - 0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20, 0x40, 0x20, 0x1F, - 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 0x03, 0x04, 0x78, 0x04, 0x03, - 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, - 0x00, 0x41, 0x41, 0x41, 0x7F, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, - 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, 0x7F, 0x28, 0x44, 0x44, 0x38, - 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28, 0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, - 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x7F, 0x08, 0x04, 0x04, 0x78, - 0x00, 0x44, 0x7D, 0x40, 0x00, 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, - 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, 0x7C, 0x08, 0x04, 0x04, 0x78, - 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x18, 0x24, 0x24, 0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, - 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24, 0x04, 0x04, 0x3F, 0x44, 0x24, - 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C, - 0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x44, 0x64, 0x54, 0x4C, 0x44, - 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, - 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x1E, 0xA1, 0xA1, 0x61, 0x12, - 0x3A, 0x40, 0x40, 0x20, 0x7A, 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41, - 0x21, 0x54, 0x54, 0x78, 0x41, 0x21, 0x55, 0x54, 0x78, 0x40, 0x20, 0x54, 0x55, 0x79, 0x40, - 0x0C, 0x1E, 0x52, 0x72, 0x12, 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54, 0x59, - 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, 0x00, 0x02, 0x45, 0x7D, 0x42, - 0x00, 0x01, 0x45, 0x7C, 0x40, 0xF0, 0x29, 0x24, 0x29, 0xF0, 0xF0, 0x28, 0x25, 0x28, 0xF0, - 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54, 0x54, 0x7C, 0x54, 0x7C, 0x0A, 0x09, 0x7F, 0x49, - 0x32, 0x49, 0x49, 0x49, 0x32, 0x32, 0x48, 0x48, 0x48, 0x32, 0x32, 0x4A, 0x48, 0x48, 0x30, - 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42, 0x40, 0x20, 0x78, 0x00, 0x9D, 0xA0, 0xA0, 0x7D, - 0x39, 0x44, 0x44, 0x44, 0x39, 0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, - 0x48, 0x7E, 0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 0xFF, 0x09, 0x29, 0xF6, 0x20, - 0xC0, 0x88, 0x7E, 0x09, 0x03, 0x20, 0x54, 0x54, 0x79, 0x41, 0x00, 0x00, 0x44, 0x7D, 0x41, - 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40, 0x22, 0x7A, 0x00, 0x7A, 0x0A, 0x0A, 0x72, - 0x7D, 0x0D, 0x19, 0x31, 0x7D, 0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, - 0x30, 0x48, 0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, - 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00, 0x00, 0x7B, 0x00, 0x00, - 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A, 0x14, 0x08, 0xAA, 0x00, 0x55, 0x00, 0xAA, - 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0x10, 0xFF, 0x00, - 0x14, 0x14, 0x14, 0xFF, 0x00, 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10, 0xF0, - 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, - 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, - 0x14, 0x14, 0x14, 0x1F, 0x00, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, - 0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x10, - 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x14, - 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, - 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x00, 0x00, 0xFF, 0x00, 0xF7, - 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17, 0x14, - 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x10, 0x10, 0xF0, 0x10, 0xF0, - 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, - 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0xFF, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x14, - 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, - 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x38, 0x44, 0x44, 0x38, 0x44, 0x7C, 0x2A, 0x2A, 0x3E, 0x14, - 0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, 0x63, 0x55, 0x49, 0x41, 0x63, - 0x38, 0x44, 0x44, 0x3C, 0x04, 0x40, 0x7E, 0x20, 0x1E, 0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, - 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 0x4C, 0x72, 0x01, 0x72, 0x4C, - 0x30, 0x4A, 0x4D, 0x4D, 0x30, 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D, - 0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, - 0x44, 0x44, 0x5F, 0x44, 0x44, 0x40, 0x51, 0x4A, 0x44, 0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, - 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0, 0x80, 0xFF, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x6B, 0x08, - 0x36, 0x12, 0x36, 0x24, 0x36, 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00, - 0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, 0x00, 0x1F, 0x01, 0x01, 0x1E, - 0x00, 0x19, 0x1D, 0x17, 0x12, 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00] #ascii fonts - def __init__(self,I2C,**args): - self.buff =[0 for a in range(1024)] - self.ADDRESS = args.get('address',self.ADDRESS) - self.I2C = I2C - self.SSD1306_command(self.SSD1306_DISPLAYOFF); #0xAE - self.SSD1306_command(self.SSD1306_SETDISPLAYCLOCKDIV); # 0xD5 - self.SSD1306_command(0x80); # the suggested ratio 0x80 - self.SSD1306_command(self.SSD1306_SETMULTIPLEX); # 0xA8 - self.SSD1306_command(0x3F); - self.SSD1306_command(self.SSD1306_SETDISPLAYOFFSET); # 0xD3 - self.SSD1306_command(0x0); # no offset - self.SSD1306_command(self.SSD1306_SETSTARTLINE | 0x0); # line #0 - self.SSD1306_command(self.SSD1306_CHARGEPUMP); # 0x8D - self.SSD1306_command(0x14); #vccstate = self.SSD1306_SWITCHCAPVCC; - self.SSD1306_command(self.SSD1306_MEMORYMODE); # 0x20 - self.SSD1306_command(0x00); # 0x0 act like ks0108 - self.SSD1306_command(self.SSD1306_SEGREMAP | 0x1); - self.SSD1306_command(self.SSD1306_COMSCANDEC); - self.SSD1306_command(self.SSD1306_SETCOMPINS); # 0xDA - self.SSD1306_command(0x12); - self.SSD1306_command(self.SSD1306_SETCONTRAST); # 0x81 - self.SSD1306_command(0xFF); # vccstate = self.SSD1306_SWITCHCAPVCC; - self.SSD1306_command(self.SSD1306_SETPRECHARGE); # 0xd9 - self.SSD1306_command(0xF1); # vccstate = self.SSD1306_SWITCHCAPVCC; - self.SSD1306_command(self.SSD1306_SETVCOMDETECT); # 0xDB - self.SSD1306_command(0x40); - self.SSD1306_command(self.SSD1306_DISPLAYALLON_RESUME); # 0xA4 - self.SSD1306_command(self.SSD1306_NORMALDISPLAY); # 0xA6 - self.SSD1306_command(self.SSD1306_DISPLAYON);#--turn on oled panel - - def load(self,arg): - self.scroll('stop') - if arg=='logo': - self.clearDisplay() - for a in range(1024):self.buff[a]=self.logobuff[a] - self.displayOLED() - - def SSD1306_command(self,cmd): - self.I2C.writeBulk(self.ADDRESS,[0x00,cmd]) - - def SSD1306_data(self,data): - self.I2C.writeBulk(self.ADDRESS,[0x40,data]) - - def clearDisplay(self): - self.setCursor(0,0) - for a in range(self.SSD1306_LCDWIDTH*self.SSD1306_LCDHEIGHT/8):self.buff[a]=0; - - - def displayOLED(self): - self.SSD1306_command(self.SSD1306_SETLOWCOLUMN | 0x0); # low col = 0 - self.SSD1306_command(self.SSD1306_SETHIGHCOLUMN | 0x0); # hi col = 0 - self.SSD1306_command(self.SSD1306_SETSTARTLINE | 0x0); # line #0 - a=0 - while (a < self.SSD1306_LCDWIDTH*self.SSD1306_LCDHEIGHT/8): - self.I2C.writeBulk(self.ADDRESS,[0x40]+self.buff[a:a+16]) - a+=16 - - def setContrast(self,i): - self.SSD1306_command(self.SSD1306_SETCONTRAST) - self.SSD1306_command(i) - - - - def drawPixel(self,x,y,color): - if (color == 1): - self.buff[x+ (y/8)*self.SSD1306_LCDWIDTH] |= (1<<(y%8)) - else: - self.buff[x+ (y/8)*self.SSD1306_LCDWIDTH] &= ~(1<<(y%8)) - - def drawCircle(self,x0,y0, r,color): - f = 1 - r - ddF_x = 1 - ddF_y = -2 * r - x = 0 - y = r - self.drawPixel(x0, y0+r, color) - self.drawPixel(x0, y0-r, color) - self.drawPixel(x0+r, y0, color) - self.drawPixel(x0-r, y0, color) - while (x= 0): - y-=1 - ddF_y += 2 - f += ddF_y - x+=1 - ddF_x += 2 - f += ddF_x - self.drawPixel(x0 + x, y0 + y, color) - self.drawPixel(x0 - x, y0 + y, color) - self.drawPixel(x0 + x, y0 - y, color) - self.drawPixel(x0 - x, y0 - y, color) - self.drawPixel(x0 + y, y0 + x, color) - self.drawPixel(x0 - y, y0 + x, color) - self.drawPixel(x0 + y, y0 - x, color) - self.drawPixel(x0 - y, y0 - x, color) - - - def drawLine(self,x0, y0, x1, y1, color): - steep = abs(y1 - y0) > abs(x1 - x0) - if (steep): - tmp = y0 - y0=x0 - x0=tmp - tmp = y1 - y1=x1 - x1=tmp - if (x0 > x1): - tmp = x1 - x1=x0 - x0=tmp - tmp = y1 - y1=y0 - y0=tmp - - dx = x1 - x0 - dy = abs(y1 - y0) - err = dx / 2 - - if (y0 < y1): - ystep = 1 - else: - ystep = -1 - - while(x0<=x1): - if (steep): self.drawPixel(y0, x0, color) - else: self.drawPixel(x0, y0, color) - err -= dy - if (err < 0): - y0 += ystep - err += dx - x0+=1 - - def drawRect(self,x, y, w,h,color): - self.drawFastHLine(x, y, w, color) - self.drawFastHLine(x, y+h-1, w, color) - self.drawFastVLine(x, y, h, color) - self.drawFastVLine(x+w-1, y, h, color) - - - def drawFastVLine(self,x, y, h, color): - self.drawLine(x, y, x, y+h-1, color) - - def drawFastHLine(self,x, y, w, color): - self.drawLine(x, y, x+w-1, y, color) - - - def fillRect(self, x, y, w, h, color): - for i in range(x,x+w): - self.drawFastVLine(i, y, h, color) - - - def writeString(self,s): - for a in s: self.writeChar(ord(a)) - - def writeChar(self,c): - if (c == '\n'): - cursor_y += textsize*8; - cursor_x = 0; - elif(c == '\r'): - pass - else: - self.drawChar(self.cursor_x, self.cursor_y, c, self.textcolor, self.textbgcolor, self.textsize) - self.cursor_x += self.textsize*6 - if (self.wrap and (self.cursor_x > (self._width - self.textsize*6))): - self.cursor_y += self.textsize*8 - self.cursor_x = 0 - - def drawChar(self, x, y, c,color, bg, size): - if((x >= self._width) or (y >= self._height) or ((x + 5 * size - 1) < 0) or ((y + 8 * size - 1) < 0)): - return; - for i in range(6): - if (i == 5): line = 0x0; - else: line = self.font[c*5+i]; - for j in range(8): - if (line & 0x1): - if (size == 1): self.drawPixel(x+i, y+j, color); - else: self.fillRect(x+(i*size), y+(j*size), size, size, color); - elif (bg != color): - if (size == 1): self.drawPixel(x+i, y+j, bg); - else: self.fillRect(x+i*size, y+j*size, size, size, bg); - line >>= 1 - - def setCursor(self, x, y): - self.cursor_x = x - self.cursor_y = y - - def setTextSize(self,s): - self.textsize = s if (s > 0) else 1 - - def setTextColor(self,c, b): - self.textcolor = c - self.textbgcolor = b - - - def setTextWrap(self,w): - self.wrap = w - - def scroll(self,arg): - if arg=='left': - self.SSD1306_command(0x27) #up-0x29 ,2A left-0x27 right0x26 - if arg=='right': - self.SSD1306_command(0x26) #up-0x29 ,2A left-0x27 right0x26 - if arg in ['topright','bottomright']: - self.SSD1306_command(0x29) #up-0x29 ,2A left-0x27 right0x26 - if arg in ['topleft','bottomleft']: - self.SSD1306_command(0x2A) #up-0x29 ,2A left-0x27 right0x26 - - if arg in ['left','right','topright','topleft','bottomleft','bottomright']: - self.SSD1306_command(0x00) #dummy - self.SSD1306_command(0x0) #start page - self.SSD1306_command(0x7) #time interval 0b100 - 3 frames - self.SSD1306_command(0xf) #end page - if arg in ['topleft','topright']: - self.SSD1306_command(0x02) #dummy 00 . xx for horizontal scroll (speed) - elif arg in ['bottomleft','bottomright']: - self.SSD1306_command(0xfe) #dummy 00 . xx for horizontal scroll (speed) - - if arg in ['left','right']: - self.SSD1306_command(0x02) #dummy 00 . xx for horizontal scroll (speed) - self.SSD1306_command(0xff) - - self.SSD1306_command(0x2F) - - if arg=='stop': - self.SSD1306_command(0x2E) - - - def pulseIt(self): - for a in range(2): - self.SSD1306_command(0xD6) - self.SSD1306_command(0x01) - time.sleep(0.1) - self.SSD1306_command(0xD6) - self.SSD1306_command(0x00) - time.sleep(0.1) - - +def connect(route, **args): + return SSD1306(route, **args) -if __name__ == "__main__": - from PSL import sciencelab - I= sciencelab.connect() - O=connect(I.I2C) - textbgcolor=0 - textcolor=1 - O.load('logo') - O.scroll('topright') - import time - time.sleep(2.8) - O.scroll('stop') +class SSD1306(): + ADDRESS = 0x3C + + # --------------Parameters-------------------- + # This must be defined in order to let GUIs automatically create menus + # 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 = {'load': ['logo'], + 'scroll': ['left', 'right', 'topright', 'topleft', 'bottomleft', 'bottomright', 'stop'] + } + + NUMPLOTS = 0 + PLOTNAMES = [''] + name = 'OLED Display' + _width = 128 + WIDTH = 128 + _height = 64 + HEIGHT = 64 + + rotation = 0 + cursor_y = 0 + cursor_x = 0 + textsize = 1 + textcolor = 1 + textbgcolor = 0 + wrap = True + + SSD1306_128_64 = 1 + SSD1306_128_32 = 2 + SSD1306_96_16 = 3 + + # -----------------------------------------------------------------------*/ + DISPLAY_TYPE = SSD1306_96_16 + ## self.SSD1306_128_32 + # /*=========================================================================*/ + + SSD1306_LCDWIDTH = 128 + SSD1306_LCDHEIGHT = 64 + + SSD1306_SETCONTRAST = 0x81 + SSD1306_DISPLAYALLON_RESUME = 0xA4 + SSD1306_DISPLAYALLON = 0xA5 + SSD1306_NORMALDISPLAY = 0xA6 + SSD1306_INVERTDISPLAY = 0xA7 + SSD1306_DISPLAYOFF = 0xAE + SSD1306_DISPLAYON = 0xAF + + SSD1306_SETDISPLAYOFFSET = 0xD3 + SSD1306_SETCOMPINS = 0xDA + + SSD1306_SETVCOMDETECT = 0xDB + + SSD1306_SETDISPLAYCLOCKDIV = 0xD5 + SSD1306_SETPRECHARGE = 0xD9 + + SSD1306_SETMULTIPLEX = 0xA8 + + SSD1306_SETLOWCOLUMN = 0x00 + SSD1306_SETHIGHCOLUMN = 0x10 + + SSD1306_SETSTARTLINE = 0x40 + + SSD1306_MEMORYMODE = 0x20 + + SSD1306_COMSCANINC = 0xC0 + SSD1306_COMSCANDEC = 0xC8 + + SSD1306_SEGREMAP = 0xA0 + + SSD1306_CHARGEPUMP = 0x8D + + SSD1306_EXTERNALVCC = 0x1 + SSD1306_SWITCHCAPVCC = 0x2 + + logobuff = [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 127, 63, 63, 159, 159, + 223, 223, 207, 207, 207, 239, 239, 47, 47, 39, 39, 7, 7, 67, 67, 83, 131, 135, 7, 7, 15, 15, 31, 191, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 63, 31, 15, 199, 99, 17, 25, 12, 4, 2, 3, 7, 63, 255, 255, 255, 255, 255, + 255, 255, 255, 254, 252, 240, 224, 224, 224, 192, 192, 128, 128, 128, 128, 129, 128, 0, 0, 0, 0, 0, 3, + 3, 7, 31, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 15, + 3, 192, 120, 134, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 252, 252, + 249, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 143, 0, 0, 124, 199, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 128, 240, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 240, 128, 0, 7, 56, 96, 128, 0, 0, 0, + 0, 0, 0, 0, 12, 63, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 31, 7, 227, 243, 249, 249, 249, + 249, 249, 249, 243, 255, 255, 199, 131, 49, 57, 57, 57, 121, 115, 255, 255, 255, 255, 15, 15, 159, 207, + 207, 207, 143, 31, 63, 255, 255, 159, 207, 207, 207, 143, 31, 63, 255, 255, 255, 15, 15, 159, 207, 207, + 207, 255, 255, 0, 0, 255, 127, 63, 159, 207, 239, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 248, 240, 224, 129, 2, 4, 8, 16, 32, 96, 64, 128, + 128, 135, 30, 115, 207, 159, 255, 255, 255, 255, 127, 63, 31, 31, 31, 31, 31, 31, 31, 7, 7, 7, 127, 127, + 127, 127, 127, 127, 255, 255, 255, 255, 252, 240, 227, 231, 207, 207, 207, 207, 207, 207, 231, 255, 255, + 231, 207, 207, 207, 207, 207, 198, 224, 240, 255, 255, 255, 0, 0, 231, 207, 207, 207, 199, 224, 240, + 255, 225, 193, 204, 204, 204, 228, 192, 192, 255, 255, 255, 192, 192, 255, 255, 255, 255, 255, 255, 192, + 192, 252, 248, 243, 231, 207, 223, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 252, 248, 248, 240, 240, 224, + 225, 225, 193, 193, 195, 195, 195, 195, 195, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 62, 62, 62, 62, 62, + 255, 243, 3, 3, 51, 51, 51, 19, 135, 239, 255, 255, 63, 63, 159, 159, 159, 159, 63, 127, 255, 255, 255, + 63, 31, 159, 159, 159, 31, 252, 252, 255, 63, 63, 159, 159, 159, 159, 63, 127, 255, 255, 255, 223, 159, + 159, 159, 31, 127, 255, 255, 255, 255, 223, 31, 31, 191, 159, 159, 159, 255, 255, 127, 63, 159, 159, + 159, 159, 31, 31, 255, 255, 247, 3, 7, 159, 159, 159, 31, 127, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 254, 252, 252, 252, 252, 252, 252, 252, 252, 224, 224, 224, 255, 255, 255, 255, 255, 255, 255, 243, + 240, 240, 247, 255, 254, 252, 248, 243, 255, 255, 248, 248, 242, 242, 242, 242, 242, 250, 255, 255, 255, + 241, 242, 242, 242, 242, 248, 253, 255, 255, 248, 248, 242, 242, 242, 242, 242, 250, 255, 255, 249, 240, + 242, 242, 242, 240, 240, 255, 255, 255, 255, 243, 240, 240, 243, 243, 255, 255, 255, 255, 252, 248, 243, + 243, 243, 243, 243, 255, 255, 255, 247, 240, 240, 247, 255, 247, 240, 240, 247, 255] + font = [0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, 0x18, 0x3C, 0x18, 0x00, 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, + 0x00, 0x18, 0x24, 0x18, 0x00, 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, 0x40, 0x7F, 0x05, 0x25, 0x3F, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, + 0x00, 0x66, 0x89, 0x95, 0x6A, 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, 0x08, 0x08, 0x2A, 0x1C, 0x08, + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06, 0x0E, 0x3E, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07, 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 0x06, + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14, 0x63, 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, 0x7F, 0x28, 0x44, 0x44, 0x38, + 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28, 0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x7F, 0x08, 0x04, 0x04, 0x78, + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, 0x7C, 0x08, 0x04, 0x04, 0x78, + 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x18, 0x24, 0x24, 0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24, 0x04, 0x04, 0x3F, 0x44, 0x24, + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x44, 0x64, 0x54, 0x4C, 0x44, + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C, 0x26, 0x23, 0x26, 0x3C, 0x1E, 0xA1, 0xA1, 0x61, 0x12, + 0x3A, 0x40, 0x40, 0x20, 0x7A, 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41, + 0x21, 0x54, 0x54, 0x78, 0x41, 0x21, 0x55, 0x54, 0x78, 0x40, 0x20, 0x54, 0x55, 0x79, 0x40, + 0x0C, 0x1E, 0x52, 0x72, 0x12, 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, 0x00, 0x02, 0x45, 0x7D, 0x42, + 0x00, 0x01, 0x45, 0x7C, 0x40, 0xF0, 0x29, 0x24, 0x29, 0xF0, 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54, 0x54, 0x7C, 0x54, 0x7C, 0x0A, 0x09, 0x7F, 0x49, + 0x32, 0x49, 0x49, 0x49, 0x32, 0x32, 0x48, 0x48, 0x48, 0x32, 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42, 0x40, 0x20, 0x78, 0x00, 0x9D, 0xA0, 0xA0, 0x7D, + 0x39, 0x44, 0x44, 0x44, 0x39, 0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 0xFF, 0x09, 0x29, 0xF6, 0x20, + 0xC0, 0x88, 0x7E, 0x09, 0x03, 0x20, 0x54, 0x54, 0x79, 0x41, 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40, 0x22, 0x7A, 0x00, 0x7A, 0x0A, 0x0A, 0x72, + 0x7D, 0x0D, 0x19, 0x31, 0x7D, 0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A, 0x14, 0x08, 0xAA, 0x00, 0x55, 0x00, 0xAA, + 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0x10, 0xFF, 0x00, + 0x14, 0x14, 0x14, 0xFF, 0x00, 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, 0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x14, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x00, 0x00, 0xFF, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0xFF, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x14, + 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x38, 0x44, 0x44, 0x38, 0x44, 0x7C, 0x2A, 0x2A, 0x3E, 0x14, + 0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, 0x63, 0x55, 0x49, 0x41, 0x63, + 0x38, 0x44, 0x44, 0x3C, 0x04, 0x40, 0x7E, 0x20, 0x1E, 0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, + 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 0x4C, 0x72, 0x01, 0x72, 0x4C, + 0x30, 0x4A, 0x4D, 0x4D, 0x30, 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D, + 0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x44, 0x44, 0x5F, 0x44, 0x44, 0x40, 0x51, 0x4A, 0x44, 0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0, 0x80, 0xFF, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x6B, 0x08, + 0x36, 0x12, 0x36, 0x24, 0x36, 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, 0x00, 0x1F, 0x01, 0x01, 0x1E, + 0x00, 0x19, 0x1D, 0x17, 0x12, 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00] # ascii fonts + + def __init__(self, I2C, **args): + self.buff = [0 for a in range(1024)] + self.ADDRESS = args.get('address', self.ADDRESS) + self.I2C = I2C + self.SSD1306_command(self.SSD1306_DISPLAYOFF) # 0xAE + self.SSD1306_command(self.SSD1306_SETDISPLAYCLOCKDIV) # 0xD5 + self.SSD1306_command(0x80) # the suggested ratio 0x80 + self.SSD1306_command(self.SSD1306_SETMULTIPLEX) # 0xA8 + self.SSD1306_command(0x3F) + self.SSD1306_command(self.SSD1306_SETDISPLAYOFFSET) # 0xD3 + self.SSD1306_command(0x0) # no offset + self.SSD1306_command(self.SSD1306_SETSTARTLINE | 0x0) # line #0 + self.SSD1306_command(self.SSD1306_CHARGEPUMP) # 0x8D + self.SSD1306_command(0x14) # vccstate = self.SSD1306_SWITCHCAPVCC + self.SSD1306_command(self.SSD1306_MEMORYMODE) # 0x20 + self.SSD1306_command(0x00) # 0x0 act like ks0108 + self.SSD1306_command(self.SSD1306_SEGREMAP | 0x1) + self.SSD1306_command(self.SSD1306_COMSCANDEC) + self.SSD1306_command(self.SSD1306_SETCOMPINS) # 0xDA + self.SSD1306_command(0x12) + self.SSD1306_command(self.SSD1306_SETCONTRAST) # 0x81 + self.SSD1306_command(0xFF) # vccstate = self.SSD1306_SWITCHCAPVCC + self.SSD1306_command(self.SSD1306_SETPRECHARGE) # 0xd9 + self.SSD1306_command(0xF1) # vccstate = self.SSD1306_SWITCHCAPVCC + self.SSD1306_command(self.SSD1306_SETVCOMDETECT) # 0xDB + self.SSD1306_command(0x40) + self.SSD1306_command(self.SSD1306_DISPLAYALLON_RESUME) # 0xA4 + self.SSD1306_command(self.SSD1306_NORMALDISPLAY) # 0xA6 + self.SSD1306_command(self.SSD1306_DISPLAYON) # --turn on oled panel + + def load(self, arg): + self.scroll('stop') + if arg == 'logo': + self.clearDisplay() + for a in range(1024): + self.buff[a] = self.logobuff[a] + self.displayOLED() + + def SSD1306_command(self, cmd): + self.I2C.writeBulk(self.ADDRESS, [0x00, cmd]) + + def SSD1306_data(self, data): + self.I2C.writeBulk(self.ADDRESS, [0x40, data]) + + def clearDisplay(self): + self.setCursor(0, 0) + for a in range(self.SSD1306_LCDWIDTH * self.SSD1306_LCDHEIGHT / 8): + self.buff[a] = 0 + + def displayOLED(self): + self.SSD1306_command(self.SSD1306_SETLOWCOLUMN | 0x0) # low col = 0 + self.SSD1306_command(self.SSD1306_SETHIGHCOLUMN | 0x0) # hi col = 0 + self.SSD1306_command(self.SSD1306_SETSTARTLINE | 0x0) # line #0 + a = 0 + while (a < self.SSD1306_LCDWIDTH * self.SSD1306_LCDHEIGHT / 8): + self.I2C.writeBulk(self.ADDRESS, [0x40] + self.buff[a:a + 16]) + a += 16 + + def setContrast(self, i): + self.SSD1306_command(self.SSD1306_SETCONTRAST) + self.SSD1306_command(i) + + def drawPixel(self, x, y, color): + if (color == 1): + self.buff[x + (y / 8) * self.SSD1306_LCDWIDTH] |= (1 << (y % 8)) + else: + self.buff[x + (y / 8) * self.SSD1306_LCDWIDTH] &= ~(1 << (y % 8)) + + def drawCircle(self, x0, y0, r, color): + f = 1 - r + ddF_x = 1 + ddF_y = -2 * r + x = 0 + y = r + self.drawPixel(x0, y0 + r, color) + self.drawPixel(x0, y0 - r, color) + self.drawPixel(x0 + r, y0, color) + self.drawPixel(x0 - r, y0, color) + while (x < y): + if (f >= 0): + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + self.drawPixel(x0 + x, y0 + y, color) + self.drawPixel(x0 - x, y0 + y, color) + self.drawPixel(x0 + x, y0 - y, color) + self.drawPixel(x0 - x, y0 - y, color) + self.drawPixel(x0 + y, y0 + x, color) + self.drawPixel(x0 - y, y0 + x, color) + self.drawPixel(x0 + y, y0 - x, color) + self.drawPixel(x0 - y, y0 - x, color) + + def drawLine(self, x0, y0, x1, y1, color): + steep = abs(y1 - y0) > abs(x1 - x0) + if (steep): + tmp = y0 + y0 = x0 + x0 = tmp + tmp = y1 + y1 = x1 + x1 = tmp + if (x0 > x1): + tmp = x1 + x1 = x0 + x0 = tmp + tmp = y1 + y1 = y0 + y0 = tmp + + dx = x1 - x0 + dy = abs(y1 - y0) + err = dx / 2 + + if (y0 < y1): + ystep = 1 + else: + ystep = -1 + + while (x0 <= x1): + if (steep): + self.drawPixel(y0, x0, color) + else: + self.drawPixel(x0, y0, color) + err -= dy + if (err < 0): + y0 += ystep + err += dx + x0 += 1 + + def drawRect(self, x, y, w, h, color): + self.drawFastHLine(x, y, w, color) + self.drawFastHLine(x, y + h - 1, w, color) + self.drawFastVLine(x, y, h, color) + self.drawFastVLine(x + w - 1, y, h, color) + + def drawFastVLine(self, x, y, h, color): + self.drawLine(x, y, x, y + h - 1, color) + + def drawFastHLine(self, x, y, w, color): + self.drawLine(x, y, x + w - 1, y, color) + + def fillRect(self, x, y, w, h, color): + for i in range(x, x + w): + self.drawFastVLine(i, y, h, color) + + def writeString(self, s): + for a in s: self.writeChar(ord(a)) + + def writeChar(self, c): + if (c == '\n'): + self.cursor_y += self.textsize * 8 + self.cursor_x = 0 + elif (c == '\r'): + pass + else: + self.drawChar(self.cursor_x, self.cursor_y, c, self.textcolor, self.textbgcolor, self.textsize) + self.cursor_x += self.textsize * 6 + if (self.wrap and (self.cursor_x > (self._width - self.textsize * 6))): + self.cursor_y += self.textsize * 8 + self.cursor_x = 0 + + def drawChar(self, x, y, c, color, bg, size): + if ((x >= self._width) or (y >= self._height) or ((x + 5 * size - 1) < 0) or ((y + 8 * size - 1) < 0)): + return + for i in range(6): + if (i == 5): + line = 0x0 + else: + line = self.font[c * 5 + i] + for j in range(8): + if (line & 0x1): + if (size == 1): + self.drawPixel(x + i, y + j, color) + else: + self.fillRect(x + (i * size), y + (j * size), size, size, color) + elif (bg != color): + if (size == 1): + self.drawPixel(x + i, y + j, bg) + else: + self.fillRect(x + i * size, y + j * size, size, size, bg) + line >>= 1 + + def setCursor(self, x, y): + self.cursor_x = x + self.cursor_y = y + + def setTextSize(self, s): + self.textsize = s if (s > 0) else 1 + + def setTextColor(self, c, b): + self.textcolor = c + self.textbgcolor = b + + def setTextWrap(self, w): + self.wrap = w + + def scroll(self, arg): + if arg == 'left': + self.SSD1306_command(0x27) # up-0x29 ,2A left-0x27 right0x26 + if arg == 'right': + self.SSD1306_command(0x26) # up-0x29 ,2A left-0x27 right0x26 + if arg in ['topright', 'bottomright']: + self.SSD1306_command(0x29) # up-0x29 ,2A left-0x27 right0x26 + if arg in ['topleft', 'bottomleft']: + self.SSD1306_command(0x2A) # up-0x29 ,2A left-0x27 right0x26 + + if arg in ['left', 'right', 'topright', 'topleft', 'bottomleft', 'bottomright']: + self.SSD1306_command(0x00) # dummy + self.SSD1306_command(0x0) # start page + self.SSD1306_command(0x7) # time interval 0b100 - 3 frames + self.SSD1306_command(0xf) # end page + if arg in ['topleft', 'topright']: + self.SSD1306_command(0x02) # dummy 00 . xx for horizontal scroll (speed) + elif arg in ['bottomleft', 'bottomright']: + self.SSD1306_command(0xfe) # dummy 00 . xx for horizontal scroll (speed) + + if arg in ['left', 'right']: + self.SSD1306_command(0x02) # dummy 00 . xx for horizontal scroll (speed) + self.SSD1306_command(0xff) + + self.SSD1306_command(0x2F) + + if arg == 'stop': + self.SSD1306_command(0x2E) + + def pulseIt(self): + for a in range(2): + self.SSD1306_command(0xD6) + self.SSD1306_command(0x01) + time.sleep(0.1) + self.SSD1306_command(0xD6) + self.SSD1306_command(0x00) + time.sleep(0.1) + +if __name__ == "__main__": + from PSL import sciencelab + + I = sciencelab.connect() + O = connect(I.I2C) + textbgcolor = 0 + textcolor = 1 + O.load('logo') + O.scroll('topright') + import time + + time.sleep(2.8) + O.scroll('stop') diff --git a/PSL/SENSORS/Sx1276.py b/PSL/SENSORS/Sx1276.py new file mode 100644 index 00000000..ffa5fecc --- /dev/null +++ b/PSL/SENSORS/Sx1276.py @@ -0,0 +1,347 @@ +# Registers adapted from sample code for SEMTECH SX1276 +from __future__ import print_function + +import time + + +def connect(SPI, frq, **kwargs): + return SX1276(SPI, frq, **kwargs) + + +class SX1276(): + name = 'SX1276' + # registers + REG_FIFO = 0x00 + REG_OP_MODE = 0x01 + REG_FRF_MSB = 0x06 + REG_FRF_MID = 0x07 + REG_FRF_LSB = 0x08 + REG_PA_CONFIG = 0x09 + REG_LNA = 0x0c + REG_FIFO_ADDR_PTR = 0x0d + REG_FIFO_TX_BASE_ADDR = 0x0e + REG_FIFO_RX_BASE_ADDR = 0x0f + REG_FIFO_RX_CURRENT_ADDR = 0x10 + REG_IRQ_FLAGS = 0x12 + REG_RX_NB_BYTES = 0x13 + REG_PKT_RSSI_VALUE = 0x1a + REG_PKT_SNR_VALUE = 0x1b + REG_MODEM_CONFIG_1 = 0x1d + REG_MODEM_CONFIG_2 = 0x1e + REG_PREAMBLE_MSB = 0x20 + REG_PREAMBLE_LSB = 0x21 + REG_PAYLOAD_LENGTH = 0x22 + REG_MODEM_CONFIG_3 = 0x26 + REG_RSSI_WIDEBAND = 0x2c + REG_DETECTION_OPTIMIZE = 0x31 + REG_DETECTION_THRESHOLD = 0x37 + REG_SYNC_WORD = 0x39 + REG_DIO_MAPPING_1 = 0x40 + REG_VERSION = 0x42 + REG_PA_DAC = 0x4D + # modes + MODE_LONG_RANGE_MODE = 0x80 + MODE_SLEEP = 0x00 + MODE_STDBY = 0x01 + MODE_TX = 0x03 + MODE_RX_CONTINUOUS = 0x05 + MODE_RX_SINGLE = 0x06 + + # PA config + PA_BOOST = 0x80 + + # IRQ masks + IRQ_TX_DONE_MASK = 0x08 + IRQ_PAYLOAD_CRC_ERROR_MASK = 0x20 + IRQ_RX_DONE_MASK = 0x40 + + MAX_PKT_LENGTH = 255 + + PA_OUTPUT_RFO_PIN = 0 + PA_OUTPUT_PA_BOOST_PIN = 1 + _onReceive = 0 + _frequency = 10 + _packetIndex = 0 + packetLength = 0 + + def __init__(self, SPI, frq, **kwargs): + self.SPI = SPI + self.SPI.set_parameters(2, 6, 1, 0) + self.name = 'SX1276' + self.frequency = frq + + self.reset() + self.version = self.SPIRead(self.REG_VERSION, 1)[0] + if self.version != 0x12: + print('version error', self.version) + self.sleep() + self.setFrequency(self.frequency) + + # set base address + self.SPIWrite(self.REG_FIFO_TX_BASE_ADDR, [0]) + self.SPIWrite(self.REG_FIFO_RX_BASE_ADDR, [0]) + + # set LNA boost + self.SPIWrite(self.REG_LNA, [self.SPIRead(self.REG_LNA)[0] | 0x03]) + + # set auto ADC + self.SPIWrite(self.REG_MODEM_CONFIG_3, [0x04]) + + # output power 17dbm + self.setTxPower(kwargs.get('power', 17), + self.PA_OUTPUT_PA_BOOST_PIN if kwargs.get('boost', True) else self.PA_OUTPUT_RFO_PIN) + self.idle() + + # set bandwidth + self.setSignalBandwidth(kwargs.get('BW', 125e3)) + self.setSpreadingFactor(kwargs.get('SF', 12)) + self.setCodingRate4(kwargs.get('CF', 5)) + + def beginPacket(self, implicitHeader=False): + self.idle() + if implicitHeader: + self.implicitHeaderMode() + else: + self.explicitHeaderMode() + + # reset FIFO & payload length + self.SPIWrite(self.REG_FIFO_ADDR_PTR, [0]) + self.SPIWrite(self.REG_PAYLOAD_LENGTH, [0]) + + def endPacket(self): + # put in TX mode + self.SPIWrite(self.REG_OP_MODE, [self.MODE_LONG_RANGE_MODE | self.MODE_TX]) + while 1: # Wait for TX done + if self.SPIRead(self.REG_IRQ_FLAGS, 1)[0] & self.IRQ_TX_DONE_MASK: + break + else: + print('wait...') + time.sleep(0.1) + self.SPIWrite(self.REG_IRQ_FLAGS, [self.IRQ_TX_DONE_MASK]) + + def parsePacket(self, size=0): + self.packetLength = 0 + irqFlags = self.SPIRead(self.REG_IRQ_FLAGS, 1)[0] + if size > 0: + self.implicitHeaderMode() + self.SPIWrite(self.REG_PAYLOAD_LENGTH, [size & 0xFF]) + else: + self.explicitHeaderMode() + self.SPIWrite(self.REG_IRQ_FLAGS, [irqFlags]) + if (irqFlags & self.IRQ_RX_DONE_MASK) and (irqFlags & self.IRQ_PAYLOAD_CRC_ERROR_MASK) == 0: + self._packetIndex = 0 + if self._implicitHeaderMode: + self.packetLength = self.SPIRead(self.REG_PAYLOAD_LENGTH, 1)[0] + else: + self.packetLength = self.SPIRead(self.REG_RX_NB_BYTES, 1)[0] + self.SPIWrite(self.REG_FIFO_ADDR_PTR, self.SPIRead(self.REG_FIFO_RX_CURRENT_ADDR, 1)) + self.idle() + elif self.SPIRead(self.REG_OP_MODE)[0] != (self.MODE_LONG_RANGE_MODE | self.MODE_RX_SINGLE): + self.SPIWrite(self.REG_FIFO_ADDR_PTR, [0]) + self.SPIWrite(self.REG_OP_MODE, [self.MODE_LONG_RANGE_MODE | self.MODE_RX_SINGLE]) + return self.packetLength + + def packetRssi(self): + return self.SPIRead(self.REG_PKT_RSSI_VALUE)[0] - (164 if self._frequency < 868e6 else 157) + + def packetSnr(self): + return self.SPIRead(self.REG_PKT_SNR_VALUE)[0] * 0.25 + + def write(self, byteArray): + size = len(byteArray) + currentLength = self.SPIRead(self.REG_PAYLOAD_LENGTH)[0] + if (currentLength + size) > self.MAX_PKT_LENGTH: + size = self.MAX_PKT_LENGTH - currentLength + self.SPIWrite(self.REG_FIFO, byteArray[:size]) + self.SPIWrite(self.REG_PAYLOAD_LENGTH, [currentLength + size]) + return size + + def available(self): + return self.SPIRead(self.REG_RX_NB_BYTES)[0] - self._packetIndex + + def checkRx(self): + irqFlags = self.SPIRead(self.REG_IRQ_FLAGS, 1)[0] + if (irqFlags & self.IRQ_RX_DONE_MASK) and (irqFlags & self.IRQ_PAYLOAD_CRC_ERROR_MASK) == 0: + return 1 + return 0; + + def read(self): + if not self.available(): return -1 + self._packetIndex += 1 + return self.SPIRead(self.REG_FIFO)[0] + + def readAll(self): + p = [] + while self.available(): + p.append(self.read()) + return p + + def peek(self): + if not self.available(): return -1 + self.currentAddress = self.SPIRead(self.REG_FIFO_ADDR_PTR) + val = self.SPIRead(self.REG_FIFO)[0] + self.SPIWrite(self.REG_FIFO_ADDR_PTR, self.currentAddress) + return val + + def flush(self): + pass + + def receive(self, size): + if size > 0: + self.implicitHeaderMode() + self.SPIWrite(self.REG_PAYLOAD_LENGTH, [size & 0xFF]) + else: + self.explicitHeaderMode() + + self.SPIWrite(self.REG_OP_MODE, [self.MODE_LONG_RANGE_MODE | self.MODE_RX_SINGLE]) + + def reset(self): + pass + + def idle(self): + self.SPIWrite(self.REG_OP_MODE, [self.MODE_LONG_RANGE_MODE | self.MODE_STDBY]) + + def sleep(self): + self.SPIWrite(self.REG_OP_MODE, [self.MODE_LONG_RANGE_MODE | self.MODE_SLEEP]) + + def setTxPower(self, level, pin): + if pin == self.PA_OUTPUT_RFO_PIN: + if level < 0: + level = 0 + elif level > 14: + level = 14 + self.SPIWrite(self.REG_PA_CONFIG, [0x70 | level]) + else: + if level < 2: + level = 2 + elif level > 17: + level = 17 + if level == 17: + print('max power output') + self.SPIWrite(self.REG_PA_DAC, [0x87]) + else: + self.SPIWrite(self.REG_PA_DAC, [0x84]) + self.SPIWrite(self.REG_PA_CONFIG, [self.PA_BOOST | 0x70 | (level - 2)]) + + print('power', hex(self.SPIRead(self.REG_PA_CONFIG)[0])) + + def setFrequency(self, frq): + self._frequency = frq + frf = (int(frq) << 19) / 32000000 + print('frf', frf) + print('freq', (frf >> 16) & 0xFF, (frf >> 8) & 0xFF, (frf) & 0xFF) + self.SPIWrite(self.REG_FRF_MSB, [(frf >> 16) & 0xFF]) + self.SPIWrite(self.REG_FRF_MID, [(frf >> 8) & 0xFF]) + self.SPIWrite(self.REG_FRF_LSB, [frf & 0xFF]) + + def setSpreadingFactor(self, sf): + if sf < 6: + sf = 6 + elif sf > 12: + sf = 12 + + if sf == 6: + self.SPIWrite(self.REG_DETECTION_OPTIMIZE, [0xc5]) + self.SPIWrite(self.REG_DETECTION_THRESHOLD, [0x0c]) + else: + self.SPIWrite(self.REG_DETECTION_OPTIMIZE, [0xc3]) + self.SPIWrite(self.REG_DETECTION_THRESHOLD, [0x0a]) + self.SPIWrite(self.REG_MODEM_CONFIG_2, [(self.SPIRead(self.REG_MODEM_CONFIG_2)[0] & 0x0F) | ((sf << 4) & 0xF0)]) + + def setSignalBandwidth(self, sbw): + bw = 9 + num = 0 + for a in [7.8e3, 10.4e3, 15.6e3, 20.8e3, 31.25e3, 41.7e3, 62.5e3, 125e3, 250e3]: + if sbw <= a: + bw = num + break + num += 1 + print('bandwidth: ', bw) + self.SPIWrite(self.REG_MODEM_CONFIG_1, [(self.SPIRead(self.REG_MODEM_CONFIG_1)[0] & 0x0F) | (bw << 4)]) + + def setCodingRate4(self, denominator): + if denominator < 5: + denominator = 5 + elif denominator > 8: + denominator = 8 + self.SPIWrite(self.REG_MODEM_CONFIG_1, + [(self.SPIRead(self.REG_MODEM_CONFIG_1)[0] & 0xF1) | ((denominator - 4) << 4)]) + + def setPreambleLength(self, length): + self.SPIWrite(self.REG_PREAMBLE_MSB, [(length >> 8) & 0xFF]) + self.SPIWrite(self.REG_PREAMBLE_LSB, [length & 0xFF]) + + def setSyncWord(self, sw): + self.SPIWrite(self.REG_SYNC_WORD, [sw]) + + def crc(self): + self.SPIWrite(self.REG_MODEM_CONFIG_2, [self.SPIRead(self.REG_MODEM_CONFIG_2)[0] | 0x04]) + + def noCrc(self): + self.SPIWrite(self.REG_MODEM_CONFIG_2, [self.SPIRead(self.REG_MODEM_CONFIG_2)[0] & 0xFB]) + + def random(self): + return self.SPIRead(self.REG_RSSI_WIDEBAND)[0] + + def explicitHeaderMode(self): + self._implicitHeaderMode = 0 + self.SPIWrite(self.REG_MODEM_CONFIG_1, [self.SPIRead(self.REG_MODEM_CONFIG_1)[0] & 0xFE]) + + def implicitHeaderMode(self): + self._implicitHeaderMode = 1 + self.SPIWrite(self.REG_MODEM_CONFIG_1, [self.SPIRead(self.REG_MODEM_CONFIG_1)[0] | 0x01]) + + def handleDio0Rise(self): + irqFlags = self.SPIRead(self.REG_IRQ_FLAGS, 1)[0] + self.SPIWrite(self.REG_IRQ_FLAGS, [irqFlags]) + + if (irqFlags & self.IRQ_PAYLOAD_CRC_ERROR_MASK) == 0: + self._packetIndex = 0 + if self._implicitHeaderMode: + self.packetLength = self.SPIRead(self.REG_PAYLOAD_LENGTH, 1)[0] + else: + self.packetLength = self.SPIRead(self.REG_RX_NB_BYTES, 1)[0] + + self.SPIWrite(self.REG_FIFO_ADDR_PTR, self.SPIRead(self.REG_FIFO_RX_CURRENT_ADDR, 1)) + if self._onReceive: + print(self.packetLength) + # self._onReceive(self.packetLength) + + self.SPIWrite(self.REG_FIFO_ADDR_PTR, [0]) + + def SPIWrite(self, adr, byteArray): + return self.SPI.xfer('CS1', [0x80 | adr] + byteArray)[1:] + + def SPIRead(self, adr, total_bytes=1): + return self.SPI.xfer('CS1', [adr] + [0] * total_bytes)[1:] + + def getRaw(self): + val = self.SPIRead(0x02, 1) + return val + + +if __name__ == "__main__": + RX = 0; + TX = 1 + mode = RX + from PSL import sciencelab + + I = sciencelab.connect() + lora = SX1276(I.SPI, 434e6, boost=True, power=17, BW=125e3, SF=12, CR=5) # settings for maximum range + lora.crc() + cntr = 0 + while 1: + time.sleep(0.01) + if mode == TX: + lora.beginPacket() + lora.write([cntr]) + # lora.write([ord(a) for a in ":"]+[cntr]) + print(time.ctime(), [ord(a) for a in ":"] + [cntr], hex(lora.SPIRead(lora.REG_OP_MODE)[0])) + lora.endPacket() + cntr += 1 + if cntr == 255: cntr = 0 + elif mode == RX: + packet_size = lora.parsePacket() + if packet_size: + print('data', lora.readAll()) + print('Rssi', lora.packetRssi(), lora.packetSnr()) diff --git a/PSL/SENSORS/TSL2561.py b/PSL/SENSORS/TSL2561.py index b0cbd2a8..6c9cfb6a 100644 --- a/PSL/SENSORS/TSL2561.py +++ b/PSL/SENSORS/TSL2561.py @@ -5,99 +5,102 @@ 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 - FULLSPECTRUM = 0 # channel 0 - - READBIT = 0x01 - COMMAND_BIT = 0x80 # Must be 1 - - CONTROL_POWERON = 0x03 - CONTROL_POWEROFF = 0x00 - - - REGISTER_CONTROL = 0x00 - REGISTER_TIMING = 0x01 - REGISTER_ID = 0x0A - - - INTEGRATIONTIME_13MS = 0x00 # 13.7ms - INTEGRATIONTIME_101MS = 0x01 # 101ms - INTEGRATIONTIME_402MS = 0x02 # 402ms - - GAIN_1X = 0x00 # No gain - GAIN_16X = 0x10 # 16x gain - - ADDRESS = 0x39 #addr normal - timing = INTEGRATIONTIME_13MS - gain = GAIN_16X - name = 'TSL2561 Luminosity' - ADDRESS = 0x39 - NUMPLOTS=3 - PLOTNAMES = ['Full','IR','Visible'] - def __init__(self, I2C,**args): - self.ADDRESS = args.get('address',0x39) - self.I2C = I2C - # set timing 101ms & 16x gain - self.enable() - self.wait() - self.I2C.writeBulk(self.ADDRESS,[0x80 | 0x01, 0x01 | 0x10 ]) - # full scale luminosity - infra = self.I2C.readBulk(self.ADDRESS,0x80 | 0x20 | 0x0E ,2) - full = self.I2C.readBulk(self.ADDRESS,0x80 | 0x20 | 0x0C ,2) - full = (full[1]<<8)|full[0] - infra = (infra[1]<<8)|infra[0] - - print("Full: %04x" % full) - print("Infrared: %04x" % infra) - print("Visible: %04x" % (full - infra) ) - - #self.I2C.writeBulk(self.ADDRESS,[0x80,0x00]) - - self.params={'setGain':['1x','16x'],'setTiming':[0,1,2]} - - def getID(self): - ID=self.I2C.readBulk(self.ADDRESS,self.REGISTER_ID ,1) - print (hex(ID)) - return ID - - def getRaw(self): - infra = self.I2C.readBulk(self.ADDRESS,0x80 | 0x20 | 0x0E ,2) - full = self.I2C.readBulk(self.ADDRESS,0x80 | 0x20 | 0x0C ,2) - if infra and full: - full = (full[1]<<8)|full[0] - infra = (infra[1]<<8)|infra[0] - return [full,infra,full-infra] - else: - return False - - def setGain(self, gain): - if(gain=='1x'):self.gain = self.GAIN_1X - elif(gain=='16x'):self.gain = self.GAIN_16X - else: self.gain = self.GAIN_0X - - self.I2C.writeBulk(self.ADDRESS,[self.COMMAND_BIT | self.REGISTER_TIMING, self.gain | self.timing]) - - def setTiming(self, timing): - print ([13,101,402][timing],'mS') - self.timing = timing - self.I2C.writeBulk(self.ADDRESS,[self.COMMAND_BIT | self.REGISTER_TIMING, self.gain | self.timing]) - - def enable(self): - self.I2C.writeBulk(self.ADDRESS,[self.COMMAND_BIT | self.REGISTER_CONTROL, self.CONTROL_POWERON]) - - def disable(self): - self.I2C.writeBulk(self.ADDRESS,[self.COMMAND_BIT | self.REGISTER_CONTROL, self.CONTROL_POWEROFF]) - - def wait(self): - if self.timing == self.INTEGRATIONTIME_13MS: - time.sleep(0.14) - if self.timing == self.INTEGRATIONTIME_101MS: - time.sleep(0.102) - if self.timing == self.INTEGRATIONTIME_402MS: - time.sleep(0.403) +def connect(route, **args): + return TSL2561(route, **args) + + +class TSL2561: + VISIBLE = 2 # channel 0 - channel 1 + INFRARED = 1 # channel 1 + FULLSPECTRUM = 0 # channel 0 + + READBIT = 0x01 + COMMAND_BIT = 0x80 # Must be 1 + + CONTROL_POWERON = 0x03 + CONTROL_POWEROFF = 0x00 + + REGISTER_CONTROL = 0x00 + REGISTER_TIMING = 0x01 + REGISTER_ID = 0x0A + + INTEGRATIONTIME_13MS = 0x00 # 13.7ms + INTEGRATIONTIME_101MS = 0x01 # 101ms + INTEGRATIONTIME_402MS = 0x02 # 402ms + + GAIN_1X = 0x00 # No gain + GAIN_16X = 0x10 # 16x gain + + ADDRESS = 0x39 # addr normal + timing = INTEGRATIONTIME_13MS + gain = GAIN_16X + name = 'TSL2561 Luminosity' + ADDRESS = 0x39 + NUMPLOTS = 3 + PLOTNAMES = ['Full', 'IR', 'Visible'] + + def __init__(self, I2C, **args): + self.ADDRESS = args.get('address', 0x39) + self.I2C = I2C + # set timing 101ms & 16x gain + self.enable() + self.wait() + self.I2C.writeBulk(self.ADDRESS, [0x80 | 0x01, 0x01 | 0x10]) + # full scale luminosity + infra = self.I2C.readBulk(self.ADDRESS, 0x80 | 0x20 | 0x0E, 2) + full = self.I2C.readBulk(self.ADDRESS, 0x80 | 0x20 | 0x0C, 2) + full = (full[1] << 8) | full[0] + infra = (infra[1] << 8) | infra[0] + + print("Full: %04x" % full) + print("Infrared: %04x" % infra) + print("Visible: %04x" % (full - infra)) + + # self.I2C.writeBulk(self.ADDRESS,[0x80,0x00]) + + self.params = {'setGain': ['1x', '16x'], 'setTiming': [0, 1, 2]} + + def getID(self): + ID = self.I2C.readBulk(self.ADDRESS, self.REGISTER_ID, 1) + print(hex(ID)) + return ID + + def getRaw(self): + infra = self.I2C.readBulk(self.ADDRESS, 0x80 | 0x20 | 0x0E, 2) + full = self.I2C.readBulk(self.ADDRESS, 0x80 | 0x20 | 0x0C, 2) + if infra and full: + full = (full[1] << 8) | full[0] + infra = (infra[1] << 8) | infra[0] + return [full, infra, full - infra] + else: + return False + + def setGain(self, gain): + if (gain == '1x'): + self.gain = self.GAIN_1X + elif (gain == '16x'): + self.gain = self.GAIN_16X + else: + self.gain = self.GAIN_0X + + self.I2C.writeBulk(self.ADDRESS, [self.COMMAND_BIT | self.REGISTER_TIMING, self.gain | self.timing]) + + def setTiming(self, timing): + print([13, 101, 402][timing], 'mS') + self.timing = timing + self.I2C.writeBulk(self.ADDRESS, [self.COMMAND_BIT | self.REGISTER_TIMING, self.gain | self.timing]) + + def enable(self): + self.I2C.writeBulk(self.ADDRESS, [self.COMMAND_BIT | self.REGISTER_CONTROL, self.CONTROL_POWERON]) + + def disable(self): + self.I2C.writeBulk(self.ADDRESS, [self.COMMAND_BIT | self.REGISTER_CONTROL, self.CONTROL_POWEROFF]) + + def wait(self): + if self.timing == self.INTEGRATIONTIME_13MS: + time.sleep(0.014) + if self.timing == self.INTEGRATIONTIME_101MS: + time.sleep(0.102) + if self.timing == self.INTEGRATIONTIME_402MS: + time.sleep(0.403) diff --git a/PSL/SENSORS/supported.py b/PSL/SENSORS/supported.py index de18368a..4b51a58b 100644 --- a/PSL/SENSORS/supported.py +++ b/PSL/SENSORS/supported.py @@ -9,19 +9,18 @@ from PSL.SENSORS import BH1750 from PSL.SENSORS import SSD1306 -supported={ -0x68:MPU6050, #3-axis gyro,3-axis accel,temperature -0x1E:HMC5883L, #3-axis magnetometer -0x5A:MLX90614, #Passive IR temperature sensor -0x77:BMP180, #Pressure, Temperature, altitude -0x39:TSL2561, #Luminosity -0x40:SHT21, #Temperature, Humidity -0x23:BH1750, #Luminosity -#0x3C:SSD1306, #OLED display +supported = { + 0x68: MPU6050, # 3-axis gyro,3-axis accel,temperature + 0x1E: HMC5883L, # 3-axis magnetometer + 0x5A: MLX90614, # Passive IR temperature sensor + 0x77: BMP180, # Pressure, Temperature, altitude + 0x39: TSL2561, # Luminosity + 0x40: SHT21, # Temperature, Humidity + 0x23: BH1750, # Luminosity + # 0x3C:SSD1306, #OLED display } - -#auto generated map of names to classes +# auto generated map of names to classes nameMap = {} for a in supported: - nameMap[supported[a].__name__.split('.')[-1]]=(supported[a]) + nameMap[supported[a].__name__.split('.')[-1]] = (supported[a]) diff --git a/PSL/achan.py b/PSL/achan.py index bfdfb2bf..b16be9fb 100644 --- a/PSL/achan.py +++ b/PSL/achan.py @@ -1,126 +1,123 @@ - from __future__ import print_function import numpy as np -gains=[1,2,4,5,8,10,16,32,1/11.] -#-----------------------Classes for input sources---------------------- -allAnalogChannels = ['CH1','CH2','CH3','MIC','CAP','SEN','AN8'] +gains = [1, 2, 4, 5, 8, 10, 16, 32, 1 / 11.] + +# -----------------------Classes for input sources---------------------- +allAnalogChannels = ['CH1', 'CH2', 'CH3', 'MIC', 'CAP', 'SEN', 'AN8'] + +bipolars = ['CH1', 'CH2', 'CH3', 'MIC'] -bipolars = ['CH1','CH2','CH3','MIC'] +inputRanges = {'CH1': [16.5, -16.5], # Specify inverted channels explicitly by reversing range!!!!!!!!! + 'CH2': [16.5, -16.5], + 'CH3': [-3.3, 3.3], # external gain control analog input + 'MIC': [-3.3, 3.3], # connected to MIC amplifier + 'CAP': [0, 3.3], + 'SEN': [0, 3.3], + 'AN8': [0, 3.3] + } -inputRanges={'CH1':[16.5,-16.5], #Specify inverted channels explicitly by reversing range!!!!!!!!! -'CH2':[16.5,-16.5], -'CH3':[-3.3,3.3], #external gain control analog input -'MIC':[-3.3,3.3], #connected to MIC amplifier -'CAP':[0,3.3], -'SEN':[0,3.3], -'AN8':[0,3.3] -} +picADCMultiplex = {'CH1': 3, 'CH2': 0, 'CH3': 1, 'MIC': 2, 'AN4': 4, 'SEN': 7, 'CAP': 5, 'AN8': 8, } -picADCMultiplex={'CH1':3,'CH2':0,'CH3':1,'MIC':2,'AN4':4,'SEN':7,'CAP':5,'AN8':8,} class analogInputSource: - gain_values=gains - gainEnabled=False - gain=None - gainPGA=None - inverted=False - inversion=1. - calPoly10 = np.poly1d([0,3.3/1023,0.]) - calPoly12 = np.poly1d([0,3.3/4095,0.]) - calibrationReady=False - defaultOffsetCode=0 - def __init__(self,name,**args): - self.name = name #The generic name of the input. like 'CH1', 'IN1' etc - self.CHOSA = picADCMultiplex[self.name] - self.adc_shifts=[] - self.polynomials={} - - self.R=inputRanges[name] - - if self.R[1]-self.R[0] < 0: - self.inverted=True - self.inversion=-1 - - self.scaling=1. - if name=='CH1': - self.gainEnabled=True - self.gainPGA = 1 - self.gain=0 #This is not the gain factor. use self.gain_values[self.gain] to find that. - elif name=='CH2': - self.gainEnabled=True - self.gainPGA = 2 - self.gain=0 - else: - pass - - - self.gain=0 - self.regenerateCalibration() - - - - def setGain(self,g): - if not self.gainEnabled: - print ('Analog gain is not available on',self.name) - return False - self.gain=self.gain_values.index(g) - self.regenerateCalibration() - - def inRange(self,val): - v = self.voltToCode12(val) - return (v>=0 and v<=4095) - - def __conservativeInRange__(self,val): - v = self.voltToCode12(val) - return (v>=50 and v<=4000) - - def loadCalibrationTable(self,table,slope, intercept): - self.adc_shifts = np.array(table)*slope - intercept - - def __ignoreCalibration__(self): - self.calibrationReady = False - - def loadPolynomials(self,polys): - for a in range(len(polys)): - epoly = [float(b) for b in polys[a]] - self.polynomials[a] = np.poly1d(epoly) - - def regenerateCalibration(self): - B=self.R[1] - A=self.R[0] - intercept = self.R[0] - - if self.gain!=None: - gain = self.gain_values[self.gain] - B = B/gain - A = A/gain - - - slope = B-A - intercept = A - if self.calibrationReady and self.gain!=8 : #special case for 1/11. gain - self.calPoly10 = self.__cal10__ - self.calPoly12 = self.__cal12__ - - else: - self.calPoly10 = np.poly1d([0,slope/1023.,intercept]) - self.calPoly12 = np.poly1d([0,slope/4095.,intercept]) - - self.voltToCode10 = np.poly1d([0,1023./slope,-1023*intercept/slope]) - self.voltToCode12 = np.poly1d([0,4095./slope,-4095*intercept/slope]) - - - def __cal12__(self,RAW): - avg_shifts=(self.adc_shifts[np.int16(np.floor(RAW))]+self.adc_shifts[np.int16(np.ceil(RAW))])/2. - RAW = RAW-4095*(avg_shifts)/3.3 - return self.polynomials[self.gain](RAW) - - def __cal10__(self,RAW): - RAW*=4095/1023. - avg_shifts=(self.adc_shifts[np.int16(np.floor(RAW))]+self.adc_shifts[np.int16(np.ceil(RAW))])/2. - RAW = RAW-4095*(avg_shifts)/3.3 - return self.polynomials[self.gain](RAW) + gain_values = gains + gainEnabled = False + gain = None + gainPGA = None + inverted = False + inversion = 1. + calPoly10 = np.poly1d([0, 3.3 / 1023, 0.]) + calPoly12 = np.poly1d([0, 3.3 / 4095, 0.]) + calibrationReady = False + defaultOffsetCode = 0 + + def __init__(self, name, **args): + self.name = name # The generic name of the input. like 'CH1', 'IN1' etc + self.CHOSA = picADCMultiplex[self.name] + self.adc_shifts = [] + self.polynomials = {} + + self.R = inputRanges[name] + + if self.R[1] - self.R[0] < 0: + self.inverted = True + self.inversion = -1 + + self.scaling = 1. + if name == 'CH1': + self.gainEnabled = True + self.gainPGA = 1 + self.gain = 0 # This is not the gain factor. use self.gain_values[self.gain] to find that. + elif name == 'CH2': + self.gainEnabled = True + self.gainPGA = 2 + self.gain = 0 + else: + pass + + self.gain = 0 + self.regenerateCalibration() + + def setGain(self, g): + if not self.gainEnabled: + print('Analog gain is not available on', self.name) + return False + self.gain = self.gain_values.index(g) + self.regenerateCalibration() + + def inRange(self, val): + v = self.voltToCode12(val) + return 0 <= v <= 4095 + + def __conservativeInRange__(self, val): + v = self.voltToCode12(val) + return 50 <= v <= 4000 + + def loadCalibrationTable(self, table, slope, intercept): + self.adc_shifts = np.array(table) * slope - intercept + + def __ignoreCalibration__(self): + self.calibrationReady = False + + def loadPolynomials(self, polys): + for a in range(len(polys)): + epoly = [float(b) for b in polys[a]] + self.polynomials[a] = np.poly1d(epoly) + + def regenerateCalibration(self): + B = self.R[1] + A = self.R[0] + intercept = self.R[0] + + if self.gain is not None: + gain = self.gain_values[self.gain] + B /= gain + A /= gain + + slope = B - A + intercept = A + if self.calibrationReady and self.gain != 8: # special case for 1/11. gain + self.calPoly10 = self.__cal10__ + self.calPoly12 = self.__cal12__ + + else: + self.calPoly10 = np.poly1d([0, slope / 1023., intercept]) + self.calPoly12 = np.poly1d([0, slope / 4095., intercept]) + + self.voltToCode10 = np.poly1d([0, 1023. / slope, -1023 * intercept / slope]) + self.voltToCode12 = np.poly1d([0, 4095. / slope, -4095 * intercept / slope]) + + def __cal12__(self, RAW): + avg_shifts = (self.adc_shifts[np.int16(np.floor(RAW))] + self.adc_shifts[np.int16(np.ceil(RAW))]) / 2. + RAW -= 4095 * avg_shifts / 3.3 + return self.polynomials[self.gain](RAW) + + def __cal10__(self, RAW): + RAW *= 4095 / 1023. + avg_shifts = (self.adc_shifts[np.int16(np.floor(RAW))] + self.adc_shifts[np.int16(np.ceil(RAW))]) / 2. + RAW -= 4095 * avg_shifts / 3.3 + return self.polynomials[self.gain](RAW) ''' @@ -133,64 +130,67 @@ def __cal10__(self,RAW): print (x.name,x.calPoly10#,calfacs[x.name][0]) print ('CAL:',x.calPoly10(0),x.calPoly10(1023)) ''' -#--------------------------------------------------------------------- +# --------------------------------------------------------------------- -class analogAcquisitionChannel: - ''' - This class takes care of oscilloscope data fetched from the device. - Each instance may be linked to a particular input. - Since only up to two channels may be captured at a time with the PSLab, only two instances will be required - - Each instance will be linked to a particular inputSource instance by the capture routines. - When data is requested , it will return after applying calibration and gain details - stored in the selected inputSource - ''' - def __init__(self,a): - self.name='' - self.gain=0 - self.channel=a - self.channel_names=allAnalogChannels - #REFERENCE VOLTAGE = 3.3 V - self.calibration_ref196=1.#measured reference voltage/3.3 - self.resolution=10 - self.xaxis=np.zeros(10000) - self.yaxis=np.zeros(10000) - self.length=100 - self.timebase = 1. - self.source = analogInputSource('CH1') #use CH1 for initialization. It will be overwritten by set_params - - def fix_value(self,val): - #val[val>1020]=np.NaN - #val[val<2]=np.NaN - if self.resolution==12: - return self.calibration_ref196*self.source.calPoly12(val) - else:return self.calibration_ref196*self.source.calPoly10(val) - - def set_yval(self,pos,val): - self.yaxis[pos] = self.fix_value(val) - - def set_xval(self,pos,val): - self.xaxis[pos] = val - - def set_params(self,**keys): - self.gain = keys.get('gain',self.gain) - self.name = keys.get('channel',self.channel) - self.source = keys.get('source',self.source) - self.resolution = keys.get('resolution',self.resolution) - l = keys.get('length',self.length) - t = keys.get('timebase',self.timebase) - if t != self.timebase or l != self.length: - self.timebase = t - self.length = l - self.regenerate_xaxis() - - def regenerate_xaxis(self): - for a in range(int(self.length)): self.xaxis[a] = self.timebase*a - - def get_xaxis(self): - return self.xaxis[:self.length] - def get_yaxis(self): - return self.yaxis[:self.length] +class analogAcquisitionChannel: + """ + This class takes care of oscilloscope data fetched from the device. + Each instance may be linked to a particular input. + Since only up to two channels may be captured at a time with the PSLab, only two instances will be required + + Each instance will be linked to a particular inputSource instance by the capture routines. + When data is requested , it will return after applying calibration and gain details + stored in the selected inputSource + """ + + def __init__(self, a): + self.name = '' + self.gain = 0 + self.channel = a + self.channel_names = allAnalogChannels + # REFERENCE VOLTAGE = 3.3 V + self.calibration_ref196 = 1. # measured reference voltage/3.3 + self.resolution = 10 + self.xaxis = np.zeros(10000) + self.yaxis = np.zeros(10000) + self.length = 100 + self.timebase = 1. + self.source = analogInputSource('CH1') # use CH1 for initialization. It will be overwritten by set_params + + def fix_value(self, val): + # val[val>1020]=np.NaN + # val[val<2]=np.NaN + if self.resolution == 12: + return self.calibration_ref196 * self.source.calPoly12(val) + else: + return self.calibration_ref196 * self.source.calPoly10(val) + + def set_yval(self, pos, val): + self.yaxis[pos] = self.fix_value(val) + + def set_xval(self, pos, val): + self.xaxis[pos] = val + + def set_params(self, **keys): + self.gain = keys.get('gain', self.gain) + self.name = keys.get('channel', self.channel) + self.source = keys.get('source', self.source) + self.resolution = keys.get('resolution', self.resolution) + l = keys.get('length', self.length) + t = keys.get('timebase', self.timebase) + if t != self.timebase or l != self.length: + self.timebase = t + self.length = l + self.regenerate_xaxis() + + def regenerate_xaxis(self): + for a in range(int(self.length)): self.xaxis[a] = self.timebase * a + + def get_xaxis(self): + return self.xaxis[:int(self.length)] + + def get_yaxis(self): + return self.yaxis[:int(self.length)] diff --git a/PSL/analyticsClass.py b/PSL/analyticsClass.py index bdd9f7db..4153e0c6 100644 --- a/PSL/analyticsClass.py +++ b/PSL/analyticsClass.py @@ -2,311 +2,299 @@ import time import numpy as np -class analyticsClass(): - """ - This class contains methods that allow mathematical analysis such as curve fitting - """ - - def __init__(self): - try: - import scipy.optimize as optimize - except ImportError: - self.optimize = None - else: - self.optimize = optimize - - try: - import scipy.fftpack as fftpack - except ImportError: - self.fftpack = None - else: - self.fftpack = fftpack - - try: - from scipy.optimize import leastsq - except ImportError: - self.leastsq = None - else: - self.leastsq = leastsq - - try: - import scipy.signal as signal - except ImportError: - self.signal = None - else: - self.signal = signal - - try: - from PSL.commands_proto import applySIPrefix as applySIPrefix - except ImportError: - self.applySIPrefix = None - else: - self.applySIPrefix = applySIPrefix - - - - def sineFunc(self,x, a1, a2, a3,a4): - return a4 + a1*np.sin(abs(a2*(2*np.pi))*x + a3) - - def squareFunc(self,x, amp,freq,phase,dc,offset): - return offset + amp*self.signal.square(2 * np.pi * freq * (x - phase), duty=dc) - - - #-------------------------- Exponential Fit ---------------------------------------- - - def func(self,x, a, b, c): - return a * np.exp(-x/ b) + c - - def fit_exp(self,t,v): # accepts numpy arrays - from scipy.optimize import curve_fit - size = len(t) - v80 = v[0] * 0.8 - for k in range(size-1): - if v[k] < v80: - rc = t[k]/.223 - break - pg = [v[0], rc, 0] - po, err = curve_fit(self.func, t, v, pg) - if abs(err[0][0]) > 0.1: - return None, None - vf = po[0] * np.exp(-t/po[1]) + po[2] - return po, vf - - - def squareFit(self,xReal,yReal): - N=len(xReal) - mx = yReal.max() - mn = yReal.min() - OFFSET = (mx+mn)/2. - amplitude = (np.average(yReal[yReal>OFFSET]) - np.average(yReal[yRealOFFSET],[0,2]) - bools = abs(np.diff(yTmp))>1 - edges = xReal[bools] - levels = yTmp[bools] - frequency = 1./(edges[2]-edges[0]) - - phase=edges[0]#.5*np.pi*((yReal[0]-offset)/amplitude) - dc=0.5 - if len(edges)>=4: - if levels[0]==0: - dc = (edges[1]-edges[0])/(edges[2]-edges[0]) - else: - dc = (edges[2]-edges[1])/(edges[3]-edges[1]) - phase = edges[1] - - guess = [amplitude, frequency, phase,dc,0] - - try: - (amplitude, frequency, phase,dc,offset), pcov = self.optimize.curve_fit(self.squareFunc, xReal, yReal-OFFSET, guess) - offset+=OFFSET - - if(frequency<0): - #print ('negative frq') - return False - - freq=1e6*abs(frequency) - amp=abs(amplitude) - pcov[0]*=1e6 - #print (pcov) - if(abs(pcov[-1][0])>1e-6): - False - return [amp, freq, phase,dc,offset] - except: - return False - - def sineFit(self,xReal,yReal,**kwargs): - N=len(xReal) - OFFSET = (yReal.max()+yReal.min())/2. - yhat = self.fftpack.rfft(yReal-OFFSET) - idx = (yhat**2).argmax() - freqs = self.fftpack.rfftfreq(N, d = (xReal[1]-xReal[0])/(2*np.pi)) - frequency = kwargs.get('freq',freqs[idx]) - frequency/=(2*np.pi) #Convert angular velocity to freq - amplitude = kwargs.get('amp',(yReal.max()-yReal.min())/2.0) - phase=kwargs.get('phase',0) #.5*np.pi*((yReal[0]-offset)/amplitude) - guess = [amplitude, frequency, phase,0] - try: - (amplitude, frequency, phase,offset), pcov = self.optimize.curve_fit(self.sineFunc, xReal, yReal-OFFSET, guess) - offset+=OFFSET - ph = ((phase)*180/(np.pi)) - if(frequency<0): - #print ('negative frq') - return False - - if(amplitude<0): - ph-=180 - - if(ph<0):ph = (ph+720)%360 - freq=1e6*abs(frequency) - amp=abs(amplitude) - pcov[0]*=1e6 - #print (pcov) - if(abs(pcov[-1][0])>1e-6): - False - return [amp, freq, offset,ph] - except: - return False - - - def find_frequency(self, v, si): # voltages, samplimg interval is seconds - from numpy import fft - NP = len(v) - v = v -v.mean() # remove DC component - frq = fft.fftfreq(NP, si)[:NP/2] # take only the +ive half of the frequncy array - amp = abs(fft.fft(v)[:NP/2])/NP # and the fft result - index = amp.argmax() # search for the tallest peak, the fundamental - return frq[index] - - def sineFit2(self,x,y): - freq = self.find_frequency(y, x[1]-x[0]) - amp =(y.max()-y.min())/2.0 - guess = [amp, freq, 0, 0] #amplitude, freq, phase,offset - #print (guess) - OS = y.mean() - try: - par, pcov = self.optimize.curve_fit(self.sineFunc, x, y-OS, guess) - except: - return None - vf = self.sineFunc(t, par[0], par[1], par[2], par[3]) - diff = sum((v-vf)**2)/max(v) - if diff > self.error_limit: - guess[2] += pi/2 # try an out of phase - try: - #print 'L1: diff = %5.0f frset= %6.3f fr = %6.2f phi = %6.2f'%(diff, res,par[1]*1e6,par[2]) - par, pcov = curve_fit(self.sineFunc, x, y, guess) - except: - return None - vf = self.sineFunc(t, par[0], par[1], par[2], par[3]) - diff = sum((v-vf)**2)/max(v) - if diff > self.error_limit: - #print 'L2: diff = %5.0f frset= %6.3f fr = %6.2f phi = %6.2f'%(diff, res,par[1]*1e6,par[2]) - return None - else: - pass - #print 'fixed ',par[1]*1e6 - return par, vf - - - def amp_spectrum(self, v, si, nhar=8): - # voltages, samplimg interval is seconds, number of harmonics to retain - from numpy import fft - NP = len(v) - frq = fft.fftfreq(NP, si)[:NP/2] # take only the +ive half of the frequncy array - amp = abs(fft.fft(v)[:NP/2])/NP # and the fft result - index = amp.argmax() # search for the tallest peak, the fundamental - if index == 0: # DC component is dominating - index = amp[4:].argmax() # skip frequencies close to zero - return frq[:index*nhar], amp[:index*nhar] # restrict to 'nhar' harmonics - - - def dampedSine(self,x, amp, freq, phase,offset,damp): - """ - A damped sine wave function - - """ - return offset + amp*np.exp(-damp*x)*np.sin(abs(freq)*x + phase) - - def getGuessValues(self,xReal,yReal,func='sine'): - if(func=='sine' or func=='damped sine'): - N=len(xReal) - offset = np.average(yReal) - yhat = self.fftpack.rfft(yReal-offset) - idx = (yhat**2).argmax() - freqs = self.fftpack.rfftfreq(N, d = (xReal[1]-xReal[0])/(2*np.pi)) - frequency = freqs[idx] - - amplitude = (yReal.max()-yReal.min())/2.0 - phase=0. - if func=='sine': - return amplitude, frequency, phase,offset - if func=='damped sine': - return amplitude, frequency, phase,offset,0 - - def arbitFit(self,xReal,yReal,func,**args): - N=len(xReal) - guess=args.get('guess',[]) - try: - results, pcov = self.optimize.curve_fit(func, xReal, yReal,guess) - pcov[0]*=1e6 - return True,results,pcov - except: - return False,[],[] - - - def fft(self,ya, si): - ''' - Returns positive half of the Fourier transform of the signal ya. - Sampling interval 'si', in milliseconds - ''' - ns = len(ya) - if ns %2 == 1: # odd values of np give exceptions - ns=ns-1 # make it even - ya=ya[:-1] - v = np.array(ya) - tr = abs(np.fft.fft(v))/ns - frq = np.fft.fftfreq(ns, si) - x = frq.reshape(2,ns/2) - y = tr.reshape(2,ns/2) - return x[0], y[0] - - - def sineFitAndDisplay(self,chan,displayObject): - ''' - chan : an object containing a get_xaxis, and a get_yaxis method. - displayObject : an object containing a setValue method - - Fits against a sine function, and writes to the object - ''' - fitres=None;fit='' - try: - fitres = self.sineFit(chan.get_xaxis(),chan.get_yaxis()) - if fitres: - amp,freq,offset,phase = fitres - if amp>0.05: fit = 'Voltage=%s\nFrequency=%s'%(self.applySIPrefix(amp,'V'),self.applySIPrefix(freq,'Hz')) - except Exception as e: - fires=None - - if not fitres or len(fit)==0: fit = 'Voltage=%s\n'%(self.applySIPrefix(np.average(chan.get_yaxis()),'V')) - displayObject.setValue(fit) - if fitres: return fitres - else: return 0,0,0,0 - - - def rmsAndDisplay(self,data,displayObject): - ''' - data : an array of numbers - displayObject : an object containing a setValue method - - Fits against a sine function, and writes to the object - ''' - rms = self.RMS(data) - displayObject.setValue('Voltage=%s'%(self.applySIPrefix(rms,'V'))) - return rms - - def RMS(self,data): - data = np.array(data) - return np.sqrt(np.average(data*data)) - - - - - def butter_notch(self,lowcut, highcut, fs, order=5): - from scipy.signal import butter - nyq = 0.5 * fs - low = lowcut / nyq - high = highcut / nyq - b, a = butter(order, [low, high], btype='bandstop') - return b, a - - - def butter_notch_filter(self,data, lowcut, highcut, fs, order=5): - from scipy.signal import lfilter - b, a = self.butter_notch(lowcut, highcut, fs, order=order) - y = lfilter(b, a, data) - return y - - - - - +class analyticsClass(): + """ + This class contains methods that allow mathematical analysis such as curve fitting + """ + + def __init__(self): + try: + import scipy.optimize as optimize + except ImportError: + self.optimize = None + else: + self.optimize = optimize + + try: + import scipy.fftpack as fftpack + except ImportError: + self.fftpack = None + else: + self.fftpack = fftpack + + try: + from scipy.optimize import leastsq + except ImportError: + self.leastsq = None + else: + self.leastsq = leastsq + + try: + import scipy.signal as signal + except ImportError: + self.signal = None + else: + self.signal = signal + + try: + from PSL.commands_proto import applySIPrefix as applySIPrefix + except ImportError: + self.applySIPrefix = None + else: + self.applySIPrefix = applySIPrefix + + def sineFunc(self, x, a1, a2, a3, a4): + return a4 + a1 * np.sin(abs(a2 * (2 * np.pi)) * x + a3) + + def squareFunc(self, x, amp, freq, phase, dc, offset): + return offset + amp * self.signal.square(2 * np.pi * freq * (x - phase), duty=dc) + + # -------------------------- Exponential Fit ---------------------------------------- + + def func(self, x, a, b, c): + return a * np.exp(-x / b) + c + + def fit_exp(self, t, v): # accepts numpy arrays + size = len(t) + v80 = v[0] * 0.8 + for k in range(size - 1): + if v[k] < v80: + rc = t[k] / .223 + break + pg = [v[0], rc, 0] + po, err = self.optimize.curve_fit(self.func, t, v, pg) + if abs(err[0][0]) > 0.1: + return None, None + vf = po[0] * np.exp(-t / po[1]) + po[2] + return po, vf + + def squareFit(self, xReal, yReal): + N = len(xReal) + mx = yReal.max() + mn = yReal.min() + OFFSET = (mx + mn) / 2. + amplitude = (np.average(yReal[yReal > OFFSET]) - np.average(yReal[yReal < OFFSET])) / 2.0 + yTmp = np.select([yReal < OFFSET, yReal > OFFSET], [0, 2]) + bools = abs(np.diff(yTmp)) > 1 + edges = xReal[bools] + levels = yTmp[bools] + frequency = 1. / (edges[2] - edges[0]) + + phase = edges[0] # .5*np.pi*((yReal[0]-offset)/amplitude) + dc = 0.5 + if len(edges) >= 4: + if levels[0] == 0: + dc = (edges[1] - edges[0]) / (edges[2] - edges[0]) + else: + dc = (edges[2] - edges[1]) / (edges[3] - edges[1]) + phase = edges[1] + + guess = [amplitude, frequency, phase, dc, 0] + + try: + (amplitude, frequency, phase, dc, offset), pcov = self.optimize.curve_fit(self.squareFunc, xReal, + yReal - OFFSET, guess) + offset += OFFSET + + if (frequency < 0): + # print ('negative frq') + return False + + freq = 1e6 * abs(frequency) + amp = abs(amplitude) + pcov[0] *= 1e6 + # print (pcov) + if (abs(pcov[-1][0]) > 1e-6): + False + return [amp, freq, phase, dc, offset] + except: + return False + + def sineFit(self, xReal, yReal, **kwargs): + N = len(xReal) + OFFSET = (yReal.max() + yReal.min()) / 2. + yhat = self.fftpack.rfft(yReal - OFFSET) + idx = (yhat ** 2).argmax() + freqs = self.fftpack.rfftfreq(N, d=(xReal[1] - xReal[0]) / (2 * np.pi)) + frequency = kwargs.get('freq', freqs[idx]) + frequency /= (2 * np.pi) # Convert angular velocity to freq + amplitude = kwargs.get('amp', (yReal.max() - yReal.min()) / 2.0) + phase = kwargs.get('phase', 0) # .5*np.pi*((yReal[0]-offset)/amplitude) + guess = [amplitude, frequency, phase, 0] + try: + (amplitude, frequency, phase, offset), pcov = self.optimize.curve_fit(self.sineFunc, xReal, yReal - OFFSET, + guess) + offset += OFFSET + ph = ((phase) * 180 / (np.pi)) + if (frequency < 0): + # print ('negative frq') + return False + + if (amplitude < 0): + ph -= 180 + + if (ph < 0): + ph = (ph + 720) % 360 + + freq = 1e6 * abs(frequency) + amp = abs(amplitude) + pcov[0] *= 1e6 + # print (pcov) + if (abs(pcov[-1][0]) > 1e-6): + return False + return [amp, freq, offset, ph] + except: + return False + + def find_frequency(self, v, si): # voltages, samplimg interval is seconds + from numpy import fft + NP = len(v) + v = v - v.mean() # remove DC component + frq = fft.fftfreq(NP, si)[:NP / 2] # take only the +ive half of the frequncy array + amp = abs(fft.fft(v)[:NP / 2]) / NP # and the fft result + index = amp.argmax() # search for the tallest peak, the fundamental + return frq[index] + + def sineFit2(self, x, y, t, v): + freq = self.find_frequency(y, x[1] - x[0]) + amp = (y.max() - y.min()) / 2.0 + guess = [amp, freq, 0, 0] # amplitude, freq, phase,offset + # print (guess) + OS = y.mean() + try: + par, pcov = self.optimize.curve_fit(self.sineFunc, x, y - OS, guess) + except: + return None + vf = self.sineFunc(t, par[0], par[1], par[2], par[3]) + diff = sum((v - vf) ** 2) / max(v) + if diff > self.error_limit: + guess[2] += np.pi / 2 # try an out of phase + try: + # print 'L1: diff = %5.0f frset= %6.3f fr = %6.2f phi = %6.2f'%(diff, res,par[1]*1e6,par[2]) + par, pcov = self.optimize.curve_fit(self.sineFunc, x, y, guess) + except: + return None + vf = self.sineFunc(t, par[0], par[1], par[2], par[3]) + diff = sum((v - vf) ** 2) / max(v) + if diff > self.error_limit: + # print 'L2: diff = %5.0f frset= %6.3f fr = %6.2f phi = %6.2f'%(diff, res,par[1]*1e6,par[2]) + return None + else: + pass + # print 'fixed ',par[1]*1e6 + return par, vf + + def amp_spectrum(self, v, si, nhar=8): + # voltages, samplimg interval is seconds, number of harmonics to retain + from numpy import fft + NP = len(v) + frq = fft.fftfreq(NP, si)[:NP / 2] # take only the +ive half of the frequncy array + amp = abs(fft.fft(v)[:NP / 2]) / NP # and the fft result + index = amp.argmax() # search for the tallest peak, the fundamental + if index == 0: # DC component is dominating + index = amp[4:].argmax() # skip frequencies close to zero + return frq[:index * nhar], amp[:index * nhar] # restrict to 'nhar' harmonics + + def dampedSine(self, x, amp, freq, phase, offset, damp): + """ + A damped sine wave function + + """ + return offset + amp * np.exp(-damp * x) * np.sin(abs(freq) * x + phase) + + def getGuessValues(self, xReal, yReal, func='sine'): + if (func == 'sine' or func == 'damped sine'): + N = len(xReal) + offset = np.average(yReal) + yhat = self.fftpack.rfft(yReal - offset) + idx = (yhat ** 2).argmax() + freqs = self.fftpack.rfftfreq(N, d=(xReal[1] - xReal[0]) / (2 * np.pi)) + frequency = freqs[idx] + + amplitude = (yReal.max() - yReal.min()) / 2.0 + phase = 0. + if func == 'sine': + return amplitude, frequency, phase, offset + if func == 'damped sine': + return amplitude, frequency, phase, offset, 0 + + def arbitFit(self, xReal, yReal, func, **args): + N = len(xReal) + guess = args.get('guess', []) + try: + results, pcov = self.optimize.curve_fit(func, xReal, yReal, guess) + pcov[0] *= 1e6 + return True, results, pcov + except: + return False, [], [] + + def fft(self, ya, si): + ''' + Returns positive half of the Fourier transform of the signal ya. + Sampling interval 'si', in milliseconds + ''' + ns = len(ya) + if ns % 2 == 1: # odd values of np give exceptions + ns -= 1 # make it even + ya = ya[:-1] + v = np.array(ya) + tr = abs(np.fft.fft(v)) / ns + frq = np.fft.fftfreq(ns, si) + x = frq.reshape(2, ns // 2) + y = tr.reshape(2, ns // 2) + return x[0], y[0] + + def sineFitAndDisplay(self, chan, displayObject): + ''' + chan : an object containing a get_xaxis, and a get_yaxis method. + displayObject : an object containing a setValue method + + Fits against a sine function, and writes to the object + ''' + fitres = None + fit = '' + try: + fitres = self.sineFit(chan.get_xaxis(), chan.get_yaxis()) + if fitres: + amp, freq, offset, phase = fitres + if amp > 0.05: fit = 'Voltage=%s\nFrequency=%s' % ( + self.applySIPrefix(amp, 'V'), self.applySIPrefix(freq, 'Hz')) + except Exception as e: + fires = None + + if not fitres or len(fit) == 0: fit = 'Voltage=%s\n' % (self.applySIPrefix(np.average(chan.get_yaxis()), 'V')) + displayObject.setValue(fit) + if fitres: + return fitres + else: + return 0, 0, 0, 0 + + def rmsAndDisplay(self, data, displayObject): + ''' + data : an array of numbers + displayObject : an object containing a setValue method + + Fits against a sine function, and writes to the object + ''' + rms = self.RMS(data) + displayObject.setValue('Voltage=%s' % (self.applySIPrefix(rms, 'V'))) + return rms + + def RMS(self, data): + data = np.array(data) + return np.sqrt(np.average(data * data)) + + def butter_notch(self, lowcut, highcut, fs, order=5): + from scipy.signal import butter + nyq = 0.5 * fs + low = lowcut / nyq + high = highcut / nyq + b, a = butter(order, [low, high], btype='bandstop') + return b, a + + def butter_notch_filter(self, data, lowcut, highcut, fs, order=5): + from scipy.signal import lfilter + b, a = self.butter_notch(lowcut, highcut, fs, order=order) + y = lfilter(b, a, data) + return y diff --git a/PSL/commands_proto.py b/PSL/commands_proto.py index 910672b7..f912e839 100644 --- a/PSL/commands_proto.py +++ b/PSL/commands_proto.py @@ -1,286 +1,283 @@ -import math,sys,time, struct +import math, sys, time, struct # allows to pack numeric values into byte strings -Byte = struct.Struct("B") # size 1 -ShortInt = struct.Struct("H") # size 2 -Integer= struct.Struct("I") # size 4 +Byte = struct.Struct("B") # size 1 +ShortInt = struct.Struct("H") # size 2 +Integer = struct.Struct("I") # size 4 ACKNOWLEDGE = Byte.pack(254) MAX_SAMPLES = 10000 DATA_SPLITTING = 200 -#/*----flash memory----*/ -FLASH =Byte.pack(1) -READ_FLASH = Byte.pack(1) -WRITE_FLASH = Byte.pack(2) +# /*----flash memory----*/ +FLASH = Byte.pack(1) +READ_FLASH = Byte.pack(1) +WRITE_FLASH = Byte.pack(2) WRITE_BULK_FLASH = Byte.pack(3) -READ_BULK_FLASH = Byte.pack(4) - -#/*-----ADC------*/ -ADC = Byte.pack(2) -CAPTURE_ONE = Byte.pack(1) -CAPTURE_TWO = Byte.pack(2) -CAPTURE_DMASPEED = Byte.pack(3) -CAPTURE_FOUR = Byte.pack(4) -CONFIGURE_TRIGGER = Byte.pack(5) -GET_CAPTURE_STATUS = Byte.pack(6) +READ_BULK_FLASH = Byte.pack(4) + +# /*-----ADC------*/ +ADC = Byte.pack(2) +CAPTURE_ONE = Byte.pack(1) +CAPTURE_TWO = Byte.pack(2) +CAPTURE_DMASPEED = Byte.pack(3) +CAPTURE_FOUR = Byte.pack(4) +CONFIGURE_TRIGGER = Byte.pack(5) +GET_CAPTURE_STATUS = Byte.pack(6) GET_CAPTURE_CHANNEL = Byte.pack(7) -SET_PGA_GAIN = Byte.pack(8) -GET_VOLTAGE = Byte.pack(9) -GET_VOLTAGE_SUMMED = Byte.pack(10) +SET_PGA_GAIN = Byte.pack(8) +GET_VOLTAGE = Byte.pack(9) +GET_VOLTAGE_SUMMED = Byte.pack(10) START_ADC_STREAMING = Byte.pack(11) -SELECT_PGA_CHANNEL = Byte.pack(12) -CAPTURE_12BIT = Byte.pack(13) -CAPTURE_MULTIPLE = Byte.pack(14) -SET_HI_CAPTURE = Byte.pack(15) -SET_LO_CAPTURE = Byte.pack(16) - -MULTIPOINT_CAPACITANCE= Byte.pack(20) -SET_CAP = Byte.pack(21) -PULSE_TRAIN = Byte.pack(22) - -#/*-----SPI--------*/ -SPI_HEADER = Byte.pack(3) -START_SPI = Byte.pack(1) -SEND_SPI8 = Byte.pack(2) -SEND_SPI16 = Byte.pack(3) -STOP_SPI = Byte.pack(4) +SELECT_PGA_CHANNEL = Byte.pack(12) +CAPTURE_12BIT = Byte.pack(13) +CAPTURE_MULTIPLE = Byte.pack(14) +SET_HI_CAPTURE = Byte.pack(15) +SET_LO_CAPTURE = Byte.pack(16) + +MULTIPOINT_CAPACITANCE = Byte.pack(20) +SET_CAP = Byte.pack(21) +PULSE_TRAIN = Byte.pack(22) + +# /*-----SPI--------*/ +SPI_HEADER = Byte.pack(3) +START_SPI = Byte.pack(1) +SEND_SPI8 = Byte.pack(2) +SEND_SPI16 = Byte.pack(3) +STOP_SPI = Byte.pack(4) SET_SPI_PARAMETERS = Byte.pack(5) -SEND_SPI8_BURST = Byte.pack(6) -SEND_SPI16_BURST = Byte.pack(7) -#/*------I2C-------*/ -I2C_HEADER = Byte.pack(4) -I2C_START = Byte.pack(1) -I2C_SEND = Byte.pack(2) -I2C_STOP = Byte.pack(3) -I2C_RESTART = Byte.pack(4) -I2C_READ_END = Byte.pack(5) -I2C_READ_MORE = Byte.pack(6) -I2C_WAIT = Byte.pack(7) -I2C_SEND_BURST = Byte.pack(8) -I2C_CONFIG = Byte.pack(9) -I2C_STATUS = Byte.pack(10) -I2C_READ_BULK = Byte.pack(11) -I2C_WRITE_BULK = Byte.pack(12) +SEND_SPI8_BURST = Byte.pack(6) +SEND_SPI16_BURST = Byte.pack(7) +# /*------I2C-------*/ +I2C_HEADER = Byte.pack(4) +I2C_START = Byte.pack(1) +I2C_SEND = Byte.pack(2) +I2C_STOP = Byte.pack(3) +I2C_RESTART = Byte.pack(4) +I2C_READ_END = Byte.pack(5) +I2C_READ_MORE = Byte.pack(6) +I2C_WAIT = Byte.pack(7) +I2C_SEND_BURST = Byte.pack(8) +I2C_CONFIG = Byte.pack(9) +I2C_STATUS = Byte.pack(10) +I2C_READ_BULK = Byte.pack(11) +I2C_WRITE_BULK = Byte.pack(12) I2C_ENABLE_SMBUS = Byte.pack(13) -I2C_INIT = Byte.pack(14) +I2C_INIT = Byte.pack(14) I2C_PULLDOWN_SCL = Byte.pack(15) -I2C_DISABLE_SMBUS= Byte.pack(16) -I2C_START_SCOPE = Byte.pack(17) +I2C_DISABLE_SMBUS = Byte.pack(16) +I2C_START_SCOPE = Byte.pack(17) -#/*------UART2--------*/ -UART_2 = Byte.pack(5) -SEND_BYTE = Byte.pack(1) -SEND_INT = Byte.pack(2) +# /*------UART2--------*/ +UART_2 = Byte.pack(5) +SEND_BYTE = Byte.pack(1) +SEND_INT = Byte.pack(2) SEND_ADDRESS = Byte.pack(3) -SET_BAUD = Byte.pack(4) -SET_MODE = Byte.pack(5) -READ_BYTE = Byte.pack(6) -READ_INT = Byte.pack(7) -READ_UART2_STATUS = Byte.pack(8) - -#/*-----------DAC--------*/ -DAC = Byte.pack(6) -SET_DAC = Byte.pack(1) +SET_BAUD = Byte.pack(4) +SET_MODE = Byte.pack(5) +READ_BYTE = Byte.pack(6) +READ_INT = Byte.pack(7) +READ_UART2_STATUS = Byte.pack(8) + +# /*-----------DAC--------*/ +DAC = Byte.pack(6) +SET_DAC = Byte.pack(1) SET_CALIBRATED_DAC = Byte.pack(2) - -#/*--------WAVEGEN-----*/ -WAVEGEN = Byte.pack(7) -SET_WG = Byte.pack(1) -SET_SQR1 = Byte.pack(3) -SET_SQR2 = Byte.pack(4) -SET_SQRS = Byte.pack(5) +# /*--------WAVEGEN-----*/ +WAVEGEN = Byte.pack(7) +SET_WG = Byte.pack(1) +SET_SQR1 = Byte.pack(3) +SET_SQR2 = Byte.pack(4) +SET_SQRS = Byte.pack(5) TUNE_SINE_OSCILLATOR = Byte.pack(6) -SQR4 = Byte.pack(7) -MAP_REFERENCE = Byte.pack(8) -SET_BOTH_WG = Byte.pack(9) -SET_WAVEFORM_TYPE = Byte.pack(10) +SQR4 = Byte.pack(7) +MAP_REFERENCE = Byte.pack(8) +SET_BOTH_WG = Byte.pack(9) +SET_WAVEFORM_TYPE = Byte.pack(10) SELECT_FREQ_REGISTER = Byte.pack(11) -DELAY_GENERATOR = Byte.pack(12) -SET_SINE1 = Byte.pack(13) -SET_SINE2 = Byte.pack(14) - -LOAD_WAVEFORM1 = Byte.pack(15) -LOAD_WAVEFORM2 = Byte.pack(16) -SQR1_PATTERN = Byte.pack(17) -#/*-----digital outputs----*/ -DOUT = Byte.pack(8) +DELAY_GENERATOR = Byte.pack(12) +SET_SINE1 = Byte.pack(13) +SET_SINE2 = Byte.pack(14) + +LOAD_WAVEFORM1 = Byte.pack(15) +LOAD_WAVEFORM2 = Byte.pack(16) +SQR1_PATTERN = Byte.pack(17) +# /*-----digital outputs----*/ +DOUT = Byte.pack(8) SET_STATE = Byte.pack(1) -#/*-----digital inputs-----*/ -DIN = Byte.pack(9) -GET_STATE = Byte.pack(1) +# /*-----digital inputs-----*/ +DIN = Byte.pack(9) +GET_STATE = Byte.pack(1) GET_STATES = Byte.pack(2) - -ID1 = Byte.pack(0) -ID2 = Byte.pack(1) -ID3 = Byte.pack(2) -ID4 = Byte.pack(3) -LMETER = Byte.pack(4) - - -#/*------TIMING FUNCTIONS-----*/ -TIMING = Byte.pack(10) -GET_TIMING = Byte.pack(1) -GET_PULSE_TIME = Byte.pack(2) -GET_DUTY_CYCLE = Byte.pack(3) -START_ONE_CHAN_LA = Byte.pack(4) -START_TWO_CHAN_LA = Byte.pack(5) -START_FOUR_CHAN_LA = Byte.pack(6) -FETCH_DMA_DATA = Byte.pack(7) -FETCH_INT_DMA_DATA = Byte.pack(8) -FETCH_LONG_DMA_DATA = Byte.pack(9) -GET_LA_PROGRESS = Byte.pack(10) -GET_INITIAL_DIGITAL_STATES = Byte.pack(11) - -TIMING_MEASUREMENTS = Byte.pack(12) -INTERVAL_MEASUREMENTS = Byte.pack(13) -CONFIGURE_COMPARATOR = Byte.pack(14) +ID1 = Byte.pack(0) +ID2 = Byte.pack(1) +ID3 = Byte.pack(2) +ID4 = Byte.pack(3) +LMETER = Byte.pack(4) + +# /*------TIMING FUNCTIONS-----*/ +TIMING = Byte.pack(10) +GET_TIMING = Byte.pack(1) +GET_PULSE_TIME = Byte.pack(2) +GET_DUTY_CYCLE = Byte.pack(3) +START_ONE_CHAN_LA = Byte.pack(4) +START_TWO_CHAN_LA = Byte.pack(5) +START_FOUR_CHAN_LA = Byte.pack(6) +FETCH_DMA_DATA = Byte.pack(7) +FETCH_INT_DMA_DATA = Byte.pack(8) +FETCH_LONG_DMA_DATA = Byte.pack(9) +GET_LA_PROGRESS = Byte.pack(10) +GET_INITIAL_DIGITAL_STATES = Byte.pack(11) + +TIMING_MEASUREMENTS = Byte.pack(12) +INTERVAL_MEASUREMENTS = Byte.pack(13) +CONFIGURE_COMPARATOR = Byte.pack(14) START_ALTERNATE_ONE_CHAN_LA = Byte.pack(15) -START_THREE_CHAN_LA = Byte.pack(16) -STOP_LA = Byte.pack(17) +START_THREE_CHAN_LA = Byte.pack(16) +STOP_LA = Byte.pack(17) -#/*--------MISCELLANEOUS------*/ -COMMON = Byte.pack(11) +# /*--------MISCELLANEOUS------*/ +COMMON = Byte.pack(11) -GET_CTMU_VOLTAGE = Byte.pack(1) -GET_CAPACITANCE = Byte.pack(2) -GET_FREQUENCY = Byte.pack(3) -GET_INDUCTANCE = Byte.pack(4) +GET_CTMU_VOLTAGE = Byte.pack(1) +GET_CAPACITANCE = Byte.pack(2) +GET_FREQUENCY = Byte.pack(3) +GET_INDUCTANCE = Byte.pack(4) -GET_VERSION = Byte.pack(5) +GET_VERSION = Byte.pack(5) -RETRIEVE_BUFFER = Byte.pack(8) -GET_HIGH_FREQUENCY = Byte.pack(9) -CLEAR_BUFFER = Byte.pack(10) -SET_RGB1 = Byte.pack(11) -READ_PROGRAM_ADDRESS = Byte.pack(12) +RETRIEVE_BUFFER = Byte.pack(8) +GET_HIGH_FREQUENCY = Byte.pack(9) +CLEAR_BUFFER = Byte.pack(10) +SET_RGB1 = Byte.pack(11) +READ_PROGRAM_ADDRESS = Byte.pack(12) WRITE_PROGRAM_ADDRESS = Byte.pack(13) -READ_DATA_ADDRESS = Byte.pack(14) -WRITE_DATA_ADDRESS = Byte.pack(15) +READ_DATA_ADDRESS = Byte.pack(14) +WRITE_DATA_ADDRESS = Byte.pack(15) -GET_CAP_RANGE = Byte.pack(16) -SET_RGB2 = Byte.pack(17) -READ_LOG = Byte.pack(18) -RESTORE_STANDALONE = Byte.pack(19) +GET_CAP_RANGE = Byte.pack(16) +SET_RGB2 = Byte.pack(17) +READ_LOG = Byte.pack(18) +RESTORE_STANDALONE = Byte.pack(19) GET_ALTERNATE_HIGH_FREQUENCY = Byte.pack(20) -SET_RGB3 = Byte.pack(22) - -START_CTMU = Byte.pack(23) -STOP_CTMU = Byte.pack(24) - -START_COUNTING = Byte.pack(25) -FETCH_COUNT = Byte.pack(26) -FILL_BUFFER = Byte.pack(27) - -#/*---------- BAUDRATE for main comm channel----*/ -SETBAUD = Byte.pack(12) -BAUD9600 = Byte.pack(1) -BAUD14400 = Byte.pack(2) -BAUD19200 = Byte.pack(3) -BAUD28800 = Byte.pack(4) -BAUD38400 = Byte.pack(5) -BAUD57600 = Byte.pack(6) -BAUD115200 = Byte.pack(7) -BAUD230400 = Byte.pack(8) +SET_RGB3 = Byte.pack(22) + +START_CTMU = Byte.pack(23) +STOP_CTMU = Byte.pack(24) + +START_COUNTING = Byte.pack(25) +FETCH_COUNT = Byte.pack(26) +FILL_BUFFER = Byte.pack(27) + +# /*---------- BAUDRATE for main comm channel----*/ +SETBAUD = Byte.pack(12) +BAUD9600 = Byte.pack(1) +BAUD14400 = Byte.pack(2) +BAUD19200 = Byte.pack(3) +BAUD28800 = Byte.pack(4) +BAUD38400 = Byte.pack(5) +BAUD57600 = Byte.pack(6) +BAUD115200 = Byte.pack(7) +BAUD230400 = Byte.pack(8) BAUD1000000 = Byte.pack(9) -#/*-----------NRFL01 radio module----------*/ -NRFL01 = Byte.pack(13) -NRF_SETUP = Byte.pack(1) -NRF_RXMODE = Byte.pack(2) -NRF_TXMODE = Byte.pack(3) -NRF_POWER_DOWN = Byte.pack(4) -NRF_RXCHAR = Byte.pack(5) -NRF_TXCHAR = Byte.pack(6) -NRF_HASDATA = Byte.pack(7) -NRF_FLUSH = Byte.pack(8) -NRF_WRITEREG = Byte.pack(9) -NRF_READREG = Byte.pack(10) -NRF_GETSTATUS = Byte.pack(11) -NRF_WRITECOMMAND = Byte.pack(12) -NRF_WRITEPAYLOAD = Byte.pack(13) -NRF_READPAYLOAD = Byte.pack(14) -NRF_WRITEADDRESS = Byte.pack(15) -NRF_TRANSACTION = Byte.pack(16) +# /*-----------NRFL01 radio module----------*/ +NRFL01 = Byte.pack(13) +NRF_SETUP = Byte.pack(1) +NRF_RXMODE = Byte.pack(2) +NRF_TXMODE = Byte.pack(3) +NRF_POWER_DOWN = Byte.pack(4) +NRF_RXCHAR = Byte.pack(5) +NRF_TXCHAR = Byte.pack(6) +NRF_HASDATA = Byte.pack(7) +NRF_FLUSH = Byte.pack(8) +NRF_WRITEREG = Byte.pack(9) +NRF_READREG = Byte.pack(10) +NRF_GETSTATUS = Byte.pack(11) +NRF_WRITECOMMAND = Byte.pack(12) +NRF_WRITEPAYLOAD = Byte.pack(13) +NRF_READPAYLOAD = Byte.pack(14) +NRF_WRITEADDRESS = Byte.pack(15) +NRF_TRANSACTION = Byte.pack(16) NRF_START_TOKEN_MANAGER = Byte.pack(17) -NRF_STOP_TOKEN_MANAGER = Byte.pack(18) -NRF_TOTAL_TOKENS = Byte.pack(19) -NRF_REPORTS = Byte.pack(20) -NRF_WRITE_REPORT = Byte.pack(21) -NRF_DELETE_REPORT_ROW = Byte.pack(22) +NRF_STOP_TOKEN_MANAGER = Byte.pack(18) +NRF_TOTAL_TOKENS = Byte.pack(19) +NRF_REPORTS = Byte.pack(20) +NRF_WRITE_REPORT = Byte.pack(21) +NRF_DELETE_REPORT_ROW = Byte.pack(22) -NRF_WRITEADDRESSES = Byte.pack(23) +NRF_WRITEADDRESSES = Byte.pack(23) -#---------Non standard IO protocols-------- +# ---------Non standard IO protocols-------- NONSTANDARD_IO = Byte.pack(14) -HX711_HEADER = Byte.pack(1) -HCSR04_HEADER = Byte.pack(2) -AM2302_HEADER = Byte.pack(3) +HX711_HEADER = Byte.pack(1) +HCSR04_HEADER = Byte.pack(2) +AM2302_HEADER = Byte.pack(3) TCD1304_HEADER = Byte.pack(4) -STEPPER_MOTOR = Byte.pack(5) +STEPPER_MOTOR = Byte.pack(5) -#--------COMMUNICATION PASSTHROUGHS-------- -#Data sent to the device is directly routed to output ports such as (SCL, SDA for UART) +# --------COMMUNICATION PASSTHROUGHS-------- +# Data sent to the device is directly routed to output ports such as (SCL, SDA for UART) PASSTHROUGHS = Byte.pack(15) -PASS_UART = Byte.pack(1) +PASS_UART = Byte.pack(1) -#/*--------STOP STREAMING------*/ +# /*--------STOP STREAMING------*/ STOP_STREAMING = Byte.pack(253) -#/*------INPUT CAPTURE---------*/ -#capture modes +# /*------INPUT CAPTURE---------*/ +# capture modes EVERY_SIXTEENTH_RISING_EDGE = Byte.pack(0b101) -EVERY_FOURTH_RISING_EDGE = Byte.pack(0b100) -EVERY_RISING_EDGE = Byte.pack(0b011) -EVERY_FALLING_EDGE = Byte.pack(0b010) -EVERY_EDGE = Byte.pack(0b001) -DISABLED = Byte.pack(0b000) +EVERY_FOURTH_RISING_EDGE = Byte.pack(0b100) +EVERY_RISING_EDGE = Byte.pack(0b011) +EVERY_FALLING_EDGE = Byte.pack(0b010) +EVERY_EDGE = Byte.pack(0b001) +DISABLED = Byte.pack(0b000) -#/*--------Chip selects-----------*/ +# /*--------Chip selects-----------*/ CSA1 = Byte.pack(1) CSA2 = Byte.pack(2) CSA3 = Byte.pack(3) CSA4 = Byte.pack(4) CSA5 = Byte.pack(5) -CS1 = Byte.pack(6) -CS2 = Byte.pack(7) +CS1 = Byte.pack(6) +CS2 = Byte.pack(7) -#resolutions -TEN_BIT = Byte.pack(10) +# resolutions +TEN_BIT = Byte.pack(10) TWELVE_BIT = Byte.pack(12) - -def applySIPrefix(value, unit='',precision=2 ): - neg = False - if value < 0.: - value *= -1; neg = True - elif value == 0.: return '0 ' # mantissa & exponnt both 0 - exponent = int(math.log10(value)) - if exponent > 0: - exponent = (exponent // 3) * 3 - else: - exponent = (-1*exponent + 3) // 3 * (-3) - - value *= (10 ** (-exponent) ) - if value >= 1000.: - value /= 1000.0 - exponent += 3 - if neg: - value *= -1 - exponent = int(exponent) - PREFIXES = "yzafpnum kMGTPEZY" - prefix_levels = (len(PREFIXES) - 1) // 2 - si_level = exponent // 3 - if abs(si_level) > prefix_levels: - raise ValueError("Exponent out range of available prefixes.") - return '%.*f %s%s' % (precision, value,PREFIXES[si_level + prefix_levels],unit) - +def applySIPrefix(value, unit='', precision=2): + neg = False + if value < 0.: + value *= -1 + neg = True + elif value == 0.: + return '0 ' # mantissa & exponnt both 0 + exponent = int(math.log10(value)) + if exponent > 0: + exponent = (exponent // 3) * 3 + else: + exponent = (-1 * exponent + 3) // 3 * (-3) + + value *= (10 ** (-exponent)) + if value >= 1000.: + value /= 1000.0 + exponent += 3 + if neg: + value *= -1 + exponent = int(exponent) + PREFIXES = "yzafpnum kMGTPEZY" + prefix_levels = (len(PREFIXES) - 1) // 2 + si_level = exponent // 3 + if abs(si_level) > prefix_levels: + raise ValueError("Exponent out range of available prefixes.") + return '%.*f %s%s' % (precision, value, PREFIXES[si_level + prefix_levels], unit) ''' diff --git a/PSL/digital_channel.py b/PSL/digital_channel.py index 53c7d516..ef8c0396 100644 --- a/PSL/digital_channel.py +++ b/PSL/digital_channel.py @@ -1,112 +1,120 @@ from __future__ import print_function import numpy as np -digital_channel_names=['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] +digital_channel_names = ['ID1', 'ID2', 'ID3', 'ID4', 'SEN', 'EXT', 'CNTR'] + class digital_channel: - EVERY_SIXTEENTH_RISING_EDGE = 5 - EVERY_FOURTH_RISING_EDGE = 4 - EVERY_RISING_EDGE = 3 - EVERY_FALLING_EDGE = 2 - EVERY_EDGE = 1 - DISABLED = 0 - def __init__(self,a): - self.gain=0 - self.channel_number=a - self.digital_channel_names=digital_channel_names - self.name=self.digital_channel_names[a] - self.xaxis=np.zeros(20000) - self.yaxis=np.zeros(20000) - self.timestamps=np.zeros(10000) - self.length=100 - self.initial_state=0 - self.prescaler = 0 - self.datatype='int' - self.trigger=0 - self.dlength=0 - self.plot_length = 0 - self.maximum_time =0 - self.maxT = 0 - self.initial_state_override = False - self.mode=self.EVERY_EDGE + EVERY_SIXTEENTH_RISING_EDGE = 5 + EVERY_FOURTH_RISING_EDGE = 4 + EVERY_RISING_EDGE = 3 + EVERY_FALLING_EDGE = 2 + EVERY_EDGE = 1 + DISABLED = 0 + + def __init__(self, a): + self.gain = 0 + self.channel_number = a + self.digital_channel_names = digital_channel_names + self.name = self.digital_channel_names[a] + self.xaxis = np.zeros(20000) + self.yaxis = np.zeros(20000) + self.timestamps = np.zeros(10000) + self.length = 100 + self.initial_state = 0 + self.prescaler = 0 + self.datatype = 'int' + self.trigger = 0 + self.dlength = 0 + self.plot_length = 0 + self.maximum_time = 0 + self.maxT = 0 + self.initial_state_override = False + self.mode = self.EVERY_EDGE - def set_params(self,**keys): - self.channel_number = keys.get('channel_number',self.channel_number) - self.name = keys.get('name','ErrOr') + def set_params(self, **keys): + self.channel_number = keys.get('channel_number', self.channel_number) + self.name = keys.get('name', 'ErrOr') - def load_data(self,initial_state,timestamps): - if self.initial_state_override: - self.initial_state = (self.initial_state_override-1)==1 - self.initial_state_override = False - else: self.initial_state = initial_state[self.name] - self.timestamps=timestamps - self.dlength = len(self.timestamps) - #print('dchan.py',self.channel_number,self.name,initial_state,self.initial_state) - self.timestamps = np.array(self.timestamps)*[1./64,1./8,1.,4.][self.prescaler] + def load_data(self, initial_state, timestamps): + if self.initial_state_override: + self.initial_state = (self.initial_state_override - 1) == 1 + self.initial_state_override = False + else: + self.initial_state = initial_state[self.name] + self.timestamps = timestamps + self.dlength = len(self.timestamps) + # print('dchan.py',self.channel_number,self.name,initial_state,self.initial_state) + self.timestamps = np.array(self.timestamps) * [1. / 64, 1. / 8, 1., 4.][self.prescaler] - if self.dlength:self.maxT=self.timestamps[-1] - else: self.maxT=0 + if self.dlength: + self.maxT = self.timestamps[-1] + else: + self.maxT = 0 - def generate_axes(self): - HIGH = 1#(4-self.channel_number)*(3) - LOW = 0#HIGH - 2.5 - state = HIGH if self.initial_state else LOW + def generate_axes(self): + HIGH = 1 # (4-self.channel_number)*(3) + LOW = 0 # HIGH - 2.5 + state = HIGH if self.initial_state else LOW + if self.mode == self.DISABLED: + self.xaxis[0] = 0 + self.yaxis[0] = state + n = 1 + self.plot_length = n - if self.mode==self.DISABLED: - self.xaxis[0]=0; self.yaxis[0]=state - n=1 - self.plot_length = n + elif self.mode == self.EVERY_EDGE: + self.xaxis[0] = 0 + self.yaxis[0] = state + n = 1 + for a in range(self.dlength): + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = state + state = LOW if state == HIGH else HIGH + n += 1 + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = state + n += 1 - elif self.mode==self.EVERY_EDGE: - self.xaxis[0]=0; self.yaxis[0]=state - n=1 - for a in range(self.dlength): - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = state - state = LOW if state==HIGH else HIGH - n+=1 - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = state - n+=1 + self.plot_length = n - self.plot_length = n + elif self.mode == self.EVERY_FALLING_EDGE: + self.xaxis[0] = 0 + self.yaxis[0] = HIGH + n = 1 + for a in range(self.dlength): + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = HIGH + n += 1 + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = LOW + n += 1 + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = HIGH + n += 1 + state = HIGH + self.plot_length = n - elif self.mode==self.EVERY_FALLING_EDGE: - self.xaxis[0]=0; self.yaxis[0]=HIGH - n=1 - for a in range(self.dlength): - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = HIGH - n+=1 - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = LOW - n+=1 - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = HIGH - n+=1 - state=HIGH - self.plot_length = n - - elif self.mode==self.EVERY_RISING_EDGE or self.mode==self.EVERY_FOURTH_RISING_EDGE or self.mode==self.EVERY_SIXTEENTH_RISING_EDGE: - self.xaxis[0]=0; self.yaxis[0]=LOW - n=1 - for a in range(self.dlength): - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = LOW - n+=1 - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = HIGH - n+=1 - self.xaxis[n] = self.timestamps[a] - self.yaxis[n] = LOW - n+=1 - state = LOW - self.plot_length = n - #print(self.channel_number,self.dlength,self.mode,len(self.yaxis),self.plot_length) + elif self.mode == self.EVERY_RISING_EDGE or self.mode == self.EVERY_FOURTH_RISING_EDGE or self.mode == self.EVERY_SIXTEENTH_RISING_EDGE: + self.xaxis[0] = 0 + self.yaxis[0] = LOW + n = 1 + for a in range(self.dlength): + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = LOW + n += 1 + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = HIGH + n += 1 + self.xaxis[n] = self.timestamps[a] + self.yaxis[n] = LOW + n += 1 + state = LOW + self.plot_length = n + # print(self.channel_number,self.dlength,self.mode,len(self.yaxis),self.plot_length) - def get_xaxis(self): - return self.xaxis[:self.plot_length] - def get_yaxis(self): - return self.yaxis[:self.plot_length] + def get_xaxis(self): + return self.xaxis[:self.plot_length] + def get_yaxis(self): + return self.yaxis[:self.plot_length] diff --git a/PSL/packet_handler.py b/PSL/packet_handler.py old mode 100644 new mode 100755 index 56fdb8f6..953d38a1 --- a/PSL/packet_handler.py +++ b/PSL/packet_handler.py @@ -1,231 +1,255 @@ from __future__ import print_function + import time -import PSL.commands_proto as CP -import serial, subprocess +import inspect +import serial +# Handle namespace conflict between packages 'pyserial' and 'serial'. +try: + serial.Serial +except AttributeError: + e = "import serial failed; PSL requires 'pyserial' but conflicting package 'serial' was found." + raise ImportError(e) + +import PSL.commands_proto as CP class Handler(): - def __init__(self,timeout=1.0,**kwargs): - self.burstBuffer=b'' - self.loadBurst=False - self.inputQueueSize=0 - self.BAUD = 1000000 - self.timeout=timeout - self.version_string=b'' - self.connected=False - self.fd = None - self.expected_version=b'CS' - self.occupiedPorts=set() - self.blockingSocket = None - if 'port' in kwargs: - self.portname=kwargs.get('port',None) - try: - self.fd,self.version_string,self.connected=self.connectToPort(self.portname) - if self.connected:return - except Exception as ex: - print('Failed to connect to ',self.portname,ex.message) - - else: #Scan and pick a port - L = self.listPorts() - for a in L: - try: - self.portname=a - self.fd,self.version_string,self.connected=self.connectToPort(self.portname) - if self.connected:return - print(a+' .yes.',version) - except : - pass - if not self.connected: - if len(self.occupiedPorts) : print('Device not found. Programs already using :',self.occupiedPorts) - - - def listPorts(self): - import platform,glob - system_name = platform.system() - if system_name == "Windows": - # Scan for available ports. - available = [] - for i in range(256): - try: - s = serial.Serial(i) - available.append(i) - s.close() - except serial.SerialException: - pass - return available - elif system_name == "Darwin": - # Mac - return glob.glob('/dev/tty*') + glob.glob('/dev/cu*') - else: - # Assume Linux or something else - return glob.glob('/dev/ttyACM*') + glob.glob('/dev/ttyUSB*') - - - def connectToPort(self,portname): - try: - import socket - self.blockingSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.blockingSocket.bind('\0PSghhhLab%s'%portname) - self.blockingSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - except socket.error as e: - self.occupiedPorts.add(portname) - raise RuntimeError("Another program is using %s (%d)" % (portname) ) - - fd = serial.Serial(portname, 9600, stopbits=1, timeout = 0.02) - fd.read(100);fd.close() - fd = serial.Serial(portname, self.BAUD, stopbits=1, timeout = 1.0) - if(fd.inWaiting()): - fd.setTimeout(0.1) - fd.read(1000) - fd.flush() - fd.setTimeout(1.0) - version= self.get_version(fd) - if version[:len(self.expected_version)]==self.expected_version: - return fd,version,True - - return None,'',False - - def disconnect(self): - if self.connected: - self.fd.close() - if self.blockingSocket: - print ('Releasing port') - self.blockingSocket.shutdown(1) - self.blockingSocket.close() - self.blockingSocket = None - - def get_version(self,fd): - fd.write(CP.COMMON) - fd.write(CP.GET_VERSION) - x=fd.readline() - #print('remaining',[ord(a) for a in fd.read(10)]) - if len(x): - x=x[:-1] - return x - - def reconnect(self,**kwargs): - if 'port' in kwargs: - self.portname=kwargs.get('port',None) - - try: - self.fd,self.version_string,self.connected=self.connectToPort(self.portname) - except serial.SerialException as ex: - msg = "failed to reconnect. Check device connections." - print(msg) - raise RuntimeError(msg) - - def __del__(self): - #print('closing port') - try:self.fd.close() - except: pass - - def __get_ack__(self): - """ - fetches the response byte - 1 SUCCESS - 2 ARGUMENT_ERROR - 3 FAILED - used as a handshake - """ - if not self.loadBurst: - x=self.fd.read(1) - else: - self.inputQueueSize+=1 - return 1 - try: - return CP.Byte.unpack(x)[0] - except: - return 3 - - def __sendInt__(self,val): - """ - transmits an integer packaged as two characters - :params int val: int to send - """ - if not self.loadBurst:self.fd.write(CP.ShortInt.pack(int(val))) - else: self.burstBuffer+=CP.ShortInt.pack(int(val)) - - def __sendByte__(self,val): - """ - transmits a BYTE - val - byte to send - """ - #print (val) - if(type(val)==int): - if not self.loadBurst:self.fd.write(CP.Byte.pack(val)) - else:self.burstBuffer+=CP.Byte.pack(val) - else: - if not self.loadBurst:self.fd.write(val) - else:self.burstBuffer+=val - - def __getByte__(self): - """ - reads a byte from the serial port and returns it - """ - ss=self.fd.read(1) - if len(ss): return CP.Byte.unpack(ss)[0] - else: - print('byte communication error.',time.ctime()) - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - #sys.exit(1) - - def __getInt__(self): - """ - reads two bytes from the serial port and - returns an integer after combining them - """ - ss = self.fd.read(2) - if len(ss)==2: return CP.ShortInt.unpack(ss)[0] - else: - print('int communication error.',time.ctime()) - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - #sys.exit(1) - - def __getLong__(self): - """ - reads four bytes. - returns long - """ - ss = self.fd.read(4) - if len(ss)==4: return CP.Integer.unpack(ss)[0] - else: - #print('.') - return -1 - - def waitForData(self,timeout=0.2): - start_time = time.time() - while time.time()-start_time>> I.loadBurst=True - >>> I.capture_traces(4,800,2) - >>> I.set_state(I.OD1,I.HIGH) - >>> I.sendBurst() - - - """ - #print([Byte.unpack(a)[0] for a in self.burstBuffer],self.inputQueueSize) - self.fd.write(self.burstBuffer) - self.burstBuffer='' - self.loadBurst=False - acks=self.fd.read(self.inputQueueSize) - self.inputQueueSize=0 - return [Byte.unpack(a)[0] for a in acks] - - + def __init__(self, timeout=1.0, **kwargs): + self.burstBuffer = b'' + self.loadBurst = False + self.inputQueueSize = 0 + self.BAUD = 1000000 + self.timeout = timeout + self.version_string = b'' + self.connected = False + self.fd = None + self.expected_version1 = b'CS' + self.expected_version2 = b'PS' + self.occupiedPorts = set() + self.blockingSocket = None + if 'port' in kwargs: + self.portname = kwargs.get('port', None) + try: + self.fd, self.version_string, self.connected = self.connectToPort(self.portname) + if self.connected: return + except Exception as ex: + print('Failed to connect to ', self.portname, ex.message) + + else: # Scan and pick a port + L = self.listPorts() + for a in L: + try: + self.portname = a + self.fd, self.version_string, self.connected = self.connectToPort(self.portname) + if self.connected: + print(a + ' .yes.', self.version_string) + return + except: + pass + if not self.connected: + if len(self.occupiedPorts): print('Device not found. Programs already using :', self.occupiedPorts) + + def listPorts(self): + import platform, glob + system_name = platform.system() + if system_name == "Windows": + # Scan for available ports. + available = [] + for i in range(256): + try: + portname = 'COM' + str(i) + s = serial.Serial(portname) + available.append(portname) + s.close() + except serial.SerialException: + pass + return available + elif system_name == "Darwin": + # Mac + return glob.glob('/dev/tty*') + glob.glob('/dev/cu*') + else: + # Assume Linux or something else + return glob.glob('/dev/ttyACM*') + glob.glob('/dev/ttyUSB*') + + def connectToPort(self, portname): + import platform + if platform.system() not in ["Windows", "Darwin"]: + import socket + try: + self.blockingSocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.blockingSocket.bind('\0PSLab%s' % portname) + self.blockingSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + except socket.error as e: + self.occupiedPorts.add(portname) + raise RuntimeError("Another program is using %s (%d)" % (portname)) + + fd = serial.Serial(portname, 9600, stopbits=1, timeout=0.02) + fd.read(100) + fd.close() + fd = serial.Serial(portname, self.BAUD, stopbits=1, timeout=1.0) + if (fd.inWaiting()): + fd.setTimeout(0.1) + fd.read(1000) + fd.flush() + fd.setTimeout(1.0) + version = self.get_version(fd) + if version[:len(self.expected_version1)] == self.expected_version1 or version[:len( + self.expected_version2)] == self.expected_version2: + return fd, str(version)[1:], True + + return None, '', False + + def disconnect(self): + if self.connected: + self.fd.close() + if self.blockingSocket: + print('Releasing port') + self.blockingSocket.shutdown(1) + self.blockingSocket.close() + self.blockingSocket = None + + def get_version(self, fd): + fd.write(CP.COMMON) + fd.write(CP.GET_VERSION) + x = fd.readline() + # print('remaining',[ord(a) for a in fd.read(10)]) + if len(x): + x = x[:-1] + return x + + def reconnect(self, **kwargs): + if 'port' in kwargs: + self.portname = kwargs.get('port', None) + + try: + self.fd, self.version_string, self.connected = self.connectToPort(self.portname) + except serial.SerialException as ex: + msg = "failed to reconnect. Check device connections." + print(msg) + raise RuntimeError(msg) + + def __del__(self): + # print('closing port') + try: + self.fd.close() + except: + pass + + def __get_ack__(self): + """ + fetches the response byte + 1 SUCCESS + 2 ARGUMENT_ERROR + 3 FAILED + used as a handshake + """ + if not self.loadBurst: + x = self.fd.read(1) + else: + self.inputQueueSize += 1 + return 1 + try: + return CP.Byte.unpack(x)[0] + except: + return 3 + + def __sendInt__(self, val): + """ + transmits an integer packaged as two characters + :params int val: int to send + """ + if not self.loadBurst: + self.fd.write(CP.ShortInt.pack(int(val))) + else: + self.burstBuffer += CP.ShortInt.pack(int(val)) + + def __sendByte__(self, val): + """ + transmits a BYTE + val - byte to send + """ + # print (val) + if (type(val) == int): + if not self.loadBurst: + self.fd.write(CP.Byte.pack(val)) + else: + self.burstBuffer += CP.Byte.pack(val) + else: + if not self.loadBurst: + self.fd.write(val) + else: + self.burstBuffer += val + + def __getByte__(self): + """ + reads a byte from the serial port and returns it + """ + ss = self.fd.read(1) + try: + if len(ss): + return CP.Byte.unpack(ss)[0] + except Exception as ex: + print('byte communication error.', time.ctime()) + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + # sys.exit(1) + + def __getInt__(self): + """ + reads two bytes from the serial port and + returns an integer after combining them + """ + ss = self.fd.read(2) + try: + if len(ss) == 2: + return CP.ShortInt.unpack(ss)[0] + except Exception as ex: + print('int communication error.', time.ctime()) + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + # sys.exit(1) + + def __getLong__(self): + """ + reads four bytes. + returns long + """ + ss = self.fd.read(4) + if len(ss) == 4: + return CP.Integer.unpack(ss)[0] + else: + # print('.') + return -1 + + def waitForData(self, timeout=0.2): + start_time = time.time() + while time.time() - start_time < timeout: + time.sleep(0.02) + if self.fd.inWaiting(): return True + return False + + def sendBurst(self): + """ + Transmits the commands stored in the burstBuffer. + empties input buffer + empties the burstBuffer. + + The following example initiates the capture routine and sets OD1 HIGH immediately. + + It is used by the Transient response experiment where the input needs to be toggled soon + after the oscilloscope has been started. + + >>> I.loadBurst=True + >>> I.capture_traces(4,800,2) + >>> I.set_state(I.OD1,I.HIGH) + >>> I.sendBurst() + + + """ + # print([Byte.unpack(a)[0] for a in self.burstBuffer],self.inputQueueSize) + self.fd.write(self.burstBuffer) + self.burstBuffer = '' + self.loadBurst = False + acks = self.fd.read(self.inputQueueSize) + self.inputQueueSize = 0 + return [CP.Byte.unpack(a)[0] for a in acks] diff --git a/PSL/sciencelab.py b/PSL/sciencelab.py index 1202eced..11e124c2 100644 --- a/PSL/sciencelab.py +++ b/PSL/sciencelab.py @@ -4,33 +4,30 @@ # License : GNU GPL from __future__ import print_function -import os,time + +import inspect +import time import PSL.commands_proto as CP import PSL.packet_handler as packet_handler - from PSL.achan import * from PSL.digital_channel import * -import serial,string,inspect -import time -import sys -import numpy as np -import math + def connect(**kwargs): ''' If hardware is found, returns an instance of 'ScienceLab', else returns None. ''' obj = ScienceLab(**kwargs) - if obj.H.fd != None: + if obj.H.fd is not None: return obj else: print('Err') raise RuntimeError('Could Not Connect') - + class ScienceLab(): - """ + """ **Communications library.** This class contains methods that can be used to interact with the FOSSASIA PSLab @@ -58,265 +55,281 @@ class ScienceLab(): into the device. - + """ - CAP_AND_PCS=0 - ADC_SHIFTS_LOCATION1=1 - ADC_SHIFTS_LOCATION2=2 - ADC_POLYNOMIALS_LOCATION=3 - - #DAC_POLYNOMIALS_LOCATION=1 - DAC_SHIFTS_PV1A=4 - DAC_SHIFTS_PV1B=5 - DAC_SHIFTS_PV2A=6 - DAC_SHIFTS_PV2B=7 - DAC_SHIFTS_PV3A=8 - DAC_SHIFTS_PV3B=9 - LOC_DICT = {'PV1':[4,5],'PV2':[6,7],'PV3':[8,9]} - BAUD = 1000000 - WType={'W1':'sine','W2':'sine'} - def __init__(self,timeout=1.0,**kwargs): - self.verbose=kwargs.get('verbose',False) - self.initialArgs = kwargs - self.generic_name = 'PSLab' - self.DDS_CLOCK = 0 - self.timebase = 40 - self.MAX_SAMPLES = CP.MAX_SAMPLES - self.samples=self.MAX_SAMPLES - self.triggerLevel=550 - self.triggerChannel = 0 - self.error_count=0 - self.channels_in_buffer=0 - self.digital_channels_in_buffer=0 - self.currents=[0.55e-3,0.55e-6,0.55e-5,0.55e-4] - self.currentScalers=[1.0,1.0,1.0,1.0] - - self.data_splitting = kwargs.get('data_splitting',CP.DATA_SPLITTING) - self.allAnalogChannels=allAnalogChannels - self.analogInputSources={} - for a in allAnalogChannels:self.analogInputSources[a]=analogInputSource(a) - - self.sine1freq = None;self.sine2freq = None - self.sqrfreq = {'SQR1':None,'SQR2':None,'SQR3':None,'SQR4':None} - self.aboutArray=[] - self.errmsg = '' - #--------------------------Initialize communication handler, and subclasses----------------- - try: - self.H = packet_handler.Handler(**kwargs) - except Exception as ex: - self.errmsg = "failed to Connect. Please check connections/arguments\n"+ex.message - self.connected = False - print(self.errmsg)#raise RuntimeError(msg) - - try: - self.__runInitSequence__(**kwargs) - except Exception as ex: - self.errmsg = "failed to run init sequence. Check device connections\n"+str(ex) - self.connected = False - print(self.errmsg)#raise RuntimeError(msg) - - def __runInitSequence__(self,**kwargs): - self.aboutArray=[] - from PSL.Peripherals import I2C,SPI,NRF24L01,MCP4728,RadioLink - self.connected = self.H.connected - if not self.H.connected: - self.__print__('Check hardware connections. Not connected') - - self.streaming=False - self.achans=[analogAcquisitionChannel(a) for a in ['CH1','CH2','CH3','MIC']] - self.gain_values=gains - self.buff=np.zeros(10000) - self.SOCKET_CAPACITANCE = 42e-12# 42e-12 is typical for the FOSSASIA PSLab. Actual values will be updated during calibration loading - self.resistanceScaling = 1. - - self.digital_channel_names=digital_channel_names - self.allDigitalChannels = self.digital_channel_names - self.gains = {'CH1':0,'CH2':0} - - #This array of four instances of digital_channel is used to store data retrieved from the - #logic analyzer section of the device. It also contains methods to generate plottable data - #from the original timestamp arrays. - self.dchans=[digital_channel(a) for a in range(4)] - - self.I2C = I2C(self.H) - #self.I2C.pullSCLLow(5000) - self.SPI = SPI(self.H) - self.hexid='' - if self.H.connected: - for a in ['CH1','CH2']: self.set_gain(a,0,True) #Force load gain - for a in ['W1','W2']:self.load_equation(a,'sine') - self.SPI.set_parameters(1,7,1,0) - self.hexid=hex(self.device_id()) - - self.NRF = NRF24L01(self.H) - - self.aboutArray.append(['Radio Transceiver is :','Installed' if self.NRF.ready else 'Not Installed']) - - self.DAC = MCP4728(self.H,3.3,0) - self.calibrated = False - #-------Check for calibration data if connected. And process them if found--------------- - if kwargs.get('load_calibration',True) and self.H.connected: - import struct - #Load constants for CTMU and PCS - cap_and_pcs=self.read_bulk_flash(self.CAP_AND_PCS,8*4+5) #READY+calibration_string - if cap_and_pcs[:5]=='READY': - scalers = list(struct.unpack('8f',cap_and_pcs[5:])) - self.SOCKET_CAPACITANCE = scalers[0] - self.DAC.CHANS['PCS'].load_calibration_twopoint(scalers[1],scalers[2]) #Slope and offset for current source - self.__calibrate_ctmu__(scalers[4:]) - self.resistanceScaling = scalers[3] #SEN - self.aboutArray.append(['Capacitance[sock,550uA,55uA,5.5uA,.55uA]']+scalers[:1]+scalers[4:]) - self.aboutArray.append(['PCS slope,offset']+scalers[1:3]) - self.aboutArray.append(['SEN']+[scalers[3]]) - else: - self.SOCKET_CAPACITANCE = 42e-12 #approx - self.__print__('Cap and PCS calibration invalid')#,cap_and_pcs[:10],'...') - - - #Load constants for ADC and DAC - polynomials = self.read_bulk_flash(self.ADC_POLYNOMIALS_LOCATION,2048) - polyDict={} - if polynomials[:9]=='PSLab': - self.__print__('ADC calibration found...') - self.aboutArray.append(['Calibration Found']) - self.aboutArray.append([]) - self.calibrated = True - adc_shifts = self.read_bulk_flash(self.ADC_SHIFTS_LOCATION1,2048)+self.read_bulk_flash(self.ADC_SHIFTS_LOCATION2,2048) - adc_shifts = [CP.Byte.unpack(a)[0] for a in adc_shifts] - #print(adc_shifts) - self.__print__('ADC INL correction table loaded.') - self.aboutArray.append(['ADC INL Correction found',adc_shifts[0],adc_shifts[1],adc_shifts[2],'...']) - poly_sections = polynomials.split('STOP') #The 2K array is split into sections containing data for ADC_INL fit, ADC_CHANNEL fit, DAC_CHANNEL fit, PCS, CAP ... - - adc_slopes_offsets = poly_sections[0] - dac_slope_intercept = poly_sections[1] - inl_slope_intercept = poly_sections[2] - #print('COMMON#########',self.__stoa__(slopes_offsets)) - #print('DAC#########',self.__stoa__(dac_slope_intercept)) - #print('ADC INL ############',self.__stoa__(inl_slope_intercept),len(inl_slope_intercept)) - #Load calibration data for ADC channels into an array that'll be evaluated in the next code block - for a in adc_slopes_offsets.split('>|')[1:]: - self.__print__( '\n','>'*20,a[:3],'<'*20) - self.aboutArray.append([]) - self.aboutArray.append(['ADC Channel',a[:3]]) - self.aboutArray.append(['Gain','X^3','X^2','X','C']) - cals=a[5:] - polyDict[a[:3]]=[] - for b in range(len(cals)//16): - try: - poly=struct.unpack('4f',cals[b*16:(b+1)*16]) - except: - self.__print__(a[:3], ' not calibrated') - self.__print__( b,poly) - self.aboutArray.append([b]+['%.3e'%v for v in poly]) - polyDict[a[:3]].append(poly) - - #Load calibration data (slopes and offsets) for ADC channels - inl_slope_intercept=struct.unpack('2f',inl_slope_intercept) - for a in self.analogInputSources: - self.analogInputSources[a].loadCalibrationTable(adc_shifts,inl_slope_intercept[0],inl_slope_intercept[1]) - if a in polyDict: - self.__print__ ('loading polynomials for ',a,polyDict[a]) - self.analogInputSources[a].loadPolynomials(polyDict[a]) - self.analogInputSources[a].calibrationReady=True - self.analogInputSources[a].regenerateCalibration() - - #Load calibration data for DAC channels - for a in dac_slope_intercept.split('>|')[1:]: - NAME = a[:3] #Name of the DAC channel . PV1, PV2 ... - self.aboutArray.append([]); self.aboutArray.append(['Calibrated :',NAME]) - try: - fits = struct.unpack('6f',a[5:]) - self.__print__(NAME, ' calibrated' , a[5:]) - except: - self.__print__(NAME, ' not calibrated' , a[5:], len(a[5:]),a) - continue - slope=fits[0];intercept=fits[1] - fitvals = fits[2:] - if NAME in ['PV1','PV2','PV3']: - ''' + CAP_AND_PCS = 0 + ADC_SHIFTS_LOCATION1 = 1 + ADC_SHIFTS_LOCATION2 = 2 + ADC_POLYNOMIALS_LOCATION = 3 + + # DAC_POLYNOMIALS_LOCATION=1 + DAC_SHIFTS_PV1A = 4 + DAC_SHIFTS_PV1B = 5 + DAC_SHIFTS_PV2A = 6 + DAC_SHIFTS_PV2B = 7 + DAC_SHIFTS_PV3A = 8 + DAC_SHIFTS_PV3B = 9 + LOC_DICT = {'PV1': [4, 5], 'PV2': [6, 7], 'PV3': [8, 9]} + BAUD = 1000000 + WType = {'W1': 'sine', 'W2': 'sine'} + + def __init__(self, timeout=1.0, **kwargs): + self.verbose = kwargs.get('verbose', False) + self.initialArgs = kwargs + self.generic_name = 'PSLab' + self.DDS_CLOCK = 0 + self.timebase = 40 + self.MAX_SAMPLES = CP.MAX_SAMPLES + self.samples = self.MAX_SAMPLES + self.triggerLevel = 550 + self.triggerChannel = 0 + self.error_count = 0 + self.channels_in_buffer = 0 + self.digital_channels_in_buffer = 0 + self.currents = [0.55e-3, 0.55e-6, 0.55e-5, 0.55e-4] + self.currentScalers = [1.0, 1.0, 1.0, 1.0] + + self.data_splitting = kwargs.get('data_splitting', CP.DATA_SPLITTING) + self.allAnalogChannels = allAnalogChannels + self.analogInputSources = {} + for a in allAnalogChannels: self.analogInputSources[a] = analogInputSource(a) + + self.sine1freq = None + self.sine2freq = None + self.sqrfreq = {'SQR1': None, 'SQR2': None, 'SQR3': None, 'SQR4': None} + self.aboutArray = [] + self.errmsg = '' + # --------------------------Initialize communication handler, and subclasses----------------- + try: + self.H = packet_handler.Handler(**kwargs) + except Exception as ex: + self.errmsg = "failed to Connect. Please check connections/arguments\n" + ex.message + self.connected = False + print(self.errmsg) # raise RuntimeError(msg) + + try: + self.__runInitSequence__(**kwargs) + except Exception as ex: + self.errmsg = "failed to run init sequence. Check device connections\n" + str(ex) + self.connected = False + print(self.errmsg) # raise RuntimeError(msg) + + def __runInitSequence__(self, **kwargs): + self.aboutArray = [] + from PSL.Peripherals import I2C, SPI, NRF24L01, MCP4728 + self.connected = self.H.connected + if not self.H.connected: + self.__print__('Check hardware connections. Not connected') + + self.streaming = False + self.achans = [analogAcquisitionChannel(a) for a in ['CH1', 'CH2', 'CH3', 'MIC']] + self.gain_values = gains + self.buff = np.zeros(10000) + self.SOCKET_CAPACITANCE = 42e-12 # 42e-12 is typical for the FOSSASIA PSLab. Actual values will be updated during calibration loading + self.resistanceScaling = 1. + + self.digital_channel_names = digital_channel_names + self.allDigitalChannels = self.digital_channel_names + self.gains = {'CH1': 0, 'CH2': 0} + + # This array of four instances of digital_channel is used to store data retrieved from the + # logic analyzer section of the device. It also contains methods to generate plottable data + # from the original timestamp arrays. + self.dchans = [digital_channel(a) for a in range(4)] + + self.I2C = I2C(self.H) + # self.I2C.pullSCLLow(5000) + self.SPI = SPI(self.H) + self.hexid = '' + if self.H.connected: + for a in ['CH1', 'CH2']: self.set_gain(a, 0, True) # Force load gain + for a in ['W1', 'W2']: self.load_equation(a, 'sine') + self.SPI.set_parameters(1, 7, 1, 0) + self.hexid = hex(self.device_id()) + + self.NRF = NRF24L01(self.H) + + self.aboutArray.append(['Radio Transceiver is :', 'Installed' if self.NRF.ready else 'Not Installed']) + + self.DAC = MCP4728(self.H, 3.3, 0) + self.calibrated = False + # -------Check for calibration data if connected. And process them if found--------------- + if kwargs.get('load_calibration', True) and self.H.connected: + import struct + # Load constants for CTMU and PCS + cap_and_pcs = self.read_bulk_flash(self.CAP_AND_PCS, 8 * 4 + 5) # READY+calibration_string + if cap_and_pcs[:5] == 'READY': + scalers = list(struct.unpack('8f', cap_and_pcs[5:])) + self.SOCKET_CAPACITANCE = scalers[0] + self.DAC.CHANS['PCS'].load_calibration_twopoint(scalers[1], + scalers[2]) # Slope and offset for current source + self.__calibrate_ctmu__(scalers[4:]) + self.resistanceScaling = scalers[3] # SEN + self.aboutArray.append(['Capacitance[sock,550uA,55uA,5.5uA,.55uA]'] + scalers[:1] + scalers[4:]) + self.aboutArray.append(['PCS slope,offset'] + scalers[1:3]) + self.aboutArray.append(['SEN'] + [scalers[3]]) + else: + self.SOCKET_CAPACITANCE = 42e-12 # approx + self.__print__('Cap and PCS calibration invalid') # ,cap_and_pcs[:10],'...') + + # Load constants for ADC and DAC + polynomials = self.read_bulk_flash(self.ADC_POLYNOMIALS_LOCATION, 2048) + polyDict = {} + if polynomials[:9] == 'PSLab': + self.__print__('ADC calibration found...') + self.aboutArray.append(['Calibration Found']) + self.aboutArray.append([]) + self.calibrated = True + adc_shifts = self.read_bulk_flash(self.ADC_SHIFTS_LOCATION1, 2048) + self.read_bulk_flash( + self.ADC_SHIFTS_LOCATION2, 2048) + adc_shifts = [CP.Byte.unpack(a)[0] for a in adc_shifts] + # print(adc_shifts) + self.__print__('ADC INL correction table loaded.') + self.aboutArray.append(['ADC INL Correction found', adc_shifts[0], adc_shifts[1], adc_shifts[2], '...']) + poly_sections = polynomials.split( + 'STOP') # The 2K array is split into sections containing data for ADC_INL fit, ADC_CHANNEL fit, DAC_CHANNEL fit, PCS, CAP ... + + adc_slopes_offsets = poly_sections[0] + dac_slope_intercept = poly_sections[1] + inl_slope_intercept = poly_sections[2] + # print('COMMON#########',self.__stoa__(slopes_offsets)) + # print('DAC#########',self.__stoa__(dac_slope_intercept)) + # print('ADC INL ############',self.__stoa__(inl_slope_intercept),len(inl_slope_intercept)) + # Load calibration data for ADC channels into an array that'll be evaluated in the next code block + for a in adc_slopes_offsets.split('>|')[1:]: + self.__print__('\n', '>' * 20, a[:3], '<' * 20) + self.aboutArray.append([]) + self.aboutArray.append(['ADC Channel', a[:3]]) + self.aboutArray.append(['Gain', 'X^3', 'X^2', 'X', 'C']) + cals = a[5:] + polyDict[a[:3]] = [] + for b in range(len(cals) // 16): + try: + poly = struct.unpack('4f', cals[b * 16:(b + 1) * 16]) + except: + self.__print__(a[:3], ' not calibrated') + self.__print__(b, poly) + self.aboutArray.append([b] + ['%.3e' % v for v in poly]) + polyDict[a[:3]].append(poly) + + # Load calibration data (slopes and offsets) for ADC channels + inl_slope_intercept = struct.unpack('2f', inl_slope_intercept) + for a in self.analogInputSources: + self.analogInputSources[a].loadCalibrationTable(adc_shifts, inl_slope_intercept[0], + inl_slope_intercept[1]) + if a in polyDict: + self.__print__('loading polynomials for ', a, polyDict[a]) + self.analogInputSources[a].loadPolynomials(polyDict[a]) + self.analogInputSources[a].calibrationReady = True + self.analogInputSources[a].regenerateCalibration() + + # Load calibration data for DAC channels + for a in dac_slope_intercept.split('>|')[1:]: + NAME = a[:3] # Name of the DAC channel . PV1, PV2 ... + self.aboutArray.append([]) + self.aboutArray.append(['Calibrated :', NAME]) + try: + fits = struct.unpack('6f', a[5:]) + self.__print__(NAME, ' calibrated', a[5:]) + except: + self.__print__(NAME, ' not calibrated', a[5:], len(a[5:]), a) + continue + slope = fits[0] + intercept = fits[1] + fitvals = fits[2:] + if NAME in ['PV1', 'PV2', 'PV3']: + ''' DACs have inherent non-linear behaviour, and the following algorithm generates a correction array from the calibration data that contains information about the offset(in codes) of each DAC code. - + The correction array defines for each DAC code, the number of codes to skip forwards or backwards in order to output the most accurate voltage value. - + E.g. if Code 1024 was found to output a voltage corresponding to code 1030 , and code 1020 was found to output a voltage corresponding to code 1024, then correction array[1024] = -4 , correction_array[1030]=-6. Adding -4 to the code 1024 will give code 1020 which will output the correct voltage value expected from code 1024. - - The variables LOOKAHEAD and LOOKBEHIND define the range of codes to search around a particular DAC code in order to + + The variables LOOKAHEAD and LOOKBEHIND define the range of codes to search around a particular DAC code in order to find the code with the minimum deviation from the expected value. - - ''' - DACX=np.linspace(self.DAC.CHANS[NAME].range[0],self.DAC.CHANS[NAME].range[1],4096) - if NAME=='PV1':OFF=self.read_bulk_flash(self.DAC_SHIFTS_PV1A,2048)+self.read_bulk_flash(self.DAC_SHIFTS_PV1B,2048) - elif NAME=='PV2':OFF=self.read_bulk_flash(self.DAC_SHIFTS_PV2A,2048)+self.read_bulk_flash(self.DAC_SHIFTS_PV2B,2048) - elif NAME=='PV3':OFF=self.read_bulk_flash(self.DAC_SHIFTS_PV3A,2048)+self.read_bulk_flash(self.DAC_SHIFTS_PV3B,2048) - OFF = np.array([ord(data) for data in OFF]) - self.__print__( '\n','>'*20,NAME,'<'*20) - self.__print__('Offsets :',OFF[:20],'...') - fitfn = np.poly1d(fitvals) - YDATA = fitfn(DACX) - (OFF*slope+intercept) - LOOKBEHIND = 100;LOOKAHEAD=100 - OFF=np.array([np.argmin(np.fabs(YDATA[max(B-LOOKBEHIND,0):min(4095,B+LOOKAHEAD)]-DACX[B]) )- (B-max(B-LOOKBEHIND,0)) for B in range(0,4096)]) - self.aboutArray.append(['Err min:',min(OFF),'Err max:',max(OFF)]) - self.DAC.CHANS[NAME].load_calibration_table(OFF) - - def get_resistance(self): - V = self.get_average_voltage('SEN') - if V>3.295:return np.Inf - I = (3.3-V)/5.1e3 - res = V/I - return res*self.resistanceScaling - - def __ignoreCalibration__(self): - print ('CALIBRATION DISABLED') - for a in self.analogInputSources: - self.analogInputSources[a].__ignoreCalibration__() - self.analogInputSources[a].regenerateCalibration() - - for a in ['PV1','PV2','PV3']: self.DAC.__ignoreCalibration__(a) - - def __print__(self,*args): - if self.verbose: - for a in args: - print(a, end="") - print() - - def __del__(self): - self.__print__('closing port') - try: - self.H.fd.close() - except: - pass - def get_version(self): - """ + ''' + DACX = np.linspace(self.DAC.CHANS[NAME].range[0], self.DAC.CHANS[NAME].range[1], 4096) + if NAME == 'PV1': + OFF = self.read_bulk_flash(self.DAC_SHIFTS_PV1A, 2048) + self.read_bulk_flash( + self.DAC_SHIFTS_PV1B, 2048) + elif NAME == 'PV2': + OFF = self.read_bulk_flash(self.DAC_SHIFTS_PV2A, 2048) + self.read_bulk_flash( + self.DAC_SHIFTS_PV2B, 2048) + elif NAME == 'PV3': + OFF = self.read_bulk_flash(self.DAC_SHIFTS_PV3A, 2048) + self.read_bulk_flash( + self.DAC_SHIFTS_PV3B, 2048) + OFF = np.array([ord(data) for data in OFF]) + self.__print__('\n', '>' * 20, NAME, '<' * 20) + self.__print__('Offsets :', OFF[:20], '...') + fitfn = np.poly1d(fitvals) + YDATA = fitfn(DACX) - (OFF * slope + intercept) + LOOKBEHIND = 100 + LOOKAHEAD = 100 + OFF = np.array([np.argmin( + np.fabs(YDATA[max(B - LOOKBEHIND, 0):min(4095, B + LOOKAHEAD)] - DACX[B])) - ( + B - max(B - LOOKBEHIND, 0)) for B in range(0, 4096)]) + self.aboutArray.append(['Err min:', min(OFF), 'Err max:', max(OFF)]) + self.DAC.CHANS[NAME].load_calibration_table(OFF) + + def get_resistance(self): + V = self.get_average_voltage('SEN') + if V > 3.295: return np.Inf + I = (3.3 - V) / 5.1e3 + res = V / I + return res * self.resistanceScaling + + def __ignoreCalibration__(self): + print('CALIBRATION DISABLED') + for a in self.analogInputSources: + self.analogInputSources[a].__ignoreCalibration__() + self.analogInputSources[a].regenerateCalibration() + + for a in ['PV1', 'PV2', 'PV3']: self.DAC.__ignoreCalibration__(a) + + def __print__(self, *args): + if self.verbose: + for a in args: + print(a, end="") + print() + + def __del__(self): + self.__print__('Closing PORT') + try: + self.H.fd.close() + except: + pass + + def get_version(self): + """ Returns the version string of the device format: LTS-...... """ - try: - return self.H.get_version(self.H.fd) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + return self.H.get_version(self.H.fd) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def getRadioLinks(self): - try: - return self.NRF.get_nodelist() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + def getRadioLinks(self): + try: + return self.NRF.get_nodelist() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def newRadioLink(self,**args): - ''' + def newRadioLink(self, **args): + ''' .. tabularcolumns:: |p{3cm}|p{11cm}| ============== ============================================================================== - **Arguments** Description + **Arguments** Description ============== ============================================================================== - \*\*Kwargs Keyword Arguments + \*\*Kwargs Keyword Arguments address Address of the node. a 24 bit number. Printed on the nodes.\n can also be retrieved using :py:meth:`~NRF24L01_class.NRF24L01.get_nodelist` ============== ============================================================================== @@ -324,42 +337,42 @@ def newRadioLink(self,**args): :return: :py:meth:`~NRF_NODE.RadioLink` - + ''' - from PSL.Peripherals import RadioLink - try: - return RadioLink(self.NRF,**args) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + from PSL.Peripherals import RadioLink + try: + return RadioLink(self.NRF, **args) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - #-------------------------------------------------------------------------------------------------------------------# + # -------------------------------------------------------------------------------------------------------------------# - #|================================================ANALOG SECTION====================================================| - #|This section has commands related to analog measurement and control. These include the oscilloscope routines, | - #|voltmeters, ammeters, and Programmable voltage sources. | - #-------------------------------------------------------------------------------------------------------------------# + # |================================================ANALOG SECTION====================================================| + # |This section has commands related to analog measurement and control. These include the oscilloscope routines, | + # |voltmeters, ammeters, and Programmable voltage sources. | + # -------------------------------------------------------------------------------------------------------------------# - def reconnect(self,**kwargs): - ''' + def reconnect(self, **kwargs): + ''' Attempts to reconnect to the device in case of a commmunication error or accidental disconnect. ''' - try: - self.H.reconnect(**kwargs) - self.__runInitSequence__(**kwargs) - except Exception as ex: - self.errmsg = str(ex) - self.H.disconnect() - print(self.errmsg) - raise RuntimeError(self.errmsg) - - def capture1(self,ch,ns,tg,*args,**kwargs): - """ + try: + self.H.reconnect(**kwargs) + self.__runInitSequence__(**kwargs) + except Exception as ex: + self.errmsg = str(ex) + self.H.disconnect() + print(self.errmsg) + raise RuntimeError(self.errmsg) + + def capture1(self, ch, ns, tg, *args, **kwargs): + """ Blocking call that fetches an oscilloscope trace from the specified input channel - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ ch Channel to select as input. ['CH1'..'CH3','SEN'] ns Number of samples to fetch. Maximum 10000 @@ -371,32 +384,32 @@ def capture1(self,ch,ns,tg,*args,**kwargs): :align: center :alt: alternate text :figclass: align-center - + A sine wave captured and plotted. - + Example - - >>> from pylab import * + + >>> from PSL import * >>> from PSL import sciencelab >>> I=sciencelab.connect() >>> x,y = I.capture1('CH1',3200,1) >>> plot(x,y) >>> show() - - + + :return: Arrays X(timestamps),Y(Corresponding Voltage values) - - """ - return self.capture_fullspeed(ch,ns,tg,*args,**kwargs) - def capture2(self,ns,tg,TraceOneRemap='CH1'): """ + return self.capture_fullspeed(ch, ns, tg, *args, **kwargs) + + def capture2(self, ns, tg, TraceOneRemap='CH1'): + """ Blocking call that fetches oscilloscope traces from CH1,CH2 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ======================================================================================================= - **Arguments** + **Arguments** ============== ======================================================================================================= ns Number of samples to fetch. Maximum 5000 tg Timegap between samples in microseconds @@ -408,48 +421,48 @@ def capture2(self,ns,tg,TraceOneRemap='CH1'): :align: center :alt: alternate text :figclass: align-center - + Two sine waves captured and plotted. - Example + Example - >>> from pylab import * + >>> from PSL import * >>> from PSL import sciencelab >>> I=sciencelab.connect() >>> x,y1,y2 = I.capture2(1600,2,'MIC') #Chan1 remapped to MIC. Chan2 reads CH2 >>> plot(x,y1) #Plot of analog input MIC >>> plot(x,y2) #plot of analog input CH2 - >>> show() - + >>> show() + :return: Arrays X(timestamps),Y1(Voltage at CH1),Y2(Voltage at CH2) - + """ - try: - self.capture_traces(2,ns,tg,TraceOneRemap) - time.sleep(1e-6*self.samples*self.timebase+.01) - while not self.oscilloscope_progress()[0]: - pass - - self.__fetch_channel__(1) - self.__fetch_channel__(2) + try: + self.capture_traces(2, ns, tg, TraceOneRemap) + time.sleep(1e-6 * self.samples * self.timebase + .01) + while not self.oscilloscope_progress()[0]: + pass - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + self.__fetch_channel__(1) + self.__fetch_channel__(2) - x=self.achans[0].get_xaxis() - y=self.achans[0].get_yaxis() - y2=self.achans[1].get_yaxis() - #x,y2=self.fetch_trace(2) - return x,y,y2 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def capture4(self,ns,tg,TraceOneRemap='CH1'): - """ + x = self.achans[0].get_xaxis() + y = self.achans[0].get_yaxis() + y2 = self.achans[1].get_yaxis() + # x,y2=self.fetch_trace(2) + return x, y, y2 + + def capture4(self, ns, tg, TraceOneRemap='CH1'): + """ Blocking call that fetches oscilloscope traces from CH1,CH2,CH3,CH4 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ====================================================================================================== - **Arguments** + **Arguments** ============== ====================================================================================================== ns Number of samples to fetch. Maximum 2500 tg Timegap between samples in microseconds. Minimum 1.75uS @@ -461,45 +474,45 @@ def capture4(self,ns,tg,TraceOneRemap='CH1'): :align: center :alt: alternate text :figclass: align-center - + Four traces captured and plotted. Example - >>> from pylab import * + >>> from PSL import * >>> I=sciencelab.ScienceLab() >>> x,y1,y2,y3,y4 = I.capture4(800,1.75) - >>> plot(x,y1) - >>> plot(x,y2) - >>> plot(x,y3) - >>> plot(x,y4) - >>> show() - + >>> plot(x,y1) + >>> plot(x,y2) + >>> plot(x,y3) + >>> plot(x,y4) + >>> show() + :return: Arrays X(timestamps),Y1(Voltage at CH1),Y2(Voltage at CH2),Y3(Voltage at CH3),Y4(Voltage at CH4) - - """ - try: - self.capture_traces(4,ns,tg,TraceOneRemap) - time.sleep(1e-6*self.samples*self.timebase+.01) - while not self.oscilloscope_progress()[0]: - pass - x,y=self.fetch_trace(1) - x,y2=self.fetch_trace(2) - x,y3=self.fetch_trace(3) - x,y4=self.fetch_trace(4) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - return x,y,y2,y3,y4 - def capture_multiple(self,samples,tg,*args): """ + try: + self.capture_traces(4, ns, tg, TraceOneRemap) + time.sleep(1e-6 * self.samples * self.timebase + .01) + while not self.oscilloscope_progress()[0]: + pass + x, y = self.fetch_trace(1) + x, y2 = self.fetch_trace(2) + x, y3 = self.fetch_trace(3) + x, y4 = self.fetch_trace(4) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return x, y, y2, y3, y4 + + def capture_multiple(self, samples, tg, *args): + """ Blocking call that fetches oscilloscope traces from a set of specified channels - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ samples Number of samples to fetch. Maximum 10000/(total specified channels) tg Timegap between samples in microseconds. @@ -508,252 +521,256 @@ def capture_multiple(self,samples,tg,*args): Example - >>> from pylab import * + >>> from PSL import * >>> I=sciencelab.ScienceLab() >>> x,y1,y2,y3,y4 = I.capture_multiple(800,1.75,'CH1','CH2','MIC','SEN') - >>> plot(x,y1) - >>> plot(x,y2) - >>> plot(x,y3) - >>> plot(x,y4) - >>> show() - - :return: Arrays X(timestamps),Y1,Y2 ... - - """ - if len(args)==0: - self.__print__('please specify channels to record') - return - tg = int(tg*8)/8. # Round off the timescale to 1/8uS units - if(tg<1.5):tg=int(1.5*8)/8. - total_chans = len(args) - - total_samples = samples*total_chans - if(total_samples>self.MAX_SAMPLES): - self.__print__('Sample limit exceeded. 10,000 total') - total_samples = self.MAX_SAMPLES - samples = self.MAX_SAMPLES/total_chans - - CHANNEL_SELECTION=0 - for chan in args: - C=self.analogInputSources[chan].CHOSA - self.__print__( chan,C) - CHANNEL_SELECTION|=(1<self.MAX_SAMPLES): - self.__print__('Sample limit exceeded. 10,000 max') - samples = self.MAX_SAMPLES - - self.timebase = int(tg*8)/8. - self.samples = samples - CHOSA=self.analogInputSources[chan].CHOSA + >>> plot(x,y1) + >>> plot(x,y2) + >>> plot(x,y3) + >>> plot(x,y4) + >>> show() - try: - self.H.__sendByte__(CP.ADC) - if 'SET_LOW' in args: - self.H.__sendByte__(CP.SET_LO_CAPTURE) - elif 'SET_HIGH' in args: - self.H.__sendByte__(CP.SET_HI_CAPTURE) - elif 'FIRE_PULSES' in args: - self.H.__sendByte__(CP.PULSE_TRAIN) - self.__print__('firing sqr1 pulses for ',kwargs.get('interval',1000) , 'uS') - else: - self.H.__sendByte__(CP.CAPTURE_DMASPEED) - self.H.__sendByte__(CHOSA) - self.H.__sendInt__(samples) #total number of samples to record - self.H.__sendInt__(int(tg*8)) #Timegap between samples. 8MHz timer clock - if 'FIRE_PULSES' in args: - t = kwargs.get('interval',1000) - print ('Firing for',t,'uS') - self.H.__sendInt__(t) - time.sleep(t*1e-6) #Wait for hardware to free up from firing pulses(blocking call). Background capture starts immediately after this - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + :return: Arrays X(timestamps),Y1,Y2 ... - def capture_fullspeed(self,chan,samples,tg,*args,**kwargs): """ + if len(args) == 0: + self.__print__('please specify channels to record') + return + tg = int(tg * 8) / 8. # Round off the timescale to 1/8uS units + if (tg < 1.5): tg = int(1.5 * 8) / 8. + total_chans = len(args) + + total_samples = samples * total_chans + if (total_samples > self.MAX_SAMPLES): + self.__print__('Sample limit exceeded. 10,000 total') + total_samples = self.MAX_SAMPLES + samples = self.MAX_SAMPLES / total_chans + + CHANNEL_SELECTION = 0 + for chan in args: + C = self.analogInputSources[chan].CHOSA + self.__print__(chan, C) + CHANNEL_SELECTION |= (1 << C) + self.__print__('selection', CHANNEL_SELECTION, len(args), hex(CHANNEL_SELECTION | ((total_chans - 1) << 12))) + + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.CAPTURE_MULTIPLE) + self.H.__sendInt__(CHANNEL_SELECTION | ((total_chans - 1) << 12)) + + self.H.__sendInt__(total_samples) # total number of samples to record + self.H.__sendInt__(int(self.timebase * 8)) # Timegap between samples. 8MHz timer clock + self.H.__get_ack__() + self.__print__('wait') + time.sleep(1e-6 * total_samples * tg + .01) + self.__print__('done') + data = b'' + for i in range(int(total_samples / self.data_splitting)): + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) # channel number . starts with A0 on PIC + self.H.__sendInt__(self.data_splitting) + self.H.__sendInt__(i * self.data_splitting) + data += self.H.fd.read(int( + self.data_splitting * 2)) # reading int by int sometimes causes a communication error. this works better. + self.H.__get_ack__() + + if total_samples % self.data_splitting: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) # channel number starts with A0 on PIC + self.H.__sendInt__(total_samples % self.data_splitting) + self.H.__sendInt__(total_samples - total_samples % self.data_splitting) + data += self.H.fd.read(int(2 * ( + total_samples % self.data_splitting))) # reading int by int may cause packets to be dropped. this works better. + self.H.__get_ack__() + + for a in range(int(total_samples)): self.buff[a] = CP.ShortInt.unpack(data[a * 2:a * 2 + 2])[0] + # self.achans[channel_number-1].yaxis = self.achans[channel_number-1].fix_value(self.buff[:samples]) + yield np.linspace(0, tg * (samples - 1), samples) + for a in range(int(total_chans)): + yield self.buff[a:total_samples][::total_chans] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __capture_fullspeed__(self, chan, samples, tg, *args, **kwargs): + tg = int(tg * 8) / 8. # Round off the timescale to 1/8uS units + if (tg < 0.5): tg = int(0.5 * 8) / 8. + if (samples > self.MAX_SAMPLES): + self.__print__('Sample limit exceeded. 10,000 max') + samples = self.MAX_SAMPLES + + self.timebase = int(tg * 8) / 8. + self.samples = samples + CHOSA = self.analogInputSources[chan].CHOSA + + try: + self.H.__sendByte__(CP.ADC) + if 'SET_LOW' in args: + self.H.__sendByte__(CP.SET_LO_CAPTURE) + elif 'SET_HIGH' in args: + self.H.__sendByte__(CP.SET_HI_CAPTURE) + elif 'FIRE_PULSES' in args: + self.H.__sendByte__(CP.PULSE_TRAIN) + self.__print__('firing sqr1 pulses for ', kwargs.get('interval', 1000), 'uS') + else: + self.H.__sendByte__(CP.CAPTURE_DMASPEED) + self.H.__sendByte__(CHOSA) + self.H.__sendInt__(samples) # total number of samples to record + self.H.__sendInt__(int(tg * 8)) # Timegap between samples. 8MHz timer clock + if 'FIRE_PULSES' in args: + t = kwargs.get('interval', 1000) + print('Firing for', t, 'uS') + self.H.__sendInt__(t) + time.sleep( + t * 1e-6) # Wait for hardware to free up from firing pulses(blocking call). Background capture starts immediately after this + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def capture_fullspeed(self, chan, samples, tg, *args, **kwargs): + """ Blocking call that fetches oscilloscope traces from a single oscilloscope channel at a maximum speed of 2MSPS - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ chan channel name 'CH1' / 'CH2' ... 'SEN' samples Number of samples to fetch. Maximum 10000/(total specified channels) tg Timegap between samples in microseconds. minimum 0.5uS \*args specify if SQR1 must be toggled right before capturing. 'SET_LOW' will set SQR1 to 0V - 'SET_HIGH' will set it to 5V. - 'FIRE_PULSES' will output a preset frequency on SQR1 for a given interval (keyword arg 'interval' + 'SET_HIGH' will set it to 5V. + 'FIRE_PULSES' will output a preset frequency on SQR1 for a given interval (keyword arg 'interval' must be specified or it will default to 1000uS) before acquiring data. This is used for measuring speed of sound using piezos if no arguments are specified, a regular capture will be executed. - \*\*kwargs + \*\*kwargs interval units:uS . Necessary if 'FIRE_PULSES' argument was supplied. default 1000uS ============== ============================================================================================ .. code-block:: python - from pylab import * + from PSL import * I=sciencelab.ScienceLab() x,y = I.capture_fullspeed('CH1',2000,1) - plot(x,y) + plot(x,y) show() - + .. code-block:: python x,y = I.capture_fullspeed('CH1',2000,1,'SET_LOW') - plot(x,y) + plot(x,y) show() .. code-block:: python I.sqr1(40e3 , 50, True ) # Prepare a 40KHz, 50% square wave. Do not output it yet x,y = I.capture_fullspeed('CH1',2000,1,'FIRE_PULSES',interval = 250) #Output the prepared 40KHz(25uS) wave for 250uS(10 cycles) before acquisition - plot(x,y) + plot(x,y) show() :return: timestamp array ,voltage_value array """ - self.__capture_fullspeed__(chan,samples,tg,*args,**kwargs) - time.sleep(1e-6*self.samples*self.timebase+kwargs.get('interval',0)*1e-6+0.1) - x,y = self.__retrieveBufferData__(chan,self.samples,self.timebase) - - return x,self.analogInputSources[chan].calPoly10(y) - - def __capture_fullspeed_hr__(self,chan,samples,tg,*args): - tg = int(tg*8)/8. # Round off the timescale to 1/8uS units - if(tg<1):tg=1. - if(samples>self.MAX_SAMPLES): - self.__print__('Sample limit exceeded. 10,000 max') - samples = self.MAX_SAMPLES - - self.timebase = int(tg*8)/8. - self.samples = samples - CHOSA=self.analogInputSources[chan].CHOSA - try: - self.H.__sendByte__(CP.ADC) - if 'SET_LOW' in args: - self.H.__sendByte__(CP.SET_LO_CAPTURE) - elif 'SET_HIGH' in args: - self.H.__sendByte__(CP.SET_HI_CAPTURE) - elif 'READ_CAP' in args: - self.H.__sendByte__(CP.MULTIPOINT_CAPACITANCE) - else: - self.H.__sendByte__(CP.CAPTURE_DMASPEED) - self.H.__sendByte__(CHOSA|0x80) - self.H.__sendInt__(samples) #total number of samples to record - self.H.__sendInt__(int(tg*8)) #Timegap between samples. 8MHz timer clock - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def capture_fullspeed_hr(self,chan,samples,tg,*args): - try: - self.__capture_fullspeed_hr__(chan,samples,tg,*args) - time.sleep(1e-6*self.samples*self.timebase+.01) - x,y = self.__retrieveBufferData__(chan,self.samples,self.timebase) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - return x,self.analogInputSources[chan].calPoly12(y) - + self.__capture_fullspeed__(chan, samples, tg, *args, **kwargs) + time.sleep(1e-6 * self.samples * self.timebase + kwargs.get('interval', 0) * 1e-6 + 0.1) + x, y = self.__retrieveBufferData__(chan, self.samples, self.timebase) + + return x, self.analogInputSources[chan].calPoly10(y) + + def __capture_fullspeed_hr__(self, chan, samples, tg, *args): + tg = int(tg * 8) / 8. # Round off the timescale to 1/8uS units + if (tg < 1): tg = 1. + if (samples > self.MAX_SAMPLES): + self.__print__('Sample limit exceeded. 10,000 max') + samples = self.MAX_SAMPLES + + self.timebase = int(tg * 8) / 8. + self.samples = samples + CHOSA = self.analogInputSources[chan].CHOSA + try: + self.H.__sendByte__(CP.ADC) + if 'SET_LOW' in args: + self.H.__sendByte__(CP.SET_LO_CAPTURE) + elif 'SET_HIGH' in args: + self.H.__sendByte__(CP.SET_HI_CAPTURE) + elif 'READ_CAP' in args: + self.H.__sendByte__(CP.MULTIPOINT_CAPACITANCE) + else: + self.H.__sendByte__(CP.CAPTURE_DMASPEED) + self.H.__sendByte__(CHOSA | 0x80) + self.H.__sendInt__(samples) # total number of samples to record + self.H.__sendInt__(int(tg * 8)) # Timegap between samples. 8MHz timer clock + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def capture_fullspeed_hr(self, chan, samples, tg, *args): + try: + self.__capture_fullspeed_hr__(chan, samples, tg, *args) + time.sleep(1e-6 * self.samples * self.timebase + .01) + x, y = self.__retrieveBufferData__(chan, self.samples, self.timebase) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return x, self.analogInputSources[chan].calPoly12(y) + + def __retrieveBufferData__(self, chan, samples, tg): + ''' - def __retrieveBufferData__(self,chan,samples,tg): ''' - - ''' - data=b'' - try: - for i in range(int(samples/self.data_splitting)): - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) - self.H.__sendByte__(0) #channel number . starts with A0 on PIC - self.H.__sendInt__(self.data_splitting) - self.H.__sendInt__(i*self.data_splitting) - data+= self.H.fd.read(int(self.data_splitting*2)) #reading int by int sometimes causes a communication error. this works better. - self.H.__get_ack__() - - if samples%self.data_splitting: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) - self.H.__sendByte__(0) #channel number starts with A0 on PIC - self.H.__sendInt__(samples%self.data_splitting) - self.H.__sendInt__(samples-samples%self.data_splitting) - data += self.H.fd.read(int(2*(samples%self.data_splitting))) #reading int by int may cause packets to be dropped. this works better. - self.H.__get_ack__() - - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - try: - for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a*2:a*2+2])[0] - except Exception as ex: - msg = "Incorrect Number of Bytes Received\n" - raise RuntimeError(msg) - - #self.achans[channel_number-1].yaxis = self.achans[channel_number-1].fix_value(self.buff[:samples]) - return np.linspace(0,tg*(samples-1),samples),self.buff[:samples] - - def capture_traces(self,num,samples,tg,channel_one_input='CH1',CH123SA=0,**kwargs): - """ + data = b'' + try: + for i in range(int(samples / self.data_splitting)): + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) # channel number . starts with A0 on PIC + self.H.__sendInt__(self.data_splitting) + self.H.__sendInt__(i * self.data_splitting) + data += self.H.fd.read(int( + self.data_splitting * 2)) # reading int by int sometimes causes a communication error. this works better. + self.H.__get_ack__() + + if samples % self.data_splitting: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(0) # channel number starts with A0 on PIC + self.H.__sendInt__(samples % self.data_splitting) + self.H.__sendInt__(samples - samples % self.data_splitting) + data += self.H.fd.read(int(2 * ( + samples % self.data_splitting))) # reading int by int may cause packets to be dropped. this works better. + self.H.__get_ack__() + + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + try: + for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a * 2:a * 2 + 2])[0] + except Exception as ex: + msg = "Incorrect Number of Bytes Received\n" + raise RuntimeError(msg) + + # self.achans[channel_number-1].yaxis = self.achans[channel_number-1].fix_value(self.buff[:samples]) + return np.linspace(0, tg * (samples - 1), samples), self.buff[:samples] + + def capture_traces(self, num, samples, tg, channel_one_input='CH1', CH123SA=0, **kwargs): + """ Instruct the ADC to start sampling. use fetch_trace to retrieve the data .. tabularcolumns:: |p{3cm}|p{11cm}| - + =================== ============================================================================================ - **Arguments** + **Arguments** =================== ============================================================================================ num Channels to acquire. 1/2/4 samples Total points to store per channel. Maximum 3200 total. tg Timegap between two successive samples (in uSec) channel_one_input map channel 1 to 'CH1' ... 'CH9' - \*\*kwargs - + \*\*kwargs + \*trigger Whether or not to trigger the oscilloscope based on the voltage level set by :func:`configure_trigger` =================== ============================================================================================ @@ -767,7 +784,7 @@ def capture_traces(self,num,samples,tg,channel_one_input='CH1',CH123SA=0,**kwarg :align: center :alt: alternate text :figclass: align-center - + Transient response of an Inductor and Capacitor in series The following example demonstrates how to use this function to record active events. @@ -782,7 +799,7 @@ def capture_traces(self,num,samples,tg,channel_one_input='CH1',CH123SA=0,**kwarg >>> I.set_state(OD1=1) #Turn on OD1 #Arbitrary delay to wait for stabilization - >>> time.sleep(0.5) + >>> time.sleep(0.5) #Start acquiring data (2 channels,800 samples, 2microsecond intervals) >>> I.capture_traces(2,800,2,trigger=False) #Turn off OD1. This must occur immediately after the previous line was executed. @@ -792,9 +809,9 @@ def capture_traces(self,num,samples,tg,channel_one_input='CH1',CH123SA=0,**kwarg >>> time.sleep(800*2*1e-6) >>> x,CH1=I.fetch_trace(1) >>> x,CH2=I.fetch_trace(2) - >>> plot(x,CH1-CH2) #Voltage across the inductor - >>> plot(x,CH2) ##Voltage across the capacitor - >>> show() + >>> plot(x,CH1-CH2) #Voltage across the inductor + >>> plot(x,CH2) ##Voltage across the capacitor + >>> show() The following events take place when the above snippet runs @@ -803,116 +820,119 @@ def capture_traces(self,num,samples,tg,channel_one_input='CH1',CH123SA=0,**kwarg (It may or may not oscillate) #. The data from CH1 and CH2 was read into x,CH1,CH2 #. Both traces were plotted in order to visualize the Transient response of series LC - - :return: nothing - - .. seealso:: - :func:`fetch_trace` , :func:`oscilloscope_progress` , :func:`capture1` , :func:`capture2` , :func:`capture4` - """ - triggerornot=0x80 if kwargs.get('trigger',True) else 0 - self.timebase=tg - self.timebase = int(self.timebase*8)/8. # Round off the timescale to 1/8uS units - if channel_one_input not in self.analogInputSources:raise RuntimeError('Invalid input %s, not in %s'%(channel_one_input,str(self.analogInputSources.keys() ))) - CHOSA = self.analogInputSources[channel_one_input].CHOSA - try: - self.H.__sendByte__(CP.ADC) - if(num==1): - if(self.timebase<1.5):self.timebase=int(1.5*8)/8. - if(samples>self.MAX_SAMPLES):samples=self.MAX_SAMPLES - - self.achans[0].set_params(channel=channel_one_input,length=samples,timebase=self.timebase,resolution=10,source=self.analogInputSources[channel_one_input]) - self.H.__sendByte__(CP.CAPTURE_ONE) #read 1 channel - self.H.__sendByte__(CHOSA|triggerornot) #channelk number - - elif(num==2): - if(self.timebase<1.75):self.timebase=int(1.75*8)/8. - if(samples>self.MAX_SAMPLES/2):samples=self.MAX_SAMPLES/2 - - self.achans[0].set_params(channel=channel_one_input,length=samples,timebase=self.timebase,resolution=10,source=self.analogInputSources[channel_one_input]) - self.achans[1].set_params(channel='CH2',length=samples,timebase=self.timebase,resolution=10,source=self.analogInputSources['CH2']) - - self.H.__sendByte__(CP.CAPTURE_TWO) #capture 2 channels - self.H.__sendByte__(CHOSA|triggerornot) #channel 0 number - - elif(num==3 or num==4): - if(self.timebase<1.75):self.timebase=int(1.75*8)/8. - if(samples>self.MAX_SAMPLES/4):samples=self.MAX_SAMPLES/4 - - self.achans[0].set_params(channel=channel_one_input,length=samples,timebase=self.timebase,\ - resolution=10,source=self.analogInputSources[channel_one_input]) - - for a in range(1,4): - chans=['NONE','CH2','CH3','MIC'] - self.achans[a].set_params(channel=chans[a],length=samples,timebase=self.timebase,\ - resolution=10,source=self.analogInputSources[chans[a]]) - - self.H.__sendByte__(CP.CAPTURE_FOUR) #read 4 channels - self.H.__sendByte__(CHOSA|(CH123SA<<4)|triggerornot) #channel number - - - self.samples=samples - self.H.__sendInt__(samples) #number of samples per channel to record - self.H.__sendInt__(int(self.timebase*8)) #Timegap between samples. 8MHz timer clock - self.H.__get_ack__() - self.channels_in_buffer=num - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + :return: nothing + .. seealso:: + :func:`fetch_trace` , :func:`oscilloscope_progress` , :func:`capture1` , :func:`capture2` , :func:`capture4` - def capture_highres_traces(self,channel,samples,tg,**kwargs): """ + triggerornot = 0x80 if kwargs.get('trigger', True) else 0 + self.timebase = tg + self.timebase = int(self.timebase * 8) / 8. # Round off the timescale to 1/8uS units + if channel_one_input not in self.analogInputSources: raise RuntimeError( + 'Invalid input %s, not in %s' % (channel_one_input, str(self.analogInputSources.keys()))) + CHOSA = self.analogInputSources[channel_one_input].CHOSA + try: + self.H.__sendByte__(CP.ADC) + if (num == 1): + if (self.timebase < 1.5): self.timebase = int(1.5 * 8) / 8. + if (samples > self.MAX_SAMPLES): samples = self.MAX_SAMPLES + + self.achans[0].set_params(channel=channel_one_input, length=samples, timebase=self.timebase, + resolution=10, source=self.analogInputSources[channel_one_input]) + self.H.__sendByte__(CP.CAPTURE_ONE) # read 1 channel + self.H.__sendByte__(CHOSA | triggerornot) # channelk number + + elif (num == 2): + if (self.timebase < 1.75): self.timebase = int(1.75 * 8) / 8. + if (samples > self.MAX_SAMPLES / 2): samples = self.MAX_SAMPLES / 2 + + self.achans[0].set_params(channel=channel_one_input, length=samples, timebase=self.timebase, + resolution=10, source=self.analogInputSources[channel_one_input]) + self.achans[1].set_params(channel='CH2', length=samples, timebase=self.timebase, resolution=10, + source=self.analogInputSources['CH2']) + + self.H.__sendByte__(CP.CAPTURE_TWO) # capture 2 channels + self.H.__sendByte__(CHOSA | triggerornot) # channel 0 number + + elif (num == 3 or num == 4): + if (self.timebase < 1.75): self.timebase = int(1.75 * 8) / 8. + if (samples > self.MAX_SAMPLES / 4): samples = self.MAX_SAMPLES / 4 + + self.achans[0].set_params(channel=channel_one_input, length=samples, timebase=self.timebase, \ + resolution=10, source=self.analogInputSources[channel_one_input]) + + for a in range(1, 4): + chans = ['NONE', 'CH2', 'CH3', 'MIC'] + self.achans[a].set_params(channel=chans[a], length=samples, timebase=self.timebase, \ + resolution=10, source=self.analogInputSources[chans[a]]) + + self.H.__sendByte__(CP.CAPTURE_FOUR) # read 4 channels + self.H.__sendByte__(CHOSA | (CH123SA << 4) | triggerornot) # channel number + + self.samples = samples + self.H.__sendInt__(samples) # number of samples per channel to record + self.H.__sendInt__(int(self.timebase * 8)) # Timegap between samples. 8MHz timer clock + self.H.__get_ack__() + self.channels_in_buffer = num + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def capture_highres_traces(self, channel, samples, tg, **kwargs): + """ Instruct the ADC to start sampling. use fetch_trace to retrieve the data .. tabularcolumns:: |p{3cm}|p{11cm}| - + =================== ============================================================================================ - **Arguments** + **Arguments** =================== ============================================================================================ channel channel to acquire data from 'CH1' ... 'CH9' samples Total points to store per channel. Maximum 3200 total. tg Timegap between two successive samples (in uSec) - \*\*kwargs - + \*\*kwargs + \*trigger Whether or not to trigger the oscilloscope based on the voltage level set by :func:`configure_trigger` =================== ============================================================================================ - + :return: nothing - + .. seealso:: - - :func:`fetch_trace` , :func:`oscilloscope_progress` , :func:`capture1` , :func:`capture2` , :func:`capture4` - """ - triggerornot=0x80 if kwargs.get('trigger',True) else 0 - self.timebase=tg - try: - self.H.__sendByte__(CP.ADC) - CHOSA = self.analogInputSources[channel].CHOSA - if(self.timebase<3):self.timebase=3 - if(samples>self.MAX_SAMPLES):samples=self.MAX_SAMPLES - self.achans[0].set_params(channel=channel,length=samples,timebase=self.timebase,resolution=12,source=self.analogInputSources[channel]) - - self.H.__sendByte__(CP.CAPTURE_12BIT) #read 1 channel - self.H.__sendByte__(CHOSA|triggerornot) #channelk number - - self.samples=samples - self.H.__sendInt__(samples) #number of samples to read - self.H.__sendInt__(int(self.timebase*8)) #Timegap between samples. 8MHz timer clock - self.H.__get_ack__() - self.channels_in_buffer=1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + :func:`fetch_trace` , :func:`oscilloscope_progress` , :func:`capture1` , :func:`capture2` , :func:`capture4` - def fetch_trace(self,channel_number): """ + triggerornot = 0x80 if kwargs.get('trigger', True) else 0 + self.timebase = tg + try: + self.H.__sendByte__(CP.ADC) + CHOSA = self.analogInputSources[channel].CHOSA + if (self.timebase < 3): self.timebase = 3 + if (samples > self.MAX_SAMPLES): samples = self.MAX_SAMPLES + self.achans[0].set_params(channel=channel, length=samples, timebase=self.timebase, resolution=12, + source=self.analogInputSources[channel]) + + self.H.__sendByte__(CP.CAPTURE_12BIT) # read 1 channel + self.H.__sendByte__(CHOSA | triggerornot) # channelk number + + self.samples = samples + self.H.__sendInt__(samples) # number of samples to read + self.H.__sendInt__(int(self.timebase * 8)) # Timegap between samples. 8MHz timer clock + self.H.__get_ack__() + self.channels_in_buffer = 1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def fetch_trace(self, channel_number): + """ fetches a channel(1-4) captured by :func:`capture_traces` called prior to this, and returns xaxis,yaxis .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel_number Any of the maximum of four channels that the oscilloscope captured. 1/2/3/4 ============== ============================================================================================ @@ -920,17 +940,17 @@ def fetch_trace(self,channel_number): :return: time array,voltage array .. seealso:: - + :func:`capture_traces` , :func:`oscilloscope_progress` """ - self.__fetch_channel__(channel_number) - return self.achans[channel_number-1].get_xaxis(),self.achans[channel_number-1].get_yaxis() - - def oscilloscope_progress(self): - """ + self.__fetch_channel__(channel_number) + return self.achans[channel_number - 1].get_xaxis(), self.achans[channel_number - 1].get_yaxis() + + def oscilloscope_progress(self): + """ returns the number of samples acquired by the capture routines, and the conversion_done status - + :return: conversion done(bool) ,samples acquired (number) >>> I.start_capture(1,3200,2) @@ -939,118 +959,121 @@ def oscilloscope_progress(self): >>> time.sleep(3200*2e-6) >>> self.__print__(I.oscilloscope_progress()) (1,3200) - + .. seealso:: - - :func:`fetch_trace` , :func:`capture_traces` - """ - conversion_done=0 - samples=0 - try: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_STATUS) - conversion_done = self.H.__getByte__() - samples = self.H.__getInt__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return conversion_done,samples + :func:`fetch_trace` , :func:`capture_traces` - def __fetch_channel__(self,channel_number): """ + conversion_done = 0 + samples = 0 + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_STATUS) + conversion_done = self.H.__getByte__() + samples = self.H.__getInt__() + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + return conversion_done, samples + + def __fetch_channel__(self, channel_number): + """ Fetches a section of data from any channel and stores it in the relevant instance of achan() - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel_number channel number (1,2,3,4) ============== ============================================================================================ - - :return: True if successful - """ - samples = self.achans[channel_number-1].length - if(channel_number>self.channels_in_buffer): - self.__print__('Channel unavailable') - return False - data=b'' - try: - for i in range(int(samples/self.data_splitting)): - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) - self.H.__sendByte__(channel_number-1) #starts with A0 on PIC - self.H.__sendInt__(self.data_splitting) - self.H.__sendInt__(i*self.data_splitting) - data+= self.H.fd.read(int(self.data_splitting*2)) #reading int by int sometimes causes a communication error. - self.H.__get_ack__() - - if samples%self.data_splitting: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) - self.H.__sendByte__(channel_number-1) #starts with A0 on PIC - self.H.__sendInt__(samples%self.data_splitting) - self.H.__sendInt__(samples-samples%self.data_splitting) - data += self.H.fd.read(int(2*(samples%self.data_splitting))) #reading int by int may cause packets to be dropped. - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - try: - for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a*2:a*2+2])[0] - self.achans[channel_number-1].yaxis = self.achans[channel_number-1].fix_value(self.buff[:samples]) - except Exception as ex: - msg = "Incorrect Number of bytes received.\n" - raise RuntimeError(msg) - return True - - def __fetch_channel_oneshot__(self,channel_number): + :return: True if successful """ + samples = self.achans[channel_number - 1].length + if (channel_number > self.channels_in_buffer): + self.__print__('Channel unavailable') + return False + data = b'' + try: + for i in range(int(samples / self.data_splitting)): + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(channel_number - 1) # starts with A0 on PIC + self.H.__sendInt__(self.data_splitting) + self.H.__sendInt__(i * self.data_splitting) + data += self.H.fd.read( + int(self.data_splitting * 2)) # reading int by int sometimes causes a communication error. + self.H.__get_ack__() + + if samples % self.data_splitting: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(channel_number - 1) # starts with A0 on PIC + self.H.__sendInt__(samples % self.data_splitting) + self.H.__sendInt__(samples - samples % self.data_splitting) + data += self.H.fd.read( + int(2 * (samples % self.data_splitting))) # reading int by int may cause packets to be dropped. + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + try: + for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a * 2:a * 2 + 2])[0] + self.achans[channel_number - 1].yaxis = self.achans[channel_number - 1].fix_value(self.buff[:int(samples)]) + except Exception as ex: + msg = "Incorrect Number of bytes received.\n" + raise RuntimeError(msg) + + return True + + def __fetch_channel_oneshot__(self, channel_number): + """ Fetches all data from given channel and stores it in the relevant instance of achan() - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel_number channel number (1,2,3,4) ============== ============================================================================================ - - """ - offset=0 - samples = self.achans[channel_number-1].length - if(channel_number>self.channels_in_buffer): - self.__print__('Channel unavailable') - return False - try: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) - self.H.__sendByte__(channel_number-1) #starts with A0 on PIC - self.H.__sendInt__(samples) - self.H.__sendInt__(offset) - data = self.H.fd.read(int(samples*2)) #reading int by int sometimes causes a communication error. this works better. - self.H.__get_ack__() - for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a*2:a*2+2])[0] - self.achans[channel_number-1].yaxis = self.achans[channel_number-1].fix_value(self.buff[:samples]) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return True - - def configure_trigger(self,chan,name,voltage,resolution=10,**kwargs): """ + offset = 0 + samples = self.achans[channel_number - 1].length + if (channel_number > self.channels_in_buffer): + self.__print__('Channel unavailable') + return False + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_CAPTURE_CHANNEL) + self.H.__sendByte__(channel_number - 1) # starts with A0 on PIC + self.H.__sendInt__(samples) + self.H.__sendInt__(offset) + data = self.H.fd.read( + int(samples * 2)) # reading int by int sometimes causes a communication error. this works better. + self.H.__get_ack__() + for a in range(int(samples)): self.buff[a] = CP.ShortInt.unpack(data[a * 2:a * 2 + 2])[0] + self.achans[channel_number - 1].yaxis = self.achans[channel_number - 1].fix_value(self.buff[:samples]) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return True + + def configure_trigger(self, chan, name, voltage, resolution=10, **kwargs): + """ configure trigger parameters for 10-bit capture commands The capture routines will wait till a rising edge of the input signal crosses the specified level. The trigger will timeout within 8mS, and capture routines will start regardless. - + These settings will not be used if the trigger option in the capture routines are set to False - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ===================================================================================================================== - **Arguments** + **Arguments** ============== ===================================================================================================================== chan channel . 0, 1,2,3. corresponding to the channels being recorded by the capture routine(not the analog inputs) name the name of the channel. 'CH1'... 'V+' @@ -1058,167 +1081,168 @@ def configure_trigger(self,chan,name,voltage,resolution=10,**kwargs): ============== ===================================================================================================================== **Example** - + >>> I.configure_trigger(0,'CH1',1.1) >>> I.capture_traces(4,800,2) #Unless a timeout occured, the first point of this channel will be close to 1.1Volts >>> I.fetch_trace(1) - #This channel was acquired simultaneously with channel 1, + #This channel was acquired simultaneously with channel 1, #so it's triggered along with the first >>> I.fetch_trace(2) - - .. seealso:: - - :func:`capture_traces` , adc_example_ - - """ - prescaler = kwargs.get('prescaler',0) - try: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.CONFIGURE_TRIGGER) - self.H.__sendByte__((prescaler<<4)|(1<(2**resolution - 1):level=(2**resolution - 1) - elif level<0:level=0 + .. seealso:: - self.H.__sendInt__(int(level)) #Trigger - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + :func:`capture_traces` , adc_example_ - def set_gain(self,channel,gain,Force=False): """ + prescaler = kwargs.get('prescaler', 0) + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.CONFIGURE_TRIGGER) + self.H.__sendByte__( + (prescaler << 4) | (1 << chan)) # Trigger channel (4lsb) , trigger timeout prescaler (4msb) + + if resolution == 12: + level = self.analogInputSources[name].voltToCode12(voltage) + level = np.clip(level, 0, 4095) + else: + level = self.analogInputSources[name].voltToCode10(voltage) + level = np.clip(level, 0, 1023) + + if level > (2 ** resolution - 1): + level = (2 ** resolution - 1) + elif level < 0: + level = 0 + + self.H.__sendInt__(int(level)) # Trigger + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def set_gain(self, channel, gain, Force=False): + """ set the gain of the selected PGA - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel 'CH1','CH2' gain (0-8) -> (1x,2x,4x,5x,8x,10x,16x,32x,1/11x) Force If True, the amplifier gain will be set even if it was previously set to the same value. ============== ============================================================================================ - + .. note:: The gain value applied to a channel will result in better resolution for small amplitude signals. - - However, values read using functions like :func:`get_average_voltage` or :func:`capture_traces` + + However, values read using functions like :func:`get_average_voltage` or :func:`capture_traces` will not be 2x, or 4x times the input signal. These are calibrated to return accurate values of the original input signal. - + in case the gain specified is 8 (1/11x) , an external 10MOhm resistor must be connected in series with the device. The input range will be +/-160 Volts - + >>> I.set_gain('CH1',7) #gain set to 32x on CH1 """ - if gain<0 or gain>8: - print('Invalid gain parameter. 0-7 only.') - return - if self.analogInputSources[channel].gainPGA==None: - self.__print__('No amplifier exists on this channel :',channel) - return False - - refresh = False - if self.gains[channel] != gain: - self.gains[channel] = gain - time.sleep(0.01) - refresh = True - if refresh or Force: - try: - self.analogInputSources[channel].setGain(self.gain_values[gain]) - if gain>7: gain = 0 # external attenuator mode. set gain 1x - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.SET_PGA_GAIN) - self.H.__sendByte__(self.analogInputSources[channel].gainPGA) #send the channel. SPI, not multiplexer - self.H.__sendByte__(gain) #send the gain - self.H.__get_ack__() - return self.gain_values[gain] - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - return refresh - - def select_range(self,channel,voltage_range): - """ + if gain < 0 or gain > 8: + print('Invalid gain parameter. 0-7 only.') + return + if self.analogInputSources[channel].gainPGA == None: + self.__print__('No amplifier exists on this channel :', channel) + return False + + refresh = False + if self.gains[channel] != gain: + self.gains[channel] = gain + time.sleep(0.01) + refresh = True + if refresh or Force: + try: + self.analogInputSources[channel].setGain(self.gain_values[gain]) + if gain > 7: gain = 0 # external attenuator mode. set gain 1x + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.SET_PGA_GAIN) + self.H.__sendByte__(self.analogInputSources[channel].gainPGA) # send the channel. SPI, not multiplexer + self.H.__sendByte__(gain) # send the gain + self.H.__get_ack__() + return self.gain_values[gain] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return refresh + + def select_range(self, channel, voltage_range): + """ set the gain of the selected PGA - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel 'CH1','CH2' voltage_range choose from [16,8,4,3,2,1.5,1,.5,160] ============== ============================================================================================ - + .. note:: - Setting the right voltage range will result in better resolution. - in case the range specified is 160 , an external 10MOhm resistor must be connected in series with the device. - + Setting the right voltage range will result in better resolution. + in case the range specified is 160 , an external 10MOhm resistor must be connected in series with the device. + Note : this function internally calls set_gain with the appropriate gain value - + >>> I.select_range('CH1',8) #gain set to 2x on CH1. Voltage range +/-8V """ - ranges = [16,8,4,3,2,1.5,1,.5,160] - if voltage_range in ranges: - g = ranges.index(voltage_range) - return self.set_gain( channel, g) - else: - print ('not a valid range. try : ',ranges) - return None - - - - def __calcCHOSA__(self,name): - name=name.upper() - source = self.analogInputSources[name] - - if name not in self.allAnalogChannels: - self.__print__('not a valid channel name. selecting CH1') - return self.__calcCHOSA__('CH1') - - return source.CHOSA - - def get_voltage(self,channel_name,**kwargs): - self.voltmeter_autorange(channel_name) - return self.get_average_voltage(channel_name,**kwargs) - - def voltmeter_autorange(self,channel_name): - if self.analogInputSources[channel_name].gainPGA==None:return None - self.set_gain(channel_name,0) - V = self.get_average_voltage(channel_name) - return self.__autoSelectRange__(channel_name,V) - - def __autoSelectRange__(self,channel_name,V): - keys = [8,4,3,2,1.5,1,.5,0] - cutoffs = {8:0,4:1,3:2,2:3,1.5:4,1.:5,.5:6,0:7} - for a in keys: - if abs(V)>a: - g=cutoffs[a] - break - self.set_gain(channel_name,g) - return g - - def __autoRangeScope__(self,tg): - x,y1,y2 = self.capture2(1000,tg) - self.__autoSelectRange__('CH1',max(abs(y1))) - self.__autoSelectRange__('CH2',max(abs(y2))) - - def get_average_voltage(self,channel_name,**kwargs): - """ + ranges = [16, 8, 4, 3, 2, 1.5, 1, .5, 160] + if voltage_range in ranges: + g = ranges.index(voltage_range) + return self.set_gain(channel, g) + else: + print('not a valid range. try : ', ranges) + return None + + def __calcCHOSA__(self, name): + name = name.upper() + source = self.analogInputSources[name] + + if name not in self.allAnalogChannels: + self.__print__('not a valid channel name. selecting CH1') + return self.__calcCHOSA__('CH1') + + return source.CHOSA + + def get_voltage(self, channel_name, **kwargs): + self.voltmeter_autorange(channel_name) + return self.get_average_voltage(channel_name, **kwargs) + + def voltmeter_autorange(self, channel_name): + if self.analogInputSources[channel_name].gainPGA == None: return None + self.set_gain(channel_name, 0) + V = self.get_average_voltage(channel_name) + return self.__autoSelectRange__(channel_name, V) + + def __autoSelectRange__(self, channel_name, V): + keys = [8, 4, 3, 2, 1.5, 1, .5, 0] + cutoffs = {8: 0, 4: 1, 3: 2, 2: 3, 1.5: 4, 1.: 5, .5: 6, 0: 7} + for a in keys: + if abs(V) > a: + g = cutoffs[a] + break + self.set_gain(channel_name, g) + return g + + def __autoRangeScope__(self, tg): + x, y1, y2 = self.capture2(1000, tg) + self.__autoSelectRange__('CH1', max(abs(y1))) + self.__autoSelectRange__('CH2', max(abs(y2))) + + def get_average_voltage(self, channel_name, **kwargs): + """ Return the voltage on the selected channel - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + +------------+-----------------------------------------------------------------------------------------+ |Arguments |Description | +============+=========================================================================================+ @@ -1233,172 +1257,171 @@ def get_average_voltage(self,channel_name,**kwargs): see :ref:`stream_video` Example: - + >>> self.__print__(I.get_average_voltage('CH4')) 1.002 - + """ - try: - poly = self.analogInputSources[channel_name].calPoly12 - except Exception as ex: - msg = "Invalid Channel"+str(ex) - raise RuntimeError(msg) - vals = [self.__get_raw_average_voltage__(channel_name,**kwargs) for a in range(int(kwargs.get('samples',1)))] - #if vals[0]>2052:print (vals) - val = np.average([poly(a) for a in vals]) - return val - - def __get_raw_average_voltage__(self,channel_name,**kwargs): - """ + try: + poly = self.analogInputSources[channel_name].calPoly12 + except Exception as ex: + msg = "Invalid Channel" + str(ex) + raise RuntimeError(msg) + vals = [self.__get_raw_average_voltage__(channel_name, **kwargs) for a in range(int(kwargs.get('samples', 1)))] + # if vals[0]>2052:print (vals) + val = np.average([poly(a) for a in vals]) + return val + + def __get_raw_average_voltage__(self, channel_name, **kwargs): + """ Return the average of 16 raw 12-bit ADC values of the voltage on the selected channel - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================================ channel_name 'CH1', 'CH2', 'CH3', 'MIC', '5V', 'IN1','SEN' sleep read voltage in CPU sleep mode ============== ============================================================================================================ """ - try: - chosa = self.__calcCHOSA__(channel_name) - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.GET_VOLTAGE_SUMMED) - self.H.__sendByte__(chosa) - V_sum = self.H.__getInt__() - self.H.__get_ack__() - return V_sum/16. #sum(V)/16.0 # - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + chosa = self.__calcCHOSA__(channel_name) + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.GET_VOLTAGE_SUMMED) + self.H.__sendByte__(chosa) + V_sum = self.H.__getInt__() + self.H.__get_ack__() + return V_sum / 16. # sum(V)/16.0 # + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def fetch_buffer(self,starting_position=0,total_points=100): - """ + def fetch_buffer(self, starting_position=0, total_points=100): + """ fetches a section of the ADC hardware buffer """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.RETRIEVE_BUFFER) - self.H.__sendInt__(starting_position) - self.H.__sendInt__(total_points) - for a in range(int(total_points)): self.buff[a]=self.H.__getInt__() - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - def clear_buffer(self,starting_position,total_points): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.RETRIEVE_BUFFER) + self.H.__sendInt__(starting_position) + self.H.__sendInt__(total_points) + for a in range(int(total_points)): self.buff[a] = self.H.__getInt__() + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def clear_buffer(self, starting_position, total_points): + """ clears a section of the ADC hardware buffer """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.CLEAR_BUFFER) - self.H.__sendInt__(starting_position) - self.H.__sendInt__(total_points) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def fill_buffer(self,starting_position,point_array): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.CLEAR_BUFFER) + self.H.__sendInt__(starting_position) + self.H.__sendInt__(total_points) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def fill_buffer(self, starting_position, point_array): + """ fill a section of the ADC hardware buffer with data """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.FILL_BUFFER) - self.H.__sendInt__(starting_position) - self.H.__sendInt__(len(point_array)) - for a in point_array: - self.H.__sendInt__(int(a)) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def start_streaming(self,tg,channel='CH1'): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.FILL_BUFFER) + self.H.__sendInt__(starting_position) + self.H.__sendInt__(len(point_array)) + for a in point_array: + self.H.__sendInt__(int(a)) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start_streaming(self, tg, channel='CH1'): + """ Instruct the ADC to start streaming 8-bit data. use stop_streaming to stop. .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ tg timegap. 250KHz clock channel channel 'CH1'... 'CH9','IN1','SEN' ============== ============================================================================================ """ - if(self.streaming):self.stop_streaming() - try: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.START_ADC_STREAMING) - self.H.__sendByte__(self.__calcCHOSA__(channel)) - self.H.__sendInt__(tg) #Timegap between samples. 8MHz timer clock - self.streaming=True - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + if (self.streaming): self.stop_streaming() + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.START_ADC_STREAMING) + self.H.__sendByte__(self.__calcCHOSA__(channel)) + self.H.__sendInt__(tg) # Timegap between samples. 8MHz timer clock + self.streaming = True + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def stop_streaming(self): - """ + def stop_streaming(self): + """ Instruct the ADC to stop streaming data """ - if(self.streaming): - self.H.__sendByte__(CP.STOP_STREAMING) - self.H.fd.read(20000) - self.H.fd.flush() - else: - self.__print__('not streaming') - self.streaming=False + if (self.streaming): + self.H.__sendByte__(CP.STOP_STREAMING) + self.H.fd.read(20000) + self.H.fd.flush() + else: + self.__print__('not streaming') + self.streaming = False - #-------------------------------------------------------------------------------------------------------------------# + # -------------------------------------------------------------------------------------------------------------------# - #|===============================================DIGITAL SECTION====================================================| - #|This section has commands related to digital measurement and control. These include the Logic Analyzer, frequency | - #|measurement calls, timing routines, digital outputs etc | - #-------------------------------------------------------------------------------------------------------------------# - - def __calcDChan__(self,name): - """ + # |===============================================DIGITAL SECTION====================================================| + # |This section has commands related to digital measurement and control. These include the Logic Analyzer, frequency | + # |measurement calls, timing routines, digital outputs etc | + # -------------------------------------------------------------------------------------------------------------------# + + def __calcDChan__(self, name): + """ accepts a string represention of a digital input ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] and returns a corresponding number """ - - if name in self.digital_channel_names: - return self.digital_channel_names.index(name) - else: - self.__print__(' invalid channel',name,' , selecting ID1 instead ') - return 0 - - def __get_high_freq__backup__(self,pin): - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_HIGH_FREQUENCY) - self.H.__sendByte__(self.__calcDChan__(pin)) - scale=self.H.__getByte__() - val = self.H.__getLong__() - self.H.__get_ack__() - return scale*(val)/1.0e-1 #100mS sampling - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def get_high_freq(self,pin): - """ + if name in self.digital_channel_names: + return self.digital_channel_names.index(name) + else: + self.__print__(' invalid channel', name, ' , selecting ID1 instead ') + return 0 + + def __get_high_freq__backup__(self, pin): + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_HIGH_FREQUENCY) + self.H.__sendByte__(self.__calcDChan__(pin)) + scale = self.H.__getByte__() + val = self.H.__getLong__() + self.H.__get_ack__() + return scale * (val) / 1.0e-1 # 100mS sampling + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_high_freq(self, pin): + """ retrieves the frequency of the signal connected to ID1. for frequencies > 1MHz also good for lower frequencies, but avoid using it since the oscilloscope cannot be used simultaneously due to hardware limitations. - + The input frequency is fed to a 32 bit counter for a period of 100mS. The value of the counter at the end of 100mS is used to calculate the frequency. - + see :ref:`freq_video` .. seealso:: :func:`get_freq` - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ **Arguments** ============== ============================================================================================ @@ -1407,28 +1430,28 @@ def get_high_freq(self,pin): :return: frequency """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_ALTERNATE_HIGH_FREQUENCY) - self.H.__sendByte__(self.__calcDChan__(pin)) - scale=self.H.__getByte__() - val = self.H.__getLong__() - self.H.__get_ack__() - #self.__print__(hex(val)) - return scale*(val)/1.0e-1 #100mS sampling - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def get_freq(self,channel='CNTR',timeout=2): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_ALTERNATE_HIGH_FREQUENCY) + self.H.__sendByte__(self.__calcDChan__(pin)) + scale = self.H.__getByte__() + val = self.H.__getLong__() + self.H.__get_ack__() + # self.__print__(hex(val)) + return scale * (val) / 1.0e-1 # 100mS sampling + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_freq(self, channel='CNTR', timeout=2): + """ Frequency measurement on IDx. Measures time taken for 16 rising edges of input signal. returns the frequency in Hertz .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel The input to measure frequency from. ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] timeout This is a blocking call which will wait for one full wavelength before returning the @@ -1438,12 +1461,12 @@ def get_freq(self,channel='CNTR',timeout=2): ============== ============================================================================================ :return float: frequency - - + + .. _timing_example: - + * connect SQR1 to ID1 - + >>> I.sqr1(4000,25) >>> self.__print__(I.get_freq('ID1')) 4000.0 @@ -1458,38 +1481,38 @@ def get_freq(self,channel='CNTR',timeout=2): 6.25e-05 >>> I.duty_cycle('ID1') #returns wavelength, high time - (0.00025,6.25e-05) - + (0.00025,6.25e-05) + """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_FREQUENCY) - timeout_msb = int((timeout*64e6))>>16 - self.H.__sendInt__(timeout_msb) - self.H.__sendByte__(self.__calcDChan__(channel)) + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_FREQUENCY) + timeout_msb = int((timeout * 64e6)) >> 16 + self.H.__sendInt__(timeout_msb) + self.H.__sendByte__(self.__calcDChan__(channel)) - self.H.waitForData(timeout) + self.H.waitForData(timeout) - tmt = self.H.__getByte__() - x=[self.H.__getLong__() for a in range(2)] - self.H.__get_ack__() - freq = lambda t: 16*64e6/t if(t) else 0 - #self.__print__(x,tmt,timeout_msb) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - if(tmt):return 0 - return freq(x[1]-x[0]) + tmt = self.H.__getByte__() + x = [self.H.__getLong__() for a in range(2)] + self.H.__get_ack__() + freq = lambda t: 16 * 64e6 / t if (t) else 0 + # self.__print__(x,tmt,timeout_msb) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + if (tmt): return 0 + return freq(x[1] - x[0]) - ''' + ''' def r2r_time(self,channel='ID1',timeout=0.1): - """ + """ Returns the time interval between two rising edges of input signal on ID1 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ================================================================================================ - **Arguments** + **Arguments** ============== ================================================================================================ channel The input to measure time between two rising edges.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] timeout Use the timeout option if you're unsure of the input signal time period. @@ -1497,7 +1520,7 @@ def r2r_time(self,channel='ID1',timeout=0.1): ============== ================================================================================================ :return float: time between two rising edges of input signal - + .. seealso:: timing_example_ """ @@ -1516,14 +1539,14 @@ def r2r_time(self,channel='ID1',timeout=0.1): return rtime(y) ''' - def r2r_time(self,channel,skip_cycle=0,timeout=5): - """ + def r2r_time(self, channel, skip_cycle=0, timeout=5): + """ Return a list of rising edges that occured within the timeout period. .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================================== - **Arguments** + **Arguments** ============== ============================================================================================================== channel The input to measure time between two rising edges.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] skip_cycle Number of points to skip. eg. Pendulums pass through light barriers twice every cycle. SO 1 must be skipped @@ -1531,34 +1554,34 @@ def r2r_time(self,channel,skip_cycle=0,timeout=5): ============== ============================================================================================================== :return list: Array of points - - """ - try: - if timeout>60:timeout=60 - self.start_one_channel_LA(channel=channel,channel_mode=3,trigger_mode=0) #every rising edge - startTime=time.time() - while time.time() - startTime =skip_cycle+2: - tmp = self.fetch_long_data_from_LA(a,1) - self.dchans[0].load_data(e,tmp) - #print (self.dchans[0].timestamps) - return [1e-6*(self.dchans[0].timestamps[skip_cycle+1]-self.dchans[0].timestamps[0])] - time.sleep(0.1) - return [] - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def f2f_time(self,channel,skip_cycle=0,timeout=5): - """ + """ + try: + if timeout > 60: timeout = 60 + self.start_one_channel_LA(channel=channel, channel_mode=3, trigger_mode=0) # every rising edge + startTime = time.time() + while time.time() - startTime < timeout: + a, b, c, d, e = self.get_LA_initial_states() + if a == self.MAX_SAMPLES / 4: + a = 0 + if a >= skip_cycle + 2: + tmp = self.fetch_long_data_from_LA(a, 1) + self.dchans[0].load_data(e, tmp) + # print (self.dchans[0].timestamps) + return [1e-6 * (self.dchans[0].timestamps[skip_cycle + 1] - self.dchans[0].timestamps[0])] + time.sleep(0.1) + return [] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def f2f_time(self, channel, skip_cycle=0, timeout=5): + """ Return a list of falling edges that occured within the timeout period. .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================================== - **Arguments** + **Arguments** ============== ============================================================================================================== channel The input to measure time between two falling edges.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] skip_cycle Number of points to skip. eg. Pendulums pass through light barriers twice every cycle. SO 1 must be skipped @@ -1566,40 +1589,40 @@ def f2f_time(self,channel,skip_cycle=0,timeout=5): ============== ============================================================================================================== :return list: Array of points - - """ - try: - if timeout>60:timeout=60 - self.start_one_channel_LA(channel=channel,channel_mode=2,trigger_mode=0) #every falling edge - startTime=time.time() - while time.time() - startTime =skip_cycle+2: - tmp = self.fetch_long_data_from_LA(a,1) - self.dchans[0].load_data(e,tmp) - #print (self.dchans[0].timestamps) - return [1e-6*(self.dchans[0].timestamps[skip_cycle+1]-self.dchans[0].timestamps[0])] - time.sleep(0.1) - return [] - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def MeasureInterval(self,channel1,channel2,edge1,edge2,timeout=0.1): - """ + """ + try: + if timeout > 60: timeout = 60 + self.start_one_channel_LA(channel=channel, channel_mode=2, trigger_mode=0) # every falling edge + startTime = time.time() + while time.time() - startTime < timeout: + a, b, c, d, e = self.get_LA_initial_states() + if a == self.MAX_SAMPLES / 4: + a = 0 + if a >= skip_cycle + 2: + tmp = self.fetch_long_data_from_LA(a, 1) + self.dchans[0].load_data(e, tmp) + # print (self.dchans[0].timestamps) + return [1e-6 * (self.dchans[0].timestamps[skip_cycle + 1] - self.dchans[0].timestamps[0])] + time.sleep(0.1) + return [] + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def MeasureInterval(self, channel1, channel2, edge1, edge2, timeout=0.1): + """ Measures time intervals between two logic level changes on any two digital inputs(both can be the same) For example, one can measure the time interval between the occurence of a rising edge on ID1, and a falling edge on ID3. If the returned time is negative, it simply means that the event corresponding to channel2 occurred first. - + returns the calculated time - - + + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel1 The input pin to measure first logic level change channel2 The input pin to measure second logic level change @@ -1619,50 +1642,56 @@ def MeasureInterval(self,channel1,channel2,edge1,edge2,timeout=0.1): :return : time .. seealso:: timing_example_ - - - """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.INTERVAL_MEASUREMENTS) - timeout_msb = int((timeout*64e6))>>16 - self.H.__sendInt__(timeout_msb) - - self.H.__sendByte__(self.__calcDChan__(channel1)|(self.__calcDChan__(channel2)<<4)) - - params =0 - if edge1 == 'rising': params |= 3 - elif edge1=='falling': params |= 2 - else: params |= 4 - - if edge2 == 'rising': params |= 3<<3 - elif edge2=='falling': params |= 2<<3 - else: params |= 4<<3 - self.H.__sendByte__(params) - A=self.H.__getLong__() - B=self.H.__getLong__() - tmt = self.H.__getInt__() - self.H.__get_ack__() - #self.__print__(A,B) - if(tmt >= timeout_msb or B==0):return np.NaN - rtime = lambda t: t/64e6 - return rtime(B-A+20) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def DutyCycle(self,channel='ID1',timeout=1.): - """ + """ + try: + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.INTERVAL_MEASUREMENTS) + timeout_msb = int((timeout * 64e6)) >> 16 + self.H.__sendInt__(timeout_msb) + + self.H.__sendByte__(self.__calcDChan__(channel1) | (self.__calcDChan__(channel2) << 4)) + + params = 0 + if edge1 == 'rising': + params |= 3 + elif edge1 == 'falling': + params |= 2 + else: + params |= 4 + + if edge2 == 'rising': + params |= 3 << 3 + elif edge2 == 'falling': + params |= 2 << 3 + else: + params |= 4 << 3 + + self.H.__sendByte__(params) + A = self.H.__getLong__() + B = self.H.__getLong__() + tmt = self.H.__getInt__() + self.H.__get_ack__() + # self.__print__(A,B) + if (tmt >= timeout_msb or B == 0): return np.NaN + rtime = lambda t: t / 64e6 + return rtime(B - A + 20) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def DutyCycle(self, channel='ID1', timeout=1.): + """ duty cycle measurement on channel - + returns wavelength(seconds), and length of first half of pulse(high time) - + low time = (wavelength - high time) .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================== - **Arguments** + **Arguments** ============== ============================================================================================== channel The input pin to measure wavelength and high time.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] timeout Use the timeout option if you're unsure of the input signal time period. @@ -1674,37 +1703,37 @@ def DutyCycle(self,channel='ID1',timeout=1.): .. seealso:: timing_example_ """ - try: - x,y = self.MeasureMultipleDigitalEdges(channel,channel,'rising','falling',2,2,timeout,zero=True) - if x!=None and y!=None: # Both timers registered something. did not timeout - if y[0]>0: #rising edge occured first - dt = [y[0],x[1]] - else: #falling edge occured first - if y[1]>x[1]: - return -1,-1 #Edge dropped. return False - dt = [y[1],x[1]] - #self.__print__(x,y,dt) - params = dt[1],dt[0]/dt[1] - if params[1]>0.5: - self.__print__(x,y,dt) - return params - else: - return -1,-1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def PulseTime(self,channel='ID1',PulseType='LOW',timeout=0.1): - """ + try: + x, y = self.MeasureMultipleDigitalEdges(channel, channel, 'rising', 'falling', 2, 2, timeout, zero=True) + if x != None and y != None: # Both timers registered something. did not timeout + if y[0] > 0: # rising edge occured first + dt = [y[0], x[1]] + else: # falling edge occured first + if y[1] > x[1]: + return -1, -1 # Edge dropped. return False + dt = [y[1], x[1]] + # self.__print__(x,y,dt) + params = dt[1], dt[0] / dt[1] + if params[1] > 0.5: + self.__print__(x, y, dt) + return params + else: + return -1, -1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def PulseTime(self, channel='ID1', PulseType='LOW', timeout=0.1): + """ duty cycle measurement on channel - + returns wavelength(seconds), and length of first half of pulse(high time) - + low time = (wavelength - high time) .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================== - **Arguments** + **Arguments** ============== ============================================================================================== channel The input pin to measure wavelength and high time.['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] PulseType Type of pulse to detect. May be 'HIGH' or 'LOW' @@ -1717,21 +1746,26 @@ def PulseTime(self,channel='ID1',PulseType='LOW',timeout=0.1): .. seealso:: timing_example_ """ - try: - x,y = self.MeasureMultipleDigitalEdges(channel,channel,'rising','falling',2,2,timeout,zero=True) - if x!=None and y!=None: # Both timers registered something. did not timeout - if y[0]>0: #rising edge occured first - if PulseType=='HIGH': return y[0] - elif PulseType=='LOW': return x[1]-y[0] - else: #falling edge occured first - if PulseType=='HIGH': return y[1] - elif PulseType=='LOW': return abs(y[0]) - return -1,-1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def MeasureMultipleDigitalEdges(self,channel1,channel2,edgeType1,edgeType2,points1,points2,timeout=0.1,**kwargs): - """ + try: + x, y = self.MeasureMultipleDigitalEdges(channel, channel, 'rising', 'falling', 2, 2, timeout, zero=True) + if x != None and y != None: # Both timers registered something. did not timeout + if y[0] > 0: # rising edge occured first + if PulseType == 'HIGH': + return y[0] + elif PulseType == 'LOW': + return x[1] - y[0] + else: # falling edge occured first + if PulseType == 'HIGH': + return y[1] + elif PulseType == 'LOW': + return abs(y[0]) + return -1, -1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def MeasureMultipleDigitalEdges(self, channel1, channel2, edgeType1, edgeType2, points1, points2, timeout=0.1, + **kwargs): + """ Measures a set of timestamped logic level changes(Type can be selected) from two different digital inputs. Example @@ -1739,15 +1773,15 @@ def MeasureMultipleDigitalEdges(self,channel1,channel2,edgeType1,edgeType2,point The setup involves a small metal nut attached to an electromagnet powered via SQ1. When SQ1 is turned off, the set up is designed to make the nut fall through two different light barriers(LED,detector pairs that show a logic change when an object gets in the middle) - placed at known distances from the initial position. - + placed at known distances from the initial position. + one can measure the timestamps for rising edges on ID1 ,and ID2 to determine the speed, and then obtain value of g - - + + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel1 The input pin to measure first logic level change channel2 The input pin to measure second logic level change @@ -1772,57 +1806,63 @@ def MeasureMultipleDigitalEdges(self,channel1,channel2,edgeType1,edgeType2,point :return : time .. seealso:: timing_example_ - - - """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.TIMING_MEASUREMENTS) - timeout_msb = int((timeout*64e6))>>16 - #print ('timeout',timeout_msb) - self.H.__sendInt__(timeout_msb) - self.H.__sendByte__(self.__calcDChan__(channel1)|(self.__calcDChan__(channel2)<<4)) - params =0 - if edgeType1 == 'rising': params |= 3 - elif edgeType1=='falling': params |= 2 - else: params |= 4 - - if edgeType2 == 'rising': params |= 3<<3 - elif edgeType2=='falling': params |= 2<<3 - else: params |= 4<<3 - - if('SQR1' in kwargs): # User wants to toggle SQ1 before starting the timer - params|=(1<<6) - if kwargs['SQR1']=='HIGH':params|=(1<<7) - self.H.__sendByte__(params) - if points1>4:points1=4 - if points2>4:points2=4 - self.H.__sendByte__(points1|(points2<<4)) #Number of points to fetch from either channel - - self.H.waitForData(timeout) - - A=np.array([self.H.__getLong__() for a in range(points1)]) - B=np.array([self.H.__getLong__() for a in range(points2)]) - tmt = self.H.__getInt__() - self.H.__get_ack__() - #print(A,B) - if(tmt >= timeout_msb ):return None,None - rtime = lambda t: t/64e6 - if(kwargs.get('zero',True)): # User wants set a reference timestamp - return rtime(A-A[0]),rtime(B-A[0]) - else: - return rtime(A),rtime(B) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def capture_edges1(self,waiting_time=1.,**args): - """ + + """ + try: + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.TIMING_MEASUREMENTS) + timeout_msb = int((timeout * 64e6)) >> 16 + # print ('timeout',timeout_msb) + self.H.__sendInt__(timeout_msb) + self.H.__sendByte__(self.__calcDChan__(channel1) | (self.__calcDChan__(channel2) << 4)) + params = 0 + if edgeType1 == 'rising': + params |= 3 + elif edgeType1 == 'falling': + params |= 2 + else: + params |= 4 + + if edgeType2 == 'rising': + params |= 3 << 3 + elif edgeType2 == 'falling': + params |= 2 << 3 + else: + params |= 4 << 3 + + if ('SQR1' in kwargs): # User wants to toggle SQ1 before starting the timer + params |= (1 << 6) + if kwargs['SQR1'] == 'HIGH': params |= (1 << 7) + self.H.__sendByte__(params) + if points1 > 4: points1 = 4 + if points2 > 4: points2 = 4 + self.H.__sendByte__(points1 | (points2 << 4)) # Number of points to fetch from either channel + + self.H.waitForData(timeout) + + A = np.array([self.H.__getLong__() for a in range(points1)]) + B = np.array([self.H.__getLong__() for a in range(points2)]) + tmt = self.H.__getInt__() + self.H.__get_ack__() + # print(A,B) + if (tmt >= timeout_msb): return None, None + rtime = lambda t: t / 64e6 + if (kwargs.get('zero', True)): # User wants set a reference timestamp + return rtime(A - A[0]), rtime(B - A[0]) + else: + return rtime(A), rtime(B) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def capture_edges1(self, waiting_time=1., **args): + """ log timestamps of rising/falling edges on one digital input .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================= ====================================================================================================== - **Arguments** + **Arguments** ================= ====================================================================================================== waiting_time Total time to allow the logic analyzer to collect data. This is implemented using a simple sleep routine, so if large delays will be involved, @@ -1841,44 +1881,44 @@ def capture_edges1(self,waiting_time=1.,**args): - EVERY_FALLING_EDGE = 2 - EVERY_EDGE = 1 - DISABLED = 0 - + trigger_mode same as channel_mode. default_value : 3 ================= ====================================================================================================== - + :return: timestamp array in Seconds >>> I.capture_edges(0.2,channel='ID1',trigger_channel='ID1',channel_mode=3,trigger_mode = 3) #captures rising edges only. with rising edge trigger on ID1 - + """ - aqchan = args.get('channel','ID1') - trchan = args.get('trigger_channel',aqchan) + aqchan = args.get('channel', 'ID1') + trchan = args.get('trigger_channel', aqchan) - aqmode = args.get('channel_mode',3) - trmode = args.get('trigger_mode',3) + aqmode = args.get('channel_mode', 3) + trmode = args.get('trigger_mode', 3) - try: - self.start_one_channel_LA(channel=aqchan,channel_mode=aqmode,trigger_channel=trchan,trigger_mode=trmode) + try: + self.start_one_channel_LA(channel=aqchan, channel_mode=aqmode, trigger_channel=trchan, trigger_mode=trmode) - time.sleep(waiting_time) + time.sleep(waiting_time) - data=self.get_LA_initial_states() - tmp = self.fetch_long_data_from_LA(data[0],1) - #data[4][0] -> initial state - return tmp/64e6 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + data = self.get_LA_initial_states() + tmp = self.fetch_long_data_from_LA(data[0], 1) + # data[4][0] -> initial state + return tmp / 64e6 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,**args): - """ + def start_one_channel_LA_backup__(self, trigger=1, channel='ID1', maximum_time=67, **args): + """ start logging timestamps of rising/falling edges on ID1 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================== ====================================================================================================== - **Arguments** + **Arguments** ================== ====================================================================================================== trigger Bool . Enable edge trigger on ID1. use keyword argument edge='rising' or 'falling' channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] @@ -1889,55 +1929,56 @@ def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,* on either or the three specified trigger inputs. edge 'rising' or 'falling' . trigger edge type for trigger_channels. ================== ====================================================================================================== - + :return: Nothing """ - try: - self.clear_buffer(0,self.MAX_SAMPLES/2); - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.START_ONE_CHAN_LA) - self.H.__sendInt__(self.MAX_SAMPLES/4) - #trigchan bit functions - # b0 - trigger or not - # b1 - trigger edge . 1 => rising. 0 => falling - # b2, b3 - channel to acquire data from. ID1,ID2,ID3,ID4,COMPARATOR - # b4 - trigger channel ID1 - # b5 - trigger channel ID2 - # b6 - trigger channel ID3 - - if ('trigger_channels' in args) and trigger&1: - trigchans = args.get('trigger_channels',0) - if 'ID1' in trigchans : trigger|= (1<<4) - if 'ID2' in trigchans : trigger|= (1<<5) - if 'ID3' in trigchans : trigger|= (1<<6) - else: - trigger |= 1<<(self.__calcDChan__(channel)+4) #trigger on specified input channel if not trigger_channel argument provided - - trigger |= 2 if args.get('edge',0)=='rising' else 0 - trigger |= self.__calcDChan__(channel)<<2 - - self.H.__sendByte__(trigger) - self.H.__get_ack__() - self.digital_channels_in_buffer = 1 - for a in self.dchans: - a.prescaler = 0 - a.datatype='long' - a.length = self.MAX_SAMPLES/4 - a.maximum_time = maximum_time*1e6 #conversion to uS - a.mode = EVERY_EDGE - - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - #def start_one_channel_LA(self,**args): - """ + try: + self.clear_buffer(0, self.MAX_SAMPLES / 2) + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.START_ONE_CHAN_LA) + self.H.__sendInt__(self.MAX_SAMPLES / 4) + # trigchan bit functions + # b0 - trigger or not + # b1 - trigger edge . 1 => rising. 0 => falling + # b2, b3 - channel to acquire data from. ID1,ID2,ID3,ID4,COMPARATOR + # b4 - trigger channel ID1 + # b5 - trigger channel ID2 + # b6 - trigger channel ID3 + + if ('trigger_channels' in args) and trigger & 1: + trigchans = args.get('trigger_channels', 0) + if 'ID1' in trigchans: trigger |= (1 << 4) + if 'ID2' in trigchans: trigger |= (1 << 5) + if 'ID3' in trigchans: trigger |= (1 << 6) + else: + trigger |= 1 << (self.__calcDChan__( + channel) + 4) # trigger on specified input channel if not trigger_channel argument provided + + trigger |= 2 if args.get('edge', 0) == 'rising' else 0 + trigger |= self.__calcDChan__(channel) << 2 + + self.H.__sendByte__(trigger) + self.H.__get_ack__() + self.digital_channels_in_buffer = 1 + for a in self.dchans: + a.prescaler = 0 + a.datatype = 'long' + a.length = self.MAX_SAMPLES / 4 + a.maximum_time = maximum_time * 1e6 # conversion to uS + a.mode = self.EVERY_EDGE + + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + # def start_one_channel_LA(self,**args): + """ start logging timestamps of rising/falling edges on ID1 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================== ====================================================================================================== - **Arguments** + **Arguments** ================== ====================================================================================================== args channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] @@ -1952,13 +1993,13 @@ def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,* - EVERY_FALLING_EDGE = 2 - EVERY_EDGE = 1 - DISABLED = 0 - + trigger_edge 1=Falling edge 0=Rising Edge -1=Disable Trigger ================== ====================================================================================================== - + :return: Nothing self.clear_buffer(0,self.MAX_SAMPLES/2); @@ -1967,7 +2008,7 @@ def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,* self.H.__sendInt__(self.MAX_SAMPLES/4) aqchan = self.__calcDChan__(args.get('channel','ID1')) aqmode = args.get('channel_mode',1) - + if 'trigger_channel' in args: trchan = self.__calcDChan__(args.get('trigger_channel','ID1')) tredge = args.get('trigger_edge',0) @@ -1986,8 +2027,8 @@ def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,* self.H.__sendByte__(0) #no triggering self.H.__sendByte__((aqchan<<4)|aqmode) - - + + self.H.__get_ack__() self.digital_channels_in_buffer = 1 @@ -2006,14 +2047,14 @@ def start_one_channel_LA_backup__(self,trigger=1,channel='ID1',maximum_time=67,* ''' """ - def start_one_channel_LA(self,**args): - """ + def start_one_channel_LA(self, **args): + """ start logging timestamps of rising/falling edges on ID1 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================== ====================================================================================================== - **Arguments** + **Arguments** ================== ====================================================================================================== args channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] @@ -2027,56 +2068,56 @@ def start_one_channel_LA(self,**args): - EVERY_FALLING_EDGE = 2 - EVERY_EDGE = 1 - DISABLED = 0 - + ================== ====================================================================================================== - + :return: Nothing see :ref:`LA_video` """ - #trigger_channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] - #trigger_mode same as channel_mode. - # default_value : 3 - try: - self.clear_buffer(0,self.MAX_SAMPLES/2); - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.START_ALTERNATE_ONE_CHAN_LA) - self.H.__sendInt__(self.MAX_SAMPLES/4) - aqchan = self.__calcDChan__(args.get('channel','ID1')) - aqmode = args.get('channel_mode',1) - trchan = self.__calcDChan__(args.get('trigger_channel','ID1')) - trmode = args.get('trigger_mode',3) - - self.H.__sendByte__((aqchan<<4)|aqmode) - self.H.__sendByte__((trchan<<4)|trmode) - self.H.__get_ack__() - self.digital_channels_in_buffer = 1 - - a = self.dchans[0] - a.prescaler = 0 - a.datatype='long' - a.length = self.MAX_SAMPLES/4 - a.maximum_time = 67*1e6 #conversion to uS - a.mode = args.get('channel_mode',1) - a.name = args.get('channel','ID1') - - if trmode in [3,4,5]: - a.initial_state_override = 2 - elif trmode == 2: - a.initial_state_override = 1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + # trigger_channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] + # trigger_mode same as channel_mode. + # default_value : 3 + try: + self.clear_buffer(0, self.MAX_SAMPLES / 2) + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.START_ALTERNATE_ONE_CHAN_LA) + self.H.__sendInt__(self.MAX_SAMPLES / 4) + aqchan = self.__calcDChan__(args.get('channel', 'ID1')) + aqmode = args.get('channel_mode', 1) + trchan = self.__calcDChan__(args.get('trigger_channel', 'ID1')) + trmode = args.get('trigger_mode', 3) + + self.H.__sendByte__((aqchan << 4) | aqmode) + self.H.__sendByte__((trchan << 4) | trmode) + self.H.__get_ack__() + self.digital_channels_in_buffer = 1 + + a = self.dchans[0] + a.prescaler = 0 + a.datatype = 'long' + a.length = self.MAX_SAMPLES / 4 + a.maximum_time = 67 * 1e6 # conversion to uS + a.mode = args.get('channel_mode', 1) + a.name = args.get('channel', 'ID1') + + if trmode in [3, 4, 5]: + a.initial_state_override = 2 + elif trmode == 2: + a.initial_state_override = 1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start_two_channel_LA(self, **args): + """ + start logging timestamps of rising/falling edges on ID1,AD2 - def start_two_channel_LA(self,**args): - """ - start logging timestamps of rising/falling edges on ID1,AD2 - .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ======================================================================================================= - **Arguments** + **Arguments** ============== ======================================================================================================= trigger Bool . Enable rising edge trigger on ID1 \*\*args @@ -2101,54 +2142,57 @@ def start_two_channel_LA(self,**args): "fetch_long_data_from_dma(samples,2)" to get data acquired from channel 2 The read data can be accessed from self.dchans[0 or 1] """ - #Trigger not working up to expectations. DMA keeps dumping Null values even though not triggered. - - #trigger True/False : Whether or not to trigger the Logic Analyzer using the first channel of the two. - #trig_type 'rising' / 'falling' . Type of logic change to trigger on - #trig_chan channel to trigger on . Any digital input. default chans[0] - - - modes = args.get('modes',[1,1]) - strchans = args.get('chans',['ID1','ID2']) - chans = [self.__calcDChan__(strchans[0]),self.__calcDChan__(strchans[1])] #Convert strings to index - maximum_time = args.get('maximum_time',67) - trigger = args.get('trigger',0) - if trigger: - trigger = 1 - if args.get('edge','rising')=='falling' : trigger|=2 - trigger |= (self.__calcDChan__(args.get('trig_chan',strchans[0]))<<4) - #print (args.get('trigger',0),args.get('edge'),args.get('trig_chan',strchans[0]),hex(trigger),args) - else: - trigger = 0 - - try: - self.clear_buffer(0,self.MAX_SAMPLES); - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.START_TWO_CHAN_LA) - self.H.__sendInt__(self.MAX_SAMPLES/4) - self.H.__sendByte__(trigger) - - self.H.__sendByte__((modes[1]<<4)|modes[0]) #Modes. four bits each - self.H.__sendByte__((chans[1]<<4)|chans[0]) #Channels. four bits each - self.H.__get_ack__() - n=0; - for a in self.dchans[:2]: - a.prescaler = 0;a.length = self.MAX_SAMPLES/4; a.datatype='long';a.maximum_time = maximum_time*1e6 #conversion to uS - a.mode = modes[n];a.channel_number=chans[n] - a.name = strchans[n] - n+=1 - self.digital_channels_in_buffer = 2 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def start_three_channel_LA(self,**args): - """ + # Trigger not working up to expectations. DMA keeps dumping Null values even though not triggered. + + # trigger True/False : Whether or not to trigger the Logic Analyzer using the first channel of the two. + # trig_type 'rising' / 'falling' . Type of logic change to trigger on + # trig_chan channel to trigger on . Any digital input. default chans[0] + + modes = args.get('modes', [1, 1]) + strchans = args.get('chans', ['ID1', 'ID2']) + chans = [self.__calcDChan__(strchans[0]), self.__calcDChan__(strchans[1])] # Convert strings to index + maximum_time = args.get('maximum_time', 67) + trigger = args.get('trigger', 0) + if trigger: + trigger = 1 + if args.get('edge', 'rising') == 'falling': trigger |= 2 + trigger |= (self.__calcDChan__(args.get('trig_chan', strchans[0])) << 4) + # print (args.get('trigger',0),args.get('edge'),args.get('trig_chan',strchans[0]),hex(trigger),args) + else: + trigger = 0 + + try: + self.clear_buffer(0, self.MAX_SAMPLES) + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.START_TWO_CHAN_LA) + self.H.__sendInt__(self.MAX_SAMPLES / 4) + self.H.__sendByte__(trigger) + + self.H.__sendByte__((modes[1] << 4) | modes[0]) # Modes. four bits each + self.H.__sendByte__((chans[1] << 4) | chans[0]) # Channels. four bits each + self.H.__get_ack__() + n = 0 + for a in self.dchans[:2]: + a.prescaler = 0 + a.length = self.MAX_SAMPLES / 4 + a.datatype = 'long' + a.maximum_time = maximum_time * 1e6 # conversion to uS + a.mode = modes[n] + a.channel_number = chans[n] + a.name = strchans[n] + n += 1 + self.digital_channels_in_buffer = 2 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start_three_channel_LA(self, **args): + """ start logging timestamps of rising/falling edges on ID1,ID2,ID3 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================== ====================================================================================================== - **Arguments** + **Arguments** ================== ====================================================================================================== args trigger_channel ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] @@ -2162,55 +2206,55 @@ def start_three_channel_LA(self,**args): - EVERY_FALLING_EDGE = 2 - EVERY_EDGE = 1 - DISABLED = 0 - + trigger_mode same as modes(previously documented keyword argument) default_value : 3 ================== ====================================================================================================== - + :return: Nothing """ - try: - self.clear_buffer(0,self.MAX_SAMPLES); - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.START_THREE_CHAN_LA) - self.H.__sendInt__(self.MAX_SAMPLES/4) - modes = args.get('modes',[1,1,1,1]) - trchan = self.__calcDChan__(args.get('trigger_channel','ID1')) - trmode = args.get('trigger_mode',3) - - self.H.__sendInt__(modes[0]|(modes[1]<<4)|(modes[2]<<8)) - self.H.__sendByte__((trchan<<4)|trmode) - - self.H.__get_ack__() - self.digital_channels_in_buffer = 3 - - n=0 - for a in self.dchans[:3]: - a.prescaler = 0 - a.length = self.MAX_SAMPLES/4 - a.datatype='int' - a.maximum_time = 1e3 #< 1 mS between each consecutive level changes in the input signal must be ensured to prevent rollover - a.mode=modes[n] - a.name = a.digital_channel_names[n] - if trmode in [3,4,5]: - a.initial_state_override = 2 - elif trmode == 2: - a.initial_state_override = 1 - n+=1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def start_four_channel_LA(self,trigger=1,maximum_time=0.001,mode=[1,1,1,1],**args): - """ + try: + self.clear_buffer(0, self.MAX_SAMPLES) + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.START_THREE_CHAN_LA) + self.H.__sendInt__(self.MAX_SAMPLES / 4) + modes = args.get('modes', [1, 1, 1, 1]) + trchan = self.__calcDChan__(args.get('trigger_channel', 'ID1')) + trmode = args.get('trigger_mode', 3) + + self.H.__sendInt__(modes[0] | (modes[1] << 4) | (modes[2] << 8)) + self.H.__sendByte__((trchan << 4) | trmode) + + self.H.__get_ack__() + self.digital_channels_in_buffer = 3 + + n = 0 + for a in self.dchans[:3]: + a.prescaler = 0 + a.length = self.MAX_SAMPLES / 4 + a.datatype = 'int' + a.maximum_time = 1e3 # < 1 mS between each consecutive level changes in the input signal must be ensured to prevent rollover + a.mode = modes[n] + a.name = a.digital_channel_names[n] + if trmode in [3, 4, 5]: + a.initial_state_override = 2 + elif trmode == 2: + a.initial_state_override = 1 + n += 1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def start_four_channel_LA(self, trigger=1, maximum_time=0.001, mode=[1, 1, 1, 1], **args): + """ Four channel Logic Analyzer. start logging timestamps from a 64MHz counter to record level changes on ID1,ID2,ID3,ID4. - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ trigger Bool . Enable rising edge trigger on ID1 @@ -2240,9 +2284,9 @@ def start_four_channel_LA(self,trigger=1,maximum_time=0.001,mode=[1,1,1,1],**arg Use :func:`fetch_long_data_from_LA` (points to read,x) to get data acquired from channel x. The read data can be accessed from :class:`~ScienceLab.dchans` [x-1] """ - self.clear_buffer(0,self.MAX_SAMPLES); - prescale = 0 - """ + self.clear_buffer(0, self.MAX_SAMPLES) + prescale = 0 + """ if(maximum_time > 0.26): #self.__print__('too long for 4 channel. try 2/1 channels') prescale = 3 @@ -2253,202 +2297,204 @@ def start_four_channel_LA(self,trigger=1,maximum_time=0.001,mode=[1,1,1,1],**arg elif(maximum_time > 0.0010239): prescale = 1 """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.START_FOUR_CHAN_LA) - self.H.__sendInt__(self.MAX_SAMPLES/4) - self.H.__sendInt__(mode[0]|(mode[1]<<4)|(mode[2]<<8)|(mode[3]<<12)) - self.H.__sendByte__(prescale) #prescaler - trigopts=0 - trigopts |= 4 if args.get('trigger_ID1',0) else 0 - trigopts |= 8 if args.get('trigger_ID2',0) else 0 - trigopts |= 16 if args.get('trigger_ID3',0) else 0 - if (trigopts==0): trigger|=4 #select one trigger channel(ID1) if none selected - trigopts |= 2 if args.get('edge',0)=='rising' else 0 - trigger|=trigopts - self.H.__sendByte__(trigger) - self.H.__get_ack__() - self.digital_channels_in_buffer = 4 - n=0 - for a in self.dchans: - a.prescaler = prescale - a.length = self.MAX_SAMPLES/4 - a.datatype='int' - a.name = a.digital_channel_names[n] - a.maximum_time = maximum_time*1e6 #conversion to uS - a.mode=mode[n] - n+=1 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def get_LA_initial_states(self): - """ + try: + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.START_FOUR_CHAN_LA) + self.H.__sendInt__(self.MAX_SAMPLES / 4) + self.H.__sendInt__(mode[0] | (mode[1] << 4) | (mode[2] << 8) | (mode[3] << 12)) + self.H.__sendByte__(prescale) # prescaler + trigopts = 0 + trigopts |= 4 if args.get('trigger_ID1', 0) else 0 + trigopts |= 8 if args.get('trigger_ID2', 0) else 0 + trigopts |= 16 if args.get('trigger_ID3', 0) else 0 + if (trigopts == 0): trigger |= 4 # select one trigger channel(ID1) if none selected + trigopts |= 2 if args.get('edge', 0) == 'rising' else 0 + trigger |= trigopts + self.H.__sendByte__(trigger) + self.H.__get_ack__() + self.digital_channels_in_buffer = 4 + n = 0 + for a in self.dchans: + a.prescaler = prescale + a.length = self.MAX_SAMPLES / 4 + a.datatype = 'int' + a.name = a.digital_channel_names[n] + a.maximum_time = maximum_time * 1e6 # conversion to uS + a.mode = mode[n] + n += 1 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_LA_initial_states(self): + """ fetches the initial states of digital inputs that were recorded right before the Logic analyzer was started, and the total points each channel recorded :return: chan1 progress,chan2 progress,chan3 progress,chan4 progress,[ID1,ID2,ID3,ID4]. eg. [1,0,1,1] """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.GET_INITIAL_DIGITAL_STATES) - initial=self.H.__getInt__() - A=(self.H.__getInt__()-initial)/2 - B=(self.H.__getInt__()-initial)/2-self.MAX_SAMPLES/4 - C=(self.H.__getInt__()-initial)/2-2*self.MAX_SAMPLES/4 - D=(self.H.__getInt__()-initial)/2-3*self.MAX_SAMPLES/4 - s=self.H.__getByte__() - s_err=self.H.__getByte__() - self.H.__get_ack__() - - if A==0: A=self.MAX_SAMPLES/4 - if B==0: B=self.MAX_SAMPLES/4 - if C==0: C=self.MAX_SAMPLES/4 - if D==0: D=self.MAX_SAMPLES/4 - - if A<0: A=0 - if B<0: B=0 - if C<0: C=0 - if D<0: D=0 - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - return A,B,C,D,{'ID1':(s&1!=0),'ID2':(s&2!=0),'ID3':(s&4!=0),'ID4':(s&8!=0),'SEN':(s&16!=16)} #SEN is inverted comparator output. - - def stop_LA(self): - """ + try: + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.GET_INITIAL_DIGITAL_STATES) + initial = self.H.__getInt__() + A = (self.H.__getInt__() - initial) / 2 + B = (self.H.__getInt__() - initial) / 2 - self.MAX_SAMPLES / 4 + C = (self.H.__getInt__() - initial) / 2 - 2 * self.MAX_SAMPLES / 4 + D = (self.H.__getInt__() - initial) / 2 - 3 * self.MAX_SAMPLES / 4 + s = self.H.__getByte__() + s_err = self.H.__getByte__() + self.H.__get_ack__() + + if A == 0: A = self.MAX_SAMPLES / 4 + if B == 0: B = self.MAX_SAMPLES / 4 + if C == 0: C = self.MAX_SAMPLES / 4 + if D == 0: D = self.MAX_SAMPLES / 4 + + if A < 0: A = 0 + if B < 0: B = 0 + if C < 0: C = 0 + if D < 0: D = 0 + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return A, B, C, D, {'ID1': (s & 1 != 0), 'ID2': (s & 2 != 0), 'ID3': (s & 4 != 0), 'ID4': (s & 8 != 0), + 'SEN': (s & 16 != 16)} # SEN is inverted comparator output. + + def stop_LA(self): + """ Stop any running logic analyzer function """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.STOP_LA) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def fetch_int_data_from_LA(self,bytes,chan=1): - """ + try: + self.H.__sendByte__(CP.TIMING) + self.H.__sendByte__(CP.STOP_LA) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def fetch_int_data_from_LA(self, bytes, chan=1): + """ fetches the data stored by DMA. integer address increments .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ bytes: number of readings(integers) to fetch chan: channel number (1-4) ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.TIMING) - self.H.__sendByte__(CP.FETCH_INT_DMA_DATA) - self.H.__sendInt__(bytes) - self.H.__sendByte__(chan-1) - - ss = self.H.fd.read(int(bytes*2)) - t = np.zeros(bytes*2) - for a in range(int(bytes)): - t[a] = CP.ShortInt.unpack(ss[a*2:a*2+2])[0] - - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - t=np.trim_zeros(t) - b=1;rollovers=0 - while b=self.digital_channels_in_buffer: - self.__print__('channel unavailable') - return False - - samples = a.length - if a.datatype=='int': - tmp = self.fetch_int_data_from_LA(initial_states[a.channel_number],a.channel_number+1) - a.load_data(s,tmp) - else: - tmp = self.fetch_long_data_from_LA(initial_states[a.channel_number*2],a.channel_number+1) - a.load_data(s,tmp) - - #offset=0 - #a.timestamps -= offset - a.generate_axes() - return True - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def get_states(self): - """ + try: + data = self.get_LA_initial_states() + # print (data) + for a in range(4): + if (self.dchans[a].channel_number < self.digital_channels_in_buffer): self.__fetch_LA_channel__(a, data) + return True + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __fetch_LA_channel__(self, channel_number, initial_states): + try: + s = initial_states[4] + a = self.dchans[channel_number] + if a.channel_number >= self.digital_channels_in_buffer: + self.__print__('channel unavailable') + return False + + samples = a.length + if a.datatype == 'int': + tmp = self.fetch_int_data_from_LA(initial_states[a.channel_number], a.channel_number + 1) + a.load_data(s, tmp) + else: + tmp = self.fetch_long_data_from_LA(initial_states[a.channel_number * 2], a.channel_number + 1) + a.load_data(s, tmp) + + # offset=0 + # a.timestamps -= offset + a.generate_axes() + return True + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_states(self): + """ gets the state of the digital inputs. returns dictionary with keys 'ID1','ID2','ID3','ID4' >>> self.__print__(get_states()) {'ID1': True, 'ID2': True, 'ID3': True, 'ID4': False} - - """ - try: - self.H.__sendByte__(CP.DIN) - self.H.__sendByte__(CP.GET_STATES) - s=self.H.__getByte__() - self.H.__get_ack__() - return {'ID1':(s&1!=0),'ID2':(s&2!=0),'ID3':(s&4!=0),'ID4':(s&8!=0)} - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def get_state(self,input_id): """ + try: + self.H.__sendByte__(CP.DIN) + self.H.__sendByte__(CP.GET_STATES) + s = self.H.__getByte__() + self.H.__get_ack__() + return {'ID1': (s & 1 != 0), 'ID2': (s & 2 != 0), 'ID3': (s & 4 != 0), 'ID4': (s & 8 != 0)} + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_state(self, input_id): + """ returns the logic level on the specified input (ID1,ID2,ID3, or ID4) .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ **Arguments** Description ============== ============================================================================================ @@ -2459,19 +2505,19 @@ def get_state(self,input_id): >>> self.__print__(I.get_state(I.ID1)) False - - """ - return self.get_states()[input_id] - def set_state(self,**kwargs): """ - + return self.get_states()[input_id] + + def set_state(self, **kwargs): + """ + set the logic level on digital outputs SQR1,SQR2,SQR3,SQR4 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ \*\*kwargs SQR1,SQR2,SQR3,SQR4 states(0 or 1) @@ -2481,317 +2527,319 @@ def set_state(self,**kwargs): sets SQR1 HIGH, SQR2 LOw, but leave SQR3,SQR4 untouched. """ - data=0 - if 'SQR1' in kwargs: - data|= 0x10|(kwargs.get('SQR1')) - if 'SQR2' in kwargs: - data|= 0x20|(kwargs.get('SQR2')<<1) - if 'SQR3' in kwargs: - data|= 0x40|(kwargs.get('SQR3')<<2) - if 'SQR4' in kwargs: - data|= 0x80|(kwargs.get('SQR4')<<3) - try: - self.H.__sendByte__(CP.DOUT) - self.H.__sendByte__(CP.SET_STATE) - self.H.__sendByte__(data) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + data = 0 + if 'SQR1' in kwargs: + data |= 0x10 | (kwargs.get('SQR1')) + if 'SQR2' in kwargs: + data |= 0x20 | (kwargs.get('SQR2') << 1) + if 'SQR3' in kwargs: + data |= 0x40 | (kwargs.get('SQR3') << 2) + if 'SQR4' in kwargs: + data |= 0x80 | (kwargs.get('SQR4') << 3) + try: + self.H.__sendByte__(CP.DOUT) + self.H.__sendByte__(CP.SET_STATE) + self.H.__sendByte__(data) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def countPulses(self, channel='SEN'): + """ - def countPulses(self,channel='SEN'): - """ - Count pulses on a digital input. Retrieve total pulses using readPulseCount .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ channel The input pin to measure rising edges on : ['ID1','ID2','ID3','ID4','SEN','EXT','CNTR'] ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.START_COUNTING) - self.H.__sendByte__(self.__calcDChan__(channel)) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.START_COUNTING) + self.H.__sendByte__(self.__calcDChan__(channel)) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def readPulseCount(self): + """ - def readPulseCount(self): - """ - Read pulses counted using a digital input. Call countPulses before using this. .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.FETCH_COUNT) - count = self.H.__getInt__() - self.H.__get_ack__() - return count - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - - def __charge_cap__(self,state,t): - try: - self.H.__sendByte__(CP.ADC) - self.H.__sendByte__(CP.SET_CAP) - self.H.__sendByte__(state) - self.H.__sendInt__(t) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __capture_capacitance__(self,samples,tg): - from PSL.analyticsClass import analyticsClass - self.AC = analyticsClass() - self.__charge_cap__(1,50000) - try: - x,y=self.capture_fullspeed_hr('CAP',samples,tg,'READ_CAP') - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - try: - fitres = self.AC.fit_exp(x*1e-6,y) - if fitres: - cVal,newy = fitres - #from pylab import * - #plot(x,newy) - #show() - return x,y,newy,cVal - else: - return None - except Exception as ex: - raise RuntimeError(" Fit Failed ") - - def capacitance_via_RC_discharge(self): - cap = self.get_capacitor_range()[1] - T = 2*cap*20e3*1e6 #uS - samples = 500 - try: - if T>5000 and T<10e6: - if T>50e3:samples=250 - RC = self.__capture_capacitance__(samples,int(T/samples))[3][1] - return RC/10e3 - else: - self.__print__('cap out of range %f %f'%(T,cap)) - return 0 - except Exception as e: - self.__print__(e) - return 0 - - - def __get_capacitor_range__(self,ctime): - try: - self.__charge_cap__(0,30000) - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_CAP_RANGE) - self.H.__sendInt__(ctime) - V_sum = self.H.__getInt__() - self.H.__get_ack__() - V=V_sum*3.3/16/4095 - C = -ctime*1e-6/1e4/np.log(1-V/3.3) - return V,C - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def get_capacitor_range(self): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.FETCH_COUNT) + count = self.H.__getInt__() + self.H.__get_ack__() + return count + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __charge_cap__(self, state, t): + try: + self.H.__sendByte__(CP.ADC) + self.H.__sendByte__(CP.SET_CAP) + self.H.__sendByte__(state) + self.H.__sendInt__(t) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __capture_capacitance__(self, samples, tg): + from PSL.analyticsClass import analyticsClass + self.AC = analyticsClass() + self.__charge_cap__(1, 50000) + try: + x, y = self.capture_fullspeed_hr('CAP', samples, tg, 'READ_CAP') + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + try: + fitres = self.AC.fit_exp(x * 1e-6, y) + if fitres: + cVal, newy = fitres + # from PSL import * + # plot(x,newy) + # show() + return x, y, newy, cVal + else: + return None + except Exception as ex: + raise RuntimeError(" Fit Failed ") + + def capacitance_via_RC_discharge(self): + cap = self.get_capacitor_range()[1] + T = 2 * cap * 20e3 * 1e6 # uS + samples = 500 + try: + if T > 5000 and T < 10e6: + if T > 50e3: samples = 250 + RC = self.__capture_capacitance__(samples, int(T / samples))[3][1] + return RC / 10e3 + else: + self.__print__('cap out of range %f %f' % (T, cap)) + return 0 + except Exception as e: + self.__print__(e) + return 0 + + def __get_capacitor_range__(self, ctime): + try: + self.__charge_cap__(0, 30000) + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_CAP_RANGE) + self.H.__sendInt__(ctime) + V_sum = self.H.__getInt__() + self.H.__get_ack__() + V = V_sum * 3.3 / 16 / 4095 + C = -ctime * 1e-6 / 1e4 / np.log(1 - V / 3.3) + return V, C + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_capacitor_range(self): + """ Charges a capacitor connected to IN1 via a 20K resistor from a 3.3V source for a fixed interval Returns the capacitance calculated using the formula Vc = Vs(1-exp(-t/RC)) This function allows an estimation of the parameters to be used with the :func:`get_capacitance` function. """ - t=10 - P=[1.5,50e-12] - for a in range(4): - P=list(self.__get_capacitor_range__(50*(10**a))) - if(P[0]>1.5): - if a==0 and P[0]>3.28: #pico farads range. Values will be incorrect using this method - P[1]=50e-12 - break - return P + t = 10 + P = [1.5, 50e-12] + for a in range(4): + P = list(self.__get_capacitor_range__(50 * (10 ** a))) + if (P[0] > 1.5): + if a == 0 and P[0] > 3.28: # pico farads range. Values will be incorrect using this method + P[1] = 50e-12 + break + return P - def get_capacitance(self): #time in uS - """ + def get_capacitance(self): # time in uS + """ measures capacitance of component connected between CAP and ground - + :return: Capacitance (F) Constant Current Charging - + .. math:: Q_{stored} = C*V - + I_{constant}*time = C*V - + C = I_{constant}*time/V_{measured} Also uses Constant Voltage Charging via 20K resistor if required. """ - GOOD_VOLTS=[2.5,2.8] - CT=10 - CR=1 - iterations = 0 - start_time=time.time() - try: - while (time.time()-start_time)<1: - #self.__print__('vals',CR,',',CT) - if CT>65000: - self.__print__('CT too high') - return self.capacitance_via_RC_discharge() - V,C = self.__get_capacitance__(CR,0,CT) - #print(CR,CT,V,C) - if CT>30000 and V<0.1: - self.__print__('Capacitance too high for this method') - return 0 - - elif V>GOOD_VOLTS[0] and V0.01 and CT<40000: - if GOOD_VOLTS[0]/V >1.1 and iterations<10: - CT=int(CT*GOOD_VOLTS[0]/V) - iterations+=1 - self.__print__('increased CT ',CT) - elif iterations==10: - return 0 - else: - return C - elif V<=0.1 and CR<3: - CR+=1 - elif CR==3: - self.__print__('Capture mode ') - return self.capacitance_via_RC_discharge() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - - - def __calibrate_ctmu__(self,scalers): - #self.currents=[0.55e-3/scalers[0],0.55e-6/scalers[1],0.55e-5/scalers[2],0.55e-4/scalers[3]] - self.currents=[0.55e-3,0.55e-6,0.55e-5,0.55e-4] - self.currentScalers = scalers - #print (self.currentScalers,scalers,self.SOCKET_CAPACITANCE) - - def __get_capacitance__(self,current_range,trim, Charge_Time): #time in uS - try: - self.__charge_cap__(0,30000) - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_CAPACITANCE) - self.H.__sendByte__(current_range) - if(trim<0): - self.H.__sendByte__( int(31-abs(trim)/2)|32) - else: - self.H.__sendByte__(int(trim/2)) - self.H.__sendInt__(Charge_Time) - time.sleep(Charge_Time*1e-6+.02) - VCode = self.H.__getInt__() - V = 3.3*VCode/4095 - self.H.__get_ack__() - Charge_Current = self.currents[current_range]*(100+trim)/100.0 - if V:C = (Charge_Current*Charge_Time*1e-6/V - self.SOCKET_CAPACITANCE)/self.currentScalers[current_range] - else: C = 0 - #self.__print__('Current if C=470pF :',V*(470e-12+self.SOCKET_CAPACITANCE)/(Charge_Time*1e-6)) - return V,C - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def get_temperature(self): - """ + GOOD_VOLTS = [2.5, 2.8] + CT = 10 + CR = 1 + iterations = 0 + start_time = time.time() + try: + while (time.time() - start_time) < 1: + # self.__print__('vals',CR,',',CT) + if CT > 65000: + self.__print__('CT too high') + return self.capacitance_via_RC_discharge() + V, C = self.__get_capacitance__(CR, 0, CT) + # print(CR,CT,V,C) + if CT > 30000 and V < 0.1: + self.__print__('Capacitance too high for this method') + return 0 + + elif V > GOOD_VOLTS[0] and V < GOOD_VOLTS[1]: + return C + elif V < GOOD_VOLTS[0] and V > 0.01 and CT < 40000: + if GOOD_VOLTS[0] / V > 1.1 and iterations < 10: + CT = int(CT * GOOD_VOLTS[0] / V) + iterations += 1 + self.__print__('increased CT ', CT) + elif iterations == 10: + return 0 + else: + return C + elif V <= 0.1 and CR < 3: + CR += 1 + elif CR == 3: + self.__print__('Capture mode ') + return self.capacitance_via_RC_discharge() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __calibrate_ctmu__(self, scalers): + # self.currents=[0.55e-3/scalers[0],0.55e-6/scalers[1],0.55e-5/scalers[2],0.55e-4/scalers[3]] + self.currents = [0.55e-3, 0.55e-6, 0.55e-5, 0.55e-4] + self.currentScalers = scalers + + # print (self.currentScalers,scalers,self.SOCKET_CAPACITANCE) + + def __get_capacitance__(self, current_range, trim, Charge_Time): # time in uS + try: + self.__charge_cap__(0, 30000) + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_CAPACITANCE) + self.H.__sendByte__(current_range) + if (trim < 0): + self.H.__sendByte__(int(31 - abs(trim) / 2) | 32) + else: + self.H.__sendByte__(int(trim / 2)) + self.H.__sendInt__(Charge_Time) + time.sleep(Charge_Time * 1e-6 + .02) + VCode = self.H.__getInt__() + V = 3.3 * VCode / 4095 + self.H.__get_ack__() + Charge_Current = self.currents[current_range] * (100 + trim) / 100.0 + if V: + C = (Charge_Current * Charge_Time * 1e-6 / V - self.SOCKET_CAPACITANCE) / self.currentScalers[ + current_range] + else: + C = 0 + # self.__print__('Current if C=470pF :',V*(470e-12+self.SOCKET_CAPACITANCE)/(Charge_Time*1e-6)) + return V, C + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def get_temperature(self): + """ return the processor's temperature - - :return: Chip Temperature in degree Celcius - """ - cs=3 - V=self.get_ctmu_voltage(0b11110,cs,0) - - if cs==1: return (646-V*1000)/1.92 #current source = 1 - elif cs==2: return (701.5-V*1000)/1.74 #current source = 2 - elif cs==3: return (760-V*1000)/1.56 #current source = 3 - def get_ctmu_voltage(self,channel,Crange,tgen=1): + :return: Chip Temperature in degree Celcius """ + cs = 3 + V = self.get_ctmu_voltage(0b11110, cs, 0) + + if cs == 1: + return (646 - V * 1000) / 1.92 # current source = 1 + elif cs == 2: + return (701.5 - V * 1000) / 1.74 # current source = 2 + elif cs == 3: + return (760 - V * 1000) / 1.56 # current source = 3 + + def get_ctmu_voltage(self, channel, Crange, tgen=1): + """ get_ctmu_voltage(5,2) will activate a constant current source of 5.5uA on IN1 and then measure the voltage at the output. If a diode is used to connect IN1 to ground, the forward voltage drop of the diode will be returned. e.g. .6V for a 4148diode. - + If a resistor is connected, ohm's law will be followed within reasonable limits - + channel=5 for IN1 - + CRange=0 implies 550uA CRange=1 implies 0.55uA CRange=2 implies 5.5uA CRange=3 implies 55uA - - :return: Voltage - """ - if channel=='CAP':channel=5 - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.GET_CTMU_VOLTAGE) - self.H.__sendByte__((channel)|(Crange<<5)|(tgen<<7)) - - #V = [self.H.__getInt__() for a in range(16)] - #print(V) - #V=V[3:] - v=self.H.__getInt__() #16*voltage across the current source - #v=sum(V) - self.H.__get_ack__() - V=3.3*v/16/4095. - #print(V) - return V - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __start_ctmu__(self,Crange,trim,tgen=1): - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.START_CTMU) - self.H.__sendByte__((Crange)|(tgen<<7)) - self.H.__sendByte__(trim) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __stop_ctmu__(self): - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.STOP_CTMU) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def resetHardware(self): + :return: Voltage """ + if channel == 'CAP': channel = 5 + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.GET_CTMU_VOLTAGE) + self.H.__sendByte__((channel) | (Crange << 5) | (tgen << 7)) + + # V = [self.H.__getInt__() for a in range(16)] + # print(V) + # V=V[3:] + v = self.H.__getInt__() # 16*voltage across the current source + # v=sum(V) + + self.H.__get_ack__() + V = 3.3 * v / 16 / 4095. + # print(V) + return V + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __start_ctmu__(self, Crange, trim, tgen=1): + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.START_CTMU) + self.H.__sendByte__((Crange) | (tgen << 7)) + self.H.__sendByte__(trim) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __stop_ctmu__(self): + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.STOP_CTMU) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def resetHardware(self): + """ Resets the device, and standalone mode will be enabled if an OLED is connected to the I2C port """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.RESTORE_STANDALONE) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.RESTORE_STANDALONE) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def read_flash(self,page,location): - """ + def read_flash(self, page, location): + """ Reads 16 BYTES from the specified location .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================ ============================================================================================ - **Arguments** + **Arguments** ================ ============================================================================================ page page number. 20 pages with 2KBytes each location The flash location(0 to 63) to read from . @@ -2799,31 +2847,31 @@ def read_flash(self,page,location): :return: a string of 16 characters read from the location """ - try: - self.H.__sendByte__(CP.FLASH) - self.H.__sendByte__(CP.READ_FLASH) - self.H.__sendByte__(page) #send the page number. 20 pages with 2K bytes each - self.H.__sendByte__(location) #send the location - ss=self.H.fd.read(16) - self.H.__get_ack__() - return ss - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __stoa__(self,s): - return [ord(a) for a in s] - - def __atos__(self,a): - return ''.join(chr(e) for e in a) - - def read_bulk_flash(self,page,numbytes): - """ + try: + self.H.__sendByte__(CP.FLASH) + self.H.__sendByte__(CP.READ_FLASH) + self.H.__sendByte__(page) # send the page number. 20 pages with 2K bytes each + self.H.__sendByte__(location) # send the location + ss = self.H.fd.read(16) + self.H.__get_ack__() + return ss + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __stoa__(self, s): + return [ord(a) for a in s.decode('utf-8')] + + def __atos__(self, a): + return ''.join(chr(e) for e in a) + + def read_bulk_flash(self, page, numbytes): + """ Reads BYTES from the specified location .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================ ============================================================================================ - **Arguments** + **Arguments** ================ ============================================================================================ page Block number. 0-20. each block is 2kB. numbytes Total bytes to read @@ -2831,34 +2879,34 @@ def read_bulk_flash(self,page,numbytes): :return: a string of 16 characters read from the location """ - try: - self.H.__sendByte__(CP.FLASH) - self.H.__sendByte__(CP.READ_BULK_FLASH) - bytes_to_read = numbytes - if numbytes%2: bytes_to_read+=1 #bytes+1 . stuff is stored as integers (byte+byte) in the hardware - self.H.__sendInt__(bytes_to_read) - self.H.__sendByte__(page) - ss=self.H.fd.read(int(bytes_to_read)) - self.H.__get_ack__() - self.__print__('Read from ',page,',',bytes_to_read,' :',self.__stoa__(ss[:40]),'...') - if numbytes%2: return ss[:-1] #Kill the extra character we read. Don't surprise the user with extra data - return ss - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_flash(self,page,location,string_to_write): - """ + try: + self.H.__sendByte__(CP.FLASH) + self.H.__sendByte__(CP.READ_BULK_FLASH) + bytes_to_read = numbytes + if numbytes % 2: bytes_to_read += 1 # bytes+1 . stuff is stored as integers (byte+byte) in the hardware + self.H.__sendInt__(bytes_to_read) + self.H.__sendByte__(page) + ss = self.H.fd.read(int(bytes_to_read)) + self.H.__get_ack__() + self.__print__('Read from ', page, ',', bytes_to_read, ' :', self.__stoa__(ss[:40]), '...') + if numbytes % 2: return ss[:-1] # Kill the extra character we read. Don't surprise the user with extra data + return ss + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_flash(self, page, location, string_to_write): + """ write a 16 BYTE string to the selected location (0-63) DO NOT USE THIS UNLESS YOU'RE ABSOLUTELY SURE KNOW THIS! YOU MAY END UP OVERWRITING THE CALIBRATION DATA, AND WILL HAVE TO GO THROUGH THE TROUBLE OF GETTING IT FROM THE MANUFACTURER AND REFLASHING IT. - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================ ============================================================================================ - **Arguments** + **Arguments** ================ ============================================================================================ page page number. 20 pages with 2KBytes each location The flash location(0 to 63) to write to. @@ -2866,499 +2914,499 @@ def write_flash(self,page,location,string_to_write): ================ ============================================================================================ """ - try: - while(len(string_to_write)<16):string_to_write+='.' - self.H.__sendByte__(CP.FLASH) - self.H.__sendByte__(CP.WRITE_FLASH) #indicate a flash write coming through - self.H.__sendByte__(page) #send the page number. 20 pages with 2K bytes each - self.H.__sendByte__(location) #send the location - self.H.fd.write(string_to_write) - time.sleep(0.1) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def write_bulk_flash(self,location,data): - """ + try: + while (len(string_to_write) < 16): string_to_write += '.' + self.H.__sendByte__(CP.FLASH) + self.H.__sendByte__(CP.WRITE_FLASH) # indicate a flash write coming through + self.H.__sendByte__(page) # send the page number. 20 pages with 2K bytes each + self.H.__sendByte__(location) # send the location + self.H.fd.write(string_to_write) + time.sleep(0.1) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def write_bulk_flash(self, location, data): + """ write a byte array to the entire flash page. Erases any other data DO NOT USE THIS UNLESS YOU'RE ABSOLUTELY SURE YOU KNOW THIS! YOU MAY END UP OVERWRITING THE CALIBRATION DATA, AND WILL HAVE TO GO THROUGH THE TROUBLE OF GETTING IT FROM THE MANUFACTURER AND REFLASHING IT. - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ================ ============================================================================================ - **Arguments** + **Arguments** ================ ============================================================================================ location Block number. 0-20. each block is 2kB. bytearray Array to dump onto flash. Max size 2048 bytes ================ ============================================================================================ """ - if(type(data)==str):data = [ord(a) for a in data] - if len(data)%2==1:data.append(0) - try: - #self.__print__('Dumping at',location,',',len(bytearray),' bytes into flash',bytearray[:10]) - self.H.__sendByte__(CP.FLASH) - self.H.__sendByte__(CP.WRITE_BULK_FLASH) #indicate a flash write coming through - self.H.__sendInt__(len(data)) #send the length - self.H.__sendByte__(location) - for n in range(len(data)): - self.H.__sendByte__(data[n]) - #Printer('Bytes written: %d'%(n+1)) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - #verification by readback - tmp=[ord(a) for a in self.read_bulk_flash(location,len(data))] - print ('Verification done',tmp == data) - if tmp!=data: raise Exception('Verification by readback failed') - - #-------------------------------------------------------------------------------------------------------------------# - - #|===============================================WAVEGEN SECTION====================================================| - #|This section has commands related to waveform generators W1, W2, PWM outputs, servo motor control etc. | - #-------------------------------------------------------------------------------------------------------------------# - - def set_wave(self,chan,freq): - """ + if (type(data) == str): data = [ord(a) for a in data] + if len(data) % 2 == 1: data.append(0) + try: + # self.__print__('Dumping at',location,',',len(bytearray),' bytes into flash',bytearray[:10]) + self.H.__sendByte__(CP.FLASH) + self.H.__sendByte__(CP.WRITE_BULK_FLASH) # indicate a flash write coming through + self.H.__sendInt__(len(data)) # send the length + self.H.__sendByte__(location) + for n in range(len(data)): + self.H.__sendByte__(data[n]) + # Printer('Bytes written: %d'%(n+1)) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + # verification by readback + tmp = [ord(a) for a in self.read_bulk_flash(location, len(data))] + print('Verification done', tmp == data) + if tmp != data: raise Exception('Verification by readback failed') + + # -------------------------------------------------------------------------------------------------------------------# + + # |===============================================WAVEGEN SECTION====================================================| + # |This section has commands related to waveform generators W1, W2, PWM outputs, servo motor control etc. | + # -------------------------------------------------------------------------------------------------------------------# + + def set_wave(self, chan, freq): + """ Set the frequency of wavegen - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ chan Channel to set frequency for. W1 or W2 - frequency Frequency to set on wave generator + frequency Frequency to set on wave generator ============== ============================================================================================ - - + + :return: frequency """ - if chan=='W1': - self.set_w1(freq) - elif chan=='W2': - self.set_w2(freq) + if chan == 'W1': + self.set_w1(freq) + elif chan == 'W2': + self.set_w2(freq) - def set_sine1(self,freq): - """ + def set_sine1(self, freq): + """ Set the frequency of wavegen 1 after setting its waveform type to sinusoidal - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ - frequency Frequency to set on wave generator 1. + frequency Frequency to set on wave generator 1. ============== ============================================================================================ - - + + :return: frequency """ - return self.set_w1(freq,'sine') + return self.set_w1(freq, 'sine') - def set_sine2(self,freq): - """ + def set_sine2(self, freq): + """ Set the frequency of wavegen 2 after setting its waveform type to sinusoidal - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ - frequency Frequency to set on wave generator 1. + frequency Frequency to set on wave generator 1. ============== ============================================================================================ - - + + :return: frequency """ - return self.set_w2(freq,'sine') + return self.set_w2(freq, 'sine') - def set_w1(self,freq,waveType=None): - """ + def set_w1(self, freq, waveType=None): + """ Set the frequency of wavegen 1 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ - frequency Frequency to set on wave generator 1. + frequency Frequency to set on wave generator 1. waveType 'sine','tria' . Default : Do not reload table. and use last set table ============== ============================================================================================ - - - :return: frequency - """ - if freq<0.1: - self.__print__('freq too low') - return 0 - elif freq<1100: - HIGHRES=1 - table_size = 512 - else: - HIGHRES=0 - table_size = 32 - - if waveType: #User wants to set a particular waveform type. sine or tria - if waveType in ['sine','tria']: - if(self.WType['W1']!=waveType): - self.load_equation('W1',waveType) - else: - print ('Not a valid waveform. try sine or tria') - - p=[1,8,64,256] - prescaler=0 - while prescaler<=3: - wavelength = int(round(64e6/freq/p[prescaler]/table_size)) - freq = (64e6/wavelength/p[prescaler]/table_size) - if wavelength<65525: break - prescaler+=1 - if prescaler==4: - self.__print__('out of range') - return 0 - - - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_SINE1) - self.H.__sendByte__(HIGHRES|(prescaler<<1)) #use larger table for low frequencies - self.H.__sendInt__(wavelength-1) - self.H.__get_ack__() - #if self.sine1freq == None: time.sleep(0.2) - self.sine1freq = freq - return freq - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def set_w2(self,freq,waveType=None): + :return: frequency """ + if freq < 0.1: + self.__print__('freq too low') + return 0 + elif freq < 1100: + HIGHRES = 1 + table_size = 512 + else: + HIGHRES = 0 + table_size = 32 + + if waveType: # User wants to set a particular waveform type. sine or tria + if waveType in ['sine', 'tria']: + if (self.WType['W1'] != waveType): + self.load_equation('W1', waveType) + else: + print('Not a valid waveform. try sine or tria') + + p = [1, 8, 64, 256] + prescaler = 0 + while prescaler <= 3: + wavelength = int(round(64e6 / freq / p[prescaler] / table_size)) + freq = (64e6 / wavelength / p[prescaler] / table_size) + if wavelength < 65525: break + prescaler += 1 + if prescaler == 4: + self.__print__('out of range') + return 0 + + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_SINE1) + self.H.__sendByte__(HIGHRES | (prescaler << 1)) # use larger table for low frequencies + self.H.__sendInt__(wavelength - 1) + self.H.__get_ack__() + # if self.sine1freq == None: time.sleep(0.2) + self.sine1freq = freq + return freq + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def set_w2(self, freq, waveType=None): + """ Set the frequency of wavegen 2 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ - frequency Frequency to set on wave generator 1. + frequency Frequency to set on wave generator 1. ============== ============================================================================================ - - :return: frequency - """ - if freq<0.1: - self.__print__('freq too low') - return 0 - elif freq<1100: - HIGHRES=1 - table_size = 512 - else: - HIGHRES=0 - table_size = 32 - - if waveType: #User wants to set a particular waveform type. sine or tria - if waveType in ['sine','tria']: - if(self.WType['W2']!=waveType): - self.load_equation('W2',waveType) - else: - print ('Not a valid waveform. try sine or tria') - - - p=[1,8,64,256] - prescaler=0 - while prescaler<=3: - wavelength = int(round(64e6/freq/p[prescaler]/table_size)) - freq = (64e6/wavelength/p[prescaler]/table_size) - if wavelength<65525: break - prescaler+=1 - if prescaler==4: - self.__print__('out of range') - return 0 - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_SINE2) - self.H.__sendByte__(HIGHRES|(prescaler<<1)) #use larger table for low frequencies - self.H.__sendInt__(wavelength-1) - self.H.__get_ack__() - #if self.sine2freq == None: time.sleep(0.2) - self.sine2freq = freq - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - return freq - - def readbackWaveform(self,chan): + :return: frequency """ + if freq < 0.1: + self.__print__('freq too low') + return 0 + elif freq < 1100: + HIGHRES = 1 + table_size = 512 + else: + HIGHRES = 0 + table_size = 32 + + if waveType: # User wants to set a particular waveform type. sine or tria + if waveType in ['sine', 'tria']: + if (self.WType['W2'] != waveType): + self.load_equation('W2', waveType) + else: + print('Not a valid waveform. try sine or tria') + + p = [1, 8, 64, 256] + prescaler = 0 + while prescaler <= 3: + wavelength = int(round(64e6 / freq / p[prescaler] / table_size)) + freq = (64e6 / wavelength / p[prescaler] / table_size) + if wavelength < 65525: break + prescaler += 1 + if prescaler == 4: + self.__print__('out of range') + return 0 + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_SINE2) + self.H.__sendByte__(HIGHRES | (prescaler << 1)) # use larger table for low frequencies + self.H.__sendInt__(wavelength - 1) + self.H.__get_ack__() + # if self.sine2freq == None: time.sleep(0.2) + self.sine2freq = freq + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + return freq + + def readbackWaveform(self, chan): + """ Set the frequency of wavegen 1 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ chan Any of W1,W2,SQR1,SQR2,SQR3,SQR4 ============== ============================================================================================ - - + + :return: frequency """ - if chan=='W1':return self.sine1freq - elif chan=='W2':return self.sine2freq - elif chan[:3]=='SQR':return self.sqrfreq.get(chan,None) + if chan == 'W1': + return self.sine1freq + elif chan == 'W2': + return self.sine2freq + elif chan[:3] == 'SQR': + return self.sqrfreq.get(chan, None) - def set_waves(self,freq,phase,f2=None): - """ + def set_waves(self, freq, phase, f2=None): + """ Set the frequency of wavegen - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ frequency Frequency to set on both wave generators phase Phase difference between the two. 0-360 degrees f2 Only specify if you require two separate frequencies to be set ============== ============================================================================================ - + :return: frequency """ - if f2: - freq2 = f2 - else: - freq2 = freq - - if freq<0.1: - self.__print__('freq1 too low') - return 0 - elif freq<1100: - HIGHRES=1 - table_size = 512 - else: - HIGHRES=0 - table_size = 32 - - if freq2<0.1: - self.__print__('freq2 too low') - return 0 - elif freq2<1100: - HIGHRES2=1 - table_size2 = 512 - else: - HIGHRES2=0 - table_size2 = 32 - if freq<1. or freq2<1. : - self.__print__('extremely low frequencies will have reduced amplitudes due to AC coupling restrictions') - - - p=[1,8,64,256] - prescaler1=0 - while prescaler1<=3: - wavelength = int(round(64e6/freq/p[prescaler1]/table_size)) - retfreq = (64e6/wavelength/p[prescaler1]/table_size) - if wavelength<65525: break - prescaler1+=1 - if prescaler1==4: - self.__print__('#1 out of range') - return 0 - - p=[1,8,64,256] - prescaler2=0 - while prescaler2<=3: - wavelength2 = int(round(64e6/freq2/p[prescaler2]/table_size2)) - retfreq2 = (64e6/wavelength2/p[prescaler2]/table_size2) - if wavelength2<65525: break - prescaler2+=1 - if prescaler2==4: - self.__print__('#2 out of range') - return 0 - - phase_coarse = int(table_size2*( phase)/360. ) - phase_fine = int(wavelength2*(phase - (phase_coarse)*360./table_size2)/(360./table_size2)) - - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_BOTH_WG) - - self.H.__sendInt__(wavelength-1) #not really wavelength. time between each datapoint - self.H.__sendInt__(wavelength2-1) #not really wavelength. time between each datapoint - self.H.__sendInt__(phase_coarse) #table position for phase adjust - self.H.__sendInt__(phase_fine) #timer delay / fine phase adjust - - self.H.__sendByte__((prescaler2<<4)|(prescaler1<<2)|(HIGHRES2<<1)|(HIGHRES)) #use larger table for low frequencies - self.H.__get_ack__() - #print ( phase_coarse,phase_fine) - #if self.sine1freq == None or self.sine2freq==None : time.sleep(0.2) - self.sine1freq = retfreq - self.sine2freq = retfreq2 - - return retfreq - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def load_equation(self,chan,function,span=None,**kwargs): - ''' + if f2: + freq2 = f2 + else: + freq2 = freq + + if freq < 0.1: + self.__print__('freq1 too low') + return 0 + elif freq < 1100: + HIGHRES = 1 + table_size = 512 + else: + HIGHRES = 0 + table_size = 32 + + if freq2 < 0.1: + self.__print__('freq2 too low') + return 0 + elif freq2 < 1100: + HIGHRES2 = 1 + table_size2 = 512 + else: + HIGHRES2 = 0 + table_size2 = 32 + if freq < 1. or freq2 < 1.: + self.__print__('extremely low frequencies will have reduced amplitudes due to AC coupling restrictions') + + p = [1, 8, 64, 256] + prescaler1 = 0 + while prescaler1 <= 3: + wavelength = int(round(64e6 / freq / p[prescaler1] / table_size)) + retfreq = (64e6 / wavelength / p[prescaler1] / table_size) + if wavelength < 65525: break + prescaler1 += 1 + if prescaler1 == 4: + self.__print__('#1 out of range') + return 0 + + p = [1, 8, 64, 256] + prescaler2 = 0 + while prescaler2 <= 3: + wavelength2 = int(round(64e6 / freq2 / p[prescaler2] / table_size2)) + retfreq2 = (64e6 / wavelength2 / p[prescaler2] / table_size2) + if wavelength2 < 65525: break + prescaler2 += 1 + if prescaler2 == 4: + self.__print__('#2 out of range') + return 0 + + phase_coarse = int(table_size2 * (phase) / 360.) + phase_fine = int(wavelength2 * (phase - (phase_coarse) * 360. / table_size2) / (360. / table_size2)) + + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_BOTH_WG) + + self.H.__sendInt__(wavelength - 1) # not really wavelength. time between each datapoint + self.H.__sendInt__(wavelength2 - 1) # not really wavelength. time between each datapoint + self.H.__sendInt__(phase_coarse) # table position for phase adjust + self.H.__sendInt__(phase_fine) # timer delay / fine phase adjust + + self.H.__sendByte__((prescaler2 << 4) | (prescaler1 << 2) | (HIGHRES2 << 1) | ( + HIGHRES)) # use larger table for low frequencies + self.H.__get_ack__() + # print ( phase_coarse,phase_fine) + # if self.sine1freq == None or self.sine2freq==None : time.sleep(0.2) + self.sine1freq = retfreq + self.sine2freq = retfreq2 + + return retfreq + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def load_equation(self, chan, function, span=None, **kwargs): + ''' Load an arbitrary waveform to the waveform generators - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ chan The waveform generator to alter. W1 or W2 function A function that will be used to generate the datapoints span the range of values in which to evaluate the given function ============== ============================================================================================ - + .. code-block:: python - - fn = lambda x:abs(x-50) #Triangular waveform + + fn = lambda x:abs(x-50) #Triangular waveform self.I.load_waveform('W1',fn,[0,100]) #Load triangular wave to wavegen 1 - + #Load sinusoidal wave to wavegen 2 self.I.load_waveform('W2',np.sin,[0,2*np.pi]) ''' - if function=='sine' or function==np.sin: - function = np.sin; span = [0,2*np.pi] - self.WType[chan] = 'sine' - elif function=='tria': - function = lambda x: abs(x%4-2)-1 - span = [-1,3] - self.WType[chan] = 'tria' - else: - self.WType[chan] = 'arbit' - - self.__print__('reloaded wave equation for %s : %s'%( chan, self.WType[chan]) ) - x1=np.linspace(span[0],span[1],512+1)[:-1] - y1=function(x1) - self.load_table(chan,y1,self.WType[chan],**kwargs) - - def load_table(self,chan,points,mode='arbit',**kwargs): - ''' + if function == 'sine' or function == np.sin: + function = np.sin + span = [0, 2 * np.pi] + self.WType[chan] = 'sine' + elif function == 'tria': + function = lambda x: abs(x % 4 - 2) - 1 + span = [-1, 3] + self.WType[chan] = 'tria' + else: + self.WType[chan] = 'arbit' + + self.__print__('reloaded wave equation for %s : %s' % (chan, self.WType[chan])) + x1 = np.linspace(span[0], span[1], 512 + 1)[:-1] + y1 = function(x1) + self.load_table(chan, y1, self.WType[chan], **kwargs) + + def load_table(self, chan, points, mode='arbit', **kwargs): + ''' Load an arbitrary waveform table to the waveform generators - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ chan The waveform generator to alter. 'W1' or 'W2' points A list of 512 datapoints exactly mode Optional argument. Type of waveform. default value 'arbit'. accepts 'sine', 'tria' ============== ============================================================================================ - + example:: - + >>> self.I.load_waveform_table(1,range(512)) #Load sawtooth wave to wavegen 1 ''' - self.__print__('reloaded wave table for %s : %s'%( chan, mode) ) - self.WType[chan] = mode - chans = ['W1', 'W2'] - if chan in chans: - num = chans.index(chan)+1 - else: - print('Channel does not exist. Try W2 or W2') - return - - - #Normalize and scale . - # y1 = array with 512 points between 0 and 512 - # y2 = array with 32 points between 0 and 64 - - amp = kwargs.get('amp',0.95) - LARGE_MAX = 511*amp # A form of amplitude control. This decides the max PWM duty cycle out of 512 clocks - SMALL_MAX = 63 *amp # Max duty cycle out of 64 clocks - y1=np.array(points) - y1-=min(y1) - y1=y1/float(max(y1)) - y1=1.-y1 - y1 = list(np.int16(np.round( LARGE_MAX - LARGE_MAX*y1 ))) - - y2=np.array(points[::16]) - y2-=min(y2) - y2 = y2/float(max(y2)) - y2=1.- y2 - y2 = list(np.int16(np.round( SMALL_MAX - SMALL_MAX*y2 ))) - - try: - self.H.__sendByte__(CP.WAVEGEN) - if(num==1):self.H.__sendByte__(CP.LOAD_WAVEFORM1) - elif(num==2):self.H.__sendByte__(CP.LOAD_WAVEFORM2) - - #print(max(y1),max(y2)) - for a in y1: - self.H.__sendInt__(a) - #time.sleep(0.001) - for a in y2: - self.H.__sendByte__(CP.Byte.pack(a)) - #time.sleep(0.001) - time.sleep(0.01) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def sqr1(self,freq,duty_cycle=50,onlyPrepare=False): - """ + self.__print__('reloaded wave table for %s : %s' % (chan, mode)) + self.WType[chan] = mode + chans = ['W1', 'W2'] + if chan in chans: + num = chans.index(chan) + 1 + else: + print('Channel does not exist. Try W2 or W2') + return + + # Normalize and scale . + # y1 = array with 512 points between 0 and 512 + # y2 = array with 32 points between 0 and 64 + + amp = kwargs.get('amp', 0.95) + LARGE_MAX = 511 * amp # A form of amplitude control. This decides the max PWM duty cycle out of 512 clocks + SMALL_MAX = 63 * amp # Max duty cycle out of 64 clocks + y1 = np.array(points) + y1 -= min(y1) + y1 = y1 / float(max(y1)) + y1 = 1. - y1 + y1 = list(np.int16(np.round(LARGE_MAX - LARGE_MAX * y1))) + + y2 = np.array(points[::16]) + y2 -= min(y2) + y2 = y2 / float(max(y2)) + y2 = 1. - y2 + y2 = list(np.int16(np.round(SMALL_MAX - SMALL_MAX * y2))) + + try: + self.H.__sendByte__(CP.WAVEGEN) + if (num == 1): + self.H.__sendByte__(CP.LOAD_WAVEFORM1) + elif (num == 2): + self.H.__sendByte__(CP.LOAD_WAVEFORM2) + + # print(max(y1),max(y2)) + for a in y1: + self.H.__sendInt__(a) + # time.sleep(0.001) + for a in y2: + self.H.__sendByte__(CP.Byte.pack(a)) + # time.sleep(0.001) + time.sleep(0.01) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def sqr1(self, freq, duty_cycle=50, onlyPrepare=False): + """ Set the frequency of sqr1 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ frequency Frequency duty_cycle Percentage of high time ============== ============================================================================================ """ - if freq==0 or duty_cycle==0 : return None - if freq>10e6: - print ('Frequency is greater than 10MHz. Please use map_reference_clock for 16 & 32MHz outputs') - return 0 - - p=[1,8,64,256] - prescaler=0 - while prescaler<=3: - wavelength = int(64e6/freq/p[prescaler]) - if wavelength<65525: break - prescaler+=1 - if prescaler==4 or wavelength==0: - self.__print__('out of range') - return 0 - high_time = wavelength*duty_cycle/100. - self.__print__(wavelength,':',high_time,':',prescaler) - if onlyPrepare: self.set_state(SQR1=False) - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_SQR1) - self.H.__sendInt__(int(round(wavelength))) - self.H.__sendInt__(int(round(high_time))) - if onlyPrepare: prescaler |= 0x4 # Instruct hardware to prepare the square wave, but do not connect it to the output. - self.H.__sendByte__(prescaler) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - self.sqrfreq['SQR1']=64e6/wavelength/p[prescaler&0x3] - return self.sqrfreq['SQR1'] - - def sqr1_pattern(self,timing_array): - """ - output a preset sqr1 frequency in fixed intervals. Can be used for sending IR signals that are packets + if freq == 0 or duty_cycle == 0: return None + if freq > 10e6: + print('Frequency is greater than 10MHz. Please use map_reference_clock for 16 & 32MHz outputs') + return 0 + + p = [1, 8, 64, 256] + prescaler = 0 + while prescaler <= 3: + wavelength = int(64e6 / freq / p[prescaler]) + if wavelength < 65525: break + prescaler += 1 + if prescaler == 4 or wavelength == 0: + self.__print__('out of range') + return 0 + high_time = wavelength * duty_cycle / 100. + self.__print__(wavelength, ':', high_time, ':', prescaler) + if onlyPrepare: self.set_state(SQR1=False) + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_SQR1) + self.H.__sendInt__(int(round(wavelength))) + self.H.__sendInt__(int(round(high_time))) + if onlyPrepare: prescaler |= 0x4 # Instruct hardware to prepare the square wave, but do not connect it to the output. + self.H.__sendByte__(prescaler) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + self.sqrfreq['SQR1'] = 64e6 / wavelength / p[prescaler & 0x3] + return self.sqrfreq['SQR1'] + + def sqr1_pattern(self, timing_array): + """ + output a preset sqr1 frequency in fixed intervals. Can be used for sending IR signals that are packets of 38KHz pulses. refer to the example .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ timing_array A list of on & off times in uS units ============== ============================================================================================ @@ -3367,63 +3415,63 @@ def sqr1_pattern(self,timing_array): I.sqr1(38e3 , 50, True ) # Prepare a 38KHz, 50% square wave. Do not output it yet I.sqr1_pattern([1000,1000,1000,1000,1000]) #On:1mS (38KHz packet), Off:1mS, On:1mS (38KHz packet), Off:1mS, On:1mS (38KHz packet), Off: indefinitely.. """ - self.fill_buffer(self.MAX_SAMPLES/2,timing_array) #Load the array to the ADCBuffer(second half) - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SQR1_PATTERN) - self.H.__sendInt__(len(timing_array)) - time.sleep(sum(timing_array)*1e-6) #Sleep for the whole duration - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - return True - - def sqr2(self,freq,duty_cycle): - """ + self.fill_buffer(self.MAX_SAMPLES / 2, timing_array) # Load the array to the ADCBuffer(second half) + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SQR1_PATTERN) + self.H.__sendInt__(len(timing_array)) + time.sleep(sum(timing_array) * 1e-6) # Sleep for the whole duration + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + return True + + def sqr2(self, freq, duty_cycle): + """ Set the frequency of sqr2 .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ frequency Frequency duty_cycle Percentage of high time ============== ============================================================================================ """ - p=[1,8,64,256] - prescaler=0 - while prescaler<=3: - wavelength = 64e6/freq/p[prescaler] - if wavelength<65525: break - prescaler+=1 - - if prescaler==4 or wavelength==0: - self.__print__('out of range') - return 0 - try: - high_time = wavelength*duty_cycle/100. - self.__print__(wavelength,high_time,prescaler) - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_SQR2) - self.H.__sendInt__(int(round(wavelength))) - self.H.__sendInt__(int(round(high_time))) - self.H.__sendByte__(prescaler) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - self.sqrfreq['SQR2']=64e6/wavelength/p[prescaler&0x3] - return self.sqrfreq['SQR2'] - - def set_sqrs(self,wavelength,phase,high_time1,high_time2,prescaler=1): - """ + p = [1, 8, 64, 256] + prescaler = 0 + while prescaler <= 3: + wavelength = 64e6 / freq / p[prescaler] + if wavelength < 65525: break + prescaler += 1 + + if prescaler == 4 or wavelength == 0: + self.__print__('out of range') + return 0 + try: + high_time = wavelength * duty_cycle / 100. + self.__print__(wavelength, high_time, prescaler) + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_SQR2) + self.H.__sendInt__(int(round(wavelength))) + self.H.__sendInt__(int(round(high_time))) + self.H.__sendByte__(prescaler) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + self.sqrfreq['SQR2'] = 64e6 / wavelength / p[prescaler & 0x3] + return self.sqrfreq['SQR2'] + + def set_sqrs(self, wavelength, phase, high_time1, high_time2, prescaler=1): + """ Set the frequency of sqr1,sqr2, with phase shift .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ wavelength Number of 64Mhz/prescaler clock cycles per wave phase Clock cycles between rising edges of SQR1 and SQR2 @@ -3431,28 +3479,28 @@ def set_sqrs(self,wavelength,phase,high_time1,high_time2,prescaler=1): high time2 Clock cycles for which SQR2 must be HIGH prescaler 0,1,2. Divides the 64Mhz clock by 8,64, or 256 ============== ============================================================================================ - - """ - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SET_SQRS) - self.H.__sendInt__(wavelength) - self.H.__sendInt__(phase) - self.H.__sendInt__(high_time1) - self.H.__sendInt__(high_time2) - self.H.__sendByte__(prescaler) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def sqrPWM(self,freq,h0,p1,h1,p2,h2,p3,h3,**kwargs): """ + try: + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SET_SQRS) + self.H.__sendInt__(wavelength) + self.H.__sendInt__(phase) + self.H.__sendInt__(high_time1) + self.H.__sendInt__(high_time2) + self.H.__sendByte__(prescaler) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def sqrPWM(self, freq, h0, p1, h1, p2, h2, p3, h3, **kwargs): + """ Initialize phase correlated square waves on SQR1,SQR2,SQR3,SQR4 - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ freq Frequency in Hertz h0 Duty Cycle for SQR1 (0-1) @@ -3463,61 +3511,60 @@ def sqrPWM(self,freq,h0,p1,h1,p2,h2,p3,h3,**kwargs): p3 Phase shift for OD2 (0-1) h3 Duty Cycle for OD2 (0-1) ============== ============================================================================================ - - """ - if freq==0 or h0==0 or h1==0 or h2==0 or h3==0: return 0 - if freq>10e6: - print ('Frequency is greater than 10MHz. Please use map_reference_clock for 16 & 32MHz outputs') - return 0 - - p=[1,8,64,256] - prescaler=0 - while prescaler<=3: - wavelength = int(64e6/freq/p[prescaler]) - if wavelength<65525: break - prescaler+=1 - if prescaler==4 or wavelength==0: - self.__print__('out of range') - return 0 - - if not kwargs.get('pulse',False):prescaler|= (1<<5) - - A1 = int(p1%1*wavelength) - B1 = int((h1+p1)%1*wavelength) - A2 = int(p2%1*wavelength) - B2 = int((h2+p2)%1*wavelength) - A3 = int(p3%1*wavelength) - B3 = int((h3+p3)%1*wavelength) - #self.__print__(p1,h1,p2,h2,p3,h3) - #print(wavelength,int(wavelength*h0),A1,B1,A2,B2,A3,B3,prescaler) - - - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SQR4) - self.H.__sendInt__(wavelength-1) - self.H.__sendInt__(int(wavelength*h0)-1) - try: - self.H.__sendInt__(max(0,A1-1)) - self.H.__sendInt__(max(1,B1-1)) - self.H.__sendInt__(max(0,A2-1)) - self.H.__sendInt__(max(1,B2-1)) - self.H.__sendInt__(max(0,A3-1)) - self.H.__sendInt__(max(1,B3-1)) - self.H.__sendByte__(prescaler) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - for a in ['SQR1','SQR2','SQR3','SQR4']:self.sqrfreq[a]=64e6/wavelength/p[prescaler&0x3] - return 64e6/wavelength/p[prescaler&0x3] - def map_reference_clock(self,scaler,*args): """ + if freq == 0 or h0 == 0 or h1 == 0 or h2 == 0 or h3 == 0: return 0 + if freq > 10e6: + print('Frequency is greater than 10MHz. Please use map_reference_clock for 16 & 32MHz outputs') + return 0 + + p = [1, 8, 64, 256] + prescaler = 0 + while prescaler <= 3: + wavelength = int(64e6 / freq / p[prescaler]) + if wavelength < 65525: break + prescaler += 1 + if prescaler == 4 or wavelength == 0: + self.__print__('out of range') + return 0 + + if not kwargs.get('pulse', False): prescaler |= (1 << 5) + + A1 = int(p1 % 1 * wavelength) + B1 = int((h1 + p1) % 1 * wavelength) + A2 = int(p2 % 1 * wavelength) + B2 = int((h2 + p2) % 1 * wavelength) + A3 = int(p3 % 1 * wavelength) + B3 = int((h3 + p3) % 1 * wavelength) + # self.__print__(p1,h1,p2,h2,p3,h3) + # print(wavelength,int(wavelength*h0),A1,B1,A2,B2,A3,B3,prescaler) + + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SQR4) + self.H.__sendInt__(wavelength - 1) + self.H.__sendInt__(int(wavelength * h0) - 1) + try: + self.H.__sendInt__(max(0, A1 - 1)) + self.H.__sendInt__(max(1, B1 - 1)) + self.H.__sendInt__(max(0, A2 - 1)) + self.H.__sendInt__(max(1, B2 - 1)) + self.H.__sendInt__(max(0, A3 - 1)) + self.H.__sendInt__(max(1, B3 - 1)) + self.H.__sendByte__(prescaler) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + for a in ['SQR1', 'SQR2', 'SQR3', 'SQR4']: self.sqrfreq[a] = 64e6 / wavelength / p[prescaler & 0x3] + return 64e6 / wavelength / p[prescaler & 0x3] + + def map_reference_clock(self, scaler, *args): + """ Map the internal oscillator output to SQR1,SQR2,SQR3,SQR4 or WAVEGEN The output frequency is 128/(1< 128MHz * 1 -> 64MHz * 2 -> 32MHz @@ -3527,161 +3574,135 @@ def map_reference_clock(self,scaler,*args): * 15 ->128./32768 MHz example:: - + >>> I.map_reference_clock(2,'SQR1','SQR2') - + outputs 32 MHz on SQR1, SQR2 pins - + .. note:: if you change the reference clock for 'wavegen' , the external waveform generator(AD9833) resolution and range will also change. default frequency for 'wavegen' is 16MHz. Setting to 1MHz will give you 16 times better resolution, but a usable range of 0Hz to about 100KHz instead of the original 2MHz. - - """ - try: - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.MAP_REFERENCE) - chan=0 - if 'SQR1' in args:chan|=1 - if 'SQR2' in args:chan|=2 - if 'SQR3' in args:chan|=4 - if 'SQR4' in args:chan|=8 - if 'WAVEGEN' in args:chan|=16 - self.H.__sendByte__(chan) - self.H.__sendByte__(scaler) - if 'WAVEGEN' in args: self.DDS_CLOCK = 128e6/(1<>> I.WS2812B([[10,0,0],[0,10,10],[10,0,10]]) #sets red, cyan, magenta to three daisy chained LEDs @@ -3689,233 +3710,240 @@ def WS2812B(self,cols,output='CS1'): """ - if output=='CS1':pin = CP.SET_RGB1 - elif output=='CS2':pin = CP.SET_RGB2 - elif output=='SQR1':pin = CP.SET_RGB3 - else: - print('invalid output') - return - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(pin) - self.H.__sendByte__(len(cols)*3) - for col in cols: - #R=reverse_bits(int(col[0]));G=reverse_bits(int(col[1]));B=reverse_bits(int(col[2])) - R=col[0];G=col[1];B=col[2]; - self.H.__sendByte__(G); self.H.__sendByte__(R);self.H.__sendByte__(B) - #print(col) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - #-------------------------------------------------------------------------------------------------------------------# - - #|======================================READ PROGRAM AND DATA ADDRESSES=============================================| - #|Direct access to RAM and FLASH | - #-------------------------------------------------------------------------------------------------------------------# - - def read_program_address(self,address): - """ + if output == 'CS1': + pin = CP.SET_RGB1 + elif output == 'CS2': + pin = CP.SET_RGB2 + elif output == 'SQR1': + pin = CP.SET_RGB3 + else: + print('invalid output') + return + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(pin) + self.H.__sendByte__(len(cols) * 3) + for col in cols: + # R=reverse_bits(int(col[0]));G=reverse_bits(int(col[1]));B=reverse_bits(int(col[2])) + R = col[0] + G = col[1] + B = col[2] + self.H.__sendByte__(G) + self.H.__sendByte__(R) + self.H.__sendByte__(B) + # print(col) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + # -------------------------------------------------------------------------------------------------------------------# + + # |======================================READ PROGRAM AND DATA ADDRESSES=============================================| + # |Direct access to RAM and FLASH | + # -------------------------------------------------------------------------------------------------------------------# + + def read_program_address(self, address): + """ Reads and returns the value stored at the specified address in program memory .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ address Address to read from. Refer to PIC24EP64GP204 programming manual ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.READ_PROGRAM_ADDRESS) - self.H.__sendInt__(address&0xFFFF) - self.H.__sendInt__((address>>16)&0xFFFF) - v=self.H.__getInt__() - self.H.__get_ack__() - return v - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def device_id(self): - try: - a=self.read_program_address(0x800FF8) - b=self.read_program_address(0x800FFa) - c=self.read_program_address(0x800FFc) - d=self.read_program_address(0x800FFe) - val = d|(c<<16)|(b<<32)|(a<<48) - self.__print__(a,b,c,d,hex(val)) - return val - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __write_program_address__(self,address,value): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.READ_PROGRAM_ADDRESS) + self.H.__sendInt__(address & 0xFFFF) + self.H.__sendInt__((address >> 16) & 0xFFFF) + v = self.H.__getInt__() + self.H.__get_ack__() + return v + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def device_id(self): + try: + a = self.read_program_address(0x800FF8) + b = self.read_program_address(0x800FFa) + c = self.read_program_address(0x800FFc) + d = self.read_program_address(0x800FFe) + val = d | (c << 16) | (b << 32) | (a << 48) + self.__print__(a, b, c, d, hex(val)) + return val + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __write_program_address__(self, address, value): + """ Writes a value to the specified address in program memory. Disabled in firmware. .. tabularcolumns:: |p{3cm}|p{11cm}| ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ address Address to write to. Refer to PIC24EP64GP204 programming manual - Do Not Screw around with this. It won't work anyway. + Do Not Screw around with this. It won't work anyway. ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.WRITE_PROGRAM_ADDRESS) - self.H.__sendInt__(address&0xFFFF) - self.H.__sendInt__((address>>16)&0xFFFF) - self.H.__sendInt__(value) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.WRITE_PROGRAM_ADDRESS) + self.H.__sendInt__(address & 0xFFFF) + self.H.__sendInt__((address >> 16) & 0xFFFF) + self.H.__sendInt__(value) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def read_data_address(self,address): - """ + def read_data_address(self, address): + """ Reads and returns the value stored at the specified address in RAM .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ address Address to read from. Refer to PIC24EP64GP204 programming manual| ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.READ_DATA_ADDRESS) - self.H.__sendInt__(address&0xFFFF) - v=self.H.__getInt__() - self.H.__get_ack__() - return v - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def __write_data_address__(self,address,value): - """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.READ_DATA_ADDRESS) + self.H.__sendInt__(address & 0xFFFF) + v = self.H.__getInt__() + self.H.__get_ack__() + return v + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def __write_data_address__(self, address, value): + """ Writes a value to the specified address in RAM .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ address Address to write to. Refer to PIC24EP64GP204 programming manual| ============== ============================================================================================ """ - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.WRITE_DATA_ADDRESS) - self.H.__sendInt__(address&0xFFFF) - self.H.__sendInt__(value) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.WRITE_DATA_ADDRESS) + self.H.__sendInt__(address & 0xFFFF) + self.H.__sendInt__(value) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - #-------------------------------------------------------------------------------------------------------------------# + # -------------------------------------------------------------------------------------------------------------------# - #|==============================================MOTOR SIGNALLING====================================================| - #|Set servo motor angles via SQ1-4. Control one stepper motor using SQ1-4 | - #-------------------------------------------------------------------------------------------------------------------# + # |==============================================MOTOR SIGNALLING====================================================| + # |Set servo motor angles via SQ1-4. Control one stepper motor using SQ1-4 | + # -------------------------------------------------------------------------------------------------------------------# + def __stepperMotor__(self, steps, delay, direction): + try: + self.H.__sendByte__(CP.NONSTANDARD_IO) + self.H.__sendByte__(CP.STEPPER_MOTOR) + self.H.__sendInt__((steps << 1) | direction) + self.H.__sendInt__(delay) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) - def __stepperMotor__(self,steps,delay,direction): - try: - self.H.__sendByte__(CP.NONSTANDARD_IO) - self.H.__sendByte__(CP.STEPPER_MOTOR) - self.H.__sendInt__((steps<<1)|direction) - self.H.__sendInt__(delay) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - t=time.time() - time.sleep(steps*delay*1e-3) #convert mS to S + time.sleep(steps * delay * 1e-3) # convert mS to S - def stepForward(self,steps,delay): - """ + def stepForward(self, steps, delay): + """ Control stepper motors using SQR1-4 - + take a fixed number of steps in the forward direction with a certain delay( in milliseconds ) between each step. - - """ - self.__stepperMotor__(steps,delay,1) - def stepBackward(self,steps,delay): """ + self.__stepperMotor__(steps, delay, 1) + + def stepBackward(self, steps, delay): + """ Control stepper motors using SQR1-4 - + take a fixed number of steps in the backward direction with a certain delay( in milliseconds ) between each step. - + """ - self.__stepperMotor__(steps,delay,0) + self.__stepperMotor__(steps, delay, 0) - def servo(self,angle,chan='SQR1'): - ''' + def servo(self, angle, chan='SQR1'): + ''' Output A PWM waveform on SQR1/SQR2 corresponding to the angle specified in the arguments. This is used to operate servo motors. Tested with 9G SG-90 Servo motor. - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ angle 0-180. Angle corresponding to which the PWM waveform is generated. - chan 'SQR1' or 'SQR2'. Whether to use SQ1 or SQ2 to output the PWM waveform used by the servo + chan 'SQR1' or 'SQR2'. Whether to use SQ1 or SQ2 to output the PWM waveform used by the servo ============== ============================================================================================ ''' - if chan=='SQR1':self.sqr1(100,7.5+19.*angle/180)#100Hz - elif chan=='SQR2':self.sqr2(100,7.5+19.*angle/180)#100Hz + if chan == 'SQR1': + self.sqr1(100, 7.5 + 19. * angle / 180) # 100Hz + elif chan == 'SQR2': + self.sqr2(100, 7.5 + 19. * angle / 180) # 100Hz - def servo4(self,a1,a2,a3,a4): - """ + def servo4(self, a1, a2, a3, a4): + """ Operate Four servo motors independently using SQR1, SQR2, SQR3, SQR4. tested with SG-90 9G servos. For high current servos, please use a different power source, and a level convertor for the PWm output signals(if needed) - + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ a1 Angle to set on Servo which uses SQR1 as PWM input. [0-180] a2 Angle to set on Servo which uses SQR2 as PWM input. [0-180] a3 Angle to set on Servo which uses SQR3 as PWM input. [0-180] a4 Angle to set on Servo which uses SQR4 as PWM input. [0-180] ============== ============================================================================================ - - """ - try: - params = (1<<5)|2 #continuous waveform. prescaler 2( 1:64) - self.H.__sendByte__(CP.WAVEGEN) - self.H.__sendByte__(CP.SQR4) - self.H.__sendInt__(10000) #10mS wavelength - self.H.__sendInt__(750+int(a1*1900/180)) - self.H.__sendInt__(0) - self.H.__sendInt__(750+int(a2*1900/180)) - self.H.__sendInt__(0) - self.H.__sendInt__(750+int(a3*1900/180)) - self.H.__sendInt__(0) - self.H.__sendInt__(750+int(a4*1900/180)) - self.H.__sendByte__(params) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - def enableUartPassthrough(self,baudrate,persist=False): - ''' + """ + try: + params = (1 << 5) | 2 # continuous waveform. prescaler 2( 1:64) + self.H.__sendByte__(CP.WAVEGEN) + self.H.__sendByte__(CP.SQR4) + self.H.__sendInt__(10000) # 10mS wavelength + self.H.__sendInt__(750 + int(a1 * 1900 / 180)) + self.H.__sendInt__(0) + self.H.__sendInt__(750 + int(a2 * 1900 / 180)) + self.H.__sendInt__(0) + self.H.__sendInt__(750 + int(a3 * 1900 / 180)) + self.H.__sendInt__(0) + self.H.__sendInt__(750 + int(a4 * 1900 / 180)) + self.H.__sendByte__(params) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def enableUartPassthrough(self, baudrate, persist=False): + ''' All data received by the device is relayed to an external port(SCL[TX],SDA[RX]) after this function is called - + If a period > .5 seconds elapses between two transmit/receive events, the device resets and resumes normal mode. This timeout feature has been implemented in lieu of a hard reset option. can be used to load programs into secondary microcontrollers with bootloaders such ATMEGA, and ESP8266 - - + + .. tabularcolumns:: |p{3cm}|p{11cm}| - + ============== ============================================================================================ - **Arguments** + **Arguments** ============== ============================================================================================ baudrate BAUDRATE to use persist If set to True, the device will stay in passthrough mode until the next power cycle. @@ -3923,20 +3951,20 @@ def enableUartPassthrough(self,baudrate,persist=False): received for a period greater than one second at a time. ============== ============================================================================================ ''' - try: - self.H.__sendByte__(CP.PASSTHROUGHS) - self.H.__sendByte__(CP.PASS_UART) - self.H.__sendByte__(1 if persist else 0) - self.H.__sendInt__(int( round(((64e6/baudrate)/4)-1) )) - self.__print__('BRGVAL:',int( round(((64e6/baudrate)/4)-1) )) - time.sleep(0.1) - self.__print__('junk bytes read:',len(self.H.fd.read(100))) - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) + try: + self.H.__sendByte__(CP.PASSTHROUGHS) + self.H.__sendByte__(CP.PASS_UART) + self.H.__sendByte__(1 if persist else 0) + self.H.__sendInt__(int(round(((64e6 / baudrate) / 4) - 1))) + self.__print__('BRGVAL:', int(round(((64e6 / baudrate) / 4) - 1))) + time.sleep(0.1) + self.__print__('junk bytes read:', len(self.H.fd.read(100))) + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def estimateDistance(self): + ''' - def estimateDistance(self): - ''' - Read data from ultrasonic distance sensor HC-SR04/HC-SR05. Sensors must have separate trigger and output pins. First a 10uS pulse is output on SQR1. SQR1 must be connected to the TRIG pin on the sensor prior to use. @@ -3949,33 +3977,33 @@ def estimateDistance(self): The time between sending and receiving of the pulse packet is used to estimate the distance. If the reflecting object is either too far away or absorbs sound, less than 8 pulses may be received, and this can cause a measurement error of 25uS which corresponds to 8mm. - + Ensure 5V supply. You may set SQR2 to HIGH [ I.set_state(SQR2=True) ] , and use that as the power supply. - + returns 0 upon timeout ''' - try: - self.H.__sendByte__(CP.NONSTANDARD_IO) - self.H.__sendByte__(CP.HCSR04_HEADER) - - timeout_msb = int((0.3*64e6))>>16 - self.H.__sendInt__(timeout_msb) - - A=self.H.__getLong__() - B=self.H.__getLong__() - tmt = self.H.__getInt__() - self.H.__get_ack__() - #self.__print__(A,B) - if(tmt >= timeout_msb or B==0):return 0 - rtime = lambda t: t/64e6 - return 330.*rtime(B-A+20)/2. - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - """ + try: + self.H.__sendByte__(CP.NONSTANDARD_IO) + self.H.__sendByte__(CP.HCSR04_HEADER) + + timeout_msb = int((0.3 * 64e6)) >> 16 + self.H.__sendInt__(timeout_msb) + + A = self.H.__getLong__() + B = self.H.__getLong__() + tmt = self.H.__getInt__() + self.H.__get_ack__() + # self.__print__(A,B) + if (tmt >= timeout_msb or B == 0): return 0 + rtime = lambda t: t / 64e6 + return 330. * rtime(B - A + 20) / 2. + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + """ def TemperatureAndHumidity(self): ''' - init AM2302. + init AM2302. This effort was a waste. There are better humidity and temperature sensors available which use well documented I2C ''' try: @@ -3988,8 +4016,8 @@ def TemperatureAndHumidity(self): self.digital_channels_in_buffer=1 """ - def opticalArray(self,SS,delay,channel = 'CH3',**kwargs): - ''' + def opticalArray(self, SS, delay, channel='CH3', **kwargs): + ''' read from 3648 element optical sensor array TCD3648P from Toshiba. Experimental feature. Neither Sine waves will be available. Connect SQR1 to MS , SQR2 to MS , A0 to CHannel , and CS1(on the expansion slot) to ICG @@ -3998,91 +4026,96 @@ def opticalArray(self,SS,delay,channel = 'CH3',**kwargs): tp : clock wavelength=tp*15nS, SS=clock/4 ''' - samples=3694 - res = kwargs.get('resolution',10) - tweak = kwargs.get('tweak',1) - - try: - self.H.__sendByte__(CP.NONSTANDARD_IO) - self.H.__sendByte__(CP.TCD1304_HEADER) - if res==10:self.H.__sendByte__(self.__calcCHOSA__(channel)) #10-bit - else:self.H.__sendByte__(self.__calcCHOSA__(channel)|0x80) #12-bit - self.H.__sendByte__(tweak) #Tweak the SH low to ICG high space. =tweak*delay - self.H.__sendInt__(delay) - self.H.__sendInt__(int(SS*64)) - self.timebase = SS - self.achans[0].set_params(channel=0,length=samples,timebase=self.timebase,resolution=12 if res!=10 else 10,source=self.analogInputSources[channel]) - self.samples=samples - self.channels_in_buffer=1 - time.sleep(2*delay*1e-6) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def setUARTBAUD(self,BAUD): - try: - self.H.__sendByte__(CP.UART_2) - self.H.__sendByte__(CP.SET_BAUD) - self.H.__sendInt__(int( round(((64e6/BAUD)/4)-1) )) - self.__print__('BRG2VAL:',int( round(((64e6/BAUD)/4)-1) )) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def writeUART(self,character): - try: - self.H.__sendByte__(CP.UART_2) - self.H.__sendByte__(CP.SEND_BYTE) - self.H.__sendByte__(character) - self.H.__get_ack__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def readUART(self): - try: - self.H.__sendByte__(CP.UART_2) - self.H.__sendByte__(CP.READ_BYTE) - return self.H.__getByte__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def readUARTStatus(self): - ''' + samples = 3694 + res = kwargs.get('resolution', 10) + tweak = kwargs.get('tweak', 1) + + try: + self.H.__sendByte__(CP.NONSTANDARD_IO) + self.H.__sendByte__(CP.TCD1304_HEADER) + if res == 10: + self.H.__sendByte__(self.__calcCHOSA__(channel)) # 10-bit + else: + self.H.__sendByte__(self.__calcCHOSA__(channel) | 0x80) # 12-bit + self.H.__sendByte__(tweak) # Tweak the SH low to ICG high space. =tweak*delay + self.H.__sendInt__(delay) + self.H.__sendInt__(int(SS * 64)) + self.timebase = SS + self.achans[0].set_params(channel=0, length=samples, timebase=self.timebase, + resolution=12 if res != 10 else 10, source=self.analogInputSources[channel]) + self.samples = samples + self.channels_in_buffer = 1 + time.sleep(2 * delay * 1e-6) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def setUARTBAUD(self, BAUD): + try: + self.H.__sendByte__(CP.UART_2) + self.H.__sendByte__(CP.SET_BAUD) + self.H.__sendInt__(int(round(((64e6 / BAUD) / 4) - 1))) + self.__print__('BRG2VAL:', int(round(((64e6 / BAUD) / 4) - 1))) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def writeUART(self, character): + try: + self.H.__sendByte__(CP.UART_2) + self.H.__sendByte__(CP.SEND_BYTE) + self.H.__sendByte__(character) + self.H.__get_ack__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def readUART(self): + try: + self.H.__sendByte__(CP.UART_2) + self.H.__sendByte__(CP.READ_BYTE) + return self.H.__getByte__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def readUARTStatus(self): + ''' return available bytes in UART buffer ''' - try: - self.H.__sendByte__(CP.UART_2) - self.H.__sendByte__(CP.READ_UART2_STATUS) - return self.H.__getByte__() - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def readLog(self): - ''' - read hardware debug log. - ''' - try: - self.H.__sendByte__(CP.COMMON) - self.H.__sendByte__(CP.READ_LOG) - log = self.H.fd.readline().strip() - self.H.__get_ack__() - return log - except Exception as ex: - self.raiseException(ex, "Communication Error , Function : "+inspect.currentframe().f_code.co_name) - - def raiseException(self,ex, msg): - msg += '\n' + ex.message - #self.H.disconnect() - raise RuntimeError(msg) + try: + self.H.__sendByte__(CP.UART_2) + self.H.__sendByte__(CP.READ_UART2_STATUS) + return self.H.__getByte__() + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def readLog(self): + """ + read hardware debug log. + """ + try: + self.H.__sendByte__(CP.COMMON) + self.H.__sendByte__(CP.READ_LOG) + log = self.H.fd.readline().strip() + self.H.__get_ack__() + return log + except Exception as ex: + self.raiseException(ex, "Communication Error , Function : " + inspect.currentframe().f_code.co_name) + + def raiseException(self, ex, msg): + msg += '\n' + ex.message + # self.H.disconnect() + raise RuntimeError(msg) if __name__ == "__main__": - print("""this is not an executable file - from PSL import sciencelab - I=sciencelab.connect() - eg. - I.get_average_voltage('CH1') - """) - #I=connect(verbose = True) - #for a in range(20):print (I.get_capacitance()) - #I=connect(verbose=True,load_calibration=False) + print( + """This is not an executable file. Use this library by importing PSL in a python project +>>> from PSL import sciencelab +>>> I = sciencelab.connect() +>>> I.get_average_voltage('CH1')\n""") + I = connect(verbose=True) + t = time.time() + for a in range(100): + s = I.read_flash(3, a) + # print(s.replace('\n','.'),len(s)) + print(time.time() - t) diff --git a/PSL/sensorlist.py b/PSL/sensorlist.py index e35a708d..420cb7ab 100644 --- a/PSL/sensorlist.py +++ b/PSL/sensorlist.py @@ -1,42 +1,83 @@ -#from http://www.ladyada.net/library/i2caddr.html -sensors={ -0x0:['Could be MLX90614. Try 0x5A'], -0x13:['VCNL4000'], -0x3c:['OLED SSD1306',], -0x3d:['OLED SSD1306',], -0x48:['PN532 RFID'], -0x29:['TSL2561'], -0x39:['TSL2561'], -0x49:['TSL2561'], -0x1D:['ADXL345','MMA7455L','LSM9DSO'], -0x53:['ADXL345'], -0x5A:['MLX90614 PIR temperature'], -0x1E:['HMC5883L magnetometer','LSM303 magnetometer'], -0x77:['BMP180/GY-68 altimeter','MS5607','MS5611'], -0x68:['MPU-6050/GY-521 accel+gyro+temp','ITG3200','DS1307','DS3231'], -0x69:['ITG3200'], -0x76:['MS5607','MS5611'], -0x6B:['LSM9DSO gyro'], -0x19:['LSM303 accel'], -0x20:['MCP23008','MCP23017'], -0x21:['MCP23008','MCP23017'], -0x22:['MCP23008','MCP23017'], -0x23:['BH1750','MCP23008','MCP23017'], -0x24:['MCP23008','MCP23017'], -0x25:['MCP23008','MCP23017'], -0x26:['MCP23008','MCP23017'], -0x27:['MCP23008','MCP23017'], -0x40:['SHT21(Temp/RH)'], -0x60:['MCP4725A0 4 chan DAC (onBoard)'], -0x61:['MCP4725A0 4 chan DAC'], -0x62:['MCP4725A1 4 chan DAC'], -0x63:['MCP4725A1 4 chan DAC','Si4713'], -0x64:['MCP4725A2 4 chan DAC'], -0x65:['MCP4725A2 4 chan DAC'], -0x66:['MCP4725A3 4 chan DAC'], -0x67:['MCP4725A3 4 chan DAC'], -0x11:['Si4713'], -0x38:['FT6206 touch controller'], -0x41:['STMPE610'], +# I2C Address list from Adafruit : https://learn.adafruit.com/i2c-addresses/the-list +sensors = { + 0x00: ['Could be MLX90614. Try 0x5A'], + 0x10: ['VEML6075', 'VEML7700'], + 0x11: ['Si4713'], + 0x13: ['VCNL40x0'], + 0x18: ['MCP9808', 'LIS3DH'], + 0x19: ['MCP9808', 'LIS3DH', 'LSM303 accelerometer & magnetometer'], + 0x1A: ['MCP9808'], + 0x1B: ['MCP9808'], + 0x1C: ['MCP9808', 'MMA845x', 'FXOS8700'], + 0x1D: ['MCP9808', 'MMA845x', 'FXOS8700','ADXL345', 'MMA7455L', 'LSM9DSO'], + 0x1E: ['MCP9808', 'FXOS8700', 'LSM303', 'LSM9DSO', 'HMC5883'], + 0x1F: ['MCP9808', 'FXOS8700'], + 0x20: ['MCP23008', 'MCP23017', 'FXAS21002', 'Chirp! Water Sensor'], + 0x21: ['MCP23008', 'MCP23017', 'FXAS21002'], + 0x22: ['MCP23008', 'MCP23017'], + 0x23: ['MCP23008', 'MCP23017'], + 0x24: ['MCP23008', 'MCP23017'], + 0x25: ['MCP23008', 'MCP23017'], + 0x26: ['MCP23008', 'MCP23017', 'MSA301'], + 0x27: ['MCP23008', 'MCP23017'], + 0x28: ['BNO055', 'CAP1188', 'TSL2591'], + 0x29: ['TSL2561', 'BNO055', 'TCS34725', 'TSL2591', 'VL53L0x', 'VL6180X', 'CAP1188'], + 0x2A: ['CAP1188'], + 0x2B: ['CAP1188'], + 0x2C: ['CAP1188'], + 0x2D: ['CAP1188'], + 0x38: ['VEML6070','FT6206 touch controller'], + 0x39: ['TSL2561', 'VEML6070', 'APDS-9960'], + 0x3c: ['SSD1306 monochrome OLED', 'SSD1305 monochrome OLED'], + 0x3d: ['SSD1306 monochrome OLED', 'SSD1305 monochrome OLED'], + 0x40: ['Si7021', 'HTU21D-F', 'HDC1008','TMP007', 'TMP006', 'PCA9685', 'INA219', 'INA260'], + 0x41: ['HDC1008','TMP007', 'TMP006', 'INA219', 'INA260', 'STMPE610/STMPE811'], + 0x42: ['HDC1008','TMP007', 'TMP006', 'INA219'], + 0x43: ['HDC1008','TMP007', 'TMP006', 'INA219', 'INA260'], + 0x44: ['SHT31', 'TMP007', 'TMP006', 'ISL29125', 'INA219', 'INA260', 'STMPE610/STMPE811'], + 0x45: ['SHT31', 'TMP007', 'TMP006', 'INA219', 'INA260'], + 0x46: ['TMP007', 'TMP006', 'INA219', 'INA260'], + 0x47: ['TMP007', 'TMP006', 'INA219', 'INA260'], + 0x48: ['TMP102', 'PN532 NFS/RFID', 'ADS1115', 'INA219', 'INA260'], + 0x49: ['TSL2561', 'TMP102', 'ADS1115', 'INA219', 'INA260'], + 0x4A: ['TMP102', 'ADS1115', 'INA219', 'INA260'], + 0x4B: ['TMP102', 'ADS1115', 'INA219', 'INA260'], + 0x4C: ['INA219', 'INA260'], + 0x4D: ['INA219', 'INA260'], + 0x4E: ['INA219', 'INA260'], + 0x4F: ['INA219', 'INA260'], + 0x50: ['MB85RC'], + 0x51: ['MB85RC'], + 0x52: ['MB85RC', 'Nintendo Nunchuck controller'], + 0x53: ['MB85RC', 'ADXL345'], + 0x54: ['MB85RC'], + 0x55: ['MB85RC'], + 0x56: ['MB85RC'], + 0x57: ['MB85RC', 'MAX3010x'], + 0x58: ['TPA2016','SGP30'], + 0x5A: ['MPR121', 'CCS811', 'MLX9061x', 'DRV2605'], + 0x5B: ['MPR121', 'CCS811'], + 0x5C: ['AM2315', 'MPR121'], + 0x5D: ['MPR121'], + 0x60: ['MPL115A2','MPL3115A2', 'Si5351A', 'Si1145', 'MCP4725A0', 'TEA5767', 'VCNL4040'], + 0x61: ['Si5351A', 'MCP4725A0'], + 0x62: ['MCP4725A0'], + 0x63: ['Si4713', 'MCP4725A1'], + 0x64: ['MCP4725A2'], + 0x65: ['MCP4725A2'], + 0x66: ['MCP4725A3'], + 0x67: ['MCP4725A3'], + 0x68: ['AMG8833', 'DS1307', 'PCF8523', 'DS3231', 'MPU-9250', 'MPU-60X0', 'ITG3200'], + 0x69: ['AMG8833', 'MPU-9250', 'MPU-60X0', 'ITG3200'], + 0x6A: ['L3GD20H', 'LSM9DS0'], + 0x6B: ['L3GD20H', 'LSM9DS0'], + 0x70: ['TCA9548', 'HT16K33'], + 0x71: ['TCA9548', 'HT16K33'], + 0x72: ['TCA9548', 'HT16K33'], + 0x73: ['TCA9548', 'HT16K33'], + 0x74: ['IS31FL3731', 'TCA9548', 'HT16K33'], + 0x75: ['IS31FL3731', 'TCA9548', 'HT16K33'], + 0x76: ['BME280 Temp/Barometric', 'IS31FL3731', 'TCA9548', 'HT16K33', 'MS5607/MS5611'], + 0x77: ['BME280 Temp/Barometric/Humidity', 'BMP180 Temp/Barometric', 'BMP085 Temp/Barometric', 'BMA180 Accelerometer', 'IS31FL3731', 'TCA9548', 'HT16K33', 'MS5607/MS5611'], } diff --git a/README.TXT b/README.TXT deleted file mode 100644 index 034b86b6..00000000 --- a/README.TXT +++ /dev/null @@ -1,32 +0,0 @@ - -This repository hosts the programs developed for GSoC-2016 Project -"Open Source Science Experiments and Data Acquisition System for Physics Education and Research with ExpEYES - Pocket Science Lab" (https://goo.gl/GKlzPh) -As a part of GSoC-16 Project, we are also working on developing new portable Lab Tool 'PSLab' which will be the main component of Fossasia Science Lab. -This new interface is inspired from ExpEYES (www.expeyes.in) - -Mentor Organization: FOSSASIA (http://fossasia.org/) - - -Mentors: Mario Behling, Lorenz Gerber, Gi Soong Chi. - - -#TODO -Create a repo on fossasia github and export all the programs. - - - - - - - -BUILDING FROM THE SOURCE -======================== -#TODO - - -TO RUN THE PROGRAMS -=================== -#TODO - - - diff --git a/README.md b/README.md index bb13482b..7e35b86b 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,99 @@ -# PSLab +# PSLab Python Library -The Pocket Science Lab from FOSSASIA +The Python library for the [Pocket Science Lab](https://pslab.io) from FOSSASIA. -[![Build Status](https://travis-ci.org/fossasia/pslab.svg?branch=development)](https://travis-ci.org/fossasia/pslab) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/ce4af216571846308f66da4b7f26efc7)](https://www.codacy.com/app/mb/pslab?utm_source=github.com&utm_medium=referral&utm_content=fossasia/pslab&utm_campaign=Badge_Grade) +[![Build Status](https://travis-ci.org/fossasia/pslab-python.svg?branch=development)](https://travis-ci.org/fossasia/pslab-python) +[![Gitter](https://badges.gitter.im/fossasia/pslab.svg)](https://gitter.im/fossasia/pslab?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/ce4af216571846308f66da4b7f26efc7)](https://www.codacy.com/app/mb/pslab-python?utm_source=github.com&utm_medium=referral&utm_content=fossasia/pslab&utm_campaign=Badge_Grade) +[![Mailing List](https://img.shields.io/badge/Mailing%20List-FOSSASIA-blue.svg)](https://groups.google.com/forum/#!forum/pslab-fossasia) +[![Twitter Follow](https://img.shields.io/twitter/follow/pslabio.svg?style=social&label=Follow&maxAge=2592000?style=flat-square)](https://twitter.com/pslabio) -This repository hosts the python library for communicating with PSLab. This can be installed on a linux pc/raspberry pi. With this, one can communicate with the hardware using simple python code. +This repository hosts the python library for communicating with the Pocket Science Lab open hardware platform (PSLab). This can be installed on a Linux or Windows system. Using this library you can communicate with the PSLab using simple Python code. The Python library is also used in the PSLab desktop application as a backend component. The goal of PSLab is to create an Open Source hardware device (open on all layers) and software applications that can be used for experiments by teachers, students and scientists. Our tiny pocket lab provides an array of instruments for doing science and engineering experiments. It provides functions of numerous measurement tools including an oscilloscope, a waveform generator, a frequency counter, a programmable voltage, current source and even a component to control robots with up to four servos. The website is at: https://pslab.io +## Buy -* The project is inspired from ExpEYES http://expeyes.in -* FOSSASIA is supporting development and promotion of ExpEYES project since 2014 mainly through Google Summer of Code -* The current work is a part of my GSoC-16 project +* You can get a Pocket Science Lab device from the [FOSSASIA Shop](https://fossasia.com). +* More resellers are listed on the [PSLab website](https://pslab.io/shop/). -##Communication -Chat: [Pocket Science Slack Channel](http://fossasia.slack.com/messages/pocketscience/) | [Get an Invite](http://fossasia-slack.herokuapp.com/) +## Communication ----------------- +* The PSLab [chat channel is on Gitter](https://gitter.im/fossasia/pslab). +* Please also join us on the [PSLab Mailing List](https://groups.google.com/forum/#!forum/pslab-fossasia). -Installation ------------- +## Installation -To install PSLab on Debian based Gnu/Linux system, the following dependencies must be installed. +To install PSLab on Debian based GNU/Linux system, the following dependencies must be installed. -####Dependencies +### Dependencies -* PyQt 4.7+, PySide, or PyQt5 -* python 2.6, 2.7, or 3.x -* NumPy, Scipy -* pyqt4-dev-tools   #for pyuic4 -* Pyqtgraph   #Plotting library -* pyopengl and qt-opengl   #for 3D graphics -* iPython-qtconsole   #optional +* Python 3.4 or higher [Link to Official download page](https://www.python.org/downloads/windows/) +* Pip   **Support package installer** +* NumPy   **For numerical calculations** +* PySerial   **For device connection** +* iPython-qtconsole   **_Optional_** +**Note**: If you are only interested in using PSLab as an acquisition device without a display/GUI, only one repository [pslab-python](https://github.com/fossasia/pslab-python) needs to be installed. If you like a GUI, install the [pslab-desktop app](https://github.com/fossasia/pslab-desktop) and follow the instructions of the Readme in that repo. -#####Now clone both the repositories [pslab-apps](https://github.com/fossasia/pslab-apps) and [pslab](https://github.com/fossasia/pslab). +### How To Install on Linux +cd into the directories -#####Libraries must be installed in ~~the following~~ any order + $ cd -1. pslab-apps -2. pslab +and run the following -**Note** -*If user is only interested in using PSLab as an acquisition device without a display/GUI, only one repository [pslab](https://github.com/fossasia/pslab) needs to be installed* + $ sudo make clean + $ sudo make + $ sudo make install +Now you are ready with the PSLab software on your machine. -#####To install, cd into the directories +### How to Install on Windows -`$ cd ` +**Step 1**: Install the latest Python version on your computer and configure `PATH` variable to have both Python installation directory and the Scripts directory to access `pip` tools. In Windows, Python is installed in `C:` drive by default. We can set `$PATH` by opening the **Environment variables** dialog box by following the steps below; -and run the following (for both the repos) +1. [Right click on My Computer] +2. Select "Properties" +3. Open "System Properties" +4. Click "Advanced" tab +5. Click "Environment Variables" button +6. Look for "**_PATH_**" in "System Variables" section and click on it and press "Edit" button +7. To the end of "Variable value" text box, append "`C:\Python34\;C:\Python34\Scripts\;`" (without quotes and `34` may differ depending on the python version installed. It could be 35, 37 ...) +8. Click "OK" twice to save and move out from path windows -`$ sudo make clean` +**Step 2**: Open up command prompt and execute the following commands to install the required dependencies. -`$ sudo make` + $ pip install pyserial + $ pip install numpy -`$ sudo make install` +#### Validate -Now you are ready with the PSLab software on your machine :) +1. Download the PSLab-Python library from this repository and extract it to a directory. +2. Browse in to that directory and create a new file named `test-pslab-libs.py` +3. Paste the following code into that file and save it. +``` +from PSL import sciencelab +I = sciencelab.connect() +capacitance = I.get_capacitance() +print(capacitance) +``` +4. Plug in the PSLab device and check if both the LEDs are turned on. +5. Now run this file by typing `python test-pslab-libs.py` on a command prompt and observe a numerical value printed on the screen along with PSLab device version and the port it is connected to. -For the main GUI (Control panel), you can run Experiments from the terminal. +### How to Setup the Development Environment -`$ Experiments` +To set up the development environment, install the packages mentioned in dependencies. For building GUI's you can use Qt Designer or create a frontend using any other compatible technology. The PSLab desktop app is using Electron for example. ------------------------ +## How to Build the Documentation Website -####Development Environment +First install sphinx by running following command -To set up the development environment, install the packages mentioned in dependencies. For building GUI's Qt Designer is used. + sudo pip install -U Sphinx -### Blog posts related to PSLab on FOSSASIA blog -* [Installation of PSLab](http://blog.fossasia.org/pslab-code-repository-and-installation/) -* [Communicating with PSLab](http://blog.fossasia.org/communicating-with-pocket-science-lab-via-usb-and-capturing-and-plotting-sine-waves/) -* [Features and Controls of PSLab](http://blog.fossasia.org/features-and-controls-of-pocket-science-lab/) -* [Design your own Experiments](http://blog.fossasia.org/design-your-own-experiments-with-pslab/) -* [New Tools and Sensors for Fossasia PSLab and ExpEYES](http://blog.fossasia.org/new-tools-and-sensors-fossasia-pslab-and-expeyes/) +Then go to pslab/docs and run the following command + + $ sudo make html + +## License + +The library is free and open source software licensed under the [GPL v3](LICENSE). The copyright is owned by FOSSASIA. diff --git a/docs/PSL.SENSORS.rst b/docs/PSL.SENSORS.rst new file mode 100644 index 00000000..44c2ed52 --- /dev/null +++ b/docs/PSL.SENSORS.rst @@ -0,0 +1,126 @@ +PSL.SENSORS package +=================== + +Submodules +---------- + +PSL.SENSORS.AD7718_class module +------------------------------- + +.. automodule:: PSL.SENSORS.AD7718_class + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.AD9833 module +------------------------- + +.. automodule:: PSL.SENSORS.AD9833 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.BH1750 module +------------------------- + +.. automodule:: PSL.SENSORS.BH1750 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.BMP180 module +------------------------- + +.. automodule:: PSL.SENSORS.BMP180 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.ComplementaryFilter module +-------------------------------------- + +.. automodule:: PSL.SENSORS.ComplementaryFilter + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.HMC5883L module +--------------------------- + +.. automodule:: PSL.SENSORS.HMC5883L + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.Kalman module +------------------------- + +.. automodule:: PSL.SENSORS.Kalman + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.MF522 module +------------------------ + +.. automodule:: PSL.SENSORS.MF522 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.MLX90614 module +--------------------------- + +.. automodule:: PSL.SENSORS.MLX90614 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.MPU6050 module +-------------------------- + +.. automodule:: PSL.SENSORS.MPU6050 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.SHT21 module +------------------------ + +.. automodule:: PSL.SENSORS.SHT21 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.SSD1306 module +-------------------------- + +.. automodule:: PSL.SENSORS.SSD1306 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.TSL2561 module +-------------------------- + +.. automodule:: PSL.SENSORS.TSL2561 + :members: + :undoc-members: + :show-inheritance: + +PSL.SENSORS.supported module +---------------------------- + +.. automodule:: PSL.SENSORS.supported + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: PSL.SENSORS + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/PSL.sensors.rst b/docs/PSL.sensors.rst deleted file mode 100644 index f9bf1abc..00000000 --- a/docs/PSL.sensors.rst +++ /dev/null @@ -1 +0,0 @@ -#TODO diff --git a/docs/conf.py b/docs/conf.py index a187af06..063669e1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,20 +12,20 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -37,7 +37,7 @@ 'sphinx.ext.mathjax', ] -mathjax_path='file:///usr/share/javascript/mathjax/MathJax.js' +mathjax_path = 'file:///usr/share/javascript/mathjax/MathJax.js' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -48,7 +48,7 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -76,9 +76,9 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -86,64 +86,62 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True - # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -#html_theme = 'alabaster' -#html_theme_path = ["/usr/lib/python2.7/dist-packages/"] - +# html_theme = 'alabaster' +# html_theme_path = ["/usr/lib/python2.7/dist-packages/"] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -153,62 +151,62 @@ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'SEELdoc' @@ -216,17 +214,17 @@ # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', -# Latex figure (float) alignment -#'figure_align': 'htbp', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples @@ -239,23 +237,23 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -268,7 +266,7 @@ ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -283,16 +281,16 @@ ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- @@ -304,62 +302,62 @@ epub_copyright = copyright # The basename for the epub file. It defaults to the project name. -#epub_basename = project +# epub_basename = project # The HTML theme for the epub output. Since the default themes are not # optimized for small screen space, using the same theme for HTML and epub # output is usually not wise. This defaults to 'epub', a theme designed to save # visual space. -#epub_theme = 'epub' +# epub_theme = 'epub' # The language of the text. It defaults to the language option # or 'en' if the language is not set. -#epub_language = '' +# epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' +# epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. -#epub_identifier = '' +# epub_identifier = '' # A unique identification for the text. -#epub_uid = '' +# epub_uid = '' # A tuple containing the cover image and cover page html template filenames. -#epub_cover = () +# epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () +# epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_pre_files = [] +# epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. -#epub_post_files = [] +# epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 +# epub_tocdepth = 3 # Allow duplicate toc entries. -#epub_tocdup = True +# epub_tocdup = True # Choose between 'default' and 'includehidden'. -#epub_tocscope = 'default' +# epub_tocscope = 'default' # Fix unsupported image types using the Pillow. -#epub_fix_images = False +# epub_fix_images = False # Scale large images. -#epub_max_image_width = 0 +# epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. -#epub_show_urls = 'inline' +# epub_show_urls = 'inline' # If false, no index is generated. -#epub_use_index = True +# epub_use_index = True diff --git a/docs/images/wiki_images/lubuntu_logo.png b/docs/images/wiki_images/lubuntu_logo.png new file mode 100644 index 00000000..1db7dd4f Binary files /dev/null and b/docs/images/wiki_images/lubuntu_logo.png differ diff --git a/docs/images/wiki_images/ubuntu_logo.png b/docs/images/wiki_images/ubuntu_logo.png new file mode 100644 index 00000000..203291a8 Binary files /dev/null and b/docs/images/wiki_images/ubuntu_logo.png differ diff --git a/docs/images/wiki_images/window_logo.png b/docs/images/wiki_images/window_logo.png new file mode 100644 index 00000000..e8b2fcc8 Binary files /dev/null and b/docs/images/wiki_images/window_logo.png differ diff --git a/docs/images/wiki_images/xubuntu_logo.png b/docs/images/wiki_images/xubuntu_logo.png new file mode 100644 index 00000000..84de4517 Binary files /dev/null and b/docs/images/wiki_images/xubuntu_logo.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..792eea4e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +setuptools >= 35.0.2 +numpy >= 1.16.3 +pyserial >= 3.4 \ No newline at end of file diff --git a/setup.py b/setup.py index 63ed69a6..850fcf0a 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,22 @@ #!/usr/bin/env python +from __future__ import print_function +import os +import shutil +from distutils.util import execute +from subprocess import call -from __future__ import print_function -#from distutils.core import setup from setuptools import setup, find_packages from setuptools.command.install import install -import os,shutil -from distutils.util import execute -from distutils.cmd import Command -from subprocess import call + def udev_reload_rules(): call(["udevadm", "control", "--reload-rules"]) + def udev_trigger(): - call(["udevadm", "trigger", "--subsystem-match=usb","--attr-match=idVendor=04d8", "--action=add"]) + call(["udevadm", "trigger", "--subsystem-match=usb", "--attr-match=idVendor=04d8", "--action=add"]) + def install_udev_rules(raise_exception): if check_root(): @@ -28,25 +30,29 @@ def install_udev_rules(raise_exception): else: print(msg) + def check_root(): return os.geteuid() == 0 + class CustomInstall(install): def run(self): - if not hasattr(self,"root") or 'debian' not in self.root: + if not hasattr(self, "root"): install_udev_rules(True) + elif self.root is not None: + if 'debian' not in self.root: + install_udev_rules(True) install.run(self) -setup(name='PSL', - version='1.0.0', - description='Pocket Science Lab from FOSSASIA - inspired by ExpEYES http://expeyes.in', - author='Praveen Patil and Jithin B.P.', - author_email='praveenkumar103@gmail.com', - url='http://fossasia.github.io/pslab.fossasia.org/', - install_requires = ['numpy>=1.8.1','pyqtgraph>=0.9.10'], - packages=find_packages(), - #scripts=["PSL/bin/"+a for a in os.listdir("PSL/bin/")], - package_data={'': ['*.css','*.png','*.gif','*.html','*.css','*.js','*.png','*.jpg','*.jpeg','*.htm','99-pslab.rules']}, - cmdclass={'install': CustomInstall}, -) +setup(name='PSL', + version='1.1.0', + description='Pocket Science Lab by FOSSASIA', + author='FOSSASIA PSLab Developers', + author_email='pslab-fossasia@googlegroups.com', + url='https://pslab.io/', + install_requires=['numpy>=1.16.3.', 'pyqtgraph>=0.9.10'], + packages=find_packages(), + package_data={'': ['*.css', '*.png', '*.gif', '*.html', '*.css', '*.js', '*.png', '*.jpg', '*.jpeg', '*.htm', + '99-pslab.rules']}, + cmdclass={'install': CustomInstall})