Setting/changing priorities

In order for our shooter to quickly reposition itself, we are running some pretty aggressive gains on the PID which is controlling the shooter angle. A side effect of this is that the PID is very sensitive to the timing in the loop which contains it (we had problems at championships when we had a gyro getting initialized during the begin VI. 20% of the time the PID would go unstable when trying to position the shooter during autonomous).

I’m thinking that making the loop containing the PID be a higher priority than the other base code would help with this, but we are a bit confused when it comes to setting priorities.

We have found that we can set the priority of a VI to one of 5-6 enumerations by setting it in the VI properties. Or we can set a priority on the timed loop to a value of 1-65,535. We tried to put a display on a basic timed loop to see what the priority is, and it is showing a steady 100 for a priority, but if we probe the value, it shows 0.

We don’t know what value to set the priority in a timed loop since we don’t know what the priority is of a “normal” loop. We asked the instructor that was at a clinic some members attended and he said that the priority was set in the FPGA and didn’t know what a “normal” value is.

Does anyone know what the priorities are of a basic while loop?

Thanks,
Kenton

You need to be INCREDIBLY careful when setting priorities on VIs and subVIs - almost to the point where if you don’t understand how LabVIEW generally treats VI priority (and how RT can affect that priority) then it’s probably not something you really want to be messing with. Why do I say this? Well, on RT you have to realize there are a ton of priorities - and different OS functions are being assigned to the different priorities. If you set the subVI to a higher priority than the OS functions, then you will effectively starve those OS functions - that may be file I/O, communications (network I/O), or you may even preempt the scheduler (which means NOTHING else will run while your subVI is running).

Often when someone asks how to play with priorities, what they’re really asking for is a lesson in control software architecture. Unfortunately I cannot do that over a short ChiefDelphi message post, but I can give you some tips that may help.

First I just want to warn you (again) that if you’re wanting to set VI priority, you’re likely doing it wrong. Let’s cover the priorities in descending order:

TIMED LOOP
CRITICAL
HIGHEST
HIGH
NORMAL
BELOW NORMAL

Timed Loop is its own priority, because it sits as the “Top Dawg” in the priority list - anything in the timed loop is going to run above everything else. Anything in the timed loop had better be really stinking important, because you’re starving literally everything else in the system. Timed Loops have priorities within the timed loop, so that you can have layered priorities within the timed loops - again, it had better be really dang important. And you had better be really careful about timing the timed loop, because while the loop is running everything else in the system is dead - let me say that again, EVERYTHING in the system is being starved out. NO - for example, you can’t call networking I/O and cause a priority boost for the networking I/O you called. As a matter of fact, if you’re doing anything in a Timed Loop except raw calculation or signal acquisition and processing - and not using a Real-Time FIFO - you’ve done something wrong and you should stop using Timed Loops (don’t do anything that could be considered blocking). Unless you know why this is important, stop using a Timed Loop. You can avoid starvation by scheduling the timed loop to run at intervals that leaves “dead time” in the timed loop so that other things can run (no, I do NOT mean “wait for ms”). If you don’t fully understand the timed loop, don’t use it, it is too powerful.

Critical priority is for TIMED_CRITICAL tasks. Things that have to run over everything else (except Timed Loops). This had better be important, because you’re starving everything else in the system out. If you’re doing File I/O, network I/O, or anything that is non-deterministic or blocking like that you’ve made your first mistake (no blocking calls!). Don’t make another. Big name companies with really smart people have boosted their VIs to Critical priority and not understood the ramifications and have had it cause nightmares in their control algorithms. Don’t do it, please. Priority inheritance from critical level VIs calling non-critical VIs is something that is impossible (okay, maybe just impossibly mind numbingly difficult) to debug.

Highest priority threads is the highest thread-level priority that threads can generally be given (without hosing the system). However, Highest priority threads are where most OS level functions are called, so it’s still possible to starve OS functions if you’re not careful. Networking I/O generally gets run at this level, but usually one level of OS priority above “Highest” - just don’t do anything super crazy like File I/O at this level unless it’s super-duper important to log that data, because you’re likely to block anyway.

High Priority is for VIs that need a little “extra” boost in priority. While this VI is running, no other lower-level VIs will be scheduled. Try not to spend too much time within High priority VIs, and don’t expect them to run “side by side” with normal level code - because it isn’t going to happen.

