Discussions about active development projects
#1150 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. :)
#1152 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
#1839 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 :-)
#1840 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
#1841 by Laurent
Thu Apr 09, 2015 1:31 pm
Update: when we replace the loop:
Code: Select allint i;
    for (i=0; i < N; i++) {
        data[i] = cha_signal[(trig_ptr+i)%BUF];
    }
by
Code: Select allint 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.
#1951 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.
#1969 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).
#1977 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.
#1984 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.

Who is online

Users browsing this forum: Bing [Bot] and 1 guest