Counter/Timer

cancel
Showing results for 
Search instead for 
Did you mean: 

Using quadrature encoder with callback function

Hello hello,

I have a rotary encoder (A-B signal) hooked up to a DI module (NI 9423) and I want it to be my source for a callback. This encoder measures distance and I now want to enter a callback function every meter for instance.

When I am only moving in one direction, everything is fine. With the DAQmxCreateCOPulseChanTicks function I set the source to "/Dev1/PFI0" and it will call the testCallback after 1 revolution (1024 ticks) only taking channel A from the encoder into account.

 The problem is that I need it to be dependent on the actual position. Meaning I need channel B as well. So how can I achieve that? As you can see I am trying to achieve this in C++ and it seems rather difficult to me.  

 

DAQmxErrChk (DAQmxCreateTask("",&taskHandle));

DAQmxErrChk (DAQmxCreateCOPulseChanTicks(taskHandle,"Dev1/ctr0","", "/Dev1Mod1/PFI0",DAQmx_Val_Low,0,1024,1024));

DAQmxErrChk (DAQmxCfgImplicitTiming(taskHandle,DAQmx_Val_ContSamps,1000));

DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle, DAQmx_Val_CounterOutputEvent, 0, testCallback, NULL));

DAQmxErrChk (DAQmxStartTask(taskHandle));

The examples provided from NI are quite helpful however none of them, targets this kind of application. With these I also achieved to calculate the current position (“DAQmxCreateCIAngEncoderChan“).

Maybe there is some sort of workaround. Maybe I could create a 2nd callback for channel B and then handle the 2 channels myself.... 

 

0 Kudos
Message 1 of 16
(2,938 Views)

I don't believe there's a way to make a counter task generate an event or signal for a specific count value other than the single specific value 0, a.k.a. "terminal count". 

 

(If memory serves, even this special case doesn't work out perfectly for encoder-based usage.  In the + direction, I believe that the terminal count event gets asserted at the edge that would transition from u32 max "up" to 0.   In the - direction, I *think* it gets asserted on the edge that brings the count from 1 down to 0 rather than from 0 "down" to u32 max.  I'll try to remember to check this later when near hw.)

 

There may be some indirect schemes where the encoders fixed-position index pulse is used to (re-)trigger a single pulse whose output generates an event/signal.  I haven't worked through all details, but you'd *at least* need a counter dedicated to each direction of travel, and both would end up firing in a single rev.   Only one of them should be "obeyed" so the callback would need to know how to figure out which one to ignore.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 2 of 16
(2,923 Views)

Thanks Kevin for the quick response.

That would explain why I just cannot find a solution.

 

One work around which I am trying out now looks like this:

  1. Create a digital input channel
  2. Change detection timing
  3. Register Signal Event
  4. Read digital input value
  5. Count “ticks” up or down
  6. Call my function after ticks reached a certain value
DAQmxErrChk (DAQmxCreateTask("",&taskHandle));
DAQmxErrChk (DAQmxCreateDIChan(taskHandle,"Div1/port0/line0, Div1/port0/line2","", DAQmx_Val_ChanPerLine));
DAQmxErrChk(DAQmxCfgChangeDetectionTiming(taskHandle, "/Div1/port0/line0", "", DAQmx_Val_ContSamps, 1));	
DAQmxErrChk(DAQmxRegisterSignalEvent(taskHandle, DAQmx_Val_ChangeDetectionEvent, 0, testCallback, NULL));
DAQmxErrChk (DAQmxStartTask(taskHandle));
....
testcallback{
DAQmxErrChk(DAQmxReadDigitalU32(taskHandle, -1, -1, DAQmx_Val_GroupByScanNumber, data, 2, &sampsRead, NULL));

// Count up or down and call another function when a certain value has been reached
}

One problem with this approach is, that it comes unresponsive if I am moving the encoder too fast. Any ideas?

0 Kudos
Message 3 of 16
(2,906 Views)

Not really any great ideas.  It sounds like the standard problem where if you generate interrupts faster than your handler can process them, the program hangs.

 

I like your basic idea of using a change detection event callback, that was an interesting approach.  You might *possibly* make it a little more responsive if you don't do your quad decode and counting in software.  (Your software cuts off without showing ths, but the comment makes it sound like you count in software.)  Here's the idea:

 

Parallel-wire your encoder to both digital lines 0,1 and to the PFI pins used for counter encoder signals A,B.  When the callback fires, simply do a software read of the counter value to make your decision.  I don't think it's likely to help a lot, but who knows, maybe you only need an incremental speedup.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 4 of 16
(2,900 Views)

Thanks, but I tried that too.

main: 
DAQmxErrChk(DAQmxCreateCIAngEncoderChan(taskHandleEnc, "Dev1/ctr0", "", DAQmx_Val_X1, 0, 0.0, DAQmx_Val_AHighBHigh, DAQmx_Val_Ticks, 1024, 0.0, "")); .... in the callback: DAQmxErrChk(DAQmxReadCounterF64(taskHandleEnc, 1, -1, data, 1, &read, 0));

