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:
- rcgroups: Dell Poweredge T710, R510(DPS-750TB) and R910 and full Pinout
- How to make a floating server psu dell dps-750TB-1A
- youtube: Turn on and eliminate DC ground from Dell 1100W server power supply.
- muRata: 1U 86mm 800watt PSU similar spec/size/pinout/i2c_addr
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:
- acan-84 Connector Card Application Note
- acan-85 PMBus Communication Protocol
- acan-100 Cold Redundancy
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:
- Part I – General Requirements, Transport And Electrical Interface 20050324
- Part II – Command Language 20050328
Further information on the PMBus can found below:
- Using_The_PMBus
- Monitoring and Optimizing AC/DC Power Supply Performance for Different Applications Using PMBusTM
- PMBus_App_Profile_ACDC_Server_Power
- Results of reverse-engineering the PIC microcontroller in the HP DPS-1200FB power supply (made by Delta)
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.
pin | resistance to GND | AC no X11 | off w X11 | on w X11 | rcgroup pin | notes |
1-12 | 1k4 | 0v | 12v | 12v | ||
13-24 | GND | GND | GND | GND | ||
25 | n.c. | 0v | 0v | 0.12v | tach | |
26 | 203 | 0v | 3v3 | 0.05v | RemoteSense- | changed |
27 | 5M3 | 3v4 | 0.3v | 3v3 | vin_good | changed |
28 | 19k | 0.23v | 3v3 | 0v2 | C-share | changed by PSU |
29 | 17k | 3v3 | 3v3 | 0v | -PS_On | changed by x11 |
30 short | 17k | 3v3 | 3v3 | 0v | PS_Kill | changed by X11 |
31 short | 98k | 3v3 | 3v3 | 3v3 | Reset | 0=enable i2c |
32 short | 5M5 | 3v3 | 3v3 | 3v3 | Alert | |
33 short | 14k | 3v3 | 3v3 | 0v5 | sda | changed by X11 |
34 short | 99 | ov | 0v | 0v | -PS_Present | |
35 short | 14k | 3v3 | 3v3 | 3v3 | scl | |
36 | GND | GND | GND | GND | GND | |
37 | 99k | 0.01v | 0.008v | 3v3 | POK | changed |
38 | 980k | 0.27v | 0.2v | 0.2v | PS_A0 | |
39 | 12v | 12v | 12v | 12V_SB | 12v_SB | |
40 | 203 to 12v | 0v | 0v | 12v | RemoteSense+ | changed by PSU |
41-52 | GND | GND | GND | GND | ||
53-64 | 1k4 | 12v | 12v |
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 pin | Name | RPi gpio | Description |
25 | Tacho | 22 | fan rpm pulse |
27 | AC_ok | 27 | low when AC is good |
37 | DC_ok | 17 | low when DC is good |
29 | PS_On | 4 | 0/1 = On/Off |
30 | PS_Kill | GND | via 100R |
31 | Enable_i2c | GND | via 1k |
33 | SDA | i2c_sda | 9k pullup to 3v3 |
35 | SCL | i2c_sda | 9k pullup to 3v3 |
on/off-button | 23 | momentary 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
You must be logged in to post a comment.