diff --git a/gex/__init__.py b/gex/__init__.py index 71c7c89..72673b4 100644 --- a/gex/__init__.py +++ b/gex/__init__.py @@ -21,6 +21,7 @@ from gex.units.SIPO import SIPO from gex.units.FCAP import FCAP from gex.units.TOUCH import TOUCH from gex.units.PWMDim import PWMDim +from gex.units.DAC import DAC # General, low level diff --git a/gex/units/DAC.py b/gex/units/DAC.py new file mode 100644 index 0000000..75a6ffb --- /dev/null +++ b/gex/units/DAC.py @@ -0,0 +1,228 @@ +import gex + +CMD_WAVE_DC = 0 +CMD_WAVE_SINE = 1 +CMD_WAVE_TRIANGLE = 2 +CMD_WAVE_SAWTOOTH_UP = 3 +CMD_WAVE_SAWTOOTH_DOWN = 4 +CMD_WAVE_RECTANGLE = 5 + +CMD_SYNC = 10 + +CMD_SET_FREQUENCY = 20 +CMD_SET_PHASE = 21 +CMD_SET_DITHER = 22 + +LUT_LEN = 8192 + +class DAC(gex.Unit): + """ + Analog output (2 channels) + """ + + def _type(self): + return 'DAC' + + + def dc(self, channel, level, confirm=True): + """ + Set DC levels, 0-4095. None to leave the level unchanged + + channel: 1,2 (3 = both) + level: 0-4095 + """ + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + pb = gex.PayloadBuilder() + pb.u8(channel) + pb.u16(level) + + if channel==3: + pb.u16(level) + + self._send(CMD_WAVE_DC, pld=pb.close(), confirm=confirm) + + + def dc_dual(self, ch1, ch2, confirm=True): + """ + Set DC levels, 0-4095 + """ + + pb = gex.PayloadBuilder() + pb.u8(0b11) + pb.u16(ch1) + pb.u16(ch2) + self._send(CMD_WAVE_DC, pld=pb.close(), confirm=confirm) + + + def rectangle(self, channel, duty=None, high=None, low=None, confirm=True): + """ Enter rectangle gen mode (duty 0..1000) """ + + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + pb = gex.PayloadBuilder() + pb.u8(channel) # 0b01 or 0b10 + + for i in range(0,1 if channel != 3 else 2): # repeat if dual + pb.u16(round(duty * LUT_LEN) if duty is not None # todo ?? + else 0xFFFF) + + pb.u16(high if high is not None else 0xFFFF) + pb.u16(low if low is not None else 0xFFFF) + + self._send(CMD_WAVE_RECTANGLE, pld=pb.close(), confirm=confirm) + + + def rectangle_dual(self, + duty1=None, duty2=None, + high1=None, high2=None, + low1=None, low2=None, + confirm=True): + """ Set rectangle dual (both at once in sync) """ + + pb = gex.PayloadBuilder() + pb.u8(0b11) # 0b01 or 0b10 + + pb.u16(round(duty1*LUT_LEN)) + pb.u16(high1 if high1 is not None else 0xFFFF) + pb.u16(low1 if low1 is not None else 0xFFFF) + + pb.u16(round(duty2*LUT_LEN)) + pb.u16(high2 if high2 is not None else 0xFFFF) + pb.u16(low2 if low2 is not None else 0xFFFF) + + self._send(CMD_WAVE_RECTANGLE, pld=pb.close(), confirm=confirm) + + + def sync(self, confirm=True): + self._send(CMD_SYNC, confirm=confirm) + + + def waveform(self, channel, waveform, confirm=True): + """ + Set a waveform. For DC or rectangle, + use the dedicated functions with extra parameters + + channel: 1,2 (3 = both) + waveform: + - None - leave unchanged + - SINE + - TRIANGLE + - SAW_UP + - SAW_DOWN + """ + + lookup = {'SINE': CMD_WAVE_SINE, + 'SIN': CMD_WAVE_SINE, + + 'TRI': CMD_WAVE_TRIANGLE, + 'TRIANGLE': CMD_WAVE_TRIANGLE, + + 'SAW': CMD_WAVE_SAWTOOTH_UP, + 'RAMP': CMD_WAVE_SAWTOOTH_UP, + 'RAMP_UP': CMD_WAVE_SAWTOOTH_UP, + 'SAW_UP': CMD_WAVE_SAWTOOTH_UP, + + 'SAW_DOWN': CMD_WAVE_SAWTOOTH_DOWN, + 'RAMP_DOWN': CMD_WAVE_SAWTOOTH_DOWN, + } + + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + pb = gex.PayloadBuilder() + pb.u8(channel) # 0b01 or 0b10 + self._send(lookup[waveform], pld=pb.close(), confirm=confirm) + + + def set_frequency(self, channel, freq, confirm=True): + """ + Set frequency using float in Hz + """ + + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + pb = gex.PayloadBuilder() + pb.u8(channel) + pb.float(freq) + + if channel == 3: + pb.float(freq) + + self._send(CMD_SET_FREQUENCY, pld=pb.close(), confirm=confirm) + + + def set_frequency_dual(self, freq1, freq2, confirm=True): + """ + Set frequency of both channels using float in Hz + """ + + pb = gex.PayloadBuilder() + pb.u8(0b11) + pb.float(freq1) + pb.float(freq2) + + self._send(CMD_SET_FREQUENCY, pld=pb.close(), confirm=confirm) + + + def set_phase(self, channel, phase360, confirm=True): + """ + Set channel phase relative to it's "base phase". + If both channels use the same frequency, this could be used for drawing XY figures. + """ + + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + pb = gex.PayloadBuilder() + pb.u8(channel) + pb.u16(round((phase360/360) * LUT_LEN)) + + if channel == 3: + pb.u16(round((phase360/360) * LUT_LEN)) + + self._send(CMD_SET_PHASE, pld=pb.close(), confirm=confirm) + + + def set_phase_dual(self, phase1, phase2, confirm=True): + """ + Set phase for both channels at once + """ + + pb = gex.PayloadBuilder() + pb.u8(0b11) + pb.u16((phase1/360) * LUT_LEN) + pb.u16((phase2/360) * LUT_LEN) + + self._send(CMD_SET_PHASE, pld=pb.close(), confirm=confirm) + + + def set_dither(self, channel, type=None, bits=None, confirm=True): + """ + Set dithering (superimposed noise waveform) + type: NONE, TRIANGLE, WHITE + bits: 1-12 + """ + + if channel != 1 and channel != 2 and channel != 3: + raise Exception("Bad channel arg: %s" % channel) + + lookup = {'NONE': 0, + + 'WHITE': 1, + 'NOISE': 1, + + 'TRIANGLE': 2, + 'TRI': 2} + + pb = gex.PayloadBuilder() + pb.u8(channel) + + for i in range(0,1 if channel != 3 else 2): # repeat if dual + pb.u8(lookup[type] if type is not None else 0xFF) + pb.u8(bits if bits is not None else 0xFF) + + self._send(CMD_SET_DITHER, pld=pb.close(), confirm=confirm) diff --git a/test_dac.py b/test_dac.py new file mode 100644 index 0000000..7bff9cd --- /dev/null +++ b/test_dac.py @@ -0,0 +1,46 @@ +#!/bin/env python3 +import time +import math + +import gex + +from scipy.io import wavfile + +with gex.Client(gex.TrxRawUSB()) as client: + dac = gex.DAC(client, 'dac') + + # dac.set_dither(1, 'TRI', 8) + # # dac.set_dither(3, 'NONE', 8) + # # # + # # # dac.set_frequency(2, 1) + # # # dac.set_frequency(1, 10.01) + # dac.set_waveform(1, 'SIN') + # # dac.set_waveform(2, 'RAMP') + # + # dac.rectangle(2, 0.5, 4095, 0) + # + # dac.set_frequency(1, 100) + # dac.set_frequency(2, 50) + # # + # dac.sync() + + for i in range(0, 1000): + dac.set_frequency(1, i) + time.sleep(0.001) + + + + # dac.waveform(1, 'SIN') + # # dac.set_frequency(1, 1000) + # # dac.dc(1,1000) + # dac.dc(2,1000) + + # + # for i in range(0,360*5, 3): + # dac.dc_dual(round(2047+math.cos(((i*3.01)/180)*math.pi)*1900), + # round(2047+math.sin(((i*2.01)/180)*math.pi)*1900), + # confirm=False) + # time.sleep(0.01) + # + # dac.dc_dual(2047,2047) +