Comparison of Sleep Currents for XIAO ESP32C6, S3, and C3

The symbol for M2 in the simulation schematic shows that the source is grounded and the drain is connected to VBUS. The connection is correct, as the drain current begins to flow when the gate voltage is positively biased.

By the way, what do you think this circuit protects from what risk?
I think it is a meaningful protection circuit if the external power supply over 5V connected to VBUS detects the overcurrent of XIAO and turns off automatically.

First of all, I’d like to thank msfujino for all his detective work with the power consumption. I’ve read a lot of your threads, and it’s been super helpful.

I have a question because of a difference in power consumption I’ve noticed between making the same project on xiao-c6 vs xiao-s3.

Here’s my project:

  • 2x xiao-c6
  • 2x xiao-s3
  • Powered via 18650 in dumb holder, directly soldered to bat+, bat-
  • Battery power being read via 220k ohm resistor voltage divider to ADC
  • code runs every 20 minutes, then sleeps
  • code reads temperature from max31855, wifi signal and power from ADC
  • code then deep sleeps after values are sent
  • code is esphome
  • c6 is using external antennae instead of onboard. See on_boot on c6 yaml.

I suspect perhaps it’s the initialization of external antennae initialization, which is only done on the c6. It could be something else. I have no idea. Curious if deep sleep current could get tested with C6 using external wifi.

I’ve noticed that my s3’s are doing much better in battery life, and I don’t think this can simply be explained by deep sleep current, since it’s so small.

HomeAssistant battery usage graphs
I can’t upload so here’s a link
www.imgur [DOT] com/a/XoEoZDd


  • Purple is an s3 with max31855
  • Brown is an c6 with max31855
  • Orange is an c6 but with PT100 & max31865

You’ll notice the battery on the c6’s goes down way faster than the s3. I’ve noticed this with both of my s3’s.

The purple s3 on the graph, has been running since Jan 24th (graph only shows it since the 29th).

Here are my esphome yaml’s

ESP32-C6

substitutions:
  name: esp32c6-thermocouple
  run_duration_time: 60s
  sleep_duration_time: 20min
  sensor_update_time: 5s
  mac_address: 41a314
  ip_address: 192.168.1.156
## Better Deep Sleep
# https://ncrmnt.org/2021/12/06/optimizing-esp8266-esphome-for-battery-power-and-making-an-ice-bath-thermometer-as-well/
# https://tatham.blog/2021/02/06/esphome-batteries-deep-sleep-and-over-the-air-updates/
# https://webbinaro.dev/blog/battery-powered-esp-sensors-esphome/
# https://community.home-assistant.io/t/esphome-api-vs-mqtt-for-deep-sleep-in-2022/440934
# https://community.home-assistant.io/t/loosing-my-mind-esphome-deep-sleep-and-mqtt-wont-work-together/298787
## LIGHT SLEEP
# https://github.com/esphome/feature-requests/issues/141
# https://gist.github.com/nagisa/8d3af3ff8bfd9563d673bb458dfd491d
esphome:
  name: ${name}
  name_add_mac_suffix: true
  # Change from Internal WIFI Antennae to External
  on_boot:
    - priority: 700.0
      then:
        lambda: |-
          gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_3, 0);
          vTaskDelay(pdMS_TO_TICKS(100));
          gpio_set_direction(GPIO_NUM_14, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_14, 1);
    - priority: -300
      then:
        - logger.log: 'START - Consider Deep Sleep'
        - script.execute: consider_deep_sleep
        - logger.log: 'FINISH - Consider Deep Sleep'

esp32:
  board: esp32-c6-devkitc-1
  variant: esp32c6
  framework:
    type: esp-idf
    version: "5.3.1"
    platform_version: 6.9.0

## https://github.com/esphome/esphome/pull/7942
external_components:
  - source: github://pr#7942
    refresh: 1d
    components:
      - adc
  - source: github://pr#7988
    refresh: 1d
    components:
      - mqtt
  - source:
      type: local
      path: my_components
    components: [ max31855 ]

