Jump to content
  • 0

Python pydwf library - voltage input on microcontroller


Oznur Caliskan

Question

0

I am using pydwf library to use it as a Wavegen. For example I want to give a Sine Wave to my microcontroller pin or just DC. I looked into pydwf examples and wrote some code but I cannot change the waves amplitude or offset or anything even though I use nodeAmplitudeSet() or nodeOffsetSet() functions.

So what am I missing guys anyone to help me?

Thanks in advance.

For Sine wave:

    #Before this line it takes the values (frequency,offset etc.)
    for channel_index in (CH1, CH2):
        print("\nChannel Info: CH",channel_index)
        analogOut.nodeFunctionSet(channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.Sine)
        print("Function info:", analogOut.nodeFunctionInfo(channel_index, DwfAnalogOutNode.Carrier))
        analogOut.nodeFrequencySet(channel_index, DwfAnalogOutNode.Carrier, frequency)
        print("Frequency info:", analogOut.frequencyInfo(channel_index))
        analogOut.amplitudeSet(channel_index, amplitude)
        print("Amplitude info:", analogOut.amplitudeInfo(channel_index))
        analogOut.offsetSet(channel_index, offset)
        print("Offset info:", analogOut.offsetInfo(channel_index))
        analogOut.idleSet(channel_index, DwfAnalogOutIdle.Initial)
        print("Idle info:", analogOut.idleInfo(channel_index))
        analogOut.nodeEnableSet(channel_index, DwfAnalogOutNode.Carrier, True)
        print("Node enable info:", analogOut.nodeInfo(channel_index))
    t_stopwatch = 0.0
    counter = 0

    while True:
        t = time.monotonic() - t0

        vx = 2.5 * math.cos(2 * math.pi * t * float(frequency))
        vy = 2.5 * math.sin(2 * math.pi * t * float(frequency))

        # To change the output signal on each of the two channels, we just need to change the channel's
        # amplitude setting.

        analogOut.nodeAmplitudeSet(CH1, DwfAnalogOutNode.Carrier, vx)
        analogOut.nodeAmplitudeSet(CH2, DwfAnalogOutNode.Carrier, vy)

        for channel_index in (CH1, CH2):
            counter += 1
            if counter == 1000:
                duration = (t - t_stopwatch)  # pylint: disable=superfluous-parens
                print("{:8.3f} loops/sec. Press Control-C to quit.".format(counter / duration))
                print("Giving output:", analogOut.amplitudeInfo(channel_index))
                counter = 0
                t_stopwatch = t

For DC:

 offset_DC = input("Please enter the DC Offset value:")  # V
    while True:
        try:
            offset_DC = float(offset_DC)
            break
        except ValueError:
            offset_DC = input("Please enter a valid number:")
    print("DC Offset:", offset_DC, "\n")

    for channel_index in (CH1, CH2):
        analogOut.nodeFunctionSet(channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.DC)
        analogOut.idleSet(channel_index, DwfAnalogOutIdle.Initial)
        analogOut.nodeEnableSet(channel_index, DwfAnalogOutNode.value, True)

    t_stopwatch = 0.0
    counter = 0

    while True:
        t = time.monotonic() - t0

        analogOut.nodeAmplitudeSet(CH1, DwfAnalogOutNode.value, offset)
        analogOut.nodeAmplitudeSet(CH2, DwfAnalogOutNode.value, offset)

        counter += 1
        if counter == 1000:
            duration = (t - t_stopwatch)  # pylint: disable=superfluous-parens
            print("{:8.3f} loops/sec. Press Control-C to quit.".format(counter / duration))
            counter = 0
            t_stopwatch = t

 

Link to comment
Share on other sites

3 answers to this question

Recommended Posts

  • 0

Hi Oznur,

There's a lot to unpack in your question. The answer below is long; please read with care, as there is a lot of information to digest.


Let's tackle the Sine example first. Reading your code, I see several things that are probably not as intended.

