diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py
index f70ffa9520..cceaa594ef 100644
--- a/esphome/components/adc/__init__.py
+++ b/esphome/components/adc/__init__.py
@@ -1 +1,118 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome import pins
+from esphome.const import CONF_INPUT
+
+from esphome.core import CORE
+from esphome.components.esp32 import get_esp32_variant
+from esphome.components.esp32.const import (
+    VARIANT_ESP32,
+    VARIANT_ESP32C3,
+    VARIANT_ESP32H2,
+    VARIANT_ESP32S2,
+    VARIANT_ESP32S3,
+)
+
 CODEOWNERS = ["@esphome/core"]
+
+ATTENUATION_MODES = {
+    "0db": cg.global_ns.ADC_ATTEN_DB_0,
+    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
+    "6db": cg.global_ns.ADC_ATTEN_DB_6,
+    "11db": cg.global_ns.ADC_ATTEN_DB_11,
+    "auto": "auto",
+}
+
+adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
+
+# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
+# pin to adc1 channel mapping
+ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
+    VARIANT_ESP32: {
+        36: adc1_channel_t.ADC1_CHANNEL_0,
+        37: adc1_channel_t.ADC1_CHANNEL_1,
+        38: adc1_channel_t.ADC1_CHANNEL_2,
+        39: adc1_channel_t.ADC1_CHANNEL_3,
+        32: adc1_channel_t.ADC1_CHANNEL_4,
+        33: adc1_channel_t.ADC1_CHANNEL_5,
+        34: adc1_channel_t.ADC1_CHANNEL_6,
+        35: adc1_channel_t.ADC1_CHANNEL_7,
+    },
+    VARIANT_ESP32S2: {
+        1: adc1_channel_t.ADC1_CHANNEL_0,
+        2: adc1_channel_t.ADC1_CHANNEL_1,
+        3: adc1_channel_t.ADC1_CHANNEL_2,
+        4: adc1_channel_t.ADC1_CHANNEL_3,
+        5: adc1_channel_t.ADC1_CHANNEL_4,
+        6: adc1_channel_t.ADC1_CHANNEL_5,
+        7: adc1_channel_t.ADC1_CHANNEL_6,
+        8: adc1_channel_t.ADC1_CHANNEL_7,
+        9: adc1_channel_t.ADC1_CHANNEL_8,
+        10: adc1_channel_t.ADC1_CHANNEL_9,
+    },
+    VARIANT_ESP32S3: {
+        1: adc1_channel_t.ADC1_CHANNEL_0,
+        2: adc1_channel_t.ADC1_CHANNEL_1,
+        3: adc1_channel_t.ADC1_CHANNEL_2,
+        4: adc1_channel_t.ADC1_CHANNEL_3,
+        5: adc1_channel_t.ADC1_CHANNEL_4,
+        6: adc1_channel_t.ADC1_CHANNEL_5,
+        7: adc1_channel_t.ADC1_CHANNEL_6,
+        8: adc1_channel_t.ADC1_CHANNEL_7,
+        9: adc1_channel_t.ADC1_CHANNEL_8,
+        10: adc1_channel_t.ADC1_CHANNEL_9,
+    },
+    VARIANT_ESP32C3: {
+        0: adc1_channel_t.ADC1_CHANNEL_0,
+        1: adc1_channel_t.ADC1_CHANNEL_1,
+        2: adc1_channel_t.ADC1_CHANNEL_2,
+        3: adc1_channel_t.ADC1_CHANNEL_3,
+        4: adc1_channel_t.ADC1_CHANNEL_4,
+    },
+    VARIANT_ESP32H2: {
+        0: adc1_channel_t.ADC1_CHANNEL_0,
+        1: adc1_channel_t.ADC1_CHANNEL_1,
+        2: adc1_channel_t.ADC1_CHANNEL_2,
+        3: adc1_channel_t.ADC1_CHANNEL_3,
+        4: adc1_channel_t.ADC1_CHANNEL_4,
+    },
+}
+
+
+def validate_adc_pin(value):
+    if str(value).upper() == "VCC":
+        return cv.only_on_esp8266("VCC")
+
+    if str(value).upper() == "TEMPERATURE":
+        return cv.only_on_rp2040("TEMPERATURE")
+
+    if CORE.is_esp32:
+        value = pins.internal_gpio_input_pin_number(value)
+        variant = get_esp32_variant()
+        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
+            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
+
+        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
+            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
+        return pins.internal_gpio_input_pin_schema(value)
+
+    if CORE.is_esp8266:
+        from esphome.components.esp8266.gpio import CONF_ANALOG
+
+        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
+            value
+        )
+
+        if value != 17:  # A0
+            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
+        return pins.gpio_pin_schema(
+            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
+        )(value)
+
+    if CORE.is_rp2040:
+        value = pins.internal_gpio_input_pin_number(value)
+        if value not in (26, 27, 28, 29):
+            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
+        return pins.internal_gpio_input_pin_schema(value)
+
+    raise NotImplementedError
diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py
index 1a519d7506..4695e96570 100644
--- a/esphome/components/adc/sensor.py
+++ b/esphome/components/adc/sensor.py
@@ -1,133 +1,27 @@
 import esphome.codegen as cg
 import esphome.config_validation as cv