# Wi-Fi configuration with WPA3
wifi:
  power_save_mode: LIGHT # default
  fast_connect: true
  networks:
    - ssid: "ASUS"
      password: "XXXXXXXX"
  manual_ip:
    static_ip: ${ip_address}
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.2
  ap:
    ap_timeout: 1min # default

# Enable logging
logger:
  level: DEBUG
  logs:
    mqtt.component: DEBUG
    mqtt.client: DEBUG

# Enable OTA updates
ota:
  platform: esphome
  on_end:
    then:
      - logger.log: "Finished OTA Update. Removing ota_mode"
      - mqtt.publish:
          topic: ${name}-${mac_address}/ota_mode
          payload: 'OFF'
          retain: true
      - globals.set:
          id: ota_mode
          value: "false"
      - delay: 5s

mqtt:
  broker: 192.168.1.2
  port: 1883
  discovery_unique_id_generator: "mac"
  discovery_object_id_generator: "device_name"
  birth_message:
  will_message:
  on_message:
    - topic: ${name}-${mac_address}/ota_mode
      payload: 'ON'
      then:
        - logger.log: "OTA Mode - ON"
        - globals.set:
            id: ota_mode
            value: "true"
        - deep_sleep.prevent: deep_sleep_1
    - topic: ${name}-${mac_address}/ota_mode
      payload: 'OFF'
      then:
        - logger.log: "OTA Mode - OFF"
        - globals.set:
            id: ota_mode
            value: "false"
        - deep_sleep.allow: deep_sleep_1
  
deep_sleep:
  id: deep_sleep_1
  run_duration: ${run_duration_time}
  sleep_duration: ${sleep_duration_time}

switch:
  - platform: shutdown
    id: switch_shutdown
    name: "ESP Shutdown"

# max31855
spi:
  id: spi_thermo
  clk_pin: GPIO19   # SCK pin
  miso_pin: GPIO20  # MISO pin

globals:
  - id: updates
    type: int
    restore_value: no
    initial_value: '0'
  - id: ota_mode
    type: bool
    restore_value: yes
    initial_value: "false"

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    id: wifi_signalzzz
    update_interval: never
    on_value:
      - logger.log: 'UPDATED Wifi Signalzzz'
      - lambda: |-
          id(updates)++;
    
  - platform: max31855
    name: "K Thermocouple Temperature"
    id: k_thermocouple
    cs_pin: GPIO17
    spi_id: spi_thermo
    accuracy_decimals: 1
    update_interval: never
    on_value:
      - logger.log: 'UPDATED Thermocouple'
      - lambda: |-
          id(updates)++;
    reference_temperature:
      name: "Reference Temp"
      id: ref_temp
      on_value:
        - logger.log: 'UPDATED RefTemp'
        - lambda: |-
            id(updates)++;
  - platform: adc
    name: "Battery Voltage"
    id: adc_vcc
    attenuation: 12db
    pin: GPIO0
    accuracy_decimals: 2
    samples: 16 # should be 16, default 1
    update_interval: never
    filters:
      - multiply: 2.0
    on_value:
      - logger.log: 'UPDATED Battery Voltage'
      - lambda: |-
          id(updates)++;

