TPS55288 DC/DC Buck-Boost I2C Driver

class src.pico_power_management.TPS55288.TPS55288(name: str, address: int, i2c_bus, description=None, *args, **kwargs)
ALT_ADDR = 117
CDC: src.pico_power_management.i2c_device.Register
DEFAULT_ADDR = 116
I2C_MAX_FREQ = 1000000
I2C_MIN_FREQ = 100000
IOUT_LIMIT: src.pico_power_management.i2c_device.Register
MODE: src.pico_power_management.i2c_device.Register
STATUS: src.pico_power_management.i2c_device.Register
VOUT_FS: src.pico_power_management.i2c_device.Register
VOUT_SR: src.pico_power_management.i2c_device.Register
VREF_HIGH: src.pico_power_management.i2c_device.Register
VREF_LOW: src.pico_power_management.i2c_device.Register
current_limit(current_limit=None, enable=None)

Query or Command the output current limit and enable status. if no value is given, return the status of the current limit system.

Parameters

value (int, optional) – Enable value, active high, defaults to None

feedback_ratio(feedback_ratio=None)

Command and Query the internal feedback ratio. Changes the step size of voltage increments.

0 = 5mV, 1 = 10mV, 2 = 15mV, 3 = 20mV

Parameters

value (_type_, optional) – _description_, defaults to None

Returns

Returns the feedback ratio integer

Return type

int

feedback_select(select=None)

Command and Query the output feedback voltage location. Defaults to internal.

Parameters

select (int, optional) – Feedback Select Either external (1) or internal (0), defaults to None

Returns

Returns the current feedback select setting.

Return type

int

initialize_rail(voltage, current_limit)

Startup Sequence for a TPS voltage rail.

Parameters
  • voltage (int | float) – Output voltage desired for the rail

  • current_limit (int | float) – Current limit boundary for the voltage rail

mode(output_en=None, freq_doubling=None, hiccup=None, discharge=None, vcc=None, i2c_addr=None, pfm=None, mode=None)

Command or Query the operation mode of the device.

Parameters
  • output_en (int, optional) – Output enable 0 = off, 1 = on, defaults to None

  • freq_doubling (int, optional) – Switching frequency doubling in buck-boost mode, 0 = off, 1 = on, defaults to None

  • hiccup (int, optional) – Hiccup mode, 0 = off, 1 = on, defaults to None

  • discharge (int, optional) – Output discharged through 100mA current sink when active, 0 = off, 1 = on, defaults to None

  • vcc (int, optional) – Choose between internal LDO (0, default) or external 5V power supply (1), defaults to None

  • i2c_addr (int, optional) – Swap between 0x74 (0, default) and 0x75 (1), defaults to None

  • pfm (int, optional) – Select operating mode at light load condition PFM (0, default) or FPWM (1), defaults to None

  • mode (int, optional) – Mode control approach set VCC, I2CADD, and PFM either through external resistor(0) or internal register (1), defaults to None

output_enable(value=None)

Query or Command the output enable status. if no value is given, return the current status.

Parameters

value (int, optional) – Enable value, active high, defaults to None

reference_voltage(value=None)

method for setting reference voltage on the device, if no value is given, returns the device set reference voltage.

Parameters

value (int, optional) – Voltage value, defaults to None

status()

method to query the status register on the device. Reset’s the STATUS register after reading.

voltage(value=None)

method for setting the voltage on the device, if no value is given, returns the device set voltage.

Parameters

value (int, optional) – Voltage value, defaults to None

