I2C Device Class
- 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