## https://webbinaro.dev/blog/battery-powered-esp-sensors-esphome/
script:
  - id: consider_deep_sleep
    mode: queued
    then:
      - logger.log: 'Doing UPDATES'
      - lambda: |-
          id(updates) = 0;
          id(k_thermocouple).update();
          id(adc_vcc).update();
          id(wifi_signalzzz).update();
      - logger.log: 'Waiting for UPDATES'
      - wait_until:
          lambda: |-
            return (id(updates) >= 4);
      - logger.log: 'DONE Waiting for UPDATES'
      - logger.log:
          format: 'Batt: %3.2f, wifi: %3.2f, temp: %3.2f, ref_temp: %3.2f'
          args: [ id(adc_vcc).state, id(wifi_signalzzz).state, id(k_thermocouple).state, id(ref_temp).state ]
      - delay: 200ms
      - if:
          ## It appears that id(vcc).state doesn't go through the multiplyer. 
          condition:
            # On USB power is 3.7/2 1.34
            lambda: 'return ((id(adc_vcc).state) < 3.0 && id(adc_vcc).state > 2.0);'
          then:
            - logger.log: 
                level: ERROR
                format: 'Going to sleep. VOLTS under 3 %3.2f'
                args: [ id(adc_vcc).state ]
            - lambda: |-
                id(switch_shutdown).turn_on();
          else:
            - logger.log: 
                format: 'Going to sleep for X amount of time. Prevent? %d'
                args: [ id(ota_mode) ]
            - lambda: |-
                id(deep_sleep_1).begin_sleep(false);
## Can't use deep_sleep.enter, as it doesn't obey prevent_sleep
#            - deep_sleep.enter:
#                id: deep_sleep_1
#                sleep_duration: ${sleep_duration_time}

ESP32-S3

substitutions:
  name: esp32s3-thermocouple
  run_duration_time: 60s
  sleep_duration_time: 20min
  sensor_update_time: 5s
  mac_address: ed061c
  ip_address: 192.168.1.159
## Better Deep Sleep
# https://ncrmnt.org/2021/12/06/optimizing-esp8266-esphome-for-battery-power-and-making-an-ice-bath-thermometer-as-well/
# https://tatham.blog/2021/02/06/esphome-batteries-deep-sleep-and-over-the-air-updates/
# https://webbinaro.dev/blog/battery-powered-esp-sensors-esphome/
# https://community.home-assistant.io/t/esphome-api-vs-mqtt-for-deep-sleep-in-2022/440934
# https://community.home-assistant.io/t/loosing-my-mind-esphome-deep-sleep-and-mqtt-wont-work-together/298787
## LIGHT SLEEP
# https://github.com/esphome/feature-requests/issues/141
# https://gist.github.com/nagisa/8d3af3ff8bfd9563d673bb458dfd491d
esphome:
  name: ${name}
  name_add_mac_suffix: true
  platformio_options:
    build_flags: -DBOARD_HAS_PSRAM
    board_build.arduino.memory_type: qio_opi
    board_build.f_flash: 80000000L
    board_build.flash_mode: qio 
  on_boot:
    - priority: -300
      then:
        - logger.log: 'START - Consider Deep Sleep'
        - script.execute: consider_deep_sleep
        - logger.log: 'FINISH - Consider Deep Sleep'

esp32:
  board: seeed_xiao_esp32s3
  variant: esp32s3
  framework:
    type: esp-idf
    version: "5.3.1"
    platform_version: 6.9.0

## https://github.com/esphome/esphome/pull/7942
external_components:
  - source: github://pr#7942
    refresh: 1d
    components:
      - adc
  - source: github://pr#7988
    refresh: 1d
    components:
      - mqtt
  - source:
      type: local
      path: my_components
    components: [ max31855 ]

# Wi-Fi configuration with WPA3
wifi:
  power_save_mode: LIGHT # default
  fast_connect: true
  networks:
    - ssid: "ASUS"
      password: "XXXXXXXXXXXX"
  manual_ip:
    static_ip: ${ip_address}
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.2
  ap:
    ap_timeout: 1min # default

# Enable logging
logger:
  level: WARN
  logs:
    mqtt.component: WARN
    mqtt.client: WARN

# Enable OTA updates
ota:
  platform: esphome
  on_end:
    then:
      - logger.log: "Finished OTA Update. Removing ota_mode"
      - mqtt.publish:
          topic: ${name}-${mac_address}/ota_mode
          payload: 'OFF'
          retain: true
      - globals.set:
          id: ota_mode
          value: "false"
      - delay: 5s

mqtt:
  broker: 192.168.1.2
  port: 1883
  discovery_unique_id_generator: "mac"
  discovery_object_id_generator: "device_name"
  birth_message:
  will_message:
  on_message:
    - topic: ${name}-${mac_address}/ota_mode
      payload: 'ON'
      then:
        - logger.log: "OTA Mode - ON"
        - globals.set:
            id: ota_mode
            value: "true"
        - deep_sleep.prevent: deep_sleep_1
    - topic: ${name}-${mac_address}/ota_mode
      payload: 'OFF'
      then:
        - logger.log: "OTA Mode - OFF"
        - globals.set:
            id: ota_mode
            value: "false"
        - deep_sleep.allow: deep_sleep_1
  
deep_sleep:
  id: deep_sleep_1
  run_duration: ${run_duration_time}
  sleep_duration: ${sleep_duration_time}

switch:
  - platform: shutdown
    id: switch_shutdown
    name: "ESP Shutdown"

# max31855
spi:
  id: spi_thermo
  clk_pin: GPIO7   # SCK pin
  miso_pin: GPIO8  # MISO pin

globals:
  - id: updates
    type: int
    restore_value: no
    initial_value: '0'
  - id: ota_mode
    type: bool
    restore_value: yes
    initial_value: "false"

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    id: wifi_signalzzz
    update_interval: never
    on_value:
      - logger.log: 'UPDATED Wifi Signalzzz'
      - lambda: |-
          id(updates)++;
    
  - platform: max31855
    name: "K Thermocouple Temperature"
    id: k_thermocouple
    cs_pin: GPIO44
    spi_id: spi_thermo
    accuracy_decimals: 1
    update_interval: never
    on_value:
      - logger.log: 'UPDATED Thermocouple'
      - lambda: |-
          id(updates)++;
    reference_temperature:
      name: "Reference Temp"
      id: ref_temp
      on_value:
        - logger.log: 'UPDATED RefTemp'
        - lambda: |-
            id(updates)++;
  - platform: adc
    name: "Battery Voltage"
    id: adc_vcc
    attenuation: 12db
    pin: GPIO1
    accuracy_decimals: 2
    samples: 16 # should be 16, default 1
    update_interval: never
    filters:
      - multiply: 2.0
    on_value:
      - logger.log: 'UPDATED Battery Voltage'
      - lambda: |-
          id(updates)++;

## https://webbinaro.dev/blog/battery-powered-esp-sensors-esphome/
script:
  - id: consider_deep_sleep
    mode: queued
    then:
      - logger.log: 'Doing UPDATES'
      - lambda: |-
          id(updates) = 0;
          id(k_thermocouple).update();
          id(adc_vcc).update();
          id(wifi_signalzzz).update();
      - logger.log: 'Waiting for UPDATES'
      - wait_until:
          lambda: |-
            return (id(updates) >= 4);
      - logger.log: 'DONE Waiting for UPDATES'
      - logger.log:
          format: 'Batt: %3.2f, wifi: %3.2f, temp: %3.2f, ref_temp: %3.2f'
          args: [ id(adc_vcc).state, id(wifi_signalzzz).state, id(k_thermocouple).state, id(ref_temp).state ]
      - delay: 200ms
      - if:
          ## It appears that id(vcc).state doesn't go through the multiplyer. 
          condition:
            # On USB power is 3.7/2 1.34
            lambda: 'return ((id(adc_vcc).state) < 3.0 && id(adc_vcc).state > 2.0);'
          then:
            - logger.log: 
                level: ERROR
                format: 'Going to sleep. VOLTS under 3 %3.2f'
                args: [ id(adc_vcc).state ]
            - lambda: |-
                id(switch_shutdown).turn_on();
          else:
            - logger.log: 
                format: 'Going to sleep for X amount of time. Prevent? %d'
                args: [ id(ota_mode) ]
            - lambda: |-
                id(deep_sleep_1).begin_sleep(false);
