Skip to content

Instantly share code, notes, and snippets.

@nevercast
Created Sep 10, 2020
Embed
What would you like to do?
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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment