ESPHome  2024.7.2
adc_sensor.cpp
Go to the documentation of this file.
1 #include "adc_sensor.h"
2 #include "esphome/core/helpers.h"
3 #include "esphome/core/log.h"
4 
5 #ifdef USE_ESP8266
6 #ifdef USE_ADC_SENSOR_VCC
7 #include <Esp.h>
8 ADC_MODE(ADC_VCC)
9 #else
10 #include <Arduino.h>
11 #endif
12 #endif
13 
14 #ifdef USE_RP2040
15 #ifdef CYW43_USES_VSYS_PIN
16 #include "pico/cyw43_arch.h"
17 #endif
18 #include <hardware/adc.h>
19 #endif
20 
21 namespace esphome {
22 namespace adc {
23 
24 static const char *const TAG = "adc";
25 
26 // 13-bit for S2, 12-bit for all other ESP32 variants
27 #ifdef USE_ESP32
28 static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
29 
30 #ifndef SOC_ADC_RTC_MAX_BITWIDTH
31 #if USE_ESP32_VARIANT_ESP32S2
32 static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
33 #else
34 static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
35 #endif
36 #endif
37 
38 static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit)
39 static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
40 #endif
41 
42 #ifdef USE_RP2040
43 extern "C"
44 #endif
45  void
47  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
48 #if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
49  this->pin_->setup();
50 #endif
51 
52 #ifdef USE_ESP32
53  if (this->channel1_ != ADC1_CHANNEL_MAX) {
54  adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
55  if (!this->autorange_) {
56  adc1_config_channel_atten(this->channel1_, this->attenuation_);
57  }
58  } else if (this->channel2_ != ADC2_CHANNEL_MAX) {
59  if (!this->autorange_) {
60  adc2_config_channel_atten(this->channel2_, this->attenuation_);
61  }
62  }
63 
64  // load characteristics for each attenuation
65  for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
66  auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
67  auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
68  1100, // default vref
69  &this->cal_characteristics_[i]);
70  switch (cal_value) {
71  case ESP_ADC_CAL_VAL_EFUSE_VREF:
72  ESP_LOGV(TAG, "Using eFuse Vref for calibration");
73  break;
74  case ESP_ADC_CAL_VAL_EFUSE_TP:
75  ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
76  break;
77  case ESP_ADC_CAL_VAL_DEFAULT_VREF:
78  default:
79  break;
80  }
81  }
82 
83 #endif // USE_ESP32
84 
85 #ifdef USE_RP2040
86  static bool initialized = false;
87  if (!initialized) {
88  adc_init();
89  initialized = true;
90  }
91 #endif
92 
93  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
94 }
95 
97  LOG_SENSOR("", "ADC Sensor", this);
98 #if defined(USE_ESP8266) || defined(USE_LIBRETINY)
99 #ifdef USE_ADC_SENSOR_VCC
100  ESP_LOGCONFIG(TAG, " Pin: VCC");
101 #else
102  LOG_PIN(" Pin: ", this->pin_);
103 #endif
104 #endif // USE_ESP8266 || USE_LIBRETINY
105 
106 #ifdef USE_ESP32
107  LOG_PIN(" Pin: ", this->pin_);
108  if (this->autorange_) {
109  ESP_LOGCONFIG(TAG, " Attenuation: auto");
110  } else {
111  switch (this->attenuation_) {
112  case ADC_ATTEN_DB_0:
113  ESP_LOGCONFIG(TAG, " Attenuation: 0db");
114  break;
115  case ADC_ATTEN_DB_2_5:
116  ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
117  break;
118  case ADC_ATTEN_DB_6:
119  ESP_LOGCONFIG(TAG, " Attenuation: 6db");
120  break;
121  case ADC_ATTEN_DB_12_COMPAT:
122  ESP_LOGCONFIG(TAG, " Attenuation: 12db");
123  break;
124  default: // This is to satisfy the unused ADC_ATTEN_MAX
125  break;
126  }
127  }
128 #endif // USE_ESP32
129 
130 #ifdef USE_RP2040
131  if (this->is_temperature_) {
132  ESP_LOGCONFIG(TAG, " Pin: Temperature");
133  } else {
134 #ifdef USE_ADC_SENSOR_VCC
135  ESP_LOGCONFIG(TAG, " Pin: VCC");
136 #else
137  LOG_PIN(" Pin: ", this->pin_);
138 #endif // USE_ADC_SENSOR_VCC
139  }
140 #endif // USE_RP2040
141  ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
142  LOG_UPDATE_INTERVAL(this);
143 }
144 
145 float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
146 void ADCSensor::update() {
147  float value_v = this->sample();
148  ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
149  this->publish_state(value_v);
150 }
151 
152 void ADCSensor::set_sample_count(uint8_t sample_count) {
153  if (sample_count != 0) {
154  this->sample_count_ = sample_count;
155  }
156 }
157 
158 #ifdef USE_ESP8266
159 float ADCSensor::sample() {
160  uint32_t raw = 0;
161  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
162 #ifdef USE_ADC_SENSOR_VCC
163  raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
164 #else
165  raw += analogRead(this->pin_->get_pin()); // NOLINT
166 #endif
167  }
168  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
169  if (this->output_raw_) {
170  return raw;
171  }
172  return raw / 1024.0f;
173 }
174 #endif
175 
176 #ifdef USE_ESP32
177 float ADCSensor::sample() {
178  if (!this->autorange_) {
179  uint32_t sum = 0;
180  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
181  int raw = -1;
182  if (this->channel1_ != ADC1_CHANNEL_MAX) {
183  raw = adc1_get_raw(this->channel1_);
184  } else if (this->channel2_ != ADC2_CHANNEL_MAX) {
185  adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
186  }
187  if (raw == -1) {
188  return NAN;
189  }
190  sum += raw;
191  }
192  sum = (sum + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
193  if (this->output_raw_) {
194  return sum;
195  }
196  uint32_t mv = esp_adc_cal_raw_to_voltage(sum, &this->cal_characteristics_[(int32_t) this->attenuation_]);
197  return mv / 1000.0f;
198  }
199 
200  int raw12 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
201 
202  if (this->channel1_ != ADC1_CHANNEL_MAX) {
203  adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_12_COMPAT);
204  raw12 = adc1_get_raw(this->channel1_);
205  if (raw12 < ADC_MAX) {
206  adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_6);
207  raw6 = adc1_get_raw(this->channel1_);
208  if (raw6 < ADC_MAX) {
209  adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_2_5);
210  raw2 = adc1_get_raw(this->channel1_);
211  if (raw2 < ADC_MAX) {
212  adc1_config_channel_atten(this->channel1_, ADC_ATTEN_DB_0);
213  raw0 = adc1_get_raw(this->channel1_);
214  }
215  }
216  }
217  } else if (this->channel2_ != ADC2_CHANNEL_MAX) {
218  adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_12_COMPAT);
219  adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw12);
220  if (raw12 < ADC_MAX) {
221  adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_6);
222  adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
223  if (raw6 < ADC_MAX) {
224  adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_2_5);
225  adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
226  if (raw2 < ADC_MAX) {
227  adc2_config_channel_atten(this->channel2_, ADC_ATTEN_DB_0);
228  adc2_get_raw(this->channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
229  }
230  }
231  }
232  }
233 
234  if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw12 == -1) {
235  return NAN;
236  }
237 
238  uint32_t mv12 = esp_adc_cal_raw_to_voltage(raw12, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_12_COMPAT]);
239  uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
240  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
241  uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
242 
243  // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
244  uint32_t c12 = std::min(raw12, ADC_HALF);
245  uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
246  uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
247  uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
248  // max theoretical csum value is 4096*4 = 16384
249  uint32_t csum = c12 + c6 + c2 + c0;
250 
251  // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
252  uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
253  return mv_scaled / (float) (csum * 1000U);
254 }
255 #endif // USE_ESP32
256 
257 #ifdef USE_RP2040
258 float ADCSensor::sample() {
259  if (this->is_temperature_) {
260  adc_set_temp_sensor_enabled(true);
261  delay(1);
262  adc_select_input(4);
263  uint32_t raw = 0;
264  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
265  raw += adc_read();
266  }
267  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
268  adc_set_temp_sensor_enabled(false);
269  if (this->output_raw_) {
270  return raw;
271  }
272  return raw * 3.3f / 4096.0f;
273  } else {
274  uint8_t pin = this->pin_->get_pin();
275 #ifdef CYW43_USES_VSYS_PIN
276  if (pin == PICO_VSYS_PIN) {
277  // Measuring VSYS on Raspberry Pico W needs to be wrapped with
278  // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
279  // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
280  // VSYS ADC both share GPIO29
281  cyw43_thread_enter();
282  }
283 #endif // CYW43_USES_VSYS_PIN
284 
285  adc_gpio_init(pin);
286  adc_select_input(pin - 26);
287 
288  uint32_t raw = 0;
289  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
290  raw += adc_read();
291  }
292  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
293 
294 #ifdef CYW43_USES_VSYS_PIN
295  if (pin == PICO_VSYS_PIN) {
296  cyw43_thread_exit();
297  }
298 #endif // CYW43_USES_VSYS_PIN
299 
300  if (this->output_raw_) {
301  return raw;
302  }
303  float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
304  return raw * 3.3f / 4096.0f * coeff;
305  }
306 }
307 #endif
308 
309 #ifdef USE_LIBRETINY
310 float ADCSensor::sample() {
311  uint32_t raw = 0;
312  if (this->output_raw_) {
313  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
314  raw += analogRead(this->pin_->get_pin()); // NOLINT
315  }
316  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
317  return raw;
318  }
319  for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
320  raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
321  }
322  raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
323  return raw / 1000.0f;
324 }
325 #endif // USE_LIBRETINY
326 
327 #ifdef USE_ESP8266
328 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
329 #endif
330 
331 } // namespace adc
332 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
uint8_t raw[35]
Definition: bl0939.h:19
adc_atten_t attenuation_
Definition: adc_sensor.h:78
virtual void setup()=0
ADC_MODE(ADC_VCC) namespace esphome
Definition: adc_sensor.cpp:8
void setup() override
Setup ADC.
virtual uint8_t get_pin() const =0
InternalGPIOPin * pin_
Definition: adc_sensor.h:69
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition: helpers.cpp:688
adc2_channel_t channel2_
Definition: adc_sensor.h:80
void set_sample_count(uint8_t sample_count)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
std::string unique_id() override
float sample() override
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM]
Definition: adc_sensor.h:83
void update() override
Update ADC values.
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
float get_setup_priority() const override
HARDWARE_LATE setup priority
void dump_config() override
const StringRef & get_name() const
Definition: entity_base.cpp:10
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26
adc1_channel_t channel1_
Definition: adc_sensor.h:79