## Can't use deep_sleep.enter, as it doesn't obey prevent_sleep
#            - deep_sleep.enter:
#                id: deep_sleep_1
#                sleep_duration: ${sleep_duration_time}

Any insights would be appreciated.

I don’t know how to download your images from the link site.

I think because my account is new, I can’t upload or add links.

So [DOT] is the period in .com . IMGUR dot com is a image hosting website.

Since we’re programmers, do this regex on the link :slight_smile:

s/[DOT]/./g

I went to Imgur: The magic of the Internet and was able to download the image, but why did you bother posting the link with the confusing expression imgur [DOT] com/a/XoEoZDd ?
s/[DOT]/. /g
What does that mean?

The FM8625H used to switch the antenna consumes more than 100uA of current.
It consumes 100uA unless it is turned off when C6 goes to sleep.

HTH
GL :slight_smile: PJ :v:

Did you look at the Chart to begin with?
on the WiKi? scroll down to low power…

I’ve just flashed code on one of my c6’s, which turns off the external antennae before entering in to deep sleep.

Added below in the on_shutdown action in esphome yaml.

New Code

esphome:
  name: ${name}
  name_add_mac_suffix: true
  # Change from Internal WIFI Antennae to External
  # https://www.sigmdel.ca/michel/ha/xiao/xiao_esp32c6_intro_en.html#antenna_1
  on_boot:
    - priority: 700.0
      then:
        lambda: |-
          gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_3, 0);
          vTaskDelay(pdMS_TO_TICKS(100));
          gpio_set_direction(GPIO_NUM_14, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_14, 1);
    - priority: -300
      then:
        - logger.log: 'START - Consider Deep Sleep'
        - script.execute: consider_deep_sleep
        - logger.log: 'FINISH - Consider Deep Sleep'
  on_shutdown:
    - priority: -100 # last
      then:
        lambda: |-
          gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_3, 0);
          vTaskDelay(pdMS_TO_TICKS(100));
          gpio_set_direction(GPIO_NUM_14, GPIO_MODE_OUTPUT);
          gpio_set_level(GPIO_NUM_14, 0);

I’ll let this c6 run for a couple days, and compare the battery graphs against it’s last run to see if the battery drain is lower over the next week.

Not sure if 100uA savings will account for the difference, but we’ll see how much it helps in real world numbers.

DOCUMENTATION
This should probably be documented, or added to arduino-esp32 shutdown code so that there’s not excessive quiescent power draw when the xiao-c6 is put into sleep mode when using the external antennae.

While it get’s initialized, it never gets turned back off when entering sleep.


Yes, but even the power delta between:

  • xiao-s3: 14uA deep sleep
  • xiao-c6: 15uA deep sleep

If msfujino’s comment is correct, and not shutting down the external antennae chip (FM8625H) is causing ~100uA extra power draw in deep sleep…this additional power draw could explain things.

I’m still a long way away from multiple weeks of battery life for my xiao’s running on 3500 mAh 18650’s though and I don’t think the 100uA in deep sleep will help me get there 100%.

If there’s any other tips on lowering power draw during sleep I’d love to hear about them.


ESPHOME Power Usage
It would be very interesting to see power consumption tests run on a “standard” esphome type project, that reads a connects to Wifi, reads a sensor, publishes to MQTT or API before sleeping for X amount of time.

I wonder how many things are getting initialized that don’t end up sleeping properly and drain battery. Or how much a fairly “standard” esphome “run” uses in power draw.

The main currents when sleeping are MAX31865: 1000uA and FM8625H: 100uA. Assume that the average current consumption during 2 minutes of wake-up is 50mA. Calculate the amount of charge consumed, respectively,
Qwakeup=2x60x50m=6[C].
Qsleep=20x60x1100u=1.32[C].
As you say, reducing the sleep current does not seem to have much effect on battery life.