Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DMM Range Handler Functions #205

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft

Conversation

Jasper-Harvey0
Copy link
Collaborator

In the interest of improving compatibility between different DMM models, I have had an idea to add functions that handle all the logic behind setting ranges on the DMM.

The idea is to add all the available measurement ranges for a DMM into its driver (rip it straight out of the datasheet). This allows us to sanitize any requests to set the range on the DMM. Part of the motivation for this is because of the differences in current ranges for the high / low range ports on Fluke / Keithley DMMs.
The Fluke has a low range port that goes up to 400mA, whereas the Keithley's low range can go to 3A. Any jigs currently in production have been deployed with the assumption that they are using a Fluke DMM. Handling the range request will allow us to select the correct port on the Keithley to improve compatibility.

@Jasper-Harvey0 Jasper-Harvey0 changed the title Add proof of concept changes Add DMM Range Handler Functions Jul 26, 2024
@Jasper-Harvey0
Copy link
Collaborator Author

This would obviously be reflected in the Fluke driver should this idea be accepted.

@clint-lawrence
Copy link
Collaborator

I'm just going to brainstorm some idea without too much thought

  • have the current range functions return an enum which reflects the appropriate input for the selected range. That return value could be used to switch the terminal
  • add an additional parameter to the current measurement functions that constrain which input. dc_current(range=1, terminal="LOW") would raise an error on the fluke because the low range is limited to 400 mA. dc_current(range=0.2, terminal="HIGH") would set the range such that the high value terminal would be used
  • have an optional VirtualMux that is passed in the dc_current function, with signals "HIGH" and "LOW". Or may register it with the driver. Then when range is selected the driver would automatically switch

There are probably other options too. I'm tempted to think this might benefit from a white board session?

@clint-lawrence
Copy link
Collaborator

I think this is good concept in general. I'm not sold on the idea you've outlined where you silently force the range change. But I'm hopeful we can come up with a way to do this that makes it generally do the "obvious" things to be compatible between both DMMs, yet still be explicit and allow some flexibility.

For example, if I was intentionally using the keithly driver and it switch ranges on me like that, I'd find it very surprising.

The other thought in the back of my head is if we're should be really targeted at the current range (where there is a clear concrete impact) vs having something general which maps out the ranges for all measurement functions.

@Jasper-Harvey0
Copy link
Collaborator Author

Good ideas. My initial implementation was based on the idea that we could make driver changes and no test script changes.
The bad thing about it is it kind of locks in the behavior of the Fluke for all other DMM drivers which is not nice. There is also the fact that it silently modifies things without telling you.

@Jasper-Harvey0
Copy link
Collaborator Author

Jasper-Harvey0 commented Aug 2, 2024

I think I might be leaning towards returning an enum for the high / low ports. This allows test scripts to check what port is being used and do appropriate jig switching, and doesn't break any existing jigs.
It will look something like

class Fluke8846A(DMM):
    ...
    self.current_port_threshold = 400e-3 # Changeover range for high and low ports.
    ...

    def current_dc(self, _range=None):
        self._set_measurement_mode("current_dc", _range)
        if _range > self.current_port_threshold:
            # This will be the high current port
            return CurrentPort.HIGH
        else:
            # This will be the low current port
            return CurrentPort.LOW

jigs can then do:

if dm,dmm.current_dc(_range=3) == 0
    # Switch jig to low current port
else:
    # Switch jig to high current port

Or in the case of some existing jigs where changing the switching is not an option, we can do something like the below:

# Jig is restricted to high range port
measurement = 2.5 # Want to measure 2.5 A
crossover_range = dm.dmm.current_port_threshold # 3 A for Keithley, 0.4 A for Fluke
if crossover_range < measurement:
    dm.dmm.current_dc(_range=measurement)
else:
    dm.dmm.current_dc(_range=crossover_range*1.2) # Force high range port

@josh-marshall-amp
Copy link
Contributor

I think I might be leaning towards returning an enum for the high / low ports. This allows test scripts to check what port is being used and do appropriate jig switching, and doesn't break any existing jigs. It will look something like

class Fluke8846A(DMM):
    ...
    self.current_port_threshold = 400e-3 # Changeover range for high and low ports.

Delete the self here, which I think you meant to do anyway. It can/should be a class variable.

def current_dc(self, _range=None):
    self._set_measurement_mode("current_dc", _range)
    if _range > self.current_port_threshold:
        # This will be the high current port
        return CurrentPort.HIGH
    else:
        # This will be the low current port
        return CurrentPort.LOW

jigs can then do:

if dm,dmm.current_dc(_range=3) == 0
# Switch jig to low current port
else:
# Switch jig to high current port

Where have you put the CurrentPort(enum)? I would put it as a class variable.
This can then be written as if dm,dmm.current_dc(_range=3) == dmm.CurrentPort.HIGH... shouldn't be comparing to 0.