First, after the various "Set" functions, you call the corresponding "Info" functions; I think, in an attempt to check if the value just set was processed. However, this is not the correct way to do that. A "...Set" function sets; the corresponding "...Get" function gets the currently configured value. The corresponding "...Info" does something else still, which is, give you info on the values that a certain parameter can take - for example, the range. Make sure you understand the difference, and understand when to use which.

Second, in your Sine example, for both channels, you configure function (Sine), frequency, amplitude, and offset. For some reason, you also call "idleSet()". Then, you enable the node. All fine so far (although the idleSet is not needed).

However, at that point, the device is still idle, i.e., not producing a waveform. To get the channel to generate the configured wave, you must call analogOut.configure(channel, True) next, which you currently do not do. Otherwise, the output channel will remain in Idle mode.

In the second half of the program you enter a loop where you are apparently attempting to generate a sine wave from within Python, by manipuating the amplitude of the signal that's currently playing. You don't generally want to do that if the device is already generating waveforms (which it will do with the aforementioned configure() call added).

The normal way of doing waveform generation is to configure the AnalogOut channels (like you do in the first half of your Sine program), then start them by calling configure(channel, True) -- perhaps using triggering, depending on your needs; e.g. if you want to make sure that your output channels are properly aligned. This method does not require any further Python action after the last configure(); in fact you can do a time.sleep(100.0) or input("press any key to stop") at the Python side then. See the very first example in the pydwf documentation in the second section of this page. Also, make sure don't immediately drop out of the Python program, since that will close the device and that, in turn, will halt any running waveform generation by the device.


There is in fact a second method to generate signals: setting a DC value programmatically from within Python, as you try to do in the second half of your Sine program, and there are some circumstances where you want to do that. There's a pydwf example for that as well, and it looks from your code as if you have been trying to adapt that, but this resulted in you mixing up two methods of control that you'd normally not mix, with confusing results.

The question you need to answer (which depends on your specific application): do I want my device to generate the waveform autonomously, or do I want to do that from a Python loop? If the former is possible, it is generally preferred, as you will be able to get MUCH tighter timing behavior and thus, cleaner waveforms. You don't want to try to generate a sine programmatically from Python; it will look horrible. You simply cannot get tight timing in Python; that's just a fact of life. The Python programming language on your desktop or laptop machine is not a microcontroller with tight and reproducible timing; all kind of stuff is going on that introduces a lot of jitter.

Now for the second example (the "DC" program), with user input, this is not a problem; you just want to get a value from the user at the side of the PC, and make your device's analog output channels produce that value as a voltage -- and you don't care about a dozen miliseconds here or there.

Instead of pointing out the problems in your code, it's probably better to just give a code fragment that will do what you're trying to do. Please study at your leasure the differences with your own version.

Note that this way of generating a signal under Python control actually is quite a clever trick, since this never enables (configures) the channel; instead, you use the documented behavior of the channel in case no signal is being generated. In that case (and only in that case), the Idle setting becomes relevant, as it says what the analog outputs will output when no waveform is being generated. We set it to Idle mode "Initial", and configure a square-wave waveform -- but without starting it (using a "configure")! In Idle mode "Initial", with an inactive channel, the channel will simply produce the very first value of the waveform that is not yet running, but is currently configured via the settings. And a square wave's first sample is precisely equal to the configured amplitude, which is why this trick works.

dwf = DwfLibrary()
with dwf.open() as device:

    # Initialize channel output of analog-out voltage controlled by Python.
    # We do this by a trick where we keep the channel disabled, and use the first value
    # of a square wave, which is output in Idle mode.
    CH1 = 0
    CH2 = 1
    for channel_index in (CH1, CH2):
        analogOut.nodeFunctionSet (channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.Square)
        analogOut.idleSet         (channel_index, DwfAnalogOutIdle.Initial)
        analogOut.nodeEnableSet   (channel_index, DwfAnalogOutNode.Carrier, True)

    while True:
        user_input = input("voltage> ")
        try:
            voltage = float(user_input)
        except ValueError:
            print("bad voltage entered")
            continue

        # We now have a valid voltage. Push set the emplitude of the (inactive) square wave.
        # Since the channel is configured to replicate the first value of the waveform when idle
        # (disabled), this value will show up on the output.
        
        for channel_index in (CH1, CH2):
            analogOut.amplitudeSet(channel_index, DwfAnalogOutNode.Carrier, voltage)