-from esphome import pins
 from esphome.components import sensor, voltage_sampler
+from esphome.components.esp32 import get_esp32_variant
 from esphome.const import (
     CONF_ATTENUATION,
-    CONF_RAW,
     CONF_ID,
-    CONF_INPUT,
     CONF_NUMBER,
     CONF_PIN,
+    CONF_RAW,
     DEVICE_CLASS_VOLTAGE,
     STATE_CLASS_MEASUREMENT,
     UNIT_VOLT,
 )
 from esphome.core import CORE
-from esphome.components.esp32 import get_esp32_variant
-from esphome.components.esp32.const import (
-    VARIANT_ESP32,
-    VARIANT_ESP32C3,
-    VARIANT_ESP32H2,
-    VARIANT_ESP32S2,
-    VARIANT_ESP32S3,
+
+from . import (
+    ATTENUATION_MODES,
+    ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
+    validate_adc_pin,
 )
 
-
 AUTO_LOAD = ["voltage_sampler"]
 
-ATTENUATION_MODES = {
-    "0db": cg.global_ns.ADC_ATTEN_DB_0,
-    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
-    "6db": cg.global_ns.ADC_ATTEN_DB_6,
-    "11db": cg.global_ns.ADC_ATTEN_DB_11,
-    "auto": "auto",
-}
-
-adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
-
-# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
-# pin to adc1 channel mapping
-ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
-    VARIANT_ESP32: {
-        36: adc1_channel_t.ADC1_CHANNEL_0,
-        37: adc1_channel_t.ADC1_CHANNEL_1,
-        38: adc1_channel_t.ADC1_CHANNEL_2,
-        39: adc1_channel_t.ADC1_CHANNEL_3,
-        32: adc1_channel_t.ADC1_CHANNEL_4,
-        33: adc1_channel_t.ADC1_CHANNEL_5,
-        34: adc1_channel_t.ADC1_CHANNEL_6,
-        35: adc1_channel_t.ADC1_CHANNEL_7,
-    },
-    VARIANT_ESP32S2: {
-        1: adc1_channel_t.ADC1_CHANNEL_0,
-        2: adc1_channel_t.ADC1_CHANNEL_1,
-        3: adc1_channel_t.ADC1_CHANNEL_2,
-        4: adc1_channel_t.ADC1_CHANNEL_3,
-        5: adc1_channel_t.ADC1_CHANNEL_4,
-        6: adc1_channel_t.ADC1_CHANNEL_5,
-        7: adc1_channel_t.ADC1_CHANNEL_6,
-        8: adc1_channel_t.ADC1_CHANNEL_7,
-        9: adc1_channel_t.ADC1_CHANNEL_8,
-        10: adc1_channel_t.ADC1_CHANNEL_9,
-    },
-    VARIANT_ESP32S3: {
-        1: adc1_channel_t.ADC1_CHANNEL_0,
-        2: adc1_channel_t.ADC1_CHANNEL_1,
-        3: adc1_channel_t.ADC1_CHANNEL_2,
-        4: adc1_channel_t.ADC1_CHANNEL_3,
-        5: adc1_channel_t.ADC1_CHANNEL_4,
-        6: adc1_channel_t.ADC1_CHANNEL_5,
-        7: adc1_channel_t.ADC1_CHANNEL_6,
-        8: adc1_channel_t.ADC1_CHANNEL_7,
-        9: adc1_channel_t.ADC1_CHANNEL_8,
-        10: adc1_channel_t.ADC1_CHANNEL_9,
-    },
-    VARIANT_ESP32C3: {
-        0: adc1_channel_t.ADC1_CHANNEL_0,
-        1: adc1_channel_t.ADC1_CHANNEL_1,
-        2: adc1_channel_t.ADC1_CHANNEL_2,
-        3: adc1_channel_t.ADC1_CHANNEL_3,
-        4: adc1_channel_t.ADC1_CHANNEL_4,
-    },
-    VARIANT_ESP32H2: {
-        0: adc1_channel_t.ADC1_CHANNEL_0,
-        1: adc1_channel_t.ADC1_CHANNEL_1,
-        2: adc1_channel_t.ADC1_CHANNEL_2,
-        3: adc1_channel_t.ADC1_CHANNEL_3,
-        4: adc1_channel_t.ADC1_CHANNEL_4,
-    },
-}
-
-
-def validate_adc_pin(value):
-    if str(value).upper() == "VCC":
-        return cv.only_on_esp8266("VCC")
-
-    if str(value).upper() == "TEMPERATURE":
-        return cv.only_on_rp2040("TEMPERATURE")
-
-    if CORE.is_esp32:
-        value = pins.internal_gpio_input_pin_number(value)
-        variant = get_esp32_variant()
-        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
-            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
-
-        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
-            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
-        return pins.internal_gpio_input_pin_schema(value)
-
-    if CORE.is_esp8266:
-        from esphome.components.esp8266.gpio import CONF_ANALOG
-
-        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
-            value
-        )
-
-        if value != 17:  # A0
-            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
-        return pins.gpio_pin_schema(
-            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
-        )(value)
-
-    if CORE.is_rp2040:
-        value = pins.internal_gpio_input_pin_number(value)
-        if value not in (26, 27, 28, 29):
-            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
-        return pins.internal_gpio_input_pin_schema(value)
-
-    raise NotImplementedError
-
 
 def validate_config(config):
     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py
index d2c73cf0d0..48d4d28f8e 100644
--- a/esphome/components/i2s_audio/microphone/__init__.py
+++ b/esphome/components/i2s_audio/microphone/__init__.py
@@ -2,8 +2,9 @@ import esphome.config_validation as cv
 import esphome.codegen as cg
 
 from esphome import pins
-from esphome.const import CONF_ID
-from esphome.components import microphone
+from esphome.const import CONF_ID, CONF_NUMBER
+from esphome.components import microphone, esp32
+from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
 
 from .. import (
     i2s_audio_ns,
@@ -16,18 +17,59 @@ from .. import (
 CODEOWNERS = ["@jesserockz"]
 DEPENDENCIES = ["i2s_audio"]
 
+CONF_ADC_PIN = "adc_pin"
+CONF_ADC_TYPE = "adc_type"
+CONF_PDM = "pdm"
+
 I2SAudioMicrophone = i2s_audio_ns.class_(
     "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
 )
 
-CONFIG_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
+INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
+PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
+
+
+def validate_esp32_variant(config):
+    variant = esp32.get_esp32_variant()
+    if config[CONF_ADC_TYPE] == "external":
+        if config[CONF_PDM]:
+            if variant not in PDM_VARIANTS:
+                raise cv.Invalid(f"{variant} does not support PDM")
+        return config
+    if config[CONF_ADC_TYPE] == "internal":
+        if variant not in INTERNAL_ADC_VARIANTS:
+            raise cv.Invalid(f"{variant} does not have an internal ADC")
+        return config
+    raise NotImplementedError
+
+
+BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
     {
         cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
         cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
-        cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
     }
 ).extend(cv.COMPONENT_SCHEMA)
 
+CONFIG_SCHEMA = cv.All(
+    cv.typed_schema(
+        {
+            "internal": BASE_SCHEMA.extend(
+                {
+                    cv.Required(CONF_ADC_PIN): validate_adc_pin,
+                }
+            ),
+            "external": BASE_SCHEMA.extend(
+                {
+                    cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
+                    cv.Required(CONF_PDM): cv.boolean,
+                }
+            ),
+        },
+        key=CONF_ADC_TYPE,
+    ),
+    validate_esp32_variant,
+)
+
 
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID])
@@ -35,6 +77,13 @@ async def to_code(config):
 
     await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 
-    cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
+    if config[CONF_ADC_TYPE] == "internal":
+        variant = esp32.get_esp32_variant()
+        pin_num = config[CONF_ADC_PIN][CONF_NUMBER]
+        channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
+        cg.add(var.set_adc_channel(channel))
+    else:
+        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
+        cg.add(var.set_pdm(config[CONF_PDM]))
 
     await microphone.register_microphone(var, config)
diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp
index 8883cdc665..2b38853528 100644
--- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp
+++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp
@@ -17,15 +17,36 @@ static const char *const TAG = "i2s_audio.microphone";
 void I2SAudioMicrophone::setup() {
   ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
   this->buffer_.resize(BUFFER_SIZE);
+
+#if SOC_I2S_SUPPORTS_ADC
+  if (this->adc_) {
+    if (this->parent_->get_port() != I2S_NUM_0) {
+      ESP_LOGE(TAG, "Internal ADC only works on I2S0!");
+      this->mark_failed();
+      return;
+    }
+  } else
+#endif
+      if (this->pdm_) {
+    if (this->parent_->get_port() != I2S_NUM_0) {
+      ESP_LOGE(TAG, "PDM only works on I2S0!");
+      this->mark_failed();
+      return;
+    }
+  }
 }
 
-void I2SAudioMicrophone::start() { this->state_ = microphone::STATE_STARTING; }
+void I2SAudioMicrophone::start() {
+  if (this->is_failed())
+    return;
+  this->state_ = microphone::STATE_STARTING;
+}
 void I2SAudioMicrophone::start_() {
   if (!this->parent_->try_lock()) {
     return;  // Waiting for another i2s to return lock
   }
   i2s_driver_config_t config = {
-      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
+      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
       .sample_rate = 16000,
       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
       .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
@@ -40,18 +61,33 @@ void I2SAudioMicrophone::start_() {
       .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
   };
 
-  i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
+#if SOC_I2S_SUPPORTS_ADC
+  if (this->adc_) {
+    config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
+    i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
 
-  i2s_pin_config_t pin_config = this->parent_->get_pin_config();
-  pin_config.data_in_num = this->din_pin_;
+    i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
+    i2s_adc_enable(this->parent_->get_port());
+  } else {
+#endif
+    if (this->pdm_)
+      config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
 
-  i2s_set_pin(this->parent_->get_port(), &pin_config);
+    i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
+
+    i2s_pin_config_t pin_config = this->parent_->get_pin_config();
+    pin_config.data_in_num = this->din_pin_;
+
+    i2s_set_pin(this->parent_->get_port(), &pin_config);
+#if SOC_I2S_SUPPORTS_ADC
+  }
+#endif
   this->state_ = microphone::STATE_RUNNING;
   this->high_freq_.start();
 }
 
 void I2SAudioMicrophone::stop() {
-  if (this->state_ == microphone::STATE_STOPPED)
+  if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
     return;
   this->state_ = microphone::STATE_STOPPING;
 }
diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h
index a36cb7340c..e704ed2915 100644
--- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h
+++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h
@@ -18,14 +18,27 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 
   void loop() override;
 
-  void set_din_pin(uint8_t pin) { this->din_pin_ = pin; }
+  void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
+  void set_pdm(bool pdm) { this->pdm_ = pdm; }
+
+#if SOC_I2S_SUPPORTS_ADC
+  void set_adc_channel(adc1_channel_t channel) {
+    this->adc_channel_ = channel;
+    this->adc_ = true;
+  }
+#endif
 
  protected:
   void start_();
   void stop_();
   void read_();
 
-  uint8_t din_pin_{0};
+  int8_t din_pin_{I2S_PIN_NO_CHANGE};
+#if SOC_I2S_SUPPORTS_ADC
+  adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX};
+  bool adc_{false};
+#endif
+  bool pdm_{false};
   std::vector<uint8_t> buffer_;
 
   HighFrequencyLoopRequester high_freq_;
diff --git a/tests/test4.yaml b/tests/test4.yaml
index d253db8f70..c1d49a4349 100644
--- a/tests/test4.yaml
+++ b/tests/test4.yaml
@@ -700,8 +700,15 @@ prometheus:
 
 microphone:
   - platform: i2s_audio
-    id: mic_id
+    id: mic_id_adc
+    adc_pin: GPIO35
+    adc_type: internal
+
+  - platform: i2s_audio
+    id: mic_id_external
     i2s_din_pin: GPIO23
+    adc_type: external
+    pdm: false
 
 speaker:
   - platform: i2s_audio
@@ -712,7 +719,7 @@ speaker:
 
 
 voice_assistant:
-  microphone: mic_id
+  microphone: mic_id_external
   on_start:
     - logger.log: "Voice assistant started"
   on_stt_end: