From 7bb67ae94b0399467a4f9752b5111b1c613f2643 Mon Sep 17 00:00:00 2001
From: Ilia Sotnikov <hostcc@users.noreply.github.com>
Date: Sat, 9 Sep 2023 12:00:45 +0300
Subject: [PATCH] [ADC] Support measuring VCC on Raspberry Pico (W) (#5335)

* [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
---
 esphome/components/adc/__init__.py    |  6 +++-
 esphome/components/adc/adc_sensor.cpp | 40 +++++++++++++++++++++++++--
 tests/test6.yaml                      |  3 ++
 3 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py
index ba72951777..0b6ee145f2 100644
--- a/esphome/components/adc/__init__.py
+++ b/esphome/components/adc/__init__.py
@@ -5,6 +5,10 @@ from esphome.const import CONF_ANALOG, CONF_INPUT
 
 from esphome.core import CORE
 from esphome.components.esp32 import get_esp32_variant
+from esphome.const import (
+    PLATFORM_ESP8266,
+    PLATFORM_RP2040,
+)
 from esphome.components.esp32.const import (
     VARIANT_ESP32,
     VARIANT_ESP32C2,
@@ -143,7 +147,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 
 def validate_adc_pin(value):
     if str(value).upper() == "VCC":
-        return cv.only_on_esp8266("VCC")
+        return cv.only_on([PLATFORM_ESP8266, PLATFORM_RP2040])("VCC")
 
     if str(value).upper() == "TEMPERATURE":
         return cv.only_on_rp2040("TEMPERATURE")
diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp
index 0642cd7f3f..e69e6b9313 100644
--- a/esphome/components/adc/adc_sensor.cpp
+++ b/esphome/components/adc/adc_sensor.cpp
@@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC)
 #endif
 
 #ifdef USE_RP2040
+#ifdef CYW43_USES_VSYS_PIN
+#include "pico/cyw43_arch.h"
+#endif
 #include <hardware/adc.h>
 #endif
 
@@ -123,13 +126,19 @@ void ADCSensor::dump_config() {
     }
   }
 #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
+#endif  // USE_RP2040
+
   LOG_UPDATE_INTERVAL(this);
 }
 
@@ -238,7 +247,20 @@ float ADCSensor::sample() {
     delay(1);
     adc_select_input(4);
   } else {
-    uint8_t pin = this->pin_->get_pin();
+    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);
   }
@@ -246,11 +268,23 @@ float ADCSensor::sample() {
   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;
   }
-  return raw * 3.3f / 4096.0f;
+  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
 
diff --git a/tests/test6.yaml b/tests/test6.yaml
index f048a4fa14..3d6a1ceb1f 100644
--- a/tests/test6.yaml
+++ b/tests/test6.yaml
@@ -62,3 +62,6 @@ switch:
 sensor:
   - platform: internal_temperature
     name: Internal Temperature
+  - platform: adc
+    pin: VCC
+    name: VSYS