I2C Device Class

../_images/i2c_device.png

I2C Device block diagram and example child classes

class src.pico_power_management.i2c_device.Device(name: str, address: int, i2c_bus, description=None, width=8, endian='big', *args, **kwargs)
add_register(name: str, address: int, *args, **kwargs) None

Register addition to the device.

Parameters
  • name (str) – Name of the Register

  • address (int) – Address of the Register.

read()

Read data from the device.

Primarily used when the I2C device does not employ a memory system or a register set.

Parameters

register (Register) – Register object to read from.

Returns

Data read from the register.

reg_read(register)

Read data from a specific register in the device’s memory.

Parameters

register (Register) – Register object to read from.

Returns

Data read from the register.

reg_write(register, data)

Write data to a specific register in the device’s memory.

Parameters
  • register (Register) – Register object to write to.

  • data (int) – Data to write to the register.

write(data)

Write data to the device.

Primarily used when the I2C device does not employ a memory system or a register set.

Parameters

data (int) – Data to write to the device.

class src.pico_power_management.i2c_device.Register(device, name: str, address: int, r_w: str = 'R/W', description=None, *args, **kwargs)
add_field(name: str, bit_offset: int, *args, **kwargs)

Field addition to the register.

Parameters
  • name (str) – Name of field.

  • bit_offset (int) – offset of the bits

  • size (_type_, optional) – size of the fields bits, defaults to 1

  • r_w (str, optional) – Read/Write permissions for the field, can be ‘R’, ‘W’, ‘R/W’, defaults to “R/W”

  • description (str, optional) – description of the field and how it operates, defaults to None

read()

Read from the register.

Returns

Returns the value of the read register.

Return type

int

write(value)

Write data to a register.

Parameters

value (int) – integer value for the data to be written.

class src.pico_power_management.i2c_device.Field(register, name: str, bit_offset: int, width: int = 1, r_w: str = 'R/W', description=None, *args, **kwargs)
read()

Read from the field.

Returns

Returns the value of the read field.

Return type

int

write(value)

Write data to a field.

Parameters

value (int) – integer value for the data to be written.

Code

  1# -----------------------------------------
  2#                 NOTES 
  3# -----------------------------------------
  4
  5# Dieter Steinhauser
  6# 10/2023
  7# I2C Device, Register, and field objects for driver implementation
  8
  9# -----------------------------------------
 10#               IMPORTS
 11# -----------------------------------------
 12
 13# from pico_power_management.helpers import check_range, check_type, check_str, check_list, read_modify
 14from src.pico_power_management.helpers import *
 15# from helpers import *
 16import time
 17
 18# -----------------------------------------
 19#               CLASS
 20# -----------------------------------------
 21
 22_RW_TYPE = ['R', 'W', 'R/W']
 23_READ = ['R', 'R/W']
 24_WRITE = ['W', 'R/W']
 25
 26
 27class Device:
 28
 29    def __init__(self, name:str, address:int, i2c_bus, description = None, width=8, endian='big', *args, **kwargs) -> None: 
 30
 31        """
 32        Creation of an I2C Device.
 33
 34        :param name: Device name.
 35        :type name: str
 36        :param address: device i2c address
 37        :type address: int
 38        :param i2c_bus: I2C Bus object for communication
 39        :type i2c_bus: i2c_bus
 40        :param description: Short Description of the device, defaults to None
 41        :type description: str, optional
 42        :param width: register bit width must be a power of 2 between 8 and 64, defaults to 8
 43        :type width: int, optional
 44        :param endian: endian structure of device, either big or little, defaults to 'big'
 45        :type endian: str, optional
 46        """
 47
 48        # Check inputs for errors
 49        check_type(name, 'name', str)
 50        check_type(address, 'address', int)
 51        check_type(width, 'width', int)
 52        check_list(width, 'width', [8, 16, 32, 64])
 53        check_type(endian, 'endian', str)
 54        check_str(endian, 'endian', ('big', 'little'))
 55
 56        self.name = name
 57        self.addr = address
 58        self.description = description
 59        self.registers= {}
 60        self.endian = endian
 61        self.width = width
 62        self.reg_bytes = int(width / 8)
 63
 64        if i2c_bus is not None:
 65            self.i2c_bus = i2c_bus
 66
 67            devices = i2c_bus.scan()
 68            # hex_addr = [hex(x) for x in devices]
 69
 70            if address not in devices:
 71                raise ValueError(f'Cannot find device address {address} upon instantiation of I2C device. \nFound Device addresses: {devices} ')
 72
 73 
 74    def read(self):
 75        """
 76        Read data from the device. 
 77        
 78        Primarily used when the I2C device does not employ a memory system or a register set.
 79
 80        :param register: Register object to read from.
 81        :type register: Register
 82        :return: Data read from the register.
 83        """
 84        if not self.i2c_bus:
 85            raise ValueError("I2C bus not initialized.")
 86        
 87        read_data = self.i2c_bus.readfrom(self.addr, 1)
 88        return int.from_bytes(read_data, self.endian) # type: ignore
 89
 90    def write(self, data):
 91        """
 92        Write data to the device.
 93
 94        Primarily used when the I2C device does not employ a memory system or a register set.
 95
 96        :param data: Data to write to the device.
 97        :type data: int
 98        """
 99        
