ESPHome 2026.5.1
Loading...
Searching...
No Matches
es8388.cpp
Go to the documentation of this file.
1#include "es8388.h"
2
3#include <cinttypes>
4#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7namespace esphome::es8388 {
8
9static const char *const TAG = "es8388";
10
11// Mark the component as failed; use only in setup
12#define ES8388_ERROR_FAILED(func) \
13 if (!(func)) { \
14 this->mark_failed(); \
15 return; \
16 }
17
18// Return false; use outside of setup
19#define ES8388_ERROR_CHECK(func) \
20 if (!(func)) { \
21 return false; \
22 }
23
24void ES8388::setup() {
25 // mute DAC
26 this->set_mute_state_(true);
27
28 // I2S worker mode
29 ES8388_ERROR_FAILED(this->write_byte(ES8388_MASTERMODE, 0x00));
30
31 /* Chip Control and Power Management */
32 ES8388_ERROR_FAILED(this->write_byte(ES8388_CONTROL2, 0x50));
33 // normal all and power up all
34 ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0x00));
35
36 // vmidsel/500k
37 // EnRef=0,Play&Record Mode,(0x17-both of mic&play)
38 ES8388_ERROR_FAILED(this->write_byte(ES8388_CONTROL1, 0x12));
39
40 // i2s 16 bits
41 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL1, 0x18));
42 // sample freq 256
43 // DACFsMode,SINGLE SPEED; DACFsRatio,256
44 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL2, 0x02));
45 // 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2
46 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL16, 0x00));
47 // only left DAC to left mixer enable 0db
48 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL17, 0x90));
49 // only right DAC to right mixer enable 0db
50 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL20, 0x90));
51 // set internal ADC and DAC use the same LRCK clock, ADC LRCK as internal LRCK
52 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL21, 0x80));
53 // vroi=0 - 1.5k VREF to analog output resistance (default)
54 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL23, 0x00));
55
56 // power down adc and line in
57 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCPOWER, 0xFF));
58
59 //@nightdav
60 ES8388_ERROR_FAILED(
61 this->write_byte(ES8388_ADCCONTROL1, 0x00)); // +21dB : recommended value for ALC & voice recording
62
63 // set to Mono Right
64 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL3, 0x02));
65
66 // I2S 16 Bits length and I2S serial audio data format
67 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL4, 0x0d));
68 // ADCFsMode,singel SPEED,RATIO=256
69 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL5, 0x02));
70
71 // ADC Volume
72 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL8, 0x00));
73 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL9, 0x00));
74
75 //@nightDav
76 // ALC Config (as recommended by ES8388 user guide for voice recording)
77
78 // Reg 0x12 = 0xe2 (ALC enable, PGA Max. Gain=23.5dB, Min. Gain=0dB)
79 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL10, 0xe2));
80
81 // Reg 0x13 = 0xa0 (ALC Target=-1.5dB, ALC Hold time =0 mS)
82 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL11, 0xa0));
83 // Reg 0x14 = 0x12(Decay time =820uS , Attack time = 416 uS)
84 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL12, 0x12));
85
86 // Reg 0x15 = 0x06(ALC mode)
87 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL13, 0x06));
88
89 // Reg 0x16 = 0xc3(nose gate = -40.5dB, NGG = 0x01(mute ADC))
90 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL14, 0xc3));
91
92 // Power on ADC
93 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL21, 0x80));
94
95 // Start state machine
96 ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0xF0));
97 delay(1);
98 ES8388_ERROR_FAILED(this->write_byte(ES8388_CHIPPOWER, 0x00));
99
100 // DAC volume max
101 // Set initial volume
102 // this->set_volume(0.75); // 0.75 = 0xBF = 0dB
103
104 this->set_mute_state_(false);
105
106 // unmute ADC with fade in
107 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCCONTROL7, 0x60));
108 // unmute DAC with fade in
109 ES8388_ERROR_FAILED(this->write_byte(ES8388_DACCONTROL3, 0x20));
110
111 // Power on ADC, Enable LIN&RIN, Power off MICBIAS, set int1lp to low power mode
112 ES8388_ERROR_FAILED(this->write_byte(ES8388_ADCPOWER, 0x09));
113
114#ifdef USE_SELECT
115 if (this->dac_output_select_ != nullptr) {
116 auto dac_power = this->get_dac_power();
117 if (dac_power.has_value()) {
118 if (this->dac_output_select_->has_index(dac_power.value())) {
119 this->dac_output_select_->publish_state(dac_power.value());
120 } else {
121 ESP_LOGW(TAG, "Unknown DAC output power value: %d", dac_power.value());
122 }
123 }
124 }
125 if (this->adc_input_mic_select_ != nullptr) {
126 auto mic_input = this->get_mic_input();
127 if (mic_input.has_value()) {
128 if (this->adc_input_mic_select_->has_index(mic_input.value())) {
129 this->adc_input_mic_select_->publish_state(mic_input.value());
130 } else {
131 ESP_LOGW(TAG, "Unknown ADC input mic value: %d", mic_input.value());
132 }
133 }
134 }
135#endif
136}
137
138void ES8388::dump_config() {
139 ESP_LOGCONFIG(TAG, "ES8388 Audio Codec:");
140 LOG_I2C_DEVICE(this);
141#ifdef USE_SELECT
142 LOG_SELECT(" ", "DacOutputSelect", this->dac_output_select_);
143 LOG_SELECT(" ", "ADCInputMicSelect", this->adc_input_mic_select_);
144#endif
145
146 if (this->is_failed()) {
147 ESP_LOGCONFIG(TAG, " Failed to initialize");
148 return;
149 }
150}
151
152bool ES8388::set_volume(float volume) {
153 volume = clamp(volume, 0.0f, 1.0f);
154 uint8_t value = remap<uint8_t, float>(volume, 0.0f, 1.0f, 192, 0);
155 ESP_LOGD(TAG, "Setting ES8388_DACCONTROL4 / ES8388_DACCONTROL5 to 0x%02X (volume: %f)", value, volume);
156 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL4, value));
157 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL5, value));
158
159 return true;
160}
161
162float ES8388::volume() {
163 uint8_t value;
164 ES8388_ERROR_CHECK(this->read_byte(ES8388_DACCONTROL4, &value));
165 return remap<float, uint8_t>(value, 192, 0, 0.0f, 1.0f);
166}
167
168bool ES8388::set_mute_state_(bool mute_state) {
169 uint8_t value = 0;
170
171 this->is_muted_ = mute_state;
172
173 ES8388_ERROR_CHECK(this->read_byte(ES8388_DACCONTROL3, &value));
174 ESP_LOGV(TAG, "Read ES8388_DACCONTROL3: 0x%02X", value);
175
176 if (mute_state) {
177 value = 0x3C;
178 }
179
180 ESP_LOGV(TAG, "Setting ES8388_DACCONTROL3 to 0x%02X (muted: %s)", value, YESNO(mute_state));
181 return this->write_byte(ES8388_DACCONTROL3, value);
182}
183
184// Set dac power output
185bool ES8388::set_dac_output(DacOutputLine line) {
186 uint8_t reg_out1 = 0;
187 uint8_t reg_out2 = 0;
188 uint8_t dac_power = 0;
189
190 // 0x00: -30dB , 0x1E: 0dB
191 switch (line) {
192 case DAC_OUTPUT_LINE1:
193 reg_out1 = 0x1E;
194 dac_power = ES8388_DAC_OUTPUT_LOUT1_ROUT1;
195 break;
196 case DAC_OUTPUT_LINE2:
197 reg_out2 = 0x1E;
198 dac_power = ES8388_DAC_OUTPUT_LOUT2_ROUT2;
199 break;
200 case DAC_OUTPUT_BOTH:
201 reg_out1 = 0x1E;
202 reg_out2 = 0x1E;
203 dac_power = ES8388_DAC_OUTPUT_BOTH;
204 break;
205 default:
206 ESP_LOGE(TAG, "Unknown DAC output line: %d", line);
207 return false;
208 };
209
210 ESP_LOGV(TAG,
211 "DAC output config:\n"
212 " DACPOWER: 0x%02X\n"
213 " DACCONTROL24/25: 0x%02X\n"
214 " DACCONTROL26/27: 0x%02X",
215 dac_power, reg_out1, reg_out2);
216
217 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL24, reg_out1)); // LOUT1VOL
218 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL25, reg_out1)); // ROUT1VOL
219 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL26, reg_out2)); // LOUT2VOL
220 ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL27, reg_out2)); // ROUT1VOL
221
222 return this->write_byte(ES8388_DACPOWER, dac_power);
223}
224
225optional<DacOutputLine> ES8388::get_dac_power() {
226 uint8_t dac_power;
227 if (!this->read_byte(ES8388_DACPOWER, &dac_power)) {
228 this->status_momentary_warning("dacpower_read");
229 return {};
230 }
231 switch (dac_power) {
232 case ES8388_DAC_OUTPUT_LOUT1_ROUT1:
233 return DAC_OUTPUT_LINE1;
234 case ES8388_DAC_OUTPUT_LOUT2_ROUT2:
235 return DAC_OUTPUT_LINE2;
236 case ES8388_DAC_OUTPUT_BOTH:
237 return DAC_OUTPUT_BOTH;
238 default:
239 return {};
240 }
241}
242
243// Set ADC input MIC
244bool ES8388::set_adc_input_mic(AdcInputMicLine line) {
245 uint8_t mic_input = 0;
246
247 switch (line) {
249 mic_input = ES8388_ADC_INPUT_LINPUT1_RINPUT1;
250 break;
252 mic_input = ES8388_ADC_INPUT_LINPUT2_RINPUT2;
253 break;
255 mic_input = ES8388_ADC_INPUT_DIFFERENCE;
256 break;
257 default:
258 ESP_LOGE(TAG, "Unknown ADC input mic line: %d", line);
259 return false;
260 }
261
262 ESP_LOGV(TAG, "Setting ES8388_ADCCONTROL2 to 0x%02X", mic_input);
263 ES8388_ERROR_CHECK(this->write_byte(ES8388_ADCCONTROL2, mic_input));
264
265 return true;
266}
267
268optional<AdcInputMicLine> ES8388::get_mic_input() {
269 uint8_t mic_input;
270 if (!this->read_byte(ES8388_ADCCONTROL2, &mic_input)) {
271 this->status_momentary_warning("adccontrol2_read");
272 return {};
273 }
274 switch (mic_input) {
275 case ES8388_ADC_INPUT_LINPUT1_RINPUT1:
276 return ADC_INPUT_MIC_LINE1;
277 case ES8388_ADC_INPUT_LINPUT2_RINPUT2:
278 return ADC_INPUT_MIC_LINE2;
279 case ES8388_ADC_INPUT_DIFFERENCE:
281 default:
282 return {};
283 };
284}
285
286} // namespace esphome::es8388
virtual void setup()
Where the component's initialization should happen.
Definition component.cpp:89
bool is_failed() const
Definition component.h:284
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
bool set_mute_state_(bool mute_state)
Mutes or unmutes the DAC audio out.
Definition es8388.cpp:168
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:265
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
@ ADC_INPUT_MIC_LINE1
Definition es8388.h:23
@ ADC_INPUT_MIC_DIFFERENCE
Definition es8388.h:25
@ ADC_INPUT_MIC_LINE2
Definition es8388.h:24
const char int line
Definition log.h:74
void HOT delay(uint32_t ms)
Definition hal.cpp:85
T remap(U value, U min, U max, T min_out, T max_out)
Remap value from the range (min, max) to (min_out, max_out).
Definition helpers.h:765