For the Sound and Vibration Measurement Hat for Raspberry Pi road test, I'm reviewing Measurement Computing's IEPE Measurement DAQ HAT for Raspberry Pi. |
All images and screenshots in this blog are from the actual designs. They reflect the status at the time of writing
Complex topic warning. This post will review multi-threading in LabVIEW and in the Linux instrument service. And uses a second tcp/ip socket to stream data over the network.
Instrument Service: streaming acquisition data
I'll have to be able to scan inputs, and stream the data to LabVIEW at the same time. If I'd know up front how many samples to take, I could just create a big enough buffer, do the sampling, and return the data. But this device supports constant sampling, so that solution won't do. Parallel processing is needed.
The mcc172 library has a partial solution for my scenario. When you ask the instrument to start scanning, it spawns a new thread that does the scanning. It 'll spend all its time getting data and filling a buffer. If you told it up front how many samples to take, it 'll stop when that job is achieved. Else it runs until you call the Stop Scan API function. Your code is then responsible in parallel to read that buffer fast enough and do something useful with the data. That could be: streaming it, perform a FFT, displaying it, …
In the instrument service that I'm writing for the road test, the goal is clear: the data has to be sent to LabVIEW. The needs to happen over the network. There is again a naive option: I could use the existing connection, and stream the data while the instrument samples. But that would block any other communication to the instrument - it would not be able to entertain incoming commands, such a s a request to stop continuous scanning. I need to develop a parallel stream mechanism. It's not hard, but requires some bookkeeping: I have to spawn a new thread to move data from the scanning buffer to the network socket. The new thread will wait until the client (LabVIEW) connects, and then continuously reads the scan buffer, formats the data to a string (note to self: I could make a binary stream too) and sends it off to the socket. It 'll be send and forget. The LabVIEW side is responsible for keeping up with the flood.
In total, there will be 3 threads active in parallel during scanning:
- the main thread. It just continues to do what it's supposed to do: wait for cammands and act upon them
- the mcc172 library's scan thread: it samples and fills a circular buffer. Runs until it is asked to stop or the requested amount of scans are acquired
- my stream thread that gets the data from that buffer and writes it. Has a similar life span as the mcc172 one.
There is a Scan Cleanup function in the instrument service (and its brothers in the SCPI service and LabVIEW driver) that will mop op the threads and buffers.
LabVIEW: producer / consumer pattern
There's an existing design pattern in LabVIEW to deal with data streams. It uses two parallel flows. The producer will eat incoming data and enqueue it. The consumer pops data off the queue and does the business logic. This takes care that the business processing doesn't slow down the data retrieval. I'm using that mechanism to deal with the data from the MCC172 DAQ. The annotated screen capture below shows the early development.
In the LabVIEW design we have 2 threads. The producer and consumer run in parallel threads. Splitting the queue's outputs and routing it to the two rectangles causes LabVIEW to multi-thread. There is no main thread at that time, because I don't need one. But if you look at it from the perspective of automating an instrument, then the Producer thread is the dominant one during the scanning execution.
In the drawing at the end of the previous section, you can see the two parts working together. It shows when threads are generated, and who waits when (or doesn't wait). It's a bit involved, the brain needs multitasking too to understand it. The Instrument Service implementation is mature. Once I have a reliable version of the LabVIEW part, I will think about simplifying it for the end consumer. It would be nice if I could club all the complexity into a separate utility block, and only expose the queue to the flow ...
The design is definitely not stable yet, but I can share the result of the first run with a signal attached to channel 0:
It's a tiny bit of a 50 Hz, 0.5 Vpp signal. I set the sample rate to 1024, samples to 64, and these are the 28 first results retrieved by LabVIEW, pasted into a spreadsheet.
You can retrieve the current status of all the software for this road test via this github tag: 20220414.
Link to all posts.