-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPT2258.py
199 lines (165 loc) · 7.81 KB
/
PT2258.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import utime
from machine import I2C
from micropython import const
class PT2258:
# Constants for volume, attenuation levels, and mute, clear registers.
# These constants are defined using the const() function from micropython to save memory on the microcontroller.
# Constants for clear registers
__CLEAR_REGISTER: int = const(0xC0)
# Constants for master volume registers 10dB
__MUTE_REGISTER: int = const(0xF8)
# Constants for master volume registers 10dB
__MASTER_VOLUME_10DB: int = const(0xD0)
__MASTER_VOLUME_1DB: int = const(0xE0)
# Constants for channel registers 10dB
__C1_10DB = const(0x80)
__C2_10DB = const(0x40)
__C3_10DB = const(0x00)
__C4_10DB = const(0x20)
__C5_10DB = const(0x60)
__C6_10DB = const(0xA0)
# Constants for channel registers 1dB
__C1_1DB = const(0x90)
__C2_1DB = const(0x50)
__C3_1DB = const(0x10)
__C4_1DB = const(0x30)
__C5_1DB = const(0x70)
__C6_1DB = const(0xB0)
def __init__(self, port: I2C = None, address: int = 0x88) -> None:
"""
Initialize the PT2258 6-channel volume controller.
:param port: The I2C bus object connected to the PT2258.
:type port: I2C
:param address: The I2C address of the PT2258 (0x8C, 0x88, 0x84, or 0x80).
:type address: int
:raises ValueError: If the I2C object or address is not valid.
:returns: None
"""
# Check if the I2C object and address are valid.
if port is None:
raise ValueError("The I2C object 'port' is missing!")
if address not in [0x8C, 0x88, 0x84, 0x80]:
raise ValueError(
f"Invalid PT2258 device address {address}. It should be 0x8C, 0x88, 0x84, or 0x80."
)
# Store the I2C object and PT2258 address as class attributes.
self.__I2C: I2C = port
# PT2258 only accept 7 bits address.
self.__PT2258_ADDR: int = const(address >> 1)
# Define channel registers for both 10dB and 1dB settings.
self.__CHANNEL_REGISTERS: tuple[tuple[int, int], ...] = (
(self.__C1_10DB, self.__C1_1DB), # channel 1 (10dB, 1dB)
(self.__C2_10DB, self.__C2_1DB), # channel 2 (10dB, 1dB)
(self.__C3_10DB, self.__C3_1DB), # channel 3 (10dB, 1dB)
(self.__C4_10DB, self.__C4_1DB), # channel 4 (10dB, 1dB)
(self.__C5_10DB, self.__C5_1DB), # channel 5 (10dB, 1dB)
(self.__C6_10DB, self.__C6_1DB), # channel 6 (10dB, 1dB)
)
# Initialize the last_ack variable to keep track of the last acknowledgment.
self.__last_ack: int = 0
# Initialize the PT2258 by calling the "initialize_pt2258()" method.
self.__last_ack = self.__initialize_pt2258()
if self.__last_ack != 1:
raise RuntimeError(
"Failed to initialize PT2258! Please double check the I2C connection's."
)
def __write_pt2258(self, write_data: int) -> int:
"""
Write an instruction to the PT2258 via I2C.
:param write_data: The instruction data to be written to PT2258.
:type write_data: int
:raises RuntimeError: If communication with PT2258 fails.
:return: acknowledgment from slave (PT2258)
"""
try:
# Try to write the instruction to the PT2258 via I2C.
ack: int = self.__I2C.writeto(self.__PT2258_ADDR, bytes([write_data]))
except OSError as error:
# Handle communication errors and raise a RuntimeError.
if error.args[0] == 5:
raise RuntimeError(
"Communication error with the PT2258 during the operation!"
)
else:
raise RuntimeError(
f"Communication error with the PT2258! Error message: {error}"
)
return ack
def __initialize_pt2258(self) -> int:
"""
Initialize the PT2258 6-channel volume controller IC.
After power is turned on, PT2258 needs to wait for at least 200ms to ensure stability.
If the waiting time period is less than 200ms, I2C control may fail.
In order to ensure exact operation under any operating voltage,
it is recommended to clear the register "C0H" as the first step of initialization.
:raises OSError: If the PT2258 device is not present on the I2C bus.
:return: acknowledgment from slave (PT2258)
"""
# Wait for at least 300ms for stability after power-on.
utime.sleep_ms(300)
# Check if the PT2258 device is present on the I2C bus.
if self.__PT2258_ADDR not in self.__I2C.scan():
raise OSError("PT2258 not found on the I2C bus.")
# Clear the specified register to initialize the PT2258.
self.__last_ack = self.__write_pt2258(self.__CLEAR_REGISTER)
return self.__last_ack
def master_volume(self, volume: int = 0) -> int:
"""
Set the master volume.
:param volume: The desired master volume level (0 to 79).
:type volume: int
:raises ValueError: If the provided volume is outside the range 0 to 79.
:return: acknowledgment from slave (PT2258)
"""
# Validate the volume input.
if not 0 <= volume <= 79:
raise ValueError("The master volume should be within the range of 0 to 79.")
# Calculate attenuation values for 10dB and 1dB settings.
att_10db, att_1db = divmod(79 - volume, 10)
# Send attenuation settings to the PT2258, first 10dB and then 1dB.
self.__last_ack = self.__write_pt2258(self.__MASTER_VOLUME_10DB | att_10db)
if self.__last_ack:
self.__last_ack = self.__write_pt2258(self.__MASTER_VOLUME_1DB | att_1db)
return self.__last_ack
def channel_volume(self, channel: int, volume: int = 0) -> int:
"""
Set the volume level for a specific channel.
:param channel: The index of the channel (0 to 5).
:type channel: int
:param volume: The desired volume level for the channel (0 to 79).
:type volume: int
:raises ValueError: If the provided channel or volume is outside the valid range.
:return: acknowledgment from slave (PT2258)
"""
# Validate the channel and volume inputs.
if not 0 <= volume <= 79:
raise ValueError("The volume should be within the range of 0 to 79.")
if not 0 <= channel <= 5:
raise ValueError(
"Invalid channel index. Channels should be within the range of 0 to 5."
)
# Get the 10dB and 1dB channel registers for the specified channel.
channel_10db, channel_1db = self.__CHANNEL_REGISTERS[channel]
# Calculate attenuation values for 10dB and 1dB settings.
att_10db, att_1db = divmod(79 - volume, 10)
# Send attenuation settings to the PT2258, first 10dB and then 1dB.
self.__last_ack = self.__write_pt2258(channel_10db | att_10db)
if self.__last_ack:
self.__last_ack = self.__write_pt2258(channel_1db | att_1db)
return self.__last_ack
def mute(self, status: bool = False) -> int:
"""
Enable or disable the mute functionality.
:param status: If True, mute is enabled. If False, mute is disabled.
:type status: bool
:raises ValueError: If the provided status is not a boolean value.
:return: acknowledgment from slave (PT2258)
"""
# Validate the mute status input.
if not isinstance(status, bool):
raise ValueError(
"Invalid mute status value. It should be a boolean (True or False)."
)
# Send the mute status to the PT2258 and store the acknowledgment.
self.__last_ack = self.__write_pt2258(self.__MUTE_REGISTER | status)
return self.__last_ack