Code

  1# -----------------------------------------
  2#                 NOTES 
  3# -----------------------------------------
  4# 
  5# Dieter Steinhauser
  6# 10/2023
  7# TPS55288 I2C Driver
  8
  9
 10# -----------------------------------------
 11#               IMPORTS
 12# -----------------------------------------
 13
 14desc = r"""
 15The TPS55288 uses I2C interface for flexible converter parameter programming. I2C is a bi-directional 2-wire
 16serial interface. Only two bus lines are required: a serial data line (SDA) and a serial clock line (SCL). I2C
 17devices can be considered as masters or slaves when performing data transfers. A master is the device that
 18initiates a data transfer on the bus and generates the clock signals to permit that transfer. At that time, any
 19device addressed is considered a slave.
 20The TPS55288 operates as a slave device with address 74h and 75h set by a different resistor at the MODE pin.
 21Receiving control inputs from the master device like a microcontroller or a digital signal processor reads and
 22writes the internal registers 00h through 07h. The I2C interface of the TPS55288 supports both standard mode
 23(up to 100 kbit/s) and fast mode plus (up to 1000 kbit/s). Both SDA and SCL must be connected to the positive
 24supply voltage through current sources or pullup resistors. When the bus is free, both lines are in high voltage.
 25"""
 26
 27
 28from machine import Pin, I2C
 29from time import sleep
 30from src.pico_power_management.i2c_device import Device, Register, Field
 31from src.pico_power_management.helpers import *
 32# from i2c_device import Device, Register, Field
 33# from helpers import *
 34# -----------------------------------------
 35#                Class:
 36# -----------------------------------------
 37
 38class TPS55288(Device):
 39
 40    I2C_MIN_FREQ = 100_000
 41    I2C_MAX_FREQ = 1_000_000
 42    DEFAULT_ADDR = 0x74
 43    ALT_ADDR = 0x75
 44
 45    VREF_LOW: Register
 46    VREF_HIGH: Register
 47    IOUT_LIMIT: Register
 48    VOUT_SR: Register
 49    VOUT_FS: Register
 50    CDC: Register
 51    VOUT_FS: Register
 52    MODE: Register
 53    STATUS: Register
 54
 55
 56    def __init__(self, name:str, address:int, i2c_bus, description = None, *args, **kwargs) -> None:
 57        """Object initialization for TPS55288. Follow device initialization and adds register information to object"""
 58        description = desc if desc is None else description
 59        super().__init__(name, address, i2c_bus, description, *args, **kwargs)
 60
 61        # Add device registers
 62        self.add_register("VREF_LOW", 0x0, r_w = "R/W", description='Low byte of internal reference voltage')
 63        self.add_register("VREF_HIGH", 0x1, r_w = "R/W", description='high byte of internal reference voltage')
 64        self.add_register("IOUT_LIMIT", 0x2, r_w = "R/W", description='Current Limit Setting')
 65        self.add_register("VOUT_SR", 0x3, r_w = "R/W", description='Slew Rate ')
 66        self.add_register("VOUT_FS", 0x4, r_w = "R/W", description='Feedback Selection')
 67        self.add_register("CDC", 0x5, r_w = "R/W", description='Cable Compensation')
 68        self.add_register("MODE", 0x6, r_w = "R/W", description='Mode Control')
 69        self.add_register("STATUS", 0x7, r_w = "R", description='Operating Status')
 70
 71        # Add fields to each register
 72        self.VREF_LOW.add_field(name='REF_LSB', bit_offset=0, width=8, r_w="R/W", description="VREF Lower byte. 10 bits total.")
 73
 74        self.VREF_HIGH.add_field(name='REF_MSB', bit_offset=0, width=2, r_w="R/W", description="VREF higher bits. 10 bits total.")
 75
 76        self.IOUT_LIMIT.add_field(name='CURR_LIMIT', bit_offset=0, width=7, r_w="R/W", description="Current Limit, setting the target voltage between ISP and ISN.")
 77        self.IOUT_LIMIT.add_field(name='CURR_LIMIT_EN', bit_offset=7, width=1, r_w="R/W", description="Current Limit Enable, Active High and on by default")
 78
 79        self.VOUT_SR.add_field(name='SR', bit_offset=0, width=2, r_w="R/W", description="Sets slew rate for output voltage change.")
 80        self.VOUT_SR.add_field(name='OCP_DELAY', bit_offset=4, width=2, r_w="R/W", description="Sets the response time of the device when the output overcurrent is reached.")
 81
 82        self.VOUT_FS.add_field(name='INTFB', bit_offset=0, width=2, r_w="R/W", description="Internal Feedback ratio")
 83        self.VOUT_FS.add_field(name='FB', bit_offset=7, width=1, r_w="R/W", description="Output feedback voltage.")
 84
 85        self.CDC.add_field(name='CDC', bit_offset=0, width=3, r_w="R/W", description="Compensation for voltage droop over cable")
 86        self.CDC.add_field(name='CDC_OPTION', bit_offset=3, width=1, r_w="R/W", description="Select the cable voltage droop compensation approach")
 87        self.CDC.add_field(name='OVP_MASK', bit_offset=5, width=1, r_w="R/W", description="Overvoltage Mask")
 88        self.CDC.add_field(name='OCP_MASK', bit_offset=6, width=1, r_w="R/W", description="Overcurrent Mask")
 89        self.CDC.add_field(name='SC_MASK', bit_offset=7, width=1, r_w="R/W", description="Short Circuit Mask")
 90
 91        self.MODE.add_field(name='MODE', bit_offset=0, width=1, r_w="R/W", description="Mode Control Approach")
 92        self.MODE.add_field(name='PFM', bit_offset=1, width=1, r_w="R/W", description="Select operating mode at light load condition")
 93        self.MODE.add_field(name='I2CADD', bit_offset=2, width=1, r_w="R/W", description="I2C address")
 94        self.MODE.add_field(name='VCC', bit_offset=3, width=1, r_w="R/W", description="VCC option")
 95        self.MODE.add_field(name='DISCHG', bit_offset=4, width=1, r_w="R/W", description="Output discharge")
 96        self.MODE.add_field(name='HICCUP', bit_offset=5, width=1, r_w="R/W", description="Hiccup mode")
 97        self.MODE.add_field(name='FSWDBL', bit_offset=6, width=1, r_w="R/W", description="Switching frequency doubling in buck-boost mode")
 98        self.MODE.add_field(name='OE', bit_offset=7, width=1, r_w="R/W", description="Output enable")
 99
