Page 1 of 3

Laser trigger

Posted: August 14th, 2015, 10:25 am
by rojhann
Hi everyone!

I was looking around to find a solution for our system (see below). I learned from collaborators that they were using FPGAs to tackle similar processes. However they are using NI embedded system which are expensive, and everything is coded in Labview. By doing some research I stumbled onto open source boards such as the papilio or the mojo.

Here are our needs:

We receive a square signal from a camera and depending on what the users set, as behavior (On=always on, Off=always off, Rising trigger=pulse at the rising edge of the camera signal, Falling trigger=same with falling, Camera=follow the camera trigger) and pulse length (only applicable for rising and falling trigger), a digital output is sent to a laser (also a square signal with the properties corresponding to the behavior and if necessary the pulse length). The pulse ranges from 1us to 100ms, the camera signal period is from 15ms to 100ms.

Now, an Arduino Due can do the job, although the microsecond regime requires a bit of optimization, but nothing fancy (like directly modifying pin registers). If we have several lasers, then an Arduino Due alone does not seem to be able to be fast enough to handle all the different pulses. For the moment we have four Arduinos on our set-up, that receive the same camera signal and independently handle their own laser.

We also would like to increase at some point the complexity by having alternating trigger for the lasers (e.g. one laser pulsing on frame i, the second one on frame i+1). This means that we need our Arduinos to be in sync.

Besides this, we also control several servos with another Arduino.

For fun I designed the digital circuit in logisim for one laser. I am a bit struggling with the d-flip-flop to make it work there in order to obtain a rising trigger.

My questions are then the following:

1) Verilog seems quite a different world and I am afraid I would not be able to code such a system. How difficult do you evaluate the task?

2) Would graphical design tools than generate code (like what is used for the papilio if I am not mistaken) do a decent job?

3) Can an Arduino code run on the soft processor (for instance for when the user wants to change the behavior of one laser) while the fpga takes care of the triggering?

4) How do the soft processor exchange information with the FPGA? Can this be achieved through the Arduino language? (setting a value in memory, like pulse length)

Thanks!

cheers

Re: Laser trigger

Posted: August 14th, 2015, 12:25 pm
by embmicro
Hey rojhann,

Not only can you use an FPGA for this, but this is pretty much an ideal application.

If you haven't already you should check out the Mojo IDE and Lucid. You will probably find it easier to get started than Verilog. You can use Xilinx's schematic tools (what Papilio shows) but I find them a bit clunky to work with.

How does the user set the parameters? If you have switches or similar you could simply connect those to the FPGA and read the values. If you use a computer program you can always send your config over the USB port of the Mojo.

I generally don't recommend dealing with softcores unless you have a really good reason to. I don't see a reason for it with your project as I understand it. You don't need a processor to do basic controls. However, if you wanted, there is the ATmega32U4 on the Mojo that you can use the Arduino IDE to program and talk directly to the FPGA over SPI or UART. By default, this acts as a USB to serial converter and an ADC for the FPGA.

I'd recommend checking out all the Lucid tutorials, especially the Hello RAM tutorial which shows you how to read data in from the USB port using just the FPGA.

Let me know if you have any other questions. I'd be happy to help.

Justin

EDIT: Just to give you an idea of what your design may look like, I wrote up a Lucid module that should do what you want.

Code: Select all

module triggerToPulse (
    input clk,  // clock
    input rst,  // reset
    input trigger, // trigger from camera
    output pulse,  // output pulse
    input mode[3], // mode of trigger
    input duration[24] // duration of pulse
  ) {
  
  .clk(clk), .rst(rst) {
    dff ctr[24];    // counter for pulse
    dff oldTrigger; // dff to detect edges
  }
  
  // modes of operation
  const OFF = 0;
  const ON = 1;
  const RISE = 2;
  const FALL = 3;
  const FOLLOW = 4;
  
  always {
    pulse = 0; // default to 0
    
    // save the trigger value so we can detect edges
    oldTrigger.d = trigger; 
    
    if (!&ctr.q) // if not maxed out
      ctr.d = ctr.q + 1; // increment
    
    case (mode) {
      OFF:
        pulse = 0;
      ON:
        pulse = 1;
      RISE:
        // if trigger was low but is now high...
        if (oldTrigger.q == 0 && trigger == 1)
          ctr.d = 0; // reset counter
        // set pulse high while ctr is less than duration
        pulse = ctr.q < duration;
      FALL:
        if (oldTrigger.q == 1 && trigger == 0)
          ctr.d = 0; // reset counter
        pulse = ctr.q < duration;
      FOLLOW:
        pulse = trigger;
    }
  }
}
You can then feed in mode and duration values to change the behavior. You could instantiate as many of these as you want in your design (within reason) and they will all operate independently with exactly the same behavior each time (no variation in latency due to program execution).