More abstractly... Why are we putting the responsibility on the jig test code rather than the driver? This is a manual intervention from the test operator to switch ports?

Side-nitpick: _range is anti-Pythonic, but I presume that's a legacy from early days. Should be range_.

Or in the case of some existing jigs where changing the switching is not an option, we can do something like the below:

# Jig is restricted to high range port
measurement = 2.5 # Want to measure 2.5 A
crossover_range = dm.dmm.current_port_threshold # 3 A for Keithley, 0.4 A for Fluke
if crossover_range < measurement:
    dm.dmm.current_dc(_range=measurement)
else:
    dm.dmm.current_dc(_range=crossover_range*1.2) # Force high range port

Context that I don't know much about the legacy stuff... I don't like this; too magical. If the test operator has to switch plugs manually to change range between HIGH and LOW, then that setting should always be an explicit action from the jig test code at the same time, given we know the expected measurement in advance.

If we measure something in an inappropriate range because the port is meant for a higher current then we can log a warning, but if we attempt to measure say 2.5A and the range is set to 0.4A that should raise a DamagingRangeError exception or something.

@josh-marshall-amp
Copy link
Contributor

And this was kinda only distantly related @Jasper-Harvey0, but a few days back I was thinking as to what a refactor would look like and my concept riffing off your code ended up like this.

The beauty of Python enums vs C is that you have easy access to both name and value at all times. In that sense they're almost like a bidirectional, const dictionary.

Note the enum is on the class. Then the Fluke could do the same, if you did that then the _select_range() and _get_ranges() could get moved to the DMM superclass.

Now of course this is once again not taking legacy issues into account, and the other guys have probably wanted to do this in the past but it's a big job. 🙃

class Keithley6500(DMM):
    class _ranges(enum.Enum):
        current_dc = ( 10e-6, 100e-6, 1e-3, 10e-3, 100e-3, 1, 3, 10,)
        current_ac = (100e-3, 1e-3, 10e-3, 100e-3, 1, 3, 10)
        voltage_dc = (0.1, 1, 10, 100, 1000)
        voltage_ac = (100e-3, 1, 10, 100, 750)
        resistance = (1, 10, 100, 1e3, 10e3, 100e3, 1e6, 10e6, 100e6)
        temperature = ()  # Empty. No ranges for temperature
        frequency = ( 300e-3,)  # No adjustable range for frequency. Just put maximum range here.
        period = (3.3e-6,)  # No adjustable range for period. Just put maximum range here.
        continuity = (1e3,)  # No selectable range for continuity. Put maximum range here.
        capacitance = (1e-9, 10e-9, 100e-9, 1e-6, 10e-6, 100e-6)
        diode = (10,)  # No selectable range for diode. Default is 10V

    def _select_range(self, value):
        if value is None:
            return None

        ranges = self._get_ranges()
        for i in ranges:
            if abs(value) <= i:
                return i
        raise ParameterError(
            f"Requested range '{value}' is too large for mode '{self.mode}'"
        )

    def _get_ranges(self):
        """
        Returns a tuple of available ranges for the current mode
        """
        if self.mode is None:
            raise InstrumentError("DMM mode is not set. Cannot return range")

        return self._ranges[self.mode].value # DMMRanges.mode_to_range[self.mode]

@Jasper-Harvey0
Copy link
Collaborator Author

Jasper-Harvey0 commented Aug 2, 2024

Yep current_threshold will be a class variable. I guess my example code was a bit too quick and dirty, comparing to 0 is definitely not the right way.

For some context. Up until about a year ago, we have only ever used a Fluke DMM. This has a low range current port that can measure up to 400mA, and a high range port for measurements up to 10A.
The new Keithley DMM has a low range port that goes up to 3A, and a high range that measures up to 10A. This means that when a test jig designed for the Fluke wants to measure, say 2.5 A, it will be passing current through the high range port on the DMM. But the Keithley would still select the 3A low range port (when given _range = 2.5)
I realized I just repeated my first comments in the description.

I think putting the responsibility on the test jig code is needed because it needs to know what current path to use (high or low range ports). And some jigs that are already designed may not even have the ability to swap ports. This is even more tangled because we cannot be designing the test code to use either fluke or keithley, we need to assume either can be used.

Maybe this just boils down to an education thing? - Make sure everyone that writes test scripts are aware that current measurements are scary and you should be careful and test with all available DMMs... After all, we almost never care about resolution in these measurements.

Also I am totally not attached to any solution, so whatever works I am happy with.

@clint-lawrence
Copy link
Collaborator

More abstractly... Why are we putting the responsibility on the jig test code rather than the driver? This is a manual intervention from the test operator to switch ports?

Because the driver doesn't know about (and shouldn't know about) how to switch signals in the jig. But the driver can provide a useful hint to the test script so it's knows what terminals to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants