Triggered high speed acquisition

Discussions about active development projects
Grozomah
Posts: 5
Joined: Fri Oct 10, 2014 10:36 am

Triggered high speed acquisition

Post by Grozomah » Mon Nov 17, 2014 3:32 pm

I spent the last few weeks working on a triggered high speed acquisition, and I think that my work is finally at a presentable level. And while experienced RedPitayans might be far from impressed by my few lines of code this project might be useful for other beginners like me, who are still trying to crawl from under our rocks.

The What:
I'm working on a program that would effectively turn my RP into a radiation spectrometer. The first stage - presented here - is a triggered acquisition ability. That means that RP continuously gathers input, until something interesting (say a high energy gamma photon crashing into a semiconductor detector) causes the input voltage to rise (or fall) above a certain threshold. At that time RP happily measures another N samples, and writes the whole thing down into a file. Then repeats the process as often as required, appending events to the file.

The Why:
You might ask: why not simply change the acquire.c to loop several times? That should do the job just as well, right?
It turns out that acquire is pretty slow, since it allows for more oscilloscope-related options and has a hard time sampling more than, say, 40 traces per second. My program is much faster, capable of sampling e.g. tens of thousands reasonably long traces per second.

The Where:
I've uploaded the current project to github: https://github.com/Grozomah/trigger
Alternatively you can simply copy this code:

Code: Select all

/**
 * $Id: trigger.c peter.ferjancic 2014/11/17 $
 *
 * @brief Red Pitaya triggered acquisition of multiple traces
 *
 * (c) Red Pitaya  http://www.redpitaya.com
 *
 * This part of code is written in C programming language.
 * Please visit http://en.wikipedia.org/wiki/C_(programming_language)
 * for more details on the language used herein.
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stddef.h>
#include <sys/param.h>
#include "fpga_osc.h"

//Buffer depth 
const int BUF = 16*1024;
const int N = 12; 			// desired length of trace (1,..., 16383)
const int decimation = 1; 	// decimation: [1;8;64;1024;8192;65536]

int main(void) 
{
	// initialization
	int start = osc_fpga_init(); 
	if(start) {
	    printf("osc_fpga_init didn't work, retval = %d",start);
	    return -1;
	}
	FILE * fp;
	fp = fopen ("file.txt", "w+"); // open the file output is stored
	int * cha_signal; 
	int * chb_signal;

	// set acquisition parameters
	osc_fpga_set_trigger_delay(N);
	g_osc_fpga_reg_mem->data_dec = decimation;

	// put sampling engine into reset
	osc_fpga_reset();

	// define initial parameters
	int trace_counts; 		// counts how many traces were sampled in for loop
	int trig_ptr; 			// trigger pointer shows memory adress where trigger was met
	int trig_test;			// trigger test checks if writing the trace has completed yet
	int trigger_voltage= 0; // enter trigger voltage in [V] as parameter [1V...~600 RP units]
	g_osc_fpga_reg_mem->cha_thr = osc_fpga_cnv_v_to_cnt(trigger_voltage); //sets trigger voltage

	/***************************/
	/** MAIN ACQUISITION LOOP **/
	/***************************/
	for (trace_counts=0; trace_counts<10; trace_counts++)
	{
		/*Set trigger, begin acquisition when condition is met*/
		osc_fpga_arm_trigger(); //start acquiring, incrementing write pointer
		osc_fpga_set_trigger(0x2); // where do you want your triggering from?
	 	/*    0 - end of acquisition/no acquisition
	 	*     1 - trig immediately
     	*     2 - ChA positive edge 
     	*     3 - ChA negative edge
     	*     4 - ChB positive edge 
     	*     5 - ChB negative edge
     	*     6 - External trigger 0
     	*     7 - External trigger 1*/
	 	// Trigger always changes to 0 after acquisition is completed, write pointer stops incrementing
	 	//->fpga.osc.h l66


	    /*Wait for the acquisition to finish = trigger is set to 0*/
     	trig_test=(g_osc_fpga_reg_mem->trig_source); // it gets the above trigger value
     	// if acquistion is not yet completed it should return the number you set above and 0 otherwise
     	while (trig_test!=0) // with this loop the program waits until the acquistion is completed before cont.
     	{
     		trig_test=(g_osc_fpga_reg_mem->trig_source);
     	}
     	//->fpga_osc.c l366


	    trig_ptr = g_osc_fpga_reg_mem->wr_ptr_trigger; // get pointer to mem. adress where trigger was met
	    //->fpga_osc.c l283
	    osc_fpga_get_sig_ptr(&cha_signal, &chb_signal); 
 	    //->fpga_osc.c l378


	    /*now read N samples from the trigger pointer location.*/
	    int i;
	    int ptr;
	    for (i=0; i < N; i++) {
	        ptr = (trig_ptr+i)%BUF;

        	if (cha_signal[ptr]>=8192) // properly display negative values fix
        	{
        		printf("%d ",cha_signal[ptr]-16384);
        		fprintf(fp, "%d ", cha_signal[ptr]-16384);
        	}
        	else
        	{
        		printf("%d ",cha_signal[ptr]);
        		fprintf(fp, "%d ", cha_signal[ptr]);
        	}  
	    }
	    printf("\n");
	    fprintf(fp, "\n");

	}

	// cleaning up all nice like mommy taught me
	fclose(fp);
    osc_fpga_exit();
	return 0;
}
and compile it. Just make sure you have the standard "fpga_osc" files nearby.

