Server Power supply DPS-750TB

and how to get PMBus access from f.ex. a Raspberry Pi

My favorite power-supply for almost anything is the Server PSU for Dell R510/R710/R910. It is a 750w 12v 60amp supply with 80-plus-gold standard,  you can get them for $10-20 on ebay delivered.

People use these for battery chargers and there is a lot of info on server-power-supplies at https://www.rcgroups.com

I use these for Battery charger and for my 3D-printers. A nice feature is the standby power with which you can have a Raspberry Pi powered all the time, and use the RPI to turn the PSU on or off.

To use this PSU you need to activate the right pins. For the impatient reader try connecting pin29 and pin30 (and pin31 for i2c) to ground.  Or you could buy one of the break-out boards the bitcoin miners use to power their rig.

I  can recommend a breakout board from parallelminer.com, the X11 works fine with the DPS-750TB, I payed $10 delivered. it has a nice little voltmeter,  so well worth its price.

Among hackers the DPS-1200FB seem to be the most popular choice and hence a lot of information is available for this unit.  I have made a blog-post about it here Server Power supply DPS-1200FB.  Even if this is a different brand power-supply a lot information will apply to this PSU as well.

Below is links to information relevant to DPS-750TB:

The muRata D1U86T-W-800-12-HB4C looks very similar to the DPS-750TB same pin-layout, same short/long pins and as you will see later on in the post, many of the PMBus commands documented for muRata PSU work.  muRata has links to 3 application notes in its product notes:

The DPS-750TB was first introduced in 2009. SMBus 1.0 was introduced in 2005, so it makes sense to look at this older specification:

PMBus™ 1.0 Power System Management Protocol Specification:

Further information on the PMBus can found below:

Pinout for the DPS-750TB

I could not get the i2c connection working according to the pinout at rcgoups. So I have done a little reverse engineering  trying to find the i2c bus.

I have included my measurement below. I have measured: resistance  on the PSU unconnected, and voltages without the X11 and with the X11 from Parrallelminer, in the power off and power on state.

pinresistance
to GND
AC
no X11
off
w X11
on
w X11
rcgroup
pin
notes
1-121k4 0v 12v12v
13-24GND GNDGNDGND
25n.c. 0v0v0.12vtach
26203 0v3v30.05vRemoteSense-changed
275M3 3v40.3v3v3vin_goodchanged
2819k 0.23v3v30v2C-sharechanged by PSU
2917k 3v33v30v-PS_Onchanged by x11
30 short17k3v3 3v30vPS_Killchanged by X11
31 short98k3v3 3v33v3Reset0=enable i2c
32 short5M53v3 3v33v3Alert
33 short14k3v3 3v30v5sdachanged by X11
34 short99 ov0v0v-PS_Present
35 short14k 3v33v33v3scl
36GND GNDGNDGNDGND
3799k 0.01v0.008v3v3POKchanged
38980k 0.27v0.2v0.2vPS_A0
39 12v12v12v12V_SB12v_SB
40203
to 12v
0v0v12vRemoteSense+changed by PSU
41-52GNDGND GNDGND
53-641k4
12v12v

The X11  from Parrallel-miner controls 3 pins via two transistors

  • pin 33 can be pulled down to pin36 GND, does not seem to be necessary for this PSU. and makes i2c use impossible, they probably did this so the X11 can support other PSU as well.
  • pin 29 and pin 30 is tied together and can be pulled down to GND

The connections to the i2c was actually as described on rcgroups, but pin31 which is labeled reset on the rcgroups pinout, need to be pulled down to ground, for the PSU to show up on i2c_address 0x58.  It is worth noting that the muRata PSU uses the same i2c address as the DPS-750TB, but pin31 is unused, and not connected in the acan-84 application note. Another difference is pin25: on the DPS-750TB it sends pulses from the fan so its speed can be monitored, as you will see in the program at the end of this posting, this information is readily available via the PMBus., so not really needed.  Pin25 is called SMART_REDUNDANT on the muRata PSU.

Status

I can turn the PSU on and off from a small python script on a raspberry pi pulling PS_On low, Software on/off via the PMBus as described in the standard does not seem to work. 

