diff --git a/datasheets/MAX11270.pdf b/datasheets/MAX11270.pdf new file mode 100644 index 0000000..e25026b Binary files /dev/null and b/datasheets/MAX11270.pdf differ diff --git a/datasheets/MCP4822.pdf b/datasheets/MCP4822.pdf new file mode 100644 index 0000000..5b44221 Binary files /dev/null and b/datasheets/MCP4822.pdf differ diff --git a/src/MAX11270.py b/src/MAX11270.py new file mode 100644 index 0000000..a118fea --- /dev/null +++ b/src/MAX11270.py @@ -0,0 +1,421 @@ +''' +Created on Tue Jul 05 2022 + +Driver class for MAX11270: 24-Bit, 24-Bit, 10mW, 130dB SNR, 64ksps Delta-Sigma ADC with Integrated PGA + +The MAX11270 interface operates in two modes, conversion mode or register access mode, + +MSB First +Conversion Mode (MODE = 0) +BIT B7 B6 B5 B4 B3 B2 B1 B0 +BIT NAME START = 1 MODE = 0 CAL IMPD RATE3 RATE2 RATE1 RATE0 + +CAL = 1 to perform a calibration, CAL = 0 for all other operates +IMPD = 1 to power down the MAX11270 and enter sleep mode +data rate bits RATE[3:0] determine the conversion speed + +Register Access Mode (MODE = 1) +BIT B7 B6 B5 B4 B3 B2 B1 B0 +BIT NAME START =1 MODE = 1 RS4 RS3 RS2 RS1 RS0 R/W + +bits RS[4:0] determine the register +R/W = 0 to write to the selected register and R/W = 1 to read from the selected register + +Status Register (Read Only) +BIT B15 B14 B13 B12 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 + +00 0 RDY Ready bit. RDY = 1 when a new conversion result is available. A read of the DATA register resets RDY = 0. The function of the RDY bit is redundant and is duplicated by RDYB pin. +01 0 MSTAT Ready bit. RDY = 1 when a new conversion result is available. A read of the DATA register resets RDY = 0. The function of the RDY bit is redundant and is duplicated by RDYB pin. +02 0 DOR Data overrange bit. DOR = 1 indicates that the conversion result has exceeded the maximum or minimum value and that the result has been clipped or limited to the maximum value. DOR = 0 when the conversion result is within the full-scale range. +03 0 SYSGOR System gain overrange bit. SYSGOR = 1 indicates that a system gain calibration was overranged. The SGC calibration coefficient maximum value is 1.9999999. +04 1 RATE0 Data rate bits +05 0 RATE1 +06 0 RATE2 +07 1 RATE3 +08 + + + +@author: elena +''' + +import pin as GPIO +GPIO.config('./config.json') +import time + +'''! @name Registers +@{ +''' +MAX11270_REG_STAT = 0x0 +MAX11270_REG_CTRL1 = 0x1 +MAX11270_REG_CTRL2 = 0x2 +MAX11270_REG_CTRL3 = 0x3 +MAX11270_REG_CTRL4 = 0x4 +MAX11270_REG_CTRL5 = 0x5 +MAX11270_REG_DATA = 0x6 +MAX11270_REG_SOC_SPI = 0x7 +MAX11270_REG_SGC_SPI = 0x8 +MAX11270_REG_SCOC_SPI = 0x9 +MAX11270_REG_SCGC_SPI = 0xA +MAX11270_REG_RAM = 0xC +MAX11270_REG_SYNC_SPI = 0xD +MAX11270_REG_SOC_ADC = 0x15 +MAX11270_REG_SGC_ADC = 0x16 +MAX11270_REG_SCOC_ADC = 0x17 +MAX11270_REG_SCGC_ADC = 0x18 + +'''! @name Conversion Rates Commands +@{ ''' +MAX11270_SPS_50 = 0b1000 +MAX11270_SPS_62 = 0b0001 +MAX11270_SPS_100 = 0b0010 +MAX11270_SPS_125 = 0b0011 +MAX11270_SPS_200 = 0b0100 +MAX11270_SPS_250 = 0b0101 +MAX11270_SPS_400 = 0b0110 +MAX11270_SPS_500 = 0b0111 +MAX11270_SPS_800 = 0b1000 +MAX11270_SPS_1000 = 0b1001 +MAX11270_SPS_1600 = 0b1010 +MAX11270_SPS_2000 = 0b1011 +MAX11270_SPS_3200 = 0b1100 +MAX11270_SPS_4000 = 0b1101 +MAX11270_SPS_6400 = 0b1110 +MAX11270_SPS_12800 = 0b1111 + +class MAX11270: + ''' + Constructor of the class, setup the GPIO pin for SPI-communication + ''' + def __init__(self, CLK_Pin, MOSI_Pin, MISO_Pin, CS_Pin, Sync_Pin, RSTB_Pin, vref=None): + self.clkPin = CLK_Pin + self.mosiPin = MOSI_Pin + self.misoPin = MISO_Pin + self.csPin = CS_Pin + self.syncPin = Sync_Pin + self.rstbPin = RSTB_Pin + + self.softSPI = True + + if self.clkPin == 11 and self.mosiPin == 10 and self.misoPin == 9: + self.spidev = 0 + if self.csPin == 8: + self.spichan = 0 + self.softSPI = False + elif self.csPin == 7: + self.spichan == 1 + self.softSPI = False + + if self.clkPin == 21 and self.mosiPin == 20 and self.misoPin == 19: + self.spidev = 1 + if self.csPin == 18: + self.spichan = 0 + self.softSPI = False + elif self.csPin == 17: + self.spichan == 1 + self.softSPI = False + elif self.csPin == 16: + self.spichan == 2 + self.softSPI = False + + if vref is None: + self.vref = 3 + else: + self.vref = vref + + GPIO.setup(self.clkPin, GPIO.OUT) + GPIO.setup(self.mosiPin, GPIO.OUT) + GPIO.setup(self.csPin, GPIO.OUT) + GPIO.setup(self.syncPin, GPIO.OUT) + GPIO.setup(self.rstbPin, GPIO.OUT) + + GPIO.setup(self.misoPin, GPIO.IN) + + GPIO.output(self.syncPin, GPIO.LOW) + GPIO.output(self.rstbPin, GPIO.LOW) + + GPIO.output(self.csPin, GPIO.HIGH) + GPIO.output(self.clkPin, GPIO.LOW) + + self.ready = 0 + self.mstat = 0 + self.dataOverrange = 0 + self.systemGainOverrange = 0 + self.rate = 0 + self.analogOverrange = 0 + self.dataReadError = 0 + self.powerState = 0 + self.error = 0 + self.inReset = 0 + self.format = 0 + self.dataBytes = 3 + + def _toggleClock(self): + # Toggle clock pin + GPIO.output(self.clkPin, GPIO.HIGH) + #time.sleep(0.0001) + GPIO.output(self.clkPin, GPIO.LOW) + #time.sleep(0.0001) + + ''' + Operates the ADC in conversion mode, eather perform calibration or trigger a converison + ''' + def _conversionMode(self,CAL = 0, IMPD=0, rate=0b1001): + adc_command = 0b10000000 | CAL<<5 | IMPD<<4 | rate + #print("{:08b}".format(adc_command)) + # Start com + GPIO.output(self.csPin, GPIO.LOW) + for bit in range(8): + #Set outgoing bit + if adc_command & 0b10000000: + GPIO.output(self.mosiPin, GPIO.HIGH) + else: + GPIO.output(self.mosiPin, GPIO.LOW) + + #Shift command by 1 bit for next tick + adc_command <<= 1 + + self._toggleClock() + + ''' + Operates the ADC in register access mode, used for configuration and readout + ''' + def _registerAccessMode(self,register=0x0,rw) + adc_command = 0b11000000 | register<<1 | rw + # Start com + GPIO.output(self.csPin, GPIO.LOW) + for bit in range(8): + #Set outgoing bit + if adc_command & 0b10000000: + GPIO.output(self.mosiPin, GPIO.HIGH) + else: + GPIO.output(self.mosiPin, GPIO.LOW) + + #Shift command by 1 bit for next tick + adc_command <<= 1 + + self._toggleClock() + + def _spi_read(self, numbytes): + retVal = 0 + + # Start readout + GPIO.output(self.csPin, GPIO.LOW) + #time.sleep(0.0001) + #Commucation consists of 32 transfered bits + for bit in range(numbytes*8): + # Read 1 data bit + if GPIO.input(self.misoPin): + retVal |= 0x1 + + # Advance input to next bit + retVal <<= 1 + + self._toggleClock() + + # Set chip select high to end the read process + GPIO.output(self.csPin, GPIO.HIGH) + #print(bin(retVal)) + return retVal + + def _spi_write(self,adc_command) + + # Start com + GPIO.output(self.csPin, GPIO.LOW) + for bit in range(8): + #Set outgoing bit + if adc_command & 0b10000000: + GPIO.output(self.mosiPin, GPIO.HIGH) + else: + GPIO.output(self.mosiPin, GPIO.LOW) + + #Shift command by 1 bit for next tick + adc_command <<= 1 + + self._toggleClock() + + def _readAndChangeRegisterValue(self,register,value,firstBit,bits=1): + #Select register + self._registerAccessMode(register,1) + #Read stored values + value = self._spi_read(1) + self._registerAccessMode(register,0) + for bit in range(0,bits): + if value >> bit & 1 == 0: + value = value & ~(1<> 0 & 1 + self.mstat = value >> 1 & 1 + self.dataOverrange = value >> 2 & 1 + self.systemGainOverrange = value >> 3 & 1 + self.rate = value >> 4 & 0b1111 + self.analogOverrange = value >> 8 & 1 + self.dataReadError = value >> 9 & 1 + self.powerState = value >> 10 & 0b11 + self.error = value >> 14 & 1 + self.inReset = value >> 15 & 1 + + return value + + +#%% Control register 1 functions +#The CTRL1 register is an 8-bit read/write register. The byte written to the CTRL1 register determines the clock #setting, synchronization mode, power-down or reset state, input range is unipolar or bipolar, data output is two’s +#complement or offset binary, and conversion mode is in single cycle or continuous. + def setConversion(self,mode): + if mode == 'single': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,0) + elif mode == 'continous': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,0) + else: + raise Exception("No known conversion mode selected") + + def setCycleControl(self,mode): + if mode == 'single-cycle': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,1) + elif mode == 'continous': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,1) + else: + raise Exception("No known cycle mode selected") + + def setDataFormat(self,mode): + if mode == 'twosComplement': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,2) + elif mode == 'offsetBinary': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,2) + else: + raise Exception("No known data format selected") + + def setInputRange(self,mode): + if mode == 'unipolar': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,3) + elif mode == 'bipolar': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,3) + else: + raise Exception("No known input mode selected") + + def setPowerState(self,mode): + if mode == 'normal': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0b00,4,2) + elif mode == 'sleep': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0b01,4,2) + elif mode == 'standy': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0b10,4,2) + elif mode == 'reset': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0b11,4,2) + else: + raise Exception("No known power state selected") + + def setSyncMode(self,mode): + if mode == 'continuousSync': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,6) + elif mode == 'pulseSync': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,6) + else: + raise Exception("No known input mode selected") + + def setClock(self,mode): + if mode == 'external': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,1,7) + elif mode == 'internal': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL1,0,7) + else: + raise Exception("No known input mode selected") + +#%% Control register 2 functions +#The CTRL2 register is an 8-bit read/write register. The byte written to the CTRL2 register determines the +#digital and analog gain settings, and whether the buffers or PGA is enabled. + def setPGAGain(self,gain): + gainMap = {'x1':0b000,'x2':0b001,'x4':0b010,'x8':0b011,'x16':0b100,'x32':0b101,'x64':0b110,'x128':0b111} + if gain in gainMap: + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,gainMap[gain],0,3) + else: + raise Exception("No known gain selected") + + def setPGAenable(self,bit): + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,bit,3) + + def setPGApower(self,mode): + if mode == 'low': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,1,4) + elif mode == 'standard': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,0,4) + else: + raise Exception("No known PGA power mode selected") + + def setBufferEnable(self,bit): + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,bit,5) + + def setModulatorGain(self,gain): + gainMap = {'x1':0b00,'x2':0b01,'x4':0b10,'x8':0b11} + if gain in gainMap: + self._readAndChangeRegisterValue(MAX11270_REG_CTRL2,gainMap[gain],6,2) + else: + raise Exception("No known gain selected") + +#%% Control register 3 functions +#The CTRL3 register is an 8-bit read/write register. The byte written to the CTRL3 register determines the operation +#of the modulator output. + def setDataMode(self,mode): + if mode == '32bit': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL3,1,3) + self.dataBytes = 4 + elif mode == '24bit': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL3,0,3) + self.dataBytes = 3 + else: + raise Exception("No known input mode selected") + + def setModulatorOutputEnable(self,bit): + self._readAndChangeRegisterValue(MAX11270_REG_CTRL3,bit,4) + + def setModulatorSyncPulseEnable(self,bit): + self._readAndChangeRegisterValue(MAX11270_REG_CTRL3,bit,5) + +#%% Control register 5 functions +#The CTRL5 register is an 8-bit read/write register. The byte written to the CTRL5 register determines the +#MAX11270’s reset, data overflow, and calibration modes. + def performCalibration(self,mode): + if mode == 'selfCalibration': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL5,0b00,6,2) + elif mode == 'offsetCalibration': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL5,0b01,6,2) + elif mode == 'fullScaleCalibration': + self._readAndChangeRegisterValue(MAX11270_REG_CTRL5,0b10,6,2) + else: + raise Exception("No known input mode selected") + +#%% Data register functions + def readDataRegister(self): + self._registerAccessMode(MAX11270_REG_DATA,1) + return self._spi_read(self.dataBytes) + +#This function is not complete, as I do not understand the data sheet... + def value_to_voltage(self, adcValue): + if adcValue == 0: + voltage = -self.vref + elif adcValue == 1: + voltage = 0 + else: + voltage = self.vref / 2**(self.dataBytes*8) *adcValue + + return voltage + + ''' + Read ADC value in differential mode + ''' + def read_differential(self): + #Triggers conversion + self._conversionMode(self) + time.sleep(0.001) + adc_code = self.readDataRegister(self) + + return self.value_to_voltage(adc_code) + + diff --git a/src/MCP3204.py b/src/MCP3204.py new file mode 100644 index 0000000..387d00d --- /dev/null +++ b/src/MCP3204.py @@ -0,0 +1,24 @@ +class MCP3204(object): + def __init__(self,spiDev,cs): + self.spi = SPI.SpiDev(spiDev, cs, max_speed_hz=1000000) + self.spi.set_mode(0) + self.spi.set_bit_order(SPI.MSBFIRST) + + def __del__(self): + self.spi.close() + + def read(self, ch): + if 4 <= ch <= 0: + raise Exception('MCP3204 channel must be 0-4: ' + str(ch)) + + cmd = 128 # 1000 0000 + cmd += 64 # 1100 0000 + cmd += ((ch & 0x07) << 3) + ret = self.spi.transfer([cmd, 0x0, 0x0]) + + # get the 12b out of the return + val = (ret[0] & 0x01) << 11 # only B11 is here + val |= ret[1] << 3 # B10:B3 + val |= ret[2] >> 5 # MSB has B2:B0 ... need to move down to LSB + + return (val & 0x0FFF) # ensure we are only sending 12b diff --git a/src/MCP4822.py b/src/MCP4822.py new file mode 100644 index 0000000..307ad4e --- /dev/null +++ b/src/MCP4822.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Jan 10 15:56:30 2018 + +Driver class for MCP4822: 12-Bit, 2-Channel DAC. + +SPI DATA FORMAT (MSB First): +Byte #1 Byte #2 +Data In : !A/B BUF !GA !SHDN D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 + +@author: jan +""" + +import pin as GPIO +GPIO.config('./config.json') +import time + +class MCP4822: + ''' + Constructor of the class, setup the GPIO pin for SPI-communication + ''' + def __init__(self, CLK_Pin, MOSI_Pin, CS_Pin, vref=None): + self.clkPin = CLK_Pin + self.mosiPin = MOSI_Pin + self.csPin = CS_Pin + + if vref is None: + self.vref = 2.048 + else: + self.vref = vref + + GPIO.setup(self.clkPin, GPIO.OUT) + GPIO.setup(self.mosiPin, GPIO.OUT) + GPIO.setup(self.csPin, GPIO.OUT) + + GPIO.output(self.csPin, GPIO.HIGH) + GPIO.output(self.clkPin, GPIO.LOW) + + + def U2dac(self,U,DAC): + #U: voltage (V) [0V-4.096V] + #DAC: DACA -> 0 + # DACB -> 1 + #calculate binary data + if U > 2*self.vref: + print('maximum output voltage overshot') + SPIdata = 666 + elif U > self.vref: + U = U/2 + #buffered, gain=2x, enabled, data + SPIdata = DAC<<15 | 1<<14 | 1<<12 | int(U/self.vref * ((1<<12)-1)) + elif U >= 0: + #buffered, gain=1x, enabled, data + SPIdata = DAC<<15 | 1<<14 | 1<<13 | 1<<12 | int(U/self.vref * ((1<<12)-1)) + else: + print('minimum output voltage undershot') + SPIdata = -666 + return SPIdata + + + def write(self,voltage,channel): + #Convert voltage to spi command + command = self.U2dac(voltage,channel) + + # Start communication + GPIO.output(self.csPin, GPIO.LOW) + + #Commucation consists of 16 transfered bits + for bit in range(16): + #Set outgoing bit + if command & 0x8000: + GPIO.output(self.mosiPin, GPIO.HIGH) + else: + GPIO.output(self.mosiPin, GPIO.LOW) + + #Shift command by 1 bit for next tick + command <<= 1 + + # Toggle clock pin + GPIO.output(self.clkPin, GPIO.HIGH) + #time.sleep(0.00001) + GPIO.output(self.clkPin, GPIO.LOW) + #time.sleep(0.00001) + + # Set chip select high to end the write process + GPIO.output(self.csPin, GPIO.HIGH)