06-06-2011 03:01 AM
Hello,
my test setup consists of an embedded PXI controller (8184 running LabVIEW RT) that uses a PXI-6221 for data acquisition. One of the 6221's analog outputs creates a speed demand that is sent to a servo controller; the servo then drives the test setup. Between the servo and the remaining test setup, a quadrature encoder is located. The two counters of the 6221 are used to measure the servo position (using the A and B pulse trains) as well as the servo speed (frequency of pulse train A). In addition to these counter inputs, some analog inputs of the 6221 are used as well to recored signals from the test setup.
In this setup, one of the analog signals has a frequency of n periods per servo revolution (determined by the mechanical design of the test setup). But when I let the servo spin at e.g. 100 rpm, then decelerate it to stand-still using a constant deceleration, and afterwards plot the analog signal against the measured angle, I can clearly see that as long as the speed is contant, I have n periods per revolution (or one zero crossing of my AC analog signal every 1/2n revolutions); however, as soon as the deceleration starts, the plot gets stretched along the angle axis (so the plot tells me there are less than n analog periods per revolution, which is impossible since it would require mechanical destruction of the setup).
However, I can calculate a position signal from the speed information I got from the second counter (by integration of the speed). When I do this for the speed profile mentioned above, and plot the analog signal against the calculated angle, I see exactly n periods per revolution, no matter how fast the setup is spinning (just the expected behaviour).
So apparently, the measured speed is "in phase" with the analog signals, while the measured angle has a "phase error". Plotting the measured and calculated angles against the time tells me the same: The measured angle always is late (compared to the calculated angle). The time delay is not constant throughout the measurement; I have seen values between 30 and 170 ms within one recording. Due to this variable shift, inserting a delay for all channels but the angle doesn't seem like a clever idea.
Unfortunately, calculating the position from the speed signal is no option for me, because the direction of rotation will change during the measurement; since the speed information I have is simply based on a pulse train frequency, it contains no direction information, so a calculated position would be unaware of direction changes.
Does anybody have a clue where thios time delay comes from and what I can do to deal with this problem?
Trying different position decoding methods or swapping the counter channels didn't make a difference.
Thanks!
Solved! Go to Solution.
06-06-2011 11:05 AM
Nothing in code jumps out at me as an obvious problem with a clear solution, but there are some things I'd suggest
probing around at. Overall, the code structure is generally sound and I can follow it despite not speaking German (?).
1. The analog acquisition doesn't appear to need the trigger. The shared sampling clock alone should help keep
everything in sync. I always worry about using the same edge as both trigger and sample clock because I'm
never sure if I'll get an off-by-one problem. (That is, will the first edge both trigger the task *and* produce a sample
or will it just trigger the task and then require a 2nd edge to produce the first sample?)
This seems *very* unlikely to be related to your phasing/delay issue, but may be lurking around to bite you later.
2. In general, your main data acq servicing loop may be a bit overconstrained for timing. You have both a msec
multiple timer and you also are constraining timing via the # samples you read/write from/to your buffered tasks.
If your loop execution time ever exceeds 1 msec, you may never catch up.
This overconstraint seems more likely to cause a buffer overflow or underflow error than the symptom you describe.
3. You're asking your data acq loop to iterate awfully fast (once per msec) for little obvious purpose. Since all you
appear to do each msec is accumulate data into growing arrays that are not passed along until after the loop ends,
I would suggest slowing the loop down and reading/writing more samples at a time but much less often.
This loop speed issue isn't likely to be the main culprit for your symptom either.
4. I'd like to focus on the AO task. I don't have a clear solution, but I do question some of what I see. I'd like you
to try this out with the AO Write removed from the main loop. That will mean setting up a completely pre-defined
AO array that you can write one time before starting the loop. The board will take care of all the rest since you've
set up your tasks to share a sample clock.
I have a suspicion that doing the AO Write in the loop right before the direct encoder measurement may somehow
play a role in the timing/phasing issue you have. I don't have a clear theory what that role may be, but it's the main
suspect I see that I'm reluctant to eliminate.
-Kevin P
06-06-2011 12:46 PM
Hi Kevin,
thanks a lot for your reply.
Some comments/remarks about the points you mentioned (and yes, it is German - it's the common language here, so the code is all in German; only it forgot to translate it):
1. Since this is my first LabVIEW project, I worked along several examples and code snippets I found, and always the AO tasks were triggered in these cases. I'll try it without the trigger.
2./3. I use the millisecond iteration because I didn't manage to get buffered frequency readings out of the 6221 (and also, the Example Finder in LabVIEW 2010 tells me that the "buffered frequency" examples are not usable with the 6221) - every attempt to do frequency measurements with "Sample Clock" timing results in error messages telling me that this measurement mode (e.g. frequency) is not supportd by the hardware. As a rersult, I have to do unbuffered frequency readings. So I only get one reading per loop - which means two things:
- If I need frequent speed updates, I need a fast iteration (speed gradients of more than 10000 rpm/sec can easily occur, so frequent updates are definitiely required), and
- If I want the speed channel to have the same number of elements as the other channels (so I can plot them against the same time axis), I need to "multiply" the measured speed (if I have 20 values per loop, the measured frequency needs to be written to the output array 20 times in a row). Basically, that's what the average/case structure stuff in the frequency channel does.
The 1 ms loop seems to be the maximum the 8184 controller can properly handle: currently, the CPU load is at about 45...50% during the measurement loop. With a Timed Structure, I can double the loop frequency to 2 kHz, with a CPU load somewhere around 95% - and with this loop frequency, the setup misses some samples every now and then (channels are shorter than they should be for a given measurement duration). So the 1 kHz loop frequency seems quite optimal here to me - it gives me the maximum speed update rate I can get without losing data.
The sample count constraint is also put there to enforce equal sample counts each loop, which makes handling the unbuffered frequency readings easier (otherwise, I'd have to get the length of the arrays returned by the AI and CI angle reads, and adjust my speed array size to that - which I don't like to do to keep the CPU load low).
However, I'll try it without the sample count constraint (the msec timer I want to keep to ensure a somewhat deterministic speed update rate).
4. You're right - given the dependency of the blocks, the AO write is executed before the CI Angle Read, so this could have something to do with the issue.
I don't know how big the ao buffer of the 6221is - given 4500 iterations with 20 samples each, that would mean 90k samples that have to be kept in the buffer. And there are also measurement modes with a measurement duration of approx. one minute (1.2M samples), so at least there I'd expect some buffer size issues - which means, I doubt I can do without the AO write in the loop.
But if the order of block execution is the problem, the phase error should move to a different channel once I re-arrange the blocks (e.g. by changing the wiring of the error lines), or even vanish if I insert a "dummy" AI read after the write - and that is something I can easily check.
06-06-2011 05:29 PM
2/3. Your description of taking unbuffered frequency readings doesn't match the code, which is actually configured to take buffered readings. You're correct that the sample rate is "implicit" in the signal being measured, and is thus neither constant nor predictable. I agree you have a problem to deal with if you want to correlate those measured frequencies with the other things you are measuring/generating at a specified constant rate. In addition to the different sample rates, you also have different start times for the tasks. More on this below.
Nevertheless, a key point is that you *can* buffer your frequency measurements as long as you use the "Implicit" sample timing mode. In fact, you *are* doing this in the screencap you posted. By buffering the measurement, you're free to slow down your software loop without losing data. And slowing the software loop may help solve other problems.
For the other tasks, I agree with the idea of handling equal # of samples from each task in each iteration. I would just make that number much larger to slow the iteration rate.
(BTW, I believe that the new-ish X-series boards support buffered frequency measurement with a constant sampling rate).
4. I'm almost positive you *can* avoid having the AO Write inside the loop. A key point is that the buffer used by the AO task isn't restricted to the daq board's own onboard FIFO. It's only restricted by available PC system RAM. So buffer sizes in the megabytes are absolutely supported. What actually happens is that your DAQmx Write will put the data into system RAM. Then the DAQmx driver will keep delivering chunks of that data to the board without any further effort on your part. It's pretty great, really.
If you can pre-define the 90k or 1.2M AO samples, go ahead and write them all at once before starting the AO task. You won't even need to interact witht the AO task inside the loop then unless you just want to check status or something.
Here's how I'd go about correlating the constant-sample-rate data with the variable-sample-rate frequencies:
A. For visualization purposes, you could use a multiplot XY Graph where many channels use a common X (Time) array, but the frequency data has to make its own unique X (Time) array. Fortunately, it's simply a cumulative sum of (1/Freq) and pretty easy to generate from your freq data.
B. For data logging purposes to a simple ASCII file, I'd do some kind of interpolation to estimate frequency values at the common sample time points. Then you'll have equal #'s of samples. Either way, you can wait and do all this stuff after the loop is done.
I have no further ideas about your original delay/phasing problem.
-Kevin P
06-07-2011 09:29 AM
Thanks a lot,
I hope I'll find the time to try your suggestions tomorrow. I'll let you know about the results.
06-08-2011 10:17 AM
Hi Kevin,
I worked through your proposals and checked what effect they have on the measurement. Attached you find my results.
1. Remove the trigger from the ai task: Works really well, but has no influence on the phase problem.
3. Slow down the main loop, alternative approach for frequency measurement: I replaced my frequency measurement with a variant that calculates the (implicit) sample times from the frequencies measured and later interpolates the speed samples to the same time steps that are also used in the ai and ao tasks (cf. the attached image). This approach works surprisingly well; I expected much more trouble from having different time bases in different tasks. However, since the frequency task and the measurement clock are not started in exactly the same moment, the time channel calculated from the frequencies might have a small offset from the "normal" time base, as you already mentioned; but from today's observations, this seems to be in the very low ms range. I have seen offsets in this range at other (functionally comparable) test setups that were professionally built and purchased for several 100.000 € - so this timing difference currently seem not like a major probelm to me. However, ideas on how to further reduce or even fully compensate it are very welcome (I was already thinking of putting all the stuff into a flat sequence, with all the preparation in the first frame, the DAQmx Start Task blocks in the second, the measurement loop in the third, and all the other stuff in the fourth - so as soon as the tasks will be started, all the setup stuff is already done; starting the tasks is not delayed by any other actions; and nothing else can come between the last task start and the acquisition loop).
This new speed measurement approach allows me to drastically reduce the loop frequency while still getting my speed updates at a higher rate than before. At 50 ms loop time, the CPU load is down at approx. 4...5%, but the phase error is still there. Further reducing the loop frerquency to 2 Hz (500 ms loop time) also didn't have an effect, so I currently stick with the 20 Hz.
2. Loosen the timing constraints on the measurement loop and the DAQmx Reads: This doesn't seem to be a very good idea. If I disconnect the sample count inputs from the DAQmx Read blocks for the ai ans angle tasks, the tasks without sample count specification occasionally create zero samples. That is, for a constantly spinning setup, the angle might be something like 50 - 51 - 52 - 0 - 53 - 54 - 55..., which doesn't make sense physically (the servo doesn't jump from a given position to zero and back in 100 µs). Also, the ai signals drop to zero every now and then for a single sample - which also doesn't make sense, keeping the physics of the setup in mind. So I keep the sample count definitions around, since the give me obviously better signals.
Also, removing the sample count defintion doesn't improve ther phase error.
4. Removing the ao from the main loop: The measurement works fine this way (I didn't try ther 1.2 M sample procedure, but only the short 90 k samples), but it also has no influence on the phase delay.
So due to your input, I now have a better speed measurement, along with a significantly decreased CPU load, which is definitely an improvement for the quality of the measured data - thanks a lot!
But unfortunately, the phase delay is still not eliminiated.
06-08-2011 11:21 AM - edited 06-08-2011 11:22 AM
Glad there's progress of a kind. I didn't have any good reason to expect the things I mentioned to help the original problem, but admit to a mild disappointment that they didn't help for ill-understood and mysterious reasons nonetheless.
<enter old grandad on a porch rocking chair w/ corncob pipe mode>
''cause sonny, the thing is, sometimes when you fix the little things, the big things take care of themselves."
Just one of those kernels of sometimes-wisdom that comes with experience. Just as odd bugs can appear for hard-to-anticipate reasons, they can also disappear for similarly hard-to-anticipate reasons. Anyway, it didn't seem to work out that way this time.
I did notice one other thing that might cause your analog input data to lag behind your other data, but not in a way that would change with motor speed. It'd just be an unknown offset in start time. It comes from the fact that you never explicitly start the ai task. Thus DAQmx must implicitly start the task inside your read loop. So the ai task won't start buffering data until some short time after the other tasks start.
Ooh! I just thought of something. It's only a maybe, but it *may* be the case that the DAQmx task state model causes the following behavior on each iteration.
A. Request to read samples from unstarted task, thus
B. Start the task
C. Read the samples
D. Revert to task state in existence prior to read request, i.e., not started, thus
E. Stop the task (and be blind to some sample clocks until next iteration when the task will redo this whole cycle)
Either way, you should start the ai task right after configuring its sample timing and before starting the sample clock generator. (The same way you start the analog out and angle measurement tasks.) Still not sure this'll help, but there's a glimmer of a working theory that it might.
That leaves us with 2 more things which I'll address in a followup post.
-Kevin P
06-08-2011 11:47 AM
2. I wasn't entirely clear before, but I agree that you should continue to specify a # samples to read from your AI and
angle tasks inside your loop. I *do* still think you're overconstrained, but the better way to remove the overconstraint
is to remove the wait timer. The loop will still be paced by the time it takes to acquire the # samples you're requesting,
and DAQmx will yield CPU while waiting.
Once you do that, I don't think the '0' value problem will return, but the issue is still worth addressing. The first thing
that came to mind is a DAQmx property called "duplicate count prevention." Under certain circumstances, that
setting can lead to some similarly odd results from counter tasks. But I don't actually think that's what happened to
you.
I think your problem came from fact you were building 1D arrays into 2D arrays. Since each loop was free to try to
read a different # of samples per iteration, the process of building 2D arrays out of unequally sized 1D arrays probably
led to padding with default values of 0. You could test this theory out by bundling each iteration's 1D array into a
cluster, then building a 1D array of these clusters of different-sized sample arrays. Your "unpack" step would need
to be modified a bit, but it wouldn't be a bad trick to learn how to do in the future.
-Kevin P
06-09-2011 09:48 AM
Hi Kevin,
I dindn't have that much time to work on the LabVIEW code today, but I could do a few tests. After what I saw today, it seems that putting the "Start Task" block in the place where the Rising Edge Trigger was before did the trick!
I hope I'll be able to provide a bit more feedback, along with a picture of the modified code, by tomorrow.
Thanks a lot again!
06-10-2011 09:19 AM - edited 06-10-2011 09:28 AM
Hi Kevin,
attached you find the latest block diagram (no new translation this time, since no changes were made that would require one), which includes your latest suggestions (explicitly start the ai task, remove the excess constraints) along with a flat sequence structure that enforces all task preparations to be finished before the first task is started (I know I could have reached the same effect by clever routing of the error signal, but I think the code is better readable this way).
Without the stucture, LabVIEW tends to start the ai, ao, and angle tasks at quite different times, which (for some reason that I don't really understand - we have a sample-clock based timing here, and the sample clock is started well after the three mentioned tasks...) tends to cause delays in the range of several 10-100 ms between the tasks. With the structure, this problem is eliminated.
The Sub-VI right above the DAQmx Write contains the "unwrapping" code that I used in the last screen shot to "serialize" the requested speeds - this way, the array that contains the speed demands doesn't need to be rebuilt.
The code that creates a time channel from the implicitly timed frequency measurements moved from the VI shown here to a second VI that runs on the host computer, since anything that involves some computation and does not require intzeraction with the DAQ hardware is better placed there (the 850 MHz celeton on the PXI controller is definitiely slower than the 2.something GHz Dual-Core host system).
The behaviour you mentioned for the "un-started" ai task matches what's written in the LabVIEW help. I thought that the trigger block I had there in the beginning would handle the task start - at least, code like this is contained at several points in the examples that come with LabVIEW.
I still don't really understand what caused the problems I had - it seems it was the combination of the quickly iterating measurement loop with the trigger-based ai task start, but I don't get what mechanisms in the background caused the problem (the over-constrained loop was not the cause; the same block diagram runs well with an additional loop timer).
So the solution consisted of the following steps:
- remove the old speed calculation code, replace it with better code outside the main loop. This allows to
- significantly decrease the main loop frequency.
- Pull the DAQmx Write out of the loop, and
- ensure that all tasks are started explicitly just before the main loop starts executing.
- Eventually remove excess constraints from the main loop.