My program  monitors the AC_ok, DC_ok pins, and the fan-speed,  by reading the tacho pin and also by reading PMBus register for fan-speed and PMBus registers for Input/Output Voltages/Currents/Power/Temperature. Below is how I have connected the RPI to the PSU.

PSU pinNameRPi gpioDescription
25Tacho22fan rpm pulse
27AC_ok27low when AC is good
37DC_ok17low when DC is good
29PS_On40/1 = On/Off
30PS_KillGNDvia 100R
31Enable_i2cGNDvia 1k
33SDAi2c_sda9k pullup to 3v3
35SCLi2c_sda9k pullup to 3v3
on/off-button23momentary microswitch

Raspberry Pi psu.py

The python program to monitor the PSU, reads pins my on/off button and a few PMBus registers and prints out the values, so the Dell DPS-750tb follow some kind of PMbus standard

Here is the output of my python script:


PMBus busID=0x01 addr=0x58 DPS-750TB connected

MFR_ID:       0FN1VTA00PS
MFR_MODEL:    DPS750TB1
MFR_REVISION: A0
PMBus revision: 0x00

AC_OK DC_OK fan_rpm: 1050 1696,  61 43 Celcius,  AC = 120 Volt,  DC 12.32 Volt  1.2 Amp In: 26 Out: 14 Watts on=0
AC_OK DC_OK fan_rpm: 1800 1696,  61 43 Celcius,  AC = 121 Volt,  DC 12.32 Volt  1.3 Amp In: 26 Out: 15 Watts on=0
AC_OK DC_OK fan_rpm: 1620 1696,  61 43 Celcius,  AC = 120 Volt,  DC 12.32 Volt  1.3 Amp In: 26 Out: 16 Watts on=0
AC_OK DC_OK fan_rpm: 1770 1696,  60 43 Celcius,  AC = 121 Volt,  DC 12.88 Volt  1.3 Amp In: 26 Out: 15 Watts on=0
AC_OK DC_OK fan_rpm: 1800 1696,  60 43 Celcius,  AC = 119 Volt,  DC 12.32 Volt  1.3 Amp In: 26 Out: 14 Watts on=0
AC_OK DC_OK fan_rpm: 1680 1696,  60 43 Celcius,  AC = 120 Volt,  DC 12.32 Volt  1.3 Amp In: 26 Out: 14 Watts on=0

The script is written in python3 (My first python script) and follows below, it builds on the PMBus library by Michael-Equi github.com/Michael-Equi/PMBus

#!/usr/bin/python3
# (C) storepeter peter@lorenzen.us 2020 BeerWare a'la phk
# -*- coding: utf-8 -*-
import sys
import RPi.GPIO as GPIO
import time
from pmbus import PMBus

# Pin configuration
PS_ON = 4
#PS_KILL = GND
DC_OK = 17
AC_OK = 27
TACH = 22       # Fan's tachometer output pin
ON_OFF = 23

def printf(format, *args):
    sys.stdout.write(format % args)

psu = PMBus(0x58
print("MFR_ID:       " + psu.MFR_ID())
print("MFR_MODEL:    " + psu.MFR_MODEL())
print("MFR_REVISION: " + psu.MFR_REVISION())
print("PMBus revision: 0x%02x\n" % psu.PMBus_revision())

# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(TACH, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Pull up to 3.3V
GPIO.setup(DC_OK, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(AC_OK, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(ON_OFF, GPIO.IN, pull_up_down=GPIO.PUD_UP)

on = 0
def on_via_hw(state):
    global on
    printf("on_via_hw on=%d, state=%d\n", on, state)
    on = state
    if state > 0:
        print("hwON")
        GPIO.setup(PS_ON, GPIO.OUT)
        GPIO.output(PS_ON,0)
    else:
        print("hwOFF")
        GPIO.setup(PS_ON, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Pull up to 3.3V

# turn-on  states: 0=off <push>  1=off+button 2=on+button  <release> 3=on
# turn-off states: 3=on  <push> -1=on+button -2=off+buttib <release> 0=off
def check_on_switch():
    global on
    global ON_OFF

    if GPIO.input(ON_OFF)==0:   # button pushed
        if on == 0:         # not on - need to be pushed for a second
            on = 1
        elif on == 1:
            on_via_hw( 2)
        elif on == 3:       # was on
            on = -1
        elif on == -1:
            on_via_hw( -2)
    else:   # button release
        if on == 2:
            on = 3    # now it can be turned off again
        elif on < 0:
            on = 0    # now it can be turned on again

if len(sys.argv) > 1:
    if sys.argv[1] == "1":
        on_via_sw(3)
    else:
        on_via_sw(0)

count = 0
def falling(n):
    global count
    count = count + 1

GPIO.add_event_detect(TACH, GPIO.FALLING, falling)

prev = int(time.time())
try:
    while True:
        check_on_switch()
        t = int(time.time())
        if t != prev:  # every second
            prev = t
            rpm = 60 * count / 2       # 2pulses per revolution
            count = 0
            if GPIO.input(AC_OK): printf("AC_OK")
            if GPIO.input(DC_OK): printf(" DC_OK")
            printf(" fan_rpm: %4.f %d, ", rpm, psu.getFanSpeed())
            printf(" %d %d Celcius, ", psu.getTempurature(), psu.getTempurature2())
            printf(" AC = %d Volt, ", psu.getVoltageIn())
            printf(" DC %2.2f Volt ", psu.getVoltageOut())
            printf(" %2.1f Amp", psu.getCurrent())
            printf(" In: %d Out: %d Watts", psu.getPowerIn(), psu.getPowerOut())
            printf(" on=%d\n", on);

        time.sleep(0.1)

except KeyboardInterrupt: # trap a CTRL+C keyboard interrupt
    GPIO.cleanup() # resets all GPIO ports used by this function

The PMBus library by Michael-Equi github.com/Michael-Equi/PMBus seems to be written for a newer muRata power-supply, so I had to make a few changes to make it work with the DPS-750TB, which doesn’t seem to support Packet-Error-Checking, I have also added a number of routines to read more PSU register variables, the full library-file is below

# based on https://github.com/Michael-Equi/PMBus GPL-3
# changes by storepeter 2020 peter@lorenzen.us
from bitstruct import *
from smbus import SMBus
import sys
import math

class PMBus:

    #constants initialized on object creation
    VOUT_N = -9

    def __init__(self, addr, id=1, pecByte=False):
        self.busID = id
        self.address = addr
        self.pec = pecByte
        print("PMBus busID=0x%02x addr=0x%02x DPS-750TB connected\n" % (self.busID, self.address))

    #Decode/encode Linear data format => X=Y*2^N
    def delinear11(self, word):
        #print("\n" + format(word,'b'))
        u = unpack("s5s11", word.to_bytes(2, byteorder='big'))
        #print("%5s            = mantissa = %d" % (format(u[0] & 0x1f,'b'), u[0]))
        #print("     %11s = base     = %d" % (format(u[1],'b'), u[1]))
        result = u[1]*(2.0**(u[0]))
        #print("%4d * 2 ** %2d   = result   = %f" % (u[1], u[0], result))
        return result

    def delinear16(self, word):
        result = word*(2.0**self.VOUT_N)
        #print("%4d * 2 ** -9   = result   = %f" % (word, result))
        return result

    def readLinear(self, register):
        return self.delinear11(self._readWordPMBus(register))

    def _encodePMBus(self, message):
        YMAX = 1023.0
        #print(message)
        Nval = int(math.log(message/YMAX,2))
        #print("NVal: " + str(Nval))
        Yval = int(message*(2**-Nval))
        #print("YVal: " + str(Yval))
        message = ((Nval & 0b00011111)<<11) | Yval
        #print(bin(message))
        return message

    #wrapper functions for reading/writing a word/byte to an address with pec
    def _writeWordPMBus(self, cmd, word, pecByte=True):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        bus.write_word_data(self.address, cmd, word)
        bus.close()
    def _readWordPMBus(self, cmd, pecByte=True):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        data = bus.read_word_data(self.address, cmd)
        bus.close()
        return data

    def _writeBytePMBus(self, cmd, byte, pecByte=True):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        bus.write_byte_data(self.address, cmd, byte)
        bus.close()

    def _readBytePMBus(self, cmd, pecByte=True):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        data = bus.read_byte_data(self.address, cmd)
        bus.close()
        return data

    def readBlock(self, cmd, len):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        data = bus.read_i2c_block_data(self.address, cmd, len)
        bus.close()
        return data

    def readString(self, cmd):
        bus = SMBus(self.busID)
        if self.pec:
            bus.pec = self.pec
        len = bus.read_byte_data(self.address, cmd)
        data = bus.read_i2c_block_data(self.address, cmd, len)
        #print("data; %s" % str(data))
        data.pop(0)
        bus.close()
        return "".join(map(chr,(data)))

    ################################### Functions getting string info
    def PMBus_revision(self):
        return self._readBytePMBus(0x98)

    def MFR_ID(self):
        return self.readString(0x99)

    def MFR_MODEL(self):
        return self.readString(0x9a)

    def MFR_REVISION(self):
        return self.readString(0x9b)

    def MFR_LOCATION(self):
        return self.readString(0x9c)

    def MFR_DATE(self):
        return self.readString(0x9d)

    def MFR_SERIAL(self):
        return self.readString(0x9e)

    def getVoutMode(self):
        return self._readBytePMBus(0x20)

    def getVoutCommand(self):
        return self._readWordPMBus(0x21)

    def getVoutTrim(self):
        return self._readWordPMBus(0x22)

    def getVoutCal(self):
        return self._readWordPMBus(0x23)

    def getVoutMax(self):
        return self._readWordPMBus(0x24)

    def getVoutMarginHigh(self):
        return self._readWordPMBus(0x25)

    def getVoutMarginLow(self):
        return self._readWordPMBus(0x26)

    def getVoutTransitionRate(self):
        return self._readWordPMBus(0x27)

    def getVoutDroop(self):
        return self._readWordPMBus(0x28)

    def getVoltageScaleLoop(self):
        return self._readWordPMBus(0x29)

    def getVoltageScaleMonitor(self):
        return self._readWordPMBus(0x2a)

    ################################### Functions for setting PMBus values
    def setVinUVLimit(self, uvLimit, minUnderVolt=32.0):
        """The VIN_UV_WARN_LIMIT command sets the value of the input voltage that causes an
        input voltage low warning. This value is typically greater than the input undervoltage
        fault threshold, VIN_UV_FAULT_LIMIT (Section 15.27). The VIN_UV_FAULT_LIMIT
        command sets the value of the input voltage that causes an input undervoltage fault."""
        #min = 32, max = 75 on DRQ1250
        if(uvLimit > minUnderVolt):
            uvWarnLimit  = float(uvLimit) + 2
            uvFaultLimit = float(uvLimit)
        else:
            uvWarnLimit  = minUnderVolt + 2
            uvFaultLimit = minUnderVolt

        #print("Old VIN UV Limit: " + str(self.getVinUVLimit()))
        self._writeWordPMBus(0x59, self._encodePMBus(uvFaultLimit))
        self._writeWordPMBus(0x58, self._encodePMBus(uvWarnLimit))
        #print("New VIN UV Limit: " + str(self.getVinUVLimit()))

    def setVinOVLimit(self, ovLimit, maxOverVolt=110.0):
        """The VIN_OV_WARN_LIMIT command sets the value of the input voltage that causes an
        input voltage high warning. This value is typically less than the input overvoltage fault
        threshold. The VIN_OV_FAULT_LIMIT command sets the value of the input voltage that causes an
        input overvoltage fault."""
        #min = 32, max = 110 on DRQ1250
        if(ovLimit < maxOverVolt):
            ovWarnLimit  = float(ovLimit) - 2
            ovFaultLimit = float(ovLimit)
        else:
            ovWarnLimit  = maxOverVolt - 2
            ovFaultLimit = maxOverVolt

        #print("Old VIN OV Limit: " + str(self.getVinOVLimit()))
        self._writeWordPMBus(0x55, self._encodePMBus(ovFaultLimit))
        self._writeWordPMBus(0x57, self._encodePMBus(ovWarnLimit))
        #print("New VIN OV Limit: " + str(self.getVinOVLimit()))

    def setVoutOVLimit(self, ovLimit, maxOverVolt=15.6):
        """The VOUT_OV_WARN_LIMIT command sets the value of the output voltage at the
        sense or output pins that causes an output voltage high warning. This value is typically
        less than the output overvoltage threshold. The VOUT_OV_FAULT_LIMIT command sets the value
        of the output voltage measured at the sense or output pins that causes an output
        overvoltage fault."""
        #min = 8.1, max=15.6 on DRQ1250
        if(ovLimit < maxOverVolt):
            ovWarnLimit  = float(ovLimit) - 1
            ovFaultLimit = float(ovLimit)
        else:
            ovWarnLimit  = maxOverVolt - 1
            ovFaultLimit = maxOverVolt

        ovWarnLimit  = int(ovWarnLimit*(2**-self.VOUT_N))
        ovFaultLimit = int(ovFaultLimit*(2**-self.VOUT_N))

        #print("Old VOUT OV Limit: " + str(self.getVoutOVLimit()))
        self._writeWordPMBus(0x40, ovFaultLimit)
        self._writeWordPMBus(0x42, ovWarnLimit)
        #print("New VOUT OV Limit: " + str(self.getVoutOVLimit()))

    def setIoutOCLimit(self, ocLimit, maxOverCurrent=65.0):
        """The IOUT_OV_WARN_LIMIT command sets the value of the output current that causes
        an output overcurrent warning. The IOUT_OC_FAULT_LIMIT command sets the value of the output current, in
        amperes, that causes the overcurrent detector to indicate an overcurrent fault condition."""
        #min = 59, max = 65 for DRQ1250
        if(ocLimit < maxOverCurrent):
            ocWarnLimit  = float(ocLimit) - 3
            ocFaultLimit = float(ocLimit)
        else:
            ocWarnLimit  = maxOverCurrent - 3
            ocFaultLimit = maxOverCurrent

        #print("Old IOUT OC Limit: " + str(self.getIoutOCLimit()))
        self._writeWordPMBus(0x46, self._encodePMBus(ocFaultLimit))
        self._writeWordPMBus(0x4A, self._encodePMBus(ocWarnLimit))
        #print("New IOUT OC Limit: " + str(self.getIoutOCLimit()))

    def setIoutFaultResponse(self, byte):
        #see page 37-40 on PMBus spec for info on response bytes
        #print("Old IOUT Fault Response: " + bin(self.getIoutFaultResponse()))
        self._writeBytePMBus(0x47, byte)
        #print("New IOUT Fault Response: " + bin(self.getIoutFaultResponse()))

    def setOTLimit(self, otLimit, maxOverTemp=145.0):
        """The OT_WARN_LIMIT command set the temperature, in degrees Celsius, of the unit at
        which it should indicate an Overtemperature Warning alarm. The OT_FAULT_LIMIT command
        set the temperature, in degrees Celsius, of the unit at which it should indicate an Overtemperature Fault."""
        #min = 30, max = 145 for DRQ1250
        if(otLimit < maxOverTemp):
            otWarnLimit  = float(otLimit) - 3
            otFaultLimit = float(otLimit)
        else:
            otWarnLimit  = maxOverTemp - 3
            otFaultLimit = maxOverTemp

        #print("Old OT Limit: " + str(self.getOTLimit()))
        self._writeWordPMBus(0x4F, self._encodePMBus(otFaultLimit))
        self._writeWordPMBus(0x51, self._encodePMBus(otWarnLimit))
        #print("New OT Limit: " + str(self.getOTLimit()))

    def setFaultResponse(self, register, byte):
        #see page 37-40 on PMBus spec for info on response bytes
        """
        DRQ1250 registers:
        VIN UV  = 0x5A
        VIN OV  = 0x56
        VOUT OV = 0x41
        OT      = 0x50
        """
        print("Old Fault Response: " + bin(self.getFaultResponse(register)))
        return self._writeBytePMBus(register, byte)
        print("New Fault Response: " + bin(self.getFaultResponse(register)))

    def setTonDelay(self, delay):
        """The TON_DELAY sets the time, in milliseconds, from when a start condition
        is received (as programmed by the ON_OFF_CONFIG command) until the output
        voltage starts to rise."""
        #max delay is 500ms min is 1ms for DRQ1250
        self._writeWordPMBus(0x60, self._encodePMBus(delay))

    def setTonRise(self, time):
        """The TON_RISE sets the time, in milliseconds, from when the output starts to rise until
        the voltage has entered the regulation band."""
        #max time is 100ms, min is 10ms for DRQ1250
        self._writeWordPMBus(0x61, self._encodePMBus(time))


    def setToffDelay(self, delay):
        """The TOFF_DELAY sets the time, in milliseconds, from a stop condition
        is received (as programmed by the ON_OFF_CONFIG command) until the unit
        stops transferring energy to the output."""
        #max delay is 500ms, min is 0ms for DRQ1250
        self._writeWordPMBus(0x64, self._encodePMBus(delay))

    def setToffFall(self, time):
        """The TOFF_FALL sets the time, in milliseconds, from the end of the turn-off delay time
        (Section 16.5) until the voltage is commanded to zero. Note that this command can only be used
        with a device whose output can sink enough current to cause the output voltage
        to decrease at a controlled rate."""
        #max time is 100ms, min is 10ms for DRQ1250
        self._writeWordPMBus(0x65, self._encodePMBus(time))


    def storeUserAll(self):
        """The STORE_USER_ALL command instructs the PMBus device to copy the entire
        contents of the Operating Memory to the matching locations in the non-volatile User
        Store memory. Any items in Operating Memory that do not have matching locations in
        the User Store are ignored."""
        self._writeBytePMBus(0x15,0x00)

    def restoreUserAll(self):
        """The RESTORE_USER_ALL command instructs the PMBus device to copy the entire
        contents of the non-volatile User Store memory to the matching locations in the
        Operating Memory. The values in the Operating Memory are overwritten by the value
        retrieved from the User Store. Any items in User Store that do not have matching
        locations in the Operating Memory are ignored."""
        self._writeBytePMBus(0x16,0x00)

    def restoreDefaultAll(self):
        """The RESTORE_DEFAULT_ALL command instructs the PMBus device to copy the entire
        contents of the non-volatile Default Store memory to the matching locations in the
        Operating Memory. The values in the Operating Memory are overwritten by the value
        retrieved from the Default Store. Any items in Default Store that do not have matching
        locations in the Operating Memory are ignored."""
        self._writeBytePMBus(0x12,0x00)

    def clearFaults(self):
        """The CLEAR_FAULTS command is used to clear any fault bits that have been set. This
        command clears all bits in all status registers simultaneously. At the same time, the
        device negates (clears, releases) its SMBALERT# signal output if the device is asserting
        the SMBALERT# signal.The CLEAR_FAULTS does not cause a unit that has latched off for a
        fault condition to restart. Units that have shut down for a fault condition are restarted
        as described in Section 10.7."""
        self._writeBytePMBus(0x03,0x00)

    #See PMBus spec page 53-54 for information on the on/off functionality
    def regOff(self, hard=False):
        if hard:
            self._writeBytePMBus(0x01,0x00) #Hard off
        else:
            self._writeBytePMBus(0x01,0x40) #Soft off

    def regOn(self):	# no effect on dps-750-tb
        self._writeBytePMBus(0x01,0x80)

    ################################### Functions for getting PMBus values
    def getVoltageIn(self):
        return self.readLinear(0x88)

    def getVoltageOut(self):
        return self.delinear16(self._readWordPMBus(0x8B))

    def getCurrent(self):
        return self.readLinear(0x8C)

    def getPowerOut(self):
        return self.readLinear(0x96)

    def getPowerIn(self):
        return self.readLinear(0x97)

    def getTempurature(self):
        return self.readLinear(0x8D)

    def getTempurature2(self):
        return self.readLinear(0x8E)

    def getFanSpeed(self):
        return self.readLinear(0x90)

    def getVinUVLimit(self):
        #returns fault, warn
        return self.readLinear(0x59), self.readLinear(0x58)

    def getVinOVLimit(self):
        #returns fault, warn
        return self.readLinear(0x55), self.readLinear(0x57)

    def getVoutOVLimit(self):
        #returns fault, warn
        return self.deLinear16( self._readWordPMBus(0x40), self.deLinear16(self._readWordPMBus(0x42)

    def getIoutOCLimit(self):
        #returns fault, warn
        return self.readLinear(0x46), self.readLinear(0x4A)

    def getOTLimit(self):
        #returns fault, warn
        return self.readLinear(0x4F), self.readLinear(0x51)

    def getTonDelay(self):
        return self.readLinear(0x60)

    def getTonRise(self):
        return self.readLinear(0x61)

    def getToffDelay(self):
        return self.readLinear(0x64)

    def getToffFall(self):
        return self.readLinear(0x65)

    def getSwitchingFreq(self):
        #returns value in kHz
        return self.readLinear(0x95)

    def getDutyCycle(self):
        #returns value in %
        return self.readLinear(0x94)

    def getIoutFaultResponse(self):
        #see page 37-40 on PMBus spec for info on response bytes
        return self._readBytePMBus(0x47)

    def getFaultResponse(self, register):
        #see page 37-40 on PMBus spec for info on response bytes
        """
        DRQ1250 registers:
        VIN UV  = 0x5A
        VIN OV  = 0x56
        VOUT OV = 0x41
        OT      = 0x50
        """
        return self._readBytePMBus(register)

    #members for getting the status of the DRQ device
    #see PMBUS spec part two pages 77-79
    def getStatusSummary(self):
        """The STATUS_WORD command returns two bytes of information with a summary of the
        unit's fault condition. Based on the information in these bytes, the host can get more
        information by reading the appropriate status registers. The low byte of the STATUS_WORD
        is the same register as the STATUS_BYTE command."""
        # BUSY | OFF | VOUT_OV_Fault | IOUT_OC_FAULT | VIN_UV_FAULT | TEMPURATURE | CML (command memory logic) | None
        # VOUT Fault | IOUT Fault | POUT  Fault | INPUT Fault | MFR_Specific | PWR_GD | Fans | Other | Unknown
        # Note: if PWR_GD is set then pwr is not good (negative logic)
        self.statusSummary = self._readWordPMBus(0x79)
        status = {
            "busy" :          bool(self.statusSummary & (0b1<<7)),
            "off" :           bool(self.statusSummary & (0b1<<6)),
            "vout_ov_fault" : bool(self.statusSummary & (0b1<<5)),
            "iout_oc_fault" : bool(self.statusSummary & (0b1<<4)),
            "vin_uv_fault" :  bool(self.statusSummary & (0b1<<3)),
            "temp_fault" :    bool(self.statusSummary & (0b1<<2)),
            "cml_fault" :     bool(self.statusSummary & (0b1<<1)),
            "vout_fault" :    bool(self.statusSummary & (0b1<<15)),
            "iout_fault" :    bool(self.statusSummary & (0b1<<14)),
            "input_fault" :   bool(self.statusSummary & (0b1<<13)),
            "pwr_gd" :        not bool(self.statusSummary & (0b1<<11)),
            "fan_fault" :     bool(self.statusSummary & (0b1<<10)),
            "other" :         bool(self.statusSummary & (0b1<<9)),
            "unknown" :       bool(self.statusSummary & (0b1<<8)),
        }
        return status, self.statusSummary

i2c troubles

The program on the raspberry Pi keeps crashing due to i2c errors, so I got the scope out, and it seems that the PSU and the RPI does not drive it scl/sda with the same force, right now I have 9k ohm pullup on the lines, they probably need to be fine-tuned, or maybe I should try with an Arduino.

 

I looked at the output circuit of another server power-supply, which looked like this, I have no idea why this should be needed, or if this is the related to my troubles, I don’t even know if this circuit is used in this PSU.

i2c simple solution

in /boot/config.txt change the i2c line to

  • dtparam=i2c_arm=on,i2c_arm_baudrate=30000

 

This entry was posted in Arduino, Camper, Embedded, Hardware, HomeAutomation, Linux, MySensors, Raspberry Pi. Bookmark the permalink.