DPS-1200FB
DPS-1200FB copied to clipboard
Read Errors
When code is run a read error is printed. If code in modified then it shows the checksum is always off by 3 and any outputted data is garbage.
Additional note: fan set speed function works And code was recompiled for python 3 on Raspberry Pi 3B
This is the new code created to work on python3.9.2 on Thonny 3.3.14
import time
import traceback
from smbus2 import SMBus, i2c_msg # pip install smbus2
class PowerSupply:
def __init__(self, i2c_bus=1, address=7): # address 0..7 reflecting the i2c address select bits on the PSU edge connector
self.i2c = SMBus(i2c_bus) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
self.address = 0x58 + address
self.EE_address = 0x50 + address
self.numReg = 0x58 // 2 # literally just 44
self.lastReg = [0 for _ in range(self.numReg)]
self.minReg = [0xffff for _ in range(self.numReg)]
self.maxReg = [0 for _ in range(self.numReg)]
# not very interesting - read the 24c02 eeprom (you can write it too)
def read_eeprom(self):
pos = 0
data = ""
while pos < 256: # fixme
data += self.i2c.read_i2c_block_data(self.EE_address, pos, 32)
pos += 32
print(" ".join(["%02x" % ord(d) for d in data]))
def read_var(self, address, write_ints, read_count):
# https://github.com/kplindegaard/smbus2 'dual i2c_rdrw'
write = i2c_msg.write(address, write_ints)
# if read_count:
read = i2c_msg.read(address, read_count)
self.i2c.i2c_rdwr(write, read)
return [chr(n) for n in list(read)]
def write_var(self, address, bytes_):
as_ints = [ord(d) for d in bytes_]
self.i2c.write_i2c_block_data(address, as_ints[0], as_ints[1:])
# the most useful
def read_dps1200(self, reg, count):
cs = reg + (self.address << 1)
reg_cs = ((0xff - cs) + 1) & 0xff # this is the 'secret sauce' - if you don't add the checksum byte when reading a register the PSU will play dumb
# checksum is [ i2c_address, reg_address, checksum ] where checksum is as above.
write_ints = [reg, reg_cs] # send register plus checksum
# this should write [address,register,checksum] and then read two bytes (send address+read bit, read lsb, read msb)
# note this device doesn't require you to use a "repeated start" I2C condition - you can just do start/write/stop;start/read/stop
return self.read_var(self.address, write_ints, count)
# Writable registers/cmds:
# Nothing very interesting discovered so far; you can control the fan speed. Not yet discovered how to turn
# the 12v output on/off in software.
#
# Random notes below:
#
# "interesting" write cmds;
# 0x35 (if msb+lsb==0 clear c4+c5)
#
# 0x3b - checks bit 5 of lsb of write data, if set does (stuff), if bit 6 set does (other stuff), low 5 bits
# stored @ 0xc8 (read with 0x3a)
# b0..2= 1=(see 0x344); sets 'fan_related' to 9000
# 2=(see 0x190a) sets 'surprise_more_flags bit 6'
# b3=
# b4=
# b5=
# b6=
#
# ..cmds that write to ram..
# 0x31 - if data=0 sets i2c_flags1 2 & 7 (0x2d8) = resets total_watts and uptime_secs
# 0x33 - if data=0 resets MaxInputWatts
# 0x35 - if data=0 resets MaxInputCurrent
# 0x37 - if data=0 resets MaxRecordedCurrent
# 0x3b - sets yet_more_flags:5, checks write data lsb:5
# 0x3d - something like set min fan speed
# 0x40 (writes 0xe5)
# 0x41 (writes 0xd4) <<d4= fan speed control - write 0x4000 to 0x40 = full speed fan (sets 'surprise_mnore_flags:5)')
# 0x45 sets some voltage threshold if written (sets a4:1)
# 0x47 sets some other threshold when written (sets a4:2) -
# 0x49 sets a4:3
# 0x4b sets a4:4
# 50/51 (writes 0xee/ef) - default is 3200
# 52/53 (0xa5/6) - some temp threshold
# 54/55 (0xa7/8) (sets some_major_flags:5) - eeprom related
# 56/57 (0xa9/a) (a9 is EEPROM read address with cmd 57)
def write_dps1200(self, reg, value):
val_lsb = value & 0xff
val_msb = value >> 8
cs = (self.address << 1) + reg + val_lsb + val_msb
reg_cs = ((0xff - cs) + 1) & 0xff # the checksum is the 'secret sauce'
write_ints = [reg, val_lsb, val_msb, reg_cs] # write these 4 bytes to i2c
bytes_ = "".join([chr(n) for n in write_ints])
return self.write_var(self.address, bytes_)
def test_write(self):
# try fuzzing things to see if we can find power on/off.. (not yet) 0x40 controls fan speed (bitfield)
# for n in [0x35,0x3b,0x40,0x50,0x52,0x54,0x56]:
for n in [0x35, 0x3b, 0x50, 0x52, 0x54, 0x56]:
# for n in [0x40]:
for b in range(16):
value = (1 << b) - 1
print("%02x : %04x" % (n, value))
self.write_dps1200(n, value)
time.sleep(0.5)
# Readable registers - some of these are slightly guessed - comments welcome if you figure something new out or have a correction.
REGS = {
# note when looking at PIC disasm table; "lookup_ram_to_read_for_cmd", below numbers are <<1
# the second arg is the scale factor
0x01: ["FLAGS", 0], # not sure but includes e.g. "power good"
0x04: ["INPUT_VOLTAGE", 32.0], # e.g. 120 (volts)
0x05: ["AMPS_IN", 128.0],
0x06: ["WATTS_IN", 2.0],
0x07: ["OUTPUT_VOLTAGE", 254.5],
# pretty sure this is right; unclear why scale is /254.5 not /256 but it's wrong - can't see how they'd not be measuring this to high precision
0x08: ["AMPS_OUT", 128.0],
# rather inaccurate at low output <10A (reads under) - appears to have internal load for stability so always reads about 1.5 even open circuit
0x09: ["WATTS_OUT", 2.0],
0x0d: ["TEMP1_INTAKE_FAHRENHEIT", 32.0], # this is a guess - may be C or F but F looks more right
0x0e: ["TEMP2_INTERNAL_FAHRENHEIT", 32.0],
0x0f: ["FAN_SPEED_RPM", 1],
# total guess at scale but this is def fan speed it may be counting ticks from the fan sensor which seem to be typically 2 ticks/revolution
0x1a: ["?flags", 0], # unknown (from disassembly)
0x1b: ["?voltage", 1], # unknown (from disassembly)
(0x2c >> 1): ["WATT_SECONDS_IN", -4.0],
# this is a special case; uses two consecutive regs to make a 32-bit value (the minus scale factor is a flag for that)
(0x30 >> 1): ["ON_SECONDS", 2.0],
(0x32 >> 1): ["PEAK_WATTS_IN", 2.0],
(0x34 >> 1): ["MIN_AMPS_IN", 128.0],
(0x36 >> 1): ["PEAK_AMPS_OUT", 128.0],
(0x3A >> 1): ["COOL_FLAGS1", 0], # unknown (from disassembly)
(0x3c >> 1): ["COOL_FLAGS2", 0], # unknown (from disassembly)
(0x40 >> 1): ["FAN_TARGET_RPM", 1], # unknown (from disassembly)
(0x44 >> 1): ["VOLTAGE_THRESHOLD_1", 1], # unknown (from disassembly)
(0x46 >> 1): ["VOLTAGE_THRESHOLD_2", 1], # unknown (from disassembly)
(0x50 >> 1): ["MAYBE_UNDERVOLTAGE_THRESH", 32.0], # unknown (from disassembly)
(0x52 >> 1): ["MAYBE_OVERVOLTAGE_THRESH", 32.0], # unknown (from disassembly)
# reading 0x57 reads internal EEPROM space in CPU (just logging info, e.g. hours in use)
}
def read_dps1200_register(self, reg):
data = self.read_dps1200(reg << 1, 3) # if low bit set returns zeros (so use even # cmds)
# check checksum (why not)
reply_cs = 0
for d in data:
reply_cs += ord(d)
reply_cs = ((0xff - reply_cs) + 1) & 0xff # check reply checksum (not really required)
data = data[:-1]
value = ord(data[0]) | ord(data[1]) << 8
return value
def read(self):
for n in range(self.numReg):
try:
value = self.read_dps1200_register(n)
self.minReg[n] = min(self.minReg[n], value)
self.maxReg[n] = max(self.maxReg[n], value)
name = ""
if n in self.REGS:
name, scale = self.REGS[n]
if scale < 0:
scale = -scale
value += self.read_dps1200_register(n + 1) << 16
else:
scale = 1
print("%02x\t%04x\t" % (n << 1, value))
if scale:
print("%d\t%d\t%d\t(%d)\t%.3f\t%s" % (
value, self.minReg[n], self.maxReg[n], self.maxReg[n] - self.minReg[n], value / scale, name))
else:
print("%s\t%s" % (bin(value), name))
except Exception as ex:
print("r %02x er %s" % (n, ex))
print(traceback.format_exc())
return
# addr = self.address
def force_fan_rpm(self, rpm):
# sets (forces) fan speed; lowest it will accept is 3300rpm conversely, 16000RPM is very, very fast indeed.
# You probably (untested) have to keep writing this regularly if you want to override what the firmware wants
# to do based on its temp sensors
self.write_dps1200(0x40, rpm)
ps = PowerSupply(i2c_bus=1, address=7)
# ps.readEEPROM()
#ps.read_dps1200(0x04, 1) # test - don't do this unless you really want to override the fan for some reason
##s.force_fan_rpm(1000)
ps.read()
#while True:
#ps.force_fan_rpm(1000)
`