I have a nice (at least, for me) VI that I run on an FPGA to handle flashing boolean values.
For me, the number of booleans is necessarily fixed (no dynamic arrays) but on a desktop there is no such restriction. I'd suggest replacing the array with perhaps a Map of values (if you have LabVIEW 2019 available) or perhaps Variant Attribute tables in older versions, using Type Cast or similar to get a string from a reference value.
![Update LEDs and Switches_BD.png Update LEDs and Switches_BD.png]()
Here I use the following VI on RT to set an LED's frequency and duty cycle:
![Request LED Setting_BD.png Request LED Setting_BD.png]()
Ignoring the data packing scheme, I'm sending an amount of time (in ticks) to be high, and then low, and the initial value.
A queue (DMA FIFO here, normal queue if you do something similar on desktop) is used to send information about a change to the frequency to the FPGA. You can see this in the second image, at the FIFO Write node. You'd want to use Enqueue Element instead.
By sharing the queue references into your free-standing loop handling your boolean "LEDs", you can asynchronously pass "requests" for an update to that loop.
In that loop (like the top VI here) you Dequeue an element, and then pass the necessary information (for me, a pair of maximum counts, a current count, the first value (starting value) and the current value) to a loop which processes each time step.
I'd suggest (like in my VI) you pick a specific loop rate for your LED processing loop, and use something like "Wait until next ms multiple" to time it. Note that you can't have better resolution that this update rate for your frequencies - for me, this is 40MHz, for you, a wait of maybe 10ms will allow a responsive update and a moderate freedom in selecting frequency.
Then you can calculate the number of "ticks"/iterations of this loop for a given time.
So if I want a boolean to flash at 1Hz, 50% on and 50% off, I'd send the values 50 (iterations on), 50 (iterations off), T (starting value). 50 is chosen because for a 10ms loop, 50% of a 1Hz loop is 500ms, and then 500ms/10ms = 50 iterations.
For completeness, this is my decoding VI for the requests:
![Decode LED Request_BD.png Decode LED Request_BD.png]()
You won't need anything so complicated, because you should directly pass a cluster of appropriate values via your Queue. I have to flatten and unflatten, hence this VI, but you can see here how some additional information ("current count", "current value") are constructed to match the first state (Count 1, Value 1).
You can also see from these VIs that if I want the boolean to be set to solidly on or off, I set the fraction of the second value to be 0. You could do something similar - in my case this means that actually the LED is set to Value 1 every iteration.