mirror of
https://github.com/espressif/esp-idf.git
synced 2025-09-30 19:19:21 +00:00
esp32: add implementation of esp_timer based on TG0 LAC timer
Closes: IDF-979
This commit is contained in:

committed by
Angus Gratton

parent
67b0a79167
commit
739eb05bb9
509
components/esp_timer/src/esp_timer.c
Normal file
509
components/esp_timer/src/esp_timer.c
Normal file
@@ -0,0 +1,509 @@
|
||||
// Copyright 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 <sys/param.h>
|
||||
#include <string.h>
|
||||
#include "soc/soc.h"
|
||||
#include "esp_types.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_task.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_timer_impl.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
#ifdef CONFIG_ESP_TIMER_PROFILING
|
||||
#define WITH_PROFILING 1
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Enable built-in checks in queue.h in debug builds
|
||||
#define INVARIANTS
|
||||
#endif
|
||||
#include "sys/queue.h"
|
||||
|
||||
#define EVENT_ID_DELETE_TIMER 0xF0DE1E1E
|
||||
|
||||
#define TIMER_EVENT_QUEUE_SIZE 16
|
||||
|
||||
struct esp_timer {
|
||||
uint64_t alarm;
|
||||
uint64_t period;
|
||||
union {
|
||||
esp_timer_cb_t callback;
|
||||
uint32_t event_id;
|
||||
};
|
||||
void* arg;
|
||||
#if WITH_PROFILING
|
||||
const char* name;
|
||||
size_t times_triggered;
|
||||
size_t times_armed;
|
||||
uint64_t total_callback_run_time;
|
||||
#endif // WITH_PROFILING
|
||||
LIST_ENTRY(esp_timer) list_entry;
|
||||
};
|
||||
|
||||
static bool is_initialized(void);
|
||||
static esp_err_t timer_insert(esp_timer_handle_t timer);
|
||||
static esp_err_t timer_remove(esp_timer_handle_t timer);
|
||||
static bool timer_armed(esp_timer_handle_t timer);
|
||||
static void timer_list_lock(void);
|
||||
static void timer_list_unlock(void);
|
||||
|
||||
#if WITH_PROFILING
|
||||
static void timer_insert_inactive(esp_timer_handle_t timer);
|
||||
static void timer_remove_inactive(esp_timer_handle_t timer);
|
||||
#endif // WITH_PROFILING
|
||||
|
||||
static const char* TAG = "esp_timer";
|
||||
|
||||
// list of currently armed timers
|
||||
static LIST_HEAD(esp_timer_list, esp_timer) s_timers =
|
||||
LIST_HEAD_INITIALIZER(s_timers);
|
||||
#if WITH_PROFILING
|
||||
// list of unarmed timers, used only to be able to dump statistics about
|
||||
// all the timers
|
||||
static LIST_HEAD(esp_inactive_timer_list, esp_timer) s_inactive_timers =
|
||||
LIST_HEAD_INITIALIZER(s_timers);
|
||||
#endif
|
||||
// task used to dispatch timer callbacks
|
||||
static TaskHandle_t s_timer_task;
|
||||
// counting semaphore used to notify the timer task from ISR
|
||||
static SemaphoreHandle_t s_timer_semaphore;
|
||||
|
||||
#if CONFIG_SPIRAM_USE_MALLOC
|
||||
// memory for s_timer_semaphore
|
||||
static StaticQueue_t s_timer_semaphore_memory;
|
||||
#endif
|
||||
|
||||
// lock protecting s_timers, s_inactive_timers
|
||||
static portMUX_TYPE s_timer_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
|
||||
|
||||
esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
|
||||
esp_timer_handle_t* out_handle)
|
||||
{
|
||||
if (!is_initialized()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (args == NULL || args->callback == NULL || out_handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_timer_handle_t result = (esp_timer_handle_t) calloc(1, sizeof(*result));
|
||||
if (result == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
result->callback = args->callback;
|
||||
result->arg = args->arg;
|
||||
#if WITH_PROFILING
|
||||
result->name = args->name;
|
||||
timer_insert_inactive(result);
|
||||
#endif
|
||||
*out_handle = result;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t IRAM_ATTR esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_initialized() || timer_armed(timer)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
timer_list_lock();
|
||||
timer->alarm = esp_timer_get_time() + timeout_us;
|
||||
timer->period = 0;
|
||||
#if WITH_PROFILING
|
||||
timer->times_armed++;
|
||||
#endif
|
||||
esp_err_t err = timer_insert(timer);
|
||||
timer_list_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t IRAM_ATTR esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period_us)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_initialized() || timer_armed(timer)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
timer_list_lock();
|
||||
period_us = MAX(period_us, esp_timer_impl_get_min_period_us());
|
||||
timer->alarm = esp_timer_get_time() + period_us;
|
||||
timer->period = period_us;
|
||||
#if WITH_PROFILING
|
||||
timer->times_armed++;
|
||||
#endif
|
||||
esp_err_t err = timer_insert(timer);
|
||||
timer_list_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_initialized() || !timer_armed(timer)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return timer_remove(timer);
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (timer_armed(timer)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
timer_list_lock();
|
||||
timer->event_id = EVENT_ID_DELETE_TIMER;
|
||||
timer->alarm = esp_timer_get_time();
|
||||
timer->period = 0;
|
||||
timer_insert(timer);
|
||||
timer_list_unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer)
|
||||
{
|
||||
#if WITH_PROFILING
|
||||
timer_remove_inactive(timer);
|
||||
#endif
|
||||
esp_timer_handle_t it, last = NULL;
|
||||
if (LIST_FIRST(&s_timers) == NULL) {
|
||||
LIST_INSERT_HEAD(&s_timers, timer, list_entry);
|
||||
} else {
|
||||
LIST_FOREACH(it, &s_timers, list_entry) {
|
||||
if (timer->alarm < it->alarm) {
|
||||
LIST_INSERT_BEFORE(it, timer, list_entry);
|
||||
break;
|
||||
}
|
||||
last = it;
|
||||
}
|
||||
if (it == NULL) {
|
||||
assert(last);
|
||||
LIST_INSERT_AFTER(last, timer, list_entry);
|
||||
}
|
||||
}
|
||||
if (timer == LIST_FIRST(&s_timers)) {
|
||||
esp_timer_impl_set_alarm(timer->alarm);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
|
||||
{
|
||||
timer_list_lock();
|
||||
LIST_REMOVE(timer, list_entry);
|
||||
timer->alarm = 0;
|
||||
timer->period = 0;
|
||||
#if WITH_PROFILING
|
||||
timer_insert_inactive(timer);
|
||||
#endif
|
||||
timer_list_unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if WITH_PROFILING
|
||||
|
||||
static IRAM_ATTR void timer_insert_inactive(esp_timer_handle_t timer)
|
||||
{
|
||||
/* May be locked or not, depending on where this is called from.
|
||||
* Lock recursively.
|
||||
*/
|
||||
timer_list_lock();
|
||||
esp_timer_handle_t head = LIST_FIRST(&s_inactive_timers);
|
||||
if (head == NULL) {
|
||||
LIST_INSERT_HEAD(&s_inactive_timers, timer, list_entry);
|
||||
} else {
|
||||
/* Insert as head element as this is the fastest thing to do.
|
||||
* Removal is O(1) anyway.
|
||||
*/
|
||||
LIST_INSERT_BEFORE(head, timer, list_entry);
|
||||
}
|
||||
timer_list_unlock();
|
||||
}
|
||||
|
||||
static IRAM_ATTR void timer_remove_inactive(esp_timer_handle_t timer)
|
||||
{
|
||||
timer_list_lock();
|
||||
LIST_REMOVE(timer, list_entry);
|
||||
timer_list_unlock();
|
||||
}
|
||||
|
||||
#endif // WITH_PROFILING
|
||||
|
||||
static IRAM_ATTR bool timer_armed(esp_timer_handle_t timer)
|
||||
{
|
||||
return timer->alarm > 0;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void timer_list_lock(void)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_timer_lock);
|
||||
}
|
||||
|
||||
static IRAM_ATTR void timer_list_unlock(void)
|
||||
{
|
||||
portEXIT_CRITICAL_SAFE(&s_timer_lock);
|
||||
}
|
||||
|
||||
static void timer_process_alarm(esp_timer_dispatch_t dispatch_method)
|
||||
{
|
||||
/* unused, provision to allow running callbacks from ISR */
|
||||
(void) dispatch_method;
|
||||
|
||||
timer_list_lock();
|
||||
uint64_t now = esp_timer_impl_get_time();
|
||||
esp_timer_handle_t it = LIST_FIRST(&s_timers);
|
||||
while (it != NULL &&
|
||||
it->alarm < now) {
|
||||
LIST_REMOVE(it, list_entry);
|
||||
if (it->event_id == EVENT_ID_DELETE_TIMER) {
|
||||
free(it);
|
||||
it = LIST_FIRST(&s_timers);
|
||||
continue;
|
||||
}
|
||||
if (it->period > 0) {
|
||||
it->alarm += it->period;
|
||||
timer_insert(it);
|
||||
} else {
|
||||
it->alarm = 0;
|
||||
#if WITH_PROFILING
|
||||
timer_insert_inactive(it);
|
||||
#endif
|
||||
}
|
||||
#if WITH_PROFILING
|
||||
uint64_t callback_start = now;
|
||||
#endif
|
||||
esp_timer_cb_t callback = it->callback;
|
||||
void* arg = it->arg;
|
||||
timer_list_unlock();
|
||||
(*callback)(arg);
|
||||
timer_list_lock();
|
||||
now = esp_timer_impl_get_time();
|
||||
#if WITH_PROFILING
|
||||
it->times_triggered++;
|
||||
it->total_callback_run_time += now - callback_start;
|
||||
#endif
|
||||
it = LIST_FIRST(&s_timers);
|
||||
}
|
||||
esp_timer_handle_t first = LIST_FIRST(&s_timers);
|
||||
if (first) {
|
||||
esp_timer_impl_set_alarm(first->alarm);
|
||||
}
|
||||
timer_list_unlock();
|
||||
}
|
||||
|
||||
static void timer_task(void* arg)
|
||||
{
|
||||
while (true){
|
||||
int res = xSemaphoreTake(s_timer_semaphore, portMAX_DELAY);
|
||||
assert(res == pdTRUE);
|
||||
timer_process_alarm(ESP_TIMER_TASK);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timer_alarm_handler(void* arg)
|
||||
{
|
||||
int need_yield;
|
||||
if (xSemaphoreGiveFromISR(s_timer_semaphore, &need_yield) != pdPASS) {
|
||||
ESP_EARLY_LOGD(TAG, "timer queue overflow");
|
||||
return;
|
||||
}
|
||||
if (need_yield == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
static IRAM_ATTR bool is_initialized(void)
|
||||
{
|
||||
return s_timer_task != NULL;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_timer_init(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
if (is_initialized()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
#if CONFIG_SPIRAM_USE_MALLOC
|
||||
memset(&s_timer_semaphore_memory, 0, sizeof(StaticQueue_t));
|
||||
s_timer_semaphore = xSemaphoreCreateCountingStatic(TIMER_EVENT_QUEUE_SIZE, 0, &s_timer_semaphore_memory);
|
||||
#else
|
||||
s_timer_semaphore = xSemaphoreCreateCounting(TIMER_EVENT_QUEUE_SIZE, 0);
|
||||
#endif
|
||||
if (!s_timer_semaphore) {
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
int ret = xTaskCreatePinnedToCore(&timer_task, "esp_timer",
|
||||
ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO, &s_timer_task, PRO_CPU_NUM);
|
||||
if (ret != pdPASS) {
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = esp_timer_impl_init(&timer_alarm_handler);
|
||||
if (err != ESP_OK) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
out:
|
||||
if (s_timer_task) {
|
||||
vTaskDelete(s_timer_task);
|
||||
s_timer_task = NULL;
|
||||
}
|
||||
if (s_timer_semaphore) {
|
||||
vSemaphoreDelete(s_timer_semaphore);
|
||||
s_timer_semaphore = NULL;
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_deinit(void)
|
||||
{
|
||||
if (!is_initialized()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Check if there are any active timers */
|
||||
if (!LIST_EMPTY(&s_timers)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* We can only check if there are any timers which are not deleted if
|
||||
* profiling is enabled.
|
||||
*/
|
||||
#if WITH_PROFILING
|
||||
if (!LIST_EMPTY(&s_inactive_timers)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_timer_impl_deinit();
|
||||
|
||||
vTaskDelete(s_timer_task);
|
||||
s_timer_task = NULL;
|
||||
vSemaphoreDelete(s_timer_semaphore);
|
||||
s_timer_semaphore = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void print_timer_info(esp_timer_handle_t t, char** dst, size_t* dst_size)
|
||||
{
|
||||
size_t cb = snprintf(*dst, *dst_size,
|
||||
#if WITH_PROFILING
|
||||
"%-12s %12lld %12lld %9d %9d %12lld\n",
|
||||
t->name, t->period, t->alarm,
|
||||
t->times_armed, t->times_triggered, t->total_callback_run_time);
|
||||
/* keep this in sync with the format string, used in esp_timer_dump */
|
||||
#define TIMER_INFO_LINE_LEN 78
|
||||
#else
|
||||
"timer@%p %12lld %12lld\n", t, t->period, t->alarm);
|
||||
#define TIMER_INFO_LINE_LEN 46
|
||||
#endif
|
||||
*dst += cb;
|
||||
*dst_size -= cb;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t esp_timer_dump(FILE* stream)
|
||||
{
|
||||
/* Since timer lock is a critical section, we don't want to print directly
|
||||
* to stdout, since that may cause a deadlock if stdout is interrupt-driven
|
||||
* (via the UART driver). Allocate sufficiently large chunk of memory first,
|
||||
* print to it, then dump this memory to stdout.
|
||||
*/
|
||||
|
||||
esp_timer_handle_t it;
|
||||
|
||||
/* First count the number of timers */
|
||||
size_t timer_count = 0;
|
||||
timer_list_lock();
|
||||
LIST_FOREACH(it, &s_timers, list_entry) {
|
||||
++timer_count;
|
||||
}
|
||||
#if WITH_PROFILING
|
||||
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
|
||||
++timer_count;
|
||||
}
|
||||
#endif
|
||||
timer_list_unlock();
|
||||
|
||||
/* Allocate the memory for this number of timers. Since we have unlocked,
|
||||
* we may find that there are more timers. There's no bulletproof solution
|
||||
* for this (can't allocate from a critical section), but we allocate
|
||||
* slightly more and the output will be truncated if that is not enough.
|
||||
*/
|
||||
size_t buf_size = TIMER_INFO_LINE_LEN * (timer_count + 3);
|
||||
char* print_buf = calloc(1, buf_size + 1);
|
||||
if (print_buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/* Print to the buffer */
|
||||
timer_list_lock();
|
||||
char* pos = print_buf;
|
||||
LIST_FOREACH(it, &s_timers, list_entry) {
|
||||
print_timer_info(it, &pos, &buf_size);
|
||||
}
|
||||
#if WITH_PROFILING
|
||||
LIST_FOREACH(it, &s_inactive_timers, list_entry) {
|
||||
print_timer_info(it, &pos, &buf_size);
|
||||
}
|
||||
#endif
|
||||
timer_list_unlock();
|
||||
|
||||
/* Print the buffer */
|
||||
fputs(print_buf, stream);
|
||||
|
||||
free(print_buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int64_t IRAM_ATTR esp_timer_get_next_alarm(void)
|
||||
{
|
||||
int64_t next_alarm = INT64_MAX;
|
||||
timer_list_lock();
|
||||
esp_timer_handle_t it = LIST_FIRST(&s_timers);
|
||||
if (it) {
|
||||
next_alarm = it->alarm;
|
||||
}
|
||||
timer_list_unlock();
|
||||
return next_alarm;
|
||||
}
|
||||
|
||||
int64_t IRAM_ATTR esp_timer_get_time(void)
|
||||
{
|
||||
return (int64_t) esp_timer_impl_get_time();
|
||||
}
|
432
components/esp_timer/src/esp_timer_impl_frc_legacy.c
Normal file
432
components/esp_timer/src/esp_timer_impl_frc_legacy.c
Normal file
@@ -0,0 +1,432 @@
|
||||
// Copyright 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 "sys/param.h"
|
||||
#include "esp_timer_impl.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_task.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp32/clk.h"
|
||||
#include "soc/frc_timer_reg.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
/**
|
||||
* @file esp_timer_frc.c
|
||||
* @brief Implementation of chip-specific part of esp_timer
|
||||
*
|
||||
* This implementation uses FRC2 (legacy) timer of the ESP32. This timer is
|
||||
* a 32-bit up-counting timer, with a programmable compare value (called 'alarm'
|
||||
* hereafter). When the timer reaches compare value, interrupt is raised.
|
||||
* The timer can be configured to produce an edge or a level interrupt.
|
||||
*
|
||||
* In this implementation the timer is used for two purposes:
|
||||
* 1. To generate interrupts at certain moments — the upper layer of esp_timer
|
||||
* uses this to trigger callbacks of esp_timer objects.
|
||||
*
|
||||
* 2. To keep track of time relative to application start. This facility is
|
||||
* used both by the upper layer of esp_timer and by time functions, such as
|
||||
* gettimeofday.
|
||||
*
|
||||
* Whenever an esp_timer timer is armed (configured to fire once or
|
||||
* periodically), timer_insert function of the upper layer calls
|
||||
* esp_timer_impl_set_alarm to enable the interrupt at the required moment.
|
||||
* This implementation sets up the timer interrupt to fire at the earliest of
|
||||
* two moments:
|
||||
* a) the time requested by upper layer
|
||||
* b) the time when the timer count reaches 0xffffffff (i.e. is about to overflow)
|
||||
*
|
||||
* Whenever the interrupt fires and timer overflow is detected, interrupt hander
|
||||
* increments s_time_base_us variable, which is used for timekeeping.
|
||||
*
|
||||
* When the interrupt fires, the upper layer is notified, and it dispatches
|
||||
* the callbacks (if any timers have expired) and sets new alarm value (if any
|
||||
* timers are still active).
|
||||
*
|
||||
* At any point in time, esp_timer_impl_get_time will return the current timer
|
||||
* value (expressed in microseconds) plus s_time_base_us. To account for the
|
||||
* case when the timer counter has overflown, but the interrupt has not fired
|
||||
* yet (for example, because interupts are temporarily disabled),
|
||||
* esp_timer_impl_get_time will also check timer overflow flag, and will add
|
||||
* s_timer_us_per_overflow to the returned value.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Timer is clocked from APB. To allow for integer scaling factor between ticks
|
||||
* and microseconds, divider 1 is used. 16 or 256 would not work for APB
|
||||
* frequencies such as 40 or 26 or 2 MHz.
|
||||
*/
|
||||
#define TIMER_DIV 1
|
||||
#define TIMER_DIV_CFG FRC_TIMER_PRESCALER_1
|
||||
|
||||
/* ALARM_OVERFLOW_VAL is used as timer alarm value when there are not timers
|
||||
* enabled which need to fire within the next timer overflow period. This alarm
|
||||
* is used to perform timekeeping (i.e. to track timer overflows).
|
||||
* Due to the 0xffffffff cannot recognize the real overflow or the scenario that
|
||||
* ISR happens follow set_alarm, so change the ALARM_OVERFLOW_VAL to resolve this problem.
|
||||
* Set it to 0xefffffffUL. The remain 0x10000000UL(about 3 second) is enough to handle ISR.
|
||||
*/
|
||||
#define DEFAULT_ALARM_OVERFLOW_VAL 0xefffffffUL
|
||||
|
||||
/* Provision to set lower overflow value for unit testing. Lowering the
|
||||
* overflow value helps check for race conditions which occur near overflow
|
||||
* moment.
|
||||
*/
|
||||
#ifndef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
|
||||
#define ALARM_OVERFLOW_VAL DEFAULT_ALARM_OVERFLOW_VAL
|
||||
#else
|
||||
static uint32_t s_alarm_overflow_val = DEFAULT_ALARM_OVERFLOW_VAL;
|
||||
#define ALARM_OVERFLOW_VAL (s_alarm_overflow_val)
|
||||
#endif
|
||||
|
||||
static const char* TAG = "esp_timer_impl";
|
||||
|
||||
// Interrupt handle returned by the interrupt allocator
|
||||
static intr_handle_t s_timer_interrupt_handle;
|
||||
|
||||
// Function from the upper layer to be called when the interrupt happens.
|
||||
// Registered in esp_timer_impl_init.
|
||||
static intr_handler_t s_alarm_handler;
|
||||
|
||||
// Time in microseconds from startup to the moment
|
||||
// when timer counter was last equal to 0. This variable is updated each time
|
||||
// when timer overflows, and when APB frequency switch is performed.
|
||||
static uint64_t s_time_base_us;
|
||||
|
||||
// Number of timer ticks per microsecond. Calculated from APB frequency.
|
||||
static uint32_t s_timer_ticks_per_us;
|
||||
|
||||
// Period between timer overflows, in microseconds.
|
||||
// Equal to 2^32 / s_timer_ticks_per_us.
|
||||
static uint32_t s_timer_us_per_overflow;
|
||||
|
||||
// When frequency switch happens, timer counter is reset to 0, s_time_base_us
|
||||
// is updated, and alarm value is re-calculated based on the new APB frequency.
|
||||
// However because the frequency switch can happen before the final
|
||||
// interrupt handler is invoked, interrupt handler may see a different alarm
|
||||
// value than the one which caused an interrupt. This can cause interrupt handler
|
||||
// to consider that the interrupt has happened due to timer overflow, incrementing
|
||||
// s_time_base_us. To avoid this, frequency switch hook sets this flag if
|
||||
// it needs to set timer alarm value to ALARM_OVERFLOW_VAL. Interrupt handler
|
||||
// will not increment s_time_base_us if this flag is set.
|
||||
static bool s_mask_overflow;
|
||||
|
||||
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
|
||||
// If DFS is enabled, upon the first frequency change this value is set to the
|
||||
// difference between esp_timer value and RTC timer value. On every subsequent
|
||||
// frequency change, s_time_base_us is adjusted to maintain the same difference
|
||||
// between esp_timer and RTC timer. (All mentioned values are in microseconds.)
|
||||
static uint64_t s_rtc_time_diff = 0;
|
||||
#endif
|
||||
|
||||
// Spinlock used to protect access to static variables above and to the hardware
|
||||
// registers.
|
||||
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
//Use FRC_TIMER_LOAD_VALUE(1) instead of UINT32_MAX, convenience to change FRC TIMER for future
|
||||
#define TIMER_IS_AFTER_OVERFLOW(a) (ALARM_OVERFLOW_VAL < (a) && (a) <= FRC_TIMER_LOAD_VALUE(1))
|
||||
|
||||
// Check if timer overflow has happened (but was not handled by ISR yet)
|
||||
static inline bool IRAM_ATTR timer_overflow_happened(void)
|
||||
{
|
||||
return ((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) != 0 &&
|
||||
((REG_READ(FRC_TIMER_ALARM_REG(1)) == ALARM_OVERFLOW_VAL && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))) && !s_mask_overflow) ||
|
||||
(!TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_ALARM_REG(1))) && TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))))));
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR timer_count_reload(void)
|
||||
{
|
||||
//this function should be only called the real overflow happened. And the count cannot be very approach to 0xffffffff.
|
||||
assert(TIMER_IS_AFTER_OVERFLOW(REG_READ(FRC_TIMER_COUNT_REG(1))));
|
||||
|
||||
/* Restart the timer count by current time count minus ALARM_OVERFLOW_VAL(0xefffffff), it may cause error, if current tick is near boundary.
|
||||
* But even if the error happen 100% per overflow(the distance of each real overflow is about 50 second),
|
||||
* the error is 0.0125us*N per 50s(the FRC time clock is 80MHz), the N is the ticks run by the line following,
|
||||
* Normally, N is less than 10, assume N is 10, so the error accumulation is only 6.48ms per month.
|
||||
* In fact, if the CPU frequency is large than 80MHz. The error accumulation will be more less than 6.48ms per month.
|
||||
* so It can be adopted.
|
||||
*/
|
||||
REG_WRITE(FRC_TIMER_LOAD_REG(1), REG_READ(FRC_TIMER_COUNT_REG(1)) - ALARM_OVERFLOW_VAL);
|
||||
}
|
||||
|
||||
void esp_timer_impl_lock(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
void esp_timer_impl_unlock(void)
|
||||
{
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
|
||||
{
|
||||
uint32_t timer_val;
|
||||
uint64_t time_base;
|
||||
uint32_t ticks_per_us;
|
||||
bool overflow;
|
||||
|
||||
do {
|
||||
/* Read all values needed to calculate current time */
|
||||
timer_val = REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
time_base = s_time_base_us;
|
||||
overflow = timer_overflow_happened();
|
||||
ticks_per_us = s_timer_ticks_per_us;
|
||||
|
||||
/* Read them again and compare */
|
||||
/* In this function, do not call timer_count_reload() when overflow is true.
|
||||
* Because there's remain count enough to allow FRC_TIMER_COUNT_REG grow
|
||||
*/
|
||||
if (REG_READ(FRC_TIMER_COUNT_REG(1)) > timer_val &&
|
||||
time_base == *((volatile uint64_t*) &s_time_base_us) &&
|
||||
ticks_per_us == *((volatile uint32_t*) &s_timer_ticks_per_us) &&
|
||||
overflow == timer_overflow_happened()) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If any value has changed (other than the counter increasing), read again */
|
||||
} while(true);
|
||||
|
||||
uint64_t result = time_base
|
||||
+ timer_val / ticks_per_us;
|
||||
return result;
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
// Use calculated alarm value if it is less than ALARM_OVERFLOW_VAL.
|
||||
// Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
|
||||
// interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
|
||||
// so need to check if alarm value is too close in the future (e.g. <2 us away).
|
||||
int32_t offset = s_timer_ticks_per_us * 2;
|
||||
do {
|
||||
// Adjust current time if overflow has happened
|
||||
if (timer_overflow_happened() ||
|
||||
((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
|
||||
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
|
||||
// 1. timer_overflow_happened() checks overflow with the interrupt flag.
|
||||
// 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
|
||||
timer_count_reload();
|
||||
s_time_base_us += s_timer_us_per_overflow;
|
||||
}
|
||||
s_mask_overflow = false;
|
||||
int64_t cur_count = REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
// Alarm time relative to the moment when counter was 0
|
||||
int64_t time_after_timebase_us = (int64_t)timestamp - s_time_base_us;
|
||||
// Calculate desired timer compare value (may exceed 2^32-1)
|
||||
int64_t compare_val = time_after_timebase_us * s_timer_ticks_per_us;
|
||||
|
||||
compare_val = MAX(compare_val, cur_count + offset);
|
||||
uint32_t alarm_reg_val = ALARM_OVERFLOW_VAL;
|
||||
if (compare_val < ALARM_OVERFLOW_VAL) {
|
||||
alarm_reg_val = (uint32_t) compare_val;
|
||||
}
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
|
||||
int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
if (delta <= 0) {
|
||||
/*
|
||||
When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
|
||||
But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
|
||||
(the alarm will be less than the counter) and it leads to the infinity loop.
|
||||
To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
|
||||
*/
|
||||
offset += abs((int)delta) + s_timer_ticks_per_us * 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timer_alarm_isr(void *arg)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&s_time_update_lock);
|
||||
// Timekeeping: adjust s_time_base_us if counter has passed ALARM_OVERFLOW_VAL
|
||||
if (timer_overflow_happened()) {
|
||||
timer_count_reload();
|
||||
s_time_base_us += s_timer_us_per_overflow;
|
||||
}
|
||||
s_mask_overflow = false;
|
||||
// Clear interrupt status
|
||||
REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR);
|
||||
// Set alarm to the next overflow moment. Later, upper layer function may
|
||||
// call esp_timer_impl_set_alarm to change this to an earlier value.
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL);
|
||||
if ((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
|
||||
((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0)) {
|
||||
/*
|
||||
This check excludes the case when the alarm can be less than the counter.
|
||||
Without this check, it is possible because DPORT uses 4-lvl, and users can use the 5 Hi-interrupt,
|
||||
they can interrupt this function between FRC_TIMER_INT_CLR and setting the alarm = ALARM_OVERFLOW_VAL
|
||||
that lead to the counter will go ahead leaving the alarm behind.
|
||||
*/
|
||||
timer_count_reload();
|
||||
s_time_base_us += s_timer_us_per_overflow;
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&s_time_update_lock);
|
||||
// Call the upper layer handler
|
||||
(*s_alarm_handler)(arg);
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&s_time_update_lock);
|
||||
/* Bail out if the timer is not initialized yet */
|
||||
if (s_timer_interrupt_handle == NULL) {
|
||||
portEXIT_CRITICAL_ISR(&s_time_update_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t new_ticks_per_us = apb_ticks_per_us / TIMER_DIV;
|
||||
uint32_t alarm = REG_READ(FRC_TIMER_ALARM_REG(1));
|
||||
uint32_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
uint64_t ticks_to_alarm = alarm - count;
|
||||
uint64_t new_ticks = (ticks_to_alarm * new_ticks_per_us) / s_timer_ticks_per_us;
|
||||
uint32_t new_alarm_val;
|
||||
if (alarm > count && new_ticks <= ALARM_OVERFLOW_VAL) {
|
||||
new_alarm_val = new_ticks;
|
||||
} else {
|
||||
new_alarm_val = ALARM_OVERFLOW_VAL;
|
||||
if (alarm != ALARM_OVERFLOW_VAL) {
|
||||
s_mask_overflow = true;
|
||||
}
|
||||
}
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), new_alarm_val);
|
||||
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
|
||||
|
||||
s_time_base_us += count / s_timer_ticks_per_us;
|
||||
|
||||
#ifdef CONFIG_PM_DFS_USE_RTC_TIMER_REF
|
||||
// Due to the extra time required to read RTC time, don't attempt this
|
||||
// adjustment when switching to a higher frequency (which usually
|
||||
// happens in an interrupt).
|
||||
if (new_ticks_per_us < s_timer_ticks_per_us) {
|
||||
uint64_t rtc_time = esp_clk_rtc_time();
|
||||
uint64_t new_rtc_time_diff = s_time_base_us - rtc_time;
|
||||
if (s_rtc_time_diff != 0) {
|
||||
uint64_t correction = new_rtc_time_diff - s_rtc_time_diff;
|
||||
s_time_base_us -= correction;
|
||||
} else {
|
||||
s_rtc_time_diff = new_rtc_time_diff;
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_PM_DFS_USE_RTC_TIMER_REF
|
||||
|
||||
s_timer_ticks_per_us = new_ticks_per_us;
|
||||
s_timer_us_per_overflow = ALARM_OVERFLOW_VAL / new_ticks_per_us;
|
||||
|
||||
portEXIT_CRITICAL_ISR(&s_time_update_lock);
|
||||
}
|
||||
|
||||
void esp_timer_impl_advance(int64_t time_us)
|
||||
{
|
||||
assert(time_us > 0 && "negative adjustments not supported yet");
|
||||
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
uint64_t count = REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
/* Trigger an ISR to handle past alarms and set new one.
|
||||
* ISR handler will run once we exit the critical section.
|
||||
*/
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), 0);
|
||||
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
|
||||
s_time_base_us += count / s_timer_ticks_per_us + time_us;
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
|
||||
{
|
||||
s_alarm_handler = alarm_handler;
|
||||
|
||||
esp_err_t err = esp_intr_alloc(ETS_TIMER2_INTR_SOURCE,
|
||||
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM,
|
||||
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
uint32_t apb_freq = rtc_clk_apb_freq_get();
|
||||
s_timer_ticks_per_us = apb_freq / 1000000 / TIMER_DIV;
|
||||
assert(s_timer_ticks_per_us > 0
|
||||
&& apb_freq % TIMER_DIV == 0
|
||||
&& "APB frequency does not result in a valid ticks_per_us value");
|
||||
s_timer_us_per_overflow = ALARM_OVERFLOW_VAL / s_timer_ticks_per_us;
|
||||
s_time_base_us = 0;
|
||||
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL);
|
||||
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
|
||||
REG_WRITE(FRC_TIMER_CTRL_REG(1),
|
||||
TIMER_DIV_CFG | FRC_TIMER_ENABLE | FRC_TIMER_LEVEL_INT);
|
||||
REG_WRITE(FRC_TIMER_INT_REG(1), FRC_TIMER_INT_CLR);
|
||||
ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) );
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_timer_impl_deinit(void)
|
||||
{
|
||||
esp_intr_disable(s_timer_interrupt_handle);
|
||||
|
||||
REG_WRITE(FRC_TIMER_CTRL_REG(1), 0);
|
||||
REG_WRITE(FRC_TIMER_ALARM_REG(1), 0);
|
||||
REG_WRITE(FRC_TIMER_LOAD_REG(1), 0);
|
||||
|
||||
esp_intr_free(s_timer_interrupt_handle);
|
||||
s_timer_interrupt_handle = NULL;
|
||||
}
|
||||
|
||||
// FIXME: This value is safe for 80MHz APB frequency.
|
||||
// Should be modified to depend on clock frequency.
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
#ifdef ESP_TIMER_DYNAMIC_OVERFLOW_VAL
|
||||
uint32_t esp_timer_impl_get_overflow_val(void)
|
||||
{
|
||||
return s_alarm_overflow_val;
|
||||
}
|
||||
|
||||
void esp_timer_impl_set_overflow_val(uint32_t overflow_val)
|
||||
{
|
||||
s_alarm_overflow_val = overflow_val;
|
||||
/* update alarm value */
|
||||
esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
|
||||
}
|
||||
#endif // ESP_TIMER_DYNAMIC_OVERFLOW_VAL
|
||||
|
||||
uint64_t esp_timer_impl_get_counter_reg(void)
|
||||
{
|
||||
return (uint64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
|
||||
}
|
||||
|
||||
uint64_t esp_timer_impl_get_alarm_reg(void)
|
||||
{
|
||||
return (uint64_t)REG_READ(FRC_TIMER_ALARM_REG(1));
|
||||
}
|
||||
|
||||
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
|
||||
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
|
||||
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
|
||||
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));
|
282
components/esp_timer/src/esp_timer_impl_lac.c
Normal file
282
components/esp_timer/src/esp_timer_impl_lac.c
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright 2020 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 "sys/param.h"
|
||||
#include "esp_timer_impl.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_task.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp32/clk.h"
|
||||
#include "driver/periph_ctrl.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
/**
|
||||
* @file esp_timer_lac.c
|
||||
* @brief Implementation of chip-specific part of esp_timer
|
||||
*
|
||||
* This implementation uses TG0 LAC timer of the ESP32. This timer is
|
||||
* a 64-bit up-counting timer, with a programmable compare value (called 'alarm'
|
||||
* hereafter). When the timer reaches compare value, interrupt is raised.
|
||||
* The timer can be configured to produce an edge or a level interrupt.
|
||||
*/
|
||||
|
||||
/* Selects which Timer Group peripheral to use */
|
||||
#define LACT_MODULE 0
|
||||
|
||||
#if LACT_MODULE == 0
|
||||
#define INTR_SOURCE_LACT ETS_TG0_LACT_LEVEL_INTR_SOURCE
|
||||
#define PERIPH_LACT PERIPH_TIMG0_MODULE
|
||||
#elif LACT_MODULE == 1
|
||||
#define INTR_SOURCE_LACT ETS_TG1_LACT_LEVEL_INTR_SOURCE
|
||||
#define PERIPH_LACT PERIPH_TIMG1_MODULE
|
||||
#else
|
||||
#error "Incorrect the number of LACT module (only 0 or 1)"
|
||||
#endif
|
||||
|
||||
/* Desired number of timer ticks per microsecond.
|
||||
* This value should be small enough so that all possible APB frequencies
|
||||
* could be divided by it without remainder.
|
||||
* On the other hand, the smaller this value is, the longer we need to wait
|
||||
* after setting UPDATE_REG before the timer value can be read.
|
||||
* If TICKS_PER_US == 1, then we need to wait up to 1 microsecond, which
|
||||
* makes esp_timer_impl_get_time function take too much time.
|
||||
* The value TICKS_PER_US == 2 allows for most of the APB frequencies, and
|
||||
* allows reading the counter quickly enough.
|
||||
*/
|
||||
#define TICKS_PER_US 2
|
||||
|
||||
/* Shorter register names, used in this file */
|
||||
#define CONFIG_REG (TIMG_LACTCONFIG_REG(LACT_MODULE))
|
||||
#define RTC_STEP_REG (TIMG_LACTRTC_REG(LACT_MODULE))
|
||||
#define ALARM_LO_REG (TIMG_LACTALARMLO_REG(LACT_MODULE))
|
||||
#define ALARM_HI_REG (TIMG_LACTALARMHI_REG(LACT_MODULE))
|
||||
#define COUNT_LO_REG (TIMG_LACTLO_REG(LACT_MODULE))
|
||||
#define COUNT_HI_REG (TIMG_LACTHI_REG(LACT_MODULE))
|
||||
#define UPDATE_REG (TIMG_LACTUPDATE_REG(LACT_MODULE))
|
||||
#define LOAD_REG (TIMG_LACTLOAD_REG(LACT_MODULE))
|
||||
#define LOAD_LO_REG (TIMG_LACTLOADLO_REG(LACT_MODULE))
|
||||
#define LOAD_HI_REG (TIMG_LACTLOADHI_REG(LACT_MODULE))
|
||||
#define INT_ENA_REG (TIMG_INT_ENA_TIMERS_REG(LACT_MODULE))
|
||||
#define INT_ST_REG (TIMG_INT_ST_TIMERS_REG(LACT_MODULE))
|
||||
#define INT_CLR_REG (TIMG_INT_CLR_TIMERS_REG(LACT_MODULE))
|
||||
|
||||
/* Helper type to convert between a 64-bit value and a pair of 32-bit values without shifts and masks */
|
||||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
uint32_t lo;
|
||||
uint32_t hi;
|
||||
};
|
||||
uint64_t val;
|
||||
};
|
||||
} timer_64b_reg_t;
|
||||
|
||||
static const char* TAG = "esp_timer_impl";
|
||||
|
||||
/* Interrupt handle returned by the interrupt allocator */
|
||||
static intr_handle_t s_timer_interrupt_handle;
|
||||
|
||||
/* Function from the upper layer to be called when the interrupt happens.
|
||||
* Registered in esp_timer_impl_init.
|
||||
*/
|
||||
static intr_handler_t s_alarm_handler;
|
||||
|
||||
/* Spinlock used to protect access to the hardware registers. */
|
||||
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
|
||||
void esp_timer_impl_lock(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
void esp_timer_impl_unlock(void)
|
||||
{
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_counter_reg(void)
|
||||
{
|
||||
uint32_t lo, hi;
|
||||
uint32_t lo_start = REG_READ(COUNT_LO_REG);
|
||||
uint32_t div = REG_GET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER);
|
||||
/* The peripheral doesn't have a bit to indicate that the update is done, so we poll the
|
||||
* lower 32 bit part of the counter until it changes, or a timeout expires.
|
||||
*/
|
||||
REG_WRITE(UPDATE_REG, 1);
|
||||
do {
|
||||
lo = REG_READ(COUNT_LO_REG);
|
||||
} while (lo == lo_start && div-- > 0);
|
||||
|
||||
/* Since this function is called without a critical section, verify that LO and HI
|
||||
* registers are consistent. That is, if an interrupt happens between reading LO and
|
||||
* HI registers, and esp_timer_impl_get_time is called from an ISR, then try to
|
||||
* detect this by the change in LO register value, and re-read both registers.
|
||||
*/
|
||||
do {
|
||||
lo_start = lo;
|
||||
hi = REG_READ(COUNT_HI_REG);
|
||||
lo = REG_READ(COUNT_LO_REG);
|
||||
} while (lo != lo_start);
|
||||
|
||||
timer_64b_reg_t result = {
|
||||
.lo = lo,
|
||||
.hi = hi
|
||||
};
|
||||
return result.val;
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
|
||||
{
|
||||
return esp_timer_impl_get_counter_reg() / TICKS_PER_US;
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
int64_t offset = TICKS_PER_US * 2;
|
||||
uint64_t now_time = esp_timer_impl_get_counter_reg();
|
||||
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
|
||||
do {
|
||||
REG_CLR_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
|
||||
REG_WRITE(ALARM_LO_REG, alarm.lo);
|
||||
REG_WRITE(ALARM_HI_REG, alarm.hi);
|
||||
REG_SET_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN);
|
||||
now_time = esp_timer_impl_get_counter_reg();
|
||||
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
|
||||
if (delta <= 0 && REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) == 0) {
|
||||
// new alarm is less than the counter and the interrupt flag is not set
|
||||
offset += abs((int)delta) + TICKS_PER_US * 2;
|
||||
alarm.val = now_time + offset;
|
||||
} else {
|
||||
// finish if either (alarm > counter) or the interrupt flag is already set.
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timer_alarm_isr(void *arg)
|
||||
{
|
||||
/* Clear interrupt status */
|
||||
REG_WRITE(INT_CLR_REG, TIMG_LACT_INT_CLR);
|
||||
/* Call the upper layer handler */
|
||||
(*s_alarm_handler)(arg);
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
|
||||
{
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
assert(apb_ticks_per_us >= 3 && "divider value too low");
|
||||
assert(apb_ticks_per_us % TICKS_PER_US == 0 && "APB frequency (in MHz) should be divisible by TICK_PER_US");
|
||||
REG_SET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER, apb_ticks_per_us / TICKS_PER_US);
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
void esp_timer_impl_advance(int64_t time_diff_us)
|
||||
{
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
uint64_t now = esp_timer_impl_get_time();
|
||||
timer_64b_reg_t dst = { .val = (now + time_diff_us) * TICKS_PER_US };
|
||||
REG_WRITE(LOAD_LO_REG, dst.lo);
|
||||
REG_WRITE(LOAD_HI_REG, dst.hi);
|
||||
REG_WRITE(LOAD_REG, 1);
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
|
||||
{
|
||||
s_alarm_handler = alarm_handler;
|
||||
|
||||
periph_module_enable(PERIPH_LACT);
|
||||
|
||||
/* Reset the state */
|
||||
REG_WRITE(CONFIG_REG, 0);
|
||||
REG_WRITE(LOAD_LO_REG, 0);
|
||||
REG_WRITE(LOAD_HI_REG, 0);
|
||||
REG_WRITE(ALARM_LO_REG, UINT32_MAX);
|
||||
REG_WRITE(ALARM_HI_REG, UINT32_MAX);
|
||||
REG_WRITE(LOAD_REG, 1);
|
||||
REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR);
|
||||
|
||||
esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT,
|
||||
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM,
|
||||
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* In theory, this needs a shared spinlock with the timer group driver.
|
||||
* However since esp_timer_impl_init is called early at startup, this
|
||||
* will not cause issues in practice.
|
||||
*/
|
||||
REG_SET_BIT(INT_ENA_REG, TIMG_LACT_INT_ENA);
|
||||
|
||||
esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
|
||||
|
||||
REG_SET_BIT(CONFIG_REG, TIMG_LACT_INCREASE |
|
||||
TIMG_LACT_LEVEL_INT_EN |
|
||||
TIMG_LACT_EN);
|
||||
|
||||
// Set the step for the sleep mode when the timer will work
|
||||
// from a slow_clk frequency instead of the APB frequency.
|
||||
uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * TICKS_PER_US;
|
||||
REG_SET_FIELD(RTC_STEP_REG, TIMG_LACT_RTC_STEP_LEN, slowclk_ticks_per_us);
|
||||
|
||||
ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) );
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_timer_impl_deinit(void)
|
||||
{
|
||||
REG_WRITE(CONFIG_REG, 0);
|
||||
REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR);
|
||||
/* TODO: also clear TIMG_LACT_INT_ENA; however see the note in esp_timer_impl_init. */
|
||||
|
||||
esp_intr_disable(s_timer_interrupt_handle);
|
||||
esp_intr_free(s_timer_interrupt_handle);
|
||||
s_timer_interrupt_handle = NULL;
|
||||
}
|
||||
|
||||
/* FIXME: This value is safe for 80MHz APB frequency, should be modified to depend on clock frequency. */
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
uint64_t esp_timer_impl_get_alarm_reg(void)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
timer_64b_reg_t alarm = {
|
||||
.lo = REG_READ(ALARM_LO_REG),
|
||||
.hi = REG_READ(ALARM_HI_REG)
|
||||
};
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
return alarm.val;
|
||||
}
|
||||
|
||||
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
|
||||
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
|
||||
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
|
||||
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));
|
249
components/esp_timer/src/esp_timer_impl_systimer.c
Normal file
249
components/esp_timer/src/esp_timer_impl_systimer.c
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 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 <sys/param.h>
|
||||
#include "esp_timer_impl.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_log.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/systimer_reg.h"
|
||||
#include "soc/periph_defs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
/**
|
||||
* @file esp_timer_systimer.c
|
||||
* @brief Implementation of chip-specific part of esp_timer
|
||||
*
|
||||
* This implementation uses SYSTIMER of the ESP32-S2. This timer is
|
||||
* a 64-bit up-counting timer, with a programmable compare value (called 'alarm'
|
||||
* hereafter). When the timer reaches compare value, interrupt is raised.
|
||||
* The timer can be configured to produce an edge or a level interrupt.
|
||||
*/
|
||||
|
||||
/* esp_timer uses the 2 compare unit of SYSTIMER. */
|
||||
#define INTR_SOURCE_LACT (ETS_SYSTIMER_TARGET2_EDGE_INTR_SOURCE)
|
||||
// Registers
|
||||
#define COUNT_LO_REG (SYSTIMER_VALUE_LO_REG)
|
||||
#define COUNT_HI_REG (SYSTIMER_VALUE_HI_REG)
|
||||
#define LOAD_LO_REG (SYSTIMER_LOAD_LO_REG)
|
||||
#define LOAD_HI_REG (SYSTIMER_LOAD_HI_REG)
|
||||
#define ALARM_LO_REG (SYSTIMER_TARGET2_LO_REG)
|
||||
#define ALARM_HI_REG (SYSTIMER_TARGET2_HI_REG)
|
||||
// Macros
|
||||
#define ENABLE_CLK() (REG_SET_BIT(SYSTIMER_CONF_REG, SYSTIMER_CLK_EN))
|
||||
#define ENABLE_INT() (REG_SET_BIT(SYSTIMER_INT_ENA_REG, SYSTIMER_INT2_ENA))
|
||||
#define DISABLE_INT() (REG_CLR_BIT(SYSTIMER_INT_ENA_REG, SYSTIMER_INT2_ENA))
|
||||
#define GET_INT_FLAG() (REG_GET_FIELD(SYSTIMER_INT_RAW_REG, SYSTIMER_INT2_RAW))
|
||||
#define CLEAR_INT() (REG_WRITE(SYSTIMER_INT_CLR_REG, SYSTIMER_INT2_CLR))
|
||||
#define DISABLE_COMPARE_UNIT() (REG_WRITE(SYSTIMER_TARGET2_CONF_REG, 0))
|
||||
#define ENABLE_COMPARE_UNIT() (REG_WRITE(SYSTIMER_TARGET2_CONF_REG, SYSTIMER_TARGET2_WORK_EN))
|
||||
#define APPLY_LOADED_VAL() (REG_SET_BIT(SYSTIMER_LOAD_REG, SYSTIMER_TIMER_LOAD))
|
||||
#define SETTING_STEP_FOR_PLL_SRC(step) (REG_SET_FIELD(SYSTIMER_STEP_REG, SYSTIMER_TIMER_PLL_STEP, step))
|
||||
#define SETTING_STEP_FOR_XTAL_SRC(step) (REG_SET_FIELD(SYSTIMER_STEP_REG, SYSTIMER_TIMER_XTAL_STEP, step))
|
||||
#define UPDATE_COUNT_REG() (REG_WRITE(SYSTIMER_UPDATE_REG, SYSTIMER_TIMER_UPDATE))
|
||||
#define GET_FLAG_UPDATED_COUNT_REG() (REG_GET_BIT(SYSTIMER_UPDATE_REG, SYSTIMER_TIMER_VALUE_VALID))
|
||||
|
||||
/* Helper type to convert between a 64-bit value and a pair of 32-bit values without shifts and masks */
|
||||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
uint32_t lo;
|
||||
uint32_t hi;
|
||||
};
|
||||
uint64_t val;
|
||||
};
|
||||
} timer_64b_reg_t;
|
||||
|
||||
static const char* TAG = "esp_timer_impl";
|
||||
|
||||
/* Interrupt handle returned by the interrupt allocator */
|
||||
static intr_handle_t s_timer_interrupt_handle;
|
||||
|
||||
/* Function from the upper layer to be called when the interrupt happens.
|
||||
* Registered in esp_timer_impl_init.
|
||||
*/
|
||||
static intr_handler_t s_alarm_handler;
|
||||
|
||||
/* Number of timer ticks per microsecond. */
|
||||
#define TICKS_PER_US (APB_CLK_FREQ / 1000000)
|
||||
|
||||
/* Spinlock used to protect access to the hardware registers. */
|
||||
portMUX_TYPE s_time_update_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
|
||||
void esp_timer_impl_lock(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
void esp_timer_impl_unlock(void)
|
||||
{
|
||||
portEXIT_CRITICAL(&s_time_update_lock);
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_counter_reg(void)
|
||||
{
|
||||
uint32_t lo, lo_start, hi;
|
||||
/* Set the "update" bit and wait for acknowledgment */
|
||||
UPDATE_COUNT_REG();
|
||||
while (GET_FLAG_UPDATED_COUNT_REG() == 0) {
|
||||
;
|
||||
}
|
||||
/* Read LO, HI, then LO again, check that LO returns the same value.
|
||||
* This accounts for the case when an interrupt may happen between reading
|
||||
* HI and LO values, and this function may get called from the ISR.
|
||||
* In this case, the repeated read will return consistent values.
|
||||
*/
|
||||
lo_start = REG_READ(COUNT_LO_REG);
|
||||
do {
|
||||
lo = lo_start;
|
||||
hi = REG_READ(COUNT_HI_REG);
|
||||
lo_start = REG_READ(COUNT_LO_REG);
|
||||
} while (lo_start != lo);
|
||||
|
||||
timer_64b_reg_t result = {
|
||||
.lo = lo,
|
||||
.hi = hi
|
||||
};
|
||||
return result.val;
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_time(void)
|
||||
{
|
||||
return esp_timer_impl_get_counter_reg() / TICKS_PER_US;
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
int64_t offset = TICKS_PER_US * 2;
|
||||
uint64_t now_time = esp_timer_impl_get_counter_reg();
|
||||
timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) };
|
||||
do {
|
||||
DISABLE_COMPARE_UNIT();
|
||||
REG_WRITE(ALARM_LO_REG, alarm.lo);
|
||||
REG_WRITE(ALARM_HI_REG, alarm.hi);
|
||||
ENABLE_COMPARE_UNIT();
|
||||
now_time = esp_timer_impl_get_counter_reg();
|
||||
int64_t delta = (int64_t)alarm.val - (int64_t)now_time;
|
||||
if (delta <= 0 && GET_INT_FLAG() == 0) {
|
||||
// new alarm is less than the counter and the interrupt flag is not set
|
||||
offset += abs((int)delta) + TICKS_PER_US * 2;
|
||||
alarm.val = now_time + offset;
|
||||
} else {
|
||||
// finish if either (alarm > counter) or the interrupt flag is already set.
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR timer_alarm_isr(void *arg)
|
||||
{
|
||||
// clear the interrupt
|
||||
CLEAR_INT();
|
||||
/* Call the upper layer handler */
|
||||
(*s_alarm_handler)(arg);
|
||||
}
|
||||
|
||||
void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
|
||||
{
|
||||
/* If this function was called when switching APB clock to PLL, don't need
|
||||
* do anything: the SYSTIMER_TIMER_PLL_STEP is already correct.
|
||||
* If this was called when switching APB clock to XTAL, need to adjust
|
||||
* XTAL_STEP value accordingly.
|
||||
*/
|
||||
if (apb_ticks_per_us != TICKS_PER_US) {
|
||||
assert((TICKS_PER_US % apb_ticks_per_us) == 0 && "TICK_PER_US should be divisible by APB frequency (in MHz)");
|
||||
SETTING_STEP_FOR_XTAL_SRC(TICKS_PER_US / apb_ticks_per_us);
|
||||
}
|
||||
}
|
||||
|
||||
void esp_timer_impl_advance(int64_t time_us)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
timer_64b_reg_t new_count = { .val = esp_timer_impl_get_counter_reg() + time_us * TICKS_PER_US };
|
||||
REG_WRITE(LOAD_LO_REG, new_count.lo);
|
||||
REG_WRITE(LOAD_HI_REG, new_count.hi);
|
||||
APPLY_LOADED_VAL();
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
|
||||
{
|
||||
s_alarm_handler = alarm_handler;
|
||||
|
||||
esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT,
|
||||
ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_EDGE,
|
||||
&timer_alarm_isr, NULL, &s_timer_interrupt_handle);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
|
||||
return err;
|
||||
}
|
||||
ENABLE_CLK();
|
||||
/* Configure the counter:
|
||||
* - increment by 1 when running from PLL (80 ticks per microsecond),
|
||||
* - increment by 2 when running from XTAL (40 ticks per microsecond).
|
||||
* Note that if the APB frequency is derived from XTAL with divider != 1,
|
||||
* XTAL_STEP needs to be adjusted accordingly. For example, if
|
||||
* the APB frequency is XTAL/4 = 10 MHz, then XTAL_STEP should be set to 8.
|
||||
* This is handled in esp_timer_impl_update_apb_freq function above.
|
||||
*/
|
||||
assert(rtc_clk_xtal_freq_get() == 40 && TICKS_PER_US == 80
|
||||
&& "update the following code to support other XTAL:APB frequency ratios");
|
||||
SETTING_STEP_FOR_PLL_SRC(1);
|
||||
SETTING_STEP_FOR_XTAL_SRC(2);
|
||||
/* TODO: if SYSTIMER is used for anything else, access to SYSTIMER_INT_ENA_REG has to be
|
||||
* protected by a shared spinlock. Since this code runs as part of early startup, this
|
||||
* is practically not an issue. Same applies to SYSTIMER_CLK_EN above.
|
||||
*/
|
||||
ENABLE_INT();
|
||||
ESP_ERROR_CHECK(esp_intr_enable(s_timer_interrupt_handle));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_timer_impl_deinit(void)
|
||||
{
|
||||
esp_intr_disable(s_timer_interrupt_handle);
|
||||
DISABLE_COMPARE_UNIT();
|
||||
/* TODO: may need a spinlock, see the note related to SYSTIMER_INT_ENA_REG in esp_timer_impl_init */
|
||||
DISABLE_INT();
|
||||
esp_intr_free(s_timer_interrupt_handle);
|
||||
s_timer_interrupt_handle = NULL;
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR esp_timer_impl_get_min_period_us(void)
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
uint64_t esp_timer_impl_get_alarm_reg(void)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&s_time_update_lock);
|
||||
timer_64b_reg_t alarm = {
|
||||
.lo = REG_READ(ALARM_LO_REG),
|
||||
.hi = REG_READ(ALARM_HI_REG)
|
||||
};
|
||||
portEXIT_CRITICAL_SAFE(&s_time_update_lock);
|
||||
return alarm.val;
|
||||
}
|
||||
|
||||
void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq")));
|
||||
void esp_timer_private_advance(int64_t time_us) __attribute__((alias("esp_timer_impl_advance")));
|
||||
void esp_timer_private_lock(void) __attribute__((alias("esp_timer_impl_lock")));
|
||||
void esp_timer_private_unlock(void) __attribute__((alias("esp_timer_impl_unlock")));
|
129
components/esp_timer/src/ets_timer_legacy.c
Normal file
129
components/esp_timer/src/ets_timer_legacy.c
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2010-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.
|
||||
|
||||
/*
|
||||
* ets_timer module implements a set of legacy timer APIs which are
|
||||
* used by the WiFi driver. This is done on top of the newer esp_timer APIs.
|
||||
* Applications should not use ets_timer functions, as they may change without
|
||||
* notice.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_types.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "soc/frc_timer_reg.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_timer.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/ets_sys.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include "esp32s2/rom/ets_sys.h"
|
||||
#endif
|
||||
|
||||
/* We abuse 'timer_arg' field of ETSTimer structure to hold a pointer to esp_timer */
|
||||
#define ESP_TIMER(p_ets_timer) ((esp_timer_handle_t) (p_ets_timer)->timer_arg)
|
||||
|
||||
/* We abuse 'timer_expire' field of ETSTimer structure to hold a magic value
|
||||
* signifying that the contents of the timer was zeroed out.
|
||||
*/
|
||||
#define TIMER_INITIALIZED_FIELD(p_ets_timer) ((p_ets_timer)->timer_expire)
|
||||
#define TIMER_INITIALIZED_VAL 0x12121212
|
||||
|
||||
static IRAM_ATTR bool timer_initialized(ETSTimer *ptimer)
|
||||
{
|
||||
return TIMER_INITIALIZED_FIELD(ptimer) == TIMER_INITIALIZED_VAL;
|
||||
}
|
||||
|
||||
void ets_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg)
|
||||
{
|
||||
if (!timer_initialized(ptimer)) {
|
||||
memset(ptimer, 0, sizeof(*ptimer));
|
||||
TIMER_INITIALIZED_FIELD(ptimer) = TIMER_INITIALIZED_VAL;
|
||||
}
|
||||
|
||||
if (ESP_TIMER(ptimer) == NULL) {
|
||||
const esp_timer_create_args_t create_args = {
|
||||
.callback = pfunction,
|
||||
.arg = parg,
|
||||
.name = "ETSTimer",
|
||||
.dispatch_method = ESP_TIMER_TASK
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK( esp_timer_create(&create_args, (esp_timer_handle_t*)&(ptimer->timer_arg)) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IRAM_ATTR ets_timer_arm_us(ETSTimer *ptimer, uint32_t time_us, bool repeat_flag)
|
||||
{
|
||||
assert(timer_initialized(ptimer));
|
||||
esp_timer_stop(ESP_TIMER(ptimer)); // no error check
|
||||
if (!repeat_flag) {
|
||||
ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) );
|
||||
} else {
|
||||
ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) );
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR ets_timer_arm(ETSTimer *ptimer, uint32_t time_ms, bool repeat_flag)
|
||||
{
|
||||
uint64_t time_us = 1000LL * (uint64_t) time_ms;
|
||||
assert(timer_initialized(ptimer));
|
||||
esp_timer_stop(ESP_TIMER(ptimer)); // no error check
|
||||
if (!repeat_flag) {
|
||||
ESP_ERROR_CHECK( esp_timer_start_once(ESP_TIMER(ptimer), time_us) );
|
||||
} else {
|
||||
ESP_ERROR_CHECK( esp_timer_start_periodic(ESP_TIMER(ptimer), time_us) );
|
||||
}
|
||||
}
|
||||
|
||||
void ets_timer_done(ETSTimer *ptimer)
|
||||
{
|
||||
if (timer_initialized(ptimer)) {
|
||||
esp_timer_delete(ESP_TIMER(ptimer));
|
||||
ptimer->timer_arg = NULL;
|
||||
TIMER_INITIALIZED_FIELD(ptimer) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR ets_timer_disarm(ETSTimer *ptimer)
|
||||
{
|
||||
if (timer_initialized(ptimer)) {
|
||||
esp_timer_stop(ESP_TIMER(ptimer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ets_timer_init(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ets_timer_deinit(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void os_timer_setfn(ETSTimer *ptimer, ETSTimerFunc *pfunction, void *parg) __attribute__((alias("ets_timer_setfn")));
|
||||
void os_timer_disarm(ETSTimer *ptimer) __attribute__((alias("ets_timer_disarm")));
|
||||
void os_timer_arm_us(ETSTimer *ptimer,uint32_t u_seconds,bool repeat_flag) __attribute__((alias("ets_timer_arm_us")));
|
||||
void os_timer_arm(ETSTimer *ptimer,uint32_t milliseconds,bool repeat_flag) __attribute__((alias("ets_timer_arm")));
|
||||
void os_timer_done(ETSTimer *ptimer) __attribute__((alias("ets_timer_done")));
|
||||
|
Reference in New Issue
Block a user