A similar effect could be achieved by having an actively generated signal with func "DC", and manipulating its value using the "offset" setting. However, that method turns out to be much less responsive, due to the very different way offsets are implemented in the hardware. The "amplitude" trick described and implemented above would normally be the way to go.

Hope this helps!

Edited by reddish
Link to comment
Share on other sites

  • 0
On 7/25/2023 at 12:31 AM, reddish said:

Hi Oznur,

There's a lot to unpack in your question. The answer below is long; please read with care, as there is a lot of information to digest.


Let's tackle the Sine example first. Reading your code, I see several things that are probably not as intended.

First, after the various "Set" functions, you call the corresponding "Info" functions; I think, in an attempt to check if the value just set was processed. However, this is not the correct way to do that. A "...Set" function sets; the corresponding "...Get" function gets the currently configured value. The corresponding "...Info" does something else still, which is, give you info on the values that a certain parameter can take - for example, the range. Make sure you understand the difference, and understand when to use which.

Second, in your Sine example, for both channels, you configure function (Sine), frequency, amplitude, and offset. For some reason, you also call "idleSet()". Then, you enable the node. All fine so far (although the idleSet is not needed).

However, at that point, the device is still idle, i.e., not producing a waveform. To get the channel to generate the configured wave, you must call analogOut.configure(channel, True) next, which you currently do not do. Otherwise, the output channel will remain in Idle mode.

In the second half of the program you enter a loop where you are apparently attempting to generate a sine wave from within Python, by manipuating the amplitude of the signal that's currently playing. You don't generally want to do that if the device is already generating waveforms (which it will do with the aforementioned configure() call added).

The normal way of doing waveform generation is to configure the AnalogOut channels (like you do in the first half of your Sine program), then start them by calling configure(channel, True) -- perhaps using triggering, depending on your needs; e.g. if you want to make sure that your output channels are properly aligned. This method does not require any further Python action after the last configure(); in fact you can do a time.sleep(100.0) or input("press any key to stop") at the Python side then. See the very first example in the pydwf documentation in the second section of this page. Also, make sure don't immediately drop out of the Python program, since that will close the device and that, in turn, will halt any running waveform generation by the device.


There is in fact a second method to generate signals: setting a DC value programmatically from within Python, as you try to do in the second half of your Sine program, and there are some circumstances where you want to do that. There's a pydwf example for that as well, and it looks from your code as if you have been trying to adapt that, but this resulted in you mixing up two methods of control that you'd normally not mix, with confusing results.

The question you need to answer (which depends on your specific application): do I want my device to generate the waveform autonomously, or do I want to do that from a Python loop? If the former is possible, it is generally preferred, as you will be able to get MUCH tighter timing behavior and thus, cleaner waveforms. You don't want to try to generate a sine programmatically from Python; it will look horrible. You simply cannot get tight timing in Python; that's just a fact of life. The Python programming language on your desktop or laptop machine is not a microcontroller with tight and reproducible timing; all kind of stuff is going on that introduces a lot of jitter.

Now for the second example (the "DC" program), with user input, this is not a problem; you just want to get a value from the user at the side of the PC, and make your device's analog output channels produce that value as a voltage -- and you don't care about a dozen miliseconds here or there.

Instead of pointing out the problems in your code, it's probably better to just give a code fragment that will do what you're trying to do. Please study at your leasure the differences with your own version.

Note that this way of generating a signal under Python control actually is quite a clever trick, since this never enables (configures) the channel; instead, you use the documented behavior of the channel in case no signal is being generated. In that case (and only in that case), the Idle setting becomes relevant, as it says what the analog outputs will output when no waveform is being generated. We set it to Idle mode "Initial", and configure a square-wave waveform -- but without starting it (using a "configure")! In Idle mode "Initial", with an inactive channel, the channel will simply produce the very first value of the waveform that is not yet running, but is currently configured via the settings. And a square wave's first sample is precisely equal to the configured amplitude, which is why this trick works.

dwf = DwfLibrary()
with dwf.open() as device:

    # Initialize channel output of analog-out voltage controlled by Python.
    # We do this by a trick where we keep the channel disabled, and use the first value
    # of a square wave, which is output in Idle mode.
    CH1 = 0
    CH2 = 1
    for channel_index in (CH1, CH2):
        analogOut.nodeFunctionSet (channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.Square)
        analogOut.idleSet         (channel_index, DwfAnalogOutIdle.Initial)
        analogOut.nodeEnableSet   (channel_index, DwfAnalogOutNode.Carrier, True)

    while True:
        user_input = input("voltage> ")
        try:
            voltage = float(user_input)
        except ValueError:
            print("bad voltage entered")
            continue

        # We now have a valid voltage. Push set the emplitude of the (inactive) square wave.
        # Since the channel is configured to replicate the first value of the waveform when idle
        # (disabled), this value will show up on the output.
        
        for channel_index in (CH1, CH2):
            analogOut.amplitudeSet(channel_index, DwfAnalogOutNode.Carrier, voltage)


A similar effect could be achieved by having an actively generated signal with func "DC", and manipulating its value using the "offset" setting. However, that method turns out to be much less responsive, due to the very different way offsets are implemented in the hardware. The "amplitude" trick described and implemented above would normally be the way to go.

Hope this helps!

Hi Reddish,

Appreciate your detailed response. I handled the Sine and DC manipulation well, thanks to you. However, I am not sure if I figured this out: When you want to give DC voltage, you set the idle value to initial to give DC more effectively. But when I want to give a square wave not DC, should I set the nodeFunctionSet(channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.Square) and idleSet(channel_index, DwfAnalogOutIdle.Offset)? Because I tried it and t behaves like I am giving DC voltage again.

 

        elif analogOut.signal_type == "3":  # Square
            CH1 = 0
            CH2 = 1
            # Gets amplitude value
            analogOut.amplitude = input("Please enter the Amplitude value:")  # V
            while True:
                try:
                    analogOut.amplitude = float(analogOut.amplitude)
                    break
                except ValueError:
                    analogOut.amplitude = input("Please enter a valid number:")
            print("Amplitude:", analogOut.amplitude, "\n")

            print("Processing...\n")
            dwf = DwfLibrary()
            t_stopwatch = 0.0
            counter = 0
            t0 = time.monotonic()

            while True:
                t = time.monotonic() - t0
                # Channel infos
                for channel_index in (CH1, CH2):
                    analogOut.nodeFunctionSet(channel_index, DwfAnalogOutNode.Carrier, DwfAnalogOutFunction.Square)
                    analogOut.nodeEnableSet(channel_index, DwfAnalogOutNode.Carrier, True)
                    analogOut.amplitudeSet(channel_index, analogOut.amplitude)
                counter += 1
                if counter == 500:
                    for channel_index in (CH1, CH2):
                        duration = (t - t_stopwatch)  # pylint: disable=superfluous-parens
                        print("{:8.3f} loops/sec. Press Q to quit.".format(counter / duration))
                        print("Giving output for Channel", CH1, "is:", analogOut.amplitudeGet(CH1))
                        print("Giving output for Channel", CH2, "is:", analogOut.amplitudeGet(CH2), "\n")
                    counter = 0
                    t_stopwatch = t

 

Thanks,

Best regards

Link to comment
Share on other sites

  • 0

Hi @Oznur Caliskan

If you want the AnalogOut channel to autonomously generate a waveform (be it Sine, Triangle, Square, Ramp, or whatever), you need to set all parameters, and follow those with an `analogOut.configure(channel, True)` call, to start the waveform generation. In your code above, it seems like that "configure" call is missing.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...