LabVIEW programming best practices

Typically we read joysticks in Teleop and all math/algorithms are calc in Teleop and then sent via global variables to periodic to change motor behavior.

Is there anything wrong with sending joystick commands to global instead and then doing all the math/etc in periodic?

All of our swerve inverse kinematics is currently in Teleop. Want to use them for auton, so want to try to move it all into periodic.

Is there a downside to this?

You should absolutely move that logic out of teleop. The ideal case is only the joystick mapping is done in teleop.

Have you considered the command based framework? This does a good job of splitting out subsystems.

Please tell me more. Where would I find more info on that?

It starts as a template when you create a new robot project. Details are here

There may also be some videos on it at ni.com/frc

This is personal preference, but I like the following:

  1. Divide your robot into systems – Drive, Shooter, Climber, Auto. Have a similar set of routines for each – DriveOpen (Begin), DriveAcquire, DriveExecute, DriveWriteNetworkTables, DriveGlobalVariables. The Acquire routines read, scale, and filter sensors then write the values to global variables. The Execute routines read the sensor global variables and global variables for setpoints and other commands and perform control functions ( closed loop control, sequencing (state machines), etc…

  2. Create a separate global variables VI for each system. This makes the code better separated (isolated), easier to port, debug, maintain, use.

  3. Create “read” and “write” VI in the system for global variables. This makes troubleshooting easier – can find faster where the reads and writes are occurring. Outside of these routines don’t ever use the global VI directly. These “read” and “write” VI’s can be made more efficient – after they are completely debugged – by setting their execution properties to :“subroutine” and “inline”.

  4. Put all the Acquire and Execute VI in a 20 millisecond loop in periodic tasks. No need to duplicate these functions in teleop and autonomous.

  5. Create a separate loop in Periodic Tasks to write things to network tables. If you want this could run slower. (Unless you specifically flush the network tables, writes to the driver station appear to only occur every 100 milliseconds, This is plenty fast enough for driver reaction.) I suggest making this loop run at 40 milliseconds.

  6. Teleop only needs to read the driver inputs, calculate wheel speed demands (and process other driver inputs) and call the VI that set global variables. Suggest not writing to Network table variables here or doing anything else that requires significant time. If sensors need to be read, don’t read them here, call your VI that read the global variable values.

  7. There are some suggestions for auto too, but may be in another post…

(The secret book of LabVIEW version 2 talks about some of this, but it probably isn’t the best written chapter…)

This isn’t swerve drive – or the easiest to look at code – but this repository follows some of these suggestions (not the one about having “read” and “write” global VI. Still working on that concept.). https://github.com/FRobotics/2022-RobotCode

1 Like

Out of curiosity, how do you create additional robot global data vis for other systems? That’s something we’ve never done.

https://zone.ni.com/reference/en-XX/help/371361R-01/lvhowto/creating_global_variables/

Backing the 90’s when I took my first programming class, I was always discouraged from creating global variables. No one could ever explain to me why. Something about allocating additional memory and slowing down loop cycle.

This was C++ and 30+ years ago.

It seems like this is no longer the case since we’re using global variables like they grew on trees. Is that true?

One (or maybe more) things here.

  1. For each system create a separate physical directory for the VI for that system. Then create an auto-populating folder in the LabVIEW project that points to that system.

  2. You may also want a separate directory and auto-populating project folder for general VI. (For example our students created an edge trigger detection VI and position control VI that were common).

  3. LabVIEW libraries could be used instead of the auto-populating folder (still make a separate physical directory). I can make a case for this either way. (The auto populating folder is easier to make and teach. Libraries are maybe a little more portable and self contained but require teaching about them.)

To create new global variable VI, I think the following works okay.

  1. Drag a new global variable onto your VI from the “structures” palette. (Top left palette). It is the thing with the globe on it.
  2. Double click on the newly created global variable. This should open the new global variable VI.
  3. Add the new “indicator” (or “control”) and save this VI file to the name and directory of your choosing.
  4. Back in the original VI use the drop down to pick the specific global variable in this new VI.

After that, I think the easiest way to create new variable instances from new global variable VI is to drag the global variable VI onto the block diagram of a normal VI. (There are other ways.)

I have to stress with students (usually multiple times once they find that global variables are easy) that global variables are NEVER a replacement for wiring – when wiring can be done. They are only for wrapping with read and write VI (in other words each global variable should only be used twice – once in the read VI and once in the write VI) so that they can be used to 1) communicate with VI in other systems, or 2) communicate with VI that aren’t subordinate (subroutines), 3) communicate with VI in different loops.

