Close

Software

A project log for 22-bit Capacitance to Digital Converter

A new capacitance displacement sensor interface for my seismometer with improved performance and low cost.

bud-bennettBud Bennett 07/18/2019 at 20:360 Comments

These code snippets are what I'm using to interface with the C2D. This first one is the class for the C2D converter. When instantiated it performs a self calibration for gain and offset. Otherwise, the user must customize it for use.

#!/usr/bin/env python3
#
#  Connections are:
#     CLK => SCLK  
#     DOUT =>  MISO
#     DIN => MOSI
#     CS => CE0

import time
import sys
import spidev
import math

class C2D:
    # commands
    SelfCalibration = 0x90
    SystemOffsetCal = 0xA0
    SystemGainCal = 0xB0
    Convert1p0 = 0x80
    Convert2p5 = 0x81
    Convert5p0 = 0x82
    Convert10 = 0x83
    # register addresses
    regSTAT = 0xC0
    regCTRL1 = 0xC2
    regCTRL2 = 0xC4
    regCTRL3 = 0xC6
    regDATA = 0xC8
    regSOC = 0xCA
    regSGC = 0xCC
    regSCOC = 0xCE
    regSCGC = 0xD0
    
    def __init__(self, debug=False):
        self.spi = spidev.SpiDev()
        self.spi.open(0,0)
        self.spi.max_speed_hz=(1000000)
        # Enable Self Calibration
        self.writeReg(self.regCTRL3,[0x18])
        time.sleep(0.1)
        # Performing System Self Calibration.
        self.command([self.SelfCalibration])
        time.sleep(0.3)

    
    def command(self, register):
        self.spi.xfer(register)
        return
    
    def writeReg(self, register, dataList):
        registerData = [register]
        for data in dataList:
            registerData.append(data)
        self.spi.xfer2(registerData)
                 
    def readReg(self, register, dataList):
        registerData = [register+1]
        for data in dataList:
            registerData.append(0)
        r = self.spi.xfer2(registerData)
        return r[-len(dataList):] # toss out the first byte

    def twosComplement(self,data):
        result = data[0] << 16 | data[1] << 8 | data[2]
        if result > (1 << 23) - 1:
            result = result - (1 << 24)
        return result
    
    def convert2volts(self, data):
        v = data/(2**23-1) * 3.6
        return v

    def readADC(self):
        r = self.readReg(self.regDATA,[0,0,0])
        return self.twosComplement(r)

    def readSelfCalOffset(self):
        r = self.readReg(self.regSCOC,[0,0,0])
        return self.twosComplement(r)

    def readSelfCalGain(self):
        r = self.readReg(self.regSCGC,[0,0,0])
        return self.twosComplement(r)/2**23

    def readSystemOffset(self):
        r = self.readReg(self.regSOC,[0,0,0])
        return int(self.twosComplement(r)/4)

    def readSystemGain(self):
        r = self.readReg(self.regSGC,[0,0,0])
        return self.twosComplement(r)
    
    def meanstdv(self, x):
        """
        Calculate mean and standard deviation of data x[]:
        mean = {\sum_i x_i \over n}
        std = math.sqrt(\sum_i (x_i - mean)^2 \over n-1)
        """
        n, mean, std = len(x), 0, 0
        for a in x:
            mean = mean + a
        mean = mean / float(n)
        for a in x:
            std = std + (a - mean)**2
        if(n > 1):
            std = math.sqrt(std / float(n-1))
        else:
            std = 0.0
        return mean, std

        
