Add support for 32k XTAL as RTC_SLOW_CLK source

- RTC_CNTL_SLOWCLK_FREQ define is removed; rtc_clk_slow_freq_get_hz
  function can be used instead to get an approximate RTC_SLOW_CLK
  frequency

- Clock calibration is performed at startup. The value is saved and used
  for timekeeping and when entering deep sleep.

- When using the 32k XTAL, startup code will wait for the oscillator to
  start up. This can be possibly optimized by starting a separate task
  to wait for oscillator startup, and performing clock switch in that
  task.

- Fix a bug that 32k XTAL would be disabled in rtc_clk_init.

- Fix a rounding error in rtc_clk_cal, which caused systematic frequency
  error.

- Fix an overflow bug which caused rtc_clk_cal to timeout early if the
  slow_clk_cycles argument would exceed certain value

- Improve 32k XTAL oscillator startup time by introducing bootstrapping
  code, which uses internal pullup/pulldown resistors on 32K_N/32K_P
  pins to set better initial conditions for the oscillator.
This commit is contained in:
Ivan Grokhotkov
2017-04-24 18:36:47 +08:00
parent 8131c77860
commit 6353bc40d7
16 changed files with 330 additions and 136 deletions

View File

@@ -542,6 +542,23 @@ config ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL
bool "External 32kHz crystal"
endchoice
config ESP32_RTC_CLK_CAL_CYCLES
int "Number of cycles for RTC_SLOW_CLK calibration"
default 1024
range 0 125000
help
When the startup code initializes RTC_SLOW_CLK, it can perform
calibration by comparing the RTC_SLOW_CLK frequency with main XTAL
frequency. This option sets the number of RTC_SLOW_CLK cycles measured
by the calibration routine. Higher numbers increase calibration
precision, which may be important for applications which spend a lot of
time in deep sleep. Lower numbers reduce startup time.
When this option is set to 0, clock calibration will not be performed at
startup, and approximate clock frequencies will be assumed:
- 150000 Hz if internal RC oscillator is used as clock source
- 32768 Hz if the 32k crystal oscillator is used
config ESP32_DEEP_SLEEP_WAKEUP_DELAY
int "Extra delay in deep sleep wake stub (in us)"
default 0

131
components/esp32/clk.c Normal file
View File

@@ -0,0 +1,131 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "rom/ets_sys.h"
#include "rom/uart.h"
#include "soc/soc.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
/* Number of cycles to wait from the 32k XTAL oscillator to consider it running.
* Larger values increase startup delay. Smaller values may cause false positive
* detection (i.e. oscillator runs for a few cycles and then stops).
*/
#define XTAL_32K_DETECT_CYCLES 32
#define SLOW_CLK_CAL_CYCLES CONFIG_ESP32_RTC_CLK_CAL_CYCLES
static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk);
static const char* TAG = "clk";
/*
* This function is not exposed as an API at this point,
* because FreeRTOS doesn't yet support dynamic changing of
* CPU frequency. Also we need to implement hooks for
* components which want to be notified of CPU frequency
* changes.
*/
void esp_clk_init(void)
{
rtc_config_t cfg = RTC_CONFIG_DEFAULT();
rtc_init(cfg);
#ifdef CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL
select_rtc_slow_clk(RTC_SLOW_FREQ_32K_XTAL);
#else
select_rtc_slow_clk(RTC_SLOW_FREQ_RTC);
#endif
uint32_t freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
rtc_cpu_freq_t freq = RTC_CPU_FREQ_80M;
switch(freq_mhz) {
case 240:
freq = RTC_CPU_FREQ_240M;
break;
case 160:
freq = RTC_CPU_FREQ_160M;
break;
default:
freq_mhz = 80;
/* no break */
case 80:
freq = RTC_CPU_FREQ_80M;
break;
}
// Wait for UART TX to finish, otherwise some UART output will be lost
// when switching APB frequency
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
rtc_clk_cpu_freq_set(freq);
}
void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us)
{
extern uint32_t g_ticks_per_us_pro; // g_ticks_us defined in ROM for PRO CPU
extern uint32_t g_ticks_per_us_app; // same defined for APP CPU
g_ticks_per_us_pro = ticks_per_us;
g_ticks_per_us_app = ticks_per_us;
}
/* This is a cached value of RTC slow clock period; it is updated by
* the select_rtc_slow_clk function at start up. This cached value is used in
* other places, like time syscalls and deep sleep.
*/
static uint32_t s_rtc_slow_clk_cal = 0;
static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk)
{
if (slow_clk == RTC_SLOW_FREQ_32K_XTAL) {
/* 32k XTAL oscillator needs to be enabled and running before it can
* be used. Hardware doesn't have a direct way of checking if the
* oscillator is running. Here we use rtc_clk_cal function to count
* the number of main XTAL cycles in the given number of 32k XTAL
* oscillator cycles. If the 32k XTAL has not started up, calibration
* will time out, returning 0.
*/
rtc_clk_32k_enable(true);
uint32_t cal_val = 0;
uint32_t wait = 0;
// increment of 'wait' counter equivalent to 3 seconds
const uint32_t warning_timeout = 3 /* sec */ * 32768 /* Hz */ / (2 * XTAL_32K_DETECT_CYCLES);
ESP_EARLY_LOGD(TAG, "waiting for 32k oscillator to start up")
do {
++wait;
cal_val = rtc_clk_cal(RTC_CAL_32K_XTAL, XTAL_32K_DETECT_CYCLES);
if (wait % warning_timeout == 0) {
ESP_EARLY_LOGW(TAG, "still waiting for 32k oscillator to start up");
}
} while (cal_val == 0);
ESP_EARLY_LOGD(TAG, "32k oscillator ready, wait=%d", wait);
}
rtc_clk_slow_freq_set(slow_clk);
if (SLOW_CLK_CAL_CYCLES > 0) {
/* TODO: 32k XTAL oscillator has some frequency drift at startup.
* Improve calibration routine to wait until the frequency is stable.
*/
s_rtc_slow_clk_cal = rtc_clk_cal(RTC_CAL_RTC_MUX, SLOW_CLK_CAL_CYCLES);
} else {
const uint64_t cal_dividend = (1ULL << RTC_CLK_CAL_FRACT) * 1000000ULL;
s_rtc_slow_clk_cal = (uint32_t) (cal_dividend / rtc_clk_slow_freq_get_hz());
}
ESP_EARLY_LOGD(TAG, "RTC_SLOW_CLK calibration value: %d", s_rtc_slow_clk_cal);
}
uint32_t esp_clk_slowclk_cal_get()
{
return s_rtc_slow_clk_cal;
}

View File

@@ -1,67 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include "esp_attr.h"
#include "rom/ets_sys.h"
#include "rom/uart.h"
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
/*
* This function is not exposed as an API at this point,
* because FreeRTOS doesn't yet support dynamic changing of
* CPU frequency. Also we need to implement hooks for
* components which want to be notified of CPU frequency
* changes.
*/
void esp_set_cpu_freq(void)
{
uint32_t freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
rtc_cpu_freq_t freq = RTC_CPU_FREQ_80M;
switch(freq_mhz) {
case 240:
freq = RTC_CPU_FREQ_240M;
break;
case 160:
freq = RTC_CPU_FREQ_160M;
break;
default:
freq_mhz = 80;
/* no break */
case 80:
freq = RTC_CPU_FREQ_80M;
break;
}
// Wait for UART TX to finish, otherwise some UART output will be lost
// when switching APB frequency
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
rtc_config_t cfg = RTC_CONFIG_DEFAULT();
rtc_init(cfg);
rtc_clk_cpu_freq_set(freq);
#if ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL
rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
#endif
}
void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us)
{
extern uint32_t g_ticks_per_us_pro; // g_ticks_us defined in ROM for PRO CPU
extern uint32_t g_ticks_per_us_app; // same defined for APP CPU
g_ticks_per_us_pro = ticks_per_us;
g_ticks_per_us_app = ticks_per_us;
}

View File

@@ -1,9 +1,9 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
@@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <string.h>
@@ -60,6 +61,7 @@
#include "esp_panic.h"
#include "esp_core_dump.h"
#include "esp_app_trace.h"
#include "esp_clk.h"
#include "trax.h"
#define STRINGIFY(s) STRINGIFY2(s)
@@ -202,7 +204,7 @@ void start_cpu0_default(void)
#endif
trax_start_trace(TRAX_DOWNCOUNT_WORDS);
#endif
esp_set_cpu_freq(); // set CPU frequency configured in menuconfig
esp_clk_init();
#ifndef CONFIG_CONSOLE_UART_NONE
uart_div_modify(CONFIG_CONSOLE_UART_NUM, (rtc_clk_apb_freq_get() << 4) / CONFIG_CONSOLE_UART_BAUDRATE);
#endif

View File

@@ -17,6 +17,7 @@
#include "esp_attr.h"
#include "esp_deep_sleep.h"
#include "esp_log.h"
#include "esp_clk.h"
#include "rom/cache.h"
#include "rom/rtc.h"
#include "rom/uart.h"
@@ -183,13 +184,7 @@ esp_err_t esp_deep_sleep_enable_timer_wakeup(uint64_t time_in_us)
static void timer_wakeup_prepare()
{
// Do calibration if not using 32k XTAL
uint32_t period;
if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) {
period = rtc_clk_cal(RTC_CAL_RTC_MUX, 128);
} else {
period = (uint32_t) ((1000000ULL /* us*Hz */ << RTC_CLK_CAL_FRACT) / 32768 /* Hz */);
}
uint32_t period = esp_clk_slowclk_cal_get();
uint64_t rtc_count_delta = rtc_time_us_to_slowclk(s_config.sleep_duration, period);
uint64_t cur_rtc_count = rtc_time_get();
rtc_sleep_set_wakeup_time(cur_rtc_count + rtc_count_delta);

View File

@@ -0,0 +1,44 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
/**
* @file esp_clk.h
*
* This file contains declarations of clock related functions.
* These functions are used in ESP-IDF components, but should not be considered
* to be part of public API.
*/
/**
* @brief Initialize clock-related settings
*
* Called from cpu_start.c, not intended to be called from other places.
* This function configures the CPU clock, RTC slow and fast clocks, and
* performs RTC slow clock calibration.
*/
void esp_clk_init(void);
/**
* @brief Get the cached calibration value of RTC slow clock
*
* The value is in the same format as returned by rtc_clk_cal (microseconds,
* in Q13.19 fixed-point format).
*
* @return the calibration value obtained using rtc_clk_cal, at startup time
*/
uint32_t esp_clk_slowclk_cal_get();

View File

@@ -317,7 +317,7 @@ static void esp_panic_wdt_start()
REG_SET_FIELD(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_STG0, RTC_WDT_STG_SEL_RESET_SYSTEM);
// 64KB of core dump data (stacks of about 30 tasks) will produce ~85KB base64 data.
// @ 115200 UART speed it will take more than 6 sec to print them out.
WRITE_PERI_REG(RTC_CNTL_WDTCONFIG1_REG, RTC_CNTL_SLOWCLK_FREQ*7);
WRITE_PERI_REG(RTC_CNTL_WDTCONFIG1_REG, rtc_clk_slow_freq_get_hz() * 7);
REG_SET_BIT(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_EN);
WRITE_PERI_REG(RTC_CNTL_WDTWPROTECT_REG, 0);
}