Skip to content

Instantly share code, notes, and snippets.

@nevercast
Created September 10, 2020 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nevercast/1cc2efb719110bf2beb928013890992f to your computer and use it in GitHub Desktop.
Save nevercast/1cc2efb719110bf2beb928013890992f to your computer and use it in GitHub Desktop.
Several tests to run on an ESP32 for crystal, PSRAM and deepsleep stuff.
// If you're building this code from Arduino IDE, you wont need to import Arduino.h
#include "Arduino.h"
#include "soc/rtc.h"
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP ((60 * 60) * 1) /* Time ESP32 will go to sleep (in seconds) */
// Multiple ways to run this code to produce different reports
#define MODE_SLEEP 1
#define MODE_DIAG 2
#define MODE_CRYSTAL_START 3
#define MODE_TEST_HIMEM 4
#define MODE (MODE_CRYSTAL_START)
RTC_DATA_ATTR int bootCount = 0;
extern "C" {
#include <esp_spiram.h>
#include <esp_himem.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"
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 crystal_start_test() {
debug_xtal_out_dac1();
int stabilityCounter = 0;
unsigned long stableTime = 0;
float lastFreq = 0;
unsigned long testStartTime = millis();
bool locking = false; // Are we locking up the crystal
uint8_t head = 0;
int lockupCounter = 0;
int unstableCounter = 0;
unsigned long results[256];
while (true) {
float frequency = crystal_frequency();
// float delta = frequency - 32.768;
float delta = frequency - lastFreq;
if (delta < 0) delta = -delta;
if (delta > 0.001) {
stabilityCounter = 0;
stableTime = 0;
unstableCounter++;
if (unstableCounter > 20) {
printf("Freq: %.3f kHz. Waiting for stability.\n", frequency);
}
} else {
if (stabilityCounter == 0) {
stableTime = millis();
}
stabilityCounter++;
if (frequency == INFINITY) {
lockupCounter++;
} else {
unstableCounter = 0;
if (lockupCounter > 100) {
printf("Crystal has freed from lockup.\n");
}
lockupCounter = 0;
}
if (lockupCounter > 100) {
lockupCounter = 1;
printf("Crystal has locked up.\n");
head = 0;
stabilityCounter = 0;
locking = false;
rtc_clk_32k_enable(true);
}
if (stabilityCounter >= 10) {
if (locking && frequency == INFINITY) {
// printf("Crystal has been locked up, restarting test.\n");
// printf("Unlocking crystal.\n");
locking = false;
rtc_clk_32k_enable(true);
testStartTime = millis();
} else {
unsigned long stabilityDuration = stableTime - testStartTime;
printf("Freq: %.3f kHz. Crystal stable after %lums\n", frequency, stabilityDuration);
locking = true;
rtc_clk_32k_enable(false);
// Test passed, lock up the crystal and try restart it to test again.
pinMode(32, OUTPUT);
digitalWrite(32, LOW);
digitalWrite(32, HIGH);
pinMode(32, INPUT);
pinMode(33, OUTPUT);
digitalWrite(33, LOW);
delay(100);
pinMode(33, INPUT);
}
}
}
lastFreq = frequency;
}
}
void test_himem() {
esp_spiram_init();
printf("spiram size %u\n", esp_spiram_get_size());
printf("himem free %u\n", esp_himem_get_free_size());
printf("himem phys %u\n", esp_himem_get_phys_size());
printf("himem reserved %u\n", esp_himem_reserved_area_size());
}
void setup() {
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();
if (MODE == MODE_CRYSTAL_START) {
crystal_start_test();
} else if (MODE == MODE_TEST_HIMEM) {
test_himem();
} else {
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;
while (delta > 0.002) {
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;
}
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");
}
print_slow_clock_source();
if (MODE == MODE_SLEEP) {
/*
First we configure the wake up source
We set our ESP32 to wake up every 5 seconds
*/
esp_sleep_enable_timer_wakeup(UINT64_C(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");
for (volatile unsigned long long x = 0; x < 9999999; x++) ;
uint64_t ticks = rtc_time_get();
Serial.printf("Going to sleep now, rtc ticks: %llu\n", ticks);
Serial.flush();
esp_deep_sleep_start();
Serial.println("This will never be printed");
} else {
while (true) {
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
}
}
}
}
void loop() {
// put your main code here, to run repeatedly:
// Left blank because we are just deepsleeping
}
RTC_DATA_ATTR rtc_slow_freq_t deepsleep_stub_clk = RTC_SLOW_FREQ_RTC;
RTC_DATA_ATTR uint32_t deepsleep_stub_clk_calibration = 0;
// This code runs when waking from deepsleep, before any other line of code.
// Does not run for any other wake reason.
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
// This function sleeps for 2ms to ensure the SRAM has started. Else there can be memory corruption.
// That is the default behaviour, which now makes me think that the 5 second deepsleep is actually
// sleeping for 23 seconds, not 25. 23/5 is 4.6, the same ratio of 150/32.768. I love it when a plan comes together.
esp_default_wake_deep_sleep();
// Can't call any RTC functions here because the code exists in ROM which is unavailable.
deepsleep_stub_clk = (rtc_slow_freq_t)REG_GET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_ANA_CLK_RTC_SEL);
deepsleep_stub_clk_calibration = REG_READ(RTC_SLOW_CLK_CAL_REG);
// Strings must be statically allocated in the deepsleep stub.
static RTC_RODATA_ATTR const char calib_f[] = "DSCAL %u\n";
static RTC_RODATA_ATTR const char osc_150[] = "DSOSC 150kHz\n";
static RTC_RODATA_ATTR const char osc_32[] = "DSOSC 32kHz\n";
static RTC_RODATA_ATTR const char osc_8[] = "DSOSC 8MHz\n";
if (deepsleep_stub_clk == RTC_SLOW_FREQ_RTC) {
ets_printf(osc_150);
} else if (deepsleep_stub_clk == RTC_SLOW_FREQ_32K_XTAL) {
ets_printf(osc_32);
} else {
ets_printf(osc_8);
}
ets_printf(calib_f, deepsleep_stub_clk_calibration);
}
@mightChamp
Copy link

mightChamp commented Mar 4, 2023

Hello @nevercast , Thanks for this code.
I have used this code, any find very impressive results, drifting of rtc comes to maximum 1 second.

But also I am facing issue of fake wakeup by IO25 pin, on deepsleep.
sometime MCU wakeup caused by IO25, immediately after ESP32 goes to deep sleep.

I have tries to comment debug line:

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);
}

It causes reduction in fake detection on IO25, but still facing issue.

Without external crystal, there is no face interrupt wake on ESP32 by IO25.

So, Is IO25 used here for debugging of crystal, as I have seen some frequency on Oscilloscope at IO25 Pin when Init of crystal.
How to use IO25 as External Interrupt with above code.

Thank You

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment