ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
pcm5122.cpp
Go to the documentation of this file.
1#include "pcm5122.h"
2
4#include "esphome/core/log.h"
5
6#include <cmath>
7
8namespace esphome::pcm5122 {
9
10static const char *const TAG = "pcm5122";
11
13 // Hold XSMT low (soft mute asserted) until init completes
14 if (this->enable_pin_ != nullptr) {
15 this->enable_pin_->setup();
16 this->enable_pin_->digital_write(false);
17 }
18
19 // Select page 0 and verify chip presence via I2C ACK
20 if (!this->select_page_(0)) {
21 ESP_LOGE(TAG, "Write failed");
22 this->status_set_error(LOG_STR("Write failed"));
23 this->mark_failed();
24 return;
25 }
26
27 // Reset audio modules
28 this->reg(PCM5122_REG_RESET) = PCM5122_RESET_MODULES;
29 delay(20);
30 this->reg(PCM5122_REG_RESET) = 0x00;
31
32 // Ignore clock halt detection; enable clock divider autoset
33 optional<uint8_t> err_detect = this->read_byte(PCM5122_REG_ERROR_DETECT);
34 if (!err_detect.has_value()) {
35 ESP_LOGE(TAG, "Failed to read ERROR_DETECT");
36 this->mark_failed();
37 return;
38 }
39 uint8_t err_detect_val = err_detect.value();
40 err_detect_val |= PCM5122_ERROR_DETECT_IGNORE_CLKHALT;
41 err_detect_val &= ~PCM5122_ERROR_DETECT_DISABLE_DIV_AUTOSET;
42 this->reg(PCM5122_REG_ERROR_DETECT) = err_detect_val;
43
44 // I2S format with the configured word length
45 uint8_t alen;
46 switch (this->bits_per_sample_) {
48 alen = PCM5122_AUDIO_FORMAT_ALEN_16BIT;
49 break;
51 alen = PCM5122_AUDIO_FORMAT_ALEN_24BIT;
52 break;
54 default:
55 alen = PCM5122_AUDIO_FORMAT_ALEN_32BIT;
56 break;
57 }
58 this->reg(PCM5122_REG_AUDIO_FORMAT) = PCM5122_AUDIO_FORMAT_I2S | alen;
59
60 if (!this->write_channel_mix_()) {
61 this->mark_failed();
62 return;
63 }
64
65 if (!this->write_analog_gain_()) {
66 this->mark_failed();
67 return;
68 }
69
70 // PLL reference clock: BCK
71 if (!this->select_page_(0)) {
72 ESP_LOGE(TAG, "Write failed");
73 this->mark_failed();
74 return;
75 }
76 optional<uint8_t> pll_ref = this->read_byte(PCM5122_REG_PLL_REF);
77 if (!pll_ref.has_value()) {
78 ESP_LOGE(TAG, "Failed to read PLL_REF");
79 this->mark_failed();
80 return;
81 }
82 uint8_t pll_ref_val = pll_ref.value();
83 pll_ref_val &= ~PCM5122_PLL_REF_MASK;
84 pll_ref_val |= PCM5122_PLL_REF_SOURCE_BCK;
85 this->reg(PCM5122_REG_PLL_REF) = pll_ref_val;
86
87 if (!this->set_mute_on() || !this->set_volume(this->volume_)) {
88 this->mark_failed();
89 return;
90 }
91
92 // Release XSMT (soft un-mute) now that init has completed
93 if (this->enable_pin_ != nullptr) {
94 this->enable_pin_->digital_write(true);
95 }
96}
97
99 const char *channel_mix_str;
100 switch (this->channel_mix_) {
102 channel_mix_str = "left only";
103 break;
105 channel_mix_str = "right only";
106 break;
108 channel_mix_str = "swapped";
109 break;
110 default:
111 channel_mix_str = "stereo";
112 break;
113 }
114 ESP_LOGCONFIG(TAG, "Audio DAC:");
115 LOG_I2C_DEVICE(this);
116 ESP_LOGCONFIG(TAG,
117 " Bits per sample: %u\n"
118 " Analog gain: %s\n"
119 " Channel mix: %s\n"
120 " Volume range: %.1f dB to %.1f dB\n"
121 " Muted: %s",
122 this->bits_per_sample_,
123 this->analog_gain_ == PCM5122_ANALOG_GAIN_0DB ? LOG_STR_LITERAL("0 dB") : LOG_STR_LITERAL("-6 dB"),
124 channel_mix_str, this->volume_min_db_, this->volume_max_db_, YESNO(this->is_muted_));
125 LOG_PIN(" Enable Pin: ", this->enable_pin_);
126}
127
129 this->is_muted_ = false;
130 return this->write_mute_();
131}
132
134 this->is_muted_ = true;
135 return this->write_mute_();
136}
137
138bool PCM5122::set_volume(float volume) {
139 this->volume_ = clamp<float>(volume, 0.0f, 1.0f);
140 return this->write_volume_();
141}
142
143bool PCM5122::is_muted() { return this->is_muted_; }
144
145float PCM5122::volume() { return this->volume_; }
146
147bool PCM5122::select_page_(uint8_t page) {
148 if (this->current_page_ == page)
149 return true;
150 if (!this->write_byte(PCM5122_REG_PAGE_SELECT, page)) {
151 this->current_page_ = -1;
152 return false;
153 }
154 this->current_page_ = page;
155 return true;
156}
157
159 uint8_t mute_byte = this->is_muted() ? 0x11 : 0x00;
160 if (!this->select_page_(0) || !this->write_byte(PCM5122_REG_MUTE, mute_byte)) {
161 ESP_LOGE(TAG, "Writing mute failed");
162 return false;
163 }
164 return true;
165}
166
168 // DVOL register: 0x00 = +24 dB, 0x30 = 0 dB, 0xFE = -103 dB, 0xFF = mute (-0.5 dB/step).
169 // Note: volume=0.0 maps to volume_min_db_, which is not true silence unless set to -103 dB.
170 // Use set_mute_on() for silence.
171 const uint8_t dvol_max_volume = static_cast<uint8_t>(lroundf(0x30 - this->volume_max_db_ * 2.0f));
172 const uint8_t dvol_min_volume = static_cast<uint8_t>(lroundf(0x30 - this->volume_min_db_ * 2.0f));
173
174 const uint8_t volume_byte =
175 dvol_max_volume + static_cast<uint8_t>(lroundf((1.0f - this->volume_) * (dvol_min_volume - dvol_max_volume)));
176
177 ESP_LOGV(TAG, "Setting volume to 0x%.2x", volume_byte);
178
179 if (!this->select_page_(0) || !this->write_byte(PCM5122_REG_DVOL_LEFT, volume_byte) ||
180 !this->write_byte(PCM5122_REG_DVOL_RIGHT, volume_byte)) {
181 ESP_LOGE(TAG, "Writing volume failed");
182 return false;
183 }
184 return true;
185}
186
188 uint8_t gain_byte = this->analog_gain_;
189 if (!this->select_page_(1) || !this->write_byte(PCM5122_REG_ANALOG_GAIN, gain_byte)) {
190 ESP_LOGE(TAG, "Writing analog gain failed");
191 return false;
192 }
193 return true;
194}
195
197 uint8_t channel_mix_byte = this->channel_mix_;
198 if (!this->select_page_(0) || !this->write_byte(PCM5122_REG_DAC_DATA_PATH, channel_mix_byte)) {
199 ESP_LOGE(TAG, "Writing channel mix failed");
200 return false;
201 }
202 return true;
203}
204
205bool PCM5122::set_standby(bool enable) {
206 bool prev_standby = this->standby_;
207 this->standby_ = enable;
208 if (!this->write_power_control_()) {
209 this->standby_ = prev_standby;
210 return false;
211 }
212 return true;
213}
214
215bool PCM5122::set_powerdown(bool enable) {
216 bool prev_powerdown = this->powerdown_;
217 this->powerdown_ = enable;
218 if (!this->write_power_control_()) {
219 this->powerdown_ = prev_powerdown;
220 return false;
221 }
222 return true;
223}
224
226 uint8_t power_byte =
227 (this->standby_ ? PCM5122_POWER_CONTROL_RQST : 0) | (this->powerdown_ ? PCM5122_POWER_CONTROL_RQPD : 0);
228 if (!this->select_page_(0) || !this->write_byte(PCM5122_REG_POWER_CONTROL, power_byte)) {
229 ESP_LOGE(TAG, "Writing power control failed");
230 return false;
231 }
232 return true;
233}
234
235} // namespace esphome::pcm5122
void mark_failed()
Mark this component as failed.
virtual void setup()=0
virtual void digital_write(bool value)=0
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
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:152
bool set_standby(bool enable)
Definition pcm5122.cpp:205
void setup() override
Definition pcm5122.cpp:12
bool set_volume(float volume) override
Definition pcm5122.cpp:138
bool is_muted() override
Definition pcm5122.cpp:143
bool set_mute_on() override
Definition pcm5122.cpp:133
float volume() override
Definition pcm5122.cpp:145
bool set_mute_off() override
Definition pcm5122.cpp:128
void dump_config() override
Definition pcm5122.cpp:98
bool select_page_(uint8_t page)
Definition pcm5122.cpp:147
bool set_powerdown(bool enable)
Definition pcm5122.cpp:215
PCM5122AnalogGain analog_gain_
Definition pcm5122.h:113
PCM5122BitsPerSample bits_per_sample_
Definition pcm5122.h:112
PCM5122ChannelMix channel_mix_
Definition pcm5122.h:114
@ PCM5122_ANALOG_GAIN_0DB
Definition pcm5122.h:59
@ PCM5122_CHANNEL_MIX_SWAPPED
Definition pcm5122.h:68
@ PCM5122_CHANNEL_MIX_LEFT_ONLY
Definition pcm5122.h:66
@ PCM5122_CHANNEL_MIX_RIGHT_ONLY
Definition pcm5122.h:67
@ PCM5122_BITS_PER_SAMPLE_16
Definition pcm5122.h:53
@ PCM5122_BITS_PER_SAMPLE_32
Definition pcm5122.h:55
@ PCM5122_BITS_PER_SAMPLE_24
Definition pcm5122.h:54
void HOT delay(uint32_t ms)
Definition hal.cpp:85