Skip to content

Commit db5eec4

Browse files
authored
add Louder-ESP32 audio board (#59)
* add Louder-ESP32 * file name type * Delete raw/esp32/Louder-ESP32/tas5805.be
1 parent 42ca890 commit db5eec4

File tree

3 files changed

+386
-0
lines changed

3 files changed

+386
-0
lines changed

raw/esp32/Louder-ESP32/autoexec.be

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# load DAC driver
2+
load("tas5805m.be")

raw/esp32/Louder-ESP32/init.bat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"NAME":"Louder-ESP32","GPIO":[1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,0,640,7776,1,0,7840,7808,608,0,0,0,0,1,0,0,1,1,0,0,1],"FLAG":0,"BASE":1}
2+
Module 0
3+

raw/esp32/Louder-ESP32/tas5805m.be

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
# TAS5805M DAC Driver for Berry/Tasmota
2+
# Based on: https://github.com/sonocotta/esp32-tas5805m-dac
3+
4+
class TAS5805M
5+
# Louder-ESP32 I2S DAC Driver for TAS5805M
6+
static i2c_addr = 0x2D # Default I2C address
7+
static pdn_pin = 33 # Power Down Pin (GPIO 33)
8+
static fault_pin = 34 # Fault Pin (GPIO 34, optional)
9+
var i2c
10+
11+
# Register definitions
12+
static PAGE_REG = 0x00
13+
static RESET_REG = 0x01
14+
static DEVICE_CTRL_1 = 0x02
15+
static DEVICE_CTRL_2 = 0x03
16+
static SIG_CH_CTRL = 0x28
17+
static SAP_CTRL1 = 0x33
18+
static SAP_CTRL2 = 0x34
19+
static SAP_CTRL3 = 0x35
20+
static FS_MON = 0x37
21+
static BCK_MON = 0x38
22+
static CLKDET_STATUS = 0x39
23+
static DIG_VOL_CTRL = 0x4C
24+
static DIG_VOL_CTRL2 = 0x4D
25+
static DIG_VOL_CTRL3 = 0x4E
26+
static AUTO_MUTE_CTRL = 0x50
27+
static AUTO_MUTE_TIME = 0x51
28+
static ANA_CTRL = 0x53
29+
static AGAIN_CTRL = 0x54
30+
static BQ_WR_CTRL1 = 0x5C
31+
static ADR_PIN_CTRL = 0x60
32+
static ADR_PIN_CONFIG = 0x61
33+
static DSP_MISC = 0x66
34+
static DIE_ID = 0x67
35+
static POWER_STATE = 0x68
36+
static AUTOMUTE_STATE = 0x69
37+
static PHASE_CTRL = 0x6A
38+
static SS_CTRL0 = 0x6B
39+
static SS_CTRL1 = 0x6C
40+
static SS_CTRL2 = 0x6D
41+
static SS_CTRL3 = 0x6E
42+
static SS_CTRL4 = 0x6F
43+
static CHAN_FAULT = 0x70
44+
static GLOBAL_FAULT1 = 0x71
45+
static GLOBAL_FAULT2 = 0x72
46+
static OT_WARNING = 0x73
47+
static PIN_CONTROL1 = 0x74
48+
static PIN_CONTROL2 = 0x75
49+
static MISC_CONTROL = 0x76
50+
static FAULT_CLEAR = 0x78
51+
52+
static CFG_META_DELAY = 254
53+
54+
# Minimal register configuration sequence for TAS5805M
55+
# could be loaded from file or defined here
56+
# Use CFG_META_DELAY for delays (value = ms)
57+
static tas5805m_registers = bytes(
58+
# RESET
59+
"0000"
60+
"7f00"
61+
"0302"
62+
"0111"
63+
"0302"
64+
"fe0a" # CFG_META_DELAY
65+
"0300"
66+
"4601"
67+
"0302"
68+
"610b"
69+
"6001"
70+
"7d11"
71+
"7eff"
72+
"0001"
73+
"5105"
74+
# Register Tuning
75+
"0000"
76+
"7f00"
77+
"0200"
78+
"3000"
79+
"4c30"
80+
"5300"
81+
"5400"
82+
"0303"
83+
"7880")
84+
85+
# Constructor
86+
def init()
87+
self.i2c = tasmota.wire_scan(self.i2c_addr)
88+
if self.i2c == nil
89+
log("TAS5805M: I2C device not found at address 0x{self.i2c_addr:02X}",1)
90+
return false
91+
end
92+
93+
# Configure PDN pin as output
94+
gpio.pin_mode(self.pdn_pin, gpio.OUTPUT)
95+
96+
# Configure Fault pin as input (if defined)
97+
if self.fault_pin >= 0
98+
gpio.pin_mode(self.fault_pin, gpio.INPUT_PULLUP)
99+
end
100+
101+
# Initialize chip
102+
log(f"TAS5805M: Initializing I2C device at address 0x{self.i2c_addr:02X}")
103+
self.hardware_reset()
104+
log("TAS5805M: Device reset complete")
105+
self.device_init()
106+
log("TAS5805M: Device initialized successfully")
107+
self.transmit_registers(self.tas5805m_registers, size(self.tas5805m_registers))
108+
tasmota.add_driver(self)
109+
log("TAS5805M: Driver added successfully")
110+
self.add_commands()
111+
log("TAS5805M: Commands i2smute and i2svol added successfully")
112+
end
113+
114+
115+
def audio(cmd, idx, payload, raw)
116+
if cmd == "power"
117+
self.set_power(idx)
118+
log(f"TAS5805M: Power {idx}")
119+
end
120+
end
121+
122+
# Hardware reset
123+
def hardware_reset()
124+
gpio.digital_write(self.pdn_pin, 0)
125+
tasmota.delay(20)
126+
gpio.digital_write(self.pdn_pin, 1)
127+
tasmota.delay(200)
128+
end
129+
130+
# Write I2C register
131+
def write_register(reg, value)
132+
return self.i2c.write(self.i2c_addr, reg, value, 1)
133+
end
134+
135+
# Read I2C register
136+
def read_register(reg)
137+
return self.i2c.read(self.i2c_addr, reg, 1)
138+
end
139+
140+
def transmit_registers(conf_buf)
141+
var i = 0
142+
var ret = true
143+
var sz = size(conf_buf)
144+
log("tas5805m_transmit_registers: enter",4)
145+
while i < sz
146+
var reg = conf_buf[i]
147+
var val = conf_buf[i + 1]
148+
if reg == self.CFG_META_DELAY
149+
# Delay in milliseconds
150+
tasmota.delay(val)
151+
else
152+
# Write register
153+
ret = self.write_register(reg, val)
154+
log(f"\t0x{reg:02X} <- 0x{val:02X}",4)
155+
end
156+
i += 2
157+
end
158+
if !ret
159+
log("tas5805m_transmit_registers: Failed to load configuration to tas5805m")
160+
return false
161+
end
162+
log(f"tas5805m_transmit_registers: leave; wrote {sz} registers",4)
163+
return true
164+
end
165+
166+
# Device initialization
167+
def device_init()
168+
# Perform reset
169+
self.write_register(self.RESET_REG, 0x01)
170+
tasmota.delay(100)
171+
172+
# Configure device control
173+
self.write_register(self.DEVICE_CTRL_1, 0x02)
174+
self.write_register(self.DEVICE_CTRL_2, 0x03)
175+
176+
# Signal channel control
177+
self.write_register(self.SIG_CH_CTRL, 0x00)
178+
179+
# Configure SAP control
180+
self.write_register(self.SAP_CTRL1, 0x00) # I2S standard
181+
self.write_register(self.SAP_CTRL2, 0x10) # 16-bit
182+
self.write_register(self.SAP_CTRL3, 0x00)
183+
184+
# Disable auto-mute
185+
self.write_register(self.AUTO_MUTE_CTRL, 0x00)
186+
187+
# Analog control
188+
self.write_register(self.ANA_CTRL, 0x00)
189+
190+
# Power-up
191+
self.write_register(self.DEVICE_CTRL_2, 0x03)
192+
193+
tasmota.delay(100)
194+
end
195+
196+
# Set volume (0-255)
197+
def set_volume(volume)
198+
if volume > 255 volume = 255 end
199+
if volume < 0 volume = 0 end
200+
201+
# Convert volume to dB (-103.5dB to 24dB)
202+
# 0 = -103.5dB, 255 = 24dB
203+
var vol_db = volume
204+
205+
self.write_register(self.DIG_VOL_CTRL, vol_db)
206+
self.write_register(self.DIG_VOL_CTRL2, vol_db)
207+
end
208+
209+
# Read volume
210+
def get_volume()
211+
return self.read_register(self.DIG_VOL_CTRL)
212+
end
213+
214+
# Set the volume as percentage [0..124], where 100 is 0 dB, 0 is mute
215+
def set_volume_pct(vol)
216+
# Clamp to allowed range
217+
if vol < 0
218+
vol = 0
219+
end
220+
if vol > 124
221+
vol = 124
222+
end
223+
224+
# Convert to register value: 0 = mute, 100 = 0 dB, 124 = max
225+
# Formula: reg = vol == 0 ? 255 : (-2 * vol + 248)
226+
var reg_val = vol == 0 ? 255 : (-2 * vol + 248)
227+
self.set_volume(reg_val)
228+
return reg_val
229+
end
230+
231+
# Get the volume as percentage [0..124], where 100 is 0 dB, 0 is mute
232+
def get_volume_pct()
233+
var reg_val = self.get_volume()
234+
# Formula: pct = reg_val >= 248 ? 0 : (248 - reg_val) / 2
235+
if reg_val >= 248
236+
return 0
237+
else
238+
return (248 - reg_val) / 2
239+
end
240+
end
241+
242+
# Set mute
243+
def set_mute(mute)
244+
var ctrl = self.read_register(self.DEVICE_CTRL_2)
245+
if mute
246+
ctrl = ctrl | 0x08 # Set mute bit
247+
else
248+
ctrl = ctrl & 0xF7 # Clear mute bit
249+
end
250+
self.write_register(self.DEVICE_CTRL_2, ctrl)
251+
end
252+
253+
# Read mute status
254+
def get_mute()
255+
var ctrl = self.read_register(self.DEVICE_CTRL_2)
256+
return (ctrl & 0x08) != 0
257+
end
258+
259+
# Set power state
260+
def set_power(power_on)
261+
if power_on
262+
self.write_register(self.DEVICE_CTRL_2, 0x03) # Power up
263+
else
264+
self.write_register(self.DEVICE_CTRL_2, 0x01) # Power down
265+
end
266+
end
267+
268+
# Read power state
269+
def get_power_state()
270+
return self.read_register(self.POWER_STATE)
271+
end
272+
273+
# Read sample rate
274+
def get_sample_rate()
275+
var fs_mon = self.read_register(self.FS_MON)
276+
var rates = [8000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000]
277+
278+
if fs_mon < size(rates)
279+
return rates[fs_mon]
280+
end
281+
return 0
282+
end
283+
284+
# Read BCK ratio
285+
def get_bck_ratio()
286+
return self.read_register(self.BCK_MON)
287+
end
288+
289+
# Read fault status
290+
def get_fault_status()
291+
return {
292+
'Channel': self.read_register(self.CHAN_FAULT),
293+
'Global1': self.read_register(self.GLOBAL_FAULT1),
294+
'Global2': self.read_register(self.GLOBAL_FAULT2)
295+
}
296+
end
297+
298+
# Clear fault status
299+
def clear_fault()
300+
self.write_register(self.FAULT_CLEAR, 0x80)
301+
tasmota.delay(10)
302+
self.write_register(self.FAULT_CLEAR, 0x00)
303+
end
304+
305+
# Set analog gain
306+
def set_analog_gain(gain)
307+
# Gain: 0 = 19.2dBV, 1 = 20.7dBV, 2 = 22.2dBV, 3 = 23.7dBV
308+
if gain > 3 gain = 3 end
309+
if gain < 0 gain = 0 end
310+
311+
var ana_ctrl = self.read_register(self.ANA_CTRL)
312+
ana_ctrl = (ana_ctrl & 0xFC) | gain
313+
self.write_register(self.ANA_CTRL, ana_ctrl)
314+
end
315+
316+
# Read analog gain
317+
def get_analog_gain()
318+
var ana_ctrl = self.read_register(self.ANA_CTRL)
319+
return ana_ctrl & 0x03
320+
end
321+
322+
# Read device ID
323+
def get_device_id()
324+
return self.read_register(self.DIE_ID)
325+
end
326+
327+
# Read auto-mute state
328+
def get_auto_mute_state()
329+
return self.read_register(self.AUTOMUTE_STATE)
330+
end
331+
332+
# Read fault pin status (if configured)
333+
def get_fault_pin()
334+
if self.fault_pin >= 0
335+
return gpio.digital_read(self.fault_pin)
336+
end
337+
return nil
338+
end
339+
340+
def status()
341+
var result = {"Status": {
342+
"DeviceID": self.get_device_id(),
343+
"PowerState": self.get_power_state(),
344+
"Volume": self.get_volume(),
345+
"Mute": self.get_mute(),
346+
"SampleRate": self.get_sample_rate(),
347+
"BCKRatio": self.get_bck_ratio(),
348+
"AnalogGain": self.get_analog_gain(),
349+
"AutoMuteState": self.get_auto_mute_state(),
350+
"FaultStatus": self.get_fault_status()
351+
}}
352+
if self.fault_pin >= 0
353+
result["FaultPin"] = self.get_fault_pin()
354+
end
355+
log(f"TAS5805M = {result}")
356+
end
357+
358+
def i2smute(cmd, idx, payload, raw)
359+
log(f"TAS5805M: i2smute command received with raw={raw}",3)
360+
if raw != nil
361+
self.set_mute(raw != 0) # idx is 0 for unmute
362+
end
363+
tasmota.resp_cmnd(format("{'i2smute':%i}}",self.get_mute()))
364+
end
365+
366+
def i2svol(cmd, idx, payload, raw)
367+
if raw
368+
log(f"TAS5805M: set new volume: {raw}")
369+
self.set_volume_pct(raw)
370+
end
371+
tasmota.resp_cmnd(format("{'i2svol':%i}}",self.get_volume_pct()))
372+
end
373+
374+
def add_commands()
375+
tasmota.add_cmd('i2smute', /c,i,p,r->self.i2smute(c,i,p,r))
376+
tasmota.add_cmd('i2svol', /c,i,p,r->self.i2svol(c,i,p,r))
377+
end
378+
end
379+
380+
# Execute at Tasmota startup
381+
dac = TAS5805M()

0 commit comments

Comments
 (0)