if __name__ == '__main__':
    
    # instantiate C2D
    cap2dig = C2D(debug=True)
    print("-1 = {0}".format(cap2dig.twosComplement([0xff,0xff,0xff])))
    print("1 = {0}".format(cap2dig.twosComplement([0x00,0x00,0x01])))
    # set CTRL3 register
    cap2dig.writeReg(cap2dig.regCTRL3,[0x18])
    CTRL3 = cap2dig.readReg(cap2dig.regCTRL3,[0])
    print("CTRL3 = {}".format(hex(CTRL3[0])))
    #config register: SCYCLE = 1, SIGBUF = 0
    cap2dig.writeReg(cap2dig.regCTRL1,[0x02])
    time.sleep(1)
    CTRL1 = cap2dig.readReg(cap2dig.regCTRL1, [0])
    print("CTRL1 = {}".format(hex(CTRL1[0])))
    print("Self Cal Offset = {0}".format(int(cap2dig.readSelfCalOffset())))
    print("Self Cal Gain = {0}".format(cap2dig.readSelfCalGain()))
    cap2dig.command([cap2dig.SystemOffsetCal])
    time.sleep(0.5)
    print("System Offset = {0}".format(cap2dig.readSystemOffset()))
    print("System Gain = {0}".format(cap2dig.readSystemGain()))
    
    result_array = []
    oldSTAT = 0x00
    n = 0
    sd_avg2 = float(0)
    try:
        while True:
            # start conversion
            cap2dig.command([cap2dig.Convert10])
            # wait for result
            time.sleep(0.11)
            STAT = cap2dig.readReg(cap2dig.regSTAT,[0])
            if (STAT != oldSTAT):
                print("STAT = {}".format(hex(STAT[0])))
                oldSTAT = STAT
            val = cap2dig.readADC()
            print ("ADC Result: {0}".format(int(val)))
            result_array.append(int(val))
            if (len(result_array) == 10):
                n += 1
                mean,sd = cap2dig.meanstdv(result_array)
                result_array = []
                print("\n\tmean: {0} Counts".format(mean))
                print("\tstd dev: {0:.4f} Counts".format(sd))
                dnr = 20 * math.log(0.8 * 2**24/sd,10)
                nfbits = math.log(0.8 * 2**24/(6 * sd),2)
                print("\tDynamic Range = {0:.1f}db, ({1:.2f} bits)".format(dnr, nfbits))
                sd_avg2 += sd**2
                sd_avg = math.sqrt(sd_avg2/n)
                print("\tAvg Std Dev = {0:.2f} Counts".format(sd_avg))
                avg_dnr = 20*math.log(0.8*2**24/sd_avg,10)
                avg_nfbits = math.log(0.8*2**24/(6 * sd_avg),2)
                print("\tAvg Dynamic Range = {0:.1f}db, ({1:.2f} bits)\n".format(avg_dnr, avg_nfbits))
                time.sleep(3)
                
            
    except KeyboardInterrupt:
        cap2dig.spi.close() 
        sys.exit(0)

 This while loop is used to get the C2D data and provide it to the ringserver daemon. The C2D is instantiated as cap2dig(). Sample period for the loop is set to 0.5 seconds (2 sps) when the C2D is set to 2.5sps.

def getData():
    '''
    TBD
    '''
    global shared, seedArray, resultArray
    sample_time = 0.5
    # throw away first conversion result
    ts = datetime.utcnow()  # a timestamp for the seed file
    timeNow = time.time() * 1000  # this creates a unix timestamp with millisecond resolution
    cap2dig.command([cap2dig.Convert2p5])
    time.sleep(sample_time)
    while True:
        # start next conversion
        timeStart = time.time()
        cap2dig.command([cap2dig.Convert2p5])
        CapCount = cap2dig.readADC()
        ts_next = datetime.utcnow()  # a timestamp for the seed file
        timeNow_next = time.time() * 1000  # this creates a unix timestamp with millisecond resolution
        CapCount = cap2dig.readADC()
        resultArray.append([timeNow, CapCount])
        seedArray.append([ts.isoformat(),int(CapCount)]) # raw data to seedlink server
        # limit array length to 1 hour
        if (len(resultArray) > 7200):
            # must use pop method for manager.list object.
            resultArray.pop(0)

        packetSize = 512  # 512 is standard packet size for mseed.
        if(len(seedArray) == packetSize):
            # write data stream to ascii file
            #logger.info("Writing seed file.")
            asciiFile = open('/home/pi/programs/Seismo/slist.ascii', 'w')
            asciiFile.write("TIMESERIES EI_AEGI__BHZ, {0} samples, {2:.4f} sps, {1}, SLIST, INTEGER, Counts\n".format(packetSize, seedArray[0][0], 1/sample_time))  # write header
            n = 0
            for line in seedArray:
                n += 1
                if (n == 1):
                    text = repr(line[1]).rjust(10)
                else:
                    text = text + repr(line[1]).rjust(12)
                if (n == 6):
                    text = text + "  \n"
                    asciiFile.write(text)
                    n = 0
            text = text + "  \n"  # finish off any partial lines
            asciiFile.write(text)  # write the last line
            asciiFile.close()
            mseedFileName = seedArray[0][0].replace(":", "_")
            command = 'ascii2mseed -r {0} -o /home/pi/ringserver/mseed/EI_AEGI__BHZ_{1}.mseed /home/pi/programs/Seismo/slist.ascii > /dev/null 2>&1 &'.format(packetSize,mseedFileName)
            seedArray = []
            os.system(command)
        ts = ts_next
        timeNow = timeNow_next
        if (sample_time > (sample_time - time.time() + timeStart) > 0):
            time.sleep(sample_time - time.time() + timeStart)
        else:
            logger.error("Sample timing error.")
            time.sleep(sample_time)

The C2D is instructed to start a new conversion before the results of the previous conversion are captured. There should be plenty of time to obtain the ADC result before the register is overwritten with the new data. After the conversion is started all of the other housekeeping can be accomplished in the intervening 0.5 seconds. The loop is trying to keep the sample period as close to 0.5 seconds as possible. Every 512 samples -- about 4 minutes -- a miniseed file is provided to the ringserver daemon.

Discussions