arduino-esp32 icon indicating copy to clipboard operation
arduino-esp32 copied to clipboard

ESP32 External 32.768KHz crystal support

Open mightChamp opened this issue 1 year ago • 13 comments

Related area

ESP32 Arduino Core

Hardware specification

ESP32 Devkit

Is your feature request related to a problem?

In deepsleep of 24 hours, ESP32 Wakeup earlier, due to inaccurate internal rtc clock, So If we can use external rtc crystal, it will be more accurate. And from my search, I found that I have to build ESP32 Arduino Core with ESP-IDF and replace static libraries, which also affects my other projects, too. So, if there is direct option to use external rtc crystal, it would be great.

Describe the solution you'd like

Provide some api to call to use rtc crystal.

Describe alternatives you've considered

No response

Additional context

No response

I have checked existing list of Feature requests and the Contribution Guide

  • [X] I confirm I have checked existing list of Feature requests and Contribution Guide.

mightChamp avatar Jan 06 '23 05:01 mightChamp

If you are not willing to invest the knowledge to rebuild the libraries, you can spend money to buy an external RTC module. That would have the added capability to run off an external battery during esp32 deep sleep.

lbernstone avatar Jan 06 '23 05:01 lbernstone

Just for curiosity, why RTC Clock selection is taken as static from menu config from IDF, instead of api call, and set clock.

mightChamp avatar Jan 09 '23 10:01 mightChamp

@mightChamp this is what I posted about this over in the esp32-arduino forum(link below):

Something in the new builds is requiring esptool and openocd-esp32 in the tools directory(tools/esptool/.. and tools/openocd-esp32/..).
I've opted to keep the standard esp32 setup installed (via the IDE) and then I tar the newly built libs over that installation. I've also copied the esptool/ and openocd-esp32/ directories from elsewhere into the hardware/esp32/2.0.6/ directory.
cp -a $HOME/.espressif/tools/openocd-esp32/v0.11.0-esp32-20221026/openocd-esp32 tools/
cp -a $HOME/Projects/esp/esp-idf-4_4/components/esptool_py/esptool tools/
and then backed those up for later use while in the 2.0.6 directory:
tar -cvzf ../othertools.tgz tools/openocd-esp32 tools/esptool

Because sdkconfig/menuconfig gets reset on every build I've found this works:
-backup default installation's sdkconfig for esp32
cp components/arduino/tools/sdk/esp32/sdkconfig components/arduino/tools/sdk/esp32/sdkconfigORG
./build.sh -t esp32 -b menuconfig
cp sdkconfig components/arduino/tools/sdk/esp32/sdkconfig
./build.sh -t esp32

the updated libraries will be in the dist/ directory

You need to have installed the esp-idk and arduino-esp. The link to thread: https://www.esp32.com/viewtopic.php?f=19&t=31371

dlarue avatar Jan 09 '23 16:01 dlarue

Below code is working for External crystal, without building libraries in esp-idf for arduino. it is also tested for 24 hour of deepsleep and working fine.

#include "Arduino.h"
#include "soc/rtc.h"


#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
#include "esp_deep_sleep.h"
#include "rom/rtc.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"


#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  5        /* Time ESP32 will go to sleep (in seconds) */


RTC_DATA_ATTR int bootCount = 0;

void debug_xtal_out_dac1() {
    SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
    CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32P_RDE | RTC_IO_X32P_RUE | RTC_IO_X32N_RUE | RTC_IO_X32N_RDE);
    CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL | RTC_IO_X32P_MUX_SEL);
    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DAC_XTAL_32K, 1, RTC_IO_DAC_XTAL_32K_S);
    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DRES_XTAL_32K, 3, RTC_IO_DRES_XTAL_32K_S);
    SET_PERI_REG_BITS(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_DBIAS_XTAL_32K, 0, RTC_IO_DBIAS_XTAL_32K_S);
    SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_XPD_XTAL_32K);
    REG_SET_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_MUX_SEL_M);
    REG_CLR_BIT(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_RDE_M | RTC_IO_PDAC1_RUE_M);
    REG_SET_FIELD(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_FUN_SEL, 1);
    REG_SET_FIELD(SENS_SAR_DAC_CTRL1_REG, SENS_DEBUG_BIT_SEL, 0);
    const uint8_t sel = 4; /* sel = 4 : 32k XTAL; sel = 5 : internal 150k RC */
    REG_SET_FIELD(RTC_IO_RTC_DEBUG_SEL_REG, RTC_IO_DEBUG_SEL0, sel);
}

/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

const float factor = (1 << 19) * 1000.0f;

#define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk)

static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name)
{

    const uint32_t cal_count = 1000;
    uint32_t cali_val;
    printf("%s:\n", name);
    for (int i = 0; i < 5; ++i)
    {
        printf("calibrate (%d): ", i);
        cali_val = rtc_clk_cal(cal_clk, cal_count);
        printf("%.3f kHz\n", factor / (float)cali_val);
    }
    return cali_val;
}

void print_slow_clock_source() {
  rtc_slow_freq_t slow_clk = rtc_clk_slow_freq_get();
  Serial.print("Slow clk source: ");
  switch(slow_clk) {
    case RTC_SLOW_FREQ_RTC: Serial.println("Internal 150kHz"); break;
    case RTC_SLOW_FREQ_32K_XTAL: Serial.println("External 32kHz"); break;
    case RTC_SLOW_FREQ_8MD256: Serial.println("Internal 8MHz");
  }
}

void print_fast_clk_math() {
  const uint32_t cal_count = 1000;
  uint32_t cali_val;
  uint64_t freq = (uint64_t)rtc_clk_xtal_freq_get();
  printf("Fast Clk: %lluMHz\n", freq);
}

float crystal_frequency() {
  const uint32_t cal_count = 100;
  uint32_t cali_val;
  cali_val = rtc_clk_cal(RTC_CAL_32K_XTAL, cal_count);
  float freq_32k = factor / (float)cali_val;
  return freq_32k;
}

void setExternalCrystalAsRTCSource(){
   if (bootCount == 1) {
    Serial.println("First boot, bootstrap and enable 32k XTAL");
    print_fast_clk_math();
    // rtc_clk_32k_bootstrap(10);
    rtc_clk_32k_enable(true);
  } else {
    Serial.println("Wake from deepsleep, not jumping XTAL.");
    printf("rtc sleep reg0: %u\n", REG_READ(RTC_CNTL_SLP_TIMER0_REG));
    printf("rtc sleep reg1: %u\n", REG_READ(RTC_CNTL_SLP_TIMER1_REG));
  }

    uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL);
    debug_xtal_out_dac1();
    float freq_32k = factor / (float)cal_32k;
    float delta = freq_32k - 32.768;
    if (delta < 0) delta = -delta;
    uint32_t startCal=millis();
    while (delta > 0.002 && millis()-startCal<15000) {
      printf("Waiting for 32kHz clock to be stable: %.3f kHz\n", freq_32k);
      cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL);
      freq_32k = factor / (float)cal_32k;
      delta = freq_32k - 32.768;
      if (delta < 0) delta = -delta;
    }
    if(delta < 0.002){
      rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
      uint32_t rtc_clk_calibration = REG_READ(RTC_SLOW_CLK_CAL_REG);
      printf("Slow clock calibration: %u\n", rtc_clk_calibration);
      printf("32k calibration: %u\n", cal_32k);
      if ((rtc_clk_calibration > (cal_32k + 5)) || (rtc_clk_calibration < (cal_32k - 5))) {
        printf("Miscalibrated, setting calibration register to 32k calibration.\n");
        REG_WRITE(RTC_SLOW_CLK_CAL_REG, cal_32k);
        rtc_clk_calibration = REG_READ(RTC_SLOW_CLK_CAL_REG);
        if (rtc_clk_calibration != cal_32k) {
          printf("ERROR Calibration write failure.\n");
        }
      }
  
      if (cal_32k == 0)
      {
          printf("32K XTAL OSC has not started up");
      }
      else
      {
          printf("done\n");
      }
  
      if (rtc_clk_32k_enabled())
      {
          Serial.println("OSC Enabled");
      }
    }
    else{
      Serial.println("OSC Not Enabled, using Internal 150KHz RC");
    }

    print_slow_clock_source();
}


