mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'refactor/timer_group_new_api' into 'master'
⛵ GPTimer driver (previous Timer Group) Closes IDF-2082 and IDF-2883 See merge request espressif/esp-idf!8317
This commit is contained in:
commit
5145183947
@ -94,7 +94,7 @@ menu "Application Level Tracing"
|
||||
prompt "Timer to use as timestamp source"
|
||||
depends on APPTRACE_SV_ENABLE
|
||||
default APPTRACE_SV_TS_SOURCE_CCOUNT if FREERTOS_UNICORE && !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
default APPTRACE_SV_TS_SOURCE_TIMER_00 if !FREERTOS_UNICORE && !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
default APPTRACE_SV_TS_SOURCE_GPTIMER if !FREERTOS_UNICORE && !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
default APPTRACE_SV_TS_SOURCE_ESP_TIMER if PM_ENABLE || IDF_TARGET_ESP32C3
|
||||
help
|
||||
SystemView needs to use a hardware timer as the source of timestamps
|
||||
@ -104,20 +104,8 @@ menu "Application Level Tracing"
|
||||
bool "CPU cycle counter (CCOUNT)"
|
||||
depends on FREERTOS_UNICORE && !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
|
||||
config APPTRACE_SV_TS_SOURCE_TIMER_00
|
||||
bool "Timer 0, Group 0"
|
||||
depends on !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
|
||||
config APPTRACE_SV_TS_SOURCE_TIMER_01
|
||||
bool "Timer 1, Group 0"
|
||||
depends on !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
|
||||
config APPTRACE_SV_TS_SOURCE_TIMER_10
|
||||
bool "Timer 0, Group 1"
|
||||
depends on !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
|
||||
config APPTRACE_SV_TS_SOURCE_TIMER_11
|
||||
bool "Timer 1, Group 1"
|
||||
config APPTRACE_SV_TS_SOURCE_GPTIMER
|
||||
bool "General Purpose Timer (Timer Group)"
|
||||
depends on !PM_ENABLE && !IDF_TARGET_ESP32C3
|
||||
|
||||
config APPTRACE_SV_TS_SOURCE_ESP_TIMER
|
||||
|
@ -12,8 +12,7 @@ entries:
|
||||
[mapping:app_trace_driver]
|
||||
archive: libdriver.a
|
||||
entries:
|
||||
if APPTRACE_SV_TS_SOURCE_TIMER_00 = y || APPTRACE_SV_TS_SOURCE_TIMER_01 = y
|
||||
|| APPTRACE_SV_TS_SOURCE_TIMER_10 = y || APPTRACE_SV_TS_SOURCE_TIMER_11 = y:
|
||||
timer (noflash)
|
||||
if APPTRACE_SV_TS_SOURCE_GPTIMER = y:
|
||||
gptimer (noflash)
|
||||
else:
|
||||
* (default)
|
||||
|
@ -14,10 +14,6 @@ CONFIG_ESP32_GCOV_ENABLE CONFIG_APPTRACE_GCOV_ENABLE
|
||||
CONFIG_SYSVIEW_ENABLE CONFIG_APPTRACE_SV_ENABLE
|
||||
CONFIG_SYSVIEW_TS_SOURCE CONFIG_APPTRACE_SV_TS_SOURCE
|
||||
CONFIG_SYSVIEW_TS_SOURCE_CCOUNT CONFIG_APPTRACE_SV_TS_SOURCE_CCOUNT
|
||||
CONFIG_SYSVIEW_TS_SOURCE_TIMER_00 CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_00
|
||||
CONFIG_SYSVIEW_TS_SOURCE_TIMER_01 CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_01
|
||||
CONFIG_SYSVIEW_TS_SOURCE_TIMER_10 CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_10
|
||||
CONFIG_SYSVIEW_TS_SOURCE_TIMER_11 CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_11
|
||||
CONFIG_SYSVIEW_TS_SOURCE_ESP_TIMER CONFIG_APPTRACE_SV_TS_SOURCE_ESP_TIMER
|
||||
CONFIG_SYSVIEW_MAX_TASKS CONFIG_APPTRACE_SV_MAX_TASKS
|
||||
CONFIG_SYSVIEW_BUF_WAIT_TMO CONFIG_APPTRACE_SV_BUF_WAIT_TMO
|
||||
|
@ -101,26 +101,16 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI;
|
||||
#endif
|
||||
|
||||
#if TS_USE_TIMERGROUP
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
// Timer group timer divisor
|
||||
#define SYSVIEW_TIMER_DIV 2
|
||||
|
||||
// Frequency of the timestamp.
|
||||
// Frequency of the timestamp, using APB as GPTimer source clock
|
||||
#define SYSVIEW_TIMESTAMP_FREQ (esp_clk_apb_freq() / SYSVIEW_TIMER_DIV)
|
||||
|
||||
// Timer ID and group ID
|
||||
#if defined(CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_00) || defined(CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_10)
|
||||
#define TS_TIMER_ID 0
|
||||
#else
|
||||
#define TS_TIMER_ID 1
|
||||
#endif // TIMER_00 || TIMER_01
|
||||
|
||||
#if defined(CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_00) || defined(CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_01)
|
||||
#define TS_TIMER_GROUP 0
|
||||
#else
|
||||
#define TS_TIMER_GROUP 1
|
||||
#endif // TIMER_00 || TIMER_10
|
||||
// GPTimer handle
|
||||
gptimer_handle_t s_sv_gptimer;
|
||||
|
||||
#endif // TS_USE_TIMERGROUP
|
||||
|
||||
@ -199,19 +189,15 @@ static void SEGGER_SYSVIEW_TS_Init(void)
|
||||
* esp_timer and ccount can be used as is.
|
||||
*/
|
||||
#if TS_USE_TIMERGROUP
|
||||
timer_config_t config = {
|
||||
.alarm_en = 0,
|
||||
.auto_reload = 0,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = SYSVIEW_TIMER_DIV,
|
||||
.counter_en = 0
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = SYSVIEW_TIMESTAMP_FREQ,
|
||||
};
|
||||
/* Configure timer */
|
||||
timer_init(TS_TIMER_GROUP, TS_TIMER_ID, &config);
|
||||
/* Load counter value */
|
||||
timer_set_counter_value(TS_TIMER_GROUP, TS_TIMER_ID, 0x00000000ULL);
|
||||
// pick any free GPTimer instance
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&config, &s_sv_gptimer));
|
||||
/* Start counting */
|
||||
timer_start(TS_TIMER_GROUP, TS_TIMER_ID);
|
||||
gptimer_start(s_sv_gptimer);
|
||||
#endif // TS_USE_TIMERGROUP
|
||||
}
|
||||
|
||||
@ -269,7 +255,7 @@ U32 SEGGER_SYSVIEW_X_GetTimestamp(void)
|
||||
{
|
||||
#if TS_USE_TIMERGROUP
|
||||
uint64_t ts = 0;
|
||||
timer_get_counter_value(TS_TIMER_GROUP, TS_TIMER_ID, &ts);
|
||||
gptimer_get_raw_count(s_sv_gptimer, &ts);
|
||||
return (U32) ts; // return lower part of counter value
|
||||
#elif TS_USE_CCOUNT
|
||||
return portGET_RUN_TIME_COUNTER_VALUE();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -9,7 +9,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "unity.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
@ -64,29 +65,6 @@ const static char *TAG = "esp_apptrace_test";
|
||||
#define ESP_APPTRACE_TEST_LOGV( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__)
|
||||
#define ESP_APPTRACE_TEST_LOGO( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(E, ESP_LOG_NONE, format, ##__VA_ARGS__)
|
||||
|
||||
static void esp_apptrace_test_timer_init(int timer_group, int timer_idx, uint32_t period)
|
||||
{
|
||||
uint64_t alarm_val = (period * (TIMER_BASE_CLK / 1000000UL)) / 2;
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 2, //Range is 2 to 65536
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
};
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Stop timer counter*/
|
||||
timer_pause(timer_group, timer_idx);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Set alarm value*/
|
||||
timer_set_alarm_value(timer_group, timer_idx, alarm_val);
|
||||
/*Enable timer interrupt*/
|
||||
timer_enable_intr(timer_group, timer_idx);
|
||||
}
|
||||
|
||||
#if CONFIG_APPTRACE_SV_ENABLE == 0
|
||||
#define ESP_APPTRACE_TEST_WRITE(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, ESP_APPTRACE_TMO_INFINITE)
|
||||
#define ESP_APPTRACE_TEST_WRITE_FROM_ISR(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 0UL)
|
||||
@ -102,9 +80,8 @@ typedef struct {
|
||||
} esp_apptrace_test_gen_data_t;
|
||||
|
||||
typedef struct {
|
||||
int group;
|
||||
int id;
|
||||
void (*isr_func)(void *);
|
||||
gptimer_handle_t gptimer;
|
||||
bool (*isr_func)(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
|
||||
esp_apptrace_test_gen_data_t data;
|
||||
} esp_apptrace_test_timer_arg_t;
|
||||
|
||||
@ -116,7 +93,6 @@ typedef struct {
|
||||
esp_apptrace_test_gen_data_t data;
|
||||
volatile int stop;
|
||||
SemaphoreHandle_t done;
|
||||
|
||||
uint32_t timers_num;
|
||||
esp_apptrace_test_timer_arg_t *timers;
|
||||
} esp_apptrace_test_task_arg_t;
|
||||
@ -132,86 +108,75 @@ static SemaphoreHandle_t s_print_lock;
|
||||
|
||||
static uint64_t esp_apptrace_test_ts_get(void);
|
||||
|
||||
static void esp_apptrace_test_timer_isr(void *arg)
|
||||
static bool esp_apptrace_test_timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)user_ctx;
|
||||
|
||||
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();
|
||||
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
|
||||
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
|
||||
if (res != ESP_OK) {
|
||||
} else {
|
||||
if (0) {
|
||||
esp_rom_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
|
||||
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
tim_arg->data.wr_err = 0;
|
||||
}
|
||||
|
||||
tim_arg->data.wr_cnt++;
|
||||
timer_group_clr_intr_status_in_isr(tim_arg->group, tim_arg->id);
|
||||
timer_group_enable_alarm_in_isr(tim_arg->group, tim_arg->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_timer_isr_crash(void *arg)
|
||||
static bool esp_apptrace_test_timer_isr_crash(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
|
||||
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)user_ctx;
|
||||
|
||||
timer_group_clr_intr_status_in_isr(tim_arg->group, tim_arg->id);
|
||||
timer_group_enable_alarm_in_isr(tim_arg->group, tim_arg->id);
|
||||
if (tim_arg->data.wr_cnt < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH) {
|
||||
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();//xthal_get_ccount();//xTaskGetTickCount();
|
||||
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
|
||||
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
|
||||
if (res != ESP_OK) {
|
||||
esp_rom_printf("tim-%d-%d: Failed to write trace %d %x!\n", tim_arg->group, tim_arg->id, res, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
esp_rom_printf("tim-%x: Failed to write trace %d %x!\n", tim_arg->gptimer, res, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
} else {
|
||||
esp_rom_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
|
||||
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
esp_rom_printf("tim-%x: Written chunk%d %d bytes, %x\n",
|
||||
timer, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
|
||||
tim_arg->data.wr_cnt++;
|
||||
}
|
||||
} else {
|
||||
uint32_t *ptr = 0;
|
||||
*ptr = 1000;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void esp_apptrace_dummy_task(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, flags = 0, i;
|
||||
timer_isr_handle_t *inth = NULL;
|
||||
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: run dummy task (period %u us, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->timers_num);
|
||||
|
||||
if (arg->timers_num > 0) {
|
||||
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
if (!inth) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
|
||||
goto on_fail;
|
||||
}
|
||||
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
|
||||
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
*(uint32_t *)arg->timers[i].data.buf = (uint32_t)inth[i] | (1 << 31);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
|
||||
res = timer_start(arg->timers[i].group, arg->timers[i].id);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &arg->timers[i].gptimer));
|
||||
*(uint32_t *)arg->timers[i].data.buf = (uint32_t)arg->timers[i].gptimer | (1 << 31);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), arg->timers[i].gptimer, arg->timers[i].data.period);
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = arg->timers[i].data.period,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = arg->timers[i].isr_func,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(arg->timers[i].gptimer, &cbs, &arg->timers[i]));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(arg->timers[i].gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(arg->timers[i].gptimer));
|
||||
}
|
||||
|
||||
i = 0;
|
||||
int i = 0;
|
||||
while (!arg->stop) {
|
||||
ESP_APPTRACE_TEST_LOGD("%x: dummy task work %d.%d", xTaskGetCurrentTaskHandle(), cpu_hal_get_core_id(), i++);
|
||||
if (tmo_ticks) {
|
||||
@ -219,16 +184,9 @@ static void esp_apptrace_dummy_task(void *p)
|
||||
}
|
||||
}
|
||||
|
||||
on_fail:
|
||||
if (inth) {
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
timer_pause(arg->timers[i].group, arg->timers[i].id);
|
||||
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
|
||||
if (inth[i]) {
|
||||
esp_intr_free(inth[i]);
|
||||
}
|
||||
}
|
||||
vPortFree(inth);
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
TEST_ESP_OK(gptimer_stop(arg->timers[i].gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(arg->timers[i].gptimer));
|
||||
}
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
@ -238,34 +196,31 @@ on_fail:
|
||||
static void esp_apptrace_test_task(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, flags = 0;
|
||||
timer_isr_handle_t *inth = NULL;
|
||||
int res;
|
||||
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
|
||||
|
||||
if (arg->timers_num > 0) {
|
||||
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
if (!inth) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
|
||||
goto on_fail;
|
||||
}
|
||||
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
|
||||
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
*(uint32_t *)arg->timers[i].data.buf = ((uint32_t)inth[i]) | (1 << 31) | (cpu_hal_get_core_id() ? 0x1 : 0);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
|
||||
res = timer_start(arg->timers[i].group, arg->timers[i].id);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
|
||||
goto on_fail;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &arg->timers[i].gptimer));
|
||||
*(uint32_t *)arg->timers[i].data.buf = ((uint32_t)arg->timers[i].gptimer) | (1 << 31) | (cpu_hal_get_core_id() ? 0x1 : 0);
|
||||
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), arg->timers[i].gptimer, arg->timers[i].data.period);
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = arg->timers[i].data.period,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = arg->timers[i].isr_func,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(arg->timers[i].gptimer, &cbs, &arg->timers[i]));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(arg->timers[i].gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(arg->timers[i].gptimer));
|
||||
}
|
||||
|
||||
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle() | (cpu_hal_get_core_id() ? 0x1 : 0);
|
||||
@ -282,7 +237,7 @@ static void esp_apptrace_test_task(void *p)
|
||||
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
|
||||
}
|
||||
if (res) {
|
||||
if (1){//arg->data.wr_err++ < ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
|
||||
if (1) { //arg->data.wr_err++ < ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
|
||||
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
|
||||
if (arg->data.wr_err == ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
|
||||
ESP_APPTRACE_TEST_LOGE("\n");
|
||||
@ -300,16 +255,9 @@ static void esp_apptrace_test_task(void *p)
|
||||
}
|
||||
}
|
||||
|
||||
on_fail:
|
||||
if (inth) {
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
timer_pause(arg->timers[i].group, arg->timers[i].id);
|
||||
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
|
||||
if (inth[i]) {
|
||||
esp_intr_free(inth[i]);
|
||||
}
|
||||
}
|
||||
vPortFree(inth);
|
||||
for (int i = 0; i < arg->timers_num; i++) {
|
||||
TEST_ESP_OK(gptimer_stop(arg->timers[i].gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(arg->timers[i].gptimer));
|
||||
}
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
@ -319,17 +267,16 @@ on_fail:
|
||||
static void esp_apptrace_test_task_crash(void *p)
|
||||
{
|
||||
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
|
||||
int res, i;
|
||||
|
||||
ESP_APPTRACE_TEST_LOGE("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
|
||||
|
||||
arg->data.wr_cnt = 0;
|
||||
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle();
|
||||
for (i = 0; i < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH; i++) {
|
||||
for (int i = 0; i < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH; i++) {
|
||||
uint32_t *ts = (uint32_t *)(arg->data.buf + sizeof(uint32_t));
|
||||
*ts = (uint32_t)esp_apptrace_test_ts_get();
|
||||
memset(arg->data.buf + sizeof(uint32_t), arg->data.wr_cnt & arg->data.mask, arg->data.buf_sz - sizeof(uint32_t));
|
||||
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
|
||||
int res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
|
||||
if (res) {
|
||||
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
|
||||
} else {
|
||||
@ -346,67 +293,47 @@ static void esp_apptrace_test_task_crash(void *p)
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static int s_ts_timer_group, s_ts_timer_idx;
|
||||
static gptimer_handle_t ts_gptimer;
|
||||
|
||||
static uint64_t esp_apptrace_test_ts_get(void)
|
||||
{
|
||||
uint64_t ts = 0;
|
||||
timer_get_counter_value(s_ts_timer_group, s_ts_timer_idx, &ts);
|
||||
gptimer_get_raw_count(ts_gptimer, &ts);
|
||||
return ts;
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_ts_init(int timer_group, int timer_idx)
|
||||
static void esp_apptrace_test_ts_init(void)
|
||||
{
|
||||
//uint64_t alarm_val = period * (TIMER_BASE_CLK / 1000000UL);
|
||||
|
||||
ESP_APPTRACE_TEST_LOGI("Use timer%d.%d for TS", timer_group, timer_idx);
|
||||
|
||||
s_ts_timer_group = timer_group;
|
||||
s_ts_timer_idx = timer_idx;
|
||||
|
||||
timer_config_t config = {
|
||||
.alarm_en = 0,
|
||||
.auto_reload = 0,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 2, //Range is 2 to 65536
|
||||
.counter_en = 0,
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 10000000,
|
||||
};
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Enable timer interrupt*/
|
||||
timer_start(timer_group, timer_idx);
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &ts_gptimer));
|
||||
ESP_APPTRACE_TEST_LOGI("Use timer %x for TS", ts_gptimer);
|
||||
TEST_ESP_OK(gptimer_start(ts_gptimer));
|
||||
}
|
||||
|
||||
static void esp_apptrace_test_ts_cleanup(void)
|
||||
{
|
||||
timer_config_t config = {
|
||||
.alarm_en = 0,
|
||||
.auto_reload = 0,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 2, //Range is 2 to 65536
|
||||
.counter_en = 0,
|
||||
};
|
||||
/*Configure timer*/
|
||||
timer_init(s_ts_timer_group, s_ts_timer_idx, &config);
|
||||
TEST_ESP_OK(gptimer_stop(ts_gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(ts_gptimer));
|
||||
ts_gptimer = NULL;
|
||||
}
|
||||
|
||||
static void esp_apptrace_test(esp_apptrace_test_cfg_t *test_cfg)
|
||||
{
|
||||
int i, k;
|
||||
int tims_in_use[TIMER_GROUP_MAX][TIMER_MAX] = {{0, 0}, {0, 0}};
|
||||
esp_apptrace_test_task_arg_t dummy_task_arg[1];
|
||||
esp_apptrace_test_task_arg_t dummy_task_arg = {
|
||||
.core = 0,
|
||||
.prio = 3,
|
||||
.task_func = esp_apptrace_test_task_crash,
|
||||
.data.buf = NULL,
|
||||
.data.buf_sz = 0,
|
||||
.data.period = 500000,
|
||||
.timers_num = 0,
|
||||
.timers = NULL,
|
||||
};
|
||||
|
||||
memset(dummy_task_arg, 0, sizeof(dummy_task_arg));
|
||||
dummy_task_arg[0].core = 0;
|
||||
dummy_task_arg[0].prio = 3;
|
||||
dummy_task_arg[0].task_func = esp_apptrace_test_task_crash;
|
||||
dummy_task_arg[0].data.buf = NULL;
|
||||
dummy_task_arg[0].data.buf_sz = 0;
|
||||
dummy_task_arg[0].data.period = 500000;
|
||||
dummy_task_arg[0].timers_num = 0;
|
||||
dummy_task_arg[0].timers = NULL;
|
||||
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
|
||||
s_print_lock = xSemaphoreCreateBinary();
|
||||
if (!s_print_lock) {
|
||||
@ -417,38 +344,17 @@ static void esp_apptrace_test(esp_apptrace_test_cfg_t *test_cfg)
|
||||
#else
|
||||
#endif
|
||||
|
||||
for (i = 0; i < test_cfg->tasks_num; i++) {
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
test_cfg->tasks[i].data.mask = 0xFF;
|
||||
test_cfg->tasks[i].stop = 0;
|
||||
test_cfg->tasks[i].done = xSemaphoreCreateBinary();
|
||||
if (!test_cfg->tasks[i].done) {
|
||||
ESP_APPTRACE_TEST_LOGE("Failed to create task completion semaphore!");
|
||||
goto on_fail;
|
||||
}
|
||||
for (k = 0; k < test_cfg->tasks[i].timers_num; k++) {
|
||||
TEST_ASSERT_NOT_NULL(test_cfg->tasks[i].done);
|
||||
for (int k = 0; k < test_cfg->tasks[i].timers_num; k++) {
|
||||
test_cfg->tasks[i].timers[k].data.mask = 0xFF;
|
||||
tims_in_use[test_cfg->tasks[i].timers[k].group][test_cfg->tasks[i].timers[k].id] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
for (i = 0; i < TIMER_GROUP_MAX; i++) {
|
||||
for (k = 0; k < TIMER_MAX; k++) {
|
||||
if (!tims_in_use[i][k]) {
|
||||
ESP_APPTRACE_TEST_LOGD("Found timer%d.%d", i, k);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_APPTRACE_TEST_LOGE("No free timer for TS!");
|
||||
goto on_fail;
|
||||
}
|
||||
esp_apptrace_test_ts_init(i, k);
|
||||
esp_apptrace_test_ts_init();
|
||||
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
char name[30];
|
||||
@ -457,16 +363,15 @@ static void esp_apptrace_test(esp_apptrace_test_cfg_t *test_cfg)
|
||||
xTaskCreatePinnedToCore(test_cfg->tasks[i].task_func, name, 2048, &test_cfg->tasks[i], test_cfg->tasks[i].prio, &thnd, test_cfg->tasks[i].core);
|
||||
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
|
||||
}
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy0", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 0);
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy0", 2048, &dummy_task_arg, dummy_task_arg.prio, NULL, 0);
|
||||
#if CONFIG_FREERTOS_UNICORE == 0
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy1", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 1);
|
||||
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy1", 2048, &dummy_task_arg, dummy_task_arg.prio, NULL, 1);
|
||||
#endif
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
//arg1.stop = 1;
|
||||
xSemaphoreTake(test_cfg->tasks[i].done, portMAX_DELAY);
|
||||
}
|
||||
|
||||
on_fail:
|
||||
for (int i = 0; i < test_cfg->tasks_num; i++) {
|
||||
if (test_cfg->tasks[i].done) {
|
||||
vSemaphoreDelete(test_cfg->tasks[i].done);
|
||||
@ -494,8 +399,6 @@ TEST_CASE("App trace test (1 task + 1 crashed timer ISR @ 1 core)", "[trace][ign
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr_crash;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
@ -548,15 +451,11 @@ TEST_CASE("App trace test (2 tasks + 1 timer @ each core", "[trace][ignore]")
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
s_test_timers[0].data.period = 150;
|
||||
|
||||
s_test_timers[1].group = TIMER_GROUP_1;
|
||||
s_test_timers[1].id = TIMER_0;
|
||||
s_test_timers[1].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[1].data.buf = s_bufs[1];
|
||||
s_test_timers[1].data.buf_sz = sizeof(s_bufs[1]);
|
||||
@ -612,8 +511,6 @@ TEST_CASE("App trace test (1 task + 1 timer @ 1 core)", "[trace][ignore]")
|
||||
memset(s_test_timers, 0, sizeof(s_test_timers));
|
||||
memset(s_test_tasks, 0, sizeof(s_test_tasks));
|
||||
|
||||
s_test_timers[0].group = TIMER_GROUP_0;
|
||||
s_test_timers[0].id = TIMER_0;
|
||||
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
|
||||
s_test_timers[0].data.buf = s_bufs[0];
|
||||
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
|
||||
@ -792,11 +689,11 @@ TEST_CASE("Log trace test (2 tasks)", "[trace][ignore]")
|
||||
vSemaphoreDelete(arg2.done);
|
||||
}
|
||||
|
||||
#else
|
||||
#else // #if CONFIG_APPTRACE_SV_ENABLE == 0
|
||||
|
||||
typedef struct {
|
||||
int group;
|
||||
int timer;
|
||||
gptimer_handle_t gptimer;
|
||||
uint32_t period;
|
||||
int flags;
|
||||
uint32_t id;
|
||||
} esp_sysviewtrace_timer_arg_t;
|
||||
@ -810,50 +707,46 @@ typedef struct {
|
||||
uint32_t id;
|
||||
} esp_sysviewtrace_task_arg_t;
|
||||
|
||||
static void esp_sysview_test_timer_isr(void *arg)
|
||||
static bool esp_sysview_test_timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
esp_sysviewtrace_timer_arg_t *tim_arg = (esp_sysviewtrace_timer_arg_t *)arg;
|
||||
|
||||
//ESP_APPTRACE_TEST_LOGI("tim-%d: IRQ %d/%d\n", tim_arg->id, tim_arg->group, tim_arg->timer);
|
||||
|
||||
timer_group_clr_intr_status_in_isr(tim_arg->group, tim_arg->id);
|
||||
timer_group_enable_alarm_in_isr(tim_arg->group, tim_arg->id);
|
||||
esp_sysviewtrace_timer_arg_t *tim_arg = (esp_sysviewtrace_timer_arg_t *)user_ctx;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void esp_sysviewtrace_test_task(void *p)
|
||||
{
|
||||
esp_sysviewtrace_task_arg_t *arg = (esp_sysviewtrace_task_arg_t *) p;
|
||||
volatile uint32_t tmp = 0;
|
||||
timer_isr_handle_t inth;
|
||||
|
||||
printf("%x: run sysview task\n", (uint32_t)xTaskGetCurrentTaskHandle());
|
||||
|
||||
if (arg->timer) {
|
||||
esp_err_t res = timer_isr_register(arg->timer->group, arg->timer->timer, esp_sysview_test_timer_isr, arg->timer, arg->timer->flags, &inth);
|
||||
if (res != ESP_OK) {
|
||||
printf("%x: failed to register timer ISR\n", (uint32_t)xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
else {
|
||||
res = timer_start(arg->timer->group, arg->timer->timer);
|
||||
if (res != ESP_OK) {
|
||||
printf("%x: failed to start timer\n", (uint32_t)xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
}
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = arg->timer->period,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = esp_sysview_test_timer_isr,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(arg->timer->gptimer, &cbs, arg->timer));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(arg->timer->gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(arg->timer->gptimer));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (1) {
|
||||
static uint32_t count;
|
||||
printf("%d", arg->id);
|
||||
if((++count % 80) == 0)
|
||||
if ((++count % 80) == 0) {
|
||||
printf("\n");
|
||||
}
|
||||
if (arg->sync) {
|
||||
xSemaphoreTake(*arg->sync, portMAX_DELAY);
|
||||
}
|
||||
for (uint32_t k = 0; k < arg->work_count; k++) {
|
||||
tmp++;
|
||||
}
|
||||
vTaskDelay(arg->sleep_tmo/portTICK_PERIOD_MS);
|
||||
vTaskDelay(arg->sleep_tmo / portTICK_PERIOD_MS);
|
||||
i++;
|
||||
if (arg->sync) {
|
||||
xSemaphoreGive(*arg->sync);
|
||||
@ -871,10 +764,9 @@ TEST_CASE("SysView trace test 1", "[trace][ignore]")
|
||||
TaskHandle_t thnd;
|
||||
|
||||
esp_sysviewtrace_timer_arg_t tim_arg1 = {
|
||||
.group = TIMER_GROUP_1,
|
||||
.timer = TIMER_1,
|
||||
.flags = ESP_INTR_FLAG_SHARED,
|
||||
.id = 0,
|
||||
.period = 500,
|
||||
};
|
||||
esp_sysviewtrace_task_arg_t arg1 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
@ -885,10 +777,9 @@ TEST_CASE("SysView trace test 1", "[trace][ignore]")
|
||||
.id = 0,
|
||||
};
|
||||
esp_sysviewtrace_timer_arg_t tim_arg2 = {
|
||||
.group = TIMER_GROUP_1,
|
||||
.timer = TIMER_0,
|
||||
.flags = 0,
|
||||
.id = 1,
|
||||
.period = 100,
|
||||
};
|
||||
esp_sysviewtrace_task_arg_t arg2 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
@ -899,8 +790,15 @@ TEST_CASE("SysView trace test 1", "[trace][ignore]")
|
||||
.id = 1,
|
||||
};
|
||||
|
||||
esp_apptrace_test_timer_init(TIMER_GROUP_1, TIMER_1, 500);
|
||||
esp_apptrace_test_timer_init(TIMER_GROUP_1, TIMER_0, 100);
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
timer_config.flags.intr_shared = (tim_arg1.flags & ESP_INTR_FLAG_SHARED) == ESP_INTR_FLAG_SHARED;
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &tim_arg1.gptimer));
|
||||
timer_config.flags.intr_shared = (tim_arg2.flags & ESP_INTR_FLAG_SHARED) == ESP_INTR_FLAG_SHARED;
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &tim_arg2.gptimer));
|
||||
|
||||
xTaskCreatePinnedToCore(esp_sysviewtrace_test_task, "svtrace0", 2048, &arg1, 3, &thnd, 0);
|
||||
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
|
||||
@ -915,6 +813,10 @@ TEST_CASE("SysView trace test 1", "[trace][ignore]")
|
||||
vSemaphoreDelete(arg1.done);
|
||||
xSemaphoreTake(arg2.done, portMAX_DELAY);
|
||||
vSemaphoreDelete(arg2.done);
|
||||
TEST_ESP_OK(gptimer_stop(tim_arg1.gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(tim_arg1.gptimer));
|
||||
TEST_ESP_OK(gptimer_stop(tim_arg2.gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(tim_arg2.gptimer));
|
||||
}
|
||||
|
||||
TEST_CASE("SysView trace test 2", "[trace][ignore]")
|
||||
@ -922,10 +824,9 @@ TEST_CASE("SysView trace test 2", "[trace][ignore]")
|
||||
TaskHandle_t thnd;
|
||||
|
||||
esp_sysviewtrace_timer_arg_t tim_arg1 = {
|
||||
.group = TIMER_GROUP_1,
|
||||
.timer = TIMER_1,
|
||||
.flags = ESP_INTR_FLAG_SHARED,
|
||||
.id = 0,
|
||||
.period = 500,
|
||||
};
|
||||
esp_sysviewtrace_task_arg_t arg1 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
@ -936,10 +837,9 @@ TEST_CASE("SysView trace test 2", "[trace][ignore]")
|
||||
.id = 0,
|
||||
};
|
||||
esp_sysviewtrace_timer_arg_t tim_arg2 = {
|
||||
.group = TIMER_GROUP_1,
|
||||
.timer = TIMER_0,
|
||||
.flags = 0,
|
||||
.id = 1,
|
||||
.period = 100,
|
||||
};
|
||||
esp_sysviewtrace_task_arg_t arg2 = {
|
||||
.done = xSemaphoreCreateBinary(),
|
||||
@ -969,8 +869,15 @@ TEST_CASE("SysView trace test 2", "[trace][ignore]")
|
||||
.id = 3,
|
||||
};
|
||||
|
||||
esp_apptrace_test_timer_init(TIMER_GROUP_1, TIMER_1, 500);
|
||||
esp_apptrace_test_timer_init(TIMER_GROUP_1, TIMER_0, 100);
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
timer_config.flags.intr_shared = (tim_arg1.flags & ESP_INTR_FLAG_SHARED) == ESP_INTR_FLAG_SHARED;
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &tim_arg1.gptimer));
|
||||
timer_config.flags.intr_shared = (tim_arg2.flags & ESP_INTR_FLAG_SHARED) == ESP_INTR_FLAG_SHARED;
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &tim_arg2.gptimer));
|
||||
|
||||
xTaskCreatePinnedToCore(esp_sysviewtrace_test_task, "svtrace0", 2048, &arg1, 3, &thnd, 0);
|
||||
printf("Created task %x\n", (uint32_t)thnd);
|
||||
@ -999,6 +906,10 @@ TEST_CASE("SysView trace test 2", "[trace][ignore]")
|
||||
xSemaphoreTake(arg4.done, portMAX_DELAY);
|
||||
vSemaphoreDelete(arg4.done);
|
||||
vSemaphoreDelete(test_sync);
|
||||
TEST_ESP_OK(gptimer_stop(tim_arg1.gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(tim_arg1.gptimer));
|
||||
TEST_ESP_OK(gptimer_stop(tim_arg2.gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(tim_arg2.gptimer));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // #if CONFIG_APPTRACE_SV_ENABLE == 0
|
||||
#endif // #if CONFIG_APPTRACE_ENABLE == 1
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -324,5 +324,5 @@ template<typename T> __attribute__((unused)) static void test_binary_operators()
|
||||
}
|
||||
|
||||
//Add more types here. If any flags cannot pass the build, use FLAG_ATTR in esp_attr.h
|
||||
#include "driver/timer.h"
|
||||
#include "driver/timer_types_legacy.h"
|
||||
template void test_binary_operators<timer_intr_t>();
|
||||
|
@ -2,8 +2,11 @@ idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
set(srcs
|
||||
"gpio.c"
|
||||
"gptimer.c"
|
||||
"timer_legacy.c"
|
||||
"i2c.c"
|
||||
"ledc.c"
|
||||
"legacy_new_driver_coexist.c"
|
||||
"periph_ctrl.c"
|
||||
"rtc_io.c"
|
||||
"rtc_module.c"
|
||||
@ -14,7 +17,6 @@ set(srcs
|
||||
"spi_master.c"
|
||||
"spi_slave.c"
|
||||
"spi_bus_lock.c"
|
||||
"timer.c"
|
||||
"uart.c")
|
||||
|
||||
set(includes "include" "${target}/include" "deprecated")
|
||||
|
@ -188,4 +188,30 @@ menu "Driver configurations"
|
||||
(e.g. SPI Flash write).
|
||||
endmenu # GDMA Configuration
|
||||
|
||||
menu "GPTimer Configuration"
|
||||
config GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
bool "Place GPTimer control functions into IRAM"
|
||||
default n
|
||||
help
|
||||
Place GPTimer control functions (like start/stop) into IRAM,
|
||||
so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context.
|
||||
Enabling this option can improve driver performance as well.
|
||||
|
||||
config GPTIMER_ISR_IRAM_SAFE
|
||||
bool "GPTimer ISR IRAM-Safe"
|
||||
default n
|
||||
help
|
||||
This will ensure the GPTimer interrupt handle is IRAM-Safe, allow to avoid flash
|
||||
cache misses, and also be able to run whilst the cache is disabled.
|
||||
(e.g. SPI Flash write)
|
||||
|
||||
config GPTIMER_SUPPRESS_DEPRECATE_WARN
|
||||
bool "Suppress leagcy driver deprecated warning"
|
||||
default n
|
||||
help
|
||||
Wether to suppress the deprecation warnings when using legacy timer group driver (driver/timer.h).
|
||||
If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
|
||||
you can enable this option.
|
||||
endmenu # GPTimer Configuration
|
||||
|
||||
endmenu # Driver configurations
|
||||
|
@ -7,147 +7,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_attr.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "hal/timer_types.h"
|
||||
#include "driver/timer_types_legacy.h"
|
||||
|
||||
#if !CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN
|
||||
#warning "legacy timer group driver is deprecated, please migrate to driver/gptimer.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Frequency of the clock on the input of the timer groups
|
||||
* @note This macro is not correct for Timer Groups with multiple clock sources (e.g. APB, XTAL)
|
||||
* So please don't use it in your application, we keep it here only for backward compatible
|
||||
*/
|
||||
#define TIMER_BASE_CLK (APB_CLK_FREQ)
|
||||
|
||||
/**
|
||||
* @brief Selects a Timer-Group out of 2 available groups
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_GROUP_0 = 0, /*!< Hw timer group 0 */
|
||||
#if SOC_TIMER_GROUPS > 1
|
||||
TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */
|
||||
#endif
|
||||
TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */
|
||||
} timer_group_t;
|
||||
|
||||
/**
|
||||
* @brief Select a hardware timer from timer groups
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_0 = 0, /*!<Select timer0 of GROUPx*/
|
||||
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
|
||||
TIMER_1 = 1, /*!<Select timer1 of GROUPx*/
|
||||
#endif
|
||||
TIMER_MAX,
|
||||
} timer_idx_t;
|
||||
|
||||
/**
|
||||
* @brief Interrupt types of the timer.
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_INTR_T0 = 1 << 0, /*!< interrupt of timer 0 */
|
||||
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
|
||||
TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */
|
||||
TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */
|
||||
#else
|
||||
TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */
|
||||
#endif
|
||||
TIMER_INTR_NONE = 0
|
||||
} timer_intr_t;
|
||||
FLAG_ATTR(timer_intr_t)
|
||||
|
||||
/**
|
||||
* @brief Decides the direction of counter
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/
|
||||
TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/
|
||||
TIMER_COUNT_MAX /*!< Maximum number of timer count directions */
|
||||
} timer_count_dir_t;
|
||||
|
||||
/**
|
||||
* @brief Decides whether timer is on or paused
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_PAUSE, /*!<Pause timer counter*/
|
||||
TIMER_START, /*!<Start timer counter*/
|
||||
} timer_start_t;
|
||||
|
||||
/**
|
||||
* @brief Decides whether to enable alarm mode
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_ALARM_DIS = 0, /*!< Disable timer alarm*/
|
||||
TIMER_ALARM_EN = 1, /*!< Enable timer alarm*/
|
||||
TIMER_ALARM_MAX
|
||||
} timer_alarm_t;
|
||||
|
||||
/**
|
||||
* @brief Select interrupt type if running in alarm mode.
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_INTR_LEVEL = 0, /*!< Interrupt mode: level mode*/
|
||||
TIMER_INTR_MAX
|
||||
} timer_intr_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Select if Alarm needs to be loaded by software or automatically reload by hardware.
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_AUTORELOAD_DIS = 0, /*!< Disable auto-reload: hardware will not load counter value after an alarm event*/
|
||||
TIMER_AUTORELOAD_EN = 1, /*!< Enable auto-reload: hardware will load counter value after an alarm event*/
|
||||
TIMER_AUTORELOAD_MAX,
|
||||
} timer_autoreload_t;
|
||||
|
||||
/**
|
||||
* @brief Select timer source clock.
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_SRC_CLK_APB = GPTIMER_CLK_SRC_APB, /*!< Select APB as the source clock*/
|
||||
#if SOC_TIMER_GROUP_SUPPORT_XTAL
|
||||
TIMER_SRC_CLK_XTAL = GPTIMER_CLK_SRC_XTAL, /*!< Select XTAL as the source clock*/
|
||||
#endif
|
||||
} timer_src_clk_t;
|
||||
|
||||
/**
|
||||
* @brief Data structure with timer's configuration settings
|
||||
*/
|
||||
typedef struct {
|
||||
timer_alarm_t alarm_en; /*!< Timer alarm enable */
|
||||
timer_start_t counter_en; /*!< Counter enable */
|
||||
timer_intr_mode_t intr_type; /*!< Interrupt mode */
|
||||
timer_count_dir_t counter_dir; /*!< Counter direction */
|
||||
timer_autoreload_t auto_reload; /*!< Timer auto-reload */
|
||||
timer_src_clk_t clk_src; /*!< Selects source clock. */
|
||||
uint32_t divider; /*!< Counter clock divider */
|
||||
} timer_config_t;
|
||||
|
||||
/**
|
||||
* @brief Interrupt handle callback function. User need to retrun a bool value
|
||||
* in callback.
|
||||
*
|
||||
* @return
|
||||
* - True Do task yield at the end of ISR
|
||||
* - False Not do task yield at the end of ISR
|
||||
*
|
||||
* @note If you called FreeRTOS functions in callback, you need to return true or false based on
|
||||
* the retrun value of argument `pxHigherPriorityTaskWoken`.
|
||||
* For example, `xQueueSendFromISR` is called in callback, if the return value `pxHigherPriorityTaskWoken`
|
||||
* of any FreeRTOS calls is pdTRUE, return true; otherwise return false.
|
||||
*/
|
||||
typedef bool (*timer_isr_t)(void *);
|
||||
|
||||
/**
|
||||
* @brief Interrupt handle, used in order to free the isr after use.
|
||||
* Aliases to an int handle for now.
|
||||
*/
|
||||
typedef intr_handle_t timer_isr_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Read the counter value of hardware timer.
|
||||
*
|
149
components/driver/deprecated/driver/timer_types_legacy.h
Normal file
149
components/driver/deprecated/driver/timer_types_legacy.h
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/timer_types.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Frequency of the clock on the input of the timer groups
|
||||
* @note This macro is not correct for Timer Groups with multiple clock sources (e.g. APB, XTAL)
|
||||
* So please don't use it in your application, we keep it here only for backward compatible
|
||||
*/
|
||||
#define TIMER_BASE_CLK _Pragma ("GCC warning \"'TIMER_BASE_CLK' macro is deprecated\"") APB_CLK_FREQ
|
||||
|
||||
/**
|
||||
* @brief Timer-Group ID
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_GROUP_0 = 0, /*!< Hw timer group 0 */
|
||||
#if SOC_TIMER_GROUPS > 1
|
||||
TIMER_GROUP_1 = 1, /*!< Hw timer group 1 */
|
||||
#endif
|
||||
TIMER_GROUP_MAX /*!< Maximum number of Hw timer groups */
|
||||
} timer_group_t;
|
||||
|
||||
/**
|
||||
* @brief Timer ID
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_0 = 0, /*!< Select timer0 of GROUPx*/
|
||||
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
|
||||
TIMER_1 = 1, /*!< Select timer1 of GROUPx*/
|
||||
#endif
|
||||
TIMER_MAX,
|
||||
} timer_idx_t;
|
||||
|
||||
/**
|
||||
* @brief Interrupt types of the timer.
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_INTR_T0 = 1 << 0, /*!< interrupt of timer 0 */
|
||||
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
|
||||
TIMER_INTR_T1 = 1 << 1, /*!< interrupt of timer 1 */
|
||||
TIMER_INTR_WDT = 1 << 2, /*!< interrupt of watchdog */
|
||||
#else
|
||||
TIMER_INTR_WDT = 1 << 1, /*!< interrupt of watchdog */
|
||||
#endif
|
||||
TIMER_INTR_NONE = 0
|
||||
} timer_intr_t;
|
||||
FLAG_ATTR(timer_intr_t)
|
||||
|
||||
/**
|
||||
* @brief Timer count direction
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_COUNT_DOWN = GPTIMER_COUNT_DOWN, /*!< Descending Count from cnt.high|cnt.low*/
|
||||
TIMER_COUNT_UP = GPTIMER_COUNT_UP, /*!< Ascending Count from Zero*/
|
||||
TIMER_COUNT_MAX /*!< Maximum number of timer count directions */
|
||||
} timer_count_dir_t;
|
||||
|
||||
/**
|
||||
* @brief Timer start/stop command
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_PAUSE, /*!< Pause timer counter*/
|
||||
TIMER_START, /*!< Start timer counter*/
|
||||
} timer_start_t;
|
||||
|
||||
/**
|
||||
* @brief Timer alarm command
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_ALARM_DIS = 0, /*!< Disable timer alarm*/
|
||||
TIMER_ALARM_EN = 1, /*!< Enable timer alarm*/
|
||||
TIMER_ALARM_MAX
|
||||
} timer_alarm_t;
|
||||
|
||||
/**
|
||||
* @brief Timer interrupt type
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_INTR_LEVEL = 0, /*!< Interrupt mode: level mode*/
|
||||
TIMER_INTR_MAX
|
||||
} timer_intr_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Timer autoreload command
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_AUTORELOAD_DIS = 0, /*!< Disable auto-reload: hardware will not load counter value after an alarm event*/
|
||||
TIMER_AUTORELOAD_EN = 1, /*!< Enable auto-reload: hardware will load counter value after an alarm event*/
|
||||
TIMER_AUTORELOAD_MAX,
|
||||
} timer_autoreload_t;
|
||||
|
||||
/**
|
||||
* @brief Timer group clock source
|
||||
*/
|
||||
typedef enum {
|
||||
TIMER_SRC_CLK_APB = GPTIMER_CLK_SRC_APB, /*!< Select APB as the source clock*/
|
||||
#if SOC_TIMER_GROUP_SUPPORT_XTAL
|
||||
TIMER_SRC_CLK_XTAL = GPTIMER_CLK_SRC_XTAL, /*!< Select XTAL as the source clock*/
|
||||
#endif
|
||||
} timer_src_clk_t;
|
||||
|
||||
/**
|
||||
* @brief Interrupt handler callback function
|
||||
*
|
||||
* @return
|
||||
* - True Do task yield at the end of ISR
|
||||
* - False Not do task yield at the end of ISR
|
||||
*
|
||||
* @note If you called FreeRTOS functions in callback, you need to return true or false based on
|
||||
* the retrun value of argument `pxHigherPriorityTaskWoken`.
|
||||
* For example, `xQueueSendFromISR` is called in callback, if the return value `pxHigherPriorityTaskWoken`
|
||||
* of any FreeRTOS calls is pdTRUE, return true; otherwise return false.
|
||||
*/
|
||||
typedef bool (*timer_isr_t)(void *);
|
||||
|
||||
/**
|
||||
* @brief Interrupt handle, used in order to free the isr after use.
|
||||
*/
|
||||
typedef intr_handle_t timer_isr_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Timer configurations
|
||||
*/
|
||||
typedef struct {
|
||||
timer_alarm_t alarm_en; /*!< Timer alarm enable */
|
||||
timer_start_t counter_en; /*!< Counter enable */
|
||||
timer_intr_mode_t intr_type; /*!< Interrupt mode */
|
||||
timer_count_dir_t counter_dir; /*!< Counter direction */
|
||||
timer_autoreload_t auto_reload; /*!< Timer auto-reload */
|
||||
timer_src_clk_t clk_src; /*!< Selects source clock. */
|
||||
uint32_t divider; /*!< Counter clock divider */
|
||||
} timer_config_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
530
components/driver/gptimer.c
Normal file
530
components/driver/gptimer.c
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG // uncomment this line to enable debug logs
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/lock.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_pm.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "hal/timer_types.h"
|
||||
#include "hal/timer_hal.h"
|
||||
#include "hal/timer_ll.h"
|
||||
#include "soc/timer_periph.h"
|
||||
#include "soc/soc_memory_types.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
|
||||
// If ISR handler is allowed to run whilst cache is disabled,
|
||||
// Make sure all the code and related variables used by the handler are in the SRAM
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#define GPTIMER_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
|
||||
#define GPTIMER_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define GPTIMER_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
|
||||
#define GPTIMER_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif //CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
|
||||
#if CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
#define GPTIMER_CTRL_FUNC_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define GPTIMER_CTRL_FUNC_ATTR
|
||||
#endif // CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM
|
||||
|
||||
#define GPTIMER_PM_LOCK_NAME_LEN_MAX 16
|
||||
|
||||
static const char *TAG = "gptimer";
|
||||
|
||||
typedef struct gptimer_platform_t gptimer_platform_t;
|
||||
typedef struct gptimer_group_t gptimer_group_t;
|
||||
typedef struct gptimer_t gptimer_t;
|
||||
|
||||
struct gptimer_platform_t {
|
||||
_lock_t mutex; // platform level mutex lock
|
||||
gptimer_group_t *groups[SOC_TIMER_GROUPS]; // timer group pool
|
||||
int group_ref_counts[SOC_TIMER_GROUPS]; // reference count used to protect group install/uninstall
|
||||
};
|
||||
|
||||
struct gptimer_group_t {
|
||||
int group_id;
|
||||
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
|
||||
gptimer_t *timers[SOC_TIMER_GROUP_TIMERS_PER_GROUP];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GPTIMER_FSM_STOP,
|
||||
GPTIMER_FSM_START,
|
||||
} gptimer_lifecycle_fsm_t;
|
||||
|
||||
struct gptimer_t {
|
||||
gptimer_group_t *group;
|
||||
int timer_id;
|
||||
unsigned int resolution_hz;
|
||||
unsigned long long reload_count;
|
||||
unsigned long long alarm_count;
|
||||
gptimer_count_direction_t direction;
|
||||
timer_hal_context_t hal;
|
||||
gptimer_lifecycle_fsm_t fsm; // access to fsm should be protect by spinlock, as fsm is also accessed from ISR handler
|
||||
intr_handle_t intr;
|
||||
_lock_t mutex; // to protect other resource allocation, like interrupt handle
|
||||
portMUX_TYPE spinlock; // to protect per-timer resources concurent accessed by task and ISR handler
|
||||
gptimer_alarm_cb_t on_alarm;
|
||||
void *user_ctx;
|
||||
esp_pm_lock_handle_t pm_lock; // power management lock
|
||||
#if CONFIG_PM_ENABLE
|
||||
char pm_lock_name[GPTIMER_PM_LOCK_NAME_LEN_MAX]; // pm lock name
|
||||
#endif
|
||||
struct {
|
||||
uint32_t intr_shared: 1;
|
||||
uint32_t auto_reload_on_alarm: 1;
|
||||
uint32_t alarm_en: 1;
|
||||
} flags;
|
||||
};
|
||||
|
||||
// gptimer driver platform, it's always a singleton
|
||||
static gptimer_platform_t s_platform;
|
||||
|
||||
static gptimer_group_t *gptimer_acquire_group_handle(int group_id);
|
||||
static void gptimer_release_group_handle(gptimer_group_t *group);
|
||||
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz);
|
||||
static esp_err_t gptimer_install_interrupt(gptimer_t *timer);
|
||||
IRAM_ATTR static void gptimer_default_isr(void *args);
|
||||
|
||||
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
gptimer_group_t *group = NULL;
|
||||
gptimer_t *timer = NULL;
|
||||
int group_id = -1;
|
||||
int timer_id = -1;
|
||||
ESP_GOTO_ON_FALSE(config && ret_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid timer resolution:%d", config->resolution_hz);
|
||||
|
||||
timer = heap_caps_calloc(1, sizeof(gptimer_t), GPTIMER_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for gptimer");
|
||||
|
||||
for (int i = 0; (i < SOC_TIMER_GROUPS) && (timer_id < 0); i++) {
|
||||
group = gptimer_acquire_group_handle(i);
|
||||
ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group (%d)", group_id);
|
||||
// loop to search free timer in the group
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
|
||||
if (!group->timers[j]) {
|
||||
group_id = i;
|
||||
timer_id = j;
|
||||
group->timers[j] = timer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (timer_id < 0) {
|
||||
gptimer_release_group_handle(group);
|
||||
group = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_GOTO_ON_FALSE(timer_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free timer");
|
||||
timer->timer_id = timer_id;
|
||||
timer->group = group;
|
||||
// initialize HAL layer
|
||||
timer_hal_init(&timer->hal, group_id, timer_id);
|
||||
// stop counter, alarm, auto-reload
|
||||
timer_ll_enable_counter(timer->hal.dev, timer_id, false);
|
||||
timer_ll_enable_auto_reload(timer->hal.dev, timer_id, false);
|
||||
timer_ll_enable_alarm(timer->hal.dev, timer_id, false);
|
||||
// select clock source, set clock resolution
|
||||
ESP_GOTO_ON_ERROR(gptimer_select_periph_clock(timer, config->clk_src, config->resolution_hz), err, TAG, "set periph clock failed");
|
||||
// initialize counter value to zero
|
||||
timer_hal_set_counter_value(&timer->hal, 0);
|
||||
// set counting direction
|
||||
timer_ll_set_count_direction(timer->hal.dev, timer_id, config->direction);
|
||||
|
||||
// interrupt register is shared by all timers in the same group
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id), false); // disable interrupt
|
||||
timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer_id)); // clear pending interrupt event
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
// initialize other members of timer
|
||||
timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
timer->fsm = GPTIMER_FSM_STOP;
|
||||
timer->direction = config->direction;
|
||||
timer->flags.intr_shared = config->flags.intr_shared;
|
||||
_lock_init(&timer->mutex);
|
||||
ESP_LOGD(TAG, "new gptimer (%d,%d) at %p, resolution=%uHz", group_id, timer_id, timer, timer->resolution_hz);
|
||||
*ret_timer = timer;
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (timer) {
|
||||
if (timer->pm_lock) {
|
||||
esp_pm_lock_delete(timer->pm_lock);
|
||||
}
|
||||
free(timer);
|
||||
}
|
||||
if (group) {
|
||||
gptimer_release_group_handle(group);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t gptimer_del_timer(gptimer_handle_t timer)
|
||||
{
|
||||
gptimer_group_t *group = NULL;
|
||||
bool valid_state = true;
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
portENTER_CRITICAL(&timer->spinlock);
|
||||
if (timer->fsm != GPTIMER_FSM_STOP) {
|
||||
valid_state = false;
|
||||
}
|
||||
portEXIT_CRITICAL(&timer->spinlock);
|
||||
ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "can't delete timer as it's not in stop state");
|
||||
group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
|
||||
if (timer->intr) {
|
||||
esp_intr_free(timer->intr);
|
||||
ESP_LOGD(TAG, "uninstall interrupt service for timer (%d,%d)", group_id, timer_id);
|
||||
}
|
||||
if (timer->pm_lock) {
|
||||
esp_pm_lock_delete(timer->pm_lock);
|
||||
ESP_LOGD(TAG, "uninstall APB_FREQ_MAX lock for timer (%d,%d)", group_id, timer_id);
|
||||
}
|
||||
_lock_close(&timer->mutex);
|
||||
free(timer);
|
||||
ESP_LOGD(TAG, "del timer (%d,%d)", group_id, timer_id);
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->timers[timer_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
// timer has a reference on group, release it now
|
||||
gptimer_release_group_handle(group);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPTIMER_CTRL_FUNC_ATTR
|
||||
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_hal_set_counter_value(&timer->hal, value);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPTIMER_CTRL_FUNC_ATTR
|
||||
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *value)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
*value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
gptimer_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(timer && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = timer->group;
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
if (cbs->on_alarm) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_alarm), ESP_ERR_INVALID_ARG, TAG, "on_alarm callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_dram(user_data) ||
|
||||
esp_ptr_in_diram_dram(user_data) ||
|
||||
esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in DRAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
ESP_RETURN_ON_ERROR(gptimer_install_interrupt(timer), TAG, "install interrupt service failed");
|
||||
|
||||
// enable/disable GPTimer interrupt events
|
||||
portENTER_CRITICAL_SAFE(&group->spinlock);
|
||||
timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id), cbs->on_alarm); // enable timer interrupt
|
||||
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
||||
|
||||
timer->on_alarm = cbs->on_alarm;
|
||||
timer->user_ctx = user_data;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPTIMER_CTRL_FUNC_ATTR
|
||||
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (config) {
|
||||
// When auto_reload is enabled, alarm_count should not be equal to reload_count
|
||||
bool valid_auto_reload = !config->flags.auto_reload_on_alarm || config->alarm_count != config->reload_count;
|
||||
ESP_RETURN_ON_FALSE(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count");
|
||||
|
||||
timer->reload_count = config->reload_count;
|
||||
timer->alarm_count = config->alarm_count;
|
||||
timer->flags.auto_reload_on_alarm = config->flags.auto_reload_on_alarm;
|
||||
timer->flags.alarm_en = true;
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_ll_set_reload_value(timer->hal.dev, timer->timer_id, config->reload_count);
|
||||
timer_ll_set_alarm_value(timer->hal.dev, timer->timer_id, config->alarm_count);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
} else {
|
||||
timer->flags.auto_reload_on_alarm = false;
|
||||
timer->flags.alarm_en = false;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_ll_enable_auto_reload(timer->hal.dev, timer->timer_id, timer->flags.auto_reload_on_alarm);
|
||||
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPTIMER_CTRL_FUNC_ATTR
|
||||
esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
// acquire power manager lock
|
||||
if (timer->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
|
||||
}
|
||||
// interrupt interupt service
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
|
||||
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
|
||||
timer->fsm = GPTIMER_FSM_START;
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPTIMER_CTRL_FUNC_ATTR
|
||||
esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
// disable counter, alarm, autoreload
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, false);
|
||||
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, false);
|
||||
timer->fsm = GPTIMER_FSM_STOP;
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
|
||||
// disable interrupt service
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
|
||||
}
|
||||
// release power manager lock
|
||||
if (timer->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static gptimer_group_t *gptimer_acquire_group_handle(int group_id)
|
||||
{
|
||||
// esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
bool new_group = false;
|
||||
gptimer_group_t *group = NULL;
|
||||
|
||||
// prevent install timer group concurrently
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
if (!s_platform.groups[group_id]) {
|
||||
group = heap_caps_calloc(1, sizeof(gptimer_group_t), GPTIMER_MEM_ALLOC_CAPS);
|
||||
if (group) {
|
||||
new_group = true;
|
||||
s_platform.groups[group_id] = group;
|
||||
// initialize timer group members
|
||||
group->group_id = group_id;
|
||||
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
// enable APB access timer registers
|
||||
periph_module_enable(timer_group_periph_signals.groups[group_id].module);
|
||||
}
|
||||
} else {
|
||||
group = s_platform.groups[group_id];
|
||||
}
|
||||
// someone acquired the group handle means we have a new object that refer to this group
|
||||
s_platform.group_ref_counts[group_id]++;
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (new_group) {
|
||||
ESP_LOGD(TAG, "new group (%d) @%p", group_id, group);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static void gptimer_release_group_handle(gptimer_group_t *group)
|
||||
{
|
||||
int group_id = group->group_id;
|
||||
bool do_deinitialize = false;
|
||||
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
s_platform.group_ref_counts[group_id]--;
|
||||
if (s_platform.group_ref_counts[group_id] == 0) {
|
||||
assert(s_platform.groups[group_id]);
|
||||
do_deinitialize = true;
|
||||
s_platform.groups[group_id] = NULL;
|
||||
// Theoretically we need to disable the peripheral clock for the timer group
|
||||
// However, next time when we enable the peripheral again, the registers will be reset to default value, including the watchdog registers inside the group
|
||||
// Then the watchdog will go into reset state, e.g. the flash boot watchdog is enabled again and reset the system very soon
|
||||
// periph_module_disable(timer_group_periph_signals.groups[group_id].module);
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (do_deinitialize) {
|
||||
free(group);
|
||||
ESP_LOGD(TAG, "del group (%d)", group_id);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz)
|
||||
{
|
||||
unsigned int counter_src_hz = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
int timer_id = timer->timer_id;
|
||||
switch (src_clk) {
|
||||
case GPTIMER_CLK_SRC_APB:
|
||||
counter_src_hz = esp_clk_apb_freq();
|
||||
#if CONFIG_PM_ENABLE
|
||||
sprintf(timer->pm_lock_name, "gptimer_%d_%d", timer->group->group_id, timer_id); // e.g. gptimer_0_0
|
||||
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, timer->pm_lock_name, &timer->pm_lock);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "create APB_FREQ_MAX lock failed");
|
||||
ESP_LOGD(TAG, "install APB_FREQ_MAX lock for timer (%d,%d)", timer->group->group_id, timer_id);
|
||||
#endif
|
||||
break;
|
||||
#if SOC_TIMER_GROUP_SUPPORT_XTAL
|
||||
case GPTIMER_CLK_SRC_XTAL:
|
||||
counter_src_hz = esp_clk_xtal_freq();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not support", src_clk);
|
||||
break;
|
||||
}
|
||||
timer_ll_set_clock_source(timer->hal.dev, timer_id, src_clk);
|
||||
unsigned int prescale = counter_src_hz / resolution_hz; // potential resolution loss here
|
||||
timer_ll_set_clock_prescale(timer->hal.dev, timer_id, prescale);
|
||||
timer->resolution_hz = counter_src_hz / prescale; // this is the real resolution
|
||||
if (timer->resolution_hz != resolution_hz) {
|
||||
ESP_LOGW(TAG, "resolution lost, expect %ul, real %ul", resolution_hz, timer->resolution_hz);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t gptimer_install_interrupt(gptimer_t *timer)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
gptimer_group_t *group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
bool new_isr = false;
|
||||
|
||||
if (!timer->intr) {
|
||||
_lock_acquire(&timer->mutex);
|
||||
if (!timer->intr) {
|
||||
// if user wants to control the interrupt allocation more precisely, we can expose more flags in `gptimer_config_t`
|
||||
int extra_isr_flags = timer->flags.intr_shared ? ESP_INTR_FLAG_SHARED : 0;
|
||||
ret = esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_id].timer_irq_id[timer_id], extra_isr_flags | GPTIMER_INTR_ALLOC_FLAGS,
|
||||
(uint32_t)timer_ll_get_intr_status_reg(timer->hal.dev), TIMER_LL_EVENT_ALARM(timer_id),
|
||||
gptimer_default_isr, timer, &timer->intr);
|
||||
new_isr = (ret == ESP_OK);
|
||||
}
|
||||
_lock_release(&timer->mutex);
|
||||
}
|
||||
|
||||
if (new_isr) {
|
||||
ESP_LOGD(TAG, "install interrupt service for timer (%d,%d)", group_id, timer_id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Put the default ISR handler in the IRAM for better performance
|
||||
IRAM_ATTR static void gptimer_default_isr(void *args)
|
||||
{
|
||||
bool need_yield = false;
|
||||
gptimer_t *timer = (gptimer_t *)args;
|
||||
gptimer_group_t *group = timer->group;
|
||||
gptimer_alarm_cb_t on_alarm_cb = timer->on_alarm;
|
||||
uint32_t intr_status = timer_ll_get_intr_status(timer->hal.dev);
|
||||
|
||||
if (intr_status & TIMER_LL_EVENT_ALARM(timer->timer_id)) {
|
||||
// Note: when alarm event happends, the alarm will be disabled automatically by hardware
|
||||
gptimer_alarm_event_data_t edata = {
|
||||
.count_value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id),
|
||||
.alarm_value = timer->alarm_count,
|
||||
};
|
||||
|
||||
portENTER_CRITICAL_ISR(&group->spinlock);
|
||||
timer_ll_clear_intr_status(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id));
|
||||
// for auto-reload, we need to re-enable the alarm manually
|
||||
if (timer->flags.auto_reload_on_alarm) {
|
||||
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, true);
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&group->spinlock);
|
||||
|
||||
if (on_alarm_cb) {
|
||||
if (on_alarm_cb(timer, &edata, timer->user_ctx)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///// The Following APIs are for internal use only (e.g. unit test) /////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
*ret_intr_handle = timer->intr;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
*ret_pm_lock = timer->pm_lock;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that gptimer driver is not running along with the legacy timer group driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_gptimer_driver_conflict(void)
|
||||
{
|
||||
extern int timer_group_driver_init_count;
|
||||
timer_group_driver_init_count++;
|
||||
if (timer_group_driver_init_count > 1) {
|
||||
ESP_EARLY_LOGE(TAG, "CONFLICT! The gptimer driver can't work along with the legacy timer group driver");
|
||||
abort();
|
||||
}
|
||||
}
|
197
components/driver/include/driver/gptimer.h
Normal file
197
components/driver/include/driver/gptimer.h
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "hal/timer_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Type of General Purpose Timer handle
|
||||
*/
|
||||
typedef struct gptimer_t *gptimer_handle_t;
|
||||
|
||||
/**
|
||||
* @brief GPTimer event data
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t count_value; /*!< Current count value */
|
||||
uint64_t alarm_value; /*!< Current alarm value */
|
||||
} gptimer_alarm_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Timer alarm callback prototype
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[in] edata Alarm event data, fed by driver
|
||||
* @param[in] user_ctx User data, passed from `gptimer_config_t`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*gptimer_alarm_cb_t) (gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief Group of supported GPTimer callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
gptimer_alarm_cb_t on_alarm; /*!< Timer alarm callback */
|
||||
} gptimer_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief General Purpose Timer configuration
|
||||
*/
|
||||
typedef struct {
|
||||
gptimer_clock_source_t clk_src; /*!< GPTimer clock source */
|
||||
gptimer_count_direction_t direction; /*!< Count direction */
|
||||
uint32_t resolution_hz; /*!< Counter resolution (working frequency) in Hz,
|
||||
hence, the step size of each count tick equals to (1 / resolution_hz) seconds */
|
||||
struct {
|
||||
uint32_t intr_shared: 1; /*!< Set true, the timer interrupt number can be shared with other peripherals */
|
||||
} flags;
|
||||
} gptimer_config_t;
|
||||
|
||||
/**
|
||||
* @brief General Purpose Timer alarm configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t alarm_count; /*!< Alarm target count value */
|
||||
uint64_t reload_count; /*!< Alarm reload count value, effect only when `auto_reload_on_alarm` is set to true */
|
||||
struct {
|
||||
uint32_t auto_reload_on_alarm: 1; /*!< Reload the count value by hardware, immediately at the alarm event */
|
||||
} flags;
|
||||
} gptimer_alarm_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create a new General Purpose Timer, and return the handle
|
||||
*
|
||||
* @note Once a timer is created, it is placed in the stopped state and will not start until `gptimer_start()` is called.
|
||||
*
|
||||
* @param[in] config GPTimer configuration
|
||||
* @param[out] ret_timer Returned timer handle
|
||||
* @return
|
||||
* - ESP_OK: Create GPTimer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create GPTimer failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create GPTimer failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create GPTimer failed because all hardware timers are used up and no more free one
|
||||
* - ESP_FAIL: Create GPTimer failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer);
|
||||
|
||||
/**
|
||||
* @brief Delete the GPTimer handle
|
||||
*
|
||||
* @note A timer must be in a stop state before it can be deleted.
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Delete GPTimer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete GPTimer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Delete GPTimer failed because the timer has not stopped
|
||||
* - ESP_FAIL: Delete GPTimer failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_del_timer(gptimer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Set GPTimer raw count value
|
||||
*
|
||||
* @note When updating the raw count of an active timer, the timer will immediately start counting from the new value.
|
||||
* @note This function is allowed to run within ISR context
|
||||
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[in] value Count value to be set
|
||||
* @return
|
||||
* - ESP_OK: Set GPTimer raw count value successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set GPTimer raw count value failed because of invalid argument
|
||||
* - ESP_FAIL: Set GPTimer raw count value failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, uint64_t value);
|
||||
|
||||
/**
|
||||
* @brief Get GPTimer raw count value
|
||||
*
|
||||
* @note With the raw count value and the resolution set in the `gptimer_config_t`, you can convert the count value into seconds.
|
||||
* @note This function is allowed to run within ISR context
|
||||
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[out] value Returned GPTimer count value
|
||||
* @return
|
||||
* - ESP_OK: Get GPTimer raw count value successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get GPTimer raw count value failed because of invalid argument
|
||||
* - ESP_FAIL: Get GPTimer raw count value failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, uint64_t *value);
|
||||
|
||||
/**
|
||||
* @brief Set callbacks for GPTimer
|
||||
*
|
||||
* @note The user registered callbacks are expected to be runnable within ISR context
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Set alarm event actions for GPTimer.
|
||||
*
|
||||
* @note This function is allowed to run within ISR context
|
||||
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[in] config Alarm configuration, especially, set config to NULL means disabling the alarm function
|
||||
* @return
|
||||
* - ESP_OK: Set alarm action for GPTimer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set alarm action for GPTimer failed because of invalid argument
|
||||
* - ESP_FAIL: Set alarm action for GPTimer failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Start GPTimer
|
||||
*
|
||||
* @note This function is allowed to run within ISR context
|
||||
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Start GPTimer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Start GPTimer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Start GPTimer failed because the timer is not in stop state
|
||||
* - ESP_FAIL: Start GPTimer failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_start(gptimer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Stop GPTimer
|
||||
*
|
||||
* @note This function is allowed to run within ISR context
|
||||
* @note This function is allowed to be executed when Cache is disabled, by enabling `CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM`
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Stop GPTimer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Stop GPTimer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Stop GPTimer failed because the timer is not in start state
|
||||
* - ESP_FAIL: Stop GPTimer failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_stop(gptimer_handle_t timer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
47
components/driver/include/esp_private/gptimer.h
Normal file
47
components/driver/include/esp_private/gptimer.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// DO NOT USE THESE APIS IN YOUR APPLICATIONS
|
||||
// The following APIs are for internal use, public to other IDF components, but not for users' applications.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_pm.h"
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get GPTimer interrupt handle
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[out] ret_intr_handle Timer's internal interrupt handle
|
||||
* @return
|
||||
* - ESP_OK: Get GPTimer interrupt handle successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get GPTimer interrupt handle failed because of invalid argument
|
||||
* - ESP_FAIL: Get GPTimer interrupt handle failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle);
|
||||
|
||||
/**
|
||||
* @brief Get GPTimer power management lock
|
||||
*
|
||||
* @param[in] timer Timer handle created by `gptimer_new_timer()`
|
||||
* @param[out] ret_pm_lock Timer's internal power management lock
|
||||
* @return
|
||||
* - ESP_OK: Get GPTimer power management lock successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get GPTimer power management lock failed because of invalid argument
|
||||
* - ESP_FAIL: Get GPTimer power management lock failed because of other error
|
||||
*/
|
||||
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
11
components/driver/legacy_new_driver_coexist.c
Normal file
11
components/driver/legacy_new_driver_coexist.c
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief This count is used to prevent the coexistence of
|
||||
* the legacy timer group driver (deprecated/driver/timer.h) and the new gptimer driver (driver/gptimer.h).
|
||||
*/
|
||||
int timer_group_driver_init_count = 0;
|
18
components/driver/test_apps/gptimer/CMakeLists.txt
Normal file
18
components/driver/test_apps/gptimer/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(gptimer_test)
|
||||
|
||||
if(CONFIG_GPTIMER_ISR_IRAM_SAFE)
|
||||
add_custom_target(check_test_app_sections ALL
|
||||
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
|
||||
--rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
|
||||
--elf-file ${CMAKE_BINARY_DIR}/gptimer_test.elf
|
||||
find-refs
|
||||
--from-sections=.iram0.text
|
||||
--to-sections=.flash.text,.flash.rodata
|
||||
--exit-code
|
||||
DEPENDS ${elf}
|
||||
)
|
||||
endif()
|
2
components/driver/test_apps/gptimer/README.md
Normal file
2
components/driver/test_apps/gptimer/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
30
components/driver/test_apps/gptimer/app_test.py
Normal file
30
components/driver/test_apps/gptimer/app_test.py
Normal file
@ -0,0 +1,30 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import glob
|
||||
import os
|
||||
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import Utility
|
||||
|
||||
|
||||
@ttfw_idf.idf_component_unit_test(env_tag='COMPONENT_UT_GENERIC', target=['esp32', 'esp32s2', 'esp32s3', 'esp32c3'])
|
||||
def test_component_ut_gptimer(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
|
||||
# Get the names of all configs (sdkconfig.ci.* files)
|
||||
config_files = glob.glob(os.path.join(os.path.dirname(__file__), 'sdkconfig.ci.*'))
|
||||
config_names = [os.path.basename(s).replace('sdkconfig.ci.', '') for s in config_files]
|
||||
|
||||
# Run test once with binaries built for each config
|
||||
for name in config_names:
|
||||
Utility.console_log(f'Checking config "{name}"... ', end='')
|
||||
dut = env.get_dut('gptimer', 'components/driver/test_apps/gptimer', app_config_name=name)
|
||||
dut.start_app()
|
||||
stdout = dut.expect('Press ENTER to see the list of tests', full_stdout=True)
|
||||
dut.write('*')
|
||||
stdout = dut.expect("Enter next test, or 'enter' to see menu", full_stdout=True, timeout=30)
|
||||
ttfw_idf.ComponentUTResult.parse_result(stdout,ttfw_idf.TestFormat.UNITY_BASIC)
|
||||
env.close_dut(dut.name)
|
||||
Utility.console_log(f'Test config "{name}" done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_component_ut_gptimer()
|
8
components/driver/test_apps/gptimer/main/CMakeLists.txt
Normal file
8
components/driver/test_apps/gptimer/main/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_gptimer.c"
|
||||
"test_gptimer_iram.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES driver unity spi_flash)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_gptimer" "-u test_app_include_gptimer_iram")
|
51
components/driver/test_apps/gptimer/main/test_app_main.c
Normal file
51
components/driver/test_apps/gptimer/main/test_app_main.c
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
// Some resources are lazy allocated in GPTimer driver, the threshold is left for that case
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-300)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// ____ ____ _____ _ _____ _
|
||||
// / ___| _ \_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_
|
||||
// | | _| |_) || | | | '_ ` _ \ / _ \ '__| | |/ _ \/ __| __|
|
||||
// | |_| | __/ | | | | | | | | | __/ | | | __/\__ \ |_
|
||||
// \____|_| |_| |_|_| |_| |_|\___|_| |_|\___||___/\__|
|
||||
printf(" ____ ____ _____ _ _____ _\r\n");
|
||||
printf(" / ___| _ \\_ _(_)_ __ ___ ___ _ __ |_ _|__ ___| |_\r\n");
|
||||
printf("| | _| |_) || | | | '_ ` _ \\ / _ \\ '__| | |/ _ \\/ __| __|\r\n");
|
||||
printf("| |_| | __/ | | | | | | | | | __/ | | | __/\\__ \\ |_\r\n");
|
||||
printf(" \\____|_| |_| |_|_| |_| |_|\\___|_| |_|\\___||___/\\__|\r\n");
|
||||
unity_run_menu();
|
||||
}
|
487
components/driver/test_apps/gptimer/main/test_gptimer.c
Normal file
487
components/driver/test_apps/gptimer/main/test_gptimer.c
Normal file
@ -0,0 +1,487 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define TEST_ALARM_CALLBACK_ATTR
|
||||
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
|
||||
void test_app_include_gptimer(void)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_set_get_raw_count", "[gptimer]")
|
||||
{
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &timers[i]));
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, gptimer_new_timer(&config, &timers[0]));
|
||||
unsigned long long get_value = 0;
|
||||
printf("check gptimer initial count value\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value));
|
||||
TEST_ASSERT_EQUAL(0, get_value);
|
||||
}
|
||||
unsigned long long set_values[] = {100, 500, 666};
|
||||
for (size_t j = 0; j < sizeof(set_values) / sizeof(set_values[0]); j++) {
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
printf("set raw count to %llu for gptimer %d\r\n", set_values[j], i);
|
||||
TEST_ESP_OK(gptimer_set_raw_count(timers[i], set_values[j]));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &get_value));
|
||||
printf("get raw count of gptimer %d: %llu\r\n", i, get_value);
|
||||
TEST_ASSERT_EQUAL(set_values[j], get_value);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_wallclock_with_various_clock_sources", "[gptimer]")
|
||||
{
|
||||
gptimer_clock_source_t test_clk_srcs[] = {
|
||||
GPTIMER_CLK_SRC_APB,
|
||||
#if SOC_TIMER_GROUP_SUPPORT_XTAL
|
||||
GPTIMER_CLK_SRC_XTAL,
|
||||
#endif // SOC_TIMER_GROUP_SUPPORT_XTAL
|
||||
};
|
||||
|
||||
// test with various clock sources
|
||||
for (size_t i = 0; i < sizeof(test_clk_srcs) / sizeof(test_clk_srcs[0]); i++) {
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = test_clk_srcs[i],
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20)); // 20ms = 20_000 ticks
|
||||
unsigned long long value = 0;
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
TEST_ASSERT_UINT_WITHIN(1000, 20000, value);
|
||||
}
|
||||
printf("stop timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
printf("check whether timers have stopped\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
printf("get raw count of gptimer %d: %llu\r\n", i, value);
|
||||
TEST_ASSERT_UINT_WITHIN(1000, 20000, value);
|
||||
}
|
||||
printf("restart timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
printf("stop timers again\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
printf("check whether timers have stopped\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
printf("get raw count of gptimer %d: %llu\r\n", i, value);
|
||||
TEST_ASSERT_UINT_WITHIN(2000, 40000, value);
|
||||
}
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_stop_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
gptimer_stop(timer);
|
||||
esp_rom_printf("count=%lld @alarm\n", edata->count_value);
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_stop_on_alarm", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_alarm_stop_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {};
|
||||
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
alarm_config.alarm_count = 100000 * (i + 1);
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
printf("alarm value for gptimer %d: %llu\r\n", i, alarm_config.alarm_count);
|
||||
}
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
|
||||
printf("check whether the timers have stopped in the ISR\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
unsigned long long value = 0;
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
printf("get raw count of gptimer %d: %llu\r\n", i, value);
|
||||
TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value);
|
||||
}
|
||||
|
||||
printf("restart timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
alarm_config.alarm_count = 100000 * (i + 1);
|
||||
// reset counter value to zero
|
||||
TEST_ESP_OK(gptimer_set_raw_count(timers[i], 0));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
}
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
printf("check whether the timers have stopped in the ISR\r\n");
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
printf("get raw count of gptimer %d: %llu\r\n", i, value);
|
||||
TEST_ASSERT_UINT_WITHIN(40, 100000 * (i + 1), value);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
|
||||
// check if the count value has been reloaded
|
||||
TEST_ASSERT_UINT_WITHIN(20, 100, edata->count_value);
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_auto_reload_on_alarm", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_alarm_reload_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 100,
|
||||
.alarm_count = 100000,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
// delete should fail if timer is not stopped
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, gptimer_del_timer(timers[i]));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_normal_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
|
||||
// check the count value at alarm event
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_one_shot_alarm", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_alarm_normal_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 100000, // 100ms
|
||||
};
|
||||
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
// no alarm event should trigger again, as auto-reload is not enabled and alarm value hasn't changed in the isr
|
||||
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
// the alarm is stopped, but the counter should still work
|
||||
uint64_t value = 0;
|
||||
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &value));
|
||||
TEST_ASSERT_UINT_WITHIN(1000, 1100000, value); // 1100000 = 100ms alarm + 1s delay
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
printf("restart timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
// alarm should be triggered immediately as the counter value has across the target alarm value already
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, 0));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_alarm_update_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = edata->count_value + 100000, // alarm in next 100ms again
|
||||
};
|
||||
gptimer_set_alarm_action(timer, &alarm_config);
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_update_alarm_dynamically", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_alarm_update_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 100000, // initial alarm count, 100ms
|
||||
};
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
// check the alarm event for multiple times
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
// check there won't be more interrupts triggered than expected
|
||||
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
}
|
||||
|
||||
printf("restart timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
// check the alarm event for multiple times
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
// check there won't be more interrupts triggered than expected
|
||||
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(500)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_count_down_reload_alarm_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
|
||||
// check if the count value has been reloaded
|
||||
TEST_ASSERT_UINT_WITHIN(20, 200000, edata->count_value);
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_count_down_reload", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_DOWN,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
TEST_ESP_OK(gptimer_set_raw_count(timers[i], 200000));
|
||||
}
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_count_down_reload_alarm_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 200000, // 200ms
|
||||
.alarm_count = 0,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
// check twice, as it's a period event
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
printf("restart gptimer with previous configuration\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
// check twice, as it's a period event
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ALARM_CALLBACK_ATTR static bool test_gptimer_overflow_reload_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
xTaskHandle task_handle = (xTaskHandle)user_data;
|
||||
BaseType_t high_task_wakeup;
|
||||
// Note: esp_rom_printf can't print value with 64 bit length, so the following print result is meaningless, but as an incidator for test that the alarm has fired
|
||||
esp_rom_printf("alarm isr count=%llu\r\n", edata->count_value);
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_overflow", "[gptimer]")
|
||||
{
|
||||
xTaskHandle task_handle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
};
|
||||
gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
#if SOC_TIMER_GROUP_COUNTER_BIT_WIDTH == 64
|
||||
uint64_t reload_at = UINT64_MAX - 100000;
|
||||
#else
|
||||
uint64_t reload_at = (1ULL << SOC_TIMER_GROUP_COUNTER_BIT_WIDTH) - 100000;
|
||||
#endif
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_gptimer_overflow_reload_callback,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = reload_at,
|
||||
.alarm_count = 100000, // 100ms
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
// The counter should start from [COUNTER_MAX-100000] and overflows to [0] and continue, then reached to alarm value [100000], reloaded to [COUNTER_MAX-100000] automatically
|
||||
// thus the period should be 200ms
|
||||
printf("start timers\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(timers[i], &cbs, task_handle));
|
||||
// we start from the reload value
|
||||
TEST_ESP_OK(gptimer_set_raw_count(timers[i], reload_at));
|
||||
TEST_ESP_OK(gptimer_start(timers[i]));
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(400)));
|
||||
TEST_ESP_OK(gptimer_stop(timers[i]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_del_timer(timers[i]));
|
||||
}
|
||||
}
|
107
components/driver/test_apps/gptimer/main/test_gptimer_iram.c
Normal file
107
components/driver/test_apps/gptimer/main/test_gptimer_iram.c
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "unity.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
void test_app_include_gptimer_iram(void)
|
||||
{
|
||||
}
|
||||
|
||||
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
|
||||
|
||||
typedef struct {
|
||||
size_t buf_size;
|
||||
uint8_t *buf;
|
||||
size_t flash_addr;
|
||||
size_t repeat_count;
|
||||
SemaphoreHandle_t done_sem;
|
||||
} read_task_arg_t;
|
||||
|
||||
typedef struct {
|
||||
size_t delay_time_us;
|
||||
size_t repeat_count;
|
||||
} block_task_arg_t;
|
||||
|
||||
static bool IRAM_ATTR on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
block_task_arg_t *arg = (block_task_arg_t *)user_ctx;
|
||||
esp_rom_delay_us(arg->delay_time_us);
|
||||
arg->repeat_count++;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void flash_read_task(void *varg)
|
||||
{
|
||||
read_task_arg_t *arg = (read_task_arg_t *)varg;
|
||||
for (size_t i = 0; i < arg->repeat_count; i++) {
|
||||
TEST_ESP_OK(spi_flash_read(arg->flash_addr, arg->buf, arg->buf_size));
|
||||
}
|
||||
xSemaphoreGive(arg->done_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("gptimer_iram_interrupt_safe", "[gptimer]")
|
||||
{
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
const size_t size = 128;
|
||||
uint8_t *buf = malloc(size);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
read_task_arg_t read_arg = {
|
||||
.buf_size = size,
|
||||
.buf = buf,
|
||||
.flash_addr = 0,
|
||||
.repeat_count = 1000,
|
||||
.done_sem = done_sem,
|
||||
};
|
||||
|
||||
block_task_arg_t block_arg = {
|
||||
.repeat_count = 0,
|
||||
.delay_time_us = 100,
|
||||
};
|
||||
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_gptimer_alarm_cb,
|
||||
};
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 120,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &block_arg));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
|
||||
xTaskCreatePinnedToCore(flash_read_task, "read_flash", 2048, &read_arg, 3, NULL, portNUM_PROCESSORS - 1);
|
||||
// wait for task done
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY);
|
||||
printf("alarm callback runs %d times\r\n", block_arg.repeat_count);
|
||||
TEST_ASSERT_GREATER_THAN(1000, block_arg.repeat_count);
|
||||
// delete gptimer
|
||||
TEST_ESP_OK(gptimer_stop(gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
vSemaphoreDelete(done_sem);
|
||||
free(buf);
|
||||
// leave time for IDLE task to recycle deleted task
|
||||
vTaskDelay(2);
|
||||
}
|
||||
|
||||
#endif // CONFIG_GPTIMER_ISR_IRAM_SAFE
|
@ -0,0 +1,5 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
|
||||
# disable log as most of log access rodata string on error, causing RTL check failure
|
||||
CONFIG_LOG_DEFAULT_LEVEL_NONE=y
|
5
components/driver/test_apps/gptimer/sdkconfig.ci.release
Normal file
5
components/driver/test_apps/gptimer/sdkconfig.ci.release
Normal file
@ -0,0 +1,5 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
2
components/driver/test_apps/gptimer/sdkconfig.defaults
Normal file
2
components/driver/test_apps/gptimer/sdkconfig.defaults
Normal file
@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
@ -0,0 +1,5 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(legacy_timer_driver_test)
|
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
30
components/driver/test_apps/legacy_timer_driver/app_test.py
Normal file
30
components/driver/test_apps/legacy_timer_driver/app_test.py
Normal file
@ -0,0 +1,30 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import glob
|
||||
import os
|
||||
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import Utility
|
||||
|
||||
|
||||
@ttfw_idf.idf_component_unit_test(env_tag='COMPONENT_UT_GENERIC', target=['esp32', 'esp32s2', 'esp32s3', 'esp32c3'])
|
||||
def test_component_ut_legacy_timer_driver(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
|
||||
# Get the names of all configs (sdkconfig.ci.* files)
|
||||
config_files = glob.glob(os.path.join(os.path.dirname(__file__), 'sdkconfig.ci.*'))
|
||||
config_names = [os.path.basename(s).replace('sdkconfig.ci.', '') for s in config_files]
|
||||
|
||||
# Run test once with binaries built for each config
|
||||
for name in config_names:
|
||||
Utility.console_log(f'Checking config "{name}"... ', end='')
|
||||
dut = env.get_dut('gptimer', 'components/driver/test_apps/legacy_timer_driver', app_config_name=name)
|
||||
dut.start_app()
|
||||
stdout = dut.expect('Press ENTER to see the list of tests', full_stdout=True)
|
||||
dut.write('*')
|
||||
stdout = dut.expect("Enter next test, or 'enter' to see menu", full_stdout=True, timeout=80)
|
||||
ttfw_idf.ComponentUTResult.parse_result(stdout,ttfw_idf.TestFormat.UNITY_BASIC)
|
||||
env.close_dut(dut.name)
|
||||
Utility.console_log(f'Test config "{name}" done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_component_ut_legacy_timer_driver()
|
@ -0,0 +1,7 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_legacy_timer.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES driver unity)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_legacy_timer")
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-600)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -9,17 +9,16 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_system.h"
|
||||
#include "unity.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "driver/timer.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#define TEST_TIMER_RESOLUTION_HZ 1000000 // 1MHz resolution
|
||||
#define TIMER_DELTA 0.001
|
||||
#define TIMER_DELTA 0.001
|
||||
|
||||
static bool alarm_flag;
|
||||
static xQueueHandle timer_queue;
|
||||
static QueueHandle_t timer_queue;
|
||||
|
||||
typedef struct {
|
||||
timer_group_t timer_group;
|
||||
@ -284,7 +283,7 @@ static void timer_intr_enable_disable_test(timer_group_t group_num, timer_idx_t
|
||||
TEST_ASSERT_EQUAL(false, alarm_flag);
|
||||
}
|
||||
|
||||
TEST_CASE("Timer init", "[hw_timer]")
|
||||
TEST_CASE("Timer_init", "[hw_timer]")
|
||||
{
|
||||
// Test init 1:config parameter
|
||||
// empty parameter
|
||||
@ -353,7 +352,7 @@ TEST_CASE("Timer init", "[hw_timer]")
|
||||
* 1. start timer compare value
|
||||
* 2. pause timer compare value
|
||||
* 3. delay some time */
|
||||
TEST_CASE("Timer read counter value", "[hw_timer]")
|
||||
TEST_CASE("Timer_read_counter_value", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -393,7 +392,7 @@ TEST_CASE("Timer read counter value", "[hw_timer]")
|
||||
* 1. normal start
|
||||
* 2. error start parameter
|
||||
* */
|
||||
TEST_CASE("Timer start", "[hw_timer]")
|
||||
TEST_CASE("Timer_start", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -425,7 +424,7 @@ TEST_CASE("Timer start", "[hw_timer]")
|
||||
* 1. normal pause, read value
|
||||
* 2. error pause error
|
||||
*/
|
||||
TEST_CASE("Timer pause", "[hw_timer]")
|
||||
TEST_CASE("Timer_pause", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -453,7 +452,7 @@ TEST_CASE("Timer pause", "[hw_timer]")
|
||||
}
|
||||
|
||||
// positive mode and negative mode
|
||||
TEST_CASE("Timer counter mode (up / down)", "[hw_timer]")
|
||||
TEST_CASE("Timer_counter_direction", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -490,7 +489,7 @@ TEST_CASE("Timer counter mode (up / down)", "[hw_timer]")
|
||||
all_timer_deinit();
|
||||
}
|
||||
|
||||
TEST_CASE("Timer divider", "[hw_timer]")
|
||||
TEST_CASE("Timer_divider", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -542,7 +541,7 @@ TEST_CASE("Timer divider", "[hw_timer]")
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
all_timer_get_counter_value(set_timer_val, false, comp_time_val);
|
||||
for (int i = 0; i < TIMER_GROUP_MAX * TIMER_MAX; i++) {
|
||||
TEST_ASSERT_INT_WITHIN(10000, APB_CLK_FREQ / 2, comp_time_val[i]);
|
||||
TEST_ASSERT_INT_WITHIN(5000, APB_CLK_FREQ / 2, comp_time_val[i]);
|
||||
}
|
||||
|
||||
all_timer_pause();
|
||||
@ -566,7 +565,7 @@ TEST_CASE("Timer divider", "[hw_timer]")
|
||||
* 1. enable alarm ,set alarm value and get value
|
||||
* 2. disable alarm ,set alarm value and get value
|
||||
*/
|
||||
TEST_CASE("Timer enable alarm", "[hw_timer]")
|
||||
TEST_CASE("Timer_enable_alarm", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config_test = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -618,7 +617,7 @@ TEST_CASE("Timer enable alarm", "[hw_timer]")
|
||||
* 1. set alarm value and get value
|
||||
* 2. interrupt test time
|
||||
*/
|
||||
TEST_CASE("Timer set alarm value", "[hw_timer]")
|
||||
TEST_CASE("Timer_set_alarm_value", "[hw_timer]")
|
||||
{
|
||||
uint64_t alarm_val[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
timer_config_t config = {
|
||||
@ -656,7 +655,7 @@ TEST_CASE("Timer set alarm value", "[hw_timer]")
|
||||
* 1. no reload
|
||||
* 2. auto reload
|
||||
*/
|
||||
TEST_CASE("Timer auto reload", "[hw_timer]")
|
||||
TEST_CASE("Timer_auto_reload", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -696,7 +695,7 @@ TEST_CASE("Timer auto reload", "[hw_timer]")
|
||||
* 1. enable timer_intr
|
||||
* 2. disable timer_intr
|
||||
*/
|
||||
TEST_CASE("Timer enable timer interrupt", "[hw_timer]")
|
||||
TEST_CASE("Timer_enable_timer_interrupt", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -736,7 +735,7 @@ TEST_CASE("Timer enable timer interrupt", "[hw_timer]")
|
||||
* 1. enable timer group
|
||||
* 2. disable timer group
|
||||
*/
|
||||
TEST_CASE("Timer enable timer group interrupt", "[hw_timer][ignore]")
|
||||
TEST_CASE("Timer_enable_timer_group_interrupt", "[hw_timer][ignore]")
|
||||
{
|
||||
intr_handle_t isr_handle = NULL;
|
||||
alarm_flag = false;
|
||||
@ -777,7 +776,7 @@ TEST_CASE("Timer enable timer group interrupt", "[hw_timer][ignore]")
|
||||
* isr_register case:
|
||||
* Cycle register 15 times, compare the heap size to ensure no memory leaks
|
||||
*/
|
||||
TEST_CASE("Timer interrupt register", "[hw_timer]")
|
||||
TEST_CASE("Timer_interrupt_register", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -832,7 +831,7 @@ TEST_CASE("Timer interrupt register", "[hw_timer]")
|
||||
* 1. configure clock source as APB clock, and enable timer interrupt
|
||||
* 2. configure clock source as XTAL clock, adn enable timer interrupt
|
||||
*/
|
||||
TEST_CASE("Timer clock source", "[hw_timer]")
|
||||
TEST_CASE("Timer_clock_source", "[hw_timer]")
|
||||
{
|
||||
// configure clock source as APB clock
|
||||
timer_config_t config = {
|
||||
@ -875,7 +874,7 @@ TEST_CASE("Timer clock source", "[hw_timer]")
|
||||
/**
|
||||
* Timer ISR callback test
|
||||
*/
|
||||
TEST_CASE("Timer ISR callback", "[hw_timer]")
|
||||
TEST_CASE("Timer_ISR_callback", "[hw_timer]")
|
||||
{
|
||||
alarm_flag = false;
|
||||
timer_config_t config = {
|
||||
@ -936,7 +935,7 @@ TEST_CASE("Timer ISR callback", "[hw_timer]")
|
||||
/**
|
||||
* Timer memory test
|
||||
*/
|
||||
TEST_CASE("Timer init/deinit stress test", "[hw_timer]")
|
||||
TEST_CASE("Timer_init_deinit_stress_test", "[hw_timer]")
|
||||
{
|
||||
timer_config_t config = {
|
||||
.clk_src = TIMER_SRC_CLK_APB,
|
||||
@ -977,42 +976,10 @@ static void timer_group_test_init(void)
|
||||
//We only need to check the interrupt status and don't have to register a interrupt routine.
|
||||
}
|
||||
|
||||
static void timer_group_test_first_stage(void)
|
||||
{
|
||||
static uint8_t loop_cnt = 0;
|
||||
timer_group_test_init();
|
||||
//Start timer
|
||||
TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
|
||||
TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0));
|
||||
//Waiting for timer_group to generate an interrupt
|
||||
while ( !(timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0) &&
|
||||
loop_cnt++ < 100) {
|
||||
vTaskDelay(200);
|
||||
}
|
||||
TEST_ASSERT_EQUAL(TIMER_INTR_T0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static void timer_group_test_second_stage(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
|
||||
timer_group_test_init();
|
||||
TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0);
|
||||
// After enable the interrupt, timer alarm should not trigger immediately
|
||||
TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
|
||||
//After the timer_group is initialized, TIMERG0.int_raw.t0 should be cleared.
|
||||
TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0);
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("timer_group software reset test",
|
||||
"[intr_status][intr_status = 0]",
|
||||
timer_group_test_first_stage,
|
||||
timer_group_test_second_stage);
|
||||
|
||||
//
|
||||
// Timer check reinitialization sequence
|
||||
//
|
||||
TEST_CASE("Timer check reinitialization sequence", "[hw_timer]")
|
||||
TEST_CASE("Timer_check_reinitialization_sequence", "[hw_timer]")
|
||||
{
|
||||
// 1. step - install driver
|
||||
timer_group_test_init();
|
||||
@ -1040,3 +1007,7 @@ TEST_CASE("Timer check reinitialization sequence", "[hw_timer]")
|
||||
// The pending timer interrupt should not be triggered
|
||||
TEST_ASSERT_EQUAL(0, timer_group_get_intr_status_in_isr(TIMER_GROUP_0) & TIMER_INTR_T0);
|
||||
}
|
||||
|
||||
void test_app_include_legacy_timer(void)
|
||||
{
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
@ -0,0 +1,3 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN=y
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -10,14 +10,14 @@
|
||||
#include "esp_check.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/timer.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/timer_types_legacy.h"
|
||||
#include "hal/timer_hal.h"
|
||||
#include "hal/timer_ll.h"
|
||||
#include "hal/check.h"
|
||||
#include "soc/timer_periph.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
|
||||
static const char *TIMER_TAG = "timer_group";
|
||||
|
||||
@ -230,6 +230,42 @@ static void IRAM_ATTR timer_isr_default(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
|
||||
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true);
|
||||
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
|
||||
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false);
|
||||
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num,
|
||||
void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal;
|
||||
return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num],
|
||||
intr_alloc_flags,
|
||||
(uint32_t)timer_ll_get_intr_status_reg(hal->dev),
|
||||
TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle);
|
||||
}
|
||||
|
||||
esp_err_t timer_isr_callback_add(timer_group_t group_num, timer_idx_t timer_num, timer_isr_t isr_handler, void *args, int intr_alloc_flags)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
@ -261,20 +297,6 @@ esp_err_t timer_isr_callback_remove(timer_group_t group_num, timer_idx_t timer_n
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t timer_isr_register(timer_group_t group_num, timer_idx_t timer_num,
|
||||
void (*fn)(void *), void *arg, int intr_alloc_flags, timer_isr_handle_t *handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(fn != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_PARAM_ADDR_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
timer_hal_context_t *hal = &p_timer_obj[group_num][timer_num]->hal;
|
||||
return esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[group_num].timer_irq_id[timer_num],
|
||||
intr_alloc_flags,
|
||||
(uint32_t)timer_ll_get_intr_status_reg(hal->dev),
|
||||
TIMER_LL_EVENT_ALARM(timer_num), fn, arg, handle);
|
||||
}
|
||||
|
||||
esp_err_t timer_init(timer_group_t group_num, timer_idx_t timer_num, const timer_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
@ -369,28 +391,6 @@ esp_err_t timer_group_intr_disable(timer_group_t group_num, timer_intr_t disable
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t timer_enable_intr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
|
||||
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), true);
|
||||
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t timer_disable_intr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(group_num < TIMER_GROUP_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_GROUP_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(timer_num < TIMER_MAX, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NUM_ERROR);
|
||||
ESP_RETURN_ON_FALSE(p_timer_obj[group_num][timer_num] != NULL, ESP_ERR_INVALID_ARG, TIMER_TAG, TIMER_NEVER_INIT_ERROR);
|
||||
TIMER_ENTER_CRITICAL(&timer_spinlock[group_num]);
|
||||
timer_ll_enable_intr(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num), false);
|
||||
TIMER_EXIT_CRITICAL(&timer_spinlock[group_num]);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* This function is deprecated */
|
||||
timer_intr_t IRAM_ATTR timer_group_intr_get_in_isr(timer_group_t group_num)
|
||||
{
|
||||
@ -411,17 +411,17 @@ uint32_t IRAM_ATTR timer_group_get_intr_status_in_isr(timer_group_t group_num)
|
||||
return intr_status;
|
||||
}
|
||||
|
||||
void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num));
|
||||
}
|
||||
|
||||
/* This function is deprecated */
|
||||
void IRAM_ATTR timer_group_intr_clr_in_isr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
timer_group_clr_intr_status_in_isr(group_num, timer_num);
|
||||
}
|
||||
|
||||
void IRAM_ATTR timer_group_clr_intr_status_in_isr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
timer_ll_clear_intr_status(p_timer_obj[group_num][timer_num]->hal.dev, TIMER_LL_EVENT_ALARM(timer_num));
|
||||
}
|
||||
|
||||
void IRAM_ATTR timer_group_enable_alarm_in_isr(timer_group_t group_num, timer_idx_t timer_num)
|
||||
{
|
||||
timer_ll_enable_alarm(p_timer_obj[group_num][timer_num]->hal.dev, timer_num, true);
|
||||
@ -474,9 +474,17 @@ esp_err_t IRAM_ATTR timer_spinlock_give(timer_group_t group_num)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T0, TIMG_T0_INT_CLR);
|
||||
#if SOC_TIMER_GROUP_TIMERS_PER_GROUP > 1
|
||||
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_T1, TIMG_T1_INT_CLR);
|
||||
#endif
|
||||
STATIC_HAL_REG_CHECK(TIMER_TAG, TIMER_INTR_WDT, TIMG_WDT_INT_CLR);
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that this legacy timer group driver is not running along with the gptimer driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_legacy_timer_driver_conflict(void)
|
||||
{
|
||||
extern int timer_group_driver_init_count;
|
||||
timer_group_driver_init_count++;
|
||||
if (timer_group_driver_init_count > 1) {
|
||||
ESP_EARLY_LOGE(TIMER_TAG, "CONFLICT! The legacy timer group driver can't work along with the gptimer driver");
|
||||
abort();
|
||||
}
|
||||
ESP_EARLY_LOGW(TIMER_TAG, "legacy timer group driver is deprecated, please migrate to use driver/gptimer.h");
|
||||
}
|
@ -8,8 +8,6 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/timer.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_event_private.h"
|
||||
|
@ -7,8 +7,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_event_private.h"
|
||||
@ -302,10 +301,6 @@ static void test_teardown(void)
|
||||
TEST_ESP_OK(esp_event_loop_delete_default());
|
||||
}
|
||||
|
||||
#define TIMER_DIVIDER 16 // Hardware timer clock divider
|
||||
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // convert counter value to seconds
|
||||
#define TIMER_INTERVAL0_SEC (2.0) // sample test interval for the first timer
|
||||
|
||||
TEST_CASE("can create and delete event loops", "[event]")
|
||||
{
|
||||
/* this test aims to verify that:
|
||||
@ -1973,63 +1968,51 @@ static void test_handler_post_from_isr(void* event_handler_arg, esp_event_base_t
|
||||
xSemaphoreGive(*sem);
|
||||
}
|
||||
|
||||
void IRAM_ATTR test_event_on_timer_alarm(void* para)
|
||||
bool test_event_on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
/* Retrieve the interrupt status and the counter value
|
||||
from the timer that reported the interrupt */
|
||||
uint64_t timer_counter_value =
|
||||
timer_group_get_counter_value_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_counter_value += (uint64_t) (TIMER_INTERVAL0_SEC * TIMER_SCALE);
|
||||
timer_group_set_alarm_value_in_isr(TIMER_GROUP_0, TIMER_0, timer_counter_value);
|
||||
|
||||
int data = (int) para;
|
||||
int data = (int)user_ctx;
|
||||
gptimer_stop(timer);
|
||||
// Posting events with data more than 4 bytes should fail.
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, 5, NULL));
|
||||
// This should succeedd, as data is int-sized. The handler for the event checks that the passed event data
|
||||
// is correct.
|
||||
BaseType_t task_unblocked;
|
||||
TEST_ESP_OK(esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, sizeof(data), &task_unblocked));
|
||||
if (task_unblocked == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
return task_unblocked == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("can post events from interrupt handler", "[event]")
|
||||
{
|
||||
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
/* Select and initialize basic parameters of the timer */
|
||||
timer_config_t config = {
|
||||
.divider = TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.auto_reload = false,
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
timer_init(TIMER_GROUP_0, TIMER_0, &config);
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
|
||||
|
||||
/* Timer's counter will initially start from value below.
|
||||
Also, if auto_reload is set, this value will be automatically reload on alarm */
|
||||
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL);
|
||||
|
||||
/* Configure the alarm value and the interrupt on alarm. */
|
||||
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_INTERVAL0_SEC * TIMER_SCALE);
|
||||
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_isr_register(TIMER_GROUP_0, TIMER_0, test_event_on_timer_alarm,
|
||||
(void *) sem, ESP_INTR_FLAG_IRAM, NULL);
|
||||
|
||||
timer_start(TIMER_GROUP_0, TIMER_0);
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 500000,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = test_event_on_timer_alarm
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, sem));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
|
||||
TEST_SETUP();
|
||||
|
||||
TEST_ESP_OK(esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1,
|
||||
test_handler_post_from_isr, &sem));
|
||||
test_handler_post_from_isr, &sem));
|
||||
|
||||
xSemaphoreTake(sem, portMAX_DELAY);
|
||||
|
||||
TEST_TEARDOWN();
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
}
|
||||
|
||||
#endif // CONFIG_ESP_EVENT_POST_FROM_ISR
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_types.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@ -16,84 +17,52 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "unity.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/spi_periph.h"
|
||||
#include "hal/spi_ll.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "esp_private/gptimer.h"
|
||||
|
||||
#define TIMER_DIVIDER (16) /*!< Hardware timer clock divider */
|
||||
#define TIMER_SCALE (APB_CLK_FREQ / TIMER_DIVIDER) /*!< used to calculate counter value */
|
||||
#define TIMER_INTERVAL0_SEC (3) /*!< test interval for timer 0 */
|
||||
#define TIMER_INTERVAL1_SEC (5) /*!< test interval for timer 1 */
|
||||
|
||||
static void my_timer_init(int timer_group, int timer_idx, uint64_t alarm_value)
|
||||
static bool on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = TIMER_DIVIDER,
|
||||
};
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Stop timer counter*/
|
||||
timer_pause(timer_group, timer_idx);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0);
|
||||
/*Set alarm value*/
|
||||
timer_set_alarm_value(timer_group, timer_idx, alarm_value);
|
||||
/*Enable timer interrupt*/
|
||||
timer_enable_intr(timer_group, timer_idx);
|
||||
}
|
||||
|
||||
static volatile int count[SOC_TIMER_GROUP_TOTAL_TIMERS] = {0};
|
||||
|
||||
static void timer_isr(void *arg)
|
||||
{
|
||||
int timer_idx = (int)arg;
|
||||
int group_id = timer_idx / SOC_TIMER_GROUP_TIMERS_PER_GROUP;
|
||||
int timer_id = timer_idx % SOC_TIMER_GROUP_TIMERS_PER_GROUP;
|
||||
count[timer_idx]++;
|
||||
|
||||
timer_group_clr_intr_status_in_isr(group_id, timer_id);
|
||||
timer_group_enable_alarm_in_isr(group_id, timer_id);
|
||||
volatile int *count = (volatile int *)user_ctx;
|
||||
(*count)++;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void timer_test(int flags)
|
||||
{
|
||||
timer_isr_handle_t inth[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
for (int i = 0; i < SOC_TIMER_GROUPS; i++) {
|
||||
for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
|
||||
my_timer_init(i, j, 100000 + 10000 * (i * SOC_TIMER_GROUP_TIMERS_PER_GROUP + j + 1));
|
||||
}
|
||||
}
|
||||
timer_isr_register(0, 0, timer_isr, (void *)0, flags | ESP_INTR_FLAG_INTRDISABLED, &inth[0]);
|
||||
printf("Interrupts allocated: %d (dis)\r\n", esp_intr_get_intno(inth[0]));
|
||||
static int count[SOC_TIMER_GROUP_TOTAL_TIMERS] = {0};
|
||||
gptimer_handle_t gptimers[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
intr_handle_t inth[SOC_TIMER_GROUP_TOTAL_TIMERS];
|
||||
|
||||
for (int j = 1; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
|
||||
timer_isr_register(0, j, timer_isr, (void *)1, flags, &inth[j]);
|
||||
printf("Interrupts allocated: %d\r\n", esp_intr_get_intno(inth[j]));
|
||||
}
|
||||
for (int i = 1; i < SOC_TIMER_GROUPS; i++) {
|
||||
for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
|
||||
timer_isr_register(i, j, timer_isr, (void *)(i * SOC_TIMER_GROUP_TIMERS_PER_GROUP + j), flags, &inth[i * SOC_TIMER_GROUP_TIMERS_PER_GROUP + j]);
|
||||
printf("Interrupts allocated: %d\r\n", esp_intr_get_intno(inth[i * SOC_TIMER_GROUP_TIMERS_PER_GROUP + j]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < SOC_TIMER_GROUPS; i++) {
|
||||
for (int j = 0; j < SOC_TIMER_GROUP_TIMERS_PER_GROUP; j++) {
|
||||
timer_start(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Timer values on start:");
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
.flags.intr_shared = (flags & ESP_INTR_FLAG_SHARED) == ESP_INTR_FLAG_SHARED,
|
||||
};
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
count[i] = 0;
|
||||
printf(" %d", count[i]);
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &gptimers[i]));
|
||||
}
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 100000,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm,
|
||||
};
|
||||
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimers[i], &cbs, &count[i]));
|
||||
alarm_config.alarm_count += 10000 * i;
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimers[i], &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimers[i]));
|
||||
TEST_ESP_OK(gptimer_get_intr_handle(gptimers[i], &inth[i]));
|
||||
printf("Interrupts allocated: %d\r\n", esp_intr_get_intno(inth[i]));
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
printf("Timer values after 1 sec:");
|
||||
@ -102,19 +71,13 @@ static void timer_test(int flags)
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
TEST_ASSERT(count[0] == 0);
|
||||
for (int i = 1; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT(count[i] != 0);
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT_NOT_EQUAL(0, count[i]);
|
||||
}
|
||||
|
||||
printf("Disabling half of timers' interrupt...\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i++) {
|
||||
esp_intr_disable(inth[i]);
|
||||
}
|
||||
for (int i = SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
esp_intr_enable(inth[i]);
|
||||
}
|
||||
printf("Disabling timers' interrupt...\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
esp_intr_disable(inth[i]);
|
||||
count[i] = 0;
|
||||
}
|
||||
|
||||
@ -124,38 +87,13 @@ static void timer_test(int flags)
|
||||
printf(" %d", count[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i++) {
|
||||
TEST_ASSERT(count[i] == 0);
|
||||
}
|
||||
for (int i = SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT(count[i] != 0);
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT_EQUAL(0, count[i]);
|
||||
}
|
||||
|
||||
printf("Disabling another half...\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i++) {
|
||||
esp_intr_enable(inth[i]);
|
||||
}
|
||||
for (int i = SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
esp_intr_disable(inth[i]);
|
||||
}
|
||||
for (int x = 0; x < SOC_TIMER_GROUP_TOTAL_TIMERS; x++) {
|
||||
count[x] = 0;
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
printf("Timer values after 1 sec:");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
printf(" %d", count[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i++) {
|
||||
TEST_ASSERT(count[i] != 0);
|
||||
}
|
||||
for (int i = SOC_TIMER_GROUP_TOTAL_TIMERS / 2; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
TEST_ASSERT(count[i] == 0);
|
||||
}
|
||||
printf("Done.\n");
|
||||
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
|
||||
esp_intr_free(inth[i]);
|
||||
TEST_ESP_OK(gptimer_stop(gptimers[i]));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,10 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "esp_private/gptimer.h"
|
||||
#include "soc/rtc_periph.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
@ -53,7 +54,8 @@ static void switch_freq(int mhz)
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
|
||||
printf("Waiting for frequency to be set to %d MHz...\n", mhz);
|
||||
while (esp_clk_cpu_freq() / MHZ != mhz) {
|
||||
while (esp_clk_cpu_freq() / MHZ != mhz)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
printf("Frequency is %d MHz\n", esp_clk_cpu_freq() / MHZ);
|
||||
}
|
||||
@ -70,7 +72,7 @@ TEST_CASE("Can switch frequency using esp_pm_configure", "[pm]")
|
||||
{
|
||||
int orig_freq_mhz = esp_clk_cpu_freq() / MHZ;
|
||||
|
||||
for (int i = 0; i < sizeof(test_freqs)/sizeof(int); i++) {
|
||||
for (int i = 0; i < sizeof(test_freqs) / sizeof(int); i++) {
|
||||
switch_freq(test_freqs[i]);
|
||||
}
|
||||
|
||||
@ -125,16 +127,23 @@ static void light_sleep_disable(void)
|
||||
|
||||
TEST_CASE("Automatic light occurs when tasks are suspended", "[pm]")
|
||||
{
|
||||
/* To figure out if light sleep takes place, use Timer Group timer.
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
/* To figure out if light sleep takes place, use GPTimer
|
||||
* It will stop working while in light sleep.
|
||||
*/
|
||||
timer_config_t config = {
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 80 /* 1 us per tick */
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, /* 1 us per tick */
|
||||
};
|
||||
timer_init(TIMER_GROUP_0, TIMER_0, &config);
|
||||
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
|
||||
timer_start(TIMER_GROUP_0, TIMER_0);
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
// if GPTimer is clocked from APB, when PM is enabled, the driver will acquire the PM lock
|
||||
// causing the auto light sleep doesn't take effect
|
||||
// so we manually release the lock here
|
||||
esp_pm_lock_handle_t gptimer_pm_lock;
|
||||
TEST_ESP_OK(gptimer_get_pm_lock(gptimer, &gptimer_pm_lock));
|
||||
TEST_ESP_OK(esp_pm_lock_release(gptimer_pm_lock));
|
||||
|
||||
light_sleep_enable();
|
||||
|
||||
@ -147,11 +156,10 @@ TEST_CASE("Automatic light occurs when tasks are suspended", "[pm]")
|
||||
|
||||
/* The following delay should cause light sleep to start */
|
||||
uint64_t count_start;
|
||||
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &count_start);
|
||||
vTaskDelay(ticks_to_delay);
|
||||
uint64_t count_end;
|
||||
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &count_end);
|
||||
|
||||
TEST_ESP_OK(gptimer_get_raw_count(gptimer, &count_start));
|
||||
vTaskDelay(ticks_to_delay);
|
||||
TEST_ESP_OK(gptimer_get_raw_count(gptimer, &count_end));
|
||||
|
||||
int timer_diff_us = (int) (count_end - count_start);
|
||||
const int us_per_tick = 1 * portTICK_PERIOD_MS * 1000;
|
||||
@ -161,6 +169,9 @@ TEST_CASE("Automatic light occurs when tasks are suspended", "[pm]")
|
||||
}
|
||||
|
||||
light_sleep_disable();
|
||||
TEST_ESP_OK(esp_pm_lock_acquire(gptimer_pm_lock));
|
||||
TEST_ESP_OK(gptimer_stop(gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
}
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
@ -185,22 +196,22 @@ TEST_CASE("Can wake up from automatic light sleep by GPIO", "[pm][ignore]")
|
||||
rtc_gpio_set_direction(ext1_wakeup_gpio, RTC_GPIO_MODE_INPUT_OUTPUT);
|
||||
rtc_gpio_set_level(ext1_wakeup_gpio, 0);
|
||||
|
||||
/* Enable wakeup */
|
||||
/* Enable wakeup */
|
||||
TEST_ESP_OK(esp_sleep_enable_ext1_wakeup(1ULL << ext1_wakeup_gpio, ESP_EXT1_WAKEUP_ANY_HIGH));
|
||||
|
||||
/* To simplify test environment, we'll use a ULP program to set GPIO high */
|
||||
ulp_insn_t ulp_code[] = {
|
||||
I_DELAY(65535), /* about 8ms, given 8MHz ULP clock */
|
||||
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 0),
|
||||
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 1),
|
||||
I_DELAY(1000),
|
||||
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 0),
|
||||
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 1),
|
||||
I_END(),
|
||||
I_HALT()
|
||||
I_DELAY(65535), /* about 8ms, given 8MHz ULP clock */
|
||||
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 0),
|
||||
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 1),
|
||||
I_DELAY(1000),
|
||||
I_WR_REG_BIT(RTC_GPIO_OUT_REG, ext_rtc_io + RTC_GPIO_OUT_DATA_S, 0),
|
||||
I_WR_REG_BIT(RTC_CNTL_HOLD_FORCE_REG, RTC_CNTL_PDAC1_HOLD_FORCE_S, 1),
|
||||
I_END(),
|
||||
I_HALT()
|
||||
};
|
||||
TEST_ESP_OK(ulp_set_wakeup_period(0, 1000 /* us */));
|
||||
size_t size = sizeof(ulp_code)/sizeof(ulp_insn_t);
|
||||
size_t size = sizeof(ulp_code) / sizeof(ulp_insn_t);
|
||||
TEST_ESP_OK(ulp_process_macros_and_load(0, ulp_code, &size));
|
||||
|
||||
light_sleep_enable();
|
||||
@ -249,9 +260,9 @@ typedef struct {
|
||||
SemaphoreHandle_t done;
|
||||
} delay_test_arg_t;
|
||||
|
||||
static void test_delay_task(void* p)
|
||||
static void test_delay_task(void *p)
|
||||
{
|
||||
delay_test_arg_t* arg = (delay_test_arg_t*) p;
|
||||
delay_test_arg_t *arg = (delay_test_arg_t *) p;
|
||||
vTaskDelay(1);
|
||||
|
||||
uint64_t start = esp_clk_rtc_time();
|
||||
@ -266,9 +277,11 @@ static void test_delay_task(void* p)
|
||||
TEST_CASE("vTaskDelay duration is correct with light sleep enabled", "[pm]")
|
||||
{
|
||||
light_sleep_enable();
|
||||
SemaphoreHandle_t done_sem = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(done_sem);
|
||||
|
||||
delay_test_arg_t args = {
|
||||
.done = xSemaphoreCreateBinary()
|
||||
.done = done_sem,
|
||||
};
|
||||
|
||||
const int delays[] = { 10, 20, 50, 100, 150, 200, 250 };
|
||||
@ -278,19 +291,19 @@ TEST_CASE("vTaskDelay duration is correct with light sleep enabled", "[pm]")
|
||||
int delay_ms = delays[i];
|
||||
args.delay_us = delay_ms * 1000;
|
||||
|
||||
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 0);
|
||||
TEST_ASSERT( xSemaphoreTake(args.done, delay_ms * 10 / portTICK_PERIOD_MS) );
|
||||
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void *) &args, 3, NULL, 0);
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, delay_ms * 10 / portTICK_PERIOD_MS) );
|
||||
printf("CPU0: %d %d\n", args.delay_us, args.result);
|
||||
TEST_ASSERT_INT32_WITHIN(1000 * portTICK_PERIOD_MS * 2, args.delay_us, args.result);
|
||||
|
||||
#if portNUM_PROCESSORS == 2
|
||||
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 1);
|
||||
TEST_ASSERT( xSemaphoreTake(args.done, delay_ms * 10 / portTICK_PERIOD_MS) );
|
||||
xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void *) &args, 3, NULL, 1);
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, delay_ms * 10 / portTICK_PERIOD_MS) );
|
||||
printf("CPU1: %d %d\n", args.delay_us, args.result);
|
||||
TEST_ASSERT_INT32_WITHIN(1000 * portTICK_PERIOD_MS * 2, args.delay_us, args.result);
|
||||
#endif
|
||||
}
|
||||
vSemaphoreDelete(args.done);
|
||||
vSemaphoreDelete(done_sem);
|
||||
|
||||
light_sleep_disable();
|
||||
}
|
||||
@ -312,16 +325,15 @@ TEST_CASE("esp_timer produces correct delays with light sleep", "[pm]")
|
||||
SemaphoreHandle_t done;
|
||||
} test_args_t;
|
||||
|
||||
void timer_func(void* arg)
|
||||
{
|
||||
test_args_t* p_args = (test_args_t*) arg;
|
||||
void timer_func(void *arg) {
|
||||
test_args_t *p_args = (test_args_t *) arg;
|
||||
int64_t t_end = esp_clk_rtc_time();
|
||||
int32_t ms_diff = (t_end - p_args->t_start) / 1000;
|
||||
printf("timer #%d %dms\n", p_args->cur_interval, ms_diff);
|
||||
p_args->intervals[p_args->cur_interval++] = ms_diff;
|
||||
// Deliberately make timer handler run longer.
|
||||
// We check that this doesn't affect the result.
|
||||
esp_rom_delay_us(10*1000);
|
||||
esp_rom_delay_us(10 * 1000);
|
||||
if (p_args->cur_interval == NUM_INTERVALS) {
|
||||
printf("done\n");
|
||||
TEST_ESP_OK(esp_timer_stop(p_args->timer));
|
||||
@ -335,9 +347,9 @@ TEST_CASE("esp_timer produces correct delays with light sleep", "[pm]")
|
||||
test_args_t args = {0};
|
||||
esp_timer_handle_t timer1;
|
||||
esp_timer_create_args_t create_args = {
|
||||
.callback = &timer_func,
|
||||
.arg = &args,
|
||||
.name = "timer1",
|
||||
.callback = timer_func,
|
||||
.arg = &args,
|
||||
.name = "timer1",
|
||||
};
|
||||
TEST_ESP_OK(esp_timer_create(&create_args, &timer1));
|
||||
|
||||
@ -365,7 +377,7 @@ TEST_CASE("esp_timer produces correct delays with light sleep", "[pm]")
|
||||
|
||||
static void timer_cb1(void *arg)
|
||||
{
|
||||
++*((int*) arg);
|
||||
++*((int *) arg);
|
||||
}
|
||||
|
||||
TEST_CASE("esp_timer with SKIP_UNHANDLED_EVENTS does not wake up CPU from sleep", "[pm]")
|
||||
@ -374,10 +386,10 @@ TEST_CASE("esp_timer with SKIP_UNHANDLED_EVENTS does not wake up CPU from sleep"
|
||||
int timer_interval_ms = 50;
|
||||
|
||||
const esp_timer_create_args_t timer_args = {
|
||||
.name = "timer_cb1",
|
||||
.arg = &count_calls,
|
||||
.callback = &timer_cb1,
|
||||
.skip_unhandled_events = true,
|
||||
.name = "timer_cb1",
|
||||
.arg = &count_calls,
|
||||
.callback = &timer_cb1,
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
esp_timer_handle_t periodic_timer;
|
||||
esp_timer_create(&timer_args, &periodic_timer);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -11,7 +11,7 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "unity.h"
|
||||
@ -29,7 +29,8 @@
|
||||
|
||||
static const uint8_t small_item[SMALL_ITEM_SIZE] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
|
||||
static const uint8_t large_item[LARGE_ITEM_SIZE] = { 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17
|
||||
};
|
||||
static RingbufHandle_t buffer_handles[NO_OF_RB_TYPES];
|
||||
static SemaphoreHandle_t done_sem;
|
||||
|
||||
@ -87,9 +88,9 @@ static void receive_check_and_return_item_allow_split(RingbufHandle_t handle, co
|
||||
uint8_t *item1, *item2;
|
||||
BaseType_t ret;
|
||||
if (in_isr) {
|
||||
ret = xRingbufferReceiveSplitFromISR(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2);
|
||||
ret = xRingbufferReceiveSplitFromISR(handle, (void **)&item1, (void **)&item2, &item_size1, &item_size2);
|
||||
} else {
|
||||
ret = xRingbufferReceiveSplit(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait);
|
||||
ret = xRingbufferReceiveSplit(handle, (void **)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait);
|
||||
}
|
||||
TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to receive item");
|
||||
TEST_ASSERT_MESSAGE(item1 != NULL, "Failed to receive item");
|
||||
@ -643,7 +644,7 @@ static void queue_set_receiving_task(void *queue_set_handle)
|
||||
//Allow-split buffer
|
||||
receive_check_and_return_item_allow_split(buffer_handles[1], small_item, SMALL_ITEM_SIZE, 0, false);
|
||||
items_rec_count[1] ++;
|
||||
} else if (xRingbufferCanRead(buffer_handles[2], member) == pdTRUE){
|
||||
} else if (xRingbufferCanRead(buffer_handles[2], member) == pdTRUE) {
|
||||
//Byte buffer
|
||||
receive_check_and_return_item_byte_buffer(buffer_handles[2], small_item, SMALL_ITEM_SIZE, 0, false);
|
||||
items_rec_count[2] ++;
|
||||
@ -653,8 +654,8 @@ static void queue_set_receiving_task(void *queue_set_handle)
|
||||
|
||||
//Check for completion
|
||||
if (items_rec_count[0] == no_of_items &&
|
||||
items_rec_count[1] == no_of_items &&
|
||||
items_rec_count[2] == no_of_items) {
|
||||
items_rec_count[1] == no_of_items &&
|
||||
items_rec_count[2] == no_of_items) {
|
||||
done = pdTRUE;
|
||||
}
|
||||
}
|
||||
@ -676,7 +677,7 @@ TEST_CASE("Test ring buffer with queue sets", "[esp_ringbuf]")
|
||||
}
|
||||
//Create a task to send items to each ring buffer
|
||||
int no_of_items = BUFFER_SIZE / SMALL_ITEM_SIZE;
|
||||
xTaskCreatePinnedToCore(queue_set_receiving_task, "rec tsk", 2048, (void *)queue_set, UNITY_FREERTOS_PRIORITY + 1 , NULL, 0);
|
||||
xTaskCreatePinnedToCore(queue_set_receiving_task, "rec tsk", 2048, (void *)queue_set, UNITY_FREERTOS_PRIORITY + 1, NULL, 0);
|
||||
|
||||
//Send multiple items to each type of ring buffer
|
||||
for (int i = 0; i < no_of_items; i++) {
|
||||
@ -687,7 +688,7 @@ TEST_CASE("Test ring buffer with queue sets", "[esp_ringbuf]")
|
||||
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY);
|
||||
vSemaphoreDelete(done_sem);
|
||||
//Remove and delete ring buffers from queue sets
|
||||
//Remove and delete ring buffers from queue sets
|
||||
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
|
||||
TEST_ASSERT_MESSAGE(xRingbufferRemoveFromQueueSetRead(buffer_handles[i], queue_set) == pdTRUE, "Failed to remove from read queue set");
|
||||
vRingbufferDelete(buffer_handles[i]);
|
||||
@ -703,19 +704,14 @@ TEST_CASE("Test ring buffer with queue sets", "[esp_ringbuf]")
|
||||
* will send then receive an item to a ring buffer.
|
||||
*/
|
||||
|
||||
#define TIMER_GROUP 0
|
||||
#define TIMER_NUMBER 0
|
||||
#define ISR_ITERATIONS ((BUFFER_SIZE / SMALL_ITEM_SIZE) * 2)
|
||||
|
||||
intr_handle_t ringbuffer_isr_handle;
|
||||
static int buf_type;
|
||||
static int iterations;
|
||||
|
||||
static void ringbuffer_isr(void *arg)
|
||||
static bool on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
//Clear timer interrupt
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, xPortGetCoreID());
|
||||
bool need_yield = false;
|
||||
|
||||
//Test sending to buffer from ISR from ISR
|
||||
if (buf_type < NO_OF_RB_TYPES) {
|
||||
@ -740,63 +736,56 @@ static void ringbuffer_isr(void *arg)
|
||||
if (iterations < ISR_ITERATIONS) {
|
||||
iterations++;
|
||||
buf_type = 0; //Reset and iterate through each buffer type again
|
||||
return;
|
||||
goto out;
|
||||
} else {
|
||||
//Signal complete
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
buf_type++;
|
||||
portYIELD_FROM_ISR();
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_timer(void)
|
||||
{
|
||||
//Setup timer for ISR
|
||||
int timer_group = TIMER_GROUP;
|
||||
int timer_idx = TIMER_NUMBER;
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 10000,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
};
|
||||
timer_init(timer_group, timer_idx, &config); //Configure timer
|
||||
timer_pause(timer_group, timer_idx); //Stop timer counter
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL); //Load counter value
|
||||
timer_set_alarm_value(timer_group, timer_idx, 20); //Set alarm value
|
||||
timer_enable_intr(timer_group, timer_idx); //Enable timer interrupt
|
||||
timer_set_auto_reload(timer_group, timer_idx, 1); //Auto Reload
|
||||
timer_isr_register(timer_group, timer_idx, ringbuffer_isr, NULL, 0, &ringbuffer_isr_handle); //Set ISR handler
|
||||
}
|
||||
|
||||
static void cleanup_timer(void)
|
||||
{
|
||||
timer_disable_intr(TIMER_GROUP, TIMER_NUMBER);
|
||||
esp_intr_free(ringbuffer_isr_handle);
|
||||
out:
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
TEST_CASE("Test ring buffer ISR", "[esp_ringbuf]")
|
||||
{
|
||||
gptimer_handle_t gptimer;
|
||||
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
|
||||
buffer_handles[i] = xRingbufferCreate(BUFFER_SIZE, i);
|
||||
}
|
||||
done_sem = xSemaphoreCreateBinary();
|
||||
buf_type = 0;
|
||||
iterations = 0;
|
||||
setup_timer();
|
||||
//Start timer to trigger ISR
|
||||
timer_start(TIMER_GROUP, TIMER_NUMBER);
|
||||
|
||||
//Setup timer for ISR
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 2000,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
//Wait for ISR to complete multiple iterations
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY);
|
||||
|
||||
//Cleanup
|
||||
cleanup_timer();
|
||||
TEST_ESP_OK(gptimer_stop(gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
vSemaphoreDelete(done_sem);
|
||||
for (int i = 0; i < NO_OF_RB_TYPES; i++) {
|
||||
vRingbufferDelete(buffer_handles[i]);
|
||||
@ -819,7 +808,8 @@ static const char continuous_data[] = {"A_very_long_string_that_will_be_split_in
|
||||
"items_of_random_lengths_and_sent_to_the_ring_"
|
||||
"buffer._The_maximum_random_length_will_also_"
|
||||
"be_increased_over_multiple_iterations_in_this"
|
||||
"_test"};
|
||||
"_test"
|
||||
};
|
||||
#define CONT_DATA_LEN sizeof(continuous_data)
|
||||
//32-bit aligned size that guarantees a wrap around at some point
|
||||
#define CONT_DATA_TEST_BUFF_LEN (((CONT_DATA_LEN/2) + 0x03) & ~0x3)
|
||||
@ -847,7 +837,7 @@ static void send_to_buffer(RingbufHandle_t buffer, size_t max_item_size)
|
||||
}
|
||||
|
||||
//Send item
|
||||
TEST_ASSERT_MESSAGE(xRingbufferSend(buffer, (void *)&(continuous_data[bytes_sent]), next_item_size, TIMEOUT_TICKS) == pdTRUE, "Failed to send an item");
|
||||
TEST_ASSERT_MESSAGE(xRingbufferSend(buffer, (void *) & (continuous_data[bytes_sent]), next_item_size, TIMEOUT_TICKS) == pdTRUE, "Failed to send an item");
|
||||
bytes_sent += next_item_size;
|
||||
}
|
||||
xSemaphoreGive(tx_done);
|
||||
@ -907,7 +897,7 @@ static void send_task(void *args)
|
||||
//Test sending short length items
|
||||
send_to_buffer(buffer, 1);
|
||||
//Test sending mid length items
|
||||
send_to_buffer(buffer, max_item_len/2);
|
||||
send_to_buffer(buffer, max_item_len / 2);
|
||||
//Test sending long length items
|
||||
send_to_buffer(buffer, max_item_len);
|
||||
vTaskDelete(NULL);
|
||||
@ -921,7 +911,7 @@ static void rec_task(void *args)
|
||||
//Test receiving short length items
|
||||
read_from_buffer(buffer, ((task_args_t *)args)->type, 1);
|
||||
//Test receiving mid length items
|
||||
read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len/2);
|
||||
read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len / 2);
|
||||
//Test receiving long length items
|
||||
read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len);
|
||||
|
||||
@ -988,10 +978,10 @@ TEST_CASE("Test static ring buffer SMP", "[esp_ringbuf]")
|
||||
//Allocate memory and create semaphores
|
||||
#if CONFIG_SPIRAM_USE_CAPS_ALLOC //When SPIRAM can only be allocated using heap_caps_malloc()
|
||||
buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM);
|
||||
buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*CONT_DATA_TEST_BUFF_LEN, MALLOC_CAP_SPIRAM);
|
||||
buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t) * CONT_DATA_TEST_BUFF_LEN, MALLOC_CAP_SPIRAM);
|
||||
#else //Case where SPIRAM is disabled or when SPIRAM is allocatable through malloc()
|
||||
buffer_struct = (StaticRingbuffer_t *)malloc(sizeof(StaticRingbuffer_t));
|
||||
buffer_storage = (uint8_t *)malloc(sizeof(uint8_t)*CONT_DATA_TEST_BUFF_LEN);
|
||||
buffer_storage = (uint8_t *)malloc(sizeof(uint8_t) * CONT_DATA_TEST_BUFF_LEN);
|
||||
#endif
|
||||
TEST_ASSERT(buffer_struct != NULL && buffer_storage != NULL);
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*/
|
||||
/*
|
||||
* FreeModbus Libary: ESP32 Port Demo Application
|
||||
@ -39,7 +39,6 @@
|
||||
|
||||
/* ----------------------- Platform includes --------------------------------*/
|
||||
#include "driver/uart.h"
|
||||
#include "driver/timer.h"
|
||||
#include "esp_log.h" // for ESP_LOGE macro
|
||||
#include "mb_m.h"
|
||||
#include "port.h"
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*/
|
||||
/*
|
||||
* FreeModbus Libary: ESP32 Port Demo Application
|
||||
@ -40,7 +40,6 @@
|
||||
/* ----------------------- Platform includes --------------------------------*/
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "driver/timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "port.h"
|
||||
|
||||
|
@ -5,15 +5,9 @@
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "unity.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define int_clr_timers int_clr
|
||||
#define update update.update
|
||||
#define int_st_timers int_st
|
||||
#endif
|
||||
|
||||
#define BIT_CALL (1 << 0)
|
||||
#define BIT_RESPONSE(TASK) (1 << (TASK+1))
|
||||
#define ALL_RESPONSE_BITS (((1 << NUM_TASKS) - 1) << 1)
|
||||
@ -72,7 +66,7 @@ TEST_CASE("FreeRTOS Event Groups", "[freertos]")
|
||||
|
||||
/* Ensure all tasks cleaned up correctly */
|
||||
for (int c = 0; c < NUM_TASKS; c++) {
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, 100/portTICK_PERIOD_MS) );
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, 100 / portTICK_PERIOD_MS) );
|
||||
}
|
||||
|
||||
vSemaphoreDelete(done_sem);
|
||||
@ -117,7 +111,7 @@ TEST_CASE("FreeRTOS Event Group Sync", "[freertos]")
|
||||
|
||||
/* Ensure all tasks cleaned up correctly */
|
||||
for (int c = 0; c < NUM_TASKS; c++) {
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, 100/portTICK_PERIOD_MS) );
|
||||
TEST_ASSERT( xSemaphoreTake(done_sem, 100 / portTICK_PERIOD_MS) );
|
||||
}
|
||||
|
||||
vSemaphoreDelete(done_sem);
|
||||
@ -132,63 +126,27 @@ TEST_CASE("FreeRTOS Event Group Sync", "[freertos]")
|
||||
*/
|
||||
|
||||
//Use a timer to trigger an ISr
|
||||
#define TIMER_DIVIDER 10000
|
||||
#define TIMER_COUNT 100
|
||||
#define TIMER_NUMBER 0
|
||||
#define BITS 0xAA
|
||||
|
||||
static timer_isr_handle_t isr_handle;
|
||||
static gptimer_handle_t gptimer;
|
||||
static bool test_set_bits;
|
||||
static bool test_clear_bits;
|
||||
|
||||
static void IRAM_ATTR event_group_isr(void *arg)
|
||||
static bool on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
portBASE_TYPE task_woken = pdFALSE;
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, xPortGetCoreID());
|
||||
|
||||
if(test_set_bits){
|
||||
gptimer_stop(timer);
|
||||
|
||||
if (test_set_bits) {
|
||||
xEventGroupSetBitsFromISR(eg, BITS, &task_woken);
|
||||
timer_pause(TIMER_GROUP_0, TIMER_NUMBER);
|
||||
test_set_bits = false;
|
||||
} else if (test_clear_bits){
|
||||
} else if (test_clear_bits) {
|
||||
xEventGroupClearBitsFromISR(eg, BITS);
|
||||
xSemaphoreGiveFromISR(done_sem, &task_woken);
|
||||
timer_pause(TIMER_GROUP_0, TIMER_NUMBER);
|
||||
test_clear_bits = false;
|
||||
}
|
||||
//Switch context if necessary
|
||||
if(task_woken == pdTRUE){
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_timer(void)
|
||||
{
|
||||
//Setup timer for ISR
|
||||
int timer_group = TIMER_GROUP_0;
|
||||
int timer_idx = TIMER_NUMBER;
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = TIMER_DIVIDER,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
};
|
||||
timer_init(timer_group, timer_idx, &config); //Configure timer
|
||||
timer_pause(timer_group, timer_idx); //Stop timer counter
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL); //Load counter value
|
||||
timer_set_alarm_value(timer_group, timer_idx, TIMER_COUNT); //Set alarm value
|
||||
timer_enable_intr(timer_group, timer_idx); //Enable timer interrupt
|
||||
timer_set_auto_reload(timer_group, timer_idx, 1); //Auto Reload
|
||||
timer_isr_register(timer_group, timer_idx, event_group_isr, NULL, ESP_INTR_FLAG_IRAM, &isr_handle); //Set ISR handler
|
||||
}
|
||||
|
||||
static void cleanup_timer(void)
|
||||
{
|
||||
timer_disable_intr(TIMER_GROUP_0, TIMER_NUMBER);
|
||||
esp_intr_free(isr_handle);
|
||||
return task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("FreeRTOS Event Group ISR", "[freertos]")
|
||||
@ -197,23 +155,41 @@ TEST_CASE("FreeRTOS Event Group ISR", "[freertos]")
|
||||
eg = xEventGroupCreate();
|
||||
test_set_bits = false;
|
||||
test_clear_bits = false;
|
||||
setup_timer(); //Init timer to trigger ISR
|
||||
//Setup timer for ISR
|
||||
gptimer_config_t config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 200000,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm_cb,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
//Test set bits
|
||||
printf("test set bits\r\n");
|
||||
test_set_bits = true;
|
||||
timer_start(TIMER_GROUP_0, TIMER_NUMBER);
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
TEST_ASSERT_EQUAL(BITS, xEventGroupWaitBits(eg, BITS, pdFALSE, pdTRUE, portMAX_DELAY)); //Let ISR set event group bits
|
||||
|
||||
//Test clear bits
|
||||
printf("test clear bits\r\n");
|
||||
xEventGroupSetBits(eg, BITS); //Set bits to be cleared
|
||||
test_clear_bits = true;
|
||||
timer_start(TIMER_GROUP_0, TIMER_NUMBER);
|
||||
TEST_ESP_OK(gptimer_set_raw_count(gptimer, 0));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for ISR to clear bits
|
||||
vTaskDelay(10); //Event group clear bits runs via daemon task, delay so daemon can run
|
||||
TEST_ASSERT_EQUAL(0, xEventGroupGetBits(eg)); //Check bits are cleared
|
||||
|
||||
//Clean up
|
||||
cleanup_timer();
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
vEventGroupDelete(eg);
|
||||
vSemaphoreDelete(done_sem);
|
||||
vTaskDelay(10); //Give time for idle task to clear up deleted tasks
|
||||
|
@ -9,30 +9,20 @@
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <freertos/semphr.h>
|
||||
#include "driver/timer.h"
|
||||
#ifndef CONFIG_FREERTOS_UNICORE
|
||||
#include "esp_ipc.h"
|
||||
#endif
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define int_clr_timers int_clr
|
||||
#define update update.update
|
||||
#define int_st_timers int_st
|
||||
#endif
|
||||
|
||||
#define NO_OF_NOTIFS 4
|
||||
#define NO_OF_TASKS 2 //Sender and receiver
|
||||
#define TIMER_DIVIDER 10000
|
||||
#define TIMER_COUNT 100
|
||||
#define MESSAGE 0xFF
|
||||
#define MESSAGE 0xFF
|
||||
|
||||
static uint32_t send_core_message = 0;
|
||||
static TaskHandle_t rec_task_handle;
|
||||
static TaskHandle_t recv_task_handle;
|
||||
static bool isr_give = false;
|
||||
|
||||
static bool test_start = false;
|
||||
static gptimer_handle_t gptimers[portNUM_PROCESSORS];
|
||||
static SemaphoreHandle_t trigger_send_semphr;
|
||||
static SemaphoreHandle_t task_delete_semphr;
|
||||
|
||||
@ -41,40 +31,77 @@ static volatile uint32_t notifs_sent = 0;
|
||||
static volatile uint32_t notifs_rec = 0;
|
||||
static bool wrong_core = false;
|
||||
|
||||
static void sender_task (void* arg){
|
||||
static bool on_alarm_sender_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
gptimer_stop(timer);
|
||||
if (!test_start) {
|
||||
return false;
|
||||
}
|
||||
int curcore = xPortGetCoreID();
|
||||
if (isr_give) { //Test vTaskNotifyGiveFromISR() on same core
|
||||
notifs_sent++;
|
||||
vTaskNotifyGiveFromISR(recv_task_handle, NULL);
|
||||
} else { //Test xTaskNotifyFromISR()
|
||||
notifs_sent++;
|
||||
xTaskNotifyFromISR(recv_task_handle, (MESSAGE << curcore), eSetValueWithOverwrite, NULL);
|
||||
}
|
||||
// always trigger a task switch when exit ISR context
|
||||
return true;
|
||||
}
|
||||
|
||||
static void test_gptimer_start(void *arg)
|
||||
{
|
||||
gptimer_handle_t gptimer = (gptimer_handle_t)arg;
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 1000,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_set_raw_count(gptimer, 0));
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_alarm_sender_cb,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
}
|
||||
|
||||
static void sender_task(void *arg)
|
||||
{
|
||||
gptimer_handle_t gptimer = (gptimer_handle_t)arg;
|
||||
int curcore = xPortGetCoreID();
|
||||
|
||||
//Test xTaskNotify
|
||||
xSemaphoreTake(trigger_send_semphr, portMAX_DELAY);
|
||||
notifs_sent++;
|
||||
xTaskNotify(rec_task_handle, (MESSAGE << curcore), eSetValueWithOverwrite);
|
||||
xTaskNotify(recv_task_handle, (MESSAGE << curcore), eSetValueWithOverwrite);
|
||||
|
||||
//Test xTaskNotifyGive
|
||||
xSemaphoreTake(trigger_send_semphr, portMAX_DELAY);
|
||||
notifs_sent++;
|
||||
xTaskNotifyGive(rec_task_handle);
|
||||
xTaskNotifyGive(recv_task_handle);
|
||||
|
||||
//Test xTaskNotifyFromISR
|
||||
xSemaphoreTake(trigger_send_semphr, portMAX_DELAY);
|
||||
isr_give = false;
|
||||
timer_start(TIMER_GROUP_0, curcore);
|
||||
test_gptimer_start(gptimer);
|
||||
|
||||
//Test vTaskNotifyGiveFromISR
|
||||
xSemaphoreTake(trigger_send_semphr, portMAX_DELAY);
|
||||
isr_give = true;
|
||||
timer_start(TIMER_GROUP_0, curcore);
|
||||
test_gptimer_start(gptimer);
|
||||
|
||||
//Delete Task and Semaphores
|
||||
xSemaphoreGive(task_delete_semphr);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void receiver_task (void* arg){
|
||||
static void receiver_task(void *arg)
|
||||
{
|
||||
uint32_t notify_value;
|
||||
|
||||
//Test xTaskNotifyWait from task
|
||||
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
|
||||
if(notify_value != send_core_message){
|
||||
if (notify_value != send_core_message) {
|
||||
wrong_core = true;
|
||||
}
|
||||
notifs_rec++;
|
||||
@ -87,7 +114,7 @@ static void receiver_task (void* arg){
|
||||
//Test xTaskNotifyWait from ISR
|
||||
xSemaphoreGive(trigger_send_semphr);
|
||||
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
|
||||
if(notify_value != send_core_message){
|
||||
if (notify_value != send_core_message) {
|
||||
wrong_core = true;
|
||||
}
|
||||
notifs_rec++;
|
||||
@ -102,111 +129,62 @@ static void receiver_task (void* arg){
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR sender_ISR (void *arg)
|
||||
static void install_gptimer_on_core(void *arg)
|
||||
{
|
||||
int curcore = xPortGetCoreID();
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, curcore);
|
||||
timer_group_set_counter_enable_in_isr(TIMER_GROUP_0, curcore, TIMER_PAUSE);
|
||||
//Re-enable alarm
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, curcore);
|
||||
|
||||
if(isr_give){ //Test vTaskNotifyGiveFromISR() on same core
|
||||
notifs_sent++;
|
||||
vTaskNotifyGiveFromISR(rec_task_handle, NULL);
|
||||
}
|
||||
else { //Test xTaskNotifyFromISR()
|
||||
notifs_sent++;
|
||||
xTaskNotifyFromISR(rec_task_handle, (MESSAGE << curcore), eSetValueWithOverwrite, NULL);
|
||||
}
|
||||
portYIELD_FROM_ISR();
|
||||
return;
|
||||
}
|
||||
|
||||
static void timerg0_init(void *isr_handle)
|
||||
{
|
||||
int timer_group = TIMER_GROUP_0;
|
||||
int timer_idx = xPortGetCoreID();
|
||||
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = TIMER_DIVIDER,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
int core_id = (int)arg;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Stop timer counter*/
|
||||
timer_pause(timer_group, timer_idx);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Set alarm value*/
|
||||
timer_set_alarm_value(timer_group, timer_idx, TIMER_COUNT);
|
||||
/*Enable timer interrupt*/
|
||||
timer_enable_intr(timer_group, timer_idx);
|
||||
//Auto Reload
|
||||
timer_set_auto_reload(timer_group, timer_idx, 1);
|
||||
/*Set ISR handler*/
|
||||
timer_isr_register(timer_group, timer_idx, sender_ISR, NULL, ESP_INTR_FLAG_IRAM, (intr_handle_t *)isr_handle);
|
||||
}
|
||||
|
||||
static void timerg0_deinit(void* isr_handle)
|
||||
{
|
||||
int timer_group = TIMER_GROUP_0;
|
||||
int timer_idx = xPortGetCoreID();
|
||||
intr_handle_t handle = *((intr_handle_t *) isr_handle);
|
||||
//Pause timer then free registered ISR
|
||||
timer_pause(timer_group, timer_idx);
|
||||
esp_intr_free(handle);
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimers[core_id]));
|
||||
test_gptimer_start(gptimers[core_id]);
|
||||
xSemaphoreGive(task_delete_semphr);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Task_Notify", "[freertos]")
|
||||
{
|
||||
//Initialize and pause timers. Used to trigger ISR
|
||||
intr_handle_t isr_handle_0 = NULL;
|
||||
timerg0_init(&isr_handle_0); //Core 0 timer
|
||||
#ifndef CONFIG_FREERTOS_UNICORE
|
||||
intr_handle_t isr_handle_1 = NULL;
|
||||
esp_ipc_call(1, timerg0_init, &isr_handle_1); //Core 1 timer
|
||||
#endif
|
||||
|
||||
test_start = false;
|
||||
trigger_send_semphr = xSemaphoreCreateBinary();
|
||||
task_delete_semphr = xQueueCreateCountingSemaphore(NO_OF_TASKS, 0);
|
||||
|
||||
for(int i = 0; i < portNUM_PROCESSORS; i++){ //Sending Core
|
||||
for(int j = 0; j < portNUM_PROCESSORS; j++){ //Receiving Core
|
||||
task_delete_semphr = xQueueCreateCountingSemaphore(10, 0);
|
||||
for (int i = 0; i < portNUM_PROCESSORS; i++) {
|
||||
xTaskCreatePinnedToCore(install_gptimer_on_core, "install_gptimer", 4096, (void *const)i, UNITY_FREERTOS_PRIORITY + 1, NULL, i);
|
||||
TEST_ASSERT(xSemaphoreTake(task_delete_semphr, pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
// wait the gptimer installation done on specific core
|
||||
vTaskDelay(10);
|
||||
// test start
|
||||
test_start = true;
|
||||
for (int i = 0; i < portNUM_PROCESSORS; i++) { //Sending Core
|
||||
for (int j = 0; j < portNUM_PROCESSORS; j++) { //Receiving Core
|
||||
//Reset Values
|
||||
notifs_sent = 0;
|
||||
notifs_rec = 0;
|
||||
wrong_core = false;
|
||||
|
||||
send_core_message = (0xFF << i); //0xFF if core 0, 0xFF0 if core 1
|
||||
|
||||
xTaskCreatePinnedToCore(receiver_task, "rec task", 1000, NULL, UNITY_FREERTOS_PRIORITY + 2, &rec_task_handle, j);
|
||||
xTaskCreatePinnedToCore(sender_task, "send task", 1000, NULL, UNITY_FREERTOS_PRIORITY + 1, NULL, i);
|
||||
send_core_message = (0xFF << i); //0xFF if core 0, 0xFF0 if core 1
|
||||
// receiver task has higher priority than sender task
|
||||
xTaskCreatePinnedToCore(receiver_task, "recv task", 1000, NULL, UNITY_FREERTOS_PRIORITY + 2, &recv_task_handle, j);
|
||||
xTaskCreatePinnedToCore(sender_task, "send task", 1000, gptimers[i], UNITY_FREERTOS_PRIORITY + 1, NULL, i);
|
||||
vTaskDelay(5); //Wait for task creation to complete
|
||||
|
||||
xSemaphoreGive(trigger_send_semphr); //Trigger sender task
|
||||
for(int k = 0; k < NO_OF_TASKS; k++){ //Wait for sender and receiver task deletion
|
||||
TEST_ASSERT( xSemaphoreTake(task_delete_semphr, 1000 / portTICK_PERIOD_MS) );
|
||||
for (int k = 0; k < NO_OF_TASKS; k++) { //Wait for sender and receiver task deletion
|
||||
TEST_ASSERT(xSemaphoreTake(task_delete_semphr, pdMS_TO_TICKS(2000)));
|
||||
}
|
||||
vTaskDelay(5); //Give time tasks to delete
|
||||
|
||||
TEST_ASSERT(notifs_sent == NO_OF_NOTIFS);
|
||||
TEST_ASSERT(notifs_rec == NO_OF_NOTIFS);
|
||||
TEST_ASSERT(wrong_core == false);
|
||||
TEST_ASSERT_EQUAL(NO_OF_NOTIFS, notifs_sent);
|
||||
TEST_ASSERT_EQUAL(NO_OF_NOTIFS, notifs_rec);
|
||||
TEST_ASSERT_EQUAL(false, wrong_core);
|
||||
}
|
||||
}
|
||||
|
||||
//Delete Semaphroes and timer ISRs
|
||||
vSemaphoreDelete(trigger_send_semphr);
|
||||
vSemaphoreDelete(task_delete_semphr);
|
||||
timerg0_deinit(&isr_handle_0);
|
||||
isr_handle_0 = NULL;
|
||||
#ifndef CONFIG_FREERTOS_UNICORE
|
||||
esp_ipc_call(1, timerg0_deinit, &isr_handle_1);
|
||||
isr_handle_1 = NULL;
|
||||
#endif
|
||||
for (int i = 0; i < portNUM_PROCESSORS; i++) {
|
||||
TEST_ESP_OK(gptimer_stop(gptimers[i]));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimers[i]));
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <freertos/semphr.h>
|
||||
#include "driver/timer.h"
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
|
@ -7,33 +7,24 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include "driver/timer.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_private/gptimer.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define int_clr_timers int_clr
|
||||
#define update update.update
|
||||
#define int_st_timers int_st
|
||||
#endif
|
||||
|
||||
static SemaphoreHandle_t isr_semaphore;
|
||||
static volatile unsigned isr_count;
|
||||
|
||||
/* Timer ISR increments an ISR counter, and signals a
|
||||
mutex semaphore to wake up another counter task */
|
||||
static void timer_group0_isr(void *vp_arg)
|
||||
static bool on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
portBASE_TYPE higher_awoken = pdFALSE;
|
||||
isr_count++;
|
||||
xSemaphoreGiveFromISR(isr_semaphore, &higher_awoken);
|
||||
if (higher_awoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
return higher_awoken == pdTRUE;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@ -45,7 +36,7 @@ static void counter_task_fn(void *vp_config)
|
||||
{
|
||||
counter_config_t *config = (counter_config_t *)vp_config;
|
||||
printf("counter_task running...\n");
|
||||
while(1) {
|
||||
while (1) {
|
||||
xSemaphoreTake(config->trigger_sem, portMAX_DELAY);
|
||||
config->counter++;
|
||||
}
|
||||
@ -59,9 +50,11 @@ static void counter_task_fn(void *vp_config)
|
||||
TEST_CASE("Scheduler disabled can handle a pending context switch on resume", "[freertos]")
|
||||
{
|
||||
isr_count = 0;
|
||||
isr_semaphore = xSemaphoreCreateBinary();
|
||||
TaskHandle_t counter_task;
|
||||
intr_handle_t isr_handle = NULL;
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
intr_handle_t intr_handle = NULL;
|
||||
isr_semaphore = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(isr_semaphore);
|
||||
|
||||
counter_config_t count_config = {
|
||||
.trigger_sem = isr_semaphore,
|
||||
@ -71,25 +64,26 @@ TEST_CASE("Scheduler disabled can handle a pending context switch on resume", "[
|
||||
&count_config, UNITY_FREERTOS_PRIORITY + 1,
|
||||
&counter_task, UNITY_FREERTOS_CPU);
|
||||
|
||||
/* Configure timer ISR */
|
||||
const timer_config_t timer_config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 2, //Range is 2 to 65536
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick=1us
|
||||
};
|
||||
/* Configure timer */
|
||||
timer_init(TIMER_GROUP_0, TIMER_0, &timer_config);
|
||||
timer_pause(TIMER_GROUP_0, TIMER_0);
|
||||
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
|
||||
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000);
|
||||
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, NULL, 0, &isr_handle);
|
||||
timer_start(TIMER_GROUP_0, TIMER_0);
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 1000, // alarm period 1ms
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm_cb,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
TEST_ESP_OK(gptimer_get_intr_handle(gptimer, &intr_handle));
|
||||
|
||||
vTaskDelay(5);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
|
||||
// Check some counts have been triggered via the ISR
|
||||
TEST_ASSERT(count_config.counter > 10);
|
||||
@ -104,24 +98,22 @@ TEST_CASE("Scheduler disabled can handle a pending context switch on resume", "[
|
||||
// scheduler off on this CPU...
|
||||
esp_rom_delay_us(20 * 1000);
|
||||
|
||||
//TEST_ASSERT_NOT_EQUAL(no_sched_isr, isr_count);
|
||||
TEST_ASSERT_EQUAL(count_config.counter, no_sched_task);
|
||||
|
||||
// disable timer interrupts
|
||||
timer_disable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
esp_intr_disable(intr_handle);
|
||||
|
||||
// When we resume scheduler, we expect the counter task
|
||||
// will preempt and count at least one more item
|
||||
esp_intr_noniram_enable();
|
||||
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
esp_intr_enable(intr_handle);
|
||||
xTaskResumeAll();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(count_config.counter, no_sched_task);
|
||||
}
|
||||
|
||||
esp_intr_free(isr_handle);
|
||||
timer_disable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
|
||||
TEST_ESP_OK(gptimer_stop(gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
vTaskDelete(counter_task);
|
||||
vSemaphoreDelete(isr_semaphore);
|
||||
}
|
||||
@ -132,7 +124,7 @@ TEST_CASE("Scheduler disabled can handle a pending context switch on resume", "[
|
||||
*/
|
||||
TEST_CASE("Scheduler disabled can wake multiple tasks on resume", "[freertos]")
|
||||
{
|
||||
#define TASKS_PER_PROC 4
|
||||
#define TASKS_PER_PROC 4
|
||||
TaskHandle_t tasks[portNUM_PROCESSORS][TASKS_PER_PROC] = { 0 };
|
||||
counter_config_t counters[portNUM_PROCESSORS][TASKS_PER_PROC] = { 0 };
|
||||
|
||||
@ -169,7 +161,7 @@ TEST_CASE("Scheduler disabled can wake multiple tasks on resume", "[freertos]")
|
||||
for (int t = 0; t < TASKS_PER_PROC; t++) {
|
||||
xSemaphoreGive(counters[p][t].trigger_sem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_rom_delay_us(200); /* Let the other CPU do some things */
|
||||
|
||||
@ -207,7 +199,7 @@ static void suspend_scheduler_5ms_task_fn(void *ignore)
|
||||
{
|
||||
vTaskSuspendAll();
|
||||
sched_suspended = true;
|
||||
for (int i = 0; i <5; i++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
esp_rom_delay_us(1000);
|
||||
}
|
||||
xTaskResumeAll();
|
||||
@ -237,7 +229,7 @@ TEST_CASE("Scheduler disabled on CPU B, tasks on A can wake", "[freertos]")
|
||||
|
||||
/* counter task is now blocked on other CPU, waiting for wake_sem, and we expect
|
||||
that this CPU's scheduler will be suspended for 5ms shortly... */
|
||||
while(!sched_suspended) { }
|
||||
while (!sched_suspended) { }
|
||||
|
||||
xSemaphoreGive(wake_sem);
|
||||
esp_rom_delay_us(1000);
|
||||
@ -247,7 +239,7 @@ TEST_CASE("Scheduler disabled on CPU B, tasks on A can wake", "[freertos]")
|
||||
TEST_ASSERT(sched_suspended);
|
||||
|
||||
/* wait for the rest of the 5ms... */
|
||||
while(sched_suspended) { }
|
||||
while (sched_suspended) { }
|
||||
|
||||
esp_rom_delay_us(100);
|
||||
TEST_ASSERT_EQUAL(1, count_config.counter); // when scheduler resumes, counter task should immediately count
|
||||
|
@ -9,22 +9,13 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include "driver/timer.h"
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
#ifndef CONFIG_FREERTOS_UNICORE
|
||||
#include "esp_ipc.h"
|
||||
#endif
|
||||
#include "esp_freertos_hooks.h"
|
||||
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define int_clr_timers int_clr
|
||||
#define update update.update
|
||||
#define int_st_timers int_st
|
||||
#endif
|
||||
|
||||
/* Counter task counts a target variable forever */
|
||||
static void task_count(void *vp_counter)
|
||||
{
|
||||
@ -116,33 +107,29 @@ TEST_CASE("Suspend the current running task", "[freertos]")
|
||||
}
|
||||
|
||||
|
||||
volatile bool timer_isr_fired;
|
||||
static volatile bool timer_isr_fired;
|
||||
static gptimer_handle_t gptimer = NULL;
|
||||
|
||||
/* Timer ISR clears interrupt, sets flag, then resumes the task supplied in the
|
||||
* callback argument.
|
||||
*/
|
||||
void IRAM_ATTR timer_group0_isr(void *vp_arg)
|
||||
bool on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
// Clear interrupt
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
|
||||
timer_isr_fired = true;
|
||||
|
||||
TaskHandle_t handle = vp_arg;
|
||||
TaskHandle_t handle = user_ctx;
|
||||
BaseType_t higherPriorityTaskWoken = pdFALSE;
|
||||
gptimer_stop(timer);
|
||||
timer_isr_fired = true;
|
||||
higherPriorityTaskWoken = xTaskResumeFromISR(handle);
|
||||
if (higherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
return higherPriorityTaskWoken == pdTRUE;
|
||||
}
|
||||
|
||||
/* Task suspends itself, then sets parameter value to the current timer group counter and deletes itself */
|
||||
static void task_suspend_self_with_timer(void *vp_resumed)
|
||||
static IRAM_ATTR void task_suspend_self_with_timer(void *vp_resumed)
|
||||
{
|
||||
volatile uint64_t *resumed_counter = vp_resumed;
|
||||
*resumed_counter = 0;
|
||||
vTaskSuspend(NULL);
|
||||
timer_get_counter_value(TIMER_GROUP_0, TIMER_0, vp_resumed);
|
||||
gptimer_get_raw_count(gptimer, (uint64_t *)resumed_counter);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
@ -157,49 +144,40 @@ static void test_resume_task_from_isr(int target_core)
|
||||
xTaskCreatePinnedToCore(task_suspend_self_with_timer, "suspend_self", 2048,
|
||||
(void *)&resumed_counter, UNITY_FREERTOS_PRIORITY + 1,
|
||||
&suspend_task, target_core);
|
||||
|
||||
// delay to make the task has resumed itself
|
||||
vTaskDelay(1);
|
||||
TEST_ASSERT_EQUAL(0, resumed_counter);
|
||||
|
||||
const unsigned APB_CYCLES_PER_TICK = TIMER_BASE_CLK / configTICK_RATE_HZ;
|
||||
const unsigned TEST_TIMER_DIV = 2;
|
||||
const unsigned TEST_TIMER_CYCLES_PER_TICK = APB_CYCLES_PER_TICK / TEST_TIMER_DIV;
|
||||
const unsigned TEST_TIMER_CYCLES_PER_MS = TIMER_BASE_CLK / 1000 / TEST_TIMER_DIV;
|
||||
const unsigned TEST_TIMER_ALARM = TEST_TIMER_CYCLES_PER_TICK / 2; // half an RTOS tick
|
||||
|
||||
/* Configure timer ISR */
|
||||
timer_isr_handle_t isr_handle;
|
||||
const timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 0,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = TEST_TIMER_DIV,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
/*Configure timer*/
|
||||
ESP_ERROR_CHECK( timer_init(TIMER_GROUP_0, TIMER_0, &config) );
|
||||
ESP_ERROR_CHECK( timer_pause(TIMER_GROUP_0, TIMER_0) );
|
||||
ESP_ERROR_CHECK( timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0) );
|
||||
ESP_ERROR_CHECK( timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TEST_TIMER_ALARM) );
|
||||
ESP_ERROR_CHECK( timer_enable_intr(TIMER_GROUP_0, TIMER_0) );
|
||||
ESP_ERROR_CHECK( timer_isr_register(TIMER_GROUP_0, TIMER_0, timer_group0_isr, (void*)suspend_task, ESP_INTR_FLAG_IRAM, &isr_handle) );
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
timer_isr_fired = false;
|
||||
vTaskDelay(1); // Make sure we're at the start of a new tick
|
||||
|
||||
ESP_ERROR_CHECK( timer_start(TIMER_GROUP_0, TIMER_0) );
|
||||
|
||||
vTaskDelay(1); // We expect timer group will fire half-way through this delay
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1000000 / configTICK_RATE_HZ / 2,
|
||||
.reload_count = 0,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm_cb,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, suspend_task));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
// wait the timer interrupt fires up
|
||||
vTaskDelay(2);
|
||||
TEST_ASSERT_TRUE(timer_isr_fired);
|
||||
// check the task was resumed
|
||||
TEST_ASSERT_NOT_EQUAL(0, resumed_counter);
|
||||
// The task should have woken within 500us of the timer interrupt event (note: task may be a flash cache miss)
|
||||
printf("alarm value %u task resumed at %u\n", TEST_TIMER_ALARM, (unsigned)resumed_counter);
|
||||
TEST_ASSERT_UINT32_WITHIN(TEST_TIMER_CYCLES_PER_MS/2, TEST_TIMER_ALARM, (unsigned)resumed_counter);
|
||||
printf("alarm value %llu task resumed at %u\n", alarm_config.alarm_count, (unsigned)resumed_counter);
|
||||
TEST_ASSERT_UINT32_WITHIN(100, alarm_config.alarm_count, (unsigned)resumed_counter);
|
||||
|
||||
// clean up
|
||||
timer_deinit(TIMER_GROUP_0, TIMER_0);
|
||||
ESP_ERROR_CHECK( esp_intr_free(isr_handle) );
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
}
|
||||
|
||||
TEST_CASE("Resume task from ISR (same core)", "[freertos]")
|
||||
@ -216,7 +194,7 @@ TEST_CASE("Resume task from ISR (other core)", "[freertos]")
|
||||
static volatile bool block;
|
||||
static bool suspend_both_cpus;
|
||||
|
||||
static void IRAM_ATTR suspend_scheduler_while_block_set(void* arg)
|
||||
static void IRAM_ATTR suspend_scheduler_while_block_set(void *arg)
|
||||
{
|
||||
vTaskSuspendAll();
|
||||
|
||||
|
@ -14,6 +14,7 @@ if(NOT BOOTLOADER_BUILD)
|
||||
"spi_slave_hal.c"
|
||||
"spi_slave_hal_iram.c"
|
||||
"timer_hal.c"
|
||||
"timer_hal_iram.c"
|
||||
"ledc_hal.c"
|
||||
"ledc_hal_iram.c"
|
||||
"i2c_hal.c"
|
||||
|
@ -22,3 +22,5 @@ entries:
|
||||
if IDF_TARGET_ESP32 = n:
|
||||
spi_flash_hal_gpspi (noflash)
|
||||
systimer_hal (noflash)
|
||||
if GPTIMER_CTRL_FUNC_IN_IRAM = y:
|
||||
timer_hal_iram (noflash)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -12,13 +12,3 @@ void timer_hal_init(timer_hal_context_t *hal, uint32_t group_num, uint32_t timer
|
||||
hal->dev = TIMER_LL_GET_HW(group_num);
|
||||
hal->timer_id = timer_num;
|
||||
}
|
||||
|
||||
void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val)
|
||||
{
|
||||
// save current reload value
|
||||
uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id);
|
||||
timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val);
|
||||
timer_ll_trigger_soft_reload(hal->dev, hal->timer_id);
|
||||
// restore the previous reload value
|
||||
timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload);
|
||||
}
|
||||
|
24
components/hal/timer_hal_iram.c
Normal file
24
components/hal/timer_hal_iram.c
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "hal/timer_hal.h"
|
||||
#include "hal/timer_ll.h"
|
||||
|
||||
void timer_hal_set_counter_value(timer_hal_context_t *hal, uint64_t load_val)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// - `timer_ll_set_reload_value()` will only indicate the `reload_value`
|
||||
// - `timer_ll_set_reload_value()` + ``timer_ll_trigger_soft_reload()` can update the HW counter value by software
|
||||
// Therefore, after updating the HW counter value, we need to restore the previous `reload_value`.
|
||||
// Attention: The following process should be protected by a lock in the driver layer.
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// save current reload value
|
||||
uint64_t old_reload = timer_ll_get_reload_value(hal->dev, hal->timer_id);
|
||||
timer_ll_set_reload_value(hal->dev, hal->timer_id, load_val);
|
||||
timer_ll_trigger_soft_reload(hal->dev, hal->timer_id);
|
||||
// restore the previous reload value
|
||||
timer_ll_set_reload_value(hal->dev, hal->timer_id, old_reload);
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
#include <unity.h>
|
||||
#include <esp_spi_flash.h>
|
||||
#include <esp_attr.h>
|
||||
#include "driver/timer.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "test_utils.h"
|
||||
#include "ccomp_timer.h"
|
||||
@ -117,88 +116,6 @@ TEST_CASE("flash write and erase work both on PRO CPU and on APP CPU", "[spi_fla
|
||||
vSemaphoreDelete(done);
|
||||
}
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
size_t buf_size;
|
||||
uint8_t* buf;
|
||||
size_t flash_addr;
|
||||
size_t repeat_count;
|
||||
SemaphoreHandle_t done;
|
||||
} read_task_arg_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
size_t delay_time_us;
|
||||
size_t repeat_count;
|
||||
} block_task_arg_t;
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
||||
#define int_clr_timers int_clr
|
||||
#endif
|
||||
|
||||
static void IRAM_ATTR timer_isr(void* varg) {
|
||||
block_task_arg_t* arg = (block_task_arg_t*) varg;
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
esp_rom_delay_us(arg->delay_time_us);
|
||||
arg->repeat_count++;
|
||||
}
|
||||
|
||||
static void read_task(void* varg) {
|
||||
read_task_arg_t* arg = (read_task_arg_t*) varg;
|
||||
for (size_t i = 0; i < arg->repeat_count; ++i) {
|
||||
ESP_ERROR_CHECK( spi_flash_read(arg->flash_addr, arg->buf, arg->buf_size) );
|
||||
}
|
||||
xSemaphoreGive(arg->done);
|
||||
vTaskDelay(1);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("spi flash functions can run along with IRAM interrupts", "[spi_flash][esp_flash]")
|
||||
{
|
||||
const size_t size = 128;
|
||||
read_task_arg_t read_arg = {
|
||||
.buf_size = size,
|
||||
.buf = (uint8_t*) malloc(size),
|
||||
.flash_addr = 0,
|
||||
.repeat_count = 1000,
|
||||
.done = xSemaphoreCreateBinary()
|
||||
};
|
||||
|
||||
timer_config_t config = {
|
||||
.alarm_en = true,
|
||||
.counter_en = false,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.auto_reload = true,
|
||||
.divider = 80
|
||||
};
|
||||
|
||||
block_task_arg_t block_arg = {
|
||||
.repeat_count = 0,
|
||||
.delay_time_us = 100
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK( timer_init(TIMER_GROUP_0, TIMER_0, &config) );
|
||||
timer_pause(TIMER_GROUP_0, TIMER_0);
|
||||
ESP_ERROR_CHECK( timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 120) );
|
||||
intr_handle_t handle;
|
||||
ESP_ERROR_CHECK( timer_isr_register(TIMER_GROUP_0, TIMER_0, &timer_isr, &block_arg, ESP_INTR_FLAG_IRAM, &handle) );
|
||||
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
|
||||
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_start(TIMER_GROUP_0, TIMER_0);
|
||||
|
||||
xTaskCreatePinnedToCore(read_task, "r", 2048, &read_arg, 3, NULL, portNUM_PROCESSORS - 1);
|
||||
xSemaphoreTake(read_arg.done, portMAX_DELAY);
|
||||
|
||||
timer_pause(TIMER_GROUP_0, TIMER_0);
|
||||
timer_disable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
esp_intr_free(handle);
|
||||
vSemaphoreDelete(read_arg.done);
|
||||
free(read_arg.buf);
|
||||
}
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3)
|
||||
// TODO ESP32-S3 IDF-2021
|
||||
|
||||
|
@ -1,26 +1,16 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_vfs_eventfd.h"
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "driver/timer.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "unity.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
|
||||
TEST_CASE("eventfd create and close", "[vfs][eventfd]")
|
||||
{
|
||||
@ -217,13 +207,14 @@ TEST_CASE("eventfd signal from task", "[vfs][eventfd]")
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
}
|
||||
|
||||
static void eventfd_select_test_isr(void *arg)
|
||||
static bool eventfd_select_test_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
int fd = *((int *)arg);
|
||||
gptimer_stop(timer);
|
||||
int fd = *((int *)user_ctx);
|
||||
uint64_t val = 1;
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
int ret = write(fd, &val, sizeof(val));
|
||||
assert(ret == sizeof(val));
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]")
|
||||
@ -234,22 +225,23 @@ TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]")
|
||||
int fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, fd);
|
||||
|
||||
timer_config_t timer_config = {
|
||||
.divider = 16,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.auto_reload = false,
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
TEST_ESP_OK(timer_init(TIMER_GROUP_0, TIMER_0, &timer_config));
|
||||
|
||||
TEST_ESP_OK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL));
|
||||
|
||||
TEST_ESP_OK(timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_BASE_CLK / 16));
|
||||
TEST_ESP_OK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
|
||||
TEST_ESP_OK(timer_isr_register(TIMER_GROUP_0, TIMER_0, eventfd_select_test_isr,
|
||||
&fd, ESP_INTR_FLAG_LOWMED, NULL));
|
||||
TEST_ESP_OK(timer_start(TIMER_GROUP_0, TIMER_0));
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 200000,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = eventfd_select_test_isr,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &fd));
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
TEST_ESP_OK(gptimer_start(gptimer));
|
||||
|
||||
struct timeval wait_time;
|
||||
fd_set read_fds, write_fds, error_fds;
|
||||
@ -264,9 +256,9 @@ TEST_CASE("eventfd signal from ISR", "[vfs][eventfd]")
|
||||
int ret = select(fd + 1, &read_fds, &write_fds, &error_fds, &wait_time);
|
||||
TEST_ASSERT_EQUAL(1, ret);
|
||||
TEST_ASSERT(FD_ISSET(fd, &read_fds));
|
||||
timer_deinit(TIMER_GROUP_0, TIMER_0);
|
||||
TEST_ASSERT_EQUAL(0, close(fd));
|
||||
TEST_ESP_OK(esp_vfs_eventfd_unregister());
|
||||
TEST_ESP_OK(gptimer_del_timer(gptimer));
|
||||
}
|
||||
|
||||
static void close_task(void *arg)
|
||||
|
@ -106,7 +106,7 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/spi_slave.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/spi_slave_hd.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/timer_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/timer.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/gptimer.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/touch_sensor_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/touch_sensor_common.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/twai_types.h \
|
||||
|
278
docs/en/api-reference/peripherals/gptimer.rst
Normal file
278
docs/en/api-reference/peripherals/gptimer.rst
Normal file
@ -0,0 +1,278 @@
|
||||
General Purpose Timer (GPTimer)
|
||||
===============================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
A general purpose timer is a hardware timer with high resolution and flexible alarm action. The behavior when the internal counter of a timer reaches a specific target value is called timer alarm. When a timer alarms, a user registered per-timer callback would be called.
|
||||
|
||||
Typically, a general purpose timer can be used in scenarios like:
|
||||
|
||||
- Free running as a wall clock, fetching a high-resolution time stamp at any time and any places
|
||||
|
||||
- Generate period alarms, trigger events periodically
|
||||
|
||||
- Generate one-shot alarm, respond in target time
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
|
||||
The following sections of this document cover the typical steps to install and operate a timer:
|
||||
|
||||
- `Resource Allocation <#resource-allocation>`__ - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working.
|
||||
|
||||
- `Set and Get count value <#set-and-get-count-value>`__ - covers how to force the timer counting from a start point and how to get the count value at anytime.
|
||||
|
||||
- `Start and Stop timer <#start-and-stop-timer>`__ - covers which parameters should be set up to start the timer with specific alarm event behavior.
|
||||
|
||||
- `Power Management <#power-management>`__ - describes how different source clock selections can affect power consumption.
|
||||
|
||||
- `IRAM safe <#iram-safe>`__ - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache.
|
||||
|
||||
Resource Allocation
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Different ESP chip might have different number of independent timer groups, and within each group, there could also be several independent timers. Refer to the datasheet to find out how many hardware timers exist (usually described in the "General Purpose Timer" chapter).
|
||||
|
||||
From driver's point of view, a GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver behind will manage all available hardware resources in a pool, so that users don't need to care about which timer and which group it belongs to.
|
||||
|
||||
To install a timer instance, there's a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`:
|
||||
|
||||
- :cpp:member:`clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, [1]_ you can only pick one of them. For the effect on power consumption of different clock source, please refer to `Power management <#power-management>`__ section.
|
||||
|
||||
- :cpp:member:`direction` sets the counting direction of the timer, supported directions are listed in :cpp:type:`gptimer_count_direction_t`, you can only pick one of them.
|
||||
|
||||
- :cpp:member:`resolution_hz` sets the resolution of the internal counter. Each count step is equivalent to **1 / resolution_hz** seconds.
|
||||
|
||||
- Optional :cpp:member:`intr_shared` sets whether or not mark the timer interrupt source as a shared one. For the pros/cons of a shared interrupt, you can refer to :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`.
|
||||
|
||||
With all the above configurations set in the structure, the structure can be passed to :cpp:func:`gptimer_new_timer` which will instantiate the timer instance and return a handle of the timer.
|
||||
|
||||
The function can fail due to various errors such as insufficient memory, invalid arguments, etc. Specifically, when there are no more free timers (i.e. all hardware resources have been used up), then :cpp:member:`ESP_ERR_NOT_FOUND` will be returned. The total number of available timers is represented by the :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` and its value will depend on the ESP chip.
|
||||
|
||||
If a previously created GPTimer instance is no longer required, you should recycle the timer by calling :cpp:func:`gptimer_del_timer`. This will allow the underlying HW timer to be used for other purposes. Before deleting a GPTimer handle, you should stop it by :cpp:func:`gptimer_stop` in advance or make sure it has not started yet by :cpp:func:`gptimer_start`.
|
||||
|
||||
Creating a GPTimer Handle with Resolution of 1MHz
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: c
|
||||
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
Set and Get Count Value
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When the GPTimer is created, the internal counter will be reset to zero by default. The counter value can be updated asynchronously by :cpp:func:`gptimer_set_raw_count`. The maximum count value is dependent on the hardware timer's bit-width, which is also reflected by the SOC macro :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH`. When updating the raw count of an active timer, the timer will immediately start counting from the new value.
|
||||
|
||||
Count value can be retrieved by :cpp:func:`gptimer_get_raw_count`, at anytime.
|
||||
|
||||
Set Up Alarm Action
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most of the use cases of GPTimer should set up the alarm action before starting the timer, except for the simple wall-clock scenario, where a free running timer is enough. To set up the alarm action, one should configure several members of :cpp:type:`gptimer_alarm_config_t` based on how he takes use of the alarm event:
|
||||
|
||||
- :cpp:member:`alarm_count` sets the target count value that will trigger the alarm event. You should also take the counting direction into consideration when setting the alarm value.
|
||||
Specially, :cpp:member:`alarm_count` and :cpp:member:`reload_count` can't be set to the same value when :cpp:member:`auto_reload_on_alarm` is true, as keeping reload with a target alarm count is meaningless.
|
||||
|
||||
- :cpp:member:`reload_count` sets the count value to be reloaded when the alarm event happens. This configuration only takes effect when :cpp:member:`auto_reload_on_alarm` is set to true.
|
||||
|
||||
- :cpp:member:`auto_reload_on_alarm` flag sets whether to enable the auto-reload feature. If enabled, the hardware timer will reload the value of :cpp:member:`reload_count` into counter immediately when alarm event happens.
|
||||
|
||||
To make the alarm configurations take effect, one should call :cpp:func:`gptimer_set_alarm_action`. Especially, if :cpp:type:`gptimer_alarm_config_t` is set to ``NULL``, the alarm function will be disabled.
|
||||
|
||||
.. note::
|
||||
|
||||
* If an alarm value is set and the timer has already crossed this value, the alarm will be triggered immediately.
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
After the timer starts up, it can generate specific event (e.g. the "Alarm Event") dynamically. If you have some function that should be called when event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`gptimer_register_event_callbacks`. All supported event callbacks are listed in the :cpp:type:`gptimer_event_callbacks_t`:
|
||||
|
||||
- :cpp:member:`on_alarm` sets callback function for alarm event. As this function is called within the ISR context, user must ensure that the function doesn't attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The function prototype is declared in :cpp:type:`gptimer_alarm_cb_t`.
|
||||
|
||||
One can save his own context to :cpp:func:`gptimer_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to the callback functions.
|
||||
|
||||
Start and Stop Timer
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To start a timer means to enable its internal counter, it can only be achieved by calling :cpp:func:`gptimer_start`. The timer can be stopped at any time (even in the interrupt context) by :cpp:func:`gptimer_stop`. One thing should be kept in mind, calling of :cpp:func:`gptimer_start` should have the same times of calling :cpp:func:`gptimer_stop` before you delete the timer, otherwise the driver might be put in an undetermined state. For example, the timer might keep a Power Management lock, which in return increase the power consumption. Also see `Power management <#power-management>`__ section.
|
||||
|
||||
Start Timer As a Wall Clock
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: c
|
||||
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
// Retrieve timestamp at anytime
|
||||
uint64_t count;
|
||||
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
|
||||
|
||||
Trigger Period Events
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: c
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Retrieve count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
// Don't introduce complex logics in callbacks.
|
||||
// Suggest dealing with event data in the main loop, instead of in this callback.
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0, // counter will reload with 0 on alarm event
|
||||
.alarm_count = 1000000, // period = 1s @resolution 1MHz
|
||||
.flags.auto_reload_on_alarm = true, // enable auto-reload
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
Trigger One-Shot Event
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: c
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// Stop timer the sooner the better
|
||||
gptimer_stop(timer);
|
||||
// Retrieve count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
Dynamic Alarm Update
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Alarm value can be updated dynamically inside the ISR handler callback, by changing the :cpp:member:`alarm_value` of :cpp:type:`gptimer_alarm_event_data_t`. Then the alarm value will be updated after the callback function returns.
|
||||
|
||||
.. code:: c
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
|
||||
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// Retrieve count value from event data
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
// Optional: send the event data to other task by OS queue
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// reconfigure alarm value
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
|
||||
};
|
||||
gptimer_set_alarm_action(timer, &alarm_config);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return high_task_awoken == pdTRUE;
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb, // register user callback
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));
|
||||
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When power management is enabled (i.e. ``CONFIG_PM_ENABLE`` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the period of a GPTimer's counting step and leading to inaccurate time keeping.
|
||||
|
||||
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :c:member:`ESP_PM_APB_FREQ_MAX`. Whenever the driver creates a GPTimer instance that has selected :c:member:`GPTIMER_CLK_SRC_APB` as its clock source, the driver will guarantee that the power management lock is acquired when the timer is started by :cpp:func:`gptimer_start`. Likewise, the driver releases the lock when :cpp:func:`gptimer_stop` is called for that timer. This requires that the :cpp:func:`gptimer_start` and :cpp:func:`gptimer_stop` should appear in pairs.
|
||||
|
||||
IRAM Safe
|
||||
^^^^^^^^^
|
||||
|
||||
By default, the GPTimer interrupt will be deferred when the Cache is disabled for reasons like writing/erasing Flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
|
||||
|
||||
There's a Kconfig option :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` that will:
|
||||
|
||||
1. Enable the interrupt being serviced even when cache is disabled
|
||||
|
||||
2. Place all functions that used by the ISR into IRAM [2]_
|
||||
|
||||
3. Place driver object into DRAM (in case it's linked to PSRAM by accident)
|
||||
|
||||
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
|
||||
|
||||
There's another Kconfig option :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are as follows:
|
||||
|
||||
- :cpp:func:`gptimer_start`
|
||||
- :cpp:func:`gptimer_stop`
|
||||
- :cpp:func:`gptimer_get_raw_count`
|
||||
- :cpp:func:`gptimer_set_raw_count`
|
||||
- :cpp:func:`gptimer_set_alarm_action`
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
Typical use cases of GPTimer are listed in the example: :example:`peripherals/timer_group/gptimer`.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/gptimer.inc
|
||||
.. include-build-file:: inc/timer_types.inc
|
||||
|
||||
.. [1]
|
||||
Some ESP chip might only support a sub-set of the clocks, if an unsupported clock source is specified, you will get a runtime error during timer installation.
|
||||
|
||||
.. [2]
|
||||
:cpp:member:`on_alarm` callback and the functions invoked by itself should also be placed in IRAM, users need to take care of them by themselves.
|
@ -8,8 +8,8 @@ Peripherals API
|
||||
|
||||
adc
|
||||
:SOC_DAC_SUPPORTED: dac
|
||||
timer
|
||||
gpio
|
||||
gptimer
|
||||
:SOC_DEDICATED_GPIO_SUPPORTED: dedic_gpio
|
||||
:SOC_HMAC_SUPPORTED: hmac
|
||||
:SOC_DIG_SIGN_SUPPORTED: ds
|
||||
|
@ -224,7 +224,7 @@ Application Example
|
||||
MCPWM example are located under: :example:`peripherals/mcpwm`:
|
||||
|
||||
* Control of BLDC (brushless DC) motor with hall sensor feedback - :example:`peripherals/mcpwm/mcpwm_bldc_hall_control`
|
||||
* Brushed DC motor control - :example:`peripherals/mcpwm/mcpwm_brushed_dc_control`
|
||||
* Brushed DC motor control - :example:`peripherals/mcpwm/mcpwm_bdc_speed_control`
|
||||
* Servo motor control - :example:`peripherals/mcpwm/mcpwm_servo_control`
|
||||
* HC-SR04 sensor with capture - :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04`
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
General Purpose Timer
|
||||
=====================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
{IDF_TARGET_TIMER_COUNTER_BIT_WIDTH:default="54", esp32="64", esp32s2="64"}
|
||||
{IDF_TARGET_TIMER_GROUPS:default="two", esp8684="one"}
|
||||
{IDF_TARGET_TIMERS_PER_GROUP:default="two", esp32c3="one", esp8684="one"}
|
||||
{IDF_TARGET_TIMERS_TOTAL:default="four", esp32c3="two", esp8684="one"}
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The {IDF_TARGET_NAME} chip contains {IDF_TARGET_TIMER_GROUPS} hardware timer group(s). Each group has {IDF_TARGET_TIMERS_PER_GROUP} general-purpose hardware timer(s). They are all {IDF_TARGET_TIMER_COUNTER_BIT_WIDTH}-bit generic timers based on 16-bit pre-scalers and {IDF_TARGET_TIMER_COUNTER_BIT_WIDTH}-bit up / down counters which are capable of being auto-reloaded.
|
||||
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
|
||||
The following sections of this document cover the typical steps to configure and operate a timer:
|
||||
|
||||
* :ref:`timer-api-timer-initialization` - covers which parameters should be set up to get the timer working, and also what specific functionality is provided depending on the timer configuration.
|
||||
* :ref:`timer-api-timer-control` - describes how to read a timer's value, pause or start a timer, and change how it operates.
|
||||
* :ref:`timer-api-alarms` - shows how to set and use alarms.
|
||||
* :ref:`timer-api-interrupts`- explains how to use interrupt callbacks.
|
||||
|
||||
|
||||
.. _timer-api-timer-initialization:
|
||||
|
||||
Timer Initialization
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The {IDF_TARGET_TIMER_GROUPS} {IDF_TARGET_NAME} timer group(s), with {IDF_TARGET_TIMERS_PER_GROUP} timer(s) in each, provide the total of {IDF_TARGET_TIMERS_TOTAL} individual timers for use. An {IDF_TARGET_NAME} timer group should be identified using :cpp:type:`timer_group_t`. An individual timer in a group should be identified with :cpp:type:`timer_idx_t`.
|
||||
|
||||
First of all, the timer should be initialized by calling the function :cpp:func:`timer_init` and passing a structure :cpp:type:`timer_config_t` to it to define how the timer should operate. In particular, the following timer parameters can be set:
|
||||
|
||||
.. list::
|
||||
|
||||
:not esp32: - **Clock Source**: Select the clock source, which together with the **Divider** define the resolution of the working timer.
|
||||
- **Divider**: Sets how quickly the timer's counter is "ticking". The setting :cpp:member:`divider` is used as a divisor of the clock source that by default is APB_CLK running at 80 MHz. For more information of APB_CLK frequency, please check *{IDF_TARGET_NAME} Technical Reference Manual* > *Reset and Clock* [`PDF <{IDF_TARGET_TRM_EN_URL}#resclk>`__] chapter for more details.
|
||||
- **Mode**: Sets if the counter should be incrementing or decrementing. It can be defined using :cpp:member:`counter_dir` by selecting one of the values from :cpp:type:`timer_count_dir_t`.
|
||||
- **Counter Enable**: If the counter is enabled, it will start incrementing / decrementing immediately after calling :cpp:func:`timer_init`. You can change the behavior with :cpp:member:`counter_en` by selecting one of the values from :cpp:type:`timer_start_t`.
|
||||
- **Alarm Enable**: Can be set using :cpp:member:`alarm_en`.
|
||||
- **Auto Reload**: Sets if the counter should :cpp:member:`auto_reload` the initial counter value on the timer's alarm or continue incrementing or decrementing.
|
||||
|
||||
To get the current values of the timer's settings, use the function :cpp:func:`timer_get_config`.
|
||||
|
||||
|
||||
.. _timer-api-timer-control:
|
||||
|
||||
Timer Control
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Once the timer is enabled, its counter starts running. To enable the timer, call the function :cpp:func:`timer_init` with :cpp:member:`counter_en` set to ``true``, or call :cpp:func:`timer_start`. You can specify the timer's initial counter value by calling :cpp:func:`timer_set_counter_value`. To check the timer's current value, call :cpp:func:`timer_get_counter_value` or :cpp:func:`timer_get_counter_time_sec`.
|
||||
|
||||
To pause the timer at any time, call :cpp:func:`timer_pause`. To resume it, call :cpp:func:`timer_start`.
|
||||
|
||||
To reconfigure the timer, you can call :cpp:func:`timer_init`. This function is described in Section :ref:`timer-api-timer-initialization`.
|
||||
|
||||
You can also reconfigure the timer by using dedicated functions to change individual settings:
|
||||
|
||||
============= =================================== ==========================================================================
|
||||
Setting Dedicated Function Description
|
||||
============= =================================== ==========================================================================
|
||||
Divider :cpp:func:`timer_set_divider` Change the rate of ticking. To avoid unpredictable results, the timer should be paused when changing the divider. If the timer is running, :cpp:func:`timer_set_divider` pauses it, change the setting, and start the timer again.
|
||||
Mode :cpp:func:`timer_set_counter_mode` Set if the counter should be incrementing or decrementing
|
||||
Auto Reload :cpp:func:`timer_set_auto_reload` Set if the initial counter value should be reloaded on the timer's alarm
|
||||
============= =================================== ==========================================================================
|
||||
|
||||
.. _timer-api-alarms:
|
||||
|
||||
Alarms
|
||||
^^^^^^
|
||||
|
||||
To set an alarm, call the function :cpp:func:`timer_set_alarm_value` and then enable the alarm using :cpp:func:`timer_set_alarm`. The alarm can also be enabled during the timer initialization stage, when :cpp:func:`timer_init` is called.
|
||||
|
||||
After the alarm is enabled, and the timer reaches the alarm value, the following two actions can occur depending on the configuration:
|
||||
|
||||
* An interrupt will be triggered if previously configured. See Section :ref:`timer-api-interrupts` on how to configure interrupts.
|
||||
* When :cpp:member:`auto_reload` is enabled, the timer's counter will automatically be reloaded to start counting again from a previously configured value. This value should be set in advance with :cpp:func:`timer_set_counter_value`.
|
||||
|
||||
.. note::
|
||||
|
||||
* If an alarm value is set and the timer has already reached this value, the alarm is triggered immediately.
|
||||
* Once triggered, the alarm is disabled automatically and needs to be re-enabled to trigger again.
|
||||
|
||||
To check the specified alarm value, call :cpp:func:`timer_get_alarm_value`.
|
||||
|
||||
|
||||
.. _timer-api-interrupts:
|
||||
|
||||
Interrupts
|
||||
^^^^^^^^^^
|
||||
|
||||
Registration of an interrupt callback for a specific timer can be done by calling :cpp:func:`timer_isr_callback_add` and passing in the group ID, timer ID, callback handler and user data. The callback handler will be invoked in ISR context, so user shouldn't put any blocking API in the callback function.
|
||||
|
||||
The benefit of using interrupt callback instead of precessing interrupt from scratch is, you don't have to deal with interrupt status check and clean stuffs, they are all addressed before the callback got run in driver's default interrupt handler.
|
||||
|
||||
For more information on how to use interrupts, please see the application example below.
|
||||
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
The {IDF_TARGET_TIMER_COUNTER_BIT_WIDTH}-bit hardware timer example: :example:`peripherals/timer_group`.
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/timer.inc
|
||||
.. include-build-file:: inc/timer_types.inc
|
@ -103,6 +103,7 @@ The following peripherals work normally even when the APB frequency is changing:
|
||||
- **UART**: if REF_TICK is used as a clock source. See `use_ref_tick` member of :cpp:class:`uart_config_t`.
|
||||
- **LEDC**: if REF_TICK is used as a clock source. See :cpp:func:`ledc_timer_config` function.
|
||||
- **RMT**: if REF_TICK or XTAL is used as a clock source. See `flags` member of :cpp:class:`rmt_config_t` and macro `RMT_CHANNEL_FLAGS_AWARE_DFS`.
|
||||
- **GPTimer**: if APB is used as the clock source. See :c:member:`clk_src` member of :c:type:`gptimer_config_t`.
|
||||
|
||||
Currently, the following peripheral drivers are aware of DFS and will use the ``ESP_PM_APB_FREQ_MAX`` lock for the duration of the transaction:
|
||||
|
||||
@ -128,7 +129,7 @@ The following peripheral drivers are not aware of DFS yet. Applications need to
|
||||
|
||||
- PCNT
|
||||
- Sigma-delta
|
||||
- Timer group
|
||||
- The legacy timer group driver (note, the new :doc:`GPTimer <../peripherals/gptimer>` will hold the ``ESP_PM_APB_FREQ_MAX`` lock while the timer is working, if the clock source is set to APB)
|
||||
:SOC_MCPWM_SUPPORTED: - MCPWM
|
||||
|
||||
API Reference
|
||||
|
@ -26,3 +26,30 @@ GPIO
|
||||
----
|
||||
|
||||
The previous Kconfig option `RTCIO_SUPPORT_RTC_GPIO_DESC` has been removed, thus the ``rtc_gpio_desc`` array is unavailable. Please use ``rtc_io_desc`` array instead.
|
||||
|
||||
Timer Group Driver
|
||||
------------------
|
||||
|
||||
Timer Group driver has been redesigned into :doc:`GPTimer <../api-reference/peripherals/gptimer>`, which aims to unify and simplify the usage of general purpose timer.
|
||||
Although it's recommended to use the the new driver APIs, the legacy driver is till available in the previous include path ``driver/timer.h``. However, by default, including ``driver/timer.h`` will bring a build warning like "legacy timer group driver is deprecated, please migrate to driver/gptimer.h". The warning can be suppressed by the Kconfig option :ref:`CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN`.
|
||||
|
||||
The major breaking changes in concept and usage are listed as follows:
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``timer_group_t`` and ``timer_idx_t`` which used to identify the hardware timer are removed from user's code. In the new driver, a timer is represented by :cpp:type:`gptimer_handle_t`.
|
||||
- Definition of timer source clock is moved to :cpp:type:`gptimer_clock_source_t`, the previous ``timer_src_clk_t`` is not used.
|
||||
- Definition of timer count direction is moved to :cpp:type:`gptimer_count_direction_t`, the previous ``timer_count_dir_t`` is not used.
|
||||
- Only level interrupt is supported, ``timer_intr_t`` and ``timer_intr_mode_t`` are not used.
|
||||
- Auto-reload is enabled by set the :cpp:member:`auto_reload_on_alarm` flag. ``timer_autoreload_t`` is not used.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Timer initialization is done by creating a timer instance from :cpp:func:`gptimer_new_timer`. Basic configurations like clock source, resolution and direction should be set in :cpp:type:`gptimer_config_t`. Note that, alarm event specific configurations are not needed during the driver install stage.
|
||||
- Alarm event is configured by :cpp:func:`gptimer_set_alarm_action`, with parameters set in the :cpp:type:`gptimer_alarm_config_t`.
|
||||
- Setting and getting count value are done by :cpp:func:`gptimer_get_raw_count` and :cpp:func:`gptimer_set_raw_count`. The driver doesn't help convert the raw value into UTC time-stamp. Instead, the conversion should be done form user's side as the timer resolution is also known to the user.
|
||||
- The driver will install the interrupt service as well if :cpp:member:`on_alarm` is set to a valid callback function. In the callback, user doesn't have to deal with the low level registers (like "clear interrupt status", "re-enable alarm event" and so on). So functions like ``timer_group_get_intr_status_in_isr`` and ``timer_group_get_auto_reload_in_isr`` are not used anymore.
|
||||
- To update the alarm configurations when alarm event happens, one can call :cpp:func:`gptimer_set_alarm_action` in the interrupt callback, then the alarm will be re-enabled again.
|
||||
- Alarm will always be re-enabled by the driver if :cpp:member:`auto_reload_on_alarm` is set to true.
|
||||
|
@ -46,3 +46,8 @@ SOC dependency
|
||||
|
||||
- Public API headers who are listed in the Doxyfiles won't expose unstable and unnecessary soc header files like ``soc/soc.h``, ``soc/rtc.h``. That means, the user has to explicitly include them in their code if these "missing" header files are still wanted.
|
||||
- Kconfig option ``LEGACY_INCLUDE_COMMON_HEADERS`` is also removed.
|
||||
|
||||
APP Trace
|
||||
---------
|
||||
|
||||
One of the timestamp sources has changed from the legacy timer group driver to the new :doc:`GPTimer <../api-reference/peripherals/gptimer>`. Kconfig choices like ``APPTRACE_SV_TS_SOURCE_TIMER00`` has been changed to ``APPTRACE_SV_TS_SOURCE_GPTIMER``. User doesn't need to choose the group and timer ID any more.
|
||||
|
1
docs/zh_CN/api-reference/peripherals/gptimer.rst
Normal file
1
docs/zh_CN/api-reference/peripherals/gptimer.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../en/api-reference/peripherals/gptimer.rst
|
@ -8,8 +8,8 @@
|
||||
|
||||
adc
|
||||
:SOC_DAC_SUPPORTED: dac
|
||||
timer
|
||||
gpio
|
||||
gptimer
|
||||
:SOC_DEDICATED_GPIO_SUPPORTED: dedic_gpio
|
||||
:SOC_HMAC_SUPPORTED: hmac
|
||||
:SOC_DIG_SIGN_SUPPORTED: ds
|
||||
|
@ -1,111 +0,0 @@
|
||||
通用定时器
|
||||
==========
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
{IDF_TARGET_TIMER_COUNTER_BIT_WIDTH:default="54", esp32="64", esp32s2="64"}
|
||||
{IDF_TARGET_TIMERS_PER_GROUP:default="2", esp32c3="1"}
|
||||
{IDF_TARGET_TIMERS_TOTAL:default="4", esp32c3="2"}
|
||||
|
||||
简介
|
||||
----
|
||||
|
||||
{IDF_TARGET_NAME} 芯片提供两组硬件定时器。每组包含 {IDF_TARGET_TIMERS_PER_GROUP} 个通用硬件定时器。这些 {IDF_TARGET_TIMER_COUNTER_BIT_WIDTH} 位通用定时器均基于 16 位预分频器和 {IDF_TARGET_TIMER_COUNTER_BIT_WIDTH} 位可自动重新加载向上/向下计数器。
|
||||
|
||||
|
||||
功能概述
|
||||
--------
|
||||
|
||||
下文介绍了配置和操作定时器的常规步骤:
|
||||
|
||||
* :ref:`timer-api-timer-initialization` - 启动定时器前应设置的参数,以及每个设置提供的具体功能。
|
||||
* :ref:`timer-api-timer-control` - 如何读取定时器的值,如何暂停/启动定时器以及如何改变定时器的操作方式。
|
||||
* :ref:`timer-api-alarms` - 如何设置和使用警报。
|
||||
* :ref:`timer-api-interrupts`- 如何使用中断提供的回调函数。
|
||||
|
||||
|
||||
.. _timer-api-timer-initialization:
|
||||
|
||||
定时器初始化
|
||||
^^^^^^^^^^^^
|
||||
|
||||
两个 {IDF_TARGET_NAME} 定时器组中,每组都有 {IDF_TARGET_TIMERS_PER_GROUP} 个定时器,两组共有 {IDF_TARGET_TIMERS_TOTAL} 个定时器供使用。可使用 :cpp:type:`timer_group_t` 查看 {IDF_TARGET_NAME} 定时器组的类型,使用 :cpp:type:`timer_idx_t` 查看每组中的个体定时器类型。
|
||||
|
||||
首先调用 :cpp:func:`timer_init` 函数,并将 :cpp:type:`timer_config_t` 结构体传递给此函数,用于定义定时器的工作方式,实现定时器初始化。特别注意设置以下定时器参数:
|
||||
|
||||
.. list::
|
||||
|
||||
:not esp32: - **时钟源**: 选择时钟源,与时钟分频器一起决定了定时器的分辨率。
|
||||
- **分频器**: 设置定时器中计数器计数的速度,:cpp:member:`divider` 的设置将用作输入时钟源的除数。默认的时钟源是 APB_CLK (一般是 80 MHz)。更多有关 APB_CLK 时钟频率信息,请查看 *{IDF_TARGET_NAME} 技术参考手册* > *复位和时钟* [`PDF <{IDF_TARGET_TRM_CN_URL}#resclk>`__] 章节。
|
||||
- **模式**: 设置计数器是递增还是递减。可通过从 :cpp:type:`timer_count_dir_t` 中选取一个值,然后使用 :cpp:member:`counter_dir` 来选择模式。
|
||||
- **计数器使能**: 如果计数器已使能,在调用 :cpp:func:`timer_init` 后计数器将立即开始递增/递减。您可通过从 :cpp:type:`timer_start_t` 中选取一个值,然后使用 :cpp:member:`counter_en` 改变此行为。
|
||||
- **报警使能**: 可使用 :cpp:member:`alarm_en` 设置。
|
||||
- **自动重载**: 设置计数器是否应该在定时器警报上使用 :cpp:member:`auto_reload` 自动重载首个计数值,还是继续递增或递减。
|
||||
|
||||
要获取定时器设置的当前值,请使用函数 :cpp:func:`timer_get_config`。
|
||||
|
||||
|
||||
.. _timer-api-timer-control:
|
||||
|
||||
定时器控制
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
定时器使能后便开始计数。要使能定时器,可首先设置 :cpp:member:`counter_en` 为 ``true``,然后调用函数 :cpp:func:`timer_init`,或者直接调用函数 :cpp:func:`timer_start`。您可通过调用函数 :cpp:func:`timer_set_counter_value` 来指定定时器的首个计数值。要检查定时器的当前值,调用函数 :cpp:func:`timer_get_counter_value` 或 :cpp:func:`timer_get_counter_time_sec`。
|
||||
|
||||
可通过调用函数 :cpp:func:`timer_pause` 随时暂停定时器。若要再次启动它,可调用函数 :cpp:func:`timer_start`。
|
||||
|
||||
要重新配置定时器,可调用函数 :cpp:func:`timer_init`,该函数详细介绍见 :ref:`timer-api-timer-initialization`。
|
||||
|
||||
除此之外,还可通过使用专有函数更改个别设置来重新配置定时器:
|
||||
|
||||
============= =================================== ==========================================================================
|
||||
设置 专有函数 描述
|
||||
============= =================================== ==========================================================================
|
||||
分频器 :cpp:func:`timer_set_divider` 更改计数频率。为避免发生不可预测情况,更改分频器时应暂停定时器。如果定时器正在运行,则使用 :cpp:func:`timer_set_divider` 将其暂停并更改设置,然后重启定时器。
|
||||
模式 :cpp:func:`timer_set_counter_mode` 设置计数器应递增还是递减
|
||||
自动重载 :cpp:func:`timer_set_auto_reload` 设置是否应在定时器警报上重载首个计数值
|
||||
============= =================================== ==========================================================================
|
||||
|
||||
.. _timer-api-alarms:
|
||||
|
||||
警报
|
||||
^^^^^^
|
||||
|
||||
要设置警报,先调用函数 :cpp:func:`timer_set_alarm_value`,然后使用 :cpp:func:`timer_set_alarm` 使能警报。当调用函数 :cpp:func:`timer_init` 时,也可以在定时器初始化阶段使能警报。
|
||||
|
||||
警报已使能且定时器达到警报值后,根据配置,可能会出现以下两种行为:
|
||||
|
||||
* 如果先前已配置,此时将触发中断。有关如何配置中断,请参见 :ref:`timer-api-interrupts`。
|
||||
* 如 :cpp:member:`auto_reload` 已使能,定时器的计数器将重新加载,从先前配置好的值开始再次计数。应使用函数 :cpp:func:`timer_set_counter_value` 预先设置该值。
|
||||
|
||||
.. note::
|
||||
|
||||
* 如果已设置警报值且定时器已超过该值,则将立即触发警报。
|
||||
* 一旦触发后,警报将自动关闭,需要重新使能以再次触发。
|
||||
|
||||
要检查某特定的警报值,调用函数 :cpp:func:`timer_get_alarm_value`。
|
||||
|
||||
|
||||
.. _timer-api-interrupts:
|
||||
|
||||
处理中断事务
|
||||
^^^^^^^^^^^^
|
||||
|
||||
通过调用 :cpp:func:`timer_isr_callback_add` 函数并向该函数传递组 ID、定时器 ID、回调处理程序以及用户数据,可以给某个定时器注册一个中断回调函数。回调处理程序会在 ISR 上下文中调用,因此用户不能在回调函数中放置任何会阻塞 CPU 的 API。
|
||||
|
||||
相较于从头编写中断处理程序,使用中断回调函数的好处是,用户无需检测和处理中断的状态位,这些操作会由驱动中默认的中断处理程序替我们完成。
|
||||
|
||||
有关如何使用中断回调函数,请参考如下应用示例。
|
||||
|
||||
|
||||
应用示例
|
||||
--------
|
||||
|
||||
{IDF_TARGET_TIMER_COUNTER_BIT_WIDTH} 位通用硬件定时器示例::example:`peripherals/timer_group`。
|
||||
|
||||
|
||||
API 参考
|
||||
--------
|
||||
|
||||
.. include-build-file:: inc/timer.inc
|
||||
.. include-build-file:: inc/timer_types.inc
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -7,16 +7,12 @@
|
||||
#ifndef __IOT_LED_H__
|
||||
#define __IOT_LED_H__
|
||||
|
||||
#include "driver/ledc.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/ledc.h"
|
||||
|
||||
#define HW_TIMER_GROUP (0) /**< Hardware timer group */
|
||||
#define HW_TIMER_ID (0) /**< Hardware timer number */
|
||||
#define HW_TIMER_DIVIDER (16) /**< Hardware timer clock divider */
|
||||
#define HW_TIMER_SCALE (TIMER_BASE_CLK / HW_TIMER_DIVIDER) /**< Convert counter value to seconds */
|
||||
#define GAMMA_CORRECTION 0.8 /**< Gamma curve parameter */
|
||||
#define GAMMA_TABLE_SIZE 256 /**< Gamma table size, used for led fade*/
|
||||
#define DUTY_SET_CYCLE (20) /**< Set duty cycle */
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -7,26 +7,14 @@
|
||||
#ifndef __IOT_LIGHT_H__
|
||||
#define __IOT_LIGHT_H__
|
||||
|
||||
#include "driver/ledc.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/ledc.h"
|
||||
|
||||
/********************************** NOTE *********************************/
|
||||
/* When we create a light object, a hardware timer will be enabled, this */
|
||||
/* timer is used to realize fade and blink operation. The default timer */
|
||||
/* occupied is timer 0 of timer group 0, user can change this config in */
|
||||
/* menuconfig. */
|
||||
/*************************************************************************/
|
||||
|
||||
typedef void *light_handle_t;
|
||||
|
||||
#define HW_TIMER_GROUP (0) /**< Hardware timer group */
|
||||
#define HW_TIMER_ID (0) /**< Hardware timer number */
|
||||
#define HW_TIMER_DIVIDER (16) /**< Hardware timer clock divider */
|
||||
#define HW_TIMER_SCALE (TIMER_BASE_CLK / HW_TIMER_DIVIDER) /**< Convert counter value to seconds */
|
||||
|
||||
#define DUTY_SET_CYCLE (20) /**< Set duty cycle */
|
||||
#define DUTY_SET_GAMMA (0.6) /**< Set the Gamma value for the fade curve, default value is 0.6 */
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -7,13 +7,12 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "errno.h"
|
||||
|
||||
#include "math.h"
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "soc/ledc_reg.h"
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/ledc_struct.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "iot_led.h"
|
||||
#include "esp_log.h"
|
||||
@ -27,6 +26,8 @@
|
||||
#define GET_FIXED_INTEGER_PART(X, Q) (X >> Q)
|
||||
#define GET_FIXED_DECIMAL_PART(X, Q) (X & ((0x1U << Q) - 1))
|
||||
|
||||
#define GPTIMER_RESOLUTION_HZ 1000000 // 1MHz, 1 tick=1us
|
||||
|
||||
typedef struct {
|
||||
int cur;
|
||||
int final;
|
||||
@ -35,16 +36,11 @@ typedef struct {
|
||||
size_t num;
|
||||
} ledc_fade_data_t;
|
||||
|
||||
typedef struct {
|
||||
timer_group_t timer_group;
|
||||
timer_idx_t timer_id;
|
||||
} hw_timer_idx_t;
|
||||
|
||||
typedef struct {
|
||||
ledc_fade_data_t fade_data[LEDC_CHANNEL_MAX];
|
||||
ledc_mode_t speed_mode;
|
||||
ledc_timer_t timer_num;
|
||||
hw_timer_idx_t timer_id;
|
||||
gptimer_handle_t gptimer;
|
||||
} iot_light_t;
|
||||
|
||||
static const char *TAG = "iot_light";
|
||||
@ -52,40 +48,27 @@ static DRAM_ATTR iot_light_t *g_light_config = NULL;
|
||||
static DRAM_ATTR uint16_t *g_gamma_table = NULL;
|
||||
static DRAM_ATTR bool g_hw_timer_started = false;
|
||||
|
||||
static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload,
|
||||
uint32_t timer_interval_ms, void *isr_handle)
|
||||
static IRAM_ATTR bool fade_timercb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
|
||||
|
||||
static void iot_timer_start(gptimer_handle_t gptimer)
|
||||
{
|
||||
/* Select and initialize basic parameters of the timer */
|
||||
timer_config_t config = {
|
||||
.divider = HW_TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.auto_reload = auto_reload,
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = DUTY_SET_CYCLE / 1000 * GPTIMER_RESOLUTION_HZ,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
timer_init(timer_id->timer_group, timer_id->timer_id, &config);
|
||||
|
||||
/* Timer's counter will initially start from value below.
|
||||
Also, if auto_reload is set, this value will be automatically reload on alarm */
|
||||
timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);
|
||||
|
||||
/* Configure the alarm value and the interrupt on alarm. */
|
||||
timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_ms * HW_TIMER_SCALE / 1000);
|
||||
timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
|
||||
timer_isr_register(timer_id->timer_group, timer_id->timer_id, isr_handle,
|
||||
(void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
|
||||
}
|
||||
|
||||
static void iot_timer_start(hw_timer_idx_t *timer_id)
|
||||
{
|
||||
timer_start(timer_id->timer_group, timer_id->timer_id);
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = fade_timercb,
|
||||
};
|
||||
gptimer_register_event_callbacks(gptimer, &cbs, NULL);
|
||||
gptimer_set_alarm_action(gptimer, &alarm_config);
|
||||
gptimer_start(gptimer);
|
||||
g_hw_timer_started = true;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void iot_timer_stop(hw_timer_idx_t *timer_id)
|
||||
static IRAM_ATTR void iot_timer_stop(gptimer_handle_t gptimer)
|
||||
{
|
||||
timer_group_set_counter_enable_in_isr(timer_id->timer_group, timer_id->timer_id, TIMER_PAUSE);
|
||||
gptimer_stop(gptimer);
|
||||
g_hw_timer_started = false;
|
||||
}
|
||||
|
||||
@ -265,18 +248,10 @@ static IRAM_ATTR uint32_t gamma_value_to_duty(int value)
|
||||
return (cur + (next - cur) * tmp_r / (0x1U << LEDC_FIXED_Q));
|
||||
}
|
||||
|
||||
static IRAM_ATTR void fade_timercb(void *para)
|
||||
static IRAM_ATTR bool fade_timercb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
int timer_idx = (int) para;
|
||||
int idle_channel_num = 0;
|
||||
|
||||
/* Retrieve the interrupt status */
|
||||
timer_group_get_intr_status_in_isr(HW_TIMER_GROUP);
|
||||
timer_group_clr_intr_status_in_isr(HW_TIMER_GROUP, timer_idx);
|
||||
/* After the alarm has been triggered
|
||||
we need enable it again, so it is triggered the next time */
|
||||
timer_group_enable_alarm_in_isr(HW_TIMER_GROUP, timer_idx);
|
||||
|
||||
for (int channel = 0; channel < LEDC_CHANNEL_MAX; channel++) {
|
||||
ledc_fade_data_t *fade_data = g_light_config->fade_data + channel;
|
||||
|
||||
@ -320,8 +295,10 @@ static IRAM_ATTR void fade_timercb(void *para)
|
||||
}
|
||||
|
||||
if (idle_channel_num >= LEDC_CHANNEL_MAX) {
|
||||
iot_timer_stop(&g_light_config->timer_id);
|
||||
iot_timer_stop(timer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t iot_led_init(ledc_timer_t timer_num, ledc_mode_t speed_mode, uint32_t freq_hz)
|
||||
@ -352,13 +329,12 @@ esp_err_t iot_led_init(ledc_timer_t timer_num, ledc_mode_t speed_mode, uint32_t
|
||||
g_light_config->timer_num = timer_num;
|
||||
g_light_config->speed_mode = speed_mode;
|
||||
|
||||
|
||||
hw_timer_idx_t hw_timer = {
|
||||
.timer_group = HW_TIMER_GROUP,
|
||||
.timer_id = HW_TIMER_ID,
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = GPTIMER_RESOLUTION_HZ,
|
||||
};
|
||||
g_light_config->timer_id = hw_timer;
|
||||
iot_timer_create(&hw_timer, 1, DUTY_SET_CYCLE, fade_timercb);
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &g_light_config->gptimer));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "g_light_config has been initialized");
|
||||
}
|
||||
@ -372,12 +348,12 @@ esp_err_t iot_led_deinit(void)
|
||||
free(g_gamma_table);
|
||||
}
|
||||
|
||||
|
||||
if (g_light_config) {
|
||||
gptimer_del_timer(g_light_config->gptimer);
|
||||
free(g_light_config);
|
||||
}
|
||||
|
||||
timer_disable_intr(g_light_config->timer_id.timer_group, g_light_config->timer_id.timer_id);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@ -449,7 +425,7 @@ esp_err_t iot_led_set_channel(ledc_channel_t channel, uint8_t value, uint32_t fa
|
||||
}
|
||||
|
||||
if (g_hw_timer_started != true) {
|
||||
iot_timer_start(&g_light_config->timer_id);
|
||||
iot_timer_start(g_light_config->gptimer);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@ -469,7 +445,7 @@ esp_err_t iot_led_start_blink(ledc_channel_t channel, uint8_t value, uint32_t pe
|
||||
fade_data->step = (fade_flag) ? fade_data->cur / fade_data->num * -1 : 0;
|
||||
|
||||
if (g_hw_timer_started != true) {
|
||||
iot_timer_start(&g_light_config->timer_id);
|
||||
iot_timer_start(g_light_config->gptimer);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
@ -1,12 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "math.h"
|
||||
#include "sys/time.h"
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
@ -14,7 +14,7 @@
|
||||
#include "soc/ledc_reg.h"
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/ledc_struct.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "iot_light.h"
|
||||
|
||||
@ -27,6 +27,8 @@ static const char *TAG = "light";
|
||||
#define POINT_ASSERT(tag, param) IOT_CHECK(tag, (param) != NULL, ESP_FAIL)
|
||||
#define LIGHT_NUM_MAX 4
|
||||
|
||||
#define GPTIMER_RESOLUTION_HZ 1000000 // 1MHz, 1 tick=1us
|
||||
|
||||
typedef enum {
|
||||
LIGHT_CH_NUM_1 = 1, /*!< Light channel number */
|
||||
LIGHT_CH_NUM_2 = 2, /*!< Light channel number */
|
||||
@ -36,11 +38,6 @@ typedef enum {
|
||||
LIGHT_CH_NUM_MAX, /*!< user shouldn't use this */
|
||||
} light_channel_num_t;
|
||||
|
||||
typedef struct {
|
||||
timer_group_t timer_group;
|
||||
timer_idx_t timer_id;
|
||||
} hw_timer_idx_t;
|
||||
|
||||
typedef struct {
|
||||
gpio_num_t io_num;
|
||||
ledc_mode_t mode;
|
||||
@ -62,8 +59,8 @@ typedef struct {
|
||||
uint32_t full_duty;
|
||||
uint32_t freq_hz;
|
||||
ledc_timer_bit_t timer_bit;
|
||||
hw_timer_idx_t hw_timer;
|
||||
light_channel_t *channel_group[0];
|
||||
gptimer_handle_t gptimer;
|
||||
light_channel_t *channel_group[];
|
||||
} light_t;
|
||||
|
||||
static bool g_fade_installed = false;
|
||||
@ -71,43 +68,6 @@ static bool g_hw_timer_started = false;
|
||||
static light_t *g_light_group[LIGHT_NUM_MAX] = {NULL};
|
||||
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty);
|
||||
|
||||
static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload, double timer_interval_sec, timer_isr_handle_t *isr_handle)
|
||||
{
|
||||
/* Select and initialize basic parameters of the timer */
|
||||
timer_config_t config = {
|
||||
.divider = HW_TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.auto_reload = auto_reload,
|
||||
};
|
||||
timer_init(timer_id->timer_group, timer_id->timer_id, &config);
|
||||
|
||||
/* Timer's counter will initially start from value below.
|
||||
Also, if auto_reload is set, this value will be automatically reload on alarm */
|
||||
timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);
|
||||
|
||||
/* Configure the alarm value and the interrupt on alarm. */
|
||||
timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_sec * HW_TIMER_SCALE);
|
||||
timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
|
||||
timer_isr_register(timer_id->timer_group, timer_id->timer_id, (void *)isr_handle,
|
||||
(void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
|
||||
}
|
||||
|
||||
static void iot_timer_start(hw_timer_idx_t *timer_id)
|
||||
{
|
||||
timer_start(timer_id->timer_group, timer_id->timer_id);
|
||||
g_hw_timer_started = true;
|
||||
}
|
||||
|
||||
static void iot_timer_stop(hw_timer_idx_t *timer_id)
|
||||
{
|
||||
timer_disable_intr(timer_id->timer_group, timer_id->timer_id);
|
||||
timer_pause(timer_id->timer_group, timer_id->timer_id);
|
||||
g_hw_timer_started = false;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void iot_ledc_ls_channel_update(ledc_mode_t speed_mode, ledc_channel_t channel_num)
|
||||
{
|
||||
if (speed_mode == LEDC_LOW_SPEED_MODE) {
|
||||
@ -155,17 +115,8 @@ static IRAM_ATTR esp_err_t iot_ledc_update_duty(ledc_mode_t speed_mode, ledc_cha
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static IRAM_ATTR void breath_timer_callback(void *para)
|
||||
static IRAM_ATTR bool breath_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
int timer_idx = (int) para;
|
||||
|
||||
/* Retrieve the interrupt status */
|
||||
timer_group_get_intr_status_in_isr(HW_TIMER_GROUP);
|
||||
timer_group_clr_intr_status_in_isr(HW_TIMER_GROUP, timer_idx);
|
||||
/* After the alarm has been triggered
|
||||
we need enable it again, so it is triggered the next time */
|
||||
timer_group_enable_alarm_in_isr(HW_TIMER_GROUP, timer_idx);
|
||||
|
||||
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
|
||||
if (g_light_group[i] != NULL) {
|
||||
light_t *light = g_light_group[i];
|
||||
@ -194,6 +145,7 @@ static IRAM_ATTR void breath_timer_callback(void *para)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static light_channel_t *light_channel_create(gpio_num_t io_num, ledc_channel_t channel, ledc_mode_t mode, ledc_timer_t timer)
|
||||
@ -268,12 +220,26 @@ light_handle_t iot_light_create(ledc_timer_t timer, ledc_mode_t speed_mode, uint
|
||||
light_ptr->freq_hz = freq_hz;
|
||||
light_ptr->mode = speed_mode;
|
||||
light_ptr->timer_bit = timer_bit;
|
||||
light_ptr->hw_timer.timer_group = HW_TIMER_GROUP;
|
||||
light_ptr->hw_timer.timer_id = HW_TIMER_ID;
|
||||
|
||||
if (g_hw_timer_started == false) {
|
||||
iot_timer_create(&(light_ptr->hw_timer), 1, (double)DUTY_SET_CYCLE / 1000, (void *)breath_timer_callback);
|
||||
iot_timer_start(&(light_ptr->hw_timer));
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = GPTIMER_RESOLUTION_HZ,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &light_ptr->gptimer));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = DUTY_SET_CYCLE / 1000 * GPTIMER_RESOLUTION_HZ,
|
||||
.reload_count = 0,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = breath_timer_callback,
|
||||
};
|
||||
gptimer_register_event_callbacks(light_ptr->gptimer, &cbs, NULL);
|
||||
gptimer_set_alarm_action(light_ptr->gptimer, &alarm_config);
|
||||
gptimer_start(light_ptr->gptimer);
|
||||
g_hw_timer_started = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < channel_num; i++) {
|
||||
@ -317,7 +283,9 @@ esp_err_t iot_light_delete(light_handle_t light_handle)
|
||||
|
||||
ledc_fade_func_uninstall();
|
||||
g_fade_installed = false;
|
||||
iot_timer_stop(&(light->hw_timer));
|
||||
g_hw_timer_started = false;
|
||||
gptimer_stop(light->gptimer);
|
||||
gptimer_del_timer(light->gptimer);
|
||||
FREE_MEM:
|
||||
free(light_handle);
|
||||
return ESP_OK;
|
||||
|
@ -2,7 +2,7 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components"
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components/pid_ctrl"
|
||||
"$ENV{IDF_PATH}/examples/peripherals/pcnt/rotary_encoder/components")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
112
examples/peripherals/mcpwm/mcpwm_bdc_speed_control/README.md
Normal file
112
examples/peripherals/mcpwm/mcpwm_bdc_speed_control/README.md
Normal file
@ -0,0 +1,112 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
# MCPWM Brushed DC Motor Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. However the PWM signals from ESP32 can't drive motors directly as the motor usually consumes high current. So an H-bridge like [DRV8848](https://www.ti.com/product/DRV8848) should be used to provide the needed voltage and current for brushed DC motor. To measure the speed of motor, a photoelectric encoder is used to generate the "speed feedback" signals (e.g. a pair of quadrature signal). The example uses a simple PID control approach to keep the motor speed in a constant speed. The example provides a console command line interface for user to update the PID parameters according to actual situation.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for Power supply and programming
|
||||
* A separate 12V power supply for brushed DC motor and H-bridge (the voltage depends on the motor model used in the example)
|
||||
* A motor driving board to transfer pwm signal into driving signal
|
||||
* A brushed DC motor, e.g. [25GA370](http://www.tronsunmotor.com/data/upload/file/201807/e03b98802b5c5390d6570939def525ba.pdf)
|
||||
* A quadrature encoder to detect speed
|
||||
|
||||
Connection :
|
||||
```
|
||||
Power(12V)
|
||||
|
|
||||
v
|
||||
+----------------+ +--------------------+
|
||||
| | | H-Bridge |
|
||||
| GND +<----------->| GND | +--------------+
|
||||
| | | | | |
|
||||
| GENA_GPIO_NUM +----PWM0A--->| IN_A OUT_A +----->| Brushed |
|
||||
| | | | | DC |
|
||||
| GENB_GPIO_NUM +----PWM0B--->| IN_B OUT_B +----->| Motor |
|
||||
| | | | | |
|
||||
| ESP | +--------------------+ | |
|
||||
| | +------+-------+
|
||||
| | |
|
||||
| | +--------------------+ |
|
||||
| VCC3.3 +------------>| VCC Encoder | |
|
||||
| | | | |
|
||||
| GND +<----------->| |<------------+
|
||||
| | | |
|
||||
|PHASEA_GPIO_NUM |<---PhaseA---+ C1 |
|
||||
| | | |
|
||||
|PHASEB_GPIO_NUM |<---PhaseB---+ C2 |
|
||||
| | | |
|
||||
+----------------+ +--------------------+
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
|
||||
## Example Output
|
||||
|
||||
Run the example, you will see the following output log:
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
configure mcpwm gpio
|
||||
init mcpwm driver
|
||||
init and start rotary encoder
|
||||
init PID control block
|
||||
init motor control timer
|
||||
D (561) gptimer: new group (0) @0x3fce0a24
|
||||
D (561) gptimer: new gptimer (0,0) at 0x3fce0964, resolution=1000000Hz
|
||||
create motor control task
|
||||
start motor control timer
|
||||
D (571) gptimer: install interrupt service for timer (0,0)
|
||||
install console command line
|
||||
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
dc-motor>
|
||||
dc-motor> help
|
||||
help
|
||||
Print the list of registered commands
|
||||
|
||||
pid [-p <kp>] [-i <ki>] [-d <kd>]
|
||||
Set PID parameters
|
||||
-p <kp> Set Kp value of PID
|
||||
-i <ki> Set Ki value of PID
|
||||
-d <kd> Set Kd value of PID
|
||||
```
|
||||
|
||||
### Set PID parameters
|
||||
|
||||
* Command: `pid -p <double> -i <double> -d <double> -t <loc/inc>`
|
||||
* 'p' - proportion value
|
||||
* 'i' - integral value
|
||||
* 'd' - differential value
|
||||
* 't' - PID calculation type (locational or incremental).
|
||||
|
||||
```bash
|
||||
mcpwm-motor> pid -p 0.8 -i 0.02 -d 0.1 -t inc
|
||||
pid: kp = 0.800
|
||||
pid: ki = 0.020
|
||||
pid: kd = 0.100
|
||||
pid: type = increment
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "mcpwm_bdc_control_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "pid_ctrl.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
|
||||
// Enable this config, we will print debug formated string, which in return can be captured and parsed by Serial-Studio
|
||||
#define SERIAL_STUDIO_DEBUG 0
|
||||
|
||||
#define BDC_MCPWM_UNIT 0
|
||||
#define BDC_MCPWM_TIMER 0
|
||||
#define BDC_MCPWM_GENA_GPIO_NUM 7
|
||||
#define BDC_MCPWM_GENB_GPIO_NUM 15
|
||||
#define BDC_MCPWM_FREQ_HZ 1500
|
||||
#define BDC_ENCODER_PCNT_UNIT 0
|
||||
#define BDC_ENCODER_PHASEA_GPIO_NUM 36
|
||||
#define BDC_ENCODER_PHASEB_GPIO_NUM 35
|
||||
|
||||
#define BDC_PID_CALCULATION_PERIOD_US 10000
|
||||
#define BDC_PID_FEEDBACK_QUEUE_LEN 10
|
||||
|
||||
static pid_ctrl_parameter_t pid_runtime_param = {
|
||||
.kp = 0.6,
|
||||
.ki = 0.3,
|
||||
.kd = 0.12,
|
||||
.cal_type = PID_CAL_TYPE_INCREMENTAL,
|
||||
.max_output = 100,
|
||||
.min_output = -100,
|
||||
.max_integral = 1000,
|
||||
.min_integral = -1000,
|
||||
};
|
||||
static bool pid_need_update = false;
|
||||
static int expect_pulses = 300;
|
||||
static int real_pulses;
|
||||
|
||||
typedef struct {
|
||||
rotary_encoder_t *encoder;
|
||||
QueueHandle_t pid_feedback_queue;
|
||||
} motor_control_timer_context_t;
|
||||
|
||||
typedef struct {
|
||||
QueueHandle_t pid_feedback_queue;
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
} motor_control_task_context_t;
|
||||
|
||||
static void brushed_motor_set_duty(float duty_cycle)
|
||||
{
|
||||
/* motor moves in forward direction, with duty cycle = duty % */
|
||||
if (duty_cycle > 0) {
|
||||
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A);
|
||||
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, duty_cycle);
|
||||
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
|
||||
}
|
||||
/* motor moves in backward direction, with duty cycle = -duty % */
|
||||
else {
|
||||
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B);
|
||||
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, -duty_cycle);
|
||||
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool motor_ctrl_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg)
|
||||
{
|
||||
static int last_pulse_count = 0;
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
motor_control_timer_context_t *user_ctx = (motor_control_timer_context_t *)arg;
|
||||
rotary_encoder_t *encoder = user_ctx->encoder;
|
||||
|
||||
int cur_pulse_count = encoder->get_counter_value(encoder);
|
||||
int delta = cur_pulse_count - last_pulse_count;
|
||||
last_pulse_count = cur_pulse_count;
|
||||
xQueueSendFromISR(user_ctx->pid_feedback_queue, &delta, &high_task_awoken);
|
||||
|
||||
return high_task_awoken == pdTRUE;
|
||||
}
|
||||
|
||||
static void bdc_ctrl_task(void *arg)
|
||||
{
|
||||
float duty_cycle = 0;
|
||||
motor_control_task_context_t *user_ctx = (motor_control_task_context_t *)arg;
|
||||
while (1) {
|
||||
xQueueReceive(user_ctx->pid_feedback_queue, &real_pulses, portMAX_DELAY);
|
||||
float error = expect_pulses - real_pulses;
|
||||
pid_compute(user_ctx->pid_ctrl, error, &duty_cycle);
|
||||
brushed_motor_set_duty(duty_cycle);
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
struct arg_dbl *kp;
|
||||
struct arg_dbl *ki;
|
||||
struct arg_dbl *kd;
|
||||
struct arg_end *end;
|
||||
} pid_ctrl_args;
|
||||
|
||||
static int do_pid_ctrl_cmd(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&pid_ctrl_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, pid_ctrl_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (pid_ctrl_args.kp->count) {
|
||||
pid_runtime_param.kp = pid_ctrl_args.kp->dval[0];
|
||||
}
|
||||
if (pid_ctrl_args.ki->count) {
|
||||
pid_runtime_param.ki = pid_ctrl_args.ki->dval[0];
|
||||
}
|
||||
if (pid_ctrl_args.kd->count) {
|
||||
pid_runtime_param.kd = pid_ctrl_args.kd->dval[0];
|
||||
}
|
||||
|
||||
pid_need_update = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_pid_console_command(void)
|
||||
{
|
||||
pid_ctrl_args.kp = arg_dbl0("p", NULL, "<kp>", "Set Kp value of PID");
|
||||
pid_ctrl_args.ki = arg_dbl0("i", NULL, "<ki>", "Set Ki value of PID");
|
||||
pid_ctrl_args.kd = arg_dbl0("d", NULL, "<kd>", "Set Kd value of PID");
|
||||
pid_ctrl_args.end = arg_end(2);
|
||||
const esp_console_cmd_t pid_ctrl_cmd = {
|
||||
.command = "pid",
|
||||
.help = "Set PID parameters",
|
||||
.hint = NULL,
|
||||
.func = &do_pid_ctrl_cmd,
|
||||
.argtable = &pid_ctrl_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&pid_ctrl_cmd));
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
QueueHandle_t pid_fb_queue = xQueueCreate(BDC_PID_FEEDBACK_QUEUE_LEN, sizeof(int));
|
||||
assert(pid_fb_queue);
|
||||
|
||||
printf("configure mcpwm gpio\r\n");
|
||||
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0A, BDC_MCPWM_GENA_GPIO_NUM));
|
||||
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0B, BDC_MCPWM_GENB_GPIO_NUM));
|
||||
printf("init mcpwm driver\n");
|
||||
mcpwm_config_t pwm_config = {
|
||||
.frequency = BDC_MCPWM_FREQ_HZ,
|
||||
.cmpr_a = 0,
|
||||
.cmpr_b = 0,
|
||||
.counter_mode = MCPWM_UP_COUNTER,
|
||||
.duty_mode = MCPWM_DUTY_MODE_0,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_init(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, &pwm_config));
|
||||
|
||||
printf("init and start rotary encoder\r\n");
|
||||
rotary_encoder_config_t config = {
|
||||
.dev = (rotary_encoder_dev_t)BDC_ENCODER_PCNT_UNIT,
|
||||
.phase_a_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
|
||||
.phase_b_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
|
||||
};
|
||||
rotary_encoder_t *speed_encoder = NULL;
|
||||
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &speed_encoder));
|
||||
ESP_ERROR_CHECK(speed_encoder->set_glitch_filter(speed_encoder, 1));
|
||||
ESP_ERROR_CHECK(speed_encoder->start(speed_encoder));
|
||||
|
||||
printf("init PID control block\r\n");
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
pid_ctrl_config_t pid_config = {
|
||||
.init_param = pid_runtime_param,
|
||||
};
|
||||
ESP_ERROR_CHECK(pid_new_control_block(&pid_config, &pid_ctrl));
|
||||
|
||||
printf("init motor control timer\r\n");
|
||||
gptimer_handle_t gptimer;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
printf("create motor control task\r\n");
|
||||
static motor_control_task_context_t my_ctrl_task_ctx = {};
|
||||
my_ctrl_task_ctx.pid_feedback_queue = pid_fb_queue;
|
||||
my_ctrl_task_ctx.pid_ctrl = pid_ctrl;
|
||||
xTaskCreate(bdc_ctrl_task, "bdc_ctrl_task", 4096, &my_ctrl_task_ctx, 5, NULL);
|
||||
|
||||
printf("start motor control timer\r\n");
|
||||
static motor_control_timer_context_t my_timer_ctx = {};
|
||||
my_timer_ctx.pid_feedback_queue = pid_fb_queue;
|
||||
my_timer_ctx.encoder = speed_encoder;
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = motor_ctrl_timer_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, &my_timer_ctx));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = BDC_PID_CALCULATION_PERIOD_US,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
printf("install console command line\r\n");
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.prompt = "dc-motor>";
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
register_pid_console_command();
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
// the following logging format is according to the requirement of serial-studio
|
||||
// also see the parser mapping file `serial-studio-proto-map.json` in the project folder
|
||||
#if SERIAL_STUDIO_DEBUG
|
||||
printf("/*%d*/\r\n", real_pulses);
|
||||
#endif
|
||||
if (pid_need_update) {
|
||||
pid_update_parameters(pid_ctrl, &pid_runtime_param);
|
||||
pid_need_update = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"fe": "*/",
|
||||
"fs": "/*",
|
||||
"g": [
|
||||
{
|
||||
"d": [
|
||||
{
|
||||
"g": true,
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"t": "pulses within 10ms",
|
||||
"u": "",
|
||||
"v": "%1",
|
||||
"w": ""
|
||||
}
|
||||
],
|
||||
"t": "Encoder Feedback",
|
||||
}
|
||||
],
|
||||
"s": ",",
|
||||
"t": "Brushed DC Motor Speed"
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
# MCPWM Brushed DC Motor Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. This example assumes an [L298N](https://www.st.com/content/st_com/en/products/motor-drivers/brushed-dc-motor-drivers/l298.html) H-bridge driver is used to provide the needed voltage and current for brushed DC motor. This example also implements a motor control command console such that users can configure and control the motors at run time using console commands.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for Power supply and programming
|
||||
* A separate 12V power supply for brushed DC motor and H-bridge (the voltage depends on the motor model used in the example)
|
||||
* A motor driving board to transfer pwm signal into driving signal
|
||||
* A brushed DC motor, e.g. [25GA370](http://www.tronsunmotor.com/data/upload/file/201807/e03b98802b5c5390d6570939def525ba.pdf)
|
||||
* A quadrature encoder to detect speed
|
||||
|
||||
Connection :
|
||||
```
|
||||
Power(12V)
|
||||
|
|
||||
v
|
||||
+----------------+ +--------------------+
|
||||
| | | H-Bridge |
|
||||
| GND +------------>| | +--------------+
|
||||
| | | | | |
|
||||
| GPIO15 +----PWM0A--->| IN_A OUT_A +----->| Brushed |
|
||||
| | | | | DC |
|
||||
| GPIO16 +----PWM0B--->| IN_A OUT_B +----->| Motor |
|
||||
| | | | | |
|
||||
| ESP | +--------------------+ | |
|
||||
| | +------+-------+
|
||||
| | |
|
||||
| | +--------------------+ |
|
||||
| VCC3.3 +------------>| Encoder | |
|
||||
| | | | |
|
||||
| GND +------------>| |<------------+
|
||||
| | | |
|
||||
| GPIO18 |<---PhaseA---+ C1 |
|
||||
| | | |
|
||||
| GPIO19 |<---PhaseB---+ C2 |
|
||||
| | | |
|
||||
+----------------+ +--------------------+
|
||||
```
|
||||
NOTE: If some other GPIO pins (e.g., 13/14) are chosen as the PCNT encoder pins, flashing might fail while the wires are connected. If this occurs, please try disconnecting the power supply of the encoder while flashing.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
|
||||
## Example Output
|
||||
|
||||
Run the example, you will see the following output log:
|
||||
|
||||
```bash
|
||||
...
|
||||
Testing brushed motor with PID...
|
||||
initializing mcpwm gpio...
|
||||
Configuring Initial Parameters of mcpwm...
|
||||
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
=================================================================
|
||||
| Example of Motor Control |
|
||||
| |
|
||||
| 1. Try 'help', check all supported commands |
|
||||
| 2. Try 'config' to set control period or pwm frequency |
|
||||
| 3. Try 'pid' to configure pid paremeters |
|
||||
| 4. Try 'expt' to set expectation value and mode |
|
||||
| 5. Try 'motor' to start motor in several seconds or stop it |
|
||||
| |
|
||||
=================================================================
|
||||
|
||||
Default configuration are shown as follows.
|
||||
You can input 'config -s' to check current status.
|
||||
-----------------------------------------------------------------
|
||||
Current Configuration Status
|
||||
|
||||
Configuration
|
||||
Period = 10 ms PID = enabled
|
||||
|
||||
PID - Increment
|
||||
Kp = 0.800 Ki = 0.000 Kd = 0.100
|
||||
|
||||
Expectation - Triangle
|
||||
init = 30.000 max = 50.000 min = -50.000 pace = 1.000
|
||||
|
||||
MCPWM
|
||||
Frequency = 1000 Hz
|
||||
|
||||
Motor
|
||||
Running seconds = 10
|
||||
-----------------------------------------------------------------
|
||||
|
||||
mcpwm-motor>
|
||||
```
|
||||
|
||||
### Check all supported commands and their usages
|
||||
* Command: `help`
|
||||
|
||||
```bash
|
||||
mcpwm-motor> help
|
||||
help
|
||||
Print the list of registered commands
|
||||
|
||||
config config -s
|
||||
Enable or disable PID and set motor control period
|
||||
--pid=<y|n> Enable or disable PID algorithm
|
||||
-T, --period=<ms> Set motor control period
|
||||
-s, --show Show current configurations
|
||||
|
||||
expt expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min -50<duty>
|
||||
Set initial value, limitation and wave mode of expectation. Both dynamic and
|
||||
static mode are available
|
||||
--max=<duty> Max limitation for dynamic expectation
|
||||
--min=<duty> Min limitation for dynamic expectation
|
||||
-p, --pace=<double> The increasing pace of expectation every 50ms
|
||||
-i, --init=<duty> Initial expectation. Usually between -100~100
|
||||
-m, --mode=<fixed/tri/rect> Select static or dynamic expectation wave mode. 'fixed' for static, 'tri' for triangle, 'rect' for rectangle
|
||||
|
||||
pid pid -p <double> -i <double> -d <double> -t <loc/inc>
|
||||
Set parameters and type for PID algorithm
|
||||
-p, --kp=<double> Set Kp value for PID
|
||||
-i, --ki=<double> Set Ki value for PID
|
||||
-d, --kd=<double> Set Kd value for PID
|
||||
-t, --type=<loc/inc> Select locational PID or incremental PID
|
||||
|
||||
motor motor -u 10
|
||||
Start or stop the motor
|
||||
-u, --start=<seconds> Set running seconds for motor, set '0' to keep motor running
|
||||
-d, --stop Stop the motor
|
||||
```
|
||||
|
||||
### Check status
|
||||
|
||||
* Command: `config -s`
|
||||
|
||||
```bash
|
||||
mcpwm-motor> config -s
|
||||
|
||||
-----------------------------------------------------------------
|
||||
Current Configuration Status
|
||||
|
||||
Configuration
|
||||
Period = 10 ms PID = enabled
|
||||
|
||||
PID - Increment
|
||||
Kp = 0.800 Ki = 0.000 Kd = 0.100
|
||||
|
||||
Expectation - Triangle
|
||||
init = 30.000 max = 50.000 min = -50.000 pace = -1.000
|
||||
|
||||
MCPWM
|
||||
Frequency = 1000 Hz
|
||||
|
||||
Motor
|
||||
Running seconds = 10
|
||||
-----------------------------------------------------------------
|
||||
```
|
||||
|
||||
### Enable or disable PID
|
||||
|
||||
* Command: `config --pid <y/n>`
|
||||
* 'y' - enable PID
|
||||
* 'n' - disable PID
|
||||
|
||||
```bash
|
||||
mcpwm-motor> config --pid n
|
||||
config: pid disabled
|
||||
mcpwm-motor> config --pid y
|
||||
config: pid enabled
|
||||
```
|
||||
|
||||
### Set PID parameters
|
||||
|
||||
* Command: `pid -p <double> -i <double> -d <double> -t <loc/inc>`
|
||||
* 'p' - proportion value
|
||||
* 'i' - integral value
|
||||
* 'd' - differential value
|
||||
* 't' - PID calculation type (locational or incremental).
|
||||
|
||||
```bash
|
||||
mcpwm-motor> pid -p 0.8 -i 0.02 -d 0.1 -t inc
|
||||
pid: kp = 0.800
|
||||
pid: ki = 0.020
|
||||
pid: kd = 0.100
|
||||
pid: type = increment
|
||||
```
|
||||
|
||||
### Set expectation parameters
|
||||
|
||||
* Command: `expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min <duty>`
|
||||
* 'i' - initial duty if you set mode 'fixed'
|
||||
* 'm' - expectation mode. 'fixed' means the expectation will never change, 'tri' means the expectation will changes with trigonometric wave, 'rect' means the expectation will changes with rectangular wave
|
||||
* 'p' - the setp size of expectation changed in every 50ms, it can adjust the expectation changing speed
|
||||
* 'max' - the maximum limitation of expectation
|
||||
* 'min' - the minimum limitation of expectation
|
||||
|
||||
```bash
|
||||
mcpwm-motor> expt -i 40 -m rect -p 1.5 --max 80 --min -60
|
||||
expt: init = 40.000
|
||||
expt: max = 80.000
|
||||
expt: min = -60.000
|
||||
expt: pace = 1.500
|
||||
expt: mode = rectangle
|
||||
```
|
||||
|
||||
### Start or stop motor
|
||||
|
||||
* Command: `motor -u <sec>`
|
||||
* Command: `motor -d`
|
||||
* 'u' - start the motor in <sec> seconds, if <sec> is 0, the motor won't stop until 'motor -d' is inputed
|
||||
* 'd' - stop the motor right now
|
||||
|
||||
```bash
|
||||
mcpwm-motor> motor -u 10
|
||||
motor: motor starts to run in 10 seconds
|
||||
mcpwm-motor> 1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
|
||||
Time up: motor stoped
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
@ -1,5 +0,0 @@
|
||||
set(COMPONENT_SRCS "motor_ctrl_timer.c")
|
||||
|
||||
idf_component_register(SRCS "${COMPONENT_SRCS}"
|
||||
INCLUDE_DIRS .
|
||||
PRIV_REQUIRES "driver")
|
@ -1,141 +0,0 @@
|
||||
/* To set the control period for DC motor Timer
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "motor_ctrl_timer.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#define MOTOR_CTRL_TIMER_DIVIDER (16) // Hardware timer clock divider
|
||||
#define MOTOR_CTRL_TIMER_SCALE (TIMER_BASE_CLK / MOTOR_CTRL_TIMER_DIVIDER) // convert counter value to seconds
|
||||
|
||||
#define MOTOR_CONTROL_TIMER_GROUP TIMER_GROUP_0
|
||||
#define MOTOR_CONTROL_TIMER_ID TIMER_0
|
||||
|
||||
static const char *TAG = "motor_ctrl_timer";
|
||||
|
||||
/**
|
||||
* @brief Callback function of timer intterupt
|
||||
*
|
||||
* @param args The parameter transmited to callback function from timer_isr_callback_add. Args here is for timer_info.
|
||||
* @return
|
||||
* - True Do task yield at the end of ISR
|
||||
* - False Not do task yield at the end of ISR
|
||||
*/
|
||||
static bool IRAM_ATTR motor_ctrl_timer_isr_callback(void *args)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
motor_ctrl_timer_info_t *info = (motor_ctrl_timer_info_t *) args;
|
||||
info->pulse_info.pulse_cnt = info->pulse_info.get_pulse_callback(info->pulse_info.callback_args);
|
||||
|
||||
/* Now just send the event data back to the main program task */
|
||||
xQueueSendFromISR(info->timer_evt_que, info, &high_task_awoken);
|
||||
|
||||
return high_task_awoken == pdTRUE; // return whether we need to yield at the end of ISR
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the motor control timer
|
||||
*
|
||||
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t
|
||||
* @param evt_que timer event queue
|
||||
* @param ctrl_period_ms motor control period
|
||||
* @param pulse_info quadrature encoder pulse information
|
||||
* @return
|
||||
* - ESP_OK: Motor control timer initialized successfully
|
||||
* - ESP_FAIL: motor control timer failed to initialize because of other errors
|
||||
*/
|
||||
esp_err_t motor_ctrl_new_timer(motor_ctrl_timer_info_t **timer_info,
|
||||
QueueHandle_t evt_que,
|
||||
unsigned int ctrl_period_ms,
|
||||
pulse_info_t pulse_info)
|
||||
{
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
/* Select and initialize basic parameters of the timer */
|
||||
timer_config_t config = {
|
||||
.divider = MOTOR_CTRL_TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.auto_reload = true,
|
||||
}; // default clock source is APB
|
||||
ret = timer_init(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, &config);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "timer init failed\n");
|
||||
|
||||
/* Timer's counter will initially start from value below.
|
||||
Since auto_reload is set, this value will be automatically reload on alarm */
|
||||
timer_set_counter_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, 0);
|
||||
|
||||
/* Configure the alarm value and the interrupt on alarm. */
|
||||
timer_set_alarm_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, ctrl_period_ms * MOTOR_CTRL_TIMER_SCALE / 1000);
|
||||
timer_enable_intr(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
|
||||
|
||||
/* Check the pointers */
|
||||
ESP_GOTO_ON_FALSE(evt_que, ESP_ERR_INVALID_ARG, err, TAG, "timer event queue handler is NULL\n");
|
||||
ESP_GOTO_ON_FALSE(timer_info, ESP_ERR_INVALID_ARG, err, TAG, "timer info structure pointer is NULL\n");
|
||||
/* Alloc and config the infomation structure for this file */
|
||||
*timer_info = calloc(1, sizeof(motor_ctrl_timer_info_t));
|
||||
ESP_GOTO_ON_FALSE(*timer_info, ESP_ERR_NO_MEM, err, TAG, "timer_info calloc failed\n");
|
||||
(*timer_info)->timer_group = MOTOR_CONTROL_TIMER_GROUP;
|
||||
(*timer_info)->timer_idx = MOTOR_CONTROL_TIMER_ID;
|
||||
(*timer_info)->timer_evt_que = evt_que;
|
||||
(*timer_info)->ctrl_period_ms = ctrl_period_ms;
|
||||
(*timer_info)->pulse_info = pulse_info;
|
||||
timer_isr_callback_add(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, motor_ctrl_timer_isr_callback, *timer_info, 0);
|
||||
|
||||
return ret;
|
||||
err:
|
||||
timer_deinit(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set timer alarm period
|
||||
*
|
||||
* @param period Timer alarm period
|
||||
* @return
|
||||
* - void
|
||||
*/
|
||||
void motor_ctrl_timer_set_period(unsigned int period)
|
||||
{
|
||||
timer_set_alarm_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, period * MOTOR_CTRL_TIMER_SCALE / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start the timer
|
||||
*/
|
||||
void motor_ctrl_timer_start(void)
|
||||
{
|
||||
/* start the timer */
|
||||
timer_start(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pause the timer and clear the counting value
|
||||
*/
|
||||
void motor_ctrl_timer_stop(void)
|
||||
{
|
||||
/* stop the timer */
|
||||
timer_pause(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
|
||||
timer_set_counter_value(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the timer
|
||||
*
|
||||
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t, the memory will be freed
|
||||
*/
|
||||
void motor_ctrl_timer_deinit(motor_ctrl_timer_info_t **timer_info)
|
||||
{
|
||||
if (*timer_info != NULL) {
|
||||
timer_deinit(MOTOR_CONTROL_TIMER_GROUP, MOTOR_CONTROL_TIMER_ID);
|
||||
free(*timer_info);
|
||||
*timer_info = NULL;
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* To set the control period for DC motor Timer
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/timer.h"
|
||||
|
||||
typedef struct {
|
||||
int (*get_pulse_callback)(void *);
|
||||
void *callback_args;
|
||||
int pulse_cnt;
|
||||
} pulse_info_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
timer_group_t timer_group; /* Timer Group number */
|
||||
timer_idx_t timer_idx; /* Timer ID */
|
||||
unsigned int ctrl_period_ms; /* Motor control period, unit in ms */
|
||||
QueueHandle_t timer_evt_que; /* The queue of timer events */
|
||||
pulse_info_t pulse_info;
|
||||
} motor_ctrl_timer_info_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the motor control timer
|
||||
*
|
||||
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t
|
||||
* @param evt_que timer event queue
|
||||
* @param ctrl_period_ms motor control period
|
||||
* @param pulse_info quadrature encoder pulse information
|
||||
* @return
|
||||
* - ESP_OK: Motor control timer initialized successfully
|
||||
* - ESP_FAIL: motor control timer failed to initialize because of other errors
|
||||
*/
|
||||
esp_err_t motor_ctrl_new_timer(motor_ctrl_timer_info_t **timer_info,
|
||||
QueueHandle_t evt_que,
|
||||
unsigned int ctrl_period_ms,
|
||||
pulse_info_t pulse_info);
|
||||
|
||||
/**
|
||||
* @brief Set timer alarm period
|
||||
*
|
||||
* @param period Timer alarm period
|
||||
*/
|
||||
void motor_ctrl_timer_set_period(unsigned int period);
|
||||
|
||||
/**
|
||||
* @brief Start the timer
|
||||
*/
|
||||
void motor_ctrl_timer_start(void);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pause the timer and clear the counting value
|
||||
*/
|
||||
void motor_ctrl_timer_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the timer
|
||||
*
|
||||
* @param timer_info the secondary pointer of motor_ctrl_timer_info_t, the memory will be freed
|
||||
*/
|
||||
void motor_ctrl_timer_deinit(motor_ctrl_timer_info_t **timer_info);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,5 +0,0 @@
|
||||
set(COMPONENT_SRCS "mcpwm_brushed_dc_control_example.c"
|
||||
"cmd_mcpwm_motor.c")
|
||||
|
||||
idf_component_register(SRCS "${COMPONENT_SRCS}"
|
||||
INCLUDE_DIRS "./")
|
@ -1,308 +0,0 @@
|
||||
/* cmd_mcpwm_motor.h
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_log.h"
|
||||
#include "mcpwm_brushed_dc_control_example.h"
|
||||
|
||||
#define MOTOR_CTRL_CMD_CHECK(ins) if(arg_parse(argc, argv, (void **)&ins)){ \
|
||||
arg_print_errors(stderr, ins.end, argv[0]); \
|
||||
return 0;}
|
||||
|
||||
static mcpwm_motor_control_t *mc;
|
||||
extern SemaphoreHandle_t g_motor_mux;
|
||||
|
||||
static struct {
|
||||
struct arg_str *pid_flag;
|
||||
struct arg_int *period;
|
||||
struct arg_lit *show;
|
||||
struct arg_end *end;
|
||||
|
||||
} motor_ctrl_config_args;
|
||||
|
||||
static struct {
|
||||
struct arg_dbl *max;
|
||||
struct arg_dbl *min;
|
||||
struct arg_dbl *pace;
|
||||
struct arg_dbl *init;
|
||||
struct arg_str *mode;
|
||||
struct arg_end *end;
|
||||
|
||||
} motor_ctrl_expt_args;
|
||||
|
||||
static struct {
|
||||
struct arg_dbl *kp;
|
||||
struct arg_dbl *ki;
|
||||
struct arg_dbl *kd;
|
||||
struct arg_str *type;
|
||||
struct arg_end *end;
|
||||
} motor_ctrl_pid_args;
|
||||
|
||||
static struct {
|
||||
struct arg_int *start;
|
||||
struct arg_lit *stop;
|
||||
struct arg_end *end;
|
||||
} motor_ctrl_motor_args;
|
||||
|
||||
|
||||
static void print_current_status(void)
|
||||
{
|
||||
printf("\n -----------------------------------------------------------------\n");
|
||||
printf(" Current Configuration Status \n\n");
|
||||
printf(" Configuration\n Period = %d ms\tPID = %s\n\n",
|
||||
mc->cfg.ctrl_period, mc->cfg.pid_enable ? "enabled" : "disabled");
|
||||
printf(" PID - %s\n Kp = %.3f\tKi = %.3f\tKd = %.3f\n\n",
|
||||
(mc->pid_param.cal_type == PID_CAL_TYPE_POSITIONAL) ? "Location" : "Increment",
|
||||
mc->pid_param.kp, mc->pid_param.ki, mc->pid_param.kd);
|
||||
printf(" Expectation - %s\n init = %.3f\tmax = %.3f\tmin = %.3f\tpace = %.3f\n\n",
|
||||
mc->cfg.expt_mode ? (mc->cfg.expt_mode == MOTOR_CTRL_MODE_TRIANGLE ? "Triangle" : "Rectangle") : "Fixed",
|
||||
mc->cfg.expt_init, mc->cfg.expt_max, mc->cfg.expt_min, mc->cfg.expt_pace);
|
||||
printf(" MCPWM\n Frequency = %d Hz\n\n", mc->cfg.pwm_freq);
|
||||
printf(" Motor\n Running seconds = %d\n", mc->cfg.running_sec);
|
||||
printf(" -----------------------------------------------------------------\n\n");
|
||||
}
|
||||
|
||||
|
||||
static int do_motor_ctrl_config_cmd(int argc, char **argv)
|
||||
{
|
||||
MOTOR_CTRL_CMD_CHECK(motor_ctrl_config_args);
|
||||
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
|
||||
if (motor_ctrl_config_args.pid_flag->count) {
|
||||
if (!strcmp(*motor_ctrl_config_args.pid_flag->sval, "n") ||
|
||||
!strcmp(*motor_ctrl_config_args.pid_flag->sval, "no")) {
|
||||
mc->cfg.pid_enable = false;
|
||||
printf("config: pid disabled\n");
|
||||
} else {
|
||||
mc->cfg.pid_enable = true;
|
||||
printf("config: pid enabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (motor_ctrl_config_args.period->count) {
|
||||
mc->cfg.ctrl_period = motor_ctrl_config_args.period->ival[0];
|
||||
motor_ctrl_timer_set_period(mc->cfg.ctrl_period);
|
||||
printf("config: control period = mc->cfg.ctrl_period\n");
|
||||
}
|
||||
|
||||
if (motor_ctrl_config_args.show->count) {
|
||||
print_current_status();
|
||||
}
|
||||
xSemaphoreGive(g_motor_mux);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_motor_ctrl_expt_cmd(int argc, char **argv)
|
||||
{
|
||||
MOTOR_CTRL_CMD_CHECK(motor_ctrl_expt_args);
|
||||
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
|
||||
if (motor_ctrl_expt_args.init->count) {
|
||||
mc->cfg.expt_init = motor_ctrl_expt_args.init->dval[0];
|
||||
printf("expt: init = %.3f\n", mc->cfg.expt_init);
|
||||
}
|
||||
if (motor_ctrl_expt_args.max->count) {
|
||||
mc->cfg.expt_max = motor_ctrl_expt_args.max->dval[0];
|
||||
printf("expt: max = %.3f\n", mc->cfg.expt_max);
|
||||
}
|
||||
if (motor_ctrl_expt_args.min->count) {
|
||||
mc->cfg.expt_min = motor_ctrl_expt_args.min->dval[0];
|
||||
printf("expt: min = %.3f\n", mc->cfg.expt_min);
|
||||
}
|
||||
if (motor_ctrl_expt_args.pace->count) {
|
||||
mc->cfg.expt_pace = motor_ctrl_expt_args.pace->dval[0];
|
||||
printf("expt: pace = %.3f\n", mc->cfg.expt_pace);
|
||||
}
|
||||
if (motor_ctrl_expt_args.mode->count) {
|
||||
if (!strcmp(*motor_ctrl_expt_args.mode->sval, "fixed")) {
|
||||
mc->cfg.expt_mode = MOTOR_CTRL_MODE_FIXED;
|
||||
printf("expt: mode = fixed\n");
|
||||
} else if (!strcmp(*motor_ctrl_expt_args.mode->sval, "tri")) {
|
||||
mc->cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
|
||||
printf("expt: mode = triangle\n");
|
||||
} else if (!strcmp(*motor_ctrl_expt_args.mode->sval, "rect")) {
|
||||
mc->cfg.expt_mode = MOTOR_CTRL_MODE_RECTANGLE;
|
||||
printf("expt: mode = rectangle\n");
|
||||
} else {
|
||||
mc->cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
|
||||
printf("expt: mode = triangle\n");
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(g_motor_mux);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_motor_ctrl_pid_cmd(int argc, char **argv)
|
||||
{
|
||||
int ret = 0;
|
||||
MOTOR_CTRL_CMD_CHECK(motor_ctrl_pid_args);
|
||||
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
|
||||
if (motor_ctrl_pid_args.kp->count) {
|
||||
mc->pid_param.kp = motor_ctrl_pid_args.kp->dval[0];
|
||||
printf("pid: kp = %.3f\n", mc->pid_param.kp);
|
||||
}
|
||||
if (motor_ctrl_pid_args.ki->count) {
|
||||
mc->pid_param.ki = motor_ctrl_pid_args.ki->dval[0];
|
||||
printf("pid: ki = %.3f\n", mc->pid_param.ki);
|
||||
}
|
||||
if (motor_ctrl_pid_args.kd->count) {
|
||||
mc->pid_param.kd = motor_ctrl_pid_args.kd->dval[0];
|
||||
printf("pid: kd = %.3f\n", mc->pid_param.kd);
|
||||
}
|
||||
|
||||
if (motor_ctrl_pid_args.type->count) {
|
||||
if (!strcmp(motor_ctrl_pid_args.type->sval[0], "loc")) {
|
||||
mc->pid_param.cal_type = PID_CAL_TYPE_POSITIONAL;
|
||||
printf("pid: type = positional\n");
|
||||
} else if (!strcmp(motor_ctrl_pid_args.type->sval[0], "inc")) {
|
||||
mc->pid_param.cal_type = PID_CAL_TYPE_INCREMENTAL;
|
||||
printf("pid: type = incremental\n");
|
||||
} else {
|
||||
printf("Invalid pid type:%s\n", motor_ctrl_pid_args.type->sval[0]);
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
pid_update_parameters(mc->pid, &mc->pid_param);
|
||||
xSemaphoreGive(g_motor_mux);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_motor_ctrl_motor_cmd(int argc, char **argv)
|
||||
{
|
||||
MOTOR_CTRL_CMD_CHECK(motor_ctrl_motor_args);
|
||||
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
|
||||
if (motor_ctrl_motor_args.start->count) {
|
||||
mc->cfg.running_sec = motor_ctrl_motor_args.start->ival[0];
|
||||
// Start the motor
|
||||
brushed_motor_start(mc);
|
||||
mc->cfg.running_sec ?
|
||||
printf("motor: motor starts to run in %d seconds\n", mc->cfg.running_sec) :
|
||||
printf("motor: motor starts to run, input 'motor -d' to stop it\n");
|
||||
}
|
||||
|
||||
if (motor_ctrl_motor_args.stop->count) {
|
||||
// Stop the motor
|
||||
brushed_motor_stop(mc);
|
||||
printf("motor: motor stoped\n");
|
||||
}
|
||||
xSemaphoreGive(g_motor_mux);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_motor_ctrl_config(void)
|
||||
{
|
||||
motor_ctrl_config_args.pid_flag = arg_str0(NULL, "pid", "<y|n>", "Enable or disable PID algorithm");
|
||||
motor_ctrl_config_args.period = arg_int0("T", "period", "<ms>", "Set motor control period");
|
||||
motor_ctrl_config_args.show = arg_lit0("s", "show", "Show current configurations");
|
||||
motor_ctrl_config_args.end = arg_end(2);
|
||||
const esp_console_cmd_t motor_ctrl_cfg_cmd = {
|
||||
.command = "config",
|
||||
.help = "Enable or disable PID and set motor control period",
|
||||
.hint = "config -s",
|
||||
.func = &do_motor_ctrl_config_cmd,
|
||||
.argtable = &motor_ctrl_config_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_cfg_cmd));
|
||||
}
|
||||
|
||||
static void register_motor_ctrl_expt(void)
|
||||
{
|
||||
motor_ctrl_expt_args.init = arg_dbl0("i", "init", "<duty>", "Initial expectation. Usually between -100~100");
|
||||
motor_ctrl_expt_args.max = arg_dbl0(NULL, "max", "<duty>", "Max limitation for dynamic expectation");
|
||||
motor_ctrl_expt_args.min = arg_dbl0(NULL, "min", "<duty>", "Min limitation for dynamic expectation");
|
||||
motor_ctrl_expt_args.pace = arg_dbl0("p", "pace", "<double>", "The increasing pace of expectation every 50ms");
|
||||
motor_ctrl_expt_args.mode = arg_str0("m", "mode", "<fixed/tri/rect>",
|
||||
"Select static or dynamic expectation wave mode. 'fixed' for static, 'tri' for triangle, 'rect' for rectangle");
|
||||
motor_ctrl_expt_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t motor_ctrl_expt_cmd = {
|
||||
.command = "expt",
|
||||
.help = "Set initial value, limitation and wave mode of expectation. Both dynamic and static mode are available",
|
||||
.hint = "expt -i <duty> -m <fixed/tri/rect> -p <double> --max <duty> --min <duty>",
|
||||
.func = &do_motor_ctrl_expt_cmd,
|
||||
.argtable = &motor_ctrl_expt_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_expt_cmd));
|
||||
}
|
||||
|
||||
static void register_motor_ctrl_pid(void)
|
||||
{
|
||||
motor_ctrl_pid_args.kp = arg_dbl0("p", "kp", "<double>", "Set Kp value for PID");
|
||||
motor_ctrl_pid_args.ki = arg_dbl0("i", "ki", "<double>", "Set Ki value for PID");
|
||||
motor_ctrl_pid_args.kd = arg_dbl0("d", "kd", "<double>", "Set Kd value for PID");
|
||||
motor_ctrl_pid_args.type = arg_str0("t", "type", "<loc/inc>", "Select locational PID or incremental PID");
|
||||
motor_ctrl_pid_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t motor_ctrl_pid_cmd = {
|
||||
.command = "pid",
|
||||
.help = "Set parameters and type for PID algorithm",
|
||||
.hint = "pid -p <double> -i <double> -d <double> -t <loc/inc>",
|
||||
.func = &do_motor_ctrl_pid_cmd,
|
||||
.argtable = &motor_ctrl_pid_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_pid_cmd));
|
||||
}
|
||||
|
||||
static void register_motor_ctrl_motor(void)
|
||||
{
|
||||
motor_ctrl_motor_args.start = arg_int0("u", "start", "<seconds>", "Set running seconds for motor, set '0' to keep motor running");
|
||||
motor_ctrl_motor_args.stop = arg_lit0("d", "stop", "Stop the motor");
|
||||
motor_ctrl_motor_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t motor_ctrl_motor_cmd = {
|
||||
.command = "motor",
|
||||
.help = "Start or stop the motor",
|
||||
.hint = "motor -u 10",
|
||||
.func = &do_motor_ctrl_motor_cmd,
|
||||
.argtable = &motor_ctrl_motor_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&motor_ctrl_motor_cmd));
|
||||
}
|
||||
|
||||
void cmd_mcpwm_motor_init(mcpwm_motor_control_t *motor_ctrl)
|
||||
{
|
||||
mc = motor_ctrl;
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.prompt = "mcpwm-motor>";
|
||||
|
||||
// install console REPL environment
|
||||
#if CONFIG_ESP_CONSOLE_UART
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
#elif CONFIG_ESP_CONSOLE_USB_CDC
|
||||
esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &repl));
|
||||
#endif
|
||||
|
||||
register_motor_ctrl_config();
|
||||
register_motor_ctrl_expt();
|
||||
register_motor_ctrl_pid();
|
||||
register_motor_ctrl_motor();
|
||||
|
||||
printf("\n =================================================================\n");
|
||||
printf(" | Example of Motor Control |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | 1. Try 'help', check all supported commands |\n");
|
||||
printf(" | 2. Try 'config' to set control period or pwm frequency |\n");
|
||||
printf(" | 3. Try 'pid' to configure pid paremeters |\n");
|
||||
printf(" | 4. Try 'expt' to set expectation value and mode |\n");
|
||||
printf(" | 5. Try 'motor' to start motor in several seconds or stop it |\n");
|
||||
printf(" | |\n");
|
||||
printf(" =================================================================\n\n");
|
||||
|
||||
printf("Default configuration are shown as follows.\nYou can input 'config -s' to check current status.");
|
||||
print_current_status();
|
||||
|
||||
// start console REPL
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
}
|
@ -1,310 +0,0 @@
|
||||
/* brushed dc motor control example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This example will show you how to use MCPWM module to control brushed dc motor.
|
||||
* This code is tested with L298 motor driver.
|
||||
* User may need to make changes according to the motor driver they use.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#include "driver/mcpwm.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "driver/pcnt.h"
|
||||
|
||||
#include "mcpwm_brushed_dc_control_example.h"
|
||||
|
||||
#define MOTOR_CTRL_MCPWM_UNIT MCPWM_UNIT_0
|
||||
#define MOTOR_CTRL_MCPWM_TIMER MCPWM_TIMER_0
|
||||
|
||||
/* The global infomation structure */
|
||||
static mcpwm_motor_control_t motor_ctrl;
|
||||
|
||||
SemaphoreHandle_t g_motor_mux;
|
||||
|
||||
/**
|
||||
* @brief Initialize the gpio as mcpwm output
|
||||
*/
|
||||
static void mcpwm_example_gpio_initialize(void)
|
||||
{
|
||||
printf("initializing mcpwm gpio...\n");
|
||||
mcpwm_gpio_init(MOTOR_CTRL_MCPWM_UNIT, MCPWM0A, GPIO_PWM0A_OUT);
|
||||
mcpwm_gpio_init(MOTOR_CTRL_MCPWM_UNIT, MCPWM0B, GPIO_PWM0B_OUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set motor moves speed and direction with duty cycle = duty %
|
||||
*/
|
||||
void brushed_motor_set_duty(float duty_cycle)
|
||||
{
|
||||
/* motor moves in forward direction, with duty cycle = duty % */
|
||||
if (duty_cycle > 0) {
|
||||
mcpwm_set_signal_low(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A);
|
||||
mcpwm_set_duty(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B, duty_cycle);
|
||||
mcpwm_set_duty_type(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
|
||||
}
|
||||
/* motor moves in backward direction, with duty cycle = -duty % */
|
||||
else {
|
||||
mcpwm_set_signal_low(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_B);
|
||||
mcpwm_set_duty(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A, -duty_cycle);
|
||||
mcpwm_set_duty_type(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief start motor
|
||||
*
|
||||
* @param mc mcpwm_motor_control_t pointer
|
||||
*/
|
||||
void brushed_motor_start(mcpwm_motor_control_t *mc)
|
||||
{
|
||||
motor_ctrl_timer_start();
|
||||
mc->sec_cnt = 0;
|
||||
mc->start_flag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief stop motor
|
||||
*
|
||||
* @param mc mcpwm_motor_control_t pointer
|
||||
*/
|
||||
void brushed_motor_stop(mcpwm_motor_control_t *mc)
|
||||
{
|
||||
mc->expt = 0;
|
||||
mc->sec_cnt = 0;
|
||||
mc->start_flag = false;
|
||||
motor_ctrl_timer_stop();
|
||||
brushed_motor_set_duty(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The callback function of timer interrupt
|
||||
* @note This callback is called by timer interrupt callback. It need to offer the PCNT pulse in one control period for PID calculation
|
||||
* @param args the rotary_encoder_t pointer, it is given by timer interrupt callback
|
||||
* @return
|
||||
* - int: the PCNT pulse in one control period
|
||||
*/
|
||||
static int pcnt_get_pulse_callback(void *args)
|
||||
{
|
||||
/* Record the last count value */
|
||||
static unsigned int last_pulse = 0;
|
||||
/* Get the encoder from args */
|
||||
rotary_encoder_t *encoder = (rotary_encoder_t *)args;
|
||||
/* Get the value current count value */
|
||||
unsigned int temp = encoder->get_counter_value(encoder);
|
||||
/* Calculate the pulse count in one control period */
|
||||
unsigned int ret = temp - last_pulse;
|
||||
/* Update last count value */
|
||||
last_pulse = temp;
|
||||
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the PCNT rotaty encoder
|
||||
*/
|
||||
static void motor_ctrl_default_init(void)
|
||||
{
|
||||
motor_ctrl.cfg.pid_enable = true;
|
||||
motor_ctrl.pid_param.kp = 0.8;
|
||||
motor_ctrl.pid_param.ki = 0.0;
|
||||
motor_ctrl.pid_param.kd = 0.1;
|
||||
motor_ctrl.pid_param.cal_type = PID_CAL_TYPE_INCREMENTAL;
|
||||
motor_ctrl.pid_param.max_output = 100;
|
||||
motor_ctrl.pid_param.min_output = -100;
|
||||
motor_ctrl.pid_param.max_integral = 1000;
|
||||
motor_ctrl.pid_param.min_integral = -1000;
|
||||
motor_ctrl.cfg.expt_init = 30;
|
||||
motor_ctrl.cfg.expt_mode = MOTOR_CTRL_MODE_TRIANGLE;
|
||||
motor_ctrl.cfg.expt_max = 50;
|
||||
motor_ctrl.cfg.expt_min = -50;
|
||||
motor_ctrl.cfg.expt_pace = 1.0;
|
||||
motor_ctrl.cfg.pwm_freq = 1000;
|
||||
motor_ctrl.cfg.running_sec = 10;
|
||||
motor_ctrl.cfg.ctrl_period = 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the PCNT rotaty encoder
|
||||
*/
|
||||
static void motor_ctrl_pcnt_rotary_encoder_init(void)
|
||||
{
|
||||
/* Rotary encoder underlying device is represented by a PCNT unit in this example */
|
||||
uint32_t pcnt_unit = 0;
|
||||
/* Create rotary encoder instance */
|
||||
rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG(
|
||||
(rotary_encoder_dev_t)pcnt_unit,
|
||||
GPIO_PCNT_PINA, GPIO_PCNT_PINB);
|
||||
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &motor_ctrl.encoder));
|
||||
/* Filter out glitch (1us) */
|
||||
ESP_ERROR_CHECK(motor_ctrl.encoder->set_glitch_filter(motor_ctrl.encoder, 1));
|
||||
/* Start encoder */
|
||||
ESP_ERROR_CHECK(motor_ctrl.encoder->start(motor_ctrl.encoder));
|
||||
pcnt_counter_clear((pcnt_unit_t)pcnt_unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the MCPWM
|
||||
*/
|
||||
static void motor_ctrl_mcpwm_init(void)
|
||||
{
|
||||
/* mcpwm gpio initialization */
|
||||
mcpwm_example_gpio_initialize();
|
||||
/* initial mcpwm configuration */
|
||||
printf("Configuring Initial Parameters of mcpwm...\n");
|
||||
mcpwm_config_t pwm_config;
|
||||
pwm_config.frequency = motor_ctrl.cfg.pwm_freq; //frequency = 1kHz,
|
||||
pwm_config.cmpr_a = 0; //initial duty cycle of PWMxA = 0
|
||||
pwm_config.cmpr_b = 0; //initial duty cycle of PWMxb = 0
|
||||
pwm_config.counter_mode = MCPWM_UP_COUNTER; //up counting mode
|
||||
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
|
||||
mcpwm_init(MOTOR_CTRL_MCPWM_UNIT, MOTOR_CTRL_MCPWM_TIMER, &pwm_config); //Configure PWM0A & PWM0B with above settings
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the timer
|
||||
*/
|
||||
static void motor_ctrl_timer_init(void)
|
||||
{
|
||||
/* Initialize timer alarm event queue */
|
||||
motor_ctrl.timer_evt_que = xQueueCreate(10, sizeof(motor_ctrl_timer_info_t));
|
||||
/* Set PCNT rotary encoder handler and pulse getting callback function */
|
||||
pulse_info_t pulse_info = {.callback_args = motor_ctrl.encoder,
|
||||
.get_pulse_callback = pcnt_get_pulse_callback
|
||||
};
|
||||
motor_ctrl_new_timer(&motor_ctrl.timer_info, motor_ctrl.timer_evt_que, motor_ctrl.cfg.ctrl_period, pulse_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief the top initialization function in this example
|
||||
*/
|
||||
static void motor_ctrl_init_all(void)
|
||||
{
|
||||
/* 1. Set default configurations */
|
||||
motor_ctrl_default_init();
|
||||
/* 2.rotary encoder initialization */
|
||||
motor_ctrl_pcnt_rotary_encoder_init();
|
||||
/* 3.MCPWM initialization */
|
||||
motor_ctrl_mcpwm_init();
|
||||
/* 4.pid_ctrl initialization */
|
||||
pid_ctrl_config_t pid_config = {
|
||||
.init_param = motor_ctrl.pid_param,
|
||||
};
|
||||
pid_new_control_block(&pid_config, &motor_ctrl.pid);
|
||||
/* 5.Timer initialization */
|
||||
motor_ctrl_timer_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Motor control thread
|
||||
*
|
||||
* @param arg Information pointer transmitted by task creating function
|
||||
*/
|
||||
static void mcpwm_brushed_motor_ctrl_thread(void *arg)
|
||||
{
|
||||
motor_ctrl_timer_info_t recv_info;
|
||||
while (1) {
|
||||
/* Wait for recieving information of timer interrupt from timer event queque */
|
||||
xQueueReceive(motor_ctrl.timer_evt_que, &recv_info, portMAX_DELAY);
|
||||
/* Get the pcnt pulse during one control period */
|
||||
motor_ctrl.pulse_in_one_period = recv_info.pulse_info.pulse_cnt;
|
||||
if (motor_ctrl.cfg.pid_enable) {
|
||||
/* Calculate the output by PID algorithm according to the pulse. Pid_output here is the duty of MCPWM */
|
||||
motor_ctrl.error = motor_ctrl.expt - motor_ctrl.pulse_in_one_period;
|
||||
pid_compute(motor_ctrl.pid, motor_ctrl.error, &motor_ctrl.pid_output);
|
||||
} else {
|
||||
motor_ctrl.pid_output = motor_ctrl.expt;
|
||||
}
|
||||
|
||||
/* Set the MCPWM duty */
|
||||
brushed_motor_set_duty(motor_ctrl.pid_output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Motor control thread
|
||||
*
|
||||
* @param arg Information pointer transmitted by task creating function
|
||||
*/
|
||||
static void mcpwm_brushed_motor_expt_thread(void *arg)
|
||||
{
|
||||
float cnt = 0;
|
||||
while (1) {
|
||||
xSemaphoreTake(g_motor_mux, portMAX_DELAY);
|
||||
switch (motor_ctrl.cfg.expt_mode) {
|
||||
/* Static expectation */
|
||||
case MOTOR_CTRL_MODE_FIXED:
|
||||
motor_ctrl.expt = motor_ctrl.cfg.expt_init;
|
||||
break;
|
||||
/* Dynamic expectation: triangle wave */
|
||||
case MOTOR_CTRL_MODE_TRIANGLE:
|
||||
motor_ctrl.expt += motor_ctrl.cfg.expt_pace;
|
||||
motor_ctrl.cfg.expt_pace = (motor_ctrl.expt > motor_ctrl.cfg.expt_max - 0.0001 ||
|
||||
motor_ctrl.expt < motor_ctrl.cfg.expt_min + 0.0001) ?
|
||||
- motor_ctrl.cfg.expt_pace : motor_ctrl.cfg.expt_pace;
|
||||
break;
|
||||
/* Dynamic expectation: rectangle wave */
|
||||
case MOTOR_CTRL_MODE_RECTANGLE:
|
||||
cnt += motor_ctrl.cfg.expt_pace;
|
||||
if (cnt > motor_ctrl.cfg.expt_max - 0.0001) {
|
||||
motor_ctrl.cfg.expt_pace = -motor_ctrl.cfg.expt_pace;
|
||||
motor_ctrl.expt = motor_ctrl.cfg.expt_min;
|
||||
}
|
||||
if (cnt < motor_ctrl.cfg.expt_min - 0.0001) {
|
||||
motor_ctrl.cfg.expt_pace = -motor_ctrl.cfg.expt_pace;
|
||||
motor_ctrl.expt = motor_ctrl.cfg.expt_max;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
motor_ctrl.expt = motor_ctrl.cfg.expt_init;
|
||||
break;
|
||||
}
|
||||
xSemaphoreGive(g_motor_mux);
|
||||
/* Motor automatic stop judgement */
|
||||
if (motor_ctrl.start_flag) {
|
||||
motor_ctrl.sec_cnt++;
|
||||
/* Show the seconds count */
|
||||
if ((motor_ctrl.sec_cnt + 1) % 20 == 0) {
|
||||
printf("%d\n", (motor_ctrl.sec_cnt + 1) / 20);
|
||||
}
|
||||
/* Stop motor if time up */
|
||||
if (motor_ctrl.sec_cnt > 20 * motor_ctrl.cfg.running_sec && motor_ctrl.cfg.running_sec != 0) {
|
||||
brushed_motor_stop(&motor_ctrl);
|
||||
printf("\nTime up: motor stoped\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Delay 50ms */
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The main entry of this example
|
||||
*/
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Testing brushed motor with PID...\n");
|
||||
/* Create semaphore */
|
||||
g_motor_mux = xSemaphoreCreateMutex();
|
||||
/* Initialize peripherals and modules */
|
||||
motor_ctrl_init_all();
|
||||
/* Initialize the console */
|
||||
cmd_mcpwm_motor_init(&motor_ctrl);
|
||||
/* Motor control thread */
|
||||
xTaskCreate(mcpwm_brushed_motor_ctrl_thread, "mcpwm_brushed_motor_ctrl_thread", 4096, NULL, 3, NULL);
|
||||
/* Motor expectation wave generate thread */
|
||||
xTaskCreate(mcpwm_brushed_motor_expt_thread, "mcpwm_brushed_motor_expt_thread", 4096, NULL, 5, NULL);
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/* cmd_mcpwm_motor.h
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rotary_encoder.h"
|
||||
#include "motor_ctrl_timer.h"
|
||||
#include "pid_ctrl.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GPIO_PWM0A_OUT 15 //Set GPIO 15 as PWM0A
|
||||
#define GPIO_PWM0B_OUT 16 //Set GPIO 16 as PWM0B
|
||||
#define GPIO_PCNT_PINA 18 //Set GPIO 18 as phaseA/C1
|
||||
#define GPIO_PCNT_PINB 19 //Set GPIO 19 as phaseB/C2
|
||||
|
||||
typedef enum {
|
||||
MOTOR_CTRL_MODE_FIXED = 0,
|
||||
MOTOR_CTRL_MODE_TRIANGLE,
|
||||
MOTOR_CTRL_MODE_RECTANGLE
|
||||
} expect_mode_t;
|
||||
|
||||
typedef struct {
|
||||
/* Handles */
|
||||
rotary_encoder_t *encoder; // PCNT rotary encoder handler
|
||||
motor_ctrl_timer_info_t *timer_info; // Timer infomation handler
|
||||
pid_ctrl_block_handle_t pid; // PID algoritm handler
|
||||
pid_ctrl_parameter_t pid_param; // PID parameters
|
||||
QueueHandle_t timer_evt_que; // Timer event queue handler
|
||||
|
||||
/* Control visualization */
|
||||
int pulse_in_one_period; // PCNT pulse in one control period
|
||||
float error; // The error between the expectation(expt) and actual value (pulse_in_one_period)
|
||||
float expt; // The expectation
|
||||
float pid_output; // PID algorithm output
|
||||
|
||||
/* Status */
|
||||
unsigned int sec_cnt; // Seconds count
|
||||
bool start_flag; // Motor start flag
|
||||
|
||||
/* Configurations */
|
||||
struct {
|
||||
/* PID configuration */
|
||||
bool pid_enable; // PID enable flag
|
||||
|
||||
/* Expectation configuration */
|
||||
float expt_init; // Initial expectation
|
||||
float expt_max; // Max expectation in dynamic mode
|
||||
float expt_min; // Min expectation in dynamic mode
|
||||
float expt_pace; // The expection pace. It can change expectation wave period
|
||||
expect_mode_t expt_mode; // Expectation wave mode (MOTOR_CTRL_EXPT_FIXED/MOTOR_CTRL_EXPT_TRIANGLE/MOTOR_CTRL_EXPT_RECTANGLE)
|
||||
|
||||
/* Other configurations */
|
||||
unsigned int ctrl_period; // Control period
|
||||
unsigned int pwm_freq; // MCPWM output frequency
|
||||
unsigned int running_sec; // Motor running seconds
|
||||
} cfg; // Configurations that should be initialized for this example
|
||||
} mcpwm_motor_control_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set pwm duty to drive the motor
|
||||
*
|
||||
* @param duty_cycle PWM duty cycle (100~-100), the motor will go backward if the duty is set to a negative value
|
||||
*/
|
||||
void brushed_motor_set_duty(float duty_cycle);
|
||||
|
||||
/**
|
||||
* @brief start motor
|
||||
*
|
||||
* @param mc mcpwm_motor_control_t pointer
|
||||
*/
|
||||
void brushed_motor_start(mcpwm_motor_control_t *mc);
|
||||
|
||||
/**
|
||||
* @brief stop motor
|
||||
*
|
||||
* @param mc mcpwm_motor_control_t pointer
|
||||
*/
|
||||
void brushed_motor_stop(mcpwm_motor_control_t *mc);
|
||||
|
||||
/**
|
||||
* @brief Initialize the motor control console
|
||||
*
|
||||
* @param motor_ctrl The top infomation struct of this example
|
||||
*/
|
||||
extern void cmd_mcpwm_motor_init(mcpwm_motor_control_t *motor_ctrl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
6
examples/peripherals/timer_group/gptimer/CMakeLists.txt
Normal file
6
examples/peripherals/timer_group/gptimer/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(gptimer)
|
46
examples/peripherals/timer_group/gptimer/README.md
Normal file
46
examples/peripherals/timer_group/gptimer/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Example: General Purpose Timer
|
||||
|
||||
This example illustrates how to use gptimer APIs to generate periodic alarm events and how different alarm actions behave on events.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP SOC chip
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [ESP-IDF Getting Started Guide](https://idf.espressif.com/) for all the steps to configure and use the ESP-IDF to build projects.
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (323) example: Create timer handle
|
||||
I (323) example: Start timer, stop it at alarm event
|
||||
I (1333) example: Timer stopped, count=1000002
|
||||
I (1333) example: Set count value
|
||||
I (1333) example: Get count value
|
||||
I (1333) example: Timer count value=100
|
||||
I (1343) example: Start timer, auto-reload at alarm event
|
||||
I (2343) example: Timer reloaded, count=2
|
||||
I (3343) example: Timer reloaded, count=2
|
||||
I (4343) example: Timer reloaded, count=2
|
||||
I (5343) example: Timer reloaded, count=2
|
||||
I (5343) example: Stop timer
|
||||
I (5343) example: Update alarm value dynamically
|
||||
I (6353) example: Timer alarmed, count=1000002
|
||||
I (7353) example: Timer alarmed, count=2000002
|
||||
I (8353) example: Timer alarmed, count=3000002
|
||||
I (9353) example: Timer alarmed, count=4000002
|
||||
I (9353) example: Stop timer
|
||||
I (9353) example: Delete timer
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "gptimer_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
typedef struct {
|
||||
uint64_t event_count;
|
||||
} example_queue_element_t;
|
||||
|
||||
static bool IRAM_ATTR example_timer_on_alarm_cb_v1(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// stop timer immediately
|
||||
gptimer_stop(timer);
|
||||
// Retrieve count value and send to queue
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return (high_task_awoken == pdTRUE);
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR example_timer_on_alarm_cb_v2(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// Retrieve count value and send to queue
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return (high_task_awoken == pdTRUE);
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR example_timer_on_alarm_cb_v3(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_data;
|
||||
// Retrieve count value and send to queue
|
||||
example_queue_element_t ele = {
|
||||
.event_count = edata->count_value
|
||||
};
|
||||
xQueueSendFromISR(queue, &ele, &high_task_awoken);
|
||||
// reconfigure alarm value
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
|
||||
};
|
||||
gptimer_set_alarm_action(timer, &alarm_config);
|
||||
// return whether we need to yield at the end of ISR
|
||||
return (high_task_awoken == pdTRUE);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
example_queue_element_t ele;
|
||||
QueueHandle_t queue = xQueueCreate(10, sizeof(example_queue_element_t));
|
||||
if (!queue) {
|
||||
ESP_LOGE(TAG, "Creating queue failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create timer handle");
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick=1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_on_alarm_cb_v1,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
|
||||
ESP_LOGI(TAG, "Start timer, stop it at alarm event");
|
||||
gptimer_alarm_config_t alarm_config1 = {
|
||||
.alarm_count = 1000000, // period = 1s
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config1));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
if (xQueueReceive(queue, &ele, pdMS_TO_TICKS(2000))) {
|
||||
ESP_LOGI(TAG, "Timer stopped, count=%llu", ele.event_count);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Missed one count event");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Set count value");
|
||||
ESP_ERROR_CHECK(gptimer_set_raw_count(gptimer, 100));
|
||||
ESP_LOGI(TAG, "Get count value");
|
||||
uint64_t count;
|
||||
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
|
||||
ESP_LOGI(TAG, "Timer count value=%llu", count);
|
||||
|
||||
ESP_LOGI(TAG, "Start timer, auto-reload at alarm event");
|
||||
cbs.on_alarm = example_timer_on_alarm_cb_v2;
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
gptimer_alarm_config_t alarm_config2 = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = 1000000, // period = 1s
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config2));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
int record = 4;
|
||||
while (record) {
|
||||
if (xQueueReceive(queue, &ele, pdMS_TO_TICKS(2000))) {
|
||||
ESP_LOGI(TAG, "Timer reloaded, count=%llu", ele.event_count);
|
||||
record--;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Missed one count event");
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Stop timer");
|
||||
ESP_ERROR_CHECK(gptimer_stop(gptimer));
|
||||
|
||||
ESP_LOGI(TAG, "Update alarm value dynamically");
|
||||
cbs.on_alarm = example_timer_on_alarm_cb_v3;
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
|
||||
gptimer_alarm_config_t alarm_config3 = {
|
||||
.alarm_count = 1000000, // period = 1s
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config3));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
record = 4;
|
||||
while (record) {
|
||||
if (xQueueReceive(queue, &ele, pdMS_TO_TICKS(2000))) {
|
||||
ESP_LOGI(TAG, "Timer alarmed, count=%llu", ele.event_count);
|
||||
record--;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Missed one count event");
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Stop timer");
|
||||
ESP_ERROR_CHECK(gptimer_stop(gptimer));
|
||||
ESP_LOGI(TAG, "Delete timer");
|
||||
ESP_ERROR_CHECK(gptimer_del_timer(gptimer));
|
||||
|
||||
vQueueDelete(queue);
|
||||
}
|
39
examples/peripherals/timer_group/gptimer/pytest_gptimer.py
Normal file
39
examples/peripherals/timer_group/gptimer/pytest_gptimer.py
Normal file
@ -0,0 +1,39 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded.dut import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.esp32c3
|
||||
@pytest.mark.generic
|
||||
def test_gptimer_example(dut: Dut) -> None:
|
||||
dut.expect(r'Create timer handle', timeout=5)
|
||||
dut.expect(r'Start timer, stop it at alarm event', timeout=5)
|
||||
res = dut.expect(r'Timer stopped, count=(\d+)', timeout=30)
|
||||
stopped_count = res.group(1).decode('utf8')
|
||||
assert (1000000 - 10) < int(stopped_count) < (1000000 + 10)
|
||||
|
||||
dut.expect(r'Set count value')
|
||||
dut.expect(r'Get count value')
|
||||
res = dut.expect(r'Timer count value=(\d+)', timeout=5)
|
||||
count_val = res.group(1).decode('utf8')
|
||||
assert int(count_val) == 100
|
||||
|
||||
dut.expect(r'Start timer, auto-reload at alarm event', timeout=5)
|
||||
res = dut.expect(r'Timer reloaded, count=(\d+)', timeout=5)
|
||||
reloaded_count = res.group(1).decode('utf8')
|
||||
assert 0 <= int(reloaded_count) < 10
|
||||
|
||||
dut.expect(r'Stop timer')
|
||||
dut.expect(r'Update alarm value dynamically')
|
||||
for i in range(1,5):
|
||||
res = dut.expect(r'Timer alarmed, count=(\d+)', timeout=5)
|
||||
alarm_count = res.group(1).decode('utf8')
|
||||
assert (i * 1000000 - 10) < int(alarm_count) < (i * 1000000 + 10)
|
||||
|
||||
dut.expect(r'Stop timer')
|
||||
dut.expect(r'Delete timer')
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
@ -10,7 +10,7 @@ from pytest_embedded.dut import Dut
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.esp32c3
|
||||
@pytest.mark.generic
|
||||
def test_timer_group_example(dut: Dut): # type: ignore
|
||||
def test_timer_group_example(dut: Dut) -> None:
|
||||
dut.expect(r'Init timer with auto-reload', timeout=5)
|
||||
res = dut.expect(r'Timer auto reloaded, count value in ISR: (\d+)', timeout=5)
|
||||
reloaded_count = res.group(1).decode('utf8')
|
@ -0,0 +1,4 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN=y
|
@ -14,7 +14,7 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/dac.h"
|
||||
#include "driver/timer.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/* The timer ISR has an execution time of 5.5 micro-seconds(us).
|
||||
@ -22,21 +22,14 @@
|
||||
7 us is a safe interval that will not trigger the watchdog. No need to customize it.
|
||||
*/
|
||||
|
||||
#define WITH_RELOAD 1
|
||||
#define TIMER_INTR_US 7 // Execution time of each ISR interval in micro-seconds
|
||||
#define TIMER_DIVIDER 16
|
||||
#define POINT_ARR_LEN 200 // Length of points array
|
||||
#define AMP_DAC 255 // Amplitude of DAC voltage. If it's more than 256 will causes dac_output_voltage() output 0.
|
||||
#define VDD 3300 // VDD is 3.3V, 3300mV
|
||||
#define CONST_PERIOD_2_PI 6.2832
|
||||
#define SEC_TO_MICRO_SEC(x) ((x) / 1000 / 1000) // Convert second to micro-second
|
||||
#define UNUSED_PARAM __attribute__((unused)) // A const period parameter which equals 2 * pai, used to calculate raw dac output value.
|
||||
#define TIMER_TICKS (TIMER_BASE_CLK / TIMER_DIVIDER) // TIMER_BASE_CLK = APB_CLK = 80MHz
|
||||
#define ALARM_VAL_US SEC_TO_MICRO_SEC(TIMER_INTR_US * TIMER_TICKS) // Alarm value in micro-seconds
|
||||
#define OUTPUT_POINT_NUM (int)(1000000 / (TIMER_INTR_US * FREQ) + 0.5) // The number of output wave points.
|
||||
|
||||
#define DAC_CHAN CONFIG_EXAMPLE_DAC_CHANNEL // DAC_CHANNEL_1 (GPIO25) by default
|
||||
#define FREQ CONFIG_EXAMPLE_WAVE_FREQUENCY // 3kHz by default
|
||||
#define OUTPUT_POINT_NUM (int)(1000000 / (TIMER_INTR_US * FREQ) + 0.5) // The number of output wave points.
|
||||
|
||||
_Static_assert(OUTPUT_POINT_NUM <= POINT_ARR_LEN, "The CONFIG_EXAMPLE_WAVE_FREQUENCY is too low and using too long buffer.");
|
||||
|
||||
@ -47,60 +40,33 @@ static const char *TAG = "wave_gen";
|
||||
static int g_index = 0;
|
||||
|
||||
/* Timer interrupt service routine */
|
||||
static void IRAM_ATTR timer0_ISR(void *ptr)
|
||||
static bool IRAM_ATTR on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
|
||||
{
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
|
||||
int *head = (int*)ptr;
|
||||
int *head = (int *)user_data;
|
||||
|
||||
/* DAC output ISR has an execution time of 4.4 us*/
|
||||
if (g_index >= OUTPUT_POINT_NUM) g_index = 0;
|
||||
if (g_index >= OUTPUT_POINT_NUM) {
|
||||
g_index = 0;
|
||||
}
|
||||
dac_output_voltage(DAC_CHAN, *(head + g_index));
|
||||
g_index++;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Timer group0 TIMER_0 initialization */
|
||||
static void example_timer_init(int timer_idx, bool auto_reload)
|
||||
static void prepare_data(int pnt_num)
|
||||
{
|
||||
esp_err_t ret;
|
||||
timer_config_t config = {
|
||||
.divider = TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.auto_reload = auto_reload,
|
||||
};
|
||||
|
||||
ret = timer_init(TIMER_GROUP_0, timer_idx, &config);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = timer_set_alarm_value(TIMER_GROUP_0, timer_idx, ALARM_VAL_US);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = timer_enable_intr(TIMER_GROUP_0, TIMER_0);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
/* Register an ISR handler */
|
||||
timer_isr_register(TIMER_GROUP_0, timer_idx, timer0_ISR, (void *)raw_val, 0, NULL);
|
||||
}
|
||||
|
||||
static void prepare_data(int pnt_num)
|
||||
{
|
||||
timer_pause(TIMER_GROUP_0, TIMER_0);
|
||||
for (int i = 0; i < pnt_num; i ++) {
|
||||
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
|
||||
raw_val[i] = (int)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(AMP_DAC)/2 + 0.5);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
|
||||
raw_val[i] = (i > (pnt_num/2)) ? (2 * AMP_DAC * (pnt_num - i) / pnt_num) : (2 * AMP_DAC * i / pnt_num);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
|
||||
raw_val[i] = (i == pnt_num) ? 0 : (i * AMP_DAC / pnt_num);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
|
||||
raw_val[i] = (i < (pnt_num/2)) ? AMP_DAC : 0;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
|
||||
raw_val[i] = (int)((sin( i * CONST_PERIOD_2_PI / pnt_num) + 1) * (double)(AMP_DAC) / 2 + 0.5);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
|
||||
raw_val[i] = (i > (pnt_num / 2)) ? (2 * AMP_DAC * (pnt_num - i) / pnt_num) : (2 * AMP_DAC * i / pnt_num);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
|
||||
raw_val[i] = (i == pnt_num) ? 0 : (i * AMP_DAC / pnt_num);
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
|
||||
raw_val[i] = (i < (pnt_num / 2)) ? AMP_DAC : 0;
|
||||
#endif
|
||||
volt_val[i] = (int)(VDD * raw_val[i] / (float)AMP_DAC);
|
||||
}
|
||||
timer_start(TIMER_GROUP_0, TIMER_0);
|
||||
}
|
||||
|
||||
static void log_info(void)
|
||||
@ -111,15 +77,15 @@ static void log_info(void)
|
||||
} else {
|
||||
ESP_LOGI(TAG, "GPIO:%d", GPIO_NUM_26);
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
|
||||
ESP_LOGI(TAG, "Waveform: SINE");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
|
||||
ESP_LOGI(TAG, "Waveform: TRIANGLE");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
|
||||
ESP_LOGI(TAG, "Waveform: SAWTOOTH");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
|
||||
ESP_LOGI(TAG, "Waveform: SQUARE");
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_WAVEFORM_SINE
|
||||
ESP_LOGI(TAG, "Waveform: SINE");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_TRIANGLE
|
||||
ESP_LOGI(TAG, "Waveform: TRIANGLE");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SAWTOOTH
|
||||
ESP_LOGI(TAG, "Waveform: SAWTOOTH");
|
||||
#elif CONFIG_EXAMPLE_WAVEFORM_SQUARE
|
||||
ESP_LOGI(TAG, "Waveform: SQUARE");
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Frequency(Hz): %d", FREQ);
|
||||
ESP_LOGI(TAG, "Output points num: %d\n", OUTPUT_POINT_NUM);
|
||||
@ -127,23 +93,38 @@ static void log_info(void)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
example_timer_init(TIMER_0, WITH_RELOAD);
|
||||
|
||||
ret = dac_output_enable(DAC_CHAN);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
g_index = 0;
|
||||
gptimer_handle_t gptimer = NULL;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
ESP_ERROR_CHECK(dac_output_enable(DAC_CHAN));
|
||||
|
||||
log_info();
|
||||
g_index = 0;
|
||||
prepare_data(OUTPUT_POINT_NUM);
|
||||
|
||||
while(1) {
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = TIMER_INTR_US,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = on_timer_alarm_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, raw_val));
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(10);
|
||||
#if CONFIG_EXAMPLE_LOG_VOLTAGE
|
||||
if (g_index < OUTPUT_POINT_NUM) {
|
||||
ESP_LOGI(TAG, "Output voltage(mV): %d", volt_val[g_index]);
|
||||
ESP_LOGD(TAG, "g_index: %d\n", g_index);
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_EXAMPLE_LOG_VOLTAGE
|
||||
if (g_index < OUTPUT_POINT_NUM) {
|
||||
ESP_LOGI(TAG, "Output voltage(mV): %d", volt_val[g_index]);
|
||||
ESP_LOGD(TAG, "g_index: %d\n", g_index);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,17 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "driver/timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "hal/timer_types.h"
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
#define TIMER_DIVIDER 16
|
||||
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER)
|
||||
#define MS_PER_S 1000
|
||||
#define TIMER_INTERVAL_SEC 2.5
|
||||
#define TEST_WITHOUT_RELOAD 0
|
||||
#define TIMER_RESOLUTION 1000000 // 1MHz, 1 tick = 1us
|
||||
#define TIMER_INTERVAL_US 2500000 // 2.5s
|
||||
#define PROGRESS_INTERVAL_MS 3500
|
||||
#define TIMER_SIGNAL 1
|
||||
#define PROGRESS_SIGNAL 2
|
||||
@ -37,22 +32,10 @@ static const char *TAG = "eventfd_example";
|
||||
static int s_timer_fd;
|
||||
static int s_progress_fd;
|
||||
static TaskHandle_t s_worker_handle;
|
||||
static gptimer_handle_t s_gptimer;
|
||||
|
||||
static bool eventfd_timer_isr_callback(void *arg)
|
||||
static bool eventfd_timer_isr_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
int timer_idx = (int) arg;
|
||||
|
||||
uint32_t timer_intr = timer_group_get_intr_status_in_isr(TIMER_GROUP_0);
|
||||
uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(TIMER_GROUP_0, timer_idx);
|
||||
|
||||
if (timer_intr & TIMER_INTR_T0) {
|
||||
timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
|
||||
timer_counter_value += (uint64_t) (TIMER_INTERVAL_SEC * TIMER_SCALE);
|
||||
timer_group_set_alarm_value_in_isr(TIMER_GROUP_0, timer_idx, timer_counter_value);
|
||||
}
|
||||
|
||||
timer_group_enable_alarm_in_isr(TIMER_GROUP_0, timer_idx);
|
||||
|
||||
uint64_t signal = TIMER_SIGNAL;
|
||||
ssize_t val = write(s_timer_fd, &signal, sizeof(signal));
|
||||
assert(val == sizeof(signal));
|
||||
@ -60,24 +43,26 @@ static bool eventfd_timer_isr_callback(void *arg)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void eventfd_timer_init(int timer_idx, double timer_interval_sec)
|
||||
static void eventfd_timer_init(void)
|
||||
{
|
||||
timer_config_t config = {
|
||||
.divider = TIMER_DIVIDER,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
.alarm_en = TIMER_ALARM_EN,
|
||||
.auto_reload = true,
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = TIMER_RESOLUTION,
|
||||
};
|
||||
ESP_ERROR_CHECK(timer_init(TIMER_GROUP_0, timer_idx, &config));
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &s_gptimer));
|
||||
|
||||
ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0x00000000ULL));
|
||||
|
||||
ESP_ERROR_CHECK(timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE));
|
||||
ESP_ERROR_CHECK(timer_enable_intr(TIMER_GROUP_0, timer_idx));
|
||||
ESP_ERROR_CHECK(timer_isr_callback_add(TIMER_GROUP_0, timer_idx, &eventfd_timer_isr_callback, (void*) timer_idx, 0));
|
||||
|
||||
ESP_ERROR_CHECK(timer_start(TIMER_GROUP_0, timer_idx));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = TIMER_INTERVAL_US,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = eventfd_timer_isr_callback,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(s_gptimer, &cbs, NULL));
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(s_gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_start(s_gptimer));
|
||||
}
|
||||
|
||||
static void worker_task(void *arg)
|
||||
@ -163,7 +148,8 @@ static void collector_task(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
timer_deinit(TIMER_GROUP_0, TIMER_0);
|
||||
gptimer_stop(s_gptimer);
|
||||
gptimer_del_timer(s_gptimer);
|
||||
close(s_timer_fd);
|
||||
close(s_progress_fd);
|
||||
esp_vfs_eventfd_unregister();
|
||||
@ -172,7 +158,7 @@ static void collector_task(void *arg)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
eventfd_timer_init(TIMER_0, TIMER_INTERVAL_SEC);
|
||||
eventfd_timer_init();
|
||||
/* Save the handle for this task as we will need to notify it */
|
||||
xTaskCreate(worker_task, "worker_task", 4 * 1024, NULL, 5, &s_worker_handle);
|
||||
xTaskCreate(collector_task, "collector_task", 4 * 1024, NULL, 5, NULL);
|
||||
|
@ -13,19 +13,10 @@
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/timer.h"
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
typedef struct {
|
||||
int group;
|
||||
int timer;
|
||||
int count;
|
||||
TaskHandle_t thnd;
|
||||
} example_event_data_t;
|
||||
|
||||
|
||||
#if CONFIG_APPTRACE_SV_ENABLE
|
||||
#if !CONFIG_USE_CUSTOM_EVENT_ID
|
||||
|
||||
@ -72,7 +63,7 @@ static void example_sysview_event_send(uint32_t id, uint32_t val)
|
||||
SEGGER_SYSVIEW_SendPacket(&aPacket[0], pPayload, s_example_sysview_module.EventOffset + id);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // !CONFIG_USE_CUSTOM_EVENT_ID
|
||||
|
||||
#else
|
||||
|
||||
@ -81,36 +72,20 @@ static void example_sysview_event_send(uint32_t id, uint32_t val)
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_START()
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_END(_val_)
|
||||
|
||||
#endif
|
||||
#endif // CONFIG_APPTRACE_SV_ENABLE
|
||||
|
||||
typedef struct {
|
||||
gptimer_handle_t gptimer;
|
||||
int count;
|
||||
TaskHandle_t thnd;
|
||||
uint64_t period;
|
||||
char task_name[32];
|
||||
} example_event_data_t;
|
||||
|
||||
static void example_timer_init(int timer_group, int timer_idx, uint32_t period)
|
||||
static bool example_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
uint64_t alarm_val = (period * (TIMER_BASE_CLK / 1000000UL)) / 2;
|
||||
|
||||
timer_config_t config = {
|
||||
.alarm_en = 1,
|
||||
.auto_reload = 1,
|
||||
.counter_dir = TIMER_COUNT_UP,
|
||||
.divider = 2, //Range is 2 to 65536
|
||||
.intr_type = TIMER_INTR_LEVEL,
|
||||
.counter_en = TIMER_PAUSE,
|
||||
};
|
||||
/*Configure timer*/
|
||||
timer_init(timer_group, timer_idx, &config);
|
||||
/*Stop timer counter*/
|
||||
timer_pause(timer_group, timer_idx);
|
||||
/*Load counter value */
|
||||
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
|
||||
/*Set alarm value*/
|
||||
timer_set_alarm_value(timer_group, timer_idx, alarm_val);
|
||||
/*Enable timer interrupt*/
|
||||
timer_enable_intr(timer_group, timer_idx);
|
||||
}
|
||||
|
||||
static void example_timer_isr(void *arg)
|
||||
{
|
||||
example_event_data_t *tim_arg = (example_event_data_t *)arg;
|
||||
example_event_data_t *tim_arg = (example_event_data_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
if (tim_arg->thnd != NULL) {
|
||||
if (tim_arg->count++ < 10) {
|
||||
@ -121,35 +96,32 @@ static void example_timer_isr(void *arg)
|
||||
} else {
|
||||
SYSVIEW_EXAMPLE_SEND_EVENT_END(tim_arg->count);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// re-start timer
|
||||
timer_group_clr_intr_status_in_isr(tim_arg->group, tim_arg->timer);
|
||||
timer_group_enable_alarm_in_isr(tim_arg->group, tim_arg->timer);
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static void example_task(void *p)
|
||||
{
|
||||
uint32_t event_val;
|
||||
example_event_data_t *arg = (example_event_data_t *) p;
|
||||
timer_isr_handle_t inth;
|
||||
|
||||
ESP_LOGI(TAG, "%p: run task", xTaskGetCurrentTaskHandle());
|
||||
|
||||
esp_err_t res = timer_isr_register(arg->group, arg->timer, example_timer_isr, arg, 0, &inth);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%p: failed to register timer ISR", xTaskGetCurrentTaskHandle());
|
||||
} else {
|
||||
res = timer_start(arg->group, arg->timer);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%p: failed to start timer", xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
}
|
||||
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = arg->period,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
// This task is pinned to a specific core, to the interrupt will also be install to that core
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_alarm_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(arg->gptimer, &cbs, arg));
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(arg->gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_start(arg->gptimer));
|
||||
while (1) {
|
||||
uint32_t event_val;
|
||||
SYSVIEW_EXAMPLE_WAIT_EVENT_START();
|
||||
xTaskNotifyWait(0, 0, &event_val, portMAX_DELAY);
|
||||
SYSVIEW_EXAMPLE_WAIT_EVENT_END(event_val);
|
||||
@ -159,18 +131,7 @@ static void example_task(void *p)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static example_event_data_t event_data[portNUM_PROCESSORS] = {
|
||||
{
|
||||
.group = TIMER_GROUP_1,
|
||||
.timer = TIMER_0,
|
||||
},
|
||||
#if CONFIG_FREERTOS_UNICORE == 0
|
||||
{
|
||||
.group = TIMER_GROUP_0,
|
||||
.timer = TIMER_0,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
static example_event_data_t event_data[portNUM_PROCESSORS];
|
||||
|
||||
#if CONFIG_APPTRACE_SV_ENABLE && CONFIG_USE_CUSTOM_EVENT_ID
|
||||
// Currently OpenOCD does not support requesting module info from target. So do the following...
|
||||
@ -183,22 +144,19 @@ void app_main(void)
|
||||
SEGGER_SYSVIEW_RegisterModule(&s_example_sysview_module);
|
||||
#endif
|
||||
|
||||
#if !CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_10
|
||||
example_timer_init(TIMER_GROUP_1, TIMER_0, 2000);
|
||||
#else
|
||||
#warning "Timer (Group 1, Timer 0) is used by sysview module itself!"
|
||||
#endif
|
||||
for (int i = 0; i < portNUM_PROCESSORS; i++) {
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_APB,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &event_data[i].gptimer));
|
||||
event_data[i].period = 1000000 * (i + 1);
|
||||
}
|
||||
|
||||
#if !CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_00
|
||||
example_timer_init(TIMER_GROUP_0, TIMER_0, 4000);
|
||||
#else
|
||||
#warning "Timer (Group 0, Timer 0) is used by sysview module itself!"
|
||||
#endif
|
||||
|
||||
xTaskCreatePinnedToCore(example_task, "svtrace0", 2048, &event_data[0], 3, &event_data[0].thnd, 0);
|
||||
ESP_LOGI(TAG, "Created task %p", event_data[0].thnd);
|
||||
#if CONFIG_FREERTOS_UNICORE == 0
|
||||
xTaskCreatePinnedToCore(example_task, "svtrace1", 2048, &event_data[1], 3, &event_data[1].thnd, 1);
|
||||
ESP_LOGI(TAG, "Created task %p", event_data[1].thnd);
|
||||
#endif
|
||||
for (int i = 0; i < portNUM_PROCESSORS; i++) {
|
||||
sprintf(event_data->task_name, "svtrace%d", i);
|
||||
xTaskCreatePinnedToCore(example_task, event_data->task_name, 4096, &event_data[i], 3, &event_data[i].thnd, i);
|
||||
ESP_LOGI(TAG, "Created task %p", event_data[i].thnd);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ CONFIG_APPTRACE_MEMBUFS_APPTRACE_PROTO_ENABLE=y
|
||||
CONFIG_APPTRACE_ENABLE=y
|
||||
# Enable FreeRTOS SystemView Tracing by default
|
||||
CONFIG_APPTRACE_SV_ENABLE=y
|
||||
CONFIG_APPTRACE_SV_TS_SOURCE_TIMER_00=y
|
||||
CONFIG_APPTRACE_SV_TS_SOURCE_GPTIMER=y
|
||||
CONFIG_APPTRACE_SV_EVT_OVERFLOW_ENABLE=y
|
||||
CONFIG_APPTRACE_SV_EVT_ISR_ENTER_ENABLE=y
|
||||
CONFIG_APPTRACE_SV_EVT_ISR_EXIT_ENABLE=y
|
||||
|
@ -57,6 +57,14 @@ examples_and_unit_tests:
|
||||
- CC0-1.0
|
||||
license_for_new_files: Unlicense OR CC0-1.0
|
||||
|
||||
freemodbus_component:
|
||||
include:
|
||||
- 'components/freemodbus/**'
|
||||
allowed_licenses:
|
||||
- Apache-2.0
|
||||
- BSD-3-Clause
|
||||
license_for_new_files: Apache-2.0
|
||||
|
||||
# files matching this section do not perform the check
|
||||
# file patterns starting with ! are negated, meaning files matching them won't match the section.
|
||||
ignore:
|
||||
|
@ -944,7 +944,6 @@ components/freertos/test/test_task_priorities.c
|
||||
components/freertos/test/test_task_suspend_resume.c
|
||||
components/freertos/test/test_tasks_snapshot.c
|
||||
components/freertos/test/test_thread_local.c
|
||||
components/freertos/test/test_timers.c
|
||||
components/freertos/test/test_xtensa_loadstore_handler.c
|
||||
components/hal/aes_hal.c
|
||||
components/hal/cpu_hal.c
|
||||
@ -2106,7 +2105,6 @@ components/vfs/include/esp_vfs_eventfd.h
|
||||
components/vfs/include/esp_vfs_semihost.h
|
||||
components/vfs/test/test_vfs_access.c
|
||||
components/vfs/test/test_vfs_append.c
|
||||
components/vfs/test/test_vfs_eventfd.c
|
||||
components/vfs/test/test_vfs_fd.c
|
||||
components/vfs/test/test_vfs_lwip.c
|
||||
components/vfs/test/test_vfs_paths.c
|
||||
@ -2582,8 +2580,6 @@ examples/peripherals/lcd/tjpgd/main/pretty_effect.h
|
||||
examples/peripherals/ledc/ledc_basic/main/ledc_basic_example_main.c
|
||||
examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c
|
||||
examples/peripherals/mcpwm/mcpwm_bldc_hall_control/main/mcpwm_bldc_hall_control_example_main.c
|
||||
examples/peripherals/mcpwm/mcpwm_brushed_dc_control/components/motor_ctrl_timer/motor_ctrl_timer.c
|
||||
examples/peripherals/mcpwm/mcpwm_brushed_dc_control/components/motor_ctrl_timer/motor_ctrl_timer.h
|
||||
examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/cmd_mcpwm_motor.c
|
||||
examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_control_example.c
|
||||
examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_control_example.h
|
||||
|
@ -1,41 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "test_utils.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "soc/periph_defs.h"
|
||||
#include "hal/timer_hal.h"
|
||||
#include "hal/timer_ll.h"
|
||||
#include "unity_config.h"
|
||||
#include "driver/gptimer.h"
|
||||
|
||||
#define TIMER_GROUP_ID (1)
|
||||
#define TIMER_ID (0)
|
||||
static timer_hal_context_t timer_hal;
|
||||
static gptimer_handle_t ts_gptimer;
|
||||
|
||||
void ref_clock_init(void)
|
||||
{
|
||||
periph_module_enable(PERIPH_TIMG1_MODULE);
|
||||
timer_hal_init(&timer_hal, TIMER_GROUP_ID, TIMER_ID);
|
||||
timer_ll_set_clock_source(timer_hal.dev, TIMER_ID, GPTIMER_CLK_SRC_XTAL); // Select XTAL, so ref_clock is independent of the APL clock
|
||||
timer_ll_enable_counter(timer_hal.dev, TIMER_ID, false); // stop timer from running
|
||||
timer_ll_set_clock_prescale(timer_hal.dev, TIMER_ID, 40); // Resolution is configured to 1MHz
|
||||
timer_ll_set_count_direction(timer_hal.dev, timer_hal.timer_id, GPTIMER_COUNT_UP); // increase mode
|
||||
timer_hal_set_counter_value(&timer_hal, 0); // initial count value to zero
|
||||
timer_ll_enable_intr(timer_hal.dev, TIMER_LL_EVENT_ALARM(TIMER_ID), false); // disable interrupt
|
||||
timer_ll_enable_alarm(timer_hal.dev, TIMER_ID, false); // alarm event is not needed
|
||||
timer_ll_enable_auto_reload(timer_hal.dev, TIMER_ID, false);
|
||||
timer_ll_enable_counter(timer_hal.dev, TIMER_ID, true); // start counter
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_XTAL,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // Resolution is configured to 1MHz
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_config, &ts_gptimer));
|
||||
TEST_ESP_OK(gptimer_start(ts_gptimer));
|
||||
}
|
||||
|
||||
void ref_clock_deinit(void)
|
||||
{
|
||||
timer_ll_enable_counter(timer_hal.dev, TIMER_ID, false); // stop timer from running
|
||||
periph_module_disable(PERIPH_TIMG1_MODULE);
|
||||
TEST_ESP_OK(gptimer_stop(ts_gptimer));
|
||||
TEST_ESP_OK(gptimer_del_timer(ts_gptimer));
|
||||
ts_gptimer = NULL;
|
||||
}
|
||||
|
||||
uint64_t ref_clock_get(void)
|
||||
{
|
||||
return timer_ll_get_counter_value(timer_hal.dev, timer_hal.timer_id);
|
||||
uint64_t ts;
|
||||
gptimer_get_raw_count(ts_gptimer, &ts);
|
||||
return ts;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user