Re: Laser trigger

Posted: September 14th, 2015, 10:15 am
by rojhann
Hi Justin,

Somehow I thought you only posted the declarations of input/output ... (didn't see the slider on the code part). So I tried to write it and ended up with something more complicated than what you wrote!

Thanks for your input. I will probably write again soon with some questions!

Re: Laser trigger

Posted: September 25th, 2015, 7:53 am
by rojhann
Hi!

So I am trying to write a user_input module to get the mode and duration from the user. I got inspiration from the "greeter" from the tutorials obviously.

Basically, the user sends a flag (0 for mode and 1 for duration), and then the value (either the mode or the duration). To simplify the flag and the mode are 8bits and the duration is 16bits.

Code: Select all

module userinput (
    input clk,         // clock
    input rst,         // reset
    input new_rx,      // new RX flag
    input rx_data[8],  // RX data
    output mode[3],
    output duration[16]
  ) {
  
 .clk(clk) {
    .rst(rst) {
      fsm state = {IDLE, FLAG, MODE, DURATION}; // our state machine
    }
 
    dff duration_count[2];
    
    // we need our RAM to have an entry for every value of name_count
    simple_ram duration_ram (#WIDTH(8), #DEPTH(2));
    simple_ram mode_ram (#WIDTH(8), #DEPTH(1));                               
  }
  
  always { 
      duration_ram.address = duration_count.q; // use name_count as the address
      duration_ram.write_data = 8hxx;      // don't care
      duration_ram.write_en = 0;           // read by default
    
      mode_ram.address = 0; // use name_count as the address              
      mode_ram.write_data = 8hxx;      // don't care
      mode_ram.write_en = 0;           // read by default
 
      duration = {duration_ram.read_data[0],duration_ram.read_data[1]};                                            
      mode = mode_ram.read_data[2:0];  
    
      case (state.q) { // our FSM
      // IDLE: Reset everything and wait for a new byte.
      state.IDLE:
        duration_count.d = 0;
        if (new_rx){ // wait for any bit
          state.d = state.FLAG;
        }

      // LISTEN: Listen to the flag.
      state.FLAG:
        if (new_rx) { 
            case(rx_data){
            0:                                                                       /// does it work?
              state.d = state.MODE;
            1:
              state.d = state.DURATION;
            default: 
              state.d = state.IDLE;
            }
          }
        
      // LISTEN: Listen to the duration.
      state.DURATION:
        if (new_rx) { // wait for a new byte
          duration_ram.write_data = rx_data;        // write the received letter to RAM
          duration_ram.write_en = 1;                // signal we want to write
          duration_count.d = duration_count.q + 1; // increment the address
 
          // if we run out of space
          if (&duration_count.q) {
            state.d = state.IDLE;
            duration_count.d = 0; // reset duration_count
          }
       }
    
      // LISTEN: Listen to the flag.
      state.MODE:
        if (new_rx) { // wait for a new byte
          mode_ram.write_data = rx_data;        // write the received letter to RAM
          mode_ram.write_en = 1;                // signal we want to write
          state.d = state.IDLE;
       }
    }
  }
}
Now several dummy questions arise:
1) The assignment of {read[0],read[1]} to the output duration[16] gives me an error and I don't understand why. If the RAM has two elements of 8bits, then concatenating those two should be a 16bits. Where is my misunderstanding?
2) Shouldn't we initialize the RAM somehow? Is it what ram.write does? I wonder what is the initial value of the output before the user does anything.
1) Does "mode_ram.address = 0;" means that it will always write at the same position? (which is what I want)
3) Do you see any mistake/better way/wrong things?

The deal after that is to plug this module with the AVR and the triggering you wrote. I still need to make a module for the output pin then.

many thanks!

Re: Laser trigger

Posted: September 28th, 2015, 12:36 pm
by embmicro
The {} syntax is for building arrays not for concatenating signals. For that you want the c{} syntax. {duration_ram.read_data[0],duration_ram.read_data[1]} will result in a 2x1 multi-dimensional array while c{duration_ram.read_data[0],duration_ram.read_data[1]} will result in a 2 bit array.

Also, if you just want any number of consecutive bits from an array you could simply write duration_ram.read_data[1:0]. See the bottom of this tutorial for more info.

If you are using RAM with such little depth, you are probably better off just using dffs instead. This is especially true since you want to read and write the data at the same time and we don't currently have a dual port RAM in the components library.

Also remember if you are using a generic serial terminal program, when you type "0" on your keyboard you send the ascii letter "0" and not the value 0 ("0" is actually 48). If you want to convert a single digit ascii number to a decimal number just subtract 48. If you have a two digit number, subtract from both digits, multiply the first by 10 and add them together.

Re: Laser trigger

Posted: October 1st, 2015, 4:23 am
by rojhann
Thanks!

