-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding support for the minghe mhs5200 (#150)
* added mhs5200 * added minghe function generator * added absolute_import * fixed scaling on frequency * switched to abstract instrument class * fixed a few docstrings * after testing with device * Minghe MHS5200 - Add instrument to docs * isolating changes from cc1 test station: * Revert "isolating changes from cc1 test station:" This reverts commit 87b8dec. * reverting changes and fixing duty cycle * Update for new FunctionGenerator multichannel consistency update
- Loading branch information
1 parent
7ccdc13
commit e05dad5
Showing
6 changed files
with
496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#!/usr/bin/python | ||
from instruments.minghe import MHS5200 | ||
import quantities as pq | ||
|
||
mhs = MHS5200.open_serial(vid=6790, pid=29987, baud=57600) | ||
print(mhs.serial_number) | ||
mhs.channel[0].frequency = 3000000*pq.Hz | ||
print(mhs.channel[0].frequency) | ||
mhs.channel[0].function = MHS5200.Function.sawtooth_down | ||
print(mhs.channel[0].function) | ||
mhs.channel[0].amplitude = 9.0*pq.V | ||
print(mhs.channel[0].amplitude) | ||
mhs.channel[0].offset = -0.5 | ||
print(mhs.channel[0].offset) | ||
mhs.channel[0].phase = 90 | ||
print(mhs.channel[0].phase) | ||
|
||
mhs.channel[1].frequency = 2000000*pq.Hz | ||
print(mhs.channel[1].frequency) | ||
mhs.channel[1].function = MHS5200.Function.square | ||
print(mhs.channel[1].function) | ||
mhs.channel[1].amplitude = 2.0*pq.V | ||
print(mhs.channel[1].amplitude) | ||
mhs.channel[1].offset = 0.0 | ||
print(mhs.channel[1].offset) | ||
mhs.channel[1].phase = 15 | ||
print(mhs.channel[1].phase) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.. | ||
TODO: put documentation license header here. | ||
.. currentmodule:: instruments.minghe | ||
|
||
====== | ||
Minghe | ||
====== | ||
|
||
:class:`MHS5200` Function Generator | ||
=================================== | ||
|
||
.. autoclass:: MHS5200 | ||
:members: | ||
:undoc-members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Module containing MingHe instruments | ||
""" | ||
from __future__ import absolute_import | ||
from .mhs5200a import MHS5200 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Provides the support for the MingHe low-cost function generator. | ||
Class originally contributed by Catherine Holloway. | ||
""" | ||
|
||
# IMPORTS ##################################################################### | ||
|
||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
from builtins import range | ||
from enum import Enum | ||
|
||
import quantities as pq | ||
from instruments.abstract_instruments import FunctionGenerator | ||
from instruments.util_fns import ProxyList, assume_units | ||
|
||
# CLASSES ##################################################################### | ||
|
||
|
||
class MHS5200(FunctionGenerator): | ||
""" | ||
The MHS5200 is a low-cost, 2 channel function generator. | ||
There is no user manual, but Al Williams has reverse-engineered the | ||
communications protocol: | ||
https://github.com/wd5gnr/mhs5200a/blob/master/MHS5200AProtocol.pdf | ||
""" | ||
def __init__(self, filelike): | ||
super(MHS5200, self).__init__(filelike) | ||
self._channel_count = 2 | ||
self.terminator = "\r\n" | ||
|
||
def _ack_expected(self, msg=""): | ||
if msg.find(":r") == 0: | ||
return None | ||
# most commands res | ||
return "ok" | ||
|
||
# INNER CLASSES # | ||
|
||
class Channel(FunctionGenerator.Channel): | ||
""" | ||
Class representing a channel on the MHS52000. | ||
""" | ||
# pylint: disable=protected-access | ||
|
||
__CHANNEL_NAMES = { | ||
1: '1', | ||
2: '2' | ||
} | ||
|
||
def __init__(self, mhs, idx): | ||
self._mhs = mhs | ||
super(MHS5200.Channel, self).__init__(parent=mhs, name=idx) | ||
# Use zero-based indexing for the external API, but one-based | ||
# for talking to the instrument. | ||
self._idx = idx + 1 | ||
self._chan = self.__CHANNEL_NAMES[self._idx] | ||
self._count = 0 | ||
|
||
def _get_amplitude_(self): | ||
query = ":r{0}a".format(self._chan) | ||
response = self._mhs.query(query) | ||
return float(response.replace(query, ""))/100.0, self._mhs.VoltageMode.rms | ||
|
||
def _set_amplitude_(self, magnitude, units): | ||
if units == self._mhs.VoltageMode.peak_to_peak or \ | ||
units == self._mhs.VoltageMode.rms: | ||
magnitude = assume_units(magnitude, "V").rescale(pq.V).magnitude | ||
elif units == self._mhs.VoltageMode.dBm: | ||
raise NotImplementedError("Decibel units are not supported.") | ||
magnitude *= 100 | ||
query = ":s{0}a{1}".format(self._chan, int(magnitude)) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def duty_cycle(self): | ||
""" | ||
Gets/Sets the duty cycle of this channel. | ||
:units: A fraction | ||
:type: `~quantities.Quantity` | ||
""" | ||
query = ":r{0}d".format(self._chan) | ||
response = self._mhs.query(query) | ||
duty = float(response.replace(query, ""))/10.0 | ||
return duty | ||
|
||
@duty_cycle.setter | ||
def duty_cycle(self, new_val): | ||
query = ":s{0}d{1}".format(self._chan, int(100.0*new_val)) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def enable(self): | ||
""" | ||
Gets/Sets the enable state of this channel. | ||
:param newval: the enable state | ||
:type: `bool` | ||
""" | ||
query = ":r{0}b".format(self._chan) | ||
return int(self._mhs.query(query).replace(query, ""). | ||
replace("\r", "")) | ||
|
||
@enable.setter | ||
def enable(self, newval): | ||
query = ":s{0}b{1}".format(self._chan, int(newval)) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def frequency(self): | ||
""" | ||
Gets/Sets the frequency of this channel. | ||
:units: As specified (if a `~quantities.Quantity`) or assumed to be | ||
of units hertz. | ||
:type: `~quantities.Quantity` | ||
""" | ||
query = ":r{0}f".format(self._chan) | ||
response = self._mhs.query(query) | ||
freq = float(response.replace(query, ""))*pq.Hz | ||
return freq/100.0 | ||
|
||
@frequency.setter | ||
def frequency(self, new_val): | ||
new_val = assume_units(new_val, pq.Hz).rescale(pq.Hz).\ | ||
magnitude*100.0 | ||
query = ":s{0}f{1}".format(self._chan, int(new_val)) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def offset(self): | ||
""" | ||
Gets/Sets the offset of this channel. | ||
:param new_val: The fraction of the duty cycle to offset the | ||
function by. | ||
:type: `float` | ||
""" | ||
# need to convert | ||
query = ":r{0}o".format(self._chan) | ||
response = self._mhs.query(query) | ||
return int(response.replace(query, ""))/100.0-1.20 | ||
|
||
@offset.setter | ||
def offset(self, new_val): | ||
new_val = int(new_val*100)+120 | ||
query = ":s{0}o{1}".format(self._chan, new_val) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def phase(self): | ||
""" | ||
Gets/Sets the phase of this channel. | ||
:units: As specified (if a `~quantities.Quantity`) or assumed to be | ||
of degrees. | ||
:type: `~quantities.Quantity` | ||
""" | ||
# need to convert | ||
query = ":r{0}p".format(self._chan) | ||
response = self._mhs.query(query) | ||
return int(response.replace(query, ""))*pq.deg | ||
|
||
@phase.setter | ||
def phase(self, new_val): | ||
new_val = assume_units(new_val, pq.deg).rescale("deg").magnitude | ||
query = ":s{0}p{1}".format(self._chan, int(new_val)) | ||
self._mhs.sendcmd(query) | ||
|
||
@property | ||
def function(self): | ||
""" | ||
Gets/Sets the wave type of this channel. | ||
:type: `MHS5200.Function` | ||
""" | ||
query = ":r{0}w".format(self._chan) | ||
response = self._mhs.query(query).replace(query, "") | ||
return self._mhs.Function(int(response)) | ||
|
||
@function.setter | ||
def function(self, new_val): | ||
query = ":s{0}w{1}".format(self._chan, | ||
self._mhs.Function(new_val).value) | ||
self._mhs.sendcmd(query) | ||
|
||
class Function(Enum): | ||
""" | ||
Enum containing valid wave modes for | ||
""" | ||
sine = 0 | ||
square = 1 | ||
triangular = 2 | ||
sawtooth_up = 3 | ||
sawtooth_down = 4 | ||
|
||
@property | ||
def channel(self): | ||
""" | ||
Gets a specific channel object. The desired channel is specified like | ||
one would access a list. | ||
For instance, this would print the counts of the first channel:: | ||
>>> mhs = ik.minghe.MHS5200.open_serial(vid=1027, pid=24577, | ||
baud=19200, timeout=1) | ||
>>> print(mhs.channel[0].frequency) | ||
:rtype: `list`[`MHS5200.Channel`] | ||
""" | ||
return ProxyList(self, MHS5200.Channel, range(self._channel_count)) | ||
|
||
@property | ||
def serial_number(self): | ||
""" | ||
Get the serial number, as an int | ||
:rtype: int | ||
""" | ||
query = ":r0c" | ||
response = self.query(query) | ||
response = response.replace(query, "").replace("\r", "") | ||
return response | ||
|
||
def _get_amplitude_(self): | ||
raise NotImplementedError() | ||
|
||
def _set_amplitude_(self, magnitude, units): | ||
raise NotImplementedError() |
Oops, something went wrong.