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