void setup() {
  delay(500);
  Serial.begin(115200);
  

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();
  print_slow_clock_source();
  setExternalCrystalAsRTCSource();
      /*
    First we configure the wake up source
    We set our ESP32 to wake up every 5 seconds
    */
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
    " Seconds");

    // By default the ESP32 powers down all peripherals which are not needed to wake up again.
    // We can change this behaviour with pd_config
    // ESP_PD_DOMAIN_RTC_PERIPH
    // ESP_PD_DOMAIN_RTC_SLOW_MEM
    // ESP_PD_DOMAIN_RTC_FAST_MEM
    esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
    Serial.println("Configured peripheral power");
    delay(50);
    uint64_t ticks = rtc_time_get();
    Serial.printf("Going to sleep now, rtc ticks: %llu\n", ticks);
    Serial.flush();
    delay(50);
    esp_deep_sleep_start();
    Serial.println("This will never be printed");
}

void loop() {
  // put your main code here, to run repeatedly:
  // Left blank because we are just deepsleeping
}

mightChamp avatar Jan 21 '23 07:01 mightChamp

@mightChamp Which version if the arduino-esp libs are you using since esp_deep_sleep.h and esp_deep_sleep_pd_config are showing up as deprecated?

Also, with this Arduino code is see this on the console:

OSC Not Enabled, using Internal 150KHz RC
Slow clk source: Internal 150kHz
Setup ESP32 to sleep for every 5 Seconds
Configured peripheral power
Going to sleep now, rtc ticks: 479008864

When building using the esp-idf and turning on the ext xtal I see this:

I (0) cpu_start: App cpu up.
W (375) clk: 32 kHz XTAL not found, switching to internal 150 kHz oscillator
I (383) cpu_start: Pro cpu start user code
I (383) cpu_start: cpu freq: 160000000 Hz

I've added a 32.768 xtal to io32/io33 at esp32 module and 2 10pf caps out on the GPIO32/GND and GPIO33/GND pin holes and can't get the xtal oscillating. Neither the Arduino code works nor esp-idf built source with xtal enabled via menuconfig.

I changed to: #include "esp_sleep.h" //"esp_deep_sleep.h" and //WAS esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);

but can't get the crystal to oscillate and be recognized. I've tried 2 different crystals and then I added 2 12pf caps off of the GPIO socket pins and ground on both sides of the D1 Mini NodeMCU ESP-WROOM-32 board and soldered 3 different crystals onto the 8th and 9th casillated pins on the left side of the module(IO32 and IO33) but neither the Arduino code above nor the esp-idf compiled code work and the esp-idf code has the ext 32.768kHz xtal enabled via menuconfig. I also just updated to esp-idf v5.0.

Anyone know what I might be doing wrong?

dlarue avatar Jan 21 '23 21:01 dlarue

I am using ESP32Arduino SDK V1.0.6 Release. and its working.

mightChamp avatar Mar 04 '23 11:03 mightChamp

@mightChamp what is V1.0.6 since Arduino_esp32 is at V2.0.7 and the esp-idf is at V5.0.1?

dlarue avatar Mar 04 '23 16:03 dlarue

Just for curiosity, why RTC Clock selection is taken as static from menu config from IDF, instead of api call, and set clock.