So I tried the full project (with I/O and the avr interface), I could obtain all the behaviours!

I need to figure out a bit the whole ascii/bits communication in order to change the pulse length while I look at the oscilloscope, but that's peanuts. Since I also want several laser I need to rethink a bit the communication. But I am very enthousiastic! Especially that f that works well, I would implement it on the other microscopes.

My userinput module is now the following:

Code: Select all

module userinput (
    input clk,         // clock
    input rst,         // reset
    input new_rx,      // new RX flag
    input rx_data[8],  // RX data
    output mode[3],
    output duration[16]
  ) {
  
 .clk(clk) {
    .rst(rst) {
      fsm state = {IDLE, FLAG, MODE, DURATION}; // our state machine
    }
 
    dff duration_count;
    dff duration_memory[16];
    dff mode_memory[8];
                          
  }
  
  always { 
 
      duration = duration_memory.q;                     
      mode = mode_memory.q[2:0];  
    
      case (state.q) { // our FSM
      // IDLE: Reset everything and wait for a new byte.
      state.IDLE:
        duration_count.d = 0;
        if (new_rx){ // wait for any bit
          state.d = state.FLAG;
        }

      // LISTEN: Listen to the flag.
      state.FLAG:
        if (new_rx && rx_data=="0") {                                                             
              state.d = state.MODE;
        } else if (new_rx && rx_data=="1") { 
              state.d = state.DURATION;
        }
        
      // LISTEN: Listen to the duration.
      state.DURATION:
        if (new_rx) { // wait for a new byte
          if(duration_count.q == 0){
            duration_memory.d[7:0]=rx_data;
          } else if(duration_count.q == 1){
            duration_memory.d[15:8]=rx_data;
          }
          duration_count.d = duration_count.q + 1; // increment the address
 
          // if we run out of space
          if (&duration_count.q) {
            state.d = state.IDLE;
            duration_count.d = 0; // reset duration_count
          }
       }
    
      // LISTEN: Listen to the flag.
      state.MODE:
        if (new_rx) { // wait for a new byte
          mode_memory.d = rx_data;        // write the received letter to RAM
          state.d = state.IDLE;
       }
    }
  }
}
Quick question, sometimes I need to send the instruction (e.g. flag+mode) two times before it is performed, and sometimes it reacts right away. I guess the new_rx flag goes down somehow and it does not take care of the "mode"... DO you have any idea how would that happen? It is a bit of an imprecise question, so I guess I have to play around to have more clues of what happens.

cheers

Re: Laser trigger

Posted: October 1st, 2015, 12:47 pm
by embmicro
Your module looks good. My guess for it sometimes not working would be that the FSM and your code gets out of sync if a random character is sent. You may want to add some kind of "begging of command" byte to signal the FSM to force itself into IDLE. For example, you could use "\n" as the reset character by adding this to the end of the always block (after the case statement).

Code: Select all

    if (new_rx && rx_data=="\n")
      state.d = state.IDLE;
This way if you ever receive a "\n" character you know the FSM will be in the IDLE state after. You could then send your commands as "\n0M" where M is the mode byte. This also assumes that "\n" (decimal 10) will never be used as the mode byte or in a duration byte. An alternative to this would be to have a reset timer. So if you don't receive a character after X amount of time, you force it back to IDLE. This works if you have a program sending the data in one shot, but won't work well if you are using a generic serial port monitor to send each letter one at a time.

Re: Laser trigger

Posted: October 5th, 2015, 7:50 am
by rojhann
For the moment it is quite funny, because by sending two times the command, then I get the right behaviour. But I haven't manage yet with an end character...

An other of my trouble is that the pulsing doesn't work. If I set in the code a fixed duration (like 200), then i get the pulse with the right length. But if duration is an input, then the pulse length seems to be always the same whatever the value of duration. I monitor duration[7:0] on the leds and there I really see the progression if I increase duration by one.

I will keep on trying out, but do you have any ideas?

cheers,

Re: Laser trigger

Posted: October 5th, 2015, 11:53 am
by embmicro
If you can post your full project I'll take a look. It's hard to say without seeing it all.

Re: Laser trigger

Posted: October 6th, 2015, 3:45 am
by rojhann
Ah yes sorry, of course... I took the module you wrote as is, and the other module is the one I posted. But small modifications I made might be the source of my trouble.
NET "camera" LOC = P22 | IOSTANDARD = LVTTL | PULLUP;
NET "sigout" LOC = P33 | IOSTANDARD = LVTTL | PULLUP;

Code: Select all

module mojo_top(
    input clk,              // 50MHz clock
    input rst_n,            // reset button (active low)
    output led [8],         // 8 user controllable LEDs
    input cclk,             // configuration clock, AVR ready when high
    output spi_miso,        // AVR SPI MISO
    input spi_ss,           // AVR SPI Slave Select
    input spi_mosi,         // AVR SPI MOSI
    input spi_sck,          // AVR SPI Clock
    output spi_channel [4], // AVR general purpose pins (used by default to select ADC channel)
    input avr_tx,           // AVR TX (FPGA RX)
    output avr_rx,          // AVR RX (FPGA TX)
    input avr_rx_busy,       // AVR RX buffer full
    input camera,
    output sigout
  ) {
 
  .clk(clk), .rst(~rst_n){
    // the avr_interface module is used to talk to the AVR for access to the USB port and analog pins
    avr_interface avr;
 
    userinput user; // instance of our user
    triggerToPulse trigger;
    
  }
 
  always {
    // connect inputs of avr
    avr.cclk = cclk;
    avr.spi_ss = spi_ss;
    avr.spi_mosi = spi_mosi;
    avr.spi_sck = spi_sck;
    avr.rx = avr_tx;        
 
    // connect outputs of avr
    spi_miso = avr.spi_miso;
    spi_channel = avr.spi_channel;
    avr_rx = avr.tx;
 
    avr.channel = hf; // ADC is unused so disable
    avr.tx_block = avr_rx_busy; // block TX when AVR is busy
 
    user.new_rx = avr.new_rx_data;
    user.rx_data = avr.rx_data;
    avr.tx_data = avr.rx_data;
    avr.new_tx_data = 0;
    
    // Connects user input to trigger
    trigger.mode = user.mode;
    trigger.duration = user.duration;
    trigger.trigger = camera;
    sigout = trigger.pulse;
        
    led = trigger.ledout;             
  }
}

Code: Select all

module userinput (
    input clk,         // clock
    input rst,         // reset
    input new_rx,      // new RX flag
    input rx_data[8],  // RX data
    output mode[3],
    output duration[16]
  ) {
  
 .clk(clk) {
    .rst(rst) {
      fsm state = {IDLE, FLAG, MODE, DURATION}; // our state machine
    }
 
    dff duration_count;
    dff duration_memory[16];
    dff mode_memory[8];
                          
  }
  
  always { 
 
      duration = duration_memory.q;                     
      mode = mode_memory.q[2:0];  
    
      case (state.q) { // our FSM
      // IDLE: Reset everything and wait for a new byte.
      state.IDLE:
        duration_count.d = 0;
        if (new_rx){ // wait for any bit
          state.d = state.FLAG;
        }

      // LISTEN: Listen to the flag.
      state.FLAG:
        if (new_rx && rx_data=="m") {                                                             
              state.d = state.MODE;
        } else if (new_rx && rx_data=="d") { 
              state.d = state.DURATION;
        }
        
      // LISTEN: Listen to the duration.
      state.DURATION:
        if (new_rx) { // wait for a new byte
          if(duration_count.q == 0){
            duration_memory.d[7:0]=rx_data;
          } else if(duration_count.q == 1){
            duration_memory.d[15:8]=rx_data;
          }
          duration_count.d = duration_count.q + 1; // increment the address
 
          // if we run out of space
          if (&duration_count.q) {
            state.d = state.IDLE;
            duration_count.d = 0; // reset duration_count
          }
       }
    
      // LISTEN: Listen to the flag.
      state.MODE:
        if (new_rx) { // wait for a new byte
          mode_memory.d = rx_data;        // write the received letter to RAM
          state.d = state.IDLE;
       }
    }
    
  }
}

Code: Select all

module triggerToPulse (
    input clk,  // clock
    input rst,  // reset
    input trigger, // trigger from camera
    output pulse,  // output pulse
    input mode[3], // mode of trigger
    input duration[16], // duration of pulse
    output ledout[8]
  ) {
  
  .clk(clk), .rst(rst) {
    dff ctr[16];    // counter for pulse
    dff oldTrigger; // dff to detect edges
  }
  
  // modes of operation
  const OFF = 0;
  const ON = 1;
  const RISE = 2;
  const FALL = 3;
  const FOLLOW = 4;
    
  always {
    pulse = 0; // default to 0
    // save the trigger value so we can detect edges
    oldTrigger.d = trigger; 
    ledout = duration[7:0];  
    
    if (!&ctr.q) // if not maxed out
      ctr.d = ctr.q + 1; // increment
      
    case (mode) {
      OFF:
        pulse = 0;
      ON:
        pulse = 1;
      RISE:
        // if trigger was low but is now high...
        if (oldTrigger.q == 0 && trigger == 1){
            ctr.d = 0; // reset counter
        }
        // set pulse high while ctr is less than duration
        pulse = ctr.q < duration;
             
      FALL:
        if (oldTrigger.q == 1 && trigger == 0)
          ctr.d = 0; // reset counter
          
        pulse = ctr.q < duration;
      FOLLOW:
        pulse = trigger;
    }
  }
}
Thanks a lot for being so available!