10-08-2020 08:48 AM
Hey there,
I am trying to count the rising edges of an internal timer of our USB-6366 (X-Series) device with Python via nidaqmx.
I found the following example to do that in the git repository ni/nidaqmx-python:
https://github.com/ni/nidaqmx-python/blob/master/nidaqmx_examples/ci_count_edges.py
This example doesn't work for me. I am always getting 0 as the count.
Therefore I tried these lines of code:
with nidaqmx.Task() as task:
task.ci_channels.add_ci_count_edges_chan("Dev1/ctr0")
task.timimng.cfg_samp_clk_timing(10000000, "10MHzRefClock")
task.start()
print("data:", task.read(1000))
I really don't have a clue if that's the right approach, but it makes (at least a little bit) sense to me.
If there are any better solutions, I will appreciate some hints!
Please keep in mind, that my program should be as simple as it sounds: just counting.
Thanks in advance. 🙂
Solved! Go to Solution.
10-08-2020 09:05 AM
I don't know the Python API, but I *can* tell you this:
A sample clock is used to latch an instantaneous count value into a buffer. At the moment, it doesn't seem like you want a sample clock or buffer, so get rid of the call that sets up task timing.
There will be some other API function where you can define a 'source terminal' for your edge counting task. There you can specify one of the internal timebases or (probably) the 10 MHz Ref Clock. Sometimes there are routing restrictions about which timing signals can be routed where, but the X-series are quite remarkably flexible about this in general.
Then the next thing is not to try to read multiple sample values, which is only possible when one *does* configure sample timing and buffering. Read just 1 sample and if there's a distinct API function dedicated to reading single samples at a time, use *that* one. (This appears to be the problem in the linked example. It does not set up a sample clock or buffer, but tries to read multiple samples at a time.)
-Kevin P
10-08-2020 09:59 AM
Hey Kevin,
thank you for your really fast reply! 🙂
I found this method in the documentation by using "source terminal" as key words:
connect_terms(source_terminal, destination_terminal, signal_modifiers=<SignalModifiers.DO_NOT_INVERT_POLARITY:
0>)
Creates a route between a source and destination terminal. The route can carry a variety of digital signals, such as triggers, clocks, and hardware events.
Parameters
• source_terminal (str) – Specifies the originating terminal of the route. A DAQmx
terminal constant lists all terminals available on devices installed in the system. You also
can specify a source terminal by specifying a string that contains a terminal name.
• destination_terminal (str) – Specifies the receiving terminal of the route. A
DAQmx terminal constant provides a list of all terminals available on devices installed in
the system. You also can specify a destination terminal by specifying a string that contains
a terminal name.
• signal_modifiers (Optional[nidaqmx.constants.
SignalModifiers]) – Specifies whether to invert the signal this function routes from
the source terminal to the destination terminal.
Because I am working with NI devices for just a few days at the moment, I am unsure about what this method really does. Regardless of the Python API I didn't really understand what I am supposed to do here to make this work.
I used this line of code before to read an AI signal, therefore I thought it might be working with the counters, too:
10-08-2020 01:06 PM
Maybe try just 'source' as a search keyword? I do quite a bit with DAQmx and "Connect Terminals" is a fairly advanced function that I never really use.
A long-traditional *OMISSION* in NI's counters is the ability to use an internal timebase to derive a sample clock of more-or-less arbitrary frequency. The AI and AO subsystems can do this, DI and DO subsystems can do this nowadays (wasn't always true), but Counter tasks can't. That's why your attempt to adapt the AI code didn't work seamlessly.
I've been around long enough to be used to this omission, but I have no insight into why it was left out of the design(s) in the first place.
To set up sample timing and buffering for a counter task, you need to supply it with a sample clock signal somehow. Quite often, I generate one with another counter task set up for pulse generation. Other times I already have an AI task in the app and I can tap into its already-derived sample clock.
The counters are pretty flexible and there's a lot you can do with them, but some of the config tends to be considerably less obvious than AI, AO, DI, and DO.
-Kevin P
10-09-2020 03:08 AM
Hi Kevin (and to whom it may concern),
we found something!
with nidaqmx.Task() as task:
task.ci_channels.add_ci_count_edges_chan("Dev1/ctr0")
# if you need to prescale
task.ci_channels[0].ci_prescaler = 8
# reference the terminal you want to use for the counter here
task.ci_channels[0].ci_count_edges_term = "100kHzTimebase"
print(task.read(100))
task.stop()
It's still a bit magical, but we get something back, that at least looks like counting. If we use the 20MHzTimebase or the 10MHz, it counts faster. (I guess that is a good omen. :D)
Thanks again, Kevin!
10-09-2020 08:30 AM
Those sound like the right kind of functions. I'm a little surprised by the read(100) which looked to me like an attempt to read from a DAQmx task buffer which you hadn't configured (and maybe in part because I told you not to bother). But it's also quite possible that particular function merely runs its own loop to iterate 100 times and read 1 unbuffered "on-demand" value each time.
Prescaling is a very rarely needed option, you won't have to configure it. When you do, it engages some special higher-speed front-end circuitry that can divide down an extremely high freq clock to something in the 10's of MHz the rest of the device can handle. I don't think I've ever needed to use it.
The only other small tip I'd give is that you probably should get in the habit of explicitly starting your task before trying to read counts from it. In many circumstances DAQmx will implicitly start the task for you if you ask to read before starting explicitly, but I think starting explicitly is a better habit to establish.
-Kevin P
10-09-2020 11:39 AM
Yes, I know you told me not to do read(100), but I tried read() in a for loop, but the output was a little bit weird.
I've needed prescaling once when I worked with a microcontroller, where it was essential to use it, because the clock was extremely high compared to what I needed and otherwise it would have ended in an overflow everytime. But if you say, we don't need it here, I will get rid of it.
I've read, that read() starts the task automatically, but I will start the task explicitly in future, Kevin, thanks. 🙂
We've discovered some other issues this morning. Do you mind to help me again? 🙂
new problem:
We generate two small rectangle pulses on one channel. There must be a specific time between those to pulses, where the signal is low (see the picture below). We defined this time as the low time of pulse 1.
Here is how we do this:
- We create a task with two counter channels (e.g. ctr0 and ctr1)
- As you can see in the picture above, they have different high and low times
- We defined delta t as the low time oft ctr0 and started both counters at the same time, but ctr1 with an initial delay of the periodic time of pulse 1
My question: Is there a better approach to do that? Maybe with pulse witdh or something like this.
I omit the code, because you said you are not familiar with the Python API. It's more about the general way of thinking.
10-11-2020 08:40 AM
You're getting into some precision variable timing things here, and it's gonna require a little detour to get familiar with DAQmx terminology & conventions, and some other counter capabilities and limitations.
1. You can't direct 2 different counters to both send their pulse signals to the same physical output pin. You need a way to do this with a single counter (or possibly with a DO task).
2. Sticking with counter-based solutions, this needs to be a buffered counter output task. You'll define pulse timing parameters for both pulses ahead of time, then the hardware will do all the precision timing after you start the task.
3. But first understand some DAQmx conventions and terminology. By definition, a pulse will first spend time in its Idle state (low by default) and then time in its pulse state (high by default). DAQmx will consider your delta t to be the low time of pulse 2. The low time of pulse 1 would be the time from 0 until it transitions high.
4. In an unbuffered counter task, the very first time period spent in idle (low) state is defined by an 'initial delay' parameter instead of by the low time. I don't think that holds true in the case of a buffered output, but cannot test to be sure. I would expect a buffered pulse output to use only the array of pulse timing parameters for low time and high time, while ignoring the 'initial delay' parameter.
5. Variable-timing buffered pulse output is a kind of special purpose operation mode. In LabVIEW, the set of timing parameters are represented as an array of clusters. A cluster is much like a "struct" in C where you can aggregate several other datatypes into one container.
I don't know if Python will have a similar data structure or whether it'll have you just send two separate arrays of low times and high times, while emphasizing that the arrays should have the same length.
But the overall message is that variable pulse timing will require a buffered counter task. As stated before, you might also consider doing it with DO, in which case you need to use a pin from port 0. Those are the DO outputs that can be controlled with hardware timing.
-Kevin P
10-12-2020 04:03 AM
Hey Kevin,
I discovered a mistake in my last entry (it was late on friday).
We use two (for whatever reason I said one) different physical channels.
So we initialize both channels in the task and start them at the exact same time. Because pulse 2 has the initial_delay property set, it will start later.
Here is some pseudo code:
# create a new task
task = new nidaqmx.Task()
# add the counter channel for pulse 1
task.add_counter_channel("ctr0", high_time=10ms, low_time=5ms)
# add the counter channel for pulse 1
# delay = high time of pulse 1 + low time of pulse 1 (one period)
task.add_counter_channel("ctr1", high_time=20ms, low_time=10ms, initial_delay=15ms)
# we need only one sample of both pulses
task.set_implicit_timing(AcquisitionType.FINITE, 1)
# after configuration, start the task
task.start()
# wait until done, to safely stop the task
task.wait_until_done()
task.stop()
I hope that's at least a little bit understandable. 😄
Nevertheless the following is what I would like to have (or something like that):
# configurations
counter = new nidaqmx.Task()
counter.set_counter(100kHz, rising_edge)
counter.threshold = 100
pulse1= new nidaqmx.Task()
pulse1.add_do_channel("port0/line0")
pulse2= new nidaqmx.Task()
pulse2.add_do_channel("port0/line1")
counter.start()
# something like a callback function, if the counter reaches his first rising_edge
counter.on_rising_edge(pulse1.start())
# start pulse2 immediately after reaching the threshold
counter.on_reached_threshold(pulse2.start())
Here is a picture:
I tried to take your advices and apply them to my problem. I will also have a look at those buffered counters (if I find something, it's a little bit hard, because the Python API isn't nearly as well documented as the C API or LabView).
One day I have to send you flowers or chocolate or something you like, Kevin. 😄
10-12-2020 09:05 AM
That was a good explanation, but I still want more.
It seems like you're describing your problem in terms that are constrained by your initial ideas of *how* you think you want to solve it. I just want to focus on the *what*. What signals do you need to create? What signals do you need to measure (and in the case of counters, what kind of measurement)? What signals do you need to react to? How? What signals are regular and repeating, which ones may repeat at unknown intervals, and which are one-time-only?
Something like the timing diagram you drew up tells a lot, but then also let me know if the internal 100 kHz clock you referenced is what you *really* want as a pacing clock or whether it's a placeholder until you hook up to a real system with a different signal. Under what circumstances will this pattern need to repeat? Will the threshold value remain constant throughout the time the task is running, or do you intend to change it in real time and see pulse 2 start sooner or later relative to pulse 1?
Based on what I've seen so far, this seems like it could be a good fit for a buffered DO task without any counter task. The 100 kHz clock acts as a sample clock, and the way you define the buffer of DO values establishes all of the pulse timing. No need for any counting. Note that only port 0 can be used for hardware-timed DO on X-series devices.
-Kevin P