diff --git a/recipes-app/fofb-daqcapt/files/daqarchiver b/recipes-app/fofb-daqcapt/files/daqarchiver new file mode 100755 index 0000000000000000000000000000000000000000..268fac80bbbd76c7199e65e3c7cc00cb51ea3bc9 --- /dev/null +++ b/recipes-app/fofb-daqcapt/files/daqarchiver @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +import numpy as np +import deviceaccess as da +import logging +import os +import struct +import mmap +import multiprocessing as mp +import argparse +import datetime + +################################################################################################### +# LOGGER +################################################################################################### +# Install a BasicLogger +logger = logging.getLogger("FofbArchiver") +sh=logging.StreamHandler() +sh.setLevel(logging.DEBUG) +sh.setFormatter(logging.Formatter("%(processName)s-%(process)d %(levelname)s: %(message)s")) + + +################################################################################################### +# DEVICE ACCESS +################################################################################################### +da.setDMapFilePath("/opt/fofb/opcua-server/devices.dmap") +app = da.Device("APPUIO") +app.open() +actbuffer = app.getScalarRegisterAccessor(np.int32, "APP.daq_0.ACTIVE_BUF") + + +################################################################################################### +# INTERRUPT HANDLING +################################################################################################### + +def irq_wait(irqfd): + """ + Re-arm and wait on a UIO IRQ + """ + + logger.debug("Re-Arm IRQ") + os.write( irqfd, struct.pack( "I", 1 ) ) + + logger.debug("Wait IRQ") + try: + r = os.read(irqfd, 4) + except KeyboardInterrupt: + logger.warning("Waiting IRQ stopped by user") + return False + + logger.debug("Reads IRQ: {}".format(int.from_bytes(r, "little"))) + + return True + +def irq_disable(irqfd): + logger.debug("Disable IRQ") + os.write( irqfd, struct.pack( "I", 0 ) ) + +def irq_flush(irqfd): + """ + Flush all IRQ until reads nothing. + """ + os.set_blocking(irqfd, False) + r=1 + while True: + logger.debug("Flushing IRQ...") + os.write( irqfd, struct.pack( "I", 1 ) ) + try: + r = os.read(irqfd, 4) + except BlockingIOError: + break + + os.set_blocking(irqfd, True) + +################################################################################################### +# DAQ HANDLING +################################################################################################### + +def daq_configure(): + """ + Configure DAQ engine to use the continuous region (region 0) + """ + + logger.info("Configure DAQ") + + app.write("APP.daq_0.DOUBLE_BUF_ENA", np.uint(1)) + app.write("APP.daq_0.TAB_SEL", np.int(1), 0) + + +def daq_enable(ena): + if ena: + logger.info("Enable DAQ, region 0") + app.write("APP.daq_0.ENABLE", np.int(1)) + app.write("APP.DAQ_CONTROL", np.int(0)) + else: + logger.info("Disable DAQ, all regions") + app.write("APP.daq_0.ENABLE", np.int(0)) + app.write("APP.DAQ_CONTROL", np.int(2)) + + +def daq_trigger(): + logger.info("Fire a DAQ trigger") + app.write("APP.DAQ_CONTROL", np.uint32(1)) + app.write("APP.DAQ_CONTROL", np.uint32(0)) + +def daq_status(): + # Read registers with one bit per region + for reg in ["DOUBLE_BUF_ENA", "ACTIVE_BUF", "ENABLE"]: + vals = app.read("APP.daq_0."+reg, np.uint32) + logger.info("{:20} {:10} {:10}".format(reg, vals&1, (vals>>1)&1)) + + + # Read registers with one element per region + for reg in ["TAB_SEL", "STROBE_CNT", "SENT_BURST_CNT", "FIFO_STATUS"]: + vals = app.read("APP.daq_0."+reg, np.uint32) + logger.info("{:20} {:10} {:10}".format(reg, *vals)) + +def daq_current_buffer(): + actbuffer.read() + return actbuffer[0]&1 + + +################################################################################################### +# DDR HANDLING +################################################################################################### + +daqmmap = mmap.mmap(os.open("/dev/ddrpl", os.O_RDONLY | os.O_SYNC), + 0x2000_0000, mmap.MAP_SHARED, (mmap.PROT_READ)) # | mmap.PROT_WRITE)) + +daqmem = [np.ndarray(0x10000000, buffer=daqmmap, dtype='b', offset=o) for o in [0x00000000, 0x10000000]] + +################################################################################################### +# TASKERS +################################################################################################### + +def tasker_filer(filepath, buf): + logger.info("Start a filer tasker.") + + t1 = datetime.datetime.now() + + # Dump to file + try: + with open(filepath, "wb") as fd: + fd.write(buf.tobytes()) + except KeyboardInterrupt: + logger.warning("File writing stopped by user on '{}'.".format(filepath)) + return + + t2 = datetime.datetime.now() + logger.debug("Writing time: {}".format((t2-t1).total_seconds())) + +################################################################################################### +# ARCHIVER LOOP +################################################################################################### + +def archiver_loop(path): + + logger.info("Start an archiver loop") + irq_filepath = "/dev/ddrpl" + + logger.debug("Opening FD on {}".format(irq_filepath)) + irqfd = os.open(irq_filepath, os.O_RDWR | os.O_CLOEXEC) + + # Prepare reception buffer + + rbuf = np.empty((4, daqmem[0].size), dtype='b') + idxbuf = 0 + + # Flush + irq_flush(irqfd) + + # Ignore first IRQ, but note the buffer + logger.debug("Wait for first IRQ...") + if not irq_wait(irqfd): + logger.error("Error while waiting first IRQ.") + os.close(irqfd) + return False + tp=datetime.datetime.now() + + nbuf = daq_current_buffer() + logger.debug("Current buffer is {}, will read it on next IRQ.".format(nbuf)) + + + try: + while True: + if not irq_wait(irqfd): + logger.error("Error while waiting IRQ.") + break + t0 = datetime.datetime.now() + logger.debug("IRQ period : {}".format((t0-tp).total_seconds())) + tp=t0 + + # Copy to buffer + rbuf[idxbuf] = daqmem[nbuf] + t1 = datetime.datetime.now() + logger.debug("Copy time: {}".format((t1-t0).total_seconds())) + + # Launch job on rbuf + p = mp.Process( + name="archiver_filer", + target=tasker_filer, + args=( + path+"/Archive_{}.bin".format(t0.strftime("%Y%m%d_%H%M%S")), + rbuf[idxbuf], + )) + p.start() + + + # Increment buffer pointers + idxbuf = (idxbuf+1)%4 + nbuf = (nbuf+1)%2 + + except KeyboardInterrupt: + logger.warning("Archiver loop ended by user.") + os.close(irqfd) + + + +################################################################################################### +# CLI INTERFACE +################################################################################################### + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument("--config", action="store_true", help="Config DAQ.") + parser.add_argument("--stop", action="store_true", help="Stop DAQ.") + parser.add_argument("--start", action="store_true", help="Start DAQ.") + parser.add_argument("--arch", action="store_true", help="Start an archiver loop") + parser.add_argument("-S", action="store_true", help="Display DAQ status and quit.") + parser.add_argument("--path", type=str, help="Path to write file", default="./") + parser.add_argument("--log", help="Log level", default="INFO") + + args = parser.parse_args() + + #### Install logger + # Remove handler previously attached + for hdlr in logger.handlers: + logger.removeHandler(hdlr) + # Attach this handler + logger.addHandler(sh) + logger.setLevel(getattr(logging, args.log.upper())) + + + if args.S: + daq_status() + exit(0) + + if args.arch: + archiver_loop(args.path) + + exit(0) + + if args.config: + daq_configure() + + if args.stop: + daq_enable(False) + + if args.start: + daq_enable(True) + daq_trigger() + + diff --git a/recipes-app/fofb-daqcapt/files/daqcapt b/recipes-app/fofb-daqcapt/files/daqcapt new file mode 100755 index 0000000000000000000000000000000000000000..f631c654451a12ede4a22562e723e72f0f76a483 --- /dev/null +++ b/recipes-app/fofb-daqcapt/files/daqcapt @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +import time +import argparse +import numpy as np +import os, mmap, ctypes +import glob +import deviceaccess + +# DAQ memory zone +daqmem = mmap.mmap(os.open("/dev/ddrpl", os.O_RDWR | os.O_SYNC), + 0x40000000, mmap.MAP_SHARED, (mmap.PROT_READ | mmap.PROT_WRITE)) + +# DAQ device +deviceaccess.setDMapFilePath("/opt/fofb/map/devices.dmap") +appdev = deviceaccess.Device("APPUIO") +appdev.open() + +# Number of Bytes Per Sample +NBPS=8 + + +def configure(ena, N): + """ + Configure DAQ regions. + + ena: int + bitsel for enabling region (one bit per region) + N: int, list(int) + Number of samples. If int, same number for both region. + """ + + # Select tab 1 for both regions + appdev.write("APP.daq_0.TAB_SEL", np.int(1), 0) + appdev.write("APP.daq_0.TAB_SEL", np.int(1), 1) + + # Set N samples for both regions + if type(N) == int: + appdev.write("APP.daq_0.SAMPLES", np.int(N), 0) + appdev.write("APP.daq_0.SAMPLES", np.int(N), 1) + else: + appdev.write("APP.daq_0.SAMPLES", np.int(N[0]), 0) + appdev.write("APP.daq_0.SAMPLES", np.int(N[1]), 1) + + # Enable regions + appdev.write("APP.daq_0.ENABLE", np.int(ena)) + +def trigger(): + appdev.write("APP.DAQ_CONTROL", np.int(1)) + appdev.write("APP.DAQ_CONTROL", np.int(0)) + +def stop(b=True): + appdev.write("APP.DAQ_CONTROL", np.int(3*b)) + +def status(): + + for r in ["ENABLE", "ACTIVE_BUF"]: + print("{:>20} {:10}".format(r, + appdev.read("APP.daq_0."+r, np.dtype('u4')))) + + for r in ["TAB_SEL", "STROBE_CNT", "SAMPLES", "FIFO_STATUS", "SENT_BURST_CNT", "TRG_CNT_BUF0", "TRG_CNT_BUF1"]: + r0, r1 =appdev.read("APP.daq_0."+r, np.dtype('u4')) + print("{:>20} {:10} {:10}".format(r, r0, r1)) + +def write_capture(path="./", region=[0,1]): + d=time.strftime("%m%d_%H%M%S") + + for r, b, o in zip((0,1), NBPS*appdev.read("APP.daq_0.SAMPLES", np.dtype('u4')), [0,0x20000000]): + if r in region: + with open(path+"DAQCAPT_{}_R{}.bin".format(d, r), 'wb') as fp: + fp.write(daqmem[o:o+b]) + + return path+"DAQCAPT_{}_R{}.bin".format(d, r) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="DAQ capture and status") + + parser.add_argument("--configure", action="store_true", help="Configure") + parser.add_argument("--stop", action="store_true", help="Send stop signal") + parser.add_argument("-t", action="store_true", help="Trigger DAQ") + parser.add_argument("-c", action="store_true", help="Dump memory to file") + parser.add_argument("-R", type=int, choices=(0,1), help="Region selector, default is both") + + parser.add_argument("-N", type=int, default=1024, help="Number of samples to capture") + parser.add_argument("--path", type=str, default="./", help="Output path") + + parser.add_argument("-s", action="store_true", help="Print status (after other actions)") + + args=parser.parse_args() + + + if args.configure: + if args.R is None: + configure(3, args.N) + else: + configure(int(args.R)+1, args.N) + + if args.stop: + stop() + exit(0) + else: + stop(False) + + if args.t: + trigger() + + if args.c: + if args.R is None: + fn=write_capture(args.path) + else: + fn=write_capture(args.path, [args.R, ]) + print(fn) + + if args.s: + status() + diff --git a/recipes-app/fofb-daqcapt/fofb-daqcapt_1.0.bb b/recipes-app/fofb-daqcapt/fofb-daqcapt_1.0.bb new file mode 100644 index 0000000000000000000000000000000000000000..2e37e7fc8954eef6ed22aad8121ad391557535ef --- /dev/null +++ b/recipes-app/fofb-daqcapt/fofb-daqcapt_1.0.bb @@ -0,0 +1,19 @@ +SUMMARY = "Simple python tool to use DESY DAQ" +SECTION = "opt" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" + +SRC_URI = " file://daqcapt" + +RDEPENDS_${PN}=" deviceaccess-mapfiles deviceaccess-python-bindings" + +FILES_${PN}+="/usr/bin/daqcapt" +FILES_${PN}+="/usr/bin/daqarchiver" + + +do_install() { + # Add FPGA version reader and FoFb Configurator + install -d ${D}/usr/bin/ + install -m 0755 ${WORKDIR}/daqcapt ${D}/usr/bin/daqcapt + install -m 0755 ${WORKDIR}/daqcapt ${D}/usr/bin/daqarchiver +} diff --git a/recipes-core/images/zup-image-soleil-fofb.bb b/recipes-core/images/zup-image-soleil-fofb.bb index 5692de2e09c7731bcf4b15d64d6f99e863550ebc..c69ef873190a671cc7cddc3ead6916470bf18134 100644 --- a/recipes-core/images/zup-image-soleil-fofb.bb +++ b/recipes-core/images/zup-image-soleil-fofb.bb @@ -28,6 +28,7 @@ IMAGE_INSTALL_append = " deviceaccess" IMAGE_INSTALL_append = " deviceaccess-python-bindings" IMAGE_INSTALL_append = " fofb-opcua-server" IMAGE_INSTALL_append = " fofb-init" +IMAGE_INSTALL_append = " fofb-daqcapt" IMAGE_INSTALL_append = " nfs-utils" # We do not need that