Normal priority is where you’re at by default. Pretty much everything by default runs at normal priority. Nothing else really needs to run at a higher priority, if you feel it does then more than likely you need to reevaluate your code architecture and find a way to run your higher-priority-needing code in some other branch/thread. There are valid reasons to run code at priorities above Normal priority, and even some REALLY GOOD reasons, but until you’ve “become one” with LabVIEW you likely won’t recognize the reasons until they’ve been pointed out.

Below Normal priority is where low-priority tasks usually run. On Real-Time systems, this is sometimes where File I/O is even run. The reason is because File I/O is not deterministic (just like network I/O isn’t) and the actual “meat” of the File I/O is done in the below-normal priority (the File I/O request from hardware triggers interrupts, and the interrupts are cleared in the hardware interrupts, but the actual work writing to disk is queued to a below-normal level). Anything running at normal priority will starve below-normal priority tasks.

One VERY IMPORTANT thing to note about LabVIEW is that even though there are priorities, a loop in LabVIEW doesn’t HAVE to round-robin to other threads of the same (or lower) priority. This is why you should NEVER EVER create a loop in LabVIEW without blocking calls in it (that actually block). Almost all loops in LabVIEW need some kind of “wait for ms” or “wait for ms multiple” to throttle the loop, yield execution to other loops of the same (or lower) priority, or similar - otherwise loops will literally eat 100% of the processor and starve out other threads in the system. Look at the Periodic Tasks VI, and you’ll see what I mean - the loops in that VI run in parallel to the Main VI, and are throttled by the wait calls in the loops. If you created a loop with no wait calls, and there were no blocking calls in the loop, you’d likely cause your cRIO to lose networking communications, become incredibly laggy, and even become unreliable.

I haven’t even talked about LabVIEW Execution System threads and how the LabVIEW diagram is broken up and run in different threads (via a method we call “clumping”), which really helps to explain how many simultaneous tasks REALLY CAN operate at the same time. Understanding the LabVIEW Compiler can go a long way into understanding how to write efficient code.

I hope I’ve made you reconsider why you think you need to deal with priorities, and instead maybe what you want is to push the PID code somewhere else so it can be run in a loop that is just running <FASTER> but not necessarily at a higher <PRIORITY>.

-Danny

how did you determine it was timing that caused the instability and not a variation in your shooter response?

With the gyro initialization in the begin VI, the PID would become unstable around 20% of the time. With the gyro initialization disabled, the PID would never become unstable. The only resource that I can see in common between the two would be processing. Given the problems that were seen in the Einstein follow up from 2012 with gyro initialization hanging and using too much CPU, it was decided that it could be a similar issue. Note, this was only the gyro initialization code in the begin VI. The gyro was only read when a button was pressed (which was never occurring because we were in autonomous).

If you’re concerned the Begin.vi is causing you problems, the issue may not be “CPU processing” but it might be thread resources. LabVIEW creates 4 threads per execution system per CPU to do its work. The “default” execution system that everything uses is the “Standard” execution system - subVIs use the execution system of the caller since it’s more efficient. However, if you’ve got a lot of things cranking up at the same time your code might be fighting over those 4 execution system threads. You can try to put your code within a different execution system, which would provide “dedicated” threads for your “parallel execution” code. Maybe set your entire Autonomous VI in another execution system?

I don’t have the FRC libraries / Default code in front of me, but maybe this is something worth considering. You can change the execution system in the VI properties under the “Execution” drop-down.

-Danny

It might be helpful if you posted your code before the change and described exactly what you removed.

118’s problem was due to an infinite loop in their own code. It’s unlikely that you wrote your gyro initialization code the same way. My gyro initialization consists of a gyro open in Begin and a gyro reset at the beginning of autonomous. How much more initialization are you doing?

I would use the Driver Station Log Viewer to compare the operating environment when with the old code when it worked and when it didn’t as well as after your change. It will show you CPU utilization as well as network statistics. It will also show errors reported by the code.

Are you using the LabVIEW PID VIs or did you write your own? The LabVIEW PID VIs can be configured to assume either a constant call rate or too measure the time since last call, in order to correct for inconsistent timing.

You may find Team 358’s Timing is Everything whitepaper helpful for comparing different wait methods.
You may find that you can increase the D term to improve stability with only slight decrease in spinup time. Alternately, you may be able to use an alternate control method (such as Bang-Bang Control which has the fastest possible spinup, without stability problems as long as your shooter wheel has enough inertia.