SMILE. (I wrote a LOT of FORTRAN code – several decades ago – that used lots and lots of global common regions between processes (tasks in MODCOMP terminology). There wasn’t much other choice…

In C, C++, (and LabVIEW) one has to be really careful about data synchronization issues. For robot use, we use global variables in situations where it is okay to use only the last written value. (Left wheel speed, Left wheel speed setpoint, gyro angle, etc.)

Also, for global variables, since the value is volatile, the compiler can’t perform some optimizations on the code using the global variable. ( I don’t know anything about the “allocating additional memory” thing.)

For object oriented languages (C++, Java, and LabVIEW classes) it always seems better to me to use the things available there, like “getter” and “setter” methods to set the value stored inside an object instantiated from a particular class. (personal preference).

I don’t show LabVIEW classes (I don’t know a lot about them other than they exist), so the global variable method described in my original post sort of imitates this for a one instance pseudo class ( drive system for example ). Easier to show and implement.

Adding to what @JSIMPSO wrote, you should use global variables carefully. It can be very hard to debug a code with a global variable that any function has access to and can possibly change its value.

This sounds like you’d really find use of the Command and Control project template. It has a built in idea for subsystems. There isn’t much need for global variables (I’ve never found a need) because you’ll create accessors there and call into the subsystems from elsewhere.

That’s still definitely true. It’s less about memory or loop speed and more about access.

If you live alone and your favorite snack is disappearing from your fridge, you know exactly who to blame.

If you live with several roomies all having access to the fridge, who did it?

You’re more likely to get yourself into trouble by letting everyone have access to your favorite snacks similar as you will if all of your app can touch the variables. Instead, you generally want to focus on the communication between things and let one thing own the data.

The Command and Control Will mentioned lets you do things like open up all of your drive train motors in a drive subsystem and only control those motors there. Anywhere else can ask the drive controller to make the robot move but none can actually control the motors themselves, for example.

1 Like

There are lots of ways to do things, but from my perspective I really don’t like the command and control architecture – regardless of the language. ( “don’t like” is probably not strong enough.) Sorry.

I only tried it briefly and looked at some samples from the languages, but here are my thoughts.

  1. One of the primary principles of hard real-time programming is the code needs to be deterministic. (It needs to do the same thing repeatedly forever.) Limiting or eliminating dynamic things is one of the ways this is achieved. Dynamic code execution (and dynamic memory allocation) can cause unexpected results, are hard to completely test, and even harder to troubleshoot. It seems that the command and control architecture is contrary to this principle.

  2. From an educational perspective, I’d rather show students concepts that they can take with them and use again later. So rather than spending time learning how to use the “command and control” library, with a lot of “black box” things in the middle, I’d rather teach them how to build a finite state machine, or use sequential digital logic, or traditional analog control concepts. These concepts will carry over to a computer science education or to industrial control systems. (The overall control concept of acquire, decide, execute seems simpler to me.)

  3. On a personal note, I like simple. The command and control system seems to add unneeded extra complexity. (This one is purely opinion…)

Maybe I missed something about this, but just my opinion.

What dynamic memory allocation are you referring to? At its core, all the command and control architecture does is put each subsystem into its own parallel loop, with a queue to pass messages. The queue has a fixed size that is pre-allocated.

This is quite a common architecture, and its not really black box since its all open source and easily modifiable. Most of the ‘extras’ in the template are more nice to haves that you can just remove if you really don’t like them. Though I like to keep them and have the compiler optimize them out in the deployed code (e.g. the trace system).

There is not a single robot in FRC that is hard real-time (though I know of one team that gets close).

You’re communicating via WiFi. There are zero true deterministic robots here.

It’s no more dynamic than what you mentioned. It’s dividing a robot into systems and having a set of routines for each. It just uses encapsulation versus global variables (a better programming practice for teaching the students).

True. But, neither exist in command and control. However, using global variables often causes unexpected results. The shift to CnC here resolves a problem you’re creating with your architecture that you claim to worry about.

If you’re showing them globals, you’re not doing this. CnC is very similar to: Model–view–controller - Wikipedia This is something they can absolutely take with them.

Nothing in what you suggested using a FSM. You’re breaking about every driving idea behind a FSM.

You’re doing the opposite of “isolating” code by creating access globally.

Computer science will want to avoid pretty much all of the advice you gave above in favor of abstraction and encapsulation.

I can understand this. Simple is often easier to digest and can be easier to explain/teach to others. It meets far fewer of the goals you listed prior, though. It’s also a bit strange to me to list simplicity and then post what you had above. You create global variables and then build getters and setters around global variables (they don’t need these. Getters and setters are made such that you can access data you don’t otherwise have access to. If you already have access, the getters/setters are redundant and add complexity without any value, for example).

I’m personally not a fan of these in the RT space. Others would disagree.

I’d agree with the first bit. The second seems strange. Again, it’s a redundant functionality and eliminates the ability for you to right-click and find the global variable instances quickly.

Global variables are going to be accessible by the other systems. I can think of a couple of ways to create specific sets of globally accessible variables for a specific system. But, it’s an absurd amount of complexity with no real gain.

There’s actually a lot of upside - particularly, Teleop only runs when a Teleop packet is received from the Driver Station. This will happen no faster than 50 Hz, and any dropped packets will make it slower still.

Doing any sensor-based control loops inside the separate Periodic Tasks vi will allow them to happen at their own rate (which can often be much faster) that is asynchronous from the DS packets. The fact that it becomes asynchronous does potentially introduce extra latency to joystick values actually reaching the motors, but that effect is marginal compared to what it does for your closed loops (especially if you’re updating the motor values at least twice as fast as Teleop runs, which is generally not too difficult to achieve in my experience).

This is why Will said teleop should only have joystick mapping; it’s the only data that’s hard-constrained by DS packet delivery anyway.

1 Like

For those that dislike the command-and-control architecture, but like the idea of separating reading inputs from managing the actuators, this concept also exists (using a standard producer-consumer architecture in LV) - Producer Consumer | FRC LabVIEW Tutorials

1 Like

Global state is still a bad practice if it can be avoided, for the same reasons it was a bad idea back in the 90s - it makes the application code difficult to understand and debug.

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.