LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

How to synchronize XY stage movement and oscilloscope data saving in one LabVIEW program (Thorlabs NRT100/M)

Hi everyone (especially GerdW 👋),

I’m a beginner in LabVIEW and currently trying to build an automated measurement setup using a single laptop that controls both:

- Thorlabs NRT100/M XY stage (via Kinesis ActiveX)
- Tektronix oscilloscope (connected via USB/LAN)

At first, I thought I would use two separate computers (one for stage, one for oscilloscope),
but now I need to control both devices **simultaneously in one LabVIEW program**.

Here’s what I want to achieve:

1. Move the X-axis by 100 µm → stop.
2. Once the stage stops, the oscilloscope should capture a waveform and save it as a CSV file.
3. After the oscilloscope finishes saving, the stage moves another 100 µm → stop → trigger oscilloscope again.
4. After scanning one full line (1 cm), the Y-axis moves down by 100 µm.
5. Then it scans back in the opposite direction (left to right).
6. Repeat this process until the entire **1 cm × 1 cm area** is covered.

I already have a **manual control VI** that moves the stage in X and Y directions using Kinesis,
but I’m not sure how to **synchronize** it with the oscilloscope code and make the process automatic.

So I’d like to ask:
- How can I make LabVIEW wait until the stage finishes moving before triggering the oscilloscope?
- Should I use events, queues, or a Producer–Consumer structure for synchronization?
- Is there any example architecture or simple reference VI I can learn from?

I’m still very new to LabVIEW, so any **block diagram suggestions, example structures, or explanations** would be extremely helpful.

I’ve attached my current manual stage control VI for reference.

Thank you so much for your time and help 🙏

Best regards,

Download All
0 Kudos
Message 1 of 15
(320 Views)

Sounds to me like a "State Machine".

 

You have a basic cycle -- "Move", "Capture", which you do repeatedly until you have "capture everything".

 

Before you start this cycle, you probably want to "Initialize".

 

Consider a While Loop that contains a Case Statement, and an Enum that has the names of the States you want to cycle through.  The While Loop has a bunch of Shift Registers that hold "current parameters" of each State, like the X and Y coordinates, and the X and Y step sizes.

 

"Initialize" will set X to 0, DeltaX to 100, Y to 0, DeltaY to 0.

"Move" -- move X by DeltaX, Y by DeltaY.  Wait for movement to finish.  Use "rules" to update DeltaX and DeltaY as appropriate (based on values of X and Y).  Go to State "Capture".

"Capture" -- Take an image and save it.  If Scan finished, go to Exit, else go back to Move.

 

There are a number of ways to build such State Machine.  One venerable model is LabVIEW's own "Queued Message Handler", which functions as a State Machine by generating "Messages" (a.k.a. States, or Commands), putting it on a Queue (updating the State), and reading it as the first operation inside the While Loop containing the Case Statement holding the States.

 

I'm partial to the Channel Message Handler (CMH), whose name I think I'm responsible for, a version of which (also) ships with LabVIEW -- I've used it in essentially every Data Acquisition/Control Project I've done since LabVIEW 2016 (when Channel Wires were first "officially" released).

 

Bob Schor

0 Kudos
Message 2 of 15
(297 Views)

Oops -- you released your code in LabVIEW 2025, which (almost all) new LabVIEW users (especially students) are using, and (most) long-time LabVIEW users haven't (yet) started to use (since they work with colleagues, and everyone needs to be using the same version of LabVIEW as LabVIEW is not "Forward Compatible" (I'm sure you've heard of "Backward Compatible" -- "Forward" means we need to be running LabVIEW 2025 or a newer version (like LabVIEW 2027!) to open your VI.

 

But don't despair -- you can do a "File", "Save for Previous Version" and specify, say, LabVIEW 2021.  I'm pretty sure almost all "seasoned" LabVIEW developers are using at least that version and can open your VIs.

 

Bob Schor (again)

 

 

0 Kudos
Message 3 of 15
(293 Views)

Thank you very much, Bob!

I saved my files for LabVIEW 2019 as you suggested.
I’m attaching both my Main.vi and sub1.vi here (zipped together).

Thanks again for your kind help!

0 Kudos
Message 4 of 15
(250 Views)

@yujiiin wrote:

Thank you very much, Bob!

I saved my files for LabVIEW 2019 as you suggested.
I’m attaching both my Main.vi and sub1.vi here (zipped together).

Thanks again for your kind help!


You forgot to attach the subVI.

 

In order for us to troubleshoot your VI, get rid of the hardware requirements and do a simulation instead. You can always add the hardware at the end.

 

(As a very first step, get rid of all these local variables. None are needed. Are you sure "not or" is the correct function to stop the loop? Why is NumOfSteps Orange instead of blue? So much code smell!)

0 Kudos
Message 5 of 15
(240 Views)

Hi,

 


@altenbach wrote:

(As a very first step, get rid of all these local variables. None are needed. Are you sure "not or" is the correct function to stop the loop? Why is NumOfSteps Orange instead of blue? So much code smell!)


More questions:

  • Why is there a sequence structure?
  • Why do you need a WHILE loop when you run for a fixed number (NumOfSteps)? Use a FOR loop (with a conditional terminal)!
  • Why do you place those locals in the 3rd frame of the sequence???
  • Do you really want to run that "StepperMotorChannel" SetMoveAbsPos command in parallel to the other code???

And PLEASE remove ALL those locals…

Best regards,
GerdW


using LV2016/2019/2021 on Win10/11+cRIO, TestStand2016/2019
0 Kudos
Message 6 of 15
(228 Views)

Thank you, Altenbach!

You’re right — I forgot to attach the subVI. I’m uploading it here.

