mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-22 21:58:29 +02:00
7bb67ae94b
* [ADC] Support measuring VCC on Raspberry Pico (W) Added support for measuring VCC on Raspberry Pico (W) with ADC. GPIO pin is provided as `VCC`, same as with ESP8266. VSYS is the voltage being actually processed, and might have an offset from actual power supply voltage (e.g. USB on VBUS) due to voltage drop on Schottky diode between VSYS and VBUS on Rasberry Pico. The offset has experimentally been found to be ~0.25V on Pico W and ~0.1 on Pico, presumably due to different power consumption. Example usage: sensor: - platform: adc pin: VCC name: "VSYS" * + Added tests for VCC measuring on `rpipicow` board
306 lines
8.5 KiB
C++
306 lines
8.5 KiB
C++
#include "adc_sensor.h"
|
|
#include "esphome/core/log.h"
|
|
#include "esphome/core/helpers.h"
|
|
|
|
#ifdef USE_ESP8266
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
#include <Esp.h>
|
|
ADC_MODE(ADC_VCC)
|
|
#else
|
|
#include <Arduino.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_RP2040
|
|
#ifdef CYW43_USES_VSYS_PIN
|
|
#include "pico/cyw43_arch.h"
|
|
#endif
|
|
#include <hardware/adc.h>
|
|
#endif
|
|
|
|
namespace esphome {
|
|
namespace adc {
|
|
|
|
static const char *const TAG = "adc";
|
|
|
|
// 13-bit for S2, 12-bit for all other ESP32 variants
|
|
#ifdef USE_ESP32
|
|
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
|
|
|
|
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
|
|
#if USE_ESP32_VARIANT_ESP32S2
|
|
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
|
|
#else
|
|
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
|
|
#endif
|
|
#endif
|
|
|
|
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit)
|
|
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
|
|
#endif
|
|
|
|
#ifdef USE_RP2040
|
|
extern "C"
|
|
#endif
|
|
void
|
|
ADCSensor::setup() {
|
|
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
|
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
|
|
pin_->setup();
|
|
#endif
|
|
|
|
#ifdef USE_ESP32
|
|
if (channel1_ != ADC1_CHANNEL_MAX) {
|
|
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
|
|
if (!autorange_) {
|
|
adc1_config_channel_atten(channel1_, attenuation_);
|
|
}
|
|
} else if (channel2_ != ADC2_CHANNEL_MAX) {
|
|
if (!autorange_) {
|
|
adc2_config_channel_atten(channel2_, attenuation_);
|
|
}
|
|
}
|
|
|
|
// load characteristics for each attenuation
|
|
for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
|
|
auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
|
|
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
|
|
1100, // default vref
|
|
&cal_characteristics_[i]);
|
|
switch (cal_value) {
|
|
case ESP_ADC_CAL_VAL_EFUSE_VREF:
|
|
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
|
|
break;
|
|
case ESP_ADC_CAL_VAL_EFUSE_TP:
|
|
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
|
|
break;
|
|
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif // USE_ESP32
|
|
|
|
#ifdef USE_RP2040
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
adc_init();
|
|
initialized = true;
|
|
}
|
|
#endif
|
|
|
|
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
|
|
}
|
|
|
|
void ADCSensor::dump_config() {
|
|
LOG_SENSOR("", "ADC Sensor", this);
|
|
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
|
#else
|
|
LOG_PIN(" Pin: ", pin_);
|
|
#endif
|
|
#endif // USE_ESP8266 || USE_LIBRETINY
|
|
|
|
#ifdef USE_ESP32
|
|
LOG_PIN(" Pin: ", pin_);
|
|
if (autorange_) {
|
|
ESP_LOGCONFIG(TAG, " Attenuation: auto");
|
|
} else {
|
|
switch (this->attenuation_) {
|
|
case ADC_ATTEN_DB_0:
|
|
ESP_LOGCONFIG(TAG, " Attenuation: 0db");
|
|
break;
|
|
case ADC_ATTEN_DB_2_5:
|
|
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
|
|
break;
|
|
case ADC_ATTEN_DB_6:
|
|
ESP_LOGCONFIG(TAG, " Attenuation: 6db");
|
|
break;
|
|
case ADC_ATTEN_DB_11:
|
|
ESP_LOGCONFIG(TAG, " Attenuation: 11db");
|
|
break;
|
|
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
|
break;
|
|
}
|
|
}
|
|
#endif // USE_ESP32
|
|
|
|
#ifdef USE_RP2040
|
|
if (this->is_temperature_) {
|
|
ESP_LOGCONFIG(TAG, " Pin: Temperature");
|
|
} else {
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
|
#else
|
|
LOG_PIN(" Pin: ", pin_);
|
|
#endif // USE_ADC_SENSOR_VCC
|
|
}
|
|
#endif // USE_RP2040
|
|
|
|
LOG_UPDATE_INTERVAL(this);
|
|
}
|
|
|
|
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
|
|
void ADCSensor::update() {
|
|
float value_v = this->sample();
|
|
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
|
this->publish_state(value_v);
|
|
}
|
|
|
|
#ifdef USE_ESP8266
|
|
float ADCSensor::sample() {
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
int32_t raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
|
|
#else
|
|
int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT
|
|
#endif
|
|
if (output_raw_) {
|
|
return raw;
|
|
}
|
|
return raw / 1024.0f;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_ESP32
|
|
float ADCSensor::sample() {
|
|
if (!autorange_) {
|
|
int raw = -1;
|
|
if (channel1_ != ADC1_CHANNEL_MAX) {
|
|
raw = adc1_get_raw(channel1_);
|
|
} else if (channel2_ != ADC2_CHANNEL_MAX) {
|
|
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
|
|
}
|
|
|
|
if (raw == -1) {
|
|
return NAN;
|
|
}
|
|
if (output_raw_) {
|
|
return raw;
|
|
}
|
|
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
|
|
return mv / 1000.0f;
|
|
}
|
|
|
|
int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
|
|
|
|
if (channel1_ != ADC1_CHANNEL_MAX) {
|
|
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
|
|
raw11 = adc1_get_raw(channel1_);
|
|
if (raw11 < ADC_MAX) {
|
|
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
|
|
raw6 = adc1_get_raw(channel1_);
|
|
if (raw6 < ADC_MAX) {
|
|
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
|
|
raw2 = adc1_get_raw(channel1_);
|
|
if (raw2 < ADC_MAX) {
|
|
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0);
|
|
raw0 = adc1_get_raw(channel1_);
|
|
}
|
|
}
|
|
}
|
|
} else if (channel2_ != ADC2_CHANNEL_MAX) {
|
|
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11);
|
|
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11);
|
|
if (raw11 < ADC_MAX) {
|
|
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6);
|
|
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
|
|
if (raw6 < ADC_MAX) {
|
|
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5);
|
|
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
|
|
if (raw2 < ADC_MAX) {
|
|
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0);
|
|
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
|
|
return NAN;
|
|
}
|
|
|
|
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]);
|
|
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
|
|
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
|
|
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
|
|
|
|
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
|
|
uint32_t c11 = std::min(raw11, ADC_HALF);
|
|
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
|
|
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
|
|
uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
|
|
// max theoretical csum value is 4096*4 = 16384
|
|
uint32_t csum = c11 + c6 + c2 + c0;
|
|
|
|
// each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
|
|
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
|
|
return mv_scaled / (float) (csum * 1000U);
|
|
}
|
|
#endif // USE_ESP32
|
|
|
|
#ifdef USE_RP2040
|
|
float ADCSensor::sample() {
|
|
if (this->is_temperature_) {
|
|
adc_set_temp_sensor_enabled(true);
|
|
delay(1);
|
|
adc_select_input(4);
|
|
} else {
|
|
uint8_t pin;
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
#ifdef CYW43_USES_VSYS_PIN
|
|
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
|
|
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
|
|
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
|
|
// VSYS ADC both share GPIO29
|
|
cyw43_thread_enter();
|
|
#endif // CYW43_USES_VSYS_PIN
|
|
pin = PICO_VSYS_PIN;
|
|
#else
|
|
pin = this->pin_->get_pin();
|
|
#endif // USE_ADC_SENSOR_VCC
|
|
|
|
adc_gpio_init(pin);
|
|
adc_select_input(pin - 26);
|
|
}
|
|
|
|
int32_t raw = adc_read();
|
|
if (this->is_temperature_) {
|
|
adc_set_temp_sensor_enabled(false);
|
|
} else {
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
#ifdef CYW43_USES_VSYS_PIN
|
|
cyw43_thread_exit();
|
|
#endif // CYW43_USES_VSYS_PIN
|
|
#endif // USE_ADC_SENSOR_VCC
|
|
}
|
|
|
|
if (output_raw_) {
|
|
return raw;
|
|
}
|
|
float coeff = 1.0;
|
|
#ifdef USE_ADC_SENSOR_VCC
|
|
// As per Raspberry Pico (W) datasheet (section 2.1) the VSYS/3 is measured
|
|
coeff = 3.0;
|
|
#endif // USE_ADC_SENSOR_VCC
|
|
return raw * 3.3f / 4096.0f * coeff;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_LIBRETINY
|
|
float ADCSensor::sample() {
|
|
if (output_raw_) {
|
|
return analogRead(this->pin_->get_pin()); // NOLINT
|
|
}
|
|
return analogReadVoltage(this->pin_->get_pin()) / 1000.0f; // NOLINT
|
|
}
|
|
#endif // USE_LIBRETINY
|
|
|
|
#ifdef USE_ESP8266
|
|
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
|
#endif
|
|
|
|
} // namespace adc
|
|
} // namespace esphome
|