100        self.STATUS.add_field(name='STATUS', bit_offset=0, width=2, r_w="R", description="Operating status")
101        self.STATUS.add_field(name='OVP', bit_offset=5, width=1, r_w="R", description="Overvoltage protection")
102        self.STATUS.add_field(name='OCP', bit_offset=6, width=1, r_w="R", description="Overcurrent protection")
103        self.STATUS.add_field(name='SCP', bit_offset=7, width=1, r_w="R", description="Short circuit protection")
104
105        
106    def voltage(self, value=None):
107        """
108        method for setting the voltage on the device, if no value is given, returns the device set voltage.
109
110        :param value: Voltage value, defaults to None
111        :type value: int, optional
112        """
113
114        # read back the voltage on the device
115        if value is None:
116            
117            # Throw Error if external feedback is being used.
118            if self.feedback_select() == 1:
119                raise ValueError("Cannot determine state of output voltage digitally because the system is utilizing an external feedback for voltage selection")
120            
121            # Check the internal feedback ratio
122            ratio_int = self.feedback_ratio() # 0 = 5mV, 1 = 10mV, 2 = 15mV, 3 = 20mV
123            ratio = (ratio_int+1) * 0.00520833
124             
125            # check the reference voltage
126            low = self.VREF_LOW.REF_LSB.read()
127            high = self.VREF_HIGH.REF_MSB.read()
128            ref_value = (high << 8) | low
129            
130            # multiply the reference voltage by the feedback ratio to give the output value.
131            voltage = ratio * ref_value
132            return voltage
133        
134        # Throw errors for incorrect values given
135        check_type(value, 'value', (int, float))
136        check_range(value, 'value', 0.8, 21)
137
138        # Determine the smallest step size required to produce the desired value
139        if (0 <= value < 5.31):
140            ratio_int = 0
141        elif (5.31 <= value < 10.64):
142            ratio_int = 1
143        elif (10.64 <= value < 15.97):
144            ratio_int = 2
145        else:
146            ratio_int = 3
147
148        # calculate the ratio and reference value required.
149        ratio = (ratio_int+1) * 0.00520833
150        ref_value = 0.045 + int(value/ratio) * 0.001129
151
152        # set the feedback ratio and reference value
153        self.feedback_ratio(ratio_int) # 0 = 5mV, 1 = 10mV, 2 = 15mV, 3 = 20mV
154        self.reference_voltage(ref_value)
155        
156        
157
158    def reference_voltage(self, value=None):
159        """
160        method for setting reference voltage on the device, if no value is given, returns the device set reference voltage.
161
162        :param value: Voltage value, defaults to None
163        :type value: int, optional
164        """
165
166        # read back the reference voltage on the device
167        if value is None:
168            low = self.VREF_LOW.REF_LSB.read()
169            high = self.VREF_HIGH.REF_MSB.read()
170            ref_value = (high << 8) | low
171
172            # Reference voltage starts at 45mV and goes in 1.129mV increments
173            ref_voltage = 0.045 + ref_value * 0.001129
174            return ref_voltage
175        
176        # Throw errors for incorrect values given
177        check_type(value, 'value', (int, float))
178        check_range(value, 'value', 0.045, 1.2)
179
180        # make voltage value into clean byte for data transfer
181        ref_value =  int(round((value - 0.045) / 0.001129))
182        ref_low = ref_value & 0x0FF
183        ref_high = (ref_value & 0x300) >> 8
184
185        # write to the registers
186        self.VREF_LOW.write(ref_low)
187        self.VREF_HIGH.write(ref_high)
188        
189
190    def status(self):
191        """
192        method to query the status register on the device. Reset's the STATUS register after reading.
193        """
194        return_dict = {}
195        status_vals = ['BOOST', 'BUCK', 'BUCK_BOOST']
196        status_ovp = ['NONE', 'OVP']
197        status_ocp = ['NONE', 'OCP']
198        status_scp = ['NONE', 'SCP']
199
200        # read back the reference voltage on the device
201        read_data = self.STATUS.read()
202        status = (read_data & 0x03)
203        ovp = (read_data & 0x20) >> 5
204        ocp = (read_data & 0x40) >> 6
205        scp = (read_data & 0x80) >> 7
206
207        # format the returned data
208        return_dict['STATUS'] = status_vals[status]
209        return_dict['OVP'] = status_ovp[ovp]
210        return_dict['OCP'] = status_ocp[ocp]
211        return_dict['SCP'] = status_scp[scp]
212        return return_dict
213    
214
215    def output_enable(self, value=None):
216        """
217        Query or Command the output enable status. if no value is given, return the current status.
218
219        :param value: Enable value, active high, defaults to None
220        :type value: int, optional
221        """
222
223        if value is None:
224            return self.MODE.OE.read()
225        
226        check_type(value, 'output_en', int)
227        check_range(value, 'output_en', 0, 1)
228
229        self.MODE.OE.write(value)
230
231
232    def current_limit(self, current_limit=None, enable=None):
233        """
234        Query or Command the output current limit and enable status. if no value is given, return the status of the current limit system.
235
236        :param value: Enable value, active high, defaults to None
237        :type value: int, optional
238        """
239
240        if all (val is None for val in [current_limit, enable]):
241            return_dict = {}
242            register = self.IOUT_LIMIT.read()
243            return_dict['current_limit'] = (register & 0x7F) * 0.0005
244            return_dict['enable'] = (register >> 7) & 1
245            return register
246        
247        check_type(current_limit, 'current_limit', (int, float))
248        check_range(current_limit, 'current_limit', 0, 0.0635)
249                
250        check_type(enable, 'enable', int)
251        check_range(enable, 'enable', 0, 1)
252
253        if current_limit is not None:
254            self.IOUT_LIMIT.CURR_LIMIT.write(int(current_limit/0.0005))
255
256        if enable is not None: 
257            self.IOUT_LIMIT.CURR_LIMIT_EN.write(enable)
258
259
260    def feedback_ratio(self, feedback_ratio=None):
261        """
262        Command and Query the internal feedback ratio. Changes the step size of voltage increments.
263
264        0 = 5mV, 1 = 10mV, 2 = 15mV, 3 = 20mV 
265
266        :param value: _description_, defaults to None
267        :type value: _type_, optional
268        :return: Returns the feedback ratio integer
269        :rtype: int
270        """
271        if feedback_ratio is None:
272            return self.VOUT_FS.INTFB.read()
273        
274        check_type(feedback_ratio, 'feedback_ratio', int)
275        check_range(feedback_ratio, 'feedback_ratio', 0, 3)
276
277        self.VOUT_FS.INTFB.write(feedback_ratio)
278
279
280    def feedback_select(self, select=None):
281        """
282        Command and Query the output feedback voltage location. Defaults to internal.
283
284        :param select: Feedback Select Either external (1) or internal (0), defaults to None
285        :type select: int, optional
286        :return: Returns the current feedback select setting.
287        :rtype: int
288        """
289
290        if select is None:
291            return self.VOUT_FS.FB.read()
292        
293        check_type(select, 'feedback_ratio', int)
294        check_range(select, 'feedback_ratio', 0, 1)
295
296        self.VOUT_FS.FB.write(select)
297
298
299    def mode(self, output_en=None, freq_doubling=None, hiccup=None, discharge=None, vcc=None, i2c_addr=None, pfm=None, mode=None):
300        """
301        Command or Query the operation mode of the device.
302
303        :param output_en: Output enable 0 = off, 1 = on, defaults to None
304        :type output_en: int, optional
305        :param freq_doubling: Switching frequency doubling in buck-boost mode, 0 = off, 1 = on, defaults to None
306        :type freq_doubling: int, optional
307        :param hiccup: Hiccup mode, 0 = off, 1 = on, defaults to None
308        :type hiccup: int, optional
309        :param discharge: Output discharged through 100mA current sink when active, 0 = off, 1 = on, defaults to None
310        :type discharge: int, optional
311        :param vcc: Choose between internal LDO (0, default) or external 5V power supply (1), defaults to None
312        :type vcc: int, optional
313        :param i2c_addr: Swap between 0x74 (0, default) and 0x75 (1), defaults to None
314        :type i2c_addr: int, optional
315        :param pfm: Select operating mode at light load condition PFM (0, default) or FPWM (1), defaults to None
316        :type pfm: int, optional
317        :param mode:  Mode control approach set VCC, I2CADD, and PFM either through external resistor(0) or internal register (1), defaults to None
318        :type mode: int, optional
319        """
320        inputs = [output_en, freq_doubling, hiccup, discharge, vcc, i2c_addr, pfm, mode]
321        # if all are none, read back the state of the device
322        if all (val is None for val in inputs):
323
324            return_dict = {}
325            register = self.MODE.read()
326            return_dict['output_en'] = (register >> 7) & 1
327            return_dict['freq_doubling'] = (register >> 6) & 1
328            return_dict['hiccup'] = (register >> 5) & 1
329            return_dict['discharge'] = (register >> 4) & 1
330            return_dict['vcc'] = (register >> 3) & 1
331            return_dict['i2c_addr'] = (register >> 2) & 1
332            return_dict['pfm'] = (register >> 1) & 1
333            return_dict['mode'] = (register >> 0) & 1
334            return return_dict
335        
336        # check the types of the input
337        check_type(output_en, 'output_en', int)
338        check_type(freq_doubling, 'freq_doubling', int)
339        check_type(hiccup, 'hiccup', int)
340        check_type(discharge, 'discharge', int)
341        check_type(vcc, 'vcc', int)
342        check_type(i2c_addr, 'i2c_addr', int)
343        check_type(pfm, 'pfm', int)
344        check_type(mode, 'mode', int)
345        
346        # check the range of the inputs
347        check_range(output_en, 'output_en', 0, 1)
348        check_range(freq_doubling, 'freq_doubling', 0, 1)
349        check_range(hiccup, 'hiccup', 0, 1)
350        check_range(discharge, 'discharge', 0, 1)
351        check_range(vcc, 'vcc', 0, 1)
352        check_range(i2c_addr, 'i2c_addr', 0, 1)
353        check_range(pfm, 'pfm', 0, 1)
354        check_range(mode, 'mode', 0, 1)
355
356        # read the current controls of the device
357        register = self.MODE.read()
358
359        # Have the inputs as binary values
360        bin_list = [0 if value is None else value for value in inputs]
361        
362        # create the field mask from the changed inputs and data to be written
363        field_mask = 0x00
364        write_data = 0x00
365
366        for index in range (7, -1, -1):
367            if inputs[index] is not None:
368
369                # Place a 1 in the position desired for editing.
370                field_mask |= (1 << index)
371
372                # make the write data equivalent to the inputs given
373                # write_command = (output_en << 7) | (freq_doubling << 6) |  (hiccup << 5) |  (discharge << 4) |  (vcc << 3) |  (i2c_addr << 2) |  (pfm << 1) |  (mode << 0)
374                write_data |= inputs[index] << (7-index)
375
376            else:
377                write_data |= 0 << index
378
379        # clear the field bits we are writing to while leaving the others untouched
380        register &= ~field_mask
381
382        # Make changes to the register
383        register |= write_data
384
385        # write the register
386        self.MODE.write(register)
387
388    def initialize_rail(self, voltage, current_limit):
389        """
390        Startup Sequence for a TPS voltage rail.
391
392        :param voltage: Output voltage desired for the rail
393        :type voltage: int | float
394        :param current_limit: Current limit boundary for the voltage rail
395        :type current_limit: int | float
396        """
397        # set the output enable low.
398        self.output_enable(0)
399
400        # check the current status of the rail, if protection has been tripped then throw errors.
401        status_dict = self.status()
402        status_dict.pop("STATUS")
403        msg = ''
404        for key, value in status_dict.items():
405            if value != "NONE":
406                msg += f'Protection field {key} is in effect. Device status is reset and output is off. \n'
407
408        if len(msg) > 0:
409            raise ValueError(msg)
410
411
412        # Throw an error for incorrect voltage or current values
413        check_type(current_limit, 'current_limit', (int, float))
414        check_range(current_limit, 'current_limit', 0, 6.35)
415
416        # calculate the acceptable voltage drop between the ISP and ISN rails, a 10m ohm resistor on the EVM
417        v_drop = current_limit / 100
418
419        # write the voltage and current limit
420        self.voltage(voltage)
421        self.current_limit(v_drop)
422
423        # set the output enable high
424        self.output_enable(1)
425
426    # TODO Implement CDC Method for CDC controls
427
428    # TODO Implement Slew Rate method for slew controls
429
430
431if __name__ == '__main__':
432    i2c_bus =  I2C(1, sda=Pin(14), scl=Pin(15), freq=100_000)
433    tps =  TPS55288(name="TPS_rail", address=0x74, i2c_bus=i2c_bus)
434    devices = tps.i2c_bus.scan()
435    hex_addr = [hex(x) for x in devices]
436    print(f"Seen device addresses: {hex_addr}")
437
438# -----------------------------------------
439#              END OF FILE
440# -----------------------------------------