The How:
I tried to comment the code (trigger.c) as much as possible, so hopefully everyone will be able to figure out quickly what stuff is and how to adapt/upgrade the program to your needs. Still, feel free to ask, if there is anything unclear.

The Who:
Roger Daltrey, Pete Townshend on the guitar, John Entwistle on the bass and Keith Moon as the drummer.
As for the project: a thank you goes to Ales Bardorfer, John Smithe, Nils Roos and of course Matjaž Vencelj for your help so far. :)

dvorakvik
Posts: 16
Joined: Fri Sep 26, 2014 9:21 pm

Re: Triggered high speed acquisition

Post by dvorakvik » Tue Nov 18, 2014 1:21 pm

Dear Grozomach,

many thanks for publishing your application. It is not usefull only in high energy gamma photon crashing into a semiconductor detector experiments. I try now to use your code in laser light reflection experiments form metal (gold and silver) nanoparticles in "pfysiological fluids".

Again many thanks

Viktor

Grozomah
Posts: 5
Joined: Fri Oct 10, 2014 10:36 am

Re: Triggered high speed acquisition

Post by Grozomah » Tue Nov 18, 2014 10:06 pm

Hey Victor!

I'm just glad this work is useful to someone else as well. :)
Feel free to let me know if you have any questions.

Cheers!

Laurent
Posts: 18
Joined: Mon Jun 23, 2014 6:38 pm
Location: Paris

Re: Triggered high speed acquisition

Post by Laurent » Thu Apr 09, 2015 8:56 am

Thanks for this useful code.

My need is to sample 1024 to 4096 samples at 125MS/s with an externat trigger at >10kHz and simultaneously transfer the data through UDP. With your code and a standard UDP transfer, I can reach only 2kHz. This is limited by the time taken by the loop to get the data from the FPGA:

Code: Select all

    int i;
    for (i=0; i < N; i++) {
        data[i] = cha_signal[(trig_ptr+i)%BUF];
    }
when I try a memcpy(data, cha_signal, N), I get a bus error when N>16.

Any help to accelerate this loop will be welcomed :-)

Arnold
Posts: 54
Joined: Wed Mar 11, 2015 3:07 pm

Re: Triggered high speed acquisition

Post by Arnold » Thu Apr 09, 2015 12:09 pm

Hi,

great work.

looks like you run this on linux!?
Any plans to run this on bare-metal? how can you receive the adc samples on the arm (bare-metal)?

Thanks,
A

Laurent
Posts: 18
Joined: Mon Jun 23, 2014 6:38 pm
Location: Paris

Re: Triggered high speed acquisition

Post by Laurent » Thu Apr 09, 2015 1:31 pm

Update: when we replace the loop:

Code: Select all

int i;
    for (i=0; i < N; i++) {
        data[i] = cha_signal[(trig_ptr+i)%BUF];
    }
by

Code: Select all