100        # Write the data to the bus
101        self.i2c_bus.writeto(self.addr, data.to_bytes(self.reg_bytes, self.endian))
102        
103        # Confirm the write by reading and comparing the data
104        read_data = self.read()
105        if read_data != data:
106            raise ValueError(f"Write confirmation failed.\nRead Data: {read_data} \nWritten Data: {data}")
107
108
109    def add_register(self, name:str, address:int, *args, **kwargs) -> None:
110        """
111        Register addition to the device.
112
113        :param name: Name of the Register
114        :type name: str
115        :param address: Address of the Register.
116        :type address: int
117        """
118
119        register = Register(self, name, address, *args, **kwargs)
120        self.registers[name] = register
121        setattr(self, name, register)
122
123    def reg_read(self, register):
124        """
125        Read data from a specific register in the device's memory.
126
127        :param register: Register object to read from.
128        :type register: Register
129        :return: Data read from the register.
130        """
131        if not self.i2c_bus:
132            raise ValueError("I2C bus not initialized.")
133        
134        read_data = self.i2c_bus.readfrom_mem(self.addr, register.addr, self.reg_bytes)
135        return int.from_bytes(read_data, self.endian)  # type: ignore
136            
137
138    def reg_write(self, register, data):
139        """
140        Write data to a specific register in the device's memory.
141
142        :param register: Register object to write to.
143        :type register: Register
144        :param data: Data to write to the register.
145        :type data: int
146        """
147        
148        # Write the data to the bus
149        self.i2c_bus.writeto_mem(self.addr, register.addr, data.to_bytes(self.reg_bytes, self.endian))
150        
151        # If possible, confirm that we correctly edited the field.
152        if register.r_w in _READ:
153
154            # Confirm the write by reading and comparing the data
155            read_data = self.reg_read(register)
156            if read_data != data:
157                raise ValueError(f"Write confirmation failed.\nRead Data: {read_data} \nWritten Data: {data}")
158
159
160
161
162
163class Register:
164
165    def __init__(self, device, name : str, address : int, r_w:str = "R/W", description = None, *args, **kwargs) -> None:
166        """
167        Register creation
168
169        :param name: Name of the Register
170        :type name: str
171        :param address: Address of the Register.
172        :type address: int
173        """
174        # Check inputs for errors
175        check_type(name, 'name', str)
176        check_type(address, 'address', int)
177
178        self.name = name
179        self.addr = address
180        self.r_w = r_w
181        self.description = description
182        self.device = device
183        self.endian = device.endian
184        self.width = device.width
185        self.reg_bytes = int(self.width / 8)
186        self.fields= {}
187        self.i2c_bus = device.i2c_bus
188
189    def add_field(self, name:str, bit_offset:int, *args, **kwargs):
190        """
191        Field addition to the register.
192
193        :param name: Name of field.
194        :type name: str
195        :param bit_offset: offset of the bits
196        :type bit_offset: int
197        :param size: size of the fields bits, defaults to 1
198        :type size: _type_, optional
199        :param r_w: Read/Write permissions for the field, can be 'R', 'W', 'R/W', defaults to "R/W"
200        :type r_w: str, optional
201        :param description: description of the field and how it operates, defaults to None
202        :type description: str, optional
203        """
204        field = Field(self, name, bit_offset, *args, **kwargs)
205        self.fields[name] = field
206        setattr(self, name, field)
207
208    def read(self):
209        """
210        Read from the register.
211
212        :return: Returns the value of the read register.
213        :rtype: int
214        """
215        # Throw an error if the I2C bus object does not exist.
216        if not self.i2c_bus:
217            raise ValueError("I2C bus not initialized.")
218        
219        # Throw an error if not readable.
220        if self.r_w not in _READ:
221            raise ValueError(f"Error writing to register, Permission is {self.r_w} 'Write Only'.")
222
223        read_byte = self.i2c_bus.readfrom_mem(self.device.addr, self.addr, self.reg_bytes)
224        return int.from_bytes(read_byte, self.endian)
225    
226         
227    def write(self, value):
228        """
229        Write data to a register.
230
231        :param value: integer value for the data to be written.
232        :type value: int
233        """
234
235        # Throw an error if the I2C bus object does not exist.
236        if not self.i2c_bus:
237            raise ValueError("I2C bus not initialized.")
238
239        # Throw an error if the field cannot be written.
240        if self.r_w not in _WRITE:
241            raise ValueError(f"Error writing to register, Permission is {self.r_w} 'Read Only'.")
242
243        # Throw an error if the value is larger than the field width
244        if value > (2**self.width - 1):
245            raise ValueError(f"Error writing to register, Value {value} is too large for {self.width}-bit register size ")
246        
247        # Write the data to the bus
248        self.i2c_bus.writeto_mem(self.device.addr, self.addr, value.to_bytes(self.reg_bytes, self.endian))
249
250        # let the device settle, avoids EIO error
251        # time.sleep_us(10)
252        
253        # If possible, confirm that we correctly edited the field.
254        if self.r_w in _READ:
255
256            # Confirm the write by reading and comparing the data
257            read_data = self.read()
258            if read_data != value:
259                raise ValueError(f"Write confirmation failed.\nRead Data: {read_data} \nWritten Data: {value}")
260
261
262
263class Field:
264
265    def __init__(self, register, name:str, bit_offset:int, width:int = 1, r_w:str = "R/W", description = None, *args, **kwargs) -> None:
266        """
267        Field creation.
268
269        :param name: Name of field.
270        :type name: str
271        :param name: Name of field.
272        :type name: str
273        :param bit_offset: offset of the bits
274        :type bit_offset: int
275        :param width: Width of the fields bits, defaults to 1
276        :type width: _type_, optional
277        :param r_w: Read/Write permissions for the field, can be 'R', 'W', 'R/W', defaults to "R/W"
278        :type r_w: str, optional
279        :param description: description of the field and how it operates, defaults to None
280        :type description: str, optional
281        """
282
283        # Check inputs for errors
284        check_type(name, 'name', str)
285        check_type(bit_offset, 'bit_offset', int)
286        check_range(bit_offset, 'bit_offset', 0, (register.width - 1))
287        check_type(width, 'width', int)
288        check_range(width, 'width', 1, register.width)
289        
290        if not isinstance(r_w, str) or r_w not in _RW_TYPE:
291            raise ValueError(f" Incorrect value for r_w: {r_w}. Input should be in {_RW_TYPE}")
292        
293        self.name = name
294        self.bit_offset = bit_offset
295        self.width = width
296        self.r_w = r_w
297        self.description = description
298        self.register = register
299        self.device = register.device
300        self.endian = register.endian
301        self.reg_bytes = register.reg_bytes
302        self.i2c_bus = register.i2c_bus
303        
304    def read(self):
305        """
306        Read from the field.
307
308        :return: Returns the value of the read field.
309        :rtype: int
310        """
311        # Throw an error if the I2C bus object does not exist.
312        if not self.i2c_bus:
313            raise ValueError("I2C bus not initialized.")
314        
315        # Throw an error if not readable.
316        if self.r_w not in _READ:
317            raise ValueError(f"Error writing to field, Permission is {self.r_w} 'Write Only'.")
318        
319        read_byte = self.i2c_bus.readfrom_mem(self.device.addr, self.register.addr, self.reg_bytes)
320        
321        read_data = int.from_bytes(read_byte, self.endian)
322        field =  (read_data >> self.bit_offset) & ((2**self.width) - 1)
323        return field
324            
325    def write(self, value):
326        """
327        Write data to a field.
328
329        :param value: integer value for the data to be written.
330        :type value: int
331        """
332
333        # Throw an error if the I2C bus object does not exist.
334        if not self.i2c_bus:
335            raise ValueError("I2C bus not initialized.")
336
337        # Throw an error if the field cannot be written.
338        if self.r_w not in _WRITE:
339            raise ValueError(f"Error writing to field, Permission is {self.r_w} 'Read Only'.")
340
341        # Throw an error if the value is larger than the field width
342        if value > (2**self.width - 1):
343            raise ValueError(f"Error writing to field, Value {value} is too large for {self.width}-bit field size ")
344        
345        # Perform a Read Modify Write Cycle.
346        write_data = read_modify(read_data = self.register.read(), modify_data = (value << self.bit_offset), bit_mask = (((2**self.width)-1) << self.bit_offset))
347        
348        # Write the data to the bus
349        self.i2c_bus.writeto_mem(self.device.addr, self.register.addr, write_data.to_bytes(self.reg_bytes, self.endian))
350
351        # let the device settle, avoids EIO error
352        # time.sleep_us(10)
353        
354        # Confirm the write by reading and comparing the data
355        read_data = self.read()
356        if read_data != value:
357            raise ValueError(f"Write confirmation failed.\nRead Data: {read_data} \nWritten Data: {value}")
358        
359
360# -----------------------------------------
361#               END OF FILE
362# -----------------------------------------
363