About the simulation part, I understand what you mean, but I’m still new to LabVIEW and not confident modifying the structure yet.

Right now, this is my manual stage control version (it moves the X/Y stage manually using buttons).
I want to ask — do you think this manual version can be extended to build the automatic scanning system
(move 100 µm → stop → trigger oscilloscope → repeat),
or would it be better to start over and make a new VI based on a proper State Machine structure?

Thank you so much for your time and feedback!

0 Kudos
Message 7 of 15
(223 Views)

You really need some instruction in LabVIEW, and need to understand how a Data Flow language (such as LabVIEW) works.

 

Do you know the three "Laws of Data Flow" (which I will paraphrase here)?

  • A Structure (or a Function) doesn't begin to execute until all of its inputs have values (i.e. until Data "flows into" the Structure or Function).
  • No Data "flows out of" a Structure or Function until all of its outputs have values (i.e. until Data are available on wires leading to "output nodes" (typically Tunnels or Shift Regsters).
  • If there is no Data Path from the output of one Structure or Function to the input of another, you cannot determine which runs first -- they effectively run at the same time (also called "run concurrently").

Note there are some ways of evading the "third rule" using things like Queues and Asynchronous Channel Wires, but that's an advanced topic.

 

LabVIEW gives you a powerful tool to enforce execution order and to permit parallel processing, namely the Error Line, which should be run from function/structure to function/structure where possible.

 

Look at your Main.  You have two Axes, X and Y, that you should set up in parallel.  You correctly run the Error Line from Serial Number to Create Device to BenchtopStepperMotor (is that the correct order?), but you should then run the Error Linne for Channel 1 into the While Loop, and start a new Error Line for Channel 2 and also run that into the While Loop.  Within the While Loop, do the same thing, run Error Line 1 and 2 separately through the Loop.  Finally, using the two Error Lines and Stepper Motor Channel lines, enable the two Devices (in parallel) and home them.

 

At this point, you have two Stepper Motor Channel lines (for X and Y) and your device is initialized.  Go ahead and merge those two Error Lines now and continue with the rest of the Main code.  

 

So all that code I just described can be encapsulated into your first SubVI, called "Initialize Stepper Motors", taking up much less space on your Block Diagram, move the cursor to the upper-left corner of the Icon drawing area, and click/drag down to the lower-right corner and let go.  You should have a White Box with a Black Edge.

 

Go click "Icon Text".  Type a short multi-line (1 to 4 lines) Identifier (such as "Initialize", "Stepper", "Control".  Click "OK" to save this label.  When you drop this 32x32 pixel VI on your Block Diagram, and if you have wired the Inputs and Outputs to the 4-2-2-4 (default) locations (inputs, try to keep to 2 or 3 on left with Error In on lower left corner, and outputs on right edge.

 

Bob Schor

 

You'll want to make an Icon for this sub-VI, perhaps a 32x32 pixel White Box with "Init Step Motor" written in three lines inside it (later you can learn to build more "artistic" Icons.  But here's how to get started:

 

When you build your first Sub-VI (be sure to put Error In and Error Out on the Block Diagram, use the default 4-2-2-4 wiring panel).  Right-click on the default Icon in the upper right corner of the Block Diagram (or Front Panel) and choose "Edit Icon".

 

First thing to do (you only need to do this once) is click "Layers" and choose "Show Layers Page".  Click on the User Layer "VI Icon" and delete it (with the X symbol to the right). Now grab the filled-in Rectangle tool on the far right, 

 

 

Bob Schor

0 Kudos
Message 8 of 15
(216 Views)

I don't know what happened here -- the tail end of my reply got messed up -- I'd attached an Image of the output of the Icon Editor showing a "Beginner Icon" that you could easily create (it will take you 2-5 minutes the first time you try, but you'll get the idea quickly, I hope).  Here's the missing picture:

Example Simple LabVIEW Icon.pngBob Schor

0 Kudos
Message 9 of 15
(214 Views)

Hi GerdW,

Thank you again for your detailed response and helpful suggestions earlier.
I carefully reviewed your feedback about removing local variables, avoiding unnecessary sequence structures, and using FOR loops instead of WHILE for fixed-step iterations.

I rebuilt my Main.vi from scratch based on your advice.
I haven’t created the subVI yet — I wanted to make sure the main structure and data flow are stable before moving on to modularization.

Here’s what I’ve done so far:

  • The VI controls both the Thorlabs NRT100/M XY stage (via Kinesis ActiveX) and the Tektronix oscilloscope from a single program.

  • I used nested FOR loops: the inner loop moves the X-axis by 100 µm, triggers the oscilloscope, saves the waveform as a CSV file, and the outer loop moves the Y-axis to the next line.

  • The goal is to perform a full 2D scan automatically while keeping the oscilloscope trigger synchronized with each stage movement.

Currently, I’m still unsure if my timing logic correctly synchronizes the oscilloscope trigger with the stage motion.
I’d really appreciate it if you could take a quick look at my current structure and let me know whether I’m on the right track.

Also, in my team project, I’m in charge of the stage control, while another teammate is developing the oscilloscope control part in a separate LabVIEW VI.
We are now trying to combine both parts into one synchronized program, but I’m not sure whether my current approach will make that integration smooth — or if there’s a better way to handle communication between the two modules.
Any advice or best practices for merging such separate VIs (stage + oscilloscope) into a single synchronized workflow would be extremely helpful.

Thank you very much again for your time and guidance.
Your feedback has been incredibly valuable for me as a LabVIEW beginner, and I’ll share my updated VI once I test this version.

Best regards,
Yujin

0 Kudos
Message 10 of 15
(193 Views)