int i;
    for (i=0; i < N; i+=16) {
        memcpy(&data[i], &cha_signal[(trig_ptr+i)%BUF], 16*sizeof(int);
    }
I can transfer data twice as fast (4kHz). The limit of 16*sizeof(int) (above, there is a bus error) might be related to the AXI bus size.

I am still not running at the targeted 10kHz, though.

brian
Posts: 4
Joined: Wed Apr 29, 2015 10:07 am

Re: Triggered high speed acquisition

Post by brian » Fri May 01, 2015 1:09 am

Hi, good to know people have the same purpose for this hardware as me :).

So if I understand you correctly, you have set an internal trigger mechanism in the software to start recording when a rising edge (from your detector amplifier) exceeds a certain level? Does it behave like you expected?

I suppose you analyse the pulses afterwards using separate (off-line) peak shape analysis?

Cheers,


Brian.

trollhassel
Posts: 9
Joined: Mon Jan 19, 2015 9:20 am

Re: Triggered high speed acquisition

Post by trollhassel » Tue May 05, 2015 2:41 pm

Hey!

Thank you very much for the code of yours, Peter!
We are using this for an ultrasound pulse-echo application, so we need as much speed as possible. This is the fastest way we found it to be done thus far:

Code: Select all

/**
 * $Id: trigger.c peter.ferjancic 2014/11/17 $
 * https://github.com/Grozomah/trigger/blob/master/trigger.c
 * @brief Red Pitaya triggered acquisition of multiple traces
 *
 * (c) Red Pitaya  http://www.redpitaya.com
 *
 * This part of code is written in C programming language.
 * Please visit http://en.wikipedia.org/wiki/C_(programming_language)
 * for more details on the language used herein.
 */
 
 /*******************************************************************************
 * Code added by the Red Pitaya bachelor thesis group:							*
 * + Read out of EEPROM calibration constants									*
 * + Argument passing															*
 * + Sending of data via TCP													*
 * + Infinite loop reading out data												*
 * + Minor structural adjustments												*
 ********************************************************************************/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stddef.h>
#include <sys/param.h>
#include "fpga_osc.h"
#include "calib.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <time.h>

#define PORT 9930
	
void err(char *s)
{
    perror(s);
    exit(1);
}

//Buffer depth 
const int BUF = 16*1024;
const char *g_argv0 = NULL;
int counter=0;

rp_calib_params_t rp_calib_params;
/** Pointer to externally defined calibration parameters. */
rp_calib_params_t *gen_calib_params = NULL;

void usage() {

    const char *format =
        "\n"
		"Version: Hans-Kristian Yddal 2015-03-12\n"
		"Original version: trigger.c peter.ferjancic 2014/11/17\n"
        "Usage: %s   Trigger Samples Decimation Loops\n"
        "\n"
        "\tTrigger     0: End of acquisition/no acquisition\n"
		"\t            1: Trigger immediately\n"
		"\t            2: Channel A rising edge\n"
		"\t            3: Channel A falling edge\n"
		"\t            4: Channel B rising edge\n"
		"\t            5: Channel B falling edge\n"
		"\t            6: External trigger 0\n"
		"\t            7: External trigger 1\n"
		"\tSamples     Desired length of trace (1,..., 16383)\n"
        "\tDecimation  [1;8;64;1024;8192;65536]\n"
        "\tLoops       Amount of times to read signal (0 for infinite loop)\n"
        "\n";

    fprintf(stderr, format, g_argv0);
}