I found this issue via the same question. I have a module I want to use (LilyGO T4 v1.3 - https://www.lilygo.cc/products/t4-v1-3-ili9341-2-4-inch-lcd ) ... which uses GPIO32 for the screen DC control. I only want the crystal RTC when its sleeping, not when the screen is running, so dynamically switching the choice of slow clock would be very handy!

GregoryPye avatar Mar 10 '23 15:03 GregoryPye

Just for curiosity, why RTC Clock selection is taken as static from menu config from IDF, instead of api call, and set clock.

I found this issue via the same question. I have a module I want to use (LilyGO T4 v1.3 - https://www.lilygo.cc/products/t4-v1-3-ili9341-2-4-inch-lcd ) ... which uses GPIO32 for the screen DC control. I only want the crystal RTC when its sleeping, not when the screen is running, so dynamically switching the choice of slow clock would be very handy!

Then you'd definitely want to try using the example code @mightChamp posted which uses the API to set the clock target. You won't be able to use an ext Xtal since it must be on GPIO32/GPIO33 but you can try using the 40MHz clock and do some periodic calibrations to offset temp changes, etc which would cause drift.

I never found an errata about putting a 1M ohm resistor across the Xtal on the ESP32 Wrover Dev kit to get the ext Xtal working and I had already removed the Xtal and caps from the ESP32 WROOM module which I'd soldered components to GPIO32/GPIO33 to try before I purchased the ESP32-WROVER-Kit which had the components onboard.

And maybe all this is the reason why the module makers do not bring out the VDD3P3_RTC to a jumper or something which would have allowed an external battery for the RTC clock circuit.

dlarue avatar Mar 10 '23 16:03 dlarue

Thanks @dlarue . If I have some downtime sometime I might actually try with the external crystal as the system is happy with that all in circuit, just as long as it's not used by the ESP32. (suspect that active use as a GPIO more than sorts the tiny loads of the XTAL components). BTW, I used a 7.5M resistor across the XTAL which sorted the startup.

GregoryPye avatar Mar 10 '23 16:03 GregoryPye

BTW, I used a 7.5M resistor across the XTAL which sorted the startup.

Great news @GregoryPye , thx.

dlarue avatar Mar 10 '23 17:03 dlarue

First, thank you @mightChamp for opening this issue! An accurate RTC is a necessity for the large majority of low-power devices and the default 150kHz RC oscillator is too inaccurate for most applications. It would be great if Espressif would document operation with an external 32kHz crystal better.

I use ESP32-WROOM32 modules and found that older modules containing the ESP32-D0WDQ6 r1 require different loading than newer modules containing the ESP32 r3. I use ECS 6pF crystals (ECS-.327-6-34B-TR) and the ESP32 r1 requires at least 10pF load capacitors to start reliably; it does not start reliably with a parallel load resistor. However newer modules containing the ESP32 r3 require a parallel load resistor (2-10M) and will not start without it; frequency is near optimal (32768Hz) with 6pF load capacitors. The design uses good layout practices

So the XO is, apparently pretty finicky and, more problematically, appears to vary significantly between r1 and r3 of the ESP32. I have not been able to find any documentation about this from EspressIF; if anyone knows where that is, please add a comment. To be safe in your designs, it's probably smart to have pads for both load caps and a parallel R. Thanks!

dalbert2 avatar Apr 12 '24 04:04 dalbert2

From my experience It is not always starting up. Cap value = 2*(CL - Cstray) Depending on board layout Cstray anything from 1.5-5pF (Might be more if you run off a dev board with pins. On the ESP32 Wroom 32D we used 12.5pF Xtal with 2x 22pF caps without the 7.5M resistor (actually got it running but at slightly higher frequency with the R) = 2*(12.5 - 1.5)=22pF maybe 20pF would work better. For 6pF Crystal it would probably be closer to 2*(6-1.5) = 9pF but maybe even 8.2pF would be better. What helped most was adding the bootstrap before the enable rtc_clk_32k_bootstrap(10); rtc_clk_32k_enable(true); Otherwise it tend to be unstable if the reset pulse is short while it was running. We probably rather going to install crystal oscilator in future designs.

hannoHI avatar Apr 26 '24 15:04 hannoHI