It might be a little faster but I still have problems with the program hanging. This problem seems to be lying in the DAQmxReadCounterF64 function. I will keep on testing, maybe I figure out a way how to solve the issue.

 

Would a digital output module help anyhow? I wouldn’t know how, but in the back of my head I think I once read about such a solution. That’s a little broad question though, sorry.

0 Kudos
Message 5 of 16
(2,890 Views)

Dependence on the USB bus (you mentioned a cDAQ module) is likely also a limiting factor.  You're looking for a callback to be initiated on every encoder edge.  It's likely that the driver mechanism for issuing callbacks is being overwhelmed.

 

I'd suggest switching over to use a desktop board instead.if possible.  Otherwise, I'm inclined to point back to my earlier idea in msg #2.  That scheme would only generate 2 callbacks per rev, and you'd ignore one of them.  That's a much lower rate than 1 callback per encoder edge.  (Note that you'd still need to work out an efficient method for keeping track of motion direction.)

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 6 of 16
(2,885 Views)

Thank you very much Kevin. I do have to admit I lacked (and certainly still do) basic understanding of some of the functions and how they work together. That applies in particular for the DAQmxReadCounter function. However my trial and error approach in combination with your answers get me step by step closer to an acceptable solution.

 

Indeed, I do not need to call my function so fast. Before, I read every sample of my counter when it arrived. Now I just wait until a certain amount of counts have been collected (e.g every 512 counts which is half of a revolution). This, with the combination of DAQmxCreateCIAngEncoderChan seems to render the callback unnecessary, because the read function waits for the counts anyway and gives me the current position.

 

Probably like this:

 

// Encoder task
DAQmxErrChk(DAQmxCreateTask("", &taskHandleEnc));
DAQmxErrChk(DAQmxCreateCIAngEncoderChan(taskHandleEnc, "Dev1/ctr0", "", DAQmx_Val_X1, 0, 0.0, DAQmx_Val_AHighBHigh, DAQmx_Val_Ticks, 1024, 0.0, ""));
DAQmxErrChk(DAQmxCfgSampClkTiming(taskHandleEnc, "/Dev1/PFI0", 1000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 1));

// Create while loop where the read counter function waits for the signals and then call a function 
while (1)
{
DAQmxErrChk(DAQmxReadCounterU32(taskHandleEnc, 512, -1, data, 512, &read, 0));
testfunction();
}

In the test function I can check the read data value and proceed accordingly.

I reckon that could work for me...

0 Kudos
Message 7 of 16
(2,880 Views)

I only program in LabVIEW and don't know syntax details for the C++ API.  I can generally follow what you've presented, but don't have 100% clarity on some details.

 

Your latest code and description don't *appear* to me to solve the problem you laid out originally.  It looks like another method that works only as long as the motion direction stays constant.

 

For example, suppose that you get a set of 512 values as the encoder hits the 0 deg position and you expect the next set at the 180 deg position.  Motion continues in the same direction for 412 more samples, then reverses direction.  The next time you collect 512 samples, you won't be at the 180 deg position.  You'll be at ((412-100)/1024)*360 deg.  The exact # doesn't matter here, the point is that you aren't guaranteeing that your code "wakes up" at the same *position* each time it collects the next 512 samples.

 

My suggestion about 2 distinct callbacks, only 1 of them relevant for each motion direction, wasn't described in full detail and frankly, thinking about it more makes me realize that it's liable to get pretty complicated.  I think it stands a chance of working though -- so it's got that going for it, which is nice.

 

Before getting into messy details, here's a couple crucial questions:

1. does the encoder have an index pulse channel?  (usually labelled either I or Z)

2. consider the discrete positions where you want your code to react.  Do they always correspond to the same fixed encoder displacement relative to its index pulse?

 

My method kinda depends on both answers being Yes.  If either one is a No, I don't think I have a good alternative method.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 8 of 16
(2,867 Views)

Of course you are absolutely right regarding the positioning.

However what you didn’t know (which I didn’t know until recently) is that the sensor from which I am receiving the data is able to store the data in a buffer. It is able to process the encoder data on its own. Therefore it could be sufficient to just ask somewhere in between my “trigger” positions and check if new buffered data is available.

 

Anyway that means I do not initiate a callback equidistantly, so I reckon your version is more neatly. Especially as I do not know how this project will develop further.

 

To answer your questions:

  1. Yes, the encoder has a Z-signal
  2. Yes, I could pull the data every half revolution
0 Kudos
Message 9 of 16
(2,863 Views)

I haven't connected all the dots about how the buffering capability helps with what I *thought* you needed initially -- the ability to trigger callbacks (presumably for the sake of decisions and reactions) equidistantly even in the presence of direction changes.

 

No worries though -- if you've got something that's working now there's no need to try to clear up my confusion.  I'll forget all about it soon enough anyway.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 10 of 16
(2,857 Views)