int main(int argc, char *argv[])  // argc er antall argument *argv[] er array med inndata
{
	g_argv0 = argv[0];
//	clock_t start_t, end_t, total_t;
	// argument parsing
	if ( argc < 4 ) {
        usage();
        return -1;
    }
	
	// Trigger argument parsing
	int trigger = atoi(argv[1]);
	if ( (trigger > 7) | (trigger < 0) ) {
		fprintf(stderr, "Invalid trigger argument: %s\n", argv[1]);
        usage();
        return -1;
	}
	
	// Samplesize argument parsing
	int sampleSize = atoi(argv[2]);
	if ((sampleSize < 1) | (sampleSize > 16384)){
		fprintf(stderr, "Invalid samples size: %s\n", argv[2]);
        usage();
        return -1;
	}
	
	// Decimation argument parsing
	int decimation = atoi(argv[3]);
	if ( (decimation == 1) | (decimation == 8) | (decimation == 64) | (decimation == 1024) | (decimation == 8192) | (decimation == 65536))
	{
	}
	else{
		fprintf(stderr, "Invalid decimation: %s\n", argv[3]);
        usage();
        return -1;
	}
	
	// Loop count argument parsing
	int loop = atoi(argv[4]);
	if (loop < 0)
	{
		fprintf(stderr, "Invalid loop count: %s\n", argv[3]);
        usage();
        return -1;
	}
	
	// Get calibration values from EEPROM
	rp_default_calib_params(&rp_calib_params);
    gen_calib_params = &rp_calib_params;

    if(rp_read_calib_params(gen_calib_params) < 0) {
        fprintf(stderr, "rp_read_calib_params() failed, using default"
            " parameters\n");
    }
	// use this to acquire calibrated offset: int offset = gen_calib_params->fe_ch1_dc_offs;

	// initialization
	int start = osc_fpga_init(); 
	if(start) 
	{
	    printf("osc_fpga_init didn't work, retval = %d",start);
	    return -1;
	}

	int * cha_signal;
	int * chb_signal;

	// set acquisition parameters
	osc_fpga_set_trigger_delay(sampleSize);
	g_osc_fpga_reg_mem->data_dec = decimation;

	// put sampling engine into reset
	osc_fpga_reset();
	
	// define initial parameters
	int trace_counts; 		// counts how many traces were sampled in for loop
	int trig_ptr; 			// trigger pointer shows memory adress where trigger was met
	int trig_test;			// trigger test checks if writing the trace has completed yet
	int trigger_voltage= 1; // enter trigger voltage in [V] as parameter [1V...~600 RP units]
	g_osc_fpga_reg_mem->chb_thr = osc_fpga_cnv_v_to_cnt(trigger_voltage); //sets trigger voltage
	
	// TCP init
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT); 
    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 
    listen(listenfd, 10); 
	connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); //Halts program until TCP connection is made

	trig_test=(g_osc_fpga_reg_mem->trig_source); // if acquistion is not yet completed it should return the number you set above and 0 otherwise
	if (loop > 0)
	{
		for (trace_counts=0; trace_counts<loop; trace_counts++)
		{
			osc_fpga_arm_trigger();
			osc_fpga_set_trigger(trigger); 
			trig_test=(g_osc_fpga_reg_mem->trig_source); // if acquistion is not yet completed it should return the number you set above and 0 otherwise
			while (trig_test!=0) // with this loop the program waits until the acquistion is completed before continue.
			{
				trig_test=(g_osc_fpga_reg_mem->trig_source);
			}

			trig_ptr = g_osc_fpga_reg_mem->wr_ptr_trigger; // get pointer to mem. adress where trigger was met
			osc_fpga_get_sig_ptr(&cha_signal, &chb_signal); 

			if (trig_ptr > (BUF-sampleSize)) // Enter logic to transition from end to beginning of cha_signal buffer.
			{
				write(connfd, &cha_signal[trig_ptr], sizeof(int)*(BUF-trig_ptr));
				write(connfd, &cha_signal[0], sizeof(int)*(sampleSize-(BUF-trig_ptr)));
			}
			else // Enter simple logic to send sampleSize from trigger point
			{
				write(connfd, &cha_signal[trig_ptr], sizeof(int)*sampleSize);
			}
		}
	}
	else
	{
		while (1) 
		{
			osc_fpga_arm_trigger();
			osc_fpga_set_trigger(trigger); 
			trig_test=(g_osc_fpga_reg_mem->trig_source); // if acquistion is not yet completed it should return the number you set above and 0 otherwise
			while (trig_test!=0) 
			{
				trig_test=(g_osc_fpga_reg_mem->trig_source);
			}

			trig_ptr = g_osc_fpga_reg_mem->wr_ptr_trigger; 
			osc_fpga_get_sig_ptr(&cha_signal, &chb_signal); 
			
			if (trig_ptr > (BUF-sampleSize)) // Enter logic to transition from end to beginning of cha_signal buffer.
			{
				write(connfd, &cha_signal[trig_ptr], sizeof(int)*(BUF-trig_ptr));
				write(connfd, &cha_signal[0], sizeof(int)*(sampleSize-(BUF-trig_ptr)));
			}
			else // Enter simple logic to send sampleSize from trigger point
			{
				write(connfd, &cha_signal[trig_ptr], sizeof(int)*sampleSize);
			}
		}	
	}

	// cleaning up all nice like mommy taught me
	close(connfd);
    osc_fpga_exit();
	return 0;
}
The way we do it is to dump the data from memory into TCP packets. (We tried with UDP packets, but data got lost. The TCP vs UDP speed was not significant.) This is a lot faster than going through for loops. The RAW data is then adjusted in LabVIEW instead of on the Red Pitaya.

