diff --git a/.gitignore b/.gitignore index 3c7027ed..6a5941cd 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,15 @@ cython_debug/ #pylint .pylint.d/ + + +new-sensor-test.py +astropy_validation.py +test.py +testdat.py +beam_test_inj.py +beam_test_old.py +modules/asic.py +archive/ +*.csv +beam_test_play.py \ No newline at end of file diff --git a/README.md b/README.md index 39615279..df40b897 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,90 @@ Create links to shared lib: ### Mac See [FTDI Mac OS X Installation Guide](https://www.ftdichip.com/Support/Documents/InstallGuides/Mac_OS_X_Installation_Guide.pdf) D2XX Driver section from page 10. + +# How to use the astropix2 module +Astropix-py is a module with the goal of simplifying and unifying all of the diffrent branches and modulles into a single module which can be easily worked with. +The goal is to provide a simple interface where astropix can be configured, initalized, monitored, and iterfaced with without having to modify source files or copy and paste code from various repositories. + +Although we aim to maintain compatibility with older branches, that will not be possible in all cases (for example the asic.py module). When this happens the original files will be preserved to maintain backwards compatibility and directions and information for moving over to the new interface. + +## Directions for use: +Must go in this order!! + +1. Creating the instance + - After import, call astropix2(). + - Usage: `astropix2([none required], clock_period_ns: int, inject: bool)` + - optional arguments: + - clock_period_ns, default 10 + - inject, default `False`. When true configures the pixels to accept an injection voltage +2. Initializing voltages + - call `astro.init_voltages([none required] slot, vcal, vsupply, vthreshold, [optional] dacvals)` + - slot: Usually 4, tells chip where the board is + - vcal: calibrated voltage. Usually 0.989 + - vsupply: voltage to gecco board, usually 2.7 + - vthreshold: ToT threshold voltage. Usually 1.075 ish + - optional, dacvals: if you want to configure the dac values, do that here +3. Initalizing the ASIC + - call `astro.asic_init()` + - Usage: `astro.asic_init(yaml:str, [opt] dac_setup: dict, bias_setup: dict, digital_mask: str)` + - Optional arguments: + - yaml: string of name of configuration .yml file in /config/*.yml. If none given command-line, default set to config/testconfig.yml + - dac_setup: dictionary of values which will be used to change the defalt dac settings. Does not need to have a complete dictionary, only values that you want to change. Default None + - bias_setup: dictionary of values which will be used to change the defalt bias settings. Does not need to have a complete dictionary, only values that you want to change. Default None + - digital_mask: text data of 1s and 0s in a 35x35 grid (newline seperated rows) specifying what pixels are on and off. If not specified chip will be in analog mode +4. Initalizing injector board (optional) + - call `astro.init_injection()` + - Has following options and defaults: + - dac_settings:tuple[int, list[float]] = (2, [0.4, 0.0]) + - position: int = 3, position in board, same as slot in init_voltages(). + - inj_period:int = 100 + - clkdiv:int = 400 + - initdelay: int = 10000 + - cycle: float = 0 + - pulseperset: int = 1 +5. enable SPI + - `astro.enable_spi()` + - takes no arguments + +Useful methods: + +astro.hits_present() --> bool. Are thre any hits on the board currently? + +astro.get_readout() --> bytearray. Gets bytestream from the chip + +astro.decode_readout(readout, [opt] printer) --> list of dictionaries. Printer prints the decoded values to terminal + +astro.write_conf_to_yaml() --> write configuration settings to *.yml + +astro.start_injection() and astro.stop_injection() are self explainatory + +## Usage of beam_test.py + +beam_test.py is a rewritten version of beam_test.py which removes the need for asic.py, and moves most configuration to command arguments. +It has the ability to: +- Save csv files +- Plot hits in real time +- Configure threshold and injection voltages +- Enable digital output based on pixel masks + +CAUTION : try not to pass arguments to astropix.py as numpy objects - if looping through a numpy array, typecast to int, float, etc for the argument call or features may not work as intended (ie - pixels may not be activated/deactivated as expected) + +Options: +| Argument | Usage | Purpose | Default | +| :--- | :--- | :--- | :--- | +| `-n` `--name` | `-n [SOMESTRING]` | Set additional name to be added to the timestamp in file outputs | None | +| `-o` `--outdir`| `-o [DIRECTORY]` | Directory to save all output files to. Will be created if it doesn't exist. | `./` | +| `-y` `--yaml`| `-y [NAME]` | Name of configuration file, assuming config/*.yml where * is passed. If not specified, uses config/testconfig.yml and disables all pixels | `testconfig` | +| `-c` `--saveascsv` | `-c` | Toggle saving csv files on and off | Does not save csv | +| `-s` `--showhits` | `-s` | Display hits in real time | Off | +| `-p` `--plotsave` | `-p` | Saves real time plots as image files. Stored in outdir. | Does not save plots | +| `-t` `--threshold`| `-t [VOLTAGE]`| Sets digital threshold voltage in mV. | `100mV` | +| `-i` `--inject`| `-i [COL]` | Toggles injection on or off at specified column. Injects 300mV unless specified. | Off| +| `-v` `--vinj` | `-v [VOLTAGE]` | Sets voltage of injection in mV. Does not enable injection. | `300mV` | +| `-M` `--maxruns` | `-M [int]` | Sets the maximum number of readouts the code will process before exiting. | No maximum | +| `-E` `--errormax`| `-E [int]` | Amount of index errors encountered in the decode before the program terminates. | `0` | +| `-a` `--analog` | `-a [COL]` | Enable analog output on specified column | `None` | +| `-L` `--loglevel` | `-L [D,I,E,W,C]`| Loglevel to be stored. Applies to both console and file. Options: D - debug, I - info, E - error, W - warning, C - critical | `I` | +| `--timeit` | `--timeit` | Measures the time it took to decode and store a hitstream. | Off | + + diff --git a/astropix.py b/astropix.py new file mode 100644 index 00000000..eebb4b0c --- /dev/null +++ b/astropix.py @@ -0,0 +1,493 @@ +""" +Central module of astropix. This incorporates all of the various modules from the original 'module' directory backend (now 'core') +The class methods of all the other modules/cores are inherited here. + +Author: Autumn Bauman +Maintained by: Amanda Steinhebel, amanda.l.steinhebel@nasa.gov +""" +# Needed modules. They all import their own suppourt libraries, +# and eventually there will be a list of which ones are needed to run +from typing import Dict +from core.spi import Spi +from core.nexysio import Nexysio +from core.decode import Decode +from core.injectionboard import Injectionboard +from core.voltageboard import Voltageboard +from core.asic import Asic +from bitstring import BitArray +from tqdm import tqdm +import pandas as pd +import regex as re +import time +import yaml +import os + +# Logging stuff +import logging +from modules.setup_logger import logger +logger = logging.getLogger(__name__) + +class astropix2: + + # Init just opens the chip and gets the handle. After this runs + # asic_config also needs to be called to set it up. Seperating these + # allows for simpler specifying of values. + def __init__(self, clock_period_ns = 5, inject:int = None, offline:bool=False): + """ + Initalizes astropix object. + No required arguments + Optional: + clock_period_ns:int - period of main clock in ns + inject:bool - if set to True will enable injection for the whole array. + offline:bool - if True, do not try to interface with chip + """ + + # _asic_start tracks if the inital configuration has been run on the ASIC yet. + # By not handeling this in the init it simplifies the function, making it simpler + # to put in custom configurations and allows for less writing to the chip, + # only doing it once at init or when settings need to be changed as opposed to + # each time a parameter is changed. + + if offline: + logger.info("Creating object for offline analysis") + else: + self._asic_start = False + self.nexys = Nexysio() + self._wait_progress(2) + self.handle = self.nexys.autoopen() + + # Ensure it is working + logger.info("Opened FPGA, testing...") + self._test_io() + logger.info("FPGA test successful.") + # Start putting the variables in for use down the line + if inject is None: + inject = (None, None) + self.injection_col = inject[1] + self.injection_row = inject[0] + + self.sampleclock_period_ns = clock_period_ns + # Creates objects used later on + self.decode = Decode(clock_period_ns) + +##################### YAML INTERACTIONS ######################### +#reading done in core/asic.py +#writing done here + + def write_conf_to_yaml(self, filename:str = None): + """ + Write ASIC config to yaml + :param chipversion: chip version + :param filename: Name of yml file in config folder + """ + + dicttofile ={self.asic.chip: + { + "telescope": {"nchips": self.asic.num_chips}, + "geometry": {"cols": self.asic.num_cols, "rows": self.asic.num_rows} + } + } + + if self.asic.num_chips > 1: + for chip in range(self.asic.num_chips): + dicttofile[self.asic.chip][f'config_{chip}'] = self.asic.asic_config[f'config_{chip}'] + else: + dicttofile[self.asic.chip]['config'] = self.asic.asic_config + + with open(f"{filename}", "w", encoding="utf-8") as stream: + try: + yaml.dump(dicttofile, stream, default_flow_style=False, sort_keys=False) + + except yaml.YAMLError as exc: + logger.error(exc) + + +##################### ASIC METHODS FOR USERS ######################### + + # Method to initalize the asic. This is taking the place of asic.py. + # All of the interfacing is handeled through asic_update + def asic_init(self, yaml:str = None, dac_setup: dict = None, bias_setup:dict = None, blankmask:bool = False, analog_col:int = None): + """ + self.asic_init() - initalize the asic configuration. Must be called first + Positional arguments: None + Optional: + dac_setup: dict - dictionary of values passed to the configuration. Only needs values diffent from defaults + bias_setup: dict - dict of values for the bias configuration Only needs key/vals for changes from default + blankmask: bool - Create a blank mask (everything disabled). Pixels can be enabled manually + analog_col: int - Sets a column to readout analog data from. + """ + + # Now that the asic has been initalized we can go and make this true + self._asic_start = True + + self.asic = Asic(self.handle, self.nexys) + + # Get config values from YAML + #set chip version + self.asic.chipversion=2 + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="."+pathdelim+"config"+pathdelim+yaml+".yml" + try: + self.asic.load_conf_from_yaml(self.asic.chipversion, ymlpath) + except Exception: + logger.error('Must pass a configuration file in the form of *.yml') + #Config stored in dictionary self.asic_config . This is used for configuration in asic_update. + #If any changes are made, make change to self.asic_config so that it is reflected on-chip when + # asic_update is called + + #Override yaml if arguments were given in run script + self.update_asic_config(bias_setup, dac_setup) + + # Set analog output + if (analog_col is not None) and (analog_col <= self.asic._num_cols): + logger.info(f"enabling analog output in column {analog_col}") + self.asic.enable_ampout_col(analog_col, inplace=False) + + # Turns on injection if so desired + if self.injection_col is not None: + self.asic.enable_inj_col(self.injection_col, inplace=False) + self.asic.enable_inj_row(self.injection_row, inplace=False) + + # Load config it to the chip + logger.info("LOADING TO ASIC...") + self.asic_update() + logger.info("ASIC SUCCESSFULLY CONFIGURED") + + #Interface with asic.py + def enable_pixel(self, col: int, row: int, inplace:bool=True): + self.asic.enable_pixel(col, row, inplace) + + #Turn on injection of different pixel than the one used in _init_ + def enable_injection(self, col:int, row:int, inplace:bool=True): + self.asic.enable_inj_col(col, inplace) + self.asic.enable_inj_row(row, inplace) + + # The method to write data to the asic. Called whenever somthing is changed + # or after a group of changes are done. Taken straight from asic.py. + def asic_update(self): + self.nexys.chip_reset() + self.asic.asic_update() + + + # Methods to update the internal variables. Please don't do it manually + # This updates the dac config + def update_asic_config(self, bias_cfg:dict = None, dac_cfg:dict = None, analog_col:int=None): + """ + Updates and writes confgbits to asic + + bias_cfg:dict - Updates the bias settings. Only needs key/value pairs which need updated + dac_cfg:dict - Updates DAC settings. Only needs key/value pairs which need updated + """ + if self._asic_start: + if bias_cfg is not None: + for key in bias_cfg: + self.asic.asic_config['biasconfig'][key][1]=bias_cfg[key] + if dac_cfg is not None: + for key in dac_cfg: + self.asic.asic_config['idacs'][key][1]=dac_cfg[key] + else: + logger.info("update_asic_config() got no arguments, nothing to do.") + return None + self.asic_update() + else: raise RuntimeError("Asic has not been initalized") + + def enable_spi(self): + """ + Starts spi bus. + + Takes no arguments, returns nothing + """ + + self.nexys.spi_enable() + self.nexys.spi_reset() + # Set SPI clockdivider + # freq = 100 MHz/spi_clkdiv + self.nexys.spi_clkdiv = 255 + self.nexys.send_routing_cmd() + logger.info("SPI ENABLED") + + def close_connection(self): + """ + Terminates the spi bus. + Takes no arguments. No returns. + """ + self.nexys.close() + + +################## Voltageboard Methods ############################ + +# Here we intitalize the 8 DAC voltageboard in slot 4. dacvals are carried over from past +# scripts. Default from beam_test.py: +# Use this: (8, [0, 0, 1.1, 1, 0, 0, 1, 1.035]) + def init_voltages(self, slot: int = 4, vcal:float = .989, vsupply: float = 2.7, vthreshold:float = None, dacvals: tuple[int, list[float]] = None): + """ + Configures the voltage board + No required parameters. No return. + + slot:int = 4 - Position of voltage board + vcal:float = 0.908 - Calibration of the voltage rails + vsupply = 2.7 - Supply Voltage + vthreshold:float = None - ToT threshold value. Takes precedence over dacvals if set. UNITS: mV + dacvals:tuple[int, list[float] - vboard dac settings. Must be fully specified if set. + """ + # The default values to pass to the voltage dac. Last value in list is threshold voltage, default 100mV or 1.1 + # Not in YAML + # From nicholas's beam_test.py: + # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix + default_vdac = (8, [0, 0, 1.1, 1, 0, 0, 1, 1.100]) + + # used to ensure this has been called in the right order: + self._voltages_exist = True + + # Set dacvals + if dacvals is None: + dacvals = default_vdac + + # dacvals takes precidence over vthreshold + if vthreshold is not None: + # Turns from mV to V with the 1V offset normally present + vthreshold = (vthreshold/1000) + 1 + if vthreshold > 1.5 or vthreshold < 0: + logger.warning("Threshold voltage out of range of sensor!") + if vthreshold <= 0: + vthreshold = 1.100 + logger.error("Threshold value too low, setting to default 100mV") + dacvals[1][-1] = vthreshold + # Create object + self.vboard = Voltageboard(self.handle, slot, dacvals) + # Set calibrated values + self.vboard.vcal = vcal + self.vboard.vsupply = vsupply + # Send config to the chip + self.vboard.update_vb() + + # Here we have the stuff to run injection + + # defaults for the arguments: + # position: 3 + # dac_settings: (2, [0.4, 0.0]) + # Settings from the orininal scripts + """ + inj.period = 100 + inj.clkdiv = 400 + inj.initdelay = 10000 + inj.cycle = 0 + inj.pulsesperset = 1 + """ + # Setup Injections + def init_injection(self, slot: int = 3, inj_voltage:float = None, inj_period:int = 100, clkdiv:int = 300, initdelay: int = 100, cycle: float = 0, pulseperset: int = 1, dac_config:tuple[int, list[float]] = None): + """ + Configure injections + No required arguments. No returns. + Optional Arguments: + slot: int - Location of the injection module + inj_voltage: float - Injection Voltage. Range from 0 to 1.8. If dac_config is set inj_voltage will be overwritten + inj_period: int + clkdiv: int + initdelay: int + cycle: float + pulseperset: int + dac_config:tuple[int, list[float]]: injdac settings. Must be fully specified if set. + """ + # Default configuration for the dac + # 0.3 is (default) injection voltage + # 2 is slot number for inj board + default_injdac = (2, [0.3, 0.0]) + # Some fault tolerance + try: + self._voltages_exist + except Exception: + raise RuntimeError("init_voltages must be called before init_injection!") + + # Sets the dac_setup if it isn't specified + if dac_config is None: + dac_settings = default_injdac + else: + dac_settings = dac_config + + # The dac_config takes presedence over a specified threshold. + if (inj_voltage is not None) and (dac_config is None): + # elifs check to ensure we are not injecting a negative value because we don't have that ability + if inj_voltage < 0: + raise ValueError("Cannot inject a negative voltage!") + elif inj_voltage > 1800: + logger.warning("Cannot inject more than 1800mV, will use defaults") + inj_voltage = 300 #Sets to 300 mV + + inj_voltage = inj_voltage / 1000 + dac_settings[1][0] = inj_voltage + + # Create the object! + self.inj_volts = Voltageboard(self.handle, slot, dac_settings) + # set the parameters + self.inj_volts.vcal = self.vboard.vcal + self.inj_volts.vsupply = self.vboard.vsupply + self.inj_volts.update_vb() + # Now to configure the actual injection thing + self.injector = Injectionboard(self.handle) + # Now to configure it. above are the values from the original scripting. + self.injector.period = inj_period + self.injector.clkdiv = clkdiv + self.injector.initdelay = initdelay + self.injector.cycle = cycle + self.injector.pulsesperset = pulseperset + + # These start and stop injecting voltage. Fairly simple. + def start_injection(self): + """ + Starts Injection. + Takes no arguments and no return + """ + self.injector.start() + logger.info("Began injection") + + def stop_injection(self): + """ + Stops Injection. + Takes no arguments and no return + """ + self.injector.stop() + logger.info("Stopped injection") + + +########################### Input and Output ############################# + # This method checks the chip to see if a hit has been logged + + def hits_present(self): + """ + Looks at interrupt + Returns bool, True if present + """ + if (int.from_bytes(self.nexys.read_register(70),"big") == 0): + return True + else: + return False + + def get_log_header(self): + """ + Returns header for use in a log file with all settings. + """ + #Get config dictionaries from yaml + digitalconfig = {} + for key in self.asic.asic_config['digitalconfig']: + digitalconfig[key]=self.asic.asic_config['digitalconfig'][key][1] + biasconfig = {} + for key in self.asic.asic_config['biasconfig']: + biasconfig[key]=self.asic.asic_config['biasconfig'][key][1] + dacconfig = {} + for key in self.asic.asic_config['idacs']: + dacconfig[key]=self.asic.asic_config['idacs'][key][1] + arrayconfig = {} + for key in self.asic.asic_config['recconfig']: + arrayconfig[key]=self.asic.asic_config['recconfig'][key][1] + + # This is not a nice line, but its the most efficent way to get all the values in the same place. + return f"Voltageboard settings: {self.vboard.dacvalues}\n" + f"Digital: {digitalconfig}\n" +f"Biasblock: {biasconfig}\n" + f"DAC: {dacconfig}\n" + f"Receiver: {arrayconfig}\n" + + +############################ Decoder ############################## + # This function generates a list of the hits in the stream. Retuerns a bytearray + + def get_readout(self, bufferlength:int = 20): + """ + Reads hit buffer. + bufferlength:int - length of buffer to write. Multiplied by 8 to give number of bytes + Returns bytearray + """ + self.nexys.write_spi_bytes(bufferlength) + readout = self.nexys.read_spi_fifo() + return readout + + + def decode_readout(self, readout:bytearray, i:int, printer: bool = True): + """ + Decodes readout + + Required argument: + readout: Bytearray - readout from sensor, not the printed Hex values + i: int - Readout number + + Optional: + printer: bool - Print decoded output to terminal + + Returns dataframe + """ + + list_hits = self.decode.hits_from_readoutstream(readout) + hit_list = [] + for hit in list_hits: + # Generates the values from the bitstream + id = int(hit[0]) >> 3 + payload = int(hit[0]) & 0b111 + location = int(hit[1]) & 0b111111 + col = 1 if (int(hit[1]) >> 7 ) & 1 else 0 + timestamp = int(hit[2]) + tot_msb = int(hit[3]) & 0b1111 + tot_lsb = int(hit[4]) + tot_total = (tot_msb << 8) + tot_lsb + + wrong_id = 0 if (id) == 0 else '\x1b[0;31;40m{}\x1b[0m'.format(id) + wrong_payload = 4 if (payload) == 4 else'\x1b[0;31;40m{}\x1b[0m'.format(payload) + + + + # will give terminal output if desiered + if printer: + print( + f"{i} Header: ChipId: {wrong_id}\tPayload: {wrong_payload}\t" + f"Location: {location}\tRow/Col: {'Col' if col else 'Row'}\t" + f"Timestamp: {timestamp}\t" + f"ToT: MSB: {tot_msb}\tLSB: {tot_lsb} Total: {tot_total} ({(tot_total * self.sampleclock_period_ns)/1000.0} us)" + ) + # hits are sored in dictionary form + # Look into dataframe + hits = { + 'readout': i, + 'Chip ID': id, + 'payload': payload, + 'location': location, + 'isCol': (True if col else False), + 'timestamp': timestamp, + 'tot_msb': tot_msb, + 'tot_lsb': tot_lsb, + 'tot_total': tot_total, + 'tot_us': ((tot_total * self.sampleclock_period_ns)/1000.0), + 'hittime': time.time() + } + hit_list.append(hits) + + # Much simpler to convert to df in the return statement vs df.concat + return pd.DataFrame(hit_list) + + # To be called when initalizing the asic, clears the FPGAs memory + def dump_fpga(self): + """ + Reads out hit buffer and disposes of the output. + + Does not return or take arguments. + """ + readout = self.get_readout() + del readout + + +###################### INTERNAL METHODS ########################### + +# Below here are internal methods used for constructing things and testing + + # _test_io(): A function to read and write a register on the chip to see if + # everythign is working. + # It takes no arguments + def _test_io(self): + try: # Attempts to write to and read from a register + self.nexys.write_register(0x09, 0x55, True) + self.nexys.read_register(0x09) + self.nexys.spi_reset() + self.nexys.sr_readback_reset() + except Exception: + raise RuntimeError("Could not read or write from astropix!") + + # progress bar + def _wait_progress(self, seconds:int): + for _ in tqdm(range(seconds), desc=f'Wait {seconds} s'): + time.sleep(1) diff --git a/beam_test.py b/beam_test.py index fabbd8fd..544ea30d 100644 --- a/beam_test.py +++ b/beam_test.py @@ -1,157 +1,289 @@ -# -*- coding: utf-8 -*- -"""""" """ -Created on Sat Jun 26 00:10:56 2021 +Updated version of beam_test.py using the astropix.py module -@author: Nicolas Striebig +Author: Autumn Bauman +Maintained by: Amanda Steinhebel, amanda.l.steinhebel@nasa.gov """ -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter import os +import binascii +import pandas as pd +import numpy as np import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Initialize +def main(args): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + # Prepare everything, create the object + astro = astropix2(inject=args.inject) #no updates in YAML + + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a + astro.asic_init(yaml=args.yaml, analog_col = args.analog) + + #If injection, ensure injection pixel is enabled and initialize + if args.inject is not None: + astro.enable_pixel(args.inject[1],args.inject[0]) + astro.init_injection(inj_voltage=args.vinj) + + #Enable final configuration + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject is not None: + astro.start_injection() + + + max_errors = args.errormax + i = 0 + errors = 0 # Sets the threshold + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + fname="" if not args.name else args.name+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Save final configuration to output file + ymlpathout=args.outdir +"/"+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # Prepare text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + # Enables the hitplotter and uses logic on whether or not to save the images + if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while errors <= max_errors: # Loop continues + + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + # We aren't using timeit, just measuring the diffrence in ns + if args.timeit: start = time.time_ns() + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + if args.timeit: + print(f"Readout took {(time.time_ns()-start)*10**-9}s") + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + errors += 1 + logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all + if errors > max_errors: + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # This handels the hitplotting. Code by Henrike and Amanda + if args.showhits: + # This ensures we aren't plotting NaN values. I don't know if this would break or not but better + # safe than sorry + if pd.isnull(hits.tot_msb.loc(0)): + pass + elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits + #Isolate row and column information from array returned from decoder + rows = hits.location[~hits.isCol] + columns = hits.location[hits.isCol] + plotter.plot_event( rows, columns, i) + + # If we are logging runtime, this does it! + if args.timeit: + print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject is not None: astro.stop_injection() + bitfile.close() # Close open file + astro.close_connection() # Closes SPI + logger.info("Program terminated successfully") + # END OF PROGRAM -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - # Send Routing command - nexys.send_routing_cmd() + - # Reset SPI Read FIFO +if __name__ == "__main__": - #inj.start() - inj.stop() + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') - wait_progress(3) + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') - #decode = Decode() + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + parser.add_argument('-s', '--showhits', action='store_true', + default=False, required=False, + help='Display hits in real time during data taking') - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - - while True: - print("Reg: {}", int.from_bytes(nexys.read_register(70),"big")) - if(int.from_bytes(nexys.read_register(70),"big") == 0): - time.sleep(0.1) - nexys.write_spi_bytes(20) - readout = nexys.read_spi_fifo() - file.write(str(binascii.hexlify(readout))) + parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, + help='Save plots as image files. If set, will be saved in same dir as data. Default: FALSE') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt. Default: FALSE') + + parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, + help = 'Turn on injection in the given row and column. Default: No injection') - print(binascii.hexlify(readout)) + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') - #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0.') - # inj.stop() + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='100', + help='Maximum index errors allowed during decoding. DEFAULT 100') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('--timeit', action="store_true", default=False, + help='Prints runtime from seeing a hit to finishing the decode to terminal') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + """ + parser.add_argument('--ludicrous-speed', type=bool, action='store_true', default=False, + help="Fastest possible data collection. No decode, no output, no file.\ + Saves bitstreams in memory until keyboard interupt or other error and then writes them to file.\ + Use is not generally recommended") + """ + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) - # Close connection - nexys.close() + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + logger = logging.getLogger(__name__) -if __name__ == "__main__": - main() + + main(args) \ No newline at end of file diff --git a/beam_test_inj.py b/beam_test_inj.py deleted file mode 100644 index 478e8e4c..00000000 --- a/beam_test_inj.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -import os -import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.2])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 10 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - inj.start() - #inj.stop() - - wait_progress(3) - - #decode = Decode() - - - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_INJECTION_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - - while True: - print("Reg: {}".format(int.from_bytes(nexys.read_register(70),"big"))) - if(int.from_bytes(nexys.read_register(70),"big") == 0): - time.sleep(0.1) - nexys.write_spi_bytes(10) - readout = nexys.read_spi_fifo() - file.write(str(binascii.hexlify(readout))) - - print(binascii.hexlify(readout)) - - #decode.decode_astropix2_hits(decode.hits_from_readoutstream(readout)) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/config/config_c0_r0.yml b/config/config_c0_r0.yml new file mode 100644 index 00000000..e2b5afb5 --- /dev/null +++ b/config/config_c0_r0.yml @@ -0,0 +1,91 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b0] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 15] + vnrec: [6, 2] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11100] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file diff --git a/config/config_c0_r1.yml b/config/config_c0_r1.yml new file mode 100644 index 00000000..6e050633 --- /dev/null +++ b/config/config_c0_r1.yml @@ -0,0 +1,91 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b0] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 60] + vnrec: [6, 30] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11010] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file diff --git a/config/config_none.yml b/config/config_none.yml new file mode 100644 index 00000000..c3a42695 --- /dev/null +++ b/config/config_none.yml @@ -0,0 +1,93 @@ +# Example Astropix2 config +# +# The following yaml should comply to the following structure. +# It is important, that the order of configbits under the config section, corresponds to the actual order in the chip +# +# astropix: +# geometry: +# cols: +# rows: +# config: +# : +# : [, ] +# ... +# +--- +astropix2: + telescope: + nchips: 1 + geometry: + cols: 35 + rows: 35 + config: + digitalconfig: + interrupt_pushpull: [1, 0b1] + en_inj: [19, 0b0] + reset: [1, 0b0] + extrabits: [14, 0b0] + + biasconfig: + DisHiDR: [1, 0b0] + q01: [1, 0b0] + qon0: [1, 0b0] + qon1: [1, 0b1] + qon2: [1, 0b0] + qon3: [1, 0b1] + + idacs: + blres: [6, 0] + nu1: [6, 0] + vn1: [6, 20] + vnfb: [6, 3] + vnfoll: [6, 10] + nu5: [6, 0] + nu6: [6, 0] + nu7: [6, 0] + nu8: [6, 0] + vn2: [6, 0] + vnfoll2: [6, 4] + vnbias: [6, 0] + vpload: [6, 5] + nu13: [6, 0] + vncomp: [6, 2] + vpfoll: [6, 60] + nu16: [6, 0] + vprec: [6, 15] + vnrec: [6, 2] + + recconfig: + col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col1: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col2: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col3: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col4: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col5: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col6: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col7: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col8: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col9: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col10: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col11: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col12: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col13: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col14: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col15: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col16: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col17: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col18: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col19: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col20: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col21: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col22: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col23: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col24: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col25: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col26: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col27: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col28: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col29: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col30: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col31: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col32: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col33: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] + col34: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] \ No newline at end of file diff --git a/config/testconfig.yml b/config/testconfig.yml index 841c5dd8..de65f864 100644 --- a/config/testconfig.yml +++ b/config/testconfig.yml @@ -14,6 +14,8 @@ # --- astropix2: + telescope: + nchips: 1 geometry: cols: 35 rows: 35 @@ -36,22 +38,22 @@ astropix2: blres: [6, 0] nu1: [6, 0] vn1: [6, 20] - vnfb: [6, 1] + vnfb: [6, 1] #3 vnfoll: [6, 10] nu5: [6, 0] nu6: [6, 0] nu7: [6, 0] nu8: [6, 0] vn2: [6, 0] - vnfoll2: [6, 1] - vnbias: [6, 10] + vnfoll2: [6, 1] #4 + vnbias: [6, 10] #0 vpload: [6, 5] nu13: [6, 0] vncomp: [6, 2] vpfoll: [6, 60] nu16: [6, 0] - vprec: [6, 60] - vnrec: [6, 30] + vprec: [6, 60] #15 + vnrec: [6, 30] #2 recconfig: col0: [38, 0b001_11111_11111_11111_11111_11111_11111_11110] diff --git a/modules/__init__.py b/core/__init__.py similarity index 100% rename from modules/__init__.py rename to core/__init__.py diff --git a/modules/asic.py b/core/asic.py similarity index 75% rename from modules/asic.py rename to core/asic.py index 303fe842..39ed389a 100644 --- a/modules/asic.py +++ b/core/asic.py @@ -4,6 +4,7 @@ Created on Fri Jun 25 16:28:27 2021 @author: Nicolas Striebig +Editor for astropix.py module: Autumn Bauman Functions for ASIC configuration """ @@ -13,7 +14,7 @@ from bitstring import BitArray -from modules.nexysio import Nexysio +from core.nexysio import Nexysio from modules.setup_logger import logger @@ -22,12 +23,12 @@ class Asic(Nexysio): """Configure ASIC""" - def __init__(self, handle) -> None: + def __init__(self, handle, nexys) -> None: self._handle = handle + self.nexys = nexys self._chipversion = None - self._num_rows = 35 self._num_cols = 35 @@ -92,7 +93,7 @@ def num_rows(self): @num_rows.setter def num_rows(self, rows): self._num_rows = rows - + @property def num_chips(self): """Get/set number of chips in telescope setup @@ -105,76 +106,101 @@ def num_chips(self): def num_chips(self, chips): self._num_chips = chips - def enable_inj_row(self, row: int): - """Enable Row injection switch + def enable_inj_row(self, row: int, inplace:bool=True): + """ + Enable injection in specified row - :param row: Row number + Takes: + row: int - Row number + inplace:bool - True - Updates asic after updating pixel mask """ if row < self.num_rows: self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b000_00000_00000_00000_00000_00000_00000_00001 + if inplace: self.asic_update() - def enable_inj_col(self, col: int): - """Enable col injection switch + def enable_inj_col(self, col: int, inplace:bool=True): + """ + Enable injection in specified column - :param col: Col number + Takes: + col: int - Column number + inplace:bool - True - Updates asic after updating pixel mask """ if col < self.num_cols: self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | 0b010_00000_00000_00000_00000_00000_00000_00000 + if inplace: self.asic_update() - def enable_ampout_col(self, col: int): - """Select Col for analog mux and disable other cols + def enable_ampout_col(self, col: int, inplace:bool=True): + """ + Enables analog output, Select Col for analog mux and disable other cols - :param col: Col number + Takes: + col:int - Column to enable + inplace:bool - True - Updates asic after updating pixel mask """ + #Disable all analog pixels for i in range(self.num_cols): - self.asic_config['recconfig'][f'col{i}'][1] = self.asic_config['recconfig'][f'col{i}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 + self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] & 0b011_11111_11111_11111_11111_11111_11111_11111 + #Enable analog pixel in column self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'][f'col{col}'][1] | 0b100_00000_00000_00000_00000_00000_00000_00000 + + if inplace: self.asic_update() - def enable_pixel(self, col: int, row: int): - """Enable pixel comparator for specified pixel + def enable_pixel(self, col: int, row: int, inplace:bool=True): + """ + Turns on comparator in specified pixel - :param col: Col number - :param row: Row number + Takes: + col: int - Column of pixel + row: int - Row of pixel + inplace:bool - True - Updates asic after updating pixel mask """ if(row < self.num_rows and col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & ~(2 << row) - def disable_pixel(self, col: int, row: int): - """Disable pixel comparator for specified pixel + if inplace: self.asic_update() - :param col: Col number - :param row: Row number + def disable_pixel(self, col: int, row: int, inplace:bool=True): + """ + Disable comparator in specified pixel + + Takes: + col: int - Column of pixel + row: int - Row of pixel + inplace:bool - True - Updates asic after updating pixel mask """ if(row < self.num_rows and col < self.num_cols): self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] | (2 << row) + if inplace: self.asic_update() + def disable_inj_row(self, row: int): """Disable row injection switch - :param row: Row number """ if row < self.num_rows: self.asic_config['recconfig'][f'col{row}'][1] = self.asic_config['recconfig'].get(f'col{row}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b111_11111_11111_11111_11111_11111_11111_11110 + def disable_inj_col(self, col: int): """Disable col injection switch - :param col: Col number """ if col < self.num_cols: self.asic_config['recconfig'][f'col{col}'][1] = self.asic_config['recconfig'].get(f'col{col}', 0b001_11111_11111_11111_11111_11111_11111_11110)[1] & 0b101_11111_11111_11111_11111_11111_11111_11111 def get_pixel(self, col: int, row: int): - """Check if Pixel is enabled + """ + Checks if a given pixel is enabled - :param col: Col number - :param row: Row number + Takes: + col: int - column of pixel + row: int - row of pixel """ if row < self.num_rows: if self.asic_config['recconfig'].get(f'col{col}')[1] & (1<<(row+1)): return False - return True logger.error("Invalid row %d larger than %d", row, self.num_rows) @@ -186,6 +212,7 @@ def reset_recconfig(self): for key in self.asic_config['recconfig']: self.asic_config['recconfig'][key][1] = 0b001_11111_11111_11111_11111_11111_11111_11110 + @staticmethod def __int2nbit(value: int, nbits: int) -> BitArray: """Convert int to 6bit bitarray @@ -204,16 +231,16 @@ def __int2nbit(value: int, nbits: int) -> BitArray: def load_conf_from_yaml(self, chipversion: int, filename: str, **kwargs) -> None: """Load ASIC config from yaml - - + :param chipversion: AstroPix version :param filename: Name of yml file in config folder """ + chipname = kwargs.get('chipname', 'astropix') self.chipversion = chipversion self.chipname = chipname - with open(f"config/{filename}.yml", "r", encoding="utf-8") as stream: + with open(f"{filename}", "r", encoding="utf-8") as stream: try: dict_from_yml = yaml.safe_load(stream) except yaml.YAMLError as exc: @@ -254,39 +281,13 @@ def load_conf_from_yaml(self, chipversion: int, filename: str, **kwargs) -> None logger.error("%s%d config not found!", chipname, chipversion) sys.exit(1) - def write_conf_to_yaml(self, filename: str) -> None: - """Write ASIC config to yaml - - :param chipversion: Name of yml file in config folder - :param filename: Name of yml file in config folder - """ - dicttofile ={self.chip: - { - "telescope": {"nchips": self.num_chips}, - "geometry": {"cols": self.num_cols, "rows": self.num_rows} - } - } - - if self.num_chips > 1: - for chip in range(self.num_chips): - dicttofile[self.chip][f'config_{chip}'] = self.asic_config[f'config_{chip}'] - else: - dicttofile[self.chip]['config'] = self.asic_config - - with open(f"config/{filename}.yml", "w", encoding="utf-8") as stream: - try: - yaml.dump(dicttofile, stream, default_flow_style=False, sort_keys=False) - - except yaml.YAMLError as exc: - logger.error(exc) - def gen_asic_vector(self, msbfirst: bool = False) -> BitArray: - """Generate asic bitvector from digital, bias and dacconfig + """ + Generate asic bitvector from digital, bias and dacconfig :param msbfirst: Send vector MSB first """ - bitvector = BitArray() if self.num_chips > 1: @@ -300,7 +301,6 @@ def gen_asic_vector(self, msbfirst: bool = False) -> BitArray: bitvector.reverse() logger.info("Generated chip_%d config successfully!", chip) - else: for key in self.asic_config: for values in self.asic_config[key].values(): @@ -309,22 +309,26 @@ def gen_asic_vector(self, msbfirst: bool = False) -> BitArray: if not msbfirst: bitvector.reverse() - return bitvector + logger.debug(bitvector) - def update_asic(self) -> None: - """Update ASIC""" + return bitvector - if self.chipversion == 1: + def readback_asic(self): + asicbits = self.gen_asic_pattern(self.gen_asic_vector(), True, readback_mode = True) + print(asicbits) + self.nexys.write(asicbits) + + def asic_update(self): + """ + Remakes configbits and writes to asic. + Takes no input and does not return + """ + if self._chipversion == 1: dummybits = self.gen_asic_pattern(BitArray(uint=0, length=245), True) # Not needed for v2 - self.write(dummybits) + self.nexys.write(dummybits) # Write config - asicbits = self.gen_asic_pattern(self.gen_asic_vector(), True) - + asicbits = self.nexys.gen_asic_pattern(self.gen_asic_vector(), True) for value in asicbits: - self.write(value) - - def readback_asic(self): - asicbits = self.gen_asic_pattern(self.gen_asic_vector(), True, readback_mode = True) - print(asicbits) - self.write(asicbits) + self.nexys.write(value) + logger.info("Wrote configbits successfully") diff --git a/modules/decode.py b/core/decode.py similarity index 98% rename from modules/decode.py rename to core/decode.py index 240e7deb..1e232e60 100644 --- a/modules/decode.py +++ b/core/decode.py @@ -5,8 +5,8 @@ @author: Nicolas Striebig """ -import pandas as pd +import pandas as pd import re import math import binascii @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) class Decode: - def __init__(self, sampleclock_period_ns = 10): + def __init__(self, sampleclock_period_ns = 5): self.sampleclock_period_ns = sampleclock_period_ns self.bytesperhit = 5 @@ -120,6 +120,8 @@ def hits_from_readoutstream_old(self, readout: bytearray, reverse_bitorder: bool :returns: List of hits """ + if type(readout) == list: readout=bytearray(readout) + #Reverse Bitorder per byte if reverse_bitorder: readout = self.reverse_bitorder(readout) diff --git a/modules/injectionboard.py b/core/injectionboard.py similarity index 99% rename from modules/injectionboard.py rename to core/injectionboard.py index 0f591c7a..a908eb42 100644 --- a/modules/injectionboard.py +++ b/core/injectionboard.py @@ -8,7 +8,7 @@ import logging -from modules.nexysio import Nexysio +from core.nexysio import Nexysio from modules.setup_logger import logger PG_RESET = 2 diff --git a/modules/nexysio.py b/core/nexysio.py similarity index 98% rename from modules/nexysio.py rename to core/nexysio.py index 21cc4e12..0ffa8cea 100644 --- a/modules/nexysio.py +++ b/core/nexysio.py @@ -13,7 +13,7 @@ import logging import binascii -from modules.spi import Spi +from core.spi import Spi from modules.setup_logger import logger READ_ADRESS = 0x00 @@ -377,16 +377,17 @@ def get_configregister(self): """ return int.from_bytes(self.read_register(0), 'big') + def chip_reset(self) -> None: """ Reset SPI - Set res_n to 0 and back to 1 after short sleep - res_n is connected to FTDI Reg: 0 Bit: 4 + Set res_n to 0 for 1s + res_n is connected to FTDI Reg0: Bit: 4 """ # Set Reset bits 1 configregister = self.set_bit(self.get_configregister(), 4) self.write_register(0, configregister, True) - time.sleep(.1) + time.sleep(0.1) # Set Reset bits and readback bit 0 configregister = self.clear_bit(self.get_configregister(), 4) self.write_register(0, configregister, True) diff --git a/modules/spi.py b/core/spi.py similarity index 100% rename from modules/spi.py rename to core/spi.py diff --git a/modules/voltageboard.py b/core/voltageboard.py similarity index 98% rename from modules/voltageboard.py rename to core/voltageboard.py index 4f0be26f..597232a7 100644 --- a/modules/voltageboard.py +++ b/core/voltageboard.py @@ -7,7 +7,7 @@ """ from bitstring import BitArray -from modules.nexysio import Nexysio +from core.nexysio import Nexysio class Voltageboard(Nexysio): diff --git a/decode_postRun.py b/decode_postRun.py new file mode 100644 index 00000000..aeb1362c --- /dev/null +++ b/decode_postRun.py @@ -0,0 +1,135 @@ +""" +Decode raw data (bitstreams) after data-taking, save decoded information in CSV format identical to when running beam_test.py with option -c + +Author: Amanda Steinhebel +amanda.l.steinhebel@nasa.gov +""" + +from astropix import astropix2 +import glob +import binascii +import pandas as pd +import numpy as np +import logging +import argparse +import re + +from modules.setup_logger import logger + +#Initialize +def main(args): + + #Allow only -f or -d to be evoked - not both + if args.fileInput and args.dirInput: + logger.error("Input a single file with -f OR a single directory with -d... not both! Try running again") + exit() + + #Define boolean for args.fileInput + f_in = True if args.fileInput is not None else False + + #Create objet + astro = astropix2(offline=True) + + #Define output file path + if args.outDir is not None: + outpath = args.outDir + elif f_in: + try: #Mac path + dirInd = args.fileInput.rindex('/') + except ValueError: #Windows path + dirInd = args.fileInput.rindex('\\') + outpath = args.fileInput[:dirInd+1] #add 1 to keep final delimiter in path + elif args.dirInput is not None: + outpath = args.dirInput + + #Symmetrize structure + inputFiles = [args.fileInput] if f_in else glob.glob(f'{args.dirInput}*.log') + + #Run over all input files + for infile in inputFiles: + + #Define output file name + csvname = re.split(r'\\|/',infile)[-1][:-4] #split Mac or OS path; identify file name and eliminate '.log' + csvpath = outpath + csvname + '_offline.csv' + + #Setup CSV structure + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + #Import data file + f=np.loadtxt(infile, skiprows=6, dtype=str) + + #isolate only bitstream without b'...' structure + strings = [a[2:-1] for a in f[:,1]] + + for s in strings: + #convert hex to binary and decode + rawdata = list(binascii.unhexlify(s)) + hits = astro.decode_readout(rawdata, 0, printer = args.printDecode) + #Overwrite hittime - computed during decoding + hits['hittime']=np.nan + #Populate csv + csvframe = pd.concat([csvframe, hits]) + + #Save csv + logger.info(f"Saving to {csvpath}") + csvframe.to_csv(csvpath) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Post-run decoding') + parser.add_argument('-f', '--fileInput', default=None, required=False, + help='Input data file to decode') + + parser.add_argument('-d', '--dirInput', default=None, required=False, + help='Input directory of data files to decode') + + parser.add_argument('-o', '--outDir', default=None, required=False, + help='Output Directory for all decoded datafiles. Defaults to directory raw data is saved in') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + + parser.add_argument('-p', '--printDecode', action='store_true', default=False, required=False, + help='Print decoded info into terminal. Default: False') + + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging - print to terminal only + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + + main(args) \ No newline at end of file diff --git a/example_loop.py b/example_loop.py new file mode 100644 index 00000000..db6d214d --- /dev/null +++ b/example_loop.py @@ -0,0 +1,239 @@ +""" +Simple script to loop through pixels enabling one at a time, using astropix.py +Based off beam_test.py + +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Init +def main(args,row,col,injectPix): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + # Prepare everything, create the object + if args.inject: + astro = astropix2(inject=injectPix) #enable injections + else: + astro = astropix2() #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + astro.asic_init(yaml=ymlpath) + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + # If injection is on initalize the board + if args.inject: + astro.init_injection(inj_voltage=args.vinj) + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject: + astro.start_injection() + + i = 0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strPix = "_col"+str(col)+"_row"+str(row) + fname=strPix if not args.name else args.name+strPix+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Save final configuration to output file + ymlpathout="config"+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while (True): # Loop continues + + # Break conditions + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject: astro.stop_injection() + bitfile.close() # Close open file + astro.close_connection() # Closes SPI + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store_true', default=False, required=False, + help = 'Turn on injection. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-C', '--colrange', action='store', default=[0,33], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,33], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') + + parser.add_argument + args = parser.parse_args() + + # Logging + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + #loop over full array by default, unless bounds are given as argument + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + for c in range(args.colrange[0],args.colrange[1]+1,1): + injectPix=[r,c] + main(args,r,c,injectPix) + time.sleep(2) # to avoid loss of connection to Nexys \ No newline at end of file diff --git a/injectionScan.py b/injectionScan.py new file mode 100644 index 00000000..0d8ef84b --- /dev/null +++ b/injectionScan.py @@ -0,0 +1,300 @@ +""" +Script to loop through injection voltage values enabling a single pixel. Test all injection values on one pixel + +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Initialize +def main(args,fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + astro = astropix2(inject=args.inject) #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) #no updates in YAML + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml and analog pixel in row0 defined with input argument -a + astro.asic_init(yaml=ymlpath, analog_col = args.analog) + + #If injection, ensure injection pixel is enabled and initialize + if args.inject is not None: + astro.enable_pixel(args.inject[1],args.inject[0]) + astro.init_injection(inj_voltage=args.vinj) + + #Enable final configuration + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject is not None: + astro.start_injection() + + + max_errors = args.errormax + i = 0 + errors = 0 # Sets the threshold + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + fname="" if not args.name else newname+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Save final configuration to output file + ymlpathout=args.outdir +pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # Prepare text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + # Enables the hitplotter and uses logic on whether or not to save the images + if args.showhits: plotter = hitplotter.HitPlotter(35, outdir=(args.outdir if args.plotsave else None)) + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while errors <= max_errors: # Loop continues + + # This might be possible to do in the loop declaration, but its a lot easier to simply add in this logic + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + # We aren't using timeit, just measuring the diffrence in ns + if args.timeit: start = time.time_ns() + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + if args.timeit: + print(f"Readout took {(time.time_ns()-start)*10**-9}s") + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + errors += 1 + logger.warning(f"Decoding failed. Failure {errors} of {max_errors} on readout {i}") + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + # This loggs the end of it all + if errors > max_errors: + logger.warning(f"Decoding failed {errors} times on an index error. Terminating Progam...") + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # This handels the hitplotting. Code by Henrike and Amanda + if args.showhits: + # This ensures we aren't plotting NaN values. I don't know if this would break or not but better + # safe than sorry + if pd.isnull(hits.tot_msb.loc(0)): + pass + elif len(hits)>0:#safeguard against bad readouts without recorded decodable hits + #Isolate row and column information from array returned from decoder + rows = hits.location[~hits.isCol] + columns = hits.location[hits.isCol] + plotter.plot_event( rows, columns, i) + + # If we are logging runtime, this does it! + if args.timeit: + print(f"Read and decode took {(time.time_ns()-start)*10**-9}s") + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject is not None: astro.stop_injection() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-s', '--showhits', action='store_true', + default=False, required=False, + help='Display hits in real time during data taking') + + parser.add_argument('-p', '--plotsave', action='store_true', default=False, required=False, + help='Save plots as image files. If set, will be saved in same dir as data. Default: FALSE') + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt. Default: FALSE') + + parser.add_argument('-i', '--inject', action='store', default=None, type=int, nargs=2, + help = 'Turn on injection in the given row and column. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 300 mV') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0.') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-E', '--errormax', action='store', type=int, default='100', + help='Maximum index errors allowed during decoding. DEFAULT 100') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('--timeit', action="store_true", default=False, + help='Prints runtime from seeing a hit to finishing the decode to terminal') + + parser.add_argument('-L', '--loglevel', type=str, choices = ['D', 'I', 'E', 'W', 'C'], action="store", default='I', + help='Set loglevel used. Options: D - debug, I - info, E - error, W - warning, C - critical. DEFAULT: D') + """ + parser.add_argument('--ludicrous-speed', type=bool, action='store_true', default=False, + help="Fastest possible data collection. No decode, no output, no file.\ + Saves bitstreams in memory until keyboard interupt or other error and then writes them to file.\ + Use is not generally recommended") + """ + parser.add_argument + args = parser.parse_args() + + # Sets the loglevel + ll = args.loglevel + if ll == 'D': + loglevel = logging.DEBUG + elif ll == 'I': + loglevel = logging.INFO + elif ll == 'E': + loglevel = logging.ERROR + elif ll == 'W': + loglevel = logging.WARNING + elif ll == 'C': + loglevel = logging.CRITICAL + + # Logging + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + injs = [100, 200, 300, 400, 500, 600, 700, 800, 900] + for i,inj in enumerate(injs): + newname = args.name + "_" + str(inj) + "mVinj" + args.vinj = inj + main(args) \ No newline at end of file diff --git a/loop_DACs.py b/loop_DACs.py new file mode 100644 index 00000000..bf5753ff --- /dev/null +++ b/loop_DACs.py @@ -0,0 +1,246 @@ +""" +Simple script to loop through DAC settings, enabling one single pixel at a time using astropix.py +Based off beam_test.py + +Author: Amanda Steinhebel +""" + +#from msilib.schema import File +#from http.client import SWITCHING_PROTOCOLS +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + +# This is the dataframe which is written to the csv if the decoding fails +decode_fail_frame = pd.DataFrame({ + 'readout': np.nan, + 'Chip ID': np.nan, + 'payload': np.nan, + 'location': np.nan, + 'isCol': np.nan, + 'timestamp': np.nan, + 'tot_msb': np.nan, + 'tot_lsb': np.nan, + 'tot_total': np.nan, + 'tot_us': np.nan, + 'hittime': np.nan + }, index=[0] +) + + + +#Init +def main(args,dac): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + # Prepare everything, create the object + if args.inject: + astro = astropix2(inject=args.pixel) #enable injections + else: + astro = astropix2() #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + # Initialie asic - blank array, no pixels enabled, analog enabled for defined pixel or (0,0) by default + if args.DAC!="": + astro.asic_init(yaml=ymlpath, dac_setup={args.DAC: dac},analog_col = args.analog) + else: + astro.asic_init(yaml=ymlpath, analog_col = args.analog) + + #Enable single pixel from argument, or (0,0) if no pixel given + astro.enable_pixel(args.pixel[1],args.pixel[0]) + + # If injection is on initalize the board + if args.inject: + astro.init_injection(inj_voltage=args.vinj) + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if args.inject: + astro.start_injection() + + i = 0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strDac = args.DAC+"_"+str(dac)+"_" + fname=strDac if not args.name else args.name+"_"+strDac + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Save final configuration to output file + ymlpathout=args.outdir+pathdelim+args.yaml+"_"+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = args.outdir + '/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + # textfiles are always saved so we open it up + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + + while (True): # Loop continues + + # Break conditions + if args.maxruns is not None: + if i >= args.maxruns: break + if args.maxtime is not None: + if time.time() >= end_time: break + + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + + except IndexError: + # We write out the failed decode dataframe + hits = decode_fail_frame + hits.readout = i + hits.hittime = time.time() + + finally: + i += 1 + + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + + + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if args.inject: astro.stop_injection() + bitfile.close() # Close open file + astro.close_connection() # Closes SPI + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt') + + parser.add_argument('-i', '--inject', action='store_true', default=False, required=False, + help = 'Turn on injection. Default: No injection') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV). DEFAULT 400 mV') + + parser.add_argument('-a', '--analog', action='store', required=False, type=int, default = 0, + help = 'Turn on analog output in the given column. Default: Column 0. Set to None to turn off analog output.') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-r', '--maxruns', type=int, action='store', default=None, + help = 'Maximum number of readouts') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-p', '--pixel', action='store', default=[0,0], type=int, nargs=2, + help = 'Single enabled pixel (row col). Default: 0 0') + + parser.add_argument('-D', '--DAC', default='', required=False, + help = 'DAC value over which to scan. Default: None') + + parser.add_argument('-d', '--dacrange', action='store', default=[0,60,5], type=int, nargs=3, + help = 'Range to scan over DAC value and increment. Default: 0 60 5') + + parser.add_argument + args = parser.parse_args() + + # Logging stuff! + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + #loop over full array by default, unless bounds are given as argument + for d in range(args.dacrange[0],args.dacrange[1],args.dacrange[2]): + main(args,d) + time.sleep(5) # to avoid loss of connection to Nexys \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 196569df..00000000 --- a/main.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.load_conf_from_yaml(2,"testconfig") - #asic.asic_config['idacs']['vncomp'] = 60 - asic.write_conf_to_yaml(2,"testconfig_write") - asic.load_conf_from_yaml(2,"testconfig_write") - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.1])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 2.7 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.3, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 4000 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 255 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - nexys.spi_reset() - - inj.start() - - wait_progress(3) - - # Write 8*1000 Bytes to MOSI - nexys.write_spi_bytes(1000) - - # Read (Width 8 Bytes) until read FIFO is empty - readout = nexys.read_spi_fifo() - - print(binascii.hexlify(readout)) - - decode = Decode() - list_hits = decode.hits_from_readoutstream(readout) - - decode.decode_astropix2_hits(list_hits) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/modules/hitplotter.py b/modules/hitplotter.py new file mode 100644 index 00000000..29a01f53 --- /dev/null +++ b/modules/hitplotter.py @@ -0,0 +1,82 @@ +import matplotlib.pyplot as plt +import os + +class HitPlotter: + """ + Class for 2-D visualization of astroPix hits in real time. + """ + + def __init__(self, nPix = (40,40), d=0.5, outdir=None): + """ + Class for 2-D visualization of astroPix hits in real time. + + nPix: number of pixels in the array (int for square arrays or tuple) + d: Width of bars for strip visualization. + outdir: If not None, save problematic events as pdf images into this directory. + """ + + if isinstance(nPix, int): + self.nPix = (nPix, nPix) + else: + self.nPix = nPix + self.d = d + self.outdir = outdir + plt.gcf().set_size_inches(7,7, forward=True) + plt.axes().set_aspect('equal') + + if outdir is not None and not os.path.isdir(self.outdir): + os.makedirs(self.outdir) + + + def plot_event(self, row, col, eventID=None): + """ + Display the row/column hits for one astroPix event in real time. + In this 2-D visualization, one colored strip is plotted for + each row and column hit. + Strip colors correspond to the overall number of hits: + * Green for one row, one column. + * Red for more than two rows or columns. + * Orange otherwise. + + row: list or numpy.array of row hit locations + col: list of numpy.array of column hit locations + eventID: event number or timestamp (for plot title) + """ + + plt.clf() + ax = plt.gca() + + plt.axis([-1, self.nPix[1], -1, self.nPix[0]]) + + if (len(col) == 1) and (len(row) == 1): + theColor="green" + theSize = "x-large" + elif (len(col) > 2) or (len(row) > 2): + theColor="red" + theSize="x-small" + else: + theColor = "orange" + theSize = "small" + + for x in col: + plt.axvspan(x-self.d, x+self.d, alpha=0.4, facecolor=theColor, edgecolor="None" ) + + for y in row: + plt.axhspan(y-self.d, y+self.d, alpha=0.4, facecolor=theColor, edgecolor="None") + + + plt.xticks(col, weight = 'bold', color=theColor, size=theSize) + plt.yticks(row, weight = 'bold', color=theColor, size=theSize) + + title = f"Event {eventID}, {len(row)} + {len(col)} hits" + plt.title(title) + + plt.xlabel("Column") + plt.ylabel("Row") + plt.tight_layout() + plt.pause(1e-6) + + if self.outdir is not None and theColor in ["orange", "red"]: + plt.savefig(f"{self.outdir}/event_{eventID}.pdf") + + diff --git a/modules/pyKeithleyCtl.py b/modules/pyKeithleyCtl.py new file mode 100644 index 00000000..d347ba58 --- /dev/null +++ b/modules/pyKeithleyCtl.py @@ -0,0 +1,104 @@ +import numpy as np +import pyvisa as visa +import yaml +import time +import pandas as pd + +VISA_RM = visa.ResourceManager('@py') +class KeithleySupply(): + + '''Used to control a single Keithley 2450 Source Meter.''' + + + def __init__(self, address, n_ch=1, visa_resource_manager=VISA_RM): + resource_str = f'TCPIP0::{address:s}::INSTR' + self.resource = VISA_RM.open_resource(resource_str, write_termination='\n', read_termination='\n') + + self.write = self.resource.write + self.query = self.resource.query + + self.clear = self.resource.clear + self.close = self.resource.close + + @property + def IDN(self): + return self.ask("*IDN?") + + @property + def IDENTITY(self): + return f"IDN: {self.IDN.split(',')[-2]} IP: {self.IP}" + + def ask(self, question, verbose=False): + response = self.query(question) + if verbose: + print("Question: {0:s} - Response: {1:s}".format(question, str(response))) + return response + + def tell(self, statement): + return self.write(statement) + + def reset(self): + return self.write("*RST") + + def init(self): + return self.write("INIT") + + def wait(self): + return self.write("*WAI") + + def enable_output(self): + return self.tell(f"OUTP:STAT ON") + + def disable_output(self): + return self.tell(f"OUTP:STAT OFF") + + def set_voltage(self, voltage): + self.tell(f":SOURCE:FUNC VOLT") + self.tell(f":SOURCE:VOLT {voltage}") + + def get_voltage(self): + return self.ask(":SOURCE:VOLT?") + + def measure_current( self ): + return self.ask(":MEASURE:CURRENT:DC?") + + def measure_voltage( self ): + return self.ask(":MEASURE:VOLTAGE:DC?") + + def set_ocp(self, ocp): + self.tell(f":SOURCe:VOLTage:ILIMit {ocp}") + + def get_ocp(self): + return self.ask(":SOURCe:VOLTage:ILIMit?") + + def start_measurement(self, max_duration_s = 60*60, delay_s = 0.05): + self.tell('SENS:FUNC "CURR"') + self.tell("SENS:CURR:RANG:AUTO ON") + + self.tell(':TRACE:DELete "myBuffer"') + bufferSize = int(2.0*max_duration_s/delay_s) + + + self.tell(f'TRACE:MAKE "myBuffer", {bufferSize}' ) + self.tell(f':TRIGger:LOAD "LoopUntilEvent", COMM, 100, ENT, 1, "myBuffer"' ) + self.init() + + def stop_measurement(self, max_duration_s = 60*60, delay_s = 0.05): + self.write('*TRG') + nRow = int(self.ask(':TRAC:ACTUAL? "myBuffer"') ) + print(nRow) + result = self.ask(f':TRAC:DATA? 1, {nRow}, "myBuffer", REL, SEC, FRAC, SOUR, SOURSTAT, STAT, READ') + + return result, nRow + + def to_csv(self, result, nRow): + + nCol=7 + data=np.reshape( np.fromstring(result, sep=','), (nRow, nCol) ) + + df = pd.DataFrame(data, columns = "REL SEC FRAC SOUR SOURSTAT STAT READ".split() ) + + df.SOURSTAT = df.SOURSTAT.astype(int) + df.STAT = df.STAT.astype(int) + + return df \ No newline at end of file diff --git a/modules/setup_logger.py b/modules/setup_logger.py index 8f561950..da10598b 100644 --- a/modules/setup_logger.py +++ b/modules/setup_logger.py @@ -8,5 +8,16 @@ import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) \ No newline at end of file +# This sets the logger name. +# logname = "./runlogs/AstropixRunlog_" + datetime.datetime.strftime("%Y%m%d-%H%M%S") + ".log" +""" +# Loglevel +logging.basicConfig(filename=logname, + filemode='a', + format='%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s', + datefmt='%H:%M:%S', + level=logging.DEBUG) +""" + +#formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') +logger = logging.getLogger(__name__) diff --git a/pixelScan_injection.py b/pixelScan_injection.py new file mode 100644 index 00000000..21aa78a9 --- /dev/null +++ b/pixelScan_injection.py @@ -0,0 +1,213 @@ +""" +Script to loop through pixels enabling one at a time, using astropix.py. +Loop over full array and record from each pixel individually. +Based off beam_test.py and example_loop.py + +Author: Amanda Steinhebel +""" + +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +#Init +def main(args,row,col, fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + if boolInj: + astro = astropix2(inject=[row,col]) + else: + astro = astropix2() + + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + #Updates injection pixel + astro.asic_init(yaml=ymlpath, analog_col=col) + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + #If injection, ensure injection pixel is enabled and initialize + if boolInj is not None: + astro.enable_injection(col,row) + astro.init_injection(inj_voltage=args.vinj) + + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + if boolInj is not None: + astro.start_injection() + + i=0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strPix = "_col"+str(col)+"_row"+str(row)+"_" + fname=strPix if not args.name else args.name+strPix+"_" + + # Prepares the file paths + if args.saveascsv: # Here for csv + csvpath = args.outdir +'/' + fname + time.strftime("%Y%m%d-%H%M%S") + '.csv' + csvframe =pd.DataFrame(columns = [ + 'readout', + 'Chip ID', + 'payload', + 'location', + 'isCol', + 'timestamp', + 'tot_msb', + 'tot_lsb', + 'tot_total', + 'tot_us', + 'hittime' + ]) + + # Prepares the file paths + # Save final configuration to output file + ymlpathout=args.outdir+pathdelim+args.yaml+"_"+fname+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = args.outdir + pathdelim + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + while (True): # Loop continues + # Break conditions + if args.maxtime is not None: + if time.time() >= end_time: break + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + #print(binascii.hexlify(readout)) + + # Added fault tolerance for decoding, the limits of which are set through arguments + try: + hits = astro.decode_readout(readout, i, printer = True) + finally: + i += 1 + # If we are saving a csv this will write it out. + if args.saveascsv: + csvframe = pd.concat([csvframe, hits]) + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + if args.saveascsv: + csvframe.index.name = "dec_order" + csvframe.to_csv(csvpath) + if boolInj is not None: astro.stop_injection() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-C', '--colrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') + + parser.add_argument('-v','--vinj', action='store', default = None, type=float, + help = 'Specify injection voltage (in mV) to turn on injection. If argument not used, injection not enabled. DEFAULT None') + parser.add_argument('-c', '--saveascsv', action='store_true', + default=False, required=False, + help='save output files as CSV. If False, save as txt. Default: FALSE') + + + parser.add_argument + args = parser.parse_args() + + # Logging + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + boolInj = True if args.vinj is not None else False + + #loop over full array by default, unless bounds are given as argument + for c in range(args.colrange[0],args.colrange[1]+1,1): + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + if r==args.rowrange[0] and c==args.colrange[0]:#first pixel probed - connect to FPGA but leave open + main(args,r,c, fpgaDiscon=False) + elif r==args.rowrange[1] and c==args.colrange[1]: #final pixel probed - disconnect from FPGA upon completion + main(args,r,c, fpgaCon=False) + else: #for bulk of pixels, FPGA is already open. Do not reconnect and do not disconnect when completed, leave it open for the next pixel + main(args,r,c, fpgaCon=False, fpgaDiscon=False) + \ No newline at end of file diff --git a/readback_test.py b/readback_test.py deleted file mode 100644 index 33ffcf36..00000000 --- a/readback_test.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -"""""" -""" -Created on Sat Jun 26 00:10:56 2021 - -@author: Nicolas Striebig -""" - -from modules.asic import Asic -from modules.injectionboard import Injectionboard -from modules.nexysio import Nexysio -from modules.voltageboard import Voltageboard -from modules.decode import Decode - -from utils.utils import wait_progress - -import binascii - -import os -import time - -def main(): - - nexys = Nexysio() - - # Open FTDI Device with Index 0 - #handle = nexys.open(0) - handle = nexys.autoopen() - - # Write and read directly to register - # Example: Write 0x55 to register 0x09 and read it back - nexys.write_register(0x09, 0x55, True) - nexys.read_register(0x09) - - nexys.spi_reset() - nexys.sr_readback_reset() - - # - # Configure ASIC - # - - # Write to asicSR - asic = Asic(handle) - asic.update_asic() - - # Example: Update Config Bit - # asic.digitalconfig['En_Inj17'] = 1 - # asic.dacs['vn1'] = 63 - # asic.update_asic() - - # - # Configure Voltageboard - # - - # Configure 8 DAC Voltageboard in Slot 4 with list values - # 3 = Vcasc2, 4=BL, 7=Vminuspix, 8=Thpix - vboard1 = Voltageboard(handle, 4, (8, [0, 0, 1.1, 1, 0, 0, 1, 1.2])) - - # Set measured 1V for one-point calibration - vboard1.vcal = 0.989 - vboard1.vsupply = 3.3 - - # Update voltageboards - vboard1.update_vb() - - # Write only first 3 DACs, other DACs will be 0 - # vboard1.dacvalues = (8, [1.2, 1, 1]) - # vboard1.update_vb() - - # - # Configure Injectionboard - # - - # Set Injection level - injvoltage = Voltageboard(handle, 3, (2, [0.4, 0.0])) - injvoltage.vcal = vboard1.vcal - injvoltage.vsupply = vboard1.vsupply - injvoltage.update_vb() - - inj = Injectionboard(handle) - - # Set Injection Params for 330MHz patgen clock - inj.period = 100 - inj.clkdiv = 400 - inj.initdelay = 10000 - inj.cycle = 0 - inj.pulsesperset = 1 - - # - # SPI - # - - # Enable SPI - nexys.spi_enable() - nexys.spi_reset() - - # Set SPI clockdivider - # freq = 100 MHz/spi_clkdiv - nexys.spi_clkdiv = 10 - - #asic.dacs['vn1'] = 5 - - # Generate bitvector for SPI ASIC config - asic_bitvector = asic.gen_asic_vector() - spi_data = nexys.asic_spi_vector(asic_bitvector, True, 10) - - # Write Config via spi - # nexys.write_spi(spi_data, False, 8191) - - # Send Routing command - nexys.send_routing_cmd() - - # Reset SPI Read FIFO - - inj.start() - #inj.stop() - - wait_progress(3) - - #decode = Decode() - - - """ i = 0 - while os.path.exists("log/sample%s.log" % i): - i += 1 - - file = open("log/sample%s.log" % i, "w") """ - timestr = time.strftime("beam_readback_%Y%m%d-%H%M%S") - file = open("log/%s.log" % timestr, "w") - file.write(f"Voltageboard settings: {vboard1.dacvalues}\n") - file.write(f"Digital: {asic.digitalconfig}\n") - file.write(f"Biasblock: {asic.biasconfig}\n") - file.write(f"DAC: {asic.dacs}\n") - file.write(f"Receiver: {asic.recconfig}\n") - - readout = bytearray() - nexys.sr_readback_reset() - - while True: - wait_progress(2) - print("Readback") - #Readback - asic.readback_asic() - #asic.update_asic() - wait_progress(1) - print(f"SR readbackConfig Reg: {nexys.read_register(0x3D)}\n") - readout = nexys.read_spi_fifo_readback() - - file.write(str(binascii.hexlify(readout))) - print(binascii.hexlify(readout)) - - # inj.stop() - - # Close connection - nexys.close() - - -if __name__ == "__main__": - main() diff --git a/thresholdScan.py b/thresholdScan.py new file mode 100644 index 00000000..174f76e0 --- /dev/null +++ b/thresholdScan.py @@ -0,0 +1,181 @@ +""" +Script to loop through pixels enabling one at a time, using astropix.py. +For a variety of threshold values, loop over full array and only record the number of times +the comparator would have fired. +Based off beam_test.py and example_loop.py + +Author: Amanda Steinhebel +""" + +from astropix import astropix2 +import modules.hitplotter as hitplotter +import os +import binascii +import pandas as pd +import numpy as np +import time +import logging +import argparse + +from modules.setup_logger import logger + + +# This sets the logger name. +logdir = "./runlogs/" +if os.path.exists(logdir) == False: + os.mkdir(logdir) +logname = "./runlogs/AstropixRunlog_" + time.strftime("%Y%m%d-%H%M%S") + ".log" + + + +#Init +def main(args,row,col,dataF, fpgaCon:bool=True, fpgaDiscon:bool=True): + + # Ensures output directory exists + if os.path.exists(args.outdir) == False: + os.mkdir(args.outdir) + + if fpgaCon: + # Prepare everything, create the object + global astro + logger.info('Initiate FPGA connection') + astro = astropix2() #initialize without enabling injections + + astro.init_voltages(vthreshold=args.threshold) + + #Define YAML path variables + pathdelim=os.path.sep #determine if Mac or Windows separators in path name + ymlpath="config"+pathdelim+args.yaml+".yml" + + #Initiate asic with pixel mask as defined in yaml + astro.asic_init(yaml=ymlpath) + + #Enable single pixel in (col,row) + #Updates asic by default + astro.enable_pixel(col,row) + + + astro.enable_spi() + logger.info("Chip configured") + astro.dump_fpga() + + i = 0 + if args.maxtime is not None: + end_time=time.time()+(args.maxtime*60.) + strPix = "_"+str(args.threshold)[:-2]+"mVThresh_col"+str(col)+"_row"+str(row)+"_" + fname=strPix if not args.name else args.name+strPix+"_" + + # Prepares the file paths + # Save final configuration to output file + ymlpathout=outdir+pathdelim+args.yaml+"_"+fname+time.strftime("%Y%m%d-%H%M%S")+".yml" + astro.write_conf_to_yaml(ymlpathout) + # And here for the text files/logs + bitpath = outdir + pathdelim + fname + time.strftime("%Y%m%d-%H%M%S") + '.log' + bitfile = open(bitpath,'w') + # Writes all the config information to the file + bitfile.write(astro.get_log_header()) + bitfile.write(str(args)) + bitfile.write("\n") + + try: # By enclosing the main loop in try/except we are able to capture keyboard interupts cleanly + while (True): # Loop continues + # Break conditions + if args.maxtime is not None: + if time.time() >= end_time: break + + if astro.hits_present(): # Checks if hits are present + + time.sleep(.001) # this is probably not needed, will ask Nicolas + + readout = astro.get_readout(3) # Gets the bytearray from the chip + + # Writes the hex version to hits + bitfile.write(f"{i}\t{str(binascii.hexlify(readout))}\n") + #print(binascii.hexlify(readout)) + i += 1 + astro.asic_update() #must be done after every interrupt check + + # If no hits are present this waits for some to accumulate + else: time.sleep(.001) + # Ends program cleanly when a keyboard interupt is sent. + except KeyboardInterrupt: + logger.info("Keyboard interupt. Program halt!") + # Catches other exceptions + except Exception as e: + logger.exception(f"Encountered Unexpected Exception! \n{e}") + finally: + interrfile = open(interrpath,'a+') + interrfile.write(f"{r} \t {i} \n") + interrfile.close() + bitfile.close() # Close open file + if fpgaDiscon: + astro.close_connection() # Closes SPI + logger.info('FPGA Connection ended') + logger.info("Program terminated successfully") + # END OF PROGRAM + + + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Astropix Driver Code') + parser.add_argument('-n', '--name', default='', required=False, + help='Option to give additional name to output files upon running') + + parser.add_argument('-o', '--outdir', default='.', required=False, + help='Output Directory for all datafiles') + + parser.add_argument('-y', '--yaml', action='store', required=False, type=str, default = 'testconfig', + help = 'filepath (in config/ directory) .yml file containing chip configuration. Default: config/testconfig.yml (All pixels off)') + + parser.add_argument('-t', '--threshold', type = float, action='store', default=None, + help = 'Threshold voltage for digital ToT (in mV). DEFAULT 100mV') + + parser.add_argument('-M', '--maxtime', type=float, action='store', default=None, + help = 'Maximum run time (in minutes)') + + parser.add_argument('-C', '--colrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of columns. Default: 0 34') + + parser.add_argument('-R', '--rowrange', action='store', default=[0,34], type=int, nargs=2, + help = 'Loop over given range of rows. Default: 0 34') + + parser.add_argument + args = parser.parse_args() + + # Logging + loglevel = logging.INFO + formatter = logging.Formatter('%(asctime)s:%(msecs)d.%(name)s.%(levelname)s:%(message)s') + fh = logging.FileHandler(logname) + fh.setFormatter(formatter) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + + logging.getLogger().addHandler(sh) + logging.getLogger().addHandler(fh) + logging.getLogger().setLevel(loglevel) + + logger = logging.getLogger(__name__) + + #loop over threshold values for long weekend run + threshs = [25., 50., 75., 100., 150., 200.] + for t in threshs: + args.threshold = t + outdir = args.outdir + "/" + str(args.threshold)[:-2] + 'mV' + if os.path.exists(outdir) == False: + os.mkdir(outdir) + #loop over full array by default, unless bounds are given as argument + for c in range(args.colrange[0],args.colrange[1]+1,1): + interrpath = outdir + '/counts_' + str(args.threshold)[:-2] + 'mVThresh_' + args.name +'_col' + str(c) + "_"+ time.strftime("%Y%m%d-%H%M%S") + '.txt' + interrfile = open(interrpath,'a+') + interrfile.write(f"Row \t Counts \n") + interrfile.close() + for r in range(args.rowrange[0],args.rowrange[1]+1,1): + if r==args.rowrange[0] and c==args.colrange[0]:#first pixel probed - connect to FPGA but leave open + main(args,r,c, interrpath, fpgaDiscon=False) + elif r==args.rowrange[1] and c==args.colrange[1]: #final pixel probed - disconnect from FPGA upon completion + main(args,r,c, interrpath, fpgaCon=False) + else: #for bulk of pixels, FPGA is already open. Do not reconnect and do not disconnect when completed, leave it open for the next pixel + main(args,r,c, interrpath, fpgaCon=False, fpgaDiscon=False) + \ No newline at end of file