We've also added input paramaters.

I am currently working on a way to store the data in 16 bit memory in the FPGA, but we are reaching the end of our bachelor thesis, so I have to prioritize writing a damn report now :/ If anyone finds a nice way to do this I would be very happy if you shared it :) This is my silly attempt thus far (red_pitaya_scope.v edited):

Code: Select all

// Read
integer buffity=3;
always @(posedge adc_clk_i) begin
    if (adc_rstn_i == 1'b0) begin
        if (buffity == 3) begin
            buffity = 1;
        end		
		else begin
		  buffity = buffity + 1;
		end
		adc_rval <= 4'h0;
	end
    else if (buffity >= 2) begin
		adc_rval <= {adc_rval[2:0], (ren || wen)};
		buffity = 3;
	end
end
assign adc_rd_dv = adc_rval[3];

always @(posedge adc_clk_i) begin
  if (adc_rstn_i == 1'b0) begin
		adc_rvalb <= 4'h0;
		end
  else
       adc_rvalb <= {adc_rvalb[2:0], (ren || wen)};
   end
end
assign adc_rd_dvb = adc_rvalb[3];

integer flip=3;
reg init=0;
always @(posedge adc_clk_i) begin
   adc_raddr   <= addr[RSZ+1:2] ; // address synchronous to clock
   adc_a_raddr <= adc_raddr     ; // double register 
	if (init == 0) begin
		temp_addr <= adc_a_raddr;
		init = 1;
		flip = 1;
	end

	if (flip == 0) begin
		if (temp_addr != adc_a_raddr) begin
			flip = 1;
			temp_addr <= adc_a_raddr;
		end
		else begin
			adc_a_rd_aa    <= adc_a_buf[adc_a_raddr];
		end
	end
	else begin
		if (temp_addr == adc_a_raddr) begin
			adc_a_rd_ab    <= adc_a_buf[adc_a_raddr];
		end
		else begin
			flip = 0;
			temp_addr <= adc_a_raddr;
		end
	end

	adc_b_raddr <= adc_raddr;
	adc_b_rd    <= adc_b_buf[adc_b_raddr];
end

// Then I send the data like this:
     20'h1????				: begin ack <= adc_rd_dv;	  rdata <= {2'h0,adc_a_rd_aa, 2'h0,adc_a_rd_ab}	; end
This is just a pure mess though... I have allmost no idea as to what I am doing since my FPGA skills are limited, and even more my Verilog skills. The idea was to load two sets of 16-bit data into the 32-bit memory instead of one... but this causes a lot of problem with control as to where the trigger happened, and the timing is off at some samples. (Data is stored in the wrong order).

brian
Posts: 4
Joined: Wed Apr 29, 2015 10:07 am

Re: Triggered high speed acquisition

Post by brian » Thu May 07, 2015 2:13 am

Hi Trollhassel,

So I understand from your post that you only implemented the first code segment, right? Can you give us any information on what rates you are getting with that?

Cheers,


Brian.

trollhassel
Posts: 9
Joined: Mon Jan 19, 2015 9:20 am

Re: Triggered high speed acquisition

Post by trollhassel » Fri May 08, 2015 11:05 am

Hi Brian.

Yes, that is correct, I have only implemented the first code I posted.

I am yet to find a good method of testing the rates I am getting, but if data is to be transfered from the Red Pitaya via ethernet this is the fastest method we have found thus far (TCP). UDP has been tested as well, but too many packets were lost. The rate has only been measured with a stop watch over a big sample and then calculating a mean value.

10000 loops of 5000 samples takes ~18 seconds which is (10000*5000)S/18s = 2.7 kSamples/second.

From what I understand this rate should be possible to easily double if data is stored in 16bit memory instead of 32bit memory in the FPGA, but my skills are far too limited at this time to achieve this.

Post Reply
jadalnie klasyczne ekskluzywne meble wypoczynkowe do salonu ekskluzywne meble tapicerowane ekskluzywne meble do sypialni ekskluzywne meble włoskie

Who is online

Users browsing this forum: huubh and 1 guest