mirror of
https://github.com/espressif/esp-idf
synced 2025-03-10 01:29:21 -04:00
Merge branch 'feature/pcnt_driver_ng' into 'master'
⛵ Driver-NG: Introduce a brand new driver for PCNT Closes IDF-2196 and IDF-2896 See merge request espressif/esp-idf!10507
This commit is contained in:
commit
52fcdbacc3
@ -692,13 +692,6 @@ UT_008:
|
||||
- UT_T1_GPIO
|
||||
- psram
|
||||
|
||||
UT_012:
|
||||
extends: .unit_test_esp32_template
|
||||
tags:
|
||||
- ESP32_IDF
|
||||
- UT_T1_LEDC
|
||||
- psram
|
||||
|
||||
UT_014:
|
||||
extends: .unit_test_esp32_template
|
||||
tags:
|
||||
@ -782,13 +775,6 @@ UT_036:
|
||||
- UT_T1_PSRAMV0
|
||||
- psram
|
||||
|
||||
# ToDo: re-enable this job when ESP32-S2 LEDC runner installed
|
||||
# UT_037:
|
||||
# extends: .unit_test_esp32s2_template
|
||||
# tags:
|
||||
# - ESP32S2_IDF
|
||||
# - UT_T1_LEDC
|
||||
|
||||
UT_038:
|
||||
extends: .unit_test_esp32s2_template
|
||||
parallel: 2
|
||||
|
@ -48,7 +48,7 @@ if(CONFIG_SOC_RMT_SUPPORTED)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_PCNT_SUPPORTED)
|
||||
list(APPEND srcs "pcnt.c")
|
||||
list(APPEND srcs "pcnt_legacy.c" "pulse_cnt.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_SDMMC_HOST_SUPPORTED)
|
||||
|
@ -22,7 +22,7 @@ menu "Driver configurations"
|
||||
endmenu # ADC Configuration
|
||||
|
||||
menu "MCPWM configuration"
|
||||
|
||||
depends on SOC_MCPWM_SUPPORTED
|
||||
config MCPWM_ISR_IN_IRAM
|
||||
bool "Place MCPWM ISR function into IRAM"
|
||||
default n
|
||||
@ -32,7 +32,6 @@ menu "Driver configurations"
|
||||
|
||||
Note that if this option is selected, all user registered ISR callbacks should never
|
||||
try to use cache as well. (with IRAM_ATTR)
|
||||
|
||||
endmenu # MCPWM Configuration
|
||||
|
||||
menu "SPI configuration"
|
||||
@ -158,8 +157,6 @@ menu "Driver configurations"
|
||||
endmenu # UART Configuration
|
||||
|
||||
menu "GPIO Configuration"
|
||||
visible if IDF_TARGET_ESP32
|
||||
|
||||
config GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL
|
||||
bool "Support light sleep GPIO pullup/pulldown configuration for ESP32"
|
||||
depends on IDF_TARGET_ESP32
|
||||
@ -168,10 +165,10 @@ menu "Driver configurations"
|
||||
pullup/pulldown mode in sleep.
|
||||
If this option is selected, chip will automatically emulate the behaviour of switching,
|
||||
and about 450B of source codes would be placed into IRAM.
|
||||
|
||||
endmenu # GPIO Configuration
|
||||
|
||||
menu "GDMA Configuration"
|
||||
depends on SOC_GDMA_SUPPORTED
|
||||
config GDMA_CTRL_FUNC_IN_IRAM
|
||||
bool "Place GDMA control functions into IRAM"
|
||||
default n
|
||||
@ -202,17 +199,56 @@ menu "Driver configurations"
|
||||
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)
|
||||
Ensure the GPTimer interrupt is IRAM-Safe by allowing the interrupt handler to be
|
||||
executable when the cache is disabled (e.g. SPI Flash write).
|
||||
|
||||
config GPTIMER_SUPPRESS_DEPRECATE_WARN
|
||||
bool "Suppress leagcy driver deprecated warning"
|
||||
bool "Suppress legacy 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.
|
||||
|
||||
config GPTIMER_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
default n
|
||||
help
|
||||
Wether to enable the debug log message for GPTimer driver.
|
||||
Note that, this option only controls the GPTimer driver log, won't affect other drivers.
|
||||
endmenu # GPTimer Configuration
|
||||
|
||||
menu "PCNT Configuration"
|
||||
depends on SOC_PCNT_SUPPORTED
|
||||
config PCNT_CTRL_FUNC_IN_IRAM
|
||||
bool "Place PCNT control functions into IRAM"
|
||||
default n
|
||||
help
|
||||
Place PCNT 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 PCNT_ISR_IRAM_SAFE
|
||||
bool "PCNT ISR IRAM-Safe"
|
||||
default n
|
||||
help
|
||||
Ensure the PCNT interrupt is IRAM-Safe by allowing the interrupt handler to be
|
||||
executable when the cache is disabled (e.g. SPI Flash write).
|
||||
|
||||
config PCNT_SUPPRESS_DEPRECATE_WARN
|
||||
bool "Suppress legacy driver deprecated warning"
|
||||
default n
|
||||
help
|
||||
Wether to suppress the deprecation warnings when using legacy PCNT driver (driver/pcnt.h).
|
||||
If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
|
||||
you can enable this option.
|
||||
|
||||
config PCNT_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
default n
|
||||
help
|
||||
Wether to enable the debug log message for PCNT driver.
|
||||
Note that, this option only controls the PCNT driver log, won't affect other drivers.
|
||||
endmenu # PCNT Configuration
|
||||
|
||||
endmenu # Driver configurations
|
||||
|
@ -1,109 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_types.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/pcnt_types.h"
|
||||
#include "driver/pcnt_types_legacy.h"
|
||||
|
||||
#if !CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN
|
||||
#warning "legacy pcnt driver is deprecated, please migrate to use driver/pulse_cnt.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PCNT_PIN_NOT_USED (-1) /*!< When selected for a pin, this pin will not be used */
|
||||
|
||||
typedef intr_handle_t pcnt_isr_handle_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT port number, the max port number is (PCNT_PORT_MAX - 1).
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_PORT_0, /*!< PCNT port 0 */
|
||||
PCNT_PORT_MAX, /*!< PCNT port max */
|
||||
} pcnt_port_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of all available PCNT units
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_UNIT_0, /*!< PCNT unit 0 */
|
||||
PCNT_UNIT_1, /*!< PCNT unit 1 */
|
||||
PCNT_UNIT_2, /*!< PCNT unit 2 */
|
||||
PCNT_UNIT_3, /*!< PCNT unit 3 */
|
||||
#if SOC_PCNT_UNITS_PER_GROUP > 4
|
||||
PCNT_UNIT_4, /*!< PCNT unit 4 */
|
||||
PCNT_UNIT_5, /*!< PCNT unit 5 */
|
||||
PCNT_UNIT_6, /*!< PCNT unit 6 */
|
||||
PCNT_UNIT_7, /*!< PCNT unit 7 */
|
||||
#endif
|
||||
PCNT_UNIT_MAX,
|
||||
} pcnt_unit_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of channels available for a single PCNT unit
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_CHANNEL_0, /*!< PCNT channel 0 */
|
||||
PCNT_CHANNEL_1, /*!< PCNT channel 1 */
|
||||
PCNT_CHANNEL_MAX,
|
||||
} pcnt_channel_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of counter's events the may trigger an interrupt
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_EVT_THRES_1 = 1 << 2, /*!< PCNT watch point event: threshold1 value event */
|
||||
PCNT_EVT_THRES_0 = 1 << 3, /*!< PCNT watch point event: threshold0 value event */
|
||||
PCNT_EVT_L_LIM = 1 << 4, /*!< PCNT watch point event: Minimum counter value */
|
||||
PCNT_EVT_H_LIM = 1 << 5, /*!< PCNT watch point event: Maximum counter value */
|
||||
PCNT_EVT_ZERO = 1 << 6, /*!< PCNT watch point event: counter value zero event */
|
||||
PCNT_EVT_MAX
|
||||
} pcnt_evt_type_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of available modes that determine the counter's action depending on the state of the control signal's input GPIO
|
||||
* @note Configuration covers two actions, one for high, and one for low level on the control input
|
||||
*/
|
||||
typedef pcnt_channel_level_action_t pcnt_ctrl_mode_t;
|
||||
#define PCNT_MODE_KEEP PCNT_CHANNEL_LEVEL_ACTION_KEEP /*!< Control mode: won't change counter mode*/
|
||||
#define PCNT_MODE_REVERSE PCNT_CHANNEL_LEVEL_ACTION_INVERSE /*!< Control mode: invert counter mode(increase -> decrease, decrease -> increase) */
|
||||
#define PCNT_MODE_DISABLE PCNT_CHANNEL_LEVEL_ACTION_HOLD /*!< Control mode: Inhibit counter(counter value will not change in this condition) */
|
||||
#define PCNT_MODE_MAX 3
|
||||
|
||||
/**
|
||||
* @brief Selection of available modes that determine the counter's action on the edge of the pulse signal's input GPIO
|
||||
* @note Configuration covers two actions, one for positive, and one for negative edge on the pulse input
|
||||
*/
|
||||
typedef pcnt_channel_edge_action_t pcnt_count_mode_t;
|
||||
#define PCNT_COUNT_DIS PCNT_CHANNEL_EDGE_ACTION_HOLD /*!< Counter mode: Inhibit counter(counter value will not change in this condition) */
|
||||
#define PCNT_COUNT_INC PCNT_CHANNEL_EDGE_ACTION_INCREASE /*!< Counter mode: Increase counter value */
|
||||
#define PCNT_COUNT_DEC PCNT_CHANNEL_EDGE_ACTION_DECREASE /*!< Counter mode: Decrease counter value */
|
||||
#define PCNT_COUNT_MAX 3
|
||||
|
||||
/**
|
||||
* @brief Pulse Counter configuration for a single channel
|
||||
*/
|
||||
typedef struct {
|
||||
int pulse_gpio_num; /*!< Pulse input GPIO number, if you want to use GPIO16, enter pulse_gpio_num = 16, a negative value will be ignored */
|
||||
int ctrl_gpio_num; /*!< Control signal input GPIO number, a negative value will be ignored */
|
||||
pcnt_ctrl_mode_t lctrl_mode; /*!< PCNT low control mode */
|
||||
pcnt_ctrl_mode_t hctrl_mode; /*!< PCNT high control mode */
|
||||
pcnt_count_mode_t pos_mode; /*!< PCNT positive edge count mode */
|
||||
pcnt_count_mode_t neg_mode; /*!< PCNT negative edge count mode */
|
||||
int16_t counter_h_lim; /*!< Maximum counter value */
|
||||
int16_t counter_l_lim; /*!< Minimum counter value */
|
||||
pcnt_unit_t unit; /*!< PCNT unit number */
|
||||
pcnt_channel_t channel; /*!< the PCNT channel */
|
||||
} pcnt_config_t;
|
||||
|
||||
/**
|
||||
* @brief Configure Pulse Counter unit
|
||||
* @note
|
108
components/driver/deprecated/driver/pcnt_types_legacy.h
Normal file
108
components/driver/deprecated/driver/pcnt_types_legacy.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/pcnt_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PCNT_PIN_NOT_USED (-1) /*!< When selected for a pin, this pin will not be used */
|
||||
|
||||
/**
|
||||
* @brief PCNT interrupt handle
|
||||
*/
|
||||
typedef intr_handle_t pcnt_isr_handle_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT port number, the max port number is (PCNT_PORT_MAX - 1).
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_PORT_0, /*!< PCNT port 0 */
|
||||
PCNT_PORT_MAX, /*!< PCNT port max */
|
||||
} pcnt_port_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of all available PCNT units
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_UNIT_0, /*!< PCNT unit 0 */
|
||||
PCNT_UNIT_1, /*!< PCNT unit 1 */
|
||||
PCNT_UNIT_2, /*!< PCNT unit 2 */
|
||||
PCNT_UNIT_3, /*!< PCNT unit 3 */
|
||||
#if SOC_PCNT_UNITS_PER_GROUP > 4
|
||||
PCNT_UNIT_4, /*!< PCNT unit 4 */
|
||||
PCNT_UNIT_5, /*!< PCNT unit 5 */
|
||||
PCNT_UNIT_6, /*!< PCNT unit 6 */
|
||||
PCNT_UNIT_7, /*!< PCNT unit 7 */
|
||||
#endif
|
||||
PCNT_UNIT_MAX,
|
||||
} pcnt_unit_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of channels available for a single PCNT unit
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_CHANNEL_0, /*!< PCNT channel 0 */
|
||||
PCNT_CHANNEL_1, /*!< PCNT channel 1 */
|
||||
PCNT_CHANNEL_MAX,
|
||||
} pcnt_channel_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of counter's events the may trigger an interrupt
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_EVT_THRES_1 = 1 << 2, /*!< PCNT watch point event: threshold1 value event */
|
||||
PCNT_EVT_THRES_0 = 1 << 3, /*!< PCNT watch point event: threshold0 value event */
|
||||
PCNT_EVT_L_LIM = 1 << 4, /*!< PCNT watch point event: Minimum counter value */
|
||||
PCNT_EVT_H_LIM = 1 << 5, /*!< PCNT watch point event: Maximum counter value */
|
||||
PCNT_EVT_ZERO = 1 << 6, /*!< PCNT watch point event: counter value zero event */
|
||||
PCNT_EVT_MAX
|
||||
} pcnt_evt_type_t;
|
||||
|
||||
/**
|
||||
* @brief Selection of available modes that determine the counter's action depending on the state of the control signal's input GPIO
|
||||
* @note Configuration covers two actions, one for high, and one for low level on the control input
|
||||
*/
|
||||
typedef pcnt_channel_level_action_t pcnt_ctrl_mode_t;
|
||||
#define PCNT_MODE_KEEP PCNT_CHANNEL_LEVEL_ACTION_KEEP /*!< Control mode: won't change counter mode*/
|
||||
#define PCNT_MODE_REVERSE PCNT_CHANNEL_LEVEL_ACTION_INVERSE /*!< Control mode: invert counter mode(increase -> decrease, decrease -> increase) */
|
||||
#define PCNT_MODE_DISABLE PCNT_CHANNEL_LEVEL_ACTION_HOLD /*!< Control mode: Inhibit counter(counter value will not change in this condition) */
|
||||
#define PCNT_MODE_MAX 3
|
||||
|
||||
/**
|
||||
* @brief Selection of available modes that determine the counter's action on the edge of the pulse signal's input GPIO
|
||||
* @note Configuration covers two actions, one for positive, and one for negative edge on the pulse input
|
||||
*/
|
||||
typedef pcnt_channel_edge_action_t pcnt_count_mode_t;
|
||||
#define PCNT_COUNT_DIS PCNT_CHANNEL_EDGE_ACTION_HOLD /*!< Counter mode: Inhibit counter(counter value will not change in this condition) */
|
||||
#define PCNT_COUNT_INC PCNT_CHANNEL_EDGE_ACTION_INCREASE /*!< Counter mode: Increase counter value */
|
||||
#define PCNT_COUNT_DEC PCNT_CHANNEL_EDGE_ACTION_DECREASE /*!< Counter mode: Decrease counter value */
|
||||
#define PCNT_COUNT_MAX 3
|
||||
|
||||
/**
|
||||
* @brief Pulse Counter configuration for a single channel
|
||||
*/
|
||||
typedef struct {
|
||||
int pulse_gpio_num; /*!< Pulse input GPIO number, if you want to use GPIO16, enter pulse_gpio_num = 16, a negative value will be ignored */
|
||||
int ctrl_gpio_num; /*!< Control signal input GPIO number, a negative value will be ignored */
|
||||
pcnt_ctrl_mode_t lctrl_mode; /*!< PCNT low control mode */
|
||||
pcnt_ctrl_mode_t hctrl_mode; /*!< PCNT high control mode */
|
||||
pcnt_count_mode_t pos_mode; /*!< PCNT positive edge count mode */
|
||||
pcnt_count_mode_t neg_mode; /*!< PCNT negative edge count mode */
|
||||
int16_t counter_h_lim; /*!< Maximum counter value */
|
||||
int16_t counter_l_lim; /*!< Minimum counter value */
|
||||
pcnt_unit_t unit; /*!< PCNT unit number */
|
||||
pcnt_channel_t channel; /*!< the PCNT channel */
|
||||
} pcnt_config_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -4,10 +4,14 @@
|
||||
* 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 "sdkconfig.h"
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
@ -90,7 +94,7 @@ 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);
|
||||
IRAM_ATTR static void gptimer_default_isr(void *args);
|
||||
static void gptimer_default_isr(void *args);
|
||||
|
||||
esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
|
||||
{
|
||||
@ -205,7 +209,7 @@ esp_err_t gptimer_del_timer(gptimer_handle_t timer)
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
timer_hal_set_counter_value(&timer->hal, value);
|
||||
@ -215,7 +219,7 @@ esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_FALSE_ISR(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);
|
||||
@ -252,9 +256,9 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
|
||||
}
|
||||
|
||||
// enable/disable GPTimer interrupt events
|
||||
portENTER_CRITICAL_SAFE(&group->spinlock);
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id), cbs->on_alarm != NULL); // enable timer interrupt
|
||||
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
timer->on_alarm = cbs->on_alarm;
|
||||
timer->user_ctx = user_data;
|
||||
@ -263,11 +267,11 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_FALSE_ISR(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");
|
||||
ESP_RETURN_ON_FALSE_ISR(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;
|
||||
@ -292,15 +296,15 @@ esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_c
|
||||
|
||||
esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE_ISR(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");
|
||||
ESP_RETURN_ON_ERROR_ISR(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");
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
@ -314,7 +318,7 @@ esp_err_t gptimer_start(gptimer_handle_t timer)
|
||||
|
||||
esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
// disable counter, alarm, autoreload
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
@ -325,11 +329,11 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
|
||||
// disable interrupt service
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
|
||||
ESP_RETURN_ON_ERROR_ISR(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");
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@ -337,7 +341,9 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
|
||||
|
||||
static gptimer_group_t *gptimer_acquire_group_handle(int group_id)
|
||||
{
|
||||
// esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
bool new_group = false;
|
||||
gptimer_group_t *group = NULL;
|
||||
|
||||
|
@ -132,7 +132,9 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
|
||||
|
||||
/**
|
||||
* @brief Set LEDC output gpio.
|
||||
* @deprecated This function is redundant, please use ledc_channel_config to set gpio pins.
|
||||
*
|
||||
* @note This function only routes the LEDC signal to GPIO through matrix, other LEDC resources initialization are not involved.
|
||||
* Please use `ledc_channel_config()` instead to fully configure a LEDC channel.
|
||||
*
|
||||
* @param gpio_num The LEDC output gpio
|
||||
* @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode.
|
||||
@ -142,8 +144,8 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Parameter error
|
||||
*/
|
||||
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel)
|
||||
__attribute__((deprecated("use ledc_channel_config instead")));
|
||||
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel);
|
||||
|
||||
/**
|
||||
* @brief LEDC stop.
|
||||
* Disable LEDC output, and set idle level
|
||||
|
296
components/driver/include/driver/pulse_cnt.h
Normal file
296
components/driver/include/driver/pulse_cnt.h
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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/pcnt_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Type of PCNT unit handle
|
||||
*/
|
||||
typedef struct pcnt_unit_t *pcnt_unit_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of PCNT channel handle
|
||||
*/
|
||||
typedef struct pcnt_chan_t *pcnt_channel_handle_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT watch event data
|
||||
*/
|
||||
typedef struct {
|
||||
int watch_point_value; /*!< Watch point value that triggered the event */
|
||||
pcnt_unit_zero_cross_mode_t zero_cross_mode; /*!< Zero cross mode */
|
||||
} pcnt_watch_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT watch event callback prototype
|
||||
*
|
||||
* @note The callback function is invoked from an ISR context, so it should meet the restrictions of not calling any blocking APIs when implementing the callback.
|
||||
* e.g. must use ISR version of FreeRTOS APIs.
|
||||
*
|
||||
* @param[in] unit PCNT unit handle
|
||||
* @param[in] edata PCNT event data, fed by the driver
|
||||
* @param[in] user_ctx User data, passed from `pcnt_unit_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been woken up by this function
|
||||
*/
|
||||
typedef bool (*pcnt_watch_cb_t)(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief Group of supported PCNT callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
* @note When CONFIG_PCNT_ISR_IRAM_SAFE is enabled, the callback itself and functions callbed by it should be placed in IRAM.
|
||||
*/
|
||||
typedef struct {
|
||||
pcnt_watch_cb_t on_reach; /*!< Called when PCNT unit counter reaches any watch point */
|
||||
} pcnt_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT unit configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int low_limit; /*!< Low limitation of the count unit, should be lower than 0 */
|
||||
int high_limit; /*!< High limitation of the count unit, should be higher than 0 */
|
||||
} pcnt_unit_config_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT channel configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int edge_gpio_num; /*!< GPIO number used by the edge signal, input mode with pull up enabled. Set to -1 if unused */
|
||||
int level_gpio_num; /*!< GPIO number used by the level signal, input mode with pull up enabled. Set to -1 if unused */
|
||||
struct {
|
||||
uint32_t invert_edge_input: 1; /*!< Invert the input edge signal */
|
||||
uint32_t invert_level_input: 1; /*!< Invert the input level signal */
|
||||
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
|
||||
} flags;
|
||||
} pcnt_chan_config_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT glitch filter configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t max_glitch_ns; /*!< Pulse width smaller than this threshold will be treated as glitch and ignored, in the unit of ns */
|
||||
} pcnt_glitch_filter_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create a new PCNT unit, and return the handle
|
||||
*
|
||||
* @note The newly created PCNT unit is put into the stopped state.
|
||||
*
|
||||
* @param[in] config PCNT unit configuration
|
||||
* @param[out] ret_unit Returned PCNT unit handle
|
||||
* @return
|
||||
* - ESP_OK: Create PCNT unit successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create PCNT unit failed because of invalid argument (e.g. high/low limit value out of the range)
|
||||
* - ESP_ERR_NO_MEM: Create PCNT unit failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create PCNT unit failed because all PCNT units are used up and no more free one
|
||||
* - ESP_FAIL: Create PCNT unit failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit);
|
||||
|
||||
/**
|
||||
* @brief Delete the PCNT unit handle
|
||||
*
|
||||
* @note Users must ensure that the PCNT unit is stopped before deleting the unit. Users can force a working unit into the stopped state via `pcnt_unit_stop()`.
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @return
|
||||
* - ESP_OK: Delete the PCNT unit successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete the PCNT unit failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Delete the PCNT unit failed because corresponding PCNT channels and/or watch points are still in working
|
||||
* - ESP_FAIL: Delete the PCNT unit failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit);
|
||||
|
||||
/**
|
||||
* @brief Set glitch filter for PCNT unit
|
||||
*
|
||||
* @note The glitch filter module is clocked from APB, and APB frequency can be changed during DFS, which in return make the filter out of action.
|
||||
* So this function will lazy-install a PM lock internally when the power management is enabled. With this lock, the APB frequency won't be changed.
|
||||
* The PM lock can only be uninstalled in `pcnt_del_unit()`.
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] config PCNT filter configuration, set config to NULL means disabling the filter function
|
||||
* @return
|
||||
* - ESP_OK: Set glitch filter successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set glitch filter failed because of invalid argument (e.g. glitch width is too big)
|
||||
* - ESP_FAIL: Set glitch filter failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Start the PCNT unit, the counter will start to count according to the edge and/or level input signals
|
||||
*
|
||||
* @note This function will acquire the PM lock when power management is enabled. Also see `pcnt_unit_set_glitch_filter()` for the condition of PM lock installation.
|
||||
* @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_stop()`.
|
||||
* @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @return
|
||||
* - ESP_OK: Start PCNT unit successfully
|
||||
* - ESP_ERR_INVALID_ARG: Start PCNT unit failed because of invalid argument
|
||||
* - ESP_FAIL: Start PCNT unit failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit);
|
||||
|
||||
/**
|
||||
* @brief Stop PCNT from counting
|
||||
*
|
||||
* @note If power management is enabled, this function will release the PM lock acquired in `pcnt_unit_start()`.
|
||||
* Also see `pcnt_unit_set_glitch_filter()` for the condition of PM lock installation.
|
||||
* @note The stop operation won't clear the counter. Also see `pcnt_unit_clear_count()` for how to clear pulse count value.
|
||||
* @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_start()`.
|
||||
* @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it is allowed to be executed when Cache is disabled
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @return
|
||||
* - ESP_OK: Stop PCNT unit successfully
|
||||
* - ESP_ERR_INVALID_ARG: Stop PCNT unit failed because of invalid argument
|
||||
* - ESP_FAIL: Stop PCNT unit failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit);
|
||||
|
||||
/**
|
||||
* @brief Clear PCNT pulse count value to zero
|
||||
*
|
||||
* @note It's recommended to call this function after adding a watch point by `pcnt_unit_add_watch_point()`, so that the newly added watch point is effective immediately.
|
||||
* @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @return
|
||||
* - ESP_OK: Clear PCNT pulse count successfully
|
||||
* - ESP_ERR_INVALID_ARG: Clear PCNT pulse count failed because of invalid argument
|
||||
* - ESP_FAIL: Clear PCNT pulse count failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit);
|
||||
|
||||
/**
|
||||
* @brief Get PCNT count value
|
||||
*
|
||||
* @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[out] value Returned count value
|
||||
* @return
|
||||
* - ESP_OK: Get PCNT pulse count successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get PCNT pulse count failed because of invalid argument
|
||||
* - ESP_FAIL: Get PCNT pulse count failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value);
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for PCNT unit
|
||||
*
|
||||
* @note User can deregister the previous callback by calling this function with an empty `cbs`.
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @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 pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Add a watch point for PCNT unit, PCNT will generate an event when the counter value reaches the watch point value
|
||||
*
|
||||
* @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_remove_watch_point()`, otherwise the unit can't be deleted
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] watch_point Value to be watched
|
||||
* @return
|
||||
* - ESP_OK: Add watch point successfully
|
||||
* - ESP_ERR_INVALID_ARG: Add watch point failed because of invalid argument (e.g. the value to be watched is out of the limitation set in `pcnt_unit_config_t`)
|
||||
* - ESP_ERR_INVALID_STATE: Add watch point failed because the same watch point has already been added
|
||||
* - ESP_ERR_NOT_FOUND: Add watch point failed because no more hardware watch point can be configured
|
||||
* - ESP_FAIL: Add watch point failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point);
|
||||
|
||||
/**
|
||||
* @brief Remove a watch point for PCNT unit
|
||||
*
|
||||
* @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_add_watch_point()`, otherwise the unit can't be deleted
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] watch_point Watch point value
|
||||
* @return
|
||||
* - ESP_OK: Remove watch point successfully
|
||||
* - ESP_ERR_INVALID_ARG: Remove watch point failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Remove watch point failed because the watch point was not added by `pcnt_unit_add_watch_point()` yet
|
||||
* - ESP_FAIL: Remove watch point failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point);
|
||||
|
||||
/**
|
||||
* @brief Create PCNT channel for specific unit, each PCNT has several channels associated with it
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] config PCNT channel configuration
|
||||
* @param[out] ret_chan Returned channel handle
|
||||
* @return
|
||||
* - ESP_OK: Create PCNT channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create PCNT channel failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create PCNT channel failed because of insufficient memory
|
||||
* - ESP_ERR_NOT_FOUND: Create PCNT channel failed because all PCNT channels are used up and no more free one
|
||||
* - ESP_FAIL: Create PCNT channel failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *config, pcnt_channel_handle_t *ret_chan);
|
||||
|
||||
/**
|
||||
* @brief Delete the PCNT channel
|
||||
*
|
||||
* @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
|
||||
* @return
|
||||
* - ESP_OK: Delete the PCNT channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete the PCNT channel failed because of invalid argument
|
||||
* - ESP_FAIL: Delete the PCNT channel failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_del_channel(pcnt_channel_handle_t chan);
|
||||
|
||||
/**
|
||||
* @brief Set channel actions when edge signal changes (e.g. falling or rising edge occurred).
|
||||
* The edge signal is input from the `edge_gpio_num` configured in `pcnt_chan_config_t`.
|
||||
* We use these actions to control when and how to change the counter value.
|
||||
*
|
||||
* @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
|
||||
* @param[in] pos_act Action on posedge signal
|
||||
* @param[in] neg_act Action on negedge signal
|
||||
* @return
|
||||
* - ESP_OK: Set edge action for PCNT channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set edge action for PCNT channel failed because of invalid argument
|
||||
* - ESP_FAIL: Set edge action for PCNT channel failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act);
|
||||
|
||||
/**
|
||||
* @brief Set channel actions when level signal changes (e.g. signal level goes from high to low).
|
||||
* The level signal is input from the `level_gpio_num` configured in `pcnt_chan_config_t`.
|
||||
* We use these actions to control when and how to change the counting mode.
|
||||
*
|
||||
* @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
|
||||
* @param[in] high_act Action on high level signal
|
||||
* @param[in] low_act Action on low level signal
|
||||
* @return
|
||||
* - ESP_OK: Set level action for PCNT channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set level action for PCNT channel failed because of invalid argument
|
||||
* - ESP_FAIL: Set level action for PCNT channel failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -9,3 +9,9 @@
|
||||
* the legacy timer group driver (deprecated/driver/timer.h) and the new gptimer driver (driver/gptimer.h).
|
||||
*/
|
||||
int timer_group_driver_init_count = 0;
|
||||
|
||||
/**
|
||||
* @brief This count is used to prevent the coexistence of
|
||||
* the legacy pcnt driver (deprecated/driver/pcnt.h) and the new pulse_cnt driver (driver/pulse_cnt.h).
|
||||
*/
|
||||
int pcnt_driver_init_count = 0;
|
||||
|
@ -18,3 +18,8 @@ entries:
|
||||
gdma: gdma_stop (noflash)
|
||||
gdma: gdma_append (noflash)
|
||||
gdma: gdma_reset (noflash)
|
||||
if PCNT_CTRL_FUNC_IN_IRAM = y:
|
||||
pulse_cnt: pcnt_unit_start (noflash)
|
||||
pulse_cnt: pcnt_unit_stop (noflash)
|
||||
pulse_cnt: pcnt_unit_clear_count (noflash)
|
||||
pulse_cnt: pcnt_unit_get_count (noflash)
|
||||
|
@ -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
|
||||
*/
|
||||
@ -8,7 +8,8 @@
|
||||
#include "esp_check.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "driver/pcnt_types_legacy.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "hal/pcnt_hal.h"
|
||||
#include "hal/pcnt_ll.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
@ -54,6 +55,9 @@ static pcnt_isr_func_t *pcnt_isr_func = NULL;
|
||||
static pcnt_isr_handle_t pcnt_isr_service = NULL;
|
||||
static portMUX_TYPE pcnt_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
esp_err_t pcnt_isr_register(void (*fun)(void *), void *arg, int intr_alloc_flags, pcnt_isr_handle_t *handle);
|
||||
esp_err_t pcnt_isr_unregister(pcnt_isr_handle_t handle);
|
||||
|
||||
static inline esp_err_t _pcnt_set_mode(pcnt_port_t pcnt_port, pcnt_unit_t unit, pcnt_channel_t channel, pcnt_count_mode_t pos_mode, pcnt_count_mode_t neg_mode, pcnt_ctrl_mode_t hctrl_mode, pcnt_ctrl_mode_t lctrl_mode)
|
||||
{
|
||||
PCNT_OBJ_CHECK(pcnt_port);
|
||||
@ -537,7 +541,22 @@ esp_err_t pcnt_isr_service_install(int intr_alloc_flags)
|
||||
return _pcnt_isr_service_install(PCNT_PORT_0, intr_alloc_flags);
|
||||
}
|
||||
|
||||
void pcnt_isr_service_uninstall()
|
||||
void pcnt_isr_service_uninstall(void)
|
||||
{
|
||||
_pcnt_isr_service_uninstall(PCNT_PORT_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that pulse_cnt driver is not running along with the legacy pcnt driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_pcnt_driver_conflict(void)
|
||||
{
|
||||
extern int pcnt_driver_init_count;
|
||||
pcnt_driver_init_count++;
|
||||
if (pcnt_driver_init_count > 1) {
|
||||
ESP_EARLY_LOGE(TAG, "CONFLICT! The pulse_cnt driver can't work along with the legacy pcnt driver");
|
||||
abort();
|
||||
}
|
||||
ESP_EARLY_LOGW(TAG, "legacy pcnt driver is deprecated, please migrate to use driver/pulse_cnt.h");
|
||||
}
|
714
components/driver/pulse_cnt.c
Normal file
714
components/driver/pulse_cnt.c
Normal file
@ -0,0 +1,714 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/lock.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_PCNT_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/pcnt_periph.h"
|
||||
#include "hal/pcnt_hal.h"
|
||||
#include "hal/pcnt_ll.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/pulse_cnt.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_PCNT_ISR_IRAM_SAFE
|
||||
#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#define PCNT_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
||||
#define PCNT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif //CONFIG_PCNT_ISR_IRAM_SAFE
|
||||
|
||||
#define PCNT_PM_LOCK_NAME_LEN_MAX 16
|
||||
|
||||
static const char *TAG = "pcnt";
|
||||
|
||||
typedef struct pcnt_platform_t pcnt_platform_t;
|
||||
typedef struct pcnt_group_t pcnt_group_t;
|
||||
typedef struct pcnt_unit_t pcnt_unit_t;
|
||||
typedef struct pcnt_chan_t pcnt_chan_t;
|
||||
|
||||
struct pcnt_platform_t {
|
||||
_lock_t mutex; // platform level mutex lock
|
||||
pcnt_group_t *groups[SOC_PCNT_GROUPS]; // pcnt group pool
|
||||
int group_ref_counts[SOC_PCNT_GROUPS]; // reference count used to protect group install/uninstall
|
||||
};
|
||||
|
||||
struct pcnt_group_t {
|
||||
int group_id; // Group ID, index from 0
|
||||
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
|
||||
pcnt_hal_context_t hal;
|
||||
pcnt_unit_t *units[SOC_PCNT_UNITS_PER_GROUP]; // array of PCNT units
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
pcnt_ll_watch_event_id_t event_id; // event type
|
||||
int watch_point_value; // value to be watched
|
||||
} pcnt_watch_point_t;
|
||||
|
||||
typedef enum {
|
||||
PCNT_FSM_STOP,
|
||||
PCNT_FSM_START,
|
||||
} pcnt_lifecycle_fsm_t;
|
||||
|
||||
struct pcnt_unit_t {
|
||||
pcnt_group_t *group; // which group the pcnt unit belongs to
|
||||
portMUX_TYPE spinlock; // Spinlock, stop one unit from accessing different parts of a same register concurrently
|
||||
uint32_t unit_id; // allocated unit numerical ID
|
||||
int low_limit; // low limit value
|
||||
int high_limit; // high limit value
|
||||
pcnt_chan_t *channels[SOC_PCNT_CHANNELS_PER_UNIT]; // array of PCNT channels
|
||||
pcnt_watch_point_t watchers[PCNT_LL_WATCH_EVENT_MAX]; // array of PCNT watchers
|
||||
intr_handle_t intr; // interrupt handle
|
||||
esp_pm_lock_handle_t pm_lock; // PM lock, for glitch filter, as that module can only be functional under APB
|
||||
#if CONFIG_PM_ENABLE
|
||||
char pm_lock_name[PCNT_PM_LOCK_NAME_LEN_MAX]; // pm lock name
|
||||
#endif
|
||||
pcnt_lifecycle_fsm_t fsm; // access to fsm should be protect by spinlock, as fsm can also accessed from ISR handler
|
||||
pcnt_watch_cb_t on_reach; // user registered callback function
|
||||
void *user_data; // user data registered by user, which would be passed to the right callback function
|
||||
};
|
||||
|
||||
struct pcnt_chan_t {
|
||||
pcnt_unit_t *unit; // pointer to the PCNT unit where it derives from
|
||||
uint32_t channel_id; // channel ID, index from 0
|
||||
int edge_gpio_num;
|
||||
int level_gpio_num;
|
||||
};
|
||||
|
||||
// pcnt driver platform, it's always a singleton
|
||||
static pcnt_platform_t s_platform;
|
||||
|
||||
static pcnt_group_t *pcnt_acquire_group_handle(int group_id);
|
||||
static void pcnt_release_group_handle(pcnt_group_t *group);
|
||||
static void pcnt_default_isr(void *args);
|
||||
|
||||
esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
pcnt_group_t *group = NULL;
|
||||
pcnt_unit_t *unit = NULL;
|
||||
int group_id = -1;
|
||||
int unit_id = -1;
|
||||
ESP_GOTO_ON_FALSE(config && ret_unit, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->low_limit < 0 && config->high_limit > 0 && config->low_limit >= PCNT_LL_MIN_LIN && config->high_limit <= PCNT_LL_MAX_LIM,
|
||||
ESP_ERR_INVALID_ARG, err, TAG, "invalid limit range:[%d,%d]", config->low_limit, config->high_limit);
|
||||
|
||||
unit = heap_caps_calloc(1, sizeof(pcnt_unit_t), PCNT_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(unit, ESP_ERR_NO_MEM, err, TAG, "no mem for unit");
|
||||
|
||||
for (int i = 0; (i < SOC_PCNT_GROUPS) && (unit_id < 0); i++) {
|
||||
group = pcnt_acquire_group_handle(i);
|
||||
ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group (%d)", i);
|
||||
// loop to search free unit in the group
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int j = 0; j < SOC_PCNT_UNITS_PER_GROUP; j++) {
|
||||
if (!group->units[j]) {
|
||||
group_id = i;
|
||||
unit_id = j;
|
||||
group->units[j] = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (unit_id < 0) {
|
||||
pcnt_release_group_handle(group);
|
||||
group = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_GOTO_ON_FALSE(unit_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free unit");
|
||||
unit->group = group;
|
||||
unit->unit_id = unit_id;
|
||||
|
||||
// some events are enabled by default, disable them all
|
||||
pcnt_ll_disable_all_events(group->hal.dev, unit_id);
|
||||
// disable filter by default
|
||||
pcnt_ll_enable_glitch_filter(group->hal.dev, unit_id, false);
|
||||
// set default high/low limitation value
|
||||
// note: limit value takes effect only after counter clear
|
||||
pcnt_ll_set_high_limit_value(group->hal.dev, unit_id, config->high_limit);
|
||||
pcnt_ll_set_low_limit_value(group->hal.dev, unit_id, config->low_limit);
|
||||
unit->high_limit = config->high_limit;
|
||||
unit->low_limit = config->low_limit;
|
||||
|
||||
// clear/pause register is shared by all units, so using group's spinlock
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
pcnt_ll_stop_count(group->hal.dev, unit_id);
|
||||
pcnt_ll_clear_count(group->hal.dev, unit_id);
|
||||
pcnt_ll_enable_intr(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id), false);
|
||||
pcnt_ll_clear_intr_status(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
unit->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
unit->fsm = PCNT_FSM_STOP;
|
||||
for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
|
||||
unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID; // invalid all watch point
|
||||
}
|
||||
ESP_LOGD(TAG, "new pcnt unit (%d,%d) at %p, count range:[%d,%d]", group_id, unit_id, unit, unit->low_limit, unit->high_limit);
|
||||
*ret_unit = unit;
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (unit) {
|
||||
free(unit);
|
||||
}
|
||||
if (group) {
|
||||
pcnt_release_group_handle(group);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_FSM_STOP, ESP_ERR_INVALID_STATE, TAG, "can't delete unit as it's not in stop state");
|
||||
for (int i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++) {
|
||||
ESP_RETURN_ON_FALSE(!unit->channels[i], ESP_ERR_INVALID_STATE, TAG, "channel %d still in working", i);
|
||||
}
|
||||
for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
|
||||
ESP_RETURN_ON_FALSE(unit->watchers[i].event_id == PCNT_LL_WATCH_EVENT_INVALID, ESP_ERR_INVALID_STATE, TAG,
|
||||
"watch point %d still in working", unit->watchers[i].watch_point_value);
|
||||
}
|
||||
group = unit->group;
|
||||
int group_id = group->group_id;
|
||||
int unit_id = unit->unit_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->units[unit_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
if (unit->intr) {
|
||||
esp_intr_free(unit->intr);
|
||||
ESP_LOGD(TAG, "uninstall interrupt service for unit (%d,%d)", group_id, unit_id);
|
||||
}
|
||||
if (unit->pm_lock) {
|
||||
esp_pm_lock_delete(unit->pm_lock);
|
||||
ESP_LOGD(TAG, "uninstall APB_FREQ_MAX lock for unit (%d,%d)", group_id, unit_id);
|
||||
}
|
||||
free(unit);
|
||||
ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id);
|
||||
// unit has a reference on group, release it now
|
||||
pcnt_release_group_handle(group);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
uint32_t glitch_filter_thres = 0;
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
if (config) {
|
||||
glitch_filter_thres = esp_clk_apb_freq() / 1000000 * config->max_glitch_ns / 1000;
|
||||
ESP_RETURN_ON_FALSE(glitch_filter_thres <= PCNT_LL_MAX_GLITCH_WIDTH, ESP_ERR_INVALID_ARG, TAG, "glitch width out of range");
|
||||
|
||||
// The filter module is working against APB clock, so lazy install PM lock
|
||||
#if CONFIG_PM_ENABLE
|
||||
if (!unit->pm_lock) {
|
||||
sprintf(unit->pm_lock_name, "pcnt_%d_%d", group->group_id, unit->unit_id); // e.g. pcnt_0_0
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, unit->pm_lock_name, &unit->pm_lock), TAG, "install pm lock failed");
|
||||
ESP_LOGD(TAG, "install APB_FREQ_MAX lock for unit (%d,%d)", group->group_id, unit->unit_id);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// filter control bit is mixed with other PCNT control bits in the same register
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
if (config) {
|
||||
pcnt_ll_set_glitch_filter_thres(group->hal.dev, unit->unit_id, glitch_filter_thres);
|
||||
pcnt_ll_enable_glitch_filter(group->hal.dev, unit->unit_id, true);
|
||||
} else {
|
||||
pcnt_ll_enable_glitch_filter(group->hal.dev, unit->unit_id, false);
|
||||
}
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
|
||||
// acquire power manager lock
|
||||
if (unit->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_acquire(unit->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
|
||||
}
|
||||
// enable interupt service
|
||||
if (unit->intr) {
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_intr_enable(unit->intr), TAG, "enable interrupt service failed");
|
||||
}
|
||||
|
||||
// all PCNT units share the same register to control counter
|
||||
portENTER_CRITICAL_SAFE(&group->spinlock);
|
||||
pcnt_ll_start_count(group->hal.dev, unit->unit_id);
|
||||
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
||||
|
||||
portENTER_CRITICAL_SAFE(&unit->spinlock);
|
||||
unit->fsm = PCNT_FSM_START;
|
||||
portEXIT_CRITICAL_SAFE(&unit->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
|
||||
// all PCNT units share the same register to control counter
|
||||
portENTER_CRITICAL_SAFE(&group->spinlock);
|
||||
pcnt_ll_stop_count(group->hal.dev, unit->unit_id);
|
||||
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
||||
|
||||
portENTER_CRITICAL_SAFE(&unit->spinlock);
|
||||
unit->fsm = PCNT_FSM_STOP;
|
||||
portEXIT_CRITICAL_SAFE(&unit->spinlock);
|
||||
|
||||
// disable interrupt service
|
||||
if (unit->intr) {
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_intr_disable(unit->intr), TAG, "disable interrupt service failed");
|
||||
}
|
||||
// release power manager lock
|
||||
if (unit->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_release(unit->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
|
||||
// all PCNT units share the same register to control counter
|
||||
portENTER_CRITICAL_SAFE(&group->spinlock);
|
||||
pcnt_ll_clear_count(group->hal.dev, unit->unit_id);
|
||||
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE_ISR(unit && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
*value = pcnt_ll_get_count(group->hal.dev, unit->unit_id);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(unit && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
int group_id = group->group_id;
|
||||
int unit_id = unit->unit_id;
|
||||
|
||||
#if CONFIG_PCNT_ISR_IRAM_SAFE
|
||||
if (cbs->on_reach) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_reach), ESP_ERR_INVALID_ARG, TAG, "on_reach 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
|
||||
if (!unit->intr) {
|
||||
int isr_flags = PCNT_INTR_ALLOC_FLAGS;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(pcnt_periph_signals.groups[group_id].irq, isr_flags,
|
||||
(uint32_t)pcnt_ll_get_intr_status_reg(group->hal.dev), PCNT_LL_UNIT_WATCH_EVENT(unit_id),
|
||||
pcnt_default_isr, unit, &unit->intr),
|
||||
TAG, "install interrupt service failed");
|
||||
}
|
||||
// enable/disable PCNT interrupt events
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
pcnt_ll_enable_intr(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id), cbs->on_reach != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
unit->on_reach = cbs->on_reach;
|
||||
unit->user_data = user_data;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(watch_point <= unit->high_limit && watch_point >= unit->low_limit,
|
||||
ESP_ERR_INVALID_ARG, TAG, "watch_point out of limit");
|
||||
group = unit->group;
|
||||
|
||||
// event enable/disable is mixed with other control function in the same register
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
// zero cross watch point
|
||||
if (watch_point == 0) {
|
||||
if (unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
|
||||
ret = ESP_ERR_INVALID_STATE; // zero cross event watcher has been installed already
|
||||
} else {
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id = PCNT_LL_WATCH_EVENT_ZERO_CROSS;
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].watch_point_value = 0;
|
||||
pcnt_ll_enable_zero_cross_event(group->hal.dev, unit->unit_id, true);
|
||||
}
|
||||
}
|
||||
// high limit watch point
|
||||
else if (watch_point == unit->high_limit) {
|
||||
if (unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
|
||||
ret = ESP_ERR_INVALID_STATE; // high limit event watcher has been installed already
|
||||
} else {
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id = PCNT_LL_WATCH_EVENT_HIGH_LIMIT;
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].watch_point_value = unit->high_limit;
|
||||
pcnt_ll_enable_high_limit_event(group->hal.dev, unit->unit_id, true);
|
||||
}
|
||||
}
|
||||
// low limit watch point
|
||||
else if (watch_point == unit->low_limit) {
|
||||
if (unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
|
||||
ret = ESP_ERR_INVALID_STATE; // low limit event watcher has been installed already
|
||||
} else {
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id = PCNT_LL_WATCH_EVENT_LOW_LIMIT;
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].watch_point_value = unit->low_limit;
|
||||
pcnt_ll_enable_low_limit_event(group->hal.dev, unit->unit_id, true);
|
||||
}
|
||||
}
|
||||
// other threshold watch point
|
||||
else {
|
||||
int thres_num = SOC_PCNT_THRES_POINT_PER_UNIT - 1;
|
||||
switch (thres_num) {
|
||||
case 1:
|
||||
if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].event_id == PCNT_LL_WATCH_EVENT_INVALID) {
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].event_id = PCNT_LL_WATCH_EVENT_THRES1;
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].watch_point_value = watch_point;
|
||||
pcnt_ll_set_thres_value(group->hal.dev, unit->unit_id, 1, watch_point);
|
||||
pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 1, true);
|
||||
break;
|
||||
} else if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].watch_point_value == watch_point) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
/* fall-through */
|
||||
case 0:
|
||||
if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].event_id == PCNT_LL_WATCH_EVENT_INVALID) {
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].event_id = PCNT_LL_WATCH_EVENT_THRES0;
|
||||
unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].watch_point_value = watch_point;
|
||||
pcnt_ll_set_thres_value(group->hal.dev, unit->unit_id, 0, watch_point);
|
||||
pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 0, true);
|
||||
break;
|
||||
} else if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].watch_point_value == watch_point) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
break;
|
||||
}
|
||||
/* fall-through */
|
||||
default:
|
||||
ret = ESP_ERR_NOT_FOUND; // no free threshold watch point available
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "add watchpoint %d failed", watch_point);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
pcnt_ll_watch_event_id_t event_id = PCNT_LL_WATCH_EVENT_INVALID;
|
||||
|
||||
// event enable/disable is mixed with other control function in the same register
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
|
||||
if (unit->watchers[i].event_id != PCNT_LL_WATCH_EVENT_INVALID && unit->watchers[i].watch_point_value == watch_point) {
|
||||
event_id = unit->watchers[i].event_id;
|
||||
unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (event_id) {
|
||||
case PCNT_LL_WATCH_EVENT_ZERO_CROSS:
|
||||
pcnt_ll_enable_zero_cross_event(group->hal.dev, unit->unit_id, false);
|
||||
break;
|
||||
case PCNT_LL_WATCH_EVENT_LOW_LIMIT:
|
||||
pcnt_ll_enable_low_limit_event(group->hal.dev, unit->unit_id, false);
|
||||
break;
|
||||
case PCNT_LL_WATCH_EVENT_HIGH_LIMIT:
|
||||
pcnt_ll_enable_high_limit_event(group->hal.dev, unit->unit_id, false);
|
||||
break;
|
||||
case PCNT_LL_WATCH_EVENT_THRES0:
|
||||
pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 0, false);
|
||||
break;
|
||||
case PCNT_LL_WATCH_EVENT_THRES1:
|
||||
pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 1, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
ESP_RETURN_ON_FALSE(event_id != PCNT_LL_WATCH_EVENT_INVALID, ESP_ERR_INVALID_STATE, TAG, "watch point %d not added yet", watch_point);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *config, pcnt_channel_handle_t *ret_chan)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
pcnt_chan_t *channel = NULL;
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_GOTO_ON_FALSE(unit && config && ret_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
int group_id = group->group_id;
|
||||
int unit_id = unit->unit_id;
|
||||
|
||||
channel = heap_caps_calloc(1, sizeof(pcnt_chan_t), PCNT_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(channel, ESP_ERR_NO_MEM, err, TAG, "no mem for channel");
|
||||
|
||||
// search for a free channel
|
||||
int channel_id = -1;
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
for (int i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++) {
|
||||
if (!unit->channels[i]) {
|
||||
channel_id = i;
|
||||
unit->channels[channel_id] = channel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
ESP_GOTO_ON_FALSE(channel_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free channel in unit (%d,%d)", group_id, unit_id);
|
||||
|
||||
// GPIO configuration
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
|
||||
.pull_down_en = false,
|
||||
.pull_up_en = true,
|
||||
};
|
||||
if (config->edge_gpio_num >= 0) {
|
||||
gpio_conf.pin_bit_mask = 1ULL << config->edge_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config edge GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->edge_gpio_num,
|
||||
pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].pulse_sig,
|
||||
config->flags.invert_edge_input);
|
||||
}
|
||||
if (config->level_gpio_num >= 0) {
|
||||
gpio_conf.pin_bit_mask = 1ULL << config->level_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config level GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->level_gpio_num,
|
||||
pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].control_sig,
|
||||
config->flags.invert_level_input);
|
||||
}
|
||||
|
||||
channel->channel_id = channel_id;
|
||||
channel->unit = unit;
|
||||
channel->edge_gpio_num = config->edge_gpio_num;
|
||||
channel->level_gpio_num = config->level_gpio_num;
|
||||
ESP_LOGD(TAG, "new pcnt channel(%d,%d,%d) at %p", group_id, unit_id, channel_id, channel);
|
||||
|
||||
*ret_chan = channel;
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (channel) {
|
||||
free(channel);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_del_channel(pcnt_channel_handle_t chan)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
pcnt_unit_t *unit = chan->unit;
|
||||
pcnt_group_t *group = unit->group;
|
||||
int group_id = group->group_id;
|
||||
int unit_id = unit->unit_id;
|
||||
int channel_id = chan->channel_id;
|
||||
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
unit->channels[channel_id] = NULL;
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
|
||||
if (chan->level_gpio_num >= 0) {
|
||||
gpio_reset_pin(chan->level_gpio_num);
|
||||
}
|
||||
if (chan->edge_gpio_num >= 0) {
|
||||
gpio_reset_pin(chan->edge_gpio_num);
|
||||
}
|
||||
|
||||
free(chan);
|
||||
ESP_LOGD(TAG, "del pcnt channel(%d,%d,%d)", group_id, unit_id, channel_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
pcnt_unit_t *unit = chan->unit;
|
||||
group = unit->group;
|
||||
|
||||
// mode control bits are mixed with other PCNT control bits in a same register
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
pcnt_ll_set_edge_action(group->hal.dev, unit->unit_id, chan->channel_id, pos_act, neg_act);
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
pcnt_unit_t *unit = chan->unit;
|
||||
group = unit->group;
|
||||
|
||||
// mode control bits are mixed with other PCNT control bits in a same register
|
||||
portENTER_CRITICAL(&unit->spinlock);
|
||||
pcnt_ll_set_level_action(group->hal.dev, unit->unit_id, chan->channel_id, high_act, low_act);
|
||||
portEXIT_CRITICAL(&unit->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static pcnt_group_t *pcnt_acquire_group_handle(int group_id)
|
||||
{
|
||||
#if CONFIG_PCNT_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
bool new_group = false;
|
||||
pcnt_group_t *group = NULL;
|
||||
|
||||
// prevent install pcnt group concurrently
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
if (!s_platform.groups[group_id]) {
|
||||
group = heap_caps_calloc(1, sizeof(pcnt_group_t), PCNT_MEM_ALLOC_CAPS);
|
||||
if (group) {
|
||||
new_group = true;
|
||||
s_platform.groups[group_id] = group; // register to platform
|
||||
// initialize pcnt group members
|
||||
group->group_id = group_id;
|
||||
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
// enable APB access pcnt registers
|
||||
periph_module_enable(pcnt_periph_signals.groups[group_id].module);
|
||||
periph_module_reset(pcnt_periph_signals.groups[group_id].module);
|
||||
// initialize HAL context
|
||||
pcnt_hal_init(&group->hal, group_id);
|
||||
}
|
||||
} else {
|
||||
group = s_platform.groups[group_id];
|
||||
}
|
||||
if (group) {
|
||||
// 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) at %p", group_id, group);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static void pcnt_release_group_handle(pcnt_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; // deregister from platform
|
||||
periph_module_disable(pcnt_periph_signals.groups[group_id].module);
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (do_deinitialize) {
|
||||
free(group);
|
||||
ESP_LOGD(TAG, "del group (%d)", group_id);
|
||||
}
|
||||
}
|
||||
|
||||
IRAM_ATTR static void pcnt_default_isr(void *args)
|
||||
{
|
||||
bool need_yield = false;
|
||||
pcnt_unit_t *unit = (pcnt_unit_t *)args;
|
||||
int unit_id = unit->unit_id;
|
||||
pcnt_group_t *group = unit->group;
|
||||
pcnt_watch_cb_t on_reach = unit->on_reach;
|
||||
|
||||
uint32_t intr_status = pcnt_ll_get_intr_status(group->hal.dev);
|
||||
if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(unit_id)) {
|
||||
pcnt_ll_clear_intr_status(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
|
||||
uint32_t event_status = pcnt_ll_get_event_status(group->hal.dev, unit_id);
|
||||
// iter on each event_id
|
||||
while (event_status) {
|
||||
int event_id = __builtin_ffs(event_status) - 1;
|
||||
event_status &= (event_status - 1); // clear the right most bit
|
||||
|
||||
// invoked user registered callback
|
||||
if (on_reach) {
|
||||
pcnt_watch_event_data_t edata = {
|
||||
.watch_point_value = unit->watchers[event_id].watch_point_value,
|
||||
.zero_cross_mode = pcnt_ll_get_zero_cross_mode(group->hal.dev, unit_id),
|
||||
};
|
||||
if (on_reach(unit, &edata, unit->user_data)) {
|
||||
// check if we need to yield for high priority task
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that pulse_cnt driver is not running along with the legacy pcnt driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_pulse_cnt_driver_conflict(void)
|
||||
{
|
||||
extern int pcnt_driver_init_count;
|
||||
pcnt_driver_init_count++;
|
||||
if (pcnt_driver_init_count > 1) {
|
||||
ESP_EARLY_LOGE(TAG, "CONFLICT! The pulse_cnt driver can't work along with the legacy pcnt driver");
|
||||
abort();
|
||||
}
|
||||
}
|
@ -6,10 +6,6 @@
|
||||
/* LEDC tested by PCNT in some case
|
||||
* PCNT can get the LEDC waveform frequency
|
||||
*
|
||||
* test environment of UT_T1_LEDC:
|
||||
* 1. connect GPIO18 with GPIO4
|
||||
* 2. connect GPIO5 to 3.3v (in case of it is pulled down by default)
|
||||
*
|
||||
* some calculation related with duty:
|
||||
* real duty = duty/2^duty_resolution
|
||||
*/
|
||||
@ -24,15 +20,13 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "unity.h"
|
||||
#include "soc/ledc_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "esp_system.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define PULSE_IO 18
|
||||
#define PCNT_INPUT_IO 4
|
||||
#define PCNT_CTRL_FLOATING_IO 5
|
||||
#define HIGHEST_LIMIT 10000
|
||||
#define LOWEST_LIMIT -10000
|
||||
#define PULSE_IO 18
|
||||
|
||||
#define TEST_PWM_FREQ 2000
|
||||
|
||||
@ -116,81 +110,6 @@ static void timer_duty_test(ledc_channel_t channel, ledc_timer_bit_t timer_bit,
|
||||
timer_duty_set_get(ledc_ch_config.speed_mode, ledc_ch_config.channel, (1 << 13) - 2);
|
||||
}
|
||||
|
||||
#if SOC_PCNT_SUPPORTED
|
||||
#include "driver/pcnt.h" // TODO: C3 doesn't have PCNT peripheral
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
//no runners
|
||||
|
||||
// use PCNT to test the waveform of LEDC
|
||||
static int16_t wave_count(int last_time)
|
||||
{
|
||||
int16_t test_counter;
|
||||
pcnt_config_t pcnt_config = {
|
||||
.pulse_gpio_num = PCNT_INPUT_IO,
|
||||
.ctrl_gpio_num = PCNT_CTRL_FLOATING_IO,
|
||||
.channel = PCNT_CHANNEL_0,
|
||||
.unit = PCNT_UNIT_0,
|
||||
.pos_mode = PCNT_COUNT_INC,
|
||||
.neg_mode = PCNT_COUNT_DIS,
|
||||
.lctrl_mode = PCNT_MODE_REVERSE,
|
||||
.hctrl_mode = PCNT_MODE_KEEP,
|
||||
.counter_h_lim = HIGHEST_LIMIT,
|
||||
.counter_l_lim = LOWEST_LIMIT,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_config(&pcnt_config));
|
||||
|
||||
// initialize first
|
||||
TEST_ESP_OK(pcnt_counter_pause(PCNT_UNIT_0));
|
||||
TEST_ESP_OK(pcnt_counter_clear(PCNT_UNIT_0));
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
TEST_ESP_OK(pcnt_counter_resume(PCNT_UNIT_0));
|
||||
TEST_ESP_OK(pcnt_get_counter_value(PCNT_UNIT_0, &test_counter));
|
||||
|
||||
vTaskDelay(last_time / portTICK_PERIOD_MS);
|
||||
TEST_ESP_OK(pcnt_get_counter_value(PCNT_UNIT_0, &test_counter));
|
||||
return test_counter;
|
||||
}
|
||||
|
||||
// the PCNT will count the frequency of it
|
||||
static void frequency_set_get(ledc_mode_t speed_mode, ledc_timer_t timer, uint32_t freq_hz, int16_t real_freq, int16_t error)
|
||||
{
|
||||
int16_t count;
|
||||
TEST_ESP_OK(ledc_set_freq(speed_mode, timer, freq_hz));
|
||||
count = wave_count(1000);
|
||||
TEST_ASSERT_INT16_WITHIN(error, count, real_freq);
|
||||
TEST_ASSERT_EQUAL_INT32(ledc_get_freq(speed_mode, timer), real_freq);
|
||||
}
|
||||
|
||||
static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_bit, ledc_timer_t timer, ledc_mode_t speed_mode)
|
||||
{
|
||||
ledc_channel_config_t ledc_ch_config = {
|
||||
.gpio_num = PULSE_IO,
|
||||
.speed_mode = speed_mode,
|
||||
.channel = channel,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = timer,
|
||||
.duty = 4000,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ledc_timer_config_t ledc_time_config = {
|
||||
.speed_mode = speed_mode,
|
||||
.duty_resolution = timer_bit,
|
||||
.timer_num = timer,
|
||||
.freq_hz = 5000,
|
||||
.clk_cfg = LEDC_USE_APB_CLK,
|
||||
};
|
||||
TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));
|
||||
TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 100, 100, 2);
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 5000, 5000, 5);
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 9000, 8993, 5);
|
||||
}
|
||||
|
||||
#endif // !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
|
||||
#endif // SOC_PCNT_SUPPORTED
|
||||
|
||||
TEST_CASE("LEDC channel config wrong gpio", "[ledc]")
|
||||
{
|
||||
ledc_channel_config_t ledc_ch_config = initialize_channel_config();
|
||||
@ -324,8 +243,8 @@ TEST_CASE("LEDC set and get duty", "[ledc]")
|
||||
{
|
||||
ledc_timer_t timer_list[4] = {LEDC_TIMER_0, LEDC_TIMER_1, LEDC_TIMER_2, LEDC_TIMER_3};
|
||||
ledc_mode_t speed_mode_list[LEDC_SPEED_MODE_MAX] = SPEED_MODE_LIST;
|
||||
for(int i=0; i<LEDC_TIMER_MAX-1; i++) {
|
||||
for(int j=0; j<LEDC_SPEED_MODE_MAX; j++) {
|
||||
for (int i = 0; i < LEDC_TIMER_MAX - 1; i++) {
|
||||
for (int j = 0; j < LEDC_SPEED_MODE_MAX; j++) {
|
||||
timer_duty_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, timer_list[i], speed_mode_list[j]);
|
||||
}
|
||||
}
|
||||
@ -473,10 +392,88 @@ TEST_CASE("LEDC fade stop test", "[ledc]")
|
||||
|
||||
#if SOC_PCNT_SUPPORTED
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
#include "driver/pulse_cnt.h"
|
||||
|
||||
TEST_CASE("LEDC set and get frequency", "[ledc][test_env=UT_T1_LEDC][timeout=60][ignore]")
|
||||
#define HIGHEST_LIMIT 10000
|
||||
#define LOWEST_LIMIT -10000
|
||||
|
||||
static pcnt_unit_handle_t pcnt_unit;
|
||||
static pcnt_channel_handle_t pcnt_chan;
|
||||
|
||||
static void setup_testbench(void)
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = HIGHEST_LIMIT,
|
||||
.low_limit = LOWEST_LIMIT,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &pcnt_unit));
|
||||
pcnt_chan_config_t chan_config = {
|
||||
.edge_gpio_num = PULSE_IO,
|
||||
.level_gpio_num = -1,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
}
|
||||
|
||||
static void tear_testbench(void)
|
||||
{
|
||||
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
|
||||
TEST_ESP_OK(pcnt_del_unit(pcnt_unit));
|
||||
}
|
||||
|
||||
// use PCNT to test the waveform of LEDC
|
||||
static int wave_count(int last_time)
|
||||
{
|
||||
// The input ability of PULSE_IO is disabled after ledc driver install, so we need to reenable it again
|
||||
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[PULSE_IO]);
|
||||
int test_counter = 0;
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_start(pcnt_unit));
|
||||
vTaskDelay(pdMS_TO_TICKS(last_time));
|
||||
TEST_ESP_OK(pcnt_unit_stop(pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(pcnt_unit, &test_counter));
|
||||
return test_counter;
|
||||
}
|
||||
|
||||
// the PCNT will count the frequency of it
|
||||
static void frequency_set_get(ledc_mode_t speed_mode, ledc_timer_t timer, uint32_t freq_hz, int16_t real_freq, int16_t error)
|
||||
{
|
||||
int count;
|
||||
TEST_ESP_OK(ledc_set_freq(speed_mode, timer, freq_hz));
|
||||
count = wave_count(1000);
|
||||
TEST_ASSERT_INT16_WITHIN(error, count, real_freq);
|
||||
TEST_ASSERT_EQUAL_INT32(real_freq, ledc_get_freq(speed_mode, timer));
|
||||
}
|
||||
|
||||
static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_bit, ledc_timer_t timer, ledc_mode_t speed_mode)
|
||||
{
|
||||
ledc_channel_config_t ledc_ch_config = {
|
||||
.gpio_num = PULSE_IO,
|
||||
.speed_mode = speed_mode,
|
||||
.channel = channel,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = timer,
|
||||
.duty = 4000,
|
||||
.hpoint = 0,
|
||||
};
|
||||
ledc_timer_config_t ledc_time_config = {
|
||||
.speed_mode = speed_mode,
|
||||
.duty_resolution = timer_bit,
|
||||
.timer_num = timer,
|
||||
.freq_hz = 5000,
|
||||
.clk_cfg = LEDC_USE_APB_CLK,
|
||||
};
|
||||
TEST_ESP_OK(ledc_channel_config(&ledc_ch_config));
|
||||
TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 100, 100, 20);
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 5000, 5000, 50);
|
||||
frequency_set_get(ledc_ch_config.speed_mode, ledc_ch_config.timer_sel, 9000, 8992, 50);
|
||||
}
|
||||
|
||||
TEST_CASE("LEDC set and get frequency", "[ledc][timeout=60][ignore]")
|
||||
{
|
||||
setup_testbench();
|
||||
#if SOC_LEDC_SUPPORT_HS_MODE
|
||||
timer_frequency_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, LEDC_TIMER_0, LEDC_HIGH_SPEED_MODE);
|
||||
timer_frequency_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, LEDC_TIMER_1, LEDC_HIGH_SPEED_MODE);
|
||||
@ -487,10 +484,12 @@ TEST_CASE("LEDC set and get frequency", "[ledc][test_env=UT_T1_LEDC][timeout=60]
|
||||
timer_frequency_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, LEDC_TIMER_1, LEDC_LOW_SPEED_MODE);
|
||||
timer_frequency_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, LEDC_TIMER_2, LEDC_LOW_SPEED_MODE);
|
||||
timer_frequency_test(LEDC_CHANNEL_0, LEDC_TIMER_13_BIT, LEDC_TIMER_3, LEDC_LOW_SPEED_MODE);
|
||||
tear_testbench();
|
||||
}
|
||||
|
||||
TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]")
|
||||
TEST_CASE("LEDC timer set", "[ledc]")
|
||||
{
|
||||
setup_testbench();
|
||||
const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
|
||||
ledc_channel_config_t ledc_ch_config = {
|
||||
.gpio_num = PULSE_IO,
|
||||
@ -513,7 +512,8 @@ TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]")
|
||||
TEST_ESP_OK(ledc_timer_config(&ledc_time_config));
|
||||
|
||||
uint32_t freq_get;
|
||||
uint32_t count;
|
||||
int count;
|
||||
#if SOC_LEDC_SUPPORT_REF_TICK
|
||||
//set timer 0 as 250Hz, use REF_TICK
|
||||
TEST_ESP_OK(ledc_timer_set(test_speed_mode, LEDC_TIMER_0, 1000, 10, LEDC_REF_TICK));
|
||||
TEST_ESP_OK(ledc_timer_rst(test_speed_mode, LEDC_TIMER_0));
|
||||
@ -521,6 +521,7 @@ TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]")
|
||||
freq_get = ledc_get_freq(test_speed_mode, LEDC_TIMER_0);
|
||||
count = wave_count(1000);
|
||||
TEST_ASSERT_UINT32_WITHIN(10, count, freq_get);
|
||||
#endif
|
||||
|
||||
//set timer 0 as 500Hz, use APB_CLK
|
||||
TEST_ESP_OK(ledc_timer_set(test_speed_mode, LEDC_TIMER_0, 5000, 13, LEDC_APB_CLK));
|
||||
@ -539,12 +540,14 @@ TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]")
|
||||
TEST_ESP_OK(ledc_stop(test_speed_mode, LEDC_CHANNEL_0, !current_level));
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
TEST_ASSERT_EQUAL_INT32( LEDC.channel_group[test_speed_mode].channel[LEDC_CHANNEL_0].conf0.idle_lv, !current_level);
|
||||
tear_testbench();
|
||||
}
|
||||
|
||||
TEST_CASE("LEDC timer pause and resume", "[ledc][test_env=UT_T1_LEDC]")
|
||||
TEST_CASE("LEDC timer pause and resume", "[ledc]")
|
||||
{
|
||||
setup_testbench();
|
||||
const ledc_mode_t test_speed_mode = TEST_SPEED_MODE;
|
||||
int16_t count;
|
||||
int count;
|
||||
ledc_channel_config_t ledc_ch_config = {
|
||||
.gpio_num = PULSE_IO,
|
||||
.speed_mode = test_speed_mode,
|
||||
@ -588,6 +591,7 @@ TEST_CASE("LEDC timer pause and resume", "[ledc][test_env=UT_T1_LEDC]")
|
||||
TEST_ESP_OK(ledc_timer_rst(test_speed_mode, LEDC_TIMER_0));
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
TEST_ASSERT_UINT32_WITHIN(5, count, 5000);
|
||||
tear_testbench();
|
||||
}
|
||||
|
||||
static void ledc_cpu_reset_test_first_stage(void)
|
||||
@ -603,17 +607,18 @@ static void ledc_cpu_reset_test_first_stage(void)
|
||||
|
||||
static void ledc_cpu_reset_test_second_stage(void)
|
||||
{
|
||||
int count;
|
||||
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
|
||||
int16_t count;
|
||||
setup_testbench();
|
||||
// reconfigure the GPIO again, as the GPIO output ability has been disabled during initialize pcnt peripheral
|
||||
ledc_set_pin(PULSE_IO, TEST_SPEED_MODE, LEDC_CHANNEL_0);
|
||||
count = wave_count(1000);
|
||||
TEST_ASSERT_UINT32_WITHIN(5, count, TEST_PWM_FREQ);
|
||||
TEST_ASSERT_UINT32_WITHIN(5, TEST_PWM_FREQ, count);
|
||||
tear_testbench();
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("LEDC software reset test",
|
||||
"[ledc][test_env=UT_T1_LEDC]",
|
||||
TEST_CASE_MULTIPLE_STAGES("LEDC continue work after software reset", "[ledc]",
|
||||
ledc_cpu_reset_test_first_stage,
|
||||
ledc_cpu_reset_test_second_stage);
|
||||
|
||||
#endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
|
||||
#endif // SOC_PCNT_SUPPORTED
|
||||
|
@ -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
|
||||
*/
|
||||
@ -15,12 +15,10 @@
|
||||
#include "soc/rtc.h"
|
||||
#if SOC_MCPWM_SUPPORTED
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define TEST_PWMA_PCNT_UNIT (0)
|
||||
#define TEST_PWMB_PCNT_UNIT (1)
|
||||
#define TEST_PWMA_GPIO (2)
|
||||
#define TEST_PWMB_GPIO (4)
|
||||
#define TEST_FAULT_GPIO (21)
|
||||
@ -41,6 +39,11 @@ const static mcpwm_io_signals_t sync_io_sig_array[] = {MCPWM_SYNC_0, MCPWM_SYNC_
|
||||
const static mcpwm_capture_signal_t cap_sig_array[] = {MCPWM_SELECT_CAP0, MCPWM_SELECT_CAP1, MCPWM_SELECT_CAP2};
|
||||
const static mcpwm_io_signals_t cap_io_sig_array[] = {MCPWM_CAP_0, MCPWM_CAP_1, MCPWM_CAP_2};
|
||||
|
||||
static pcnt_unit_handle_t pcnt_unit_a;
|
||||
static pcnt_channel_handle_t pcnt_chan_a;
|
||||
static pcnt_unit_handle_t pcnt_unit_b;
|
||||
static pcnt_channel_handle_t pcnt_chan_b;
|
||||
|
||||
// This GPIO init function is almost the same to public API `mcpwm_gpio_init()`, except that
|
||||
// this function will configure all MCPWM GPIOs into output and input capable
|
||||
// which is useful to simulate a trigger source
|
||||
@ -75,26 +78,9 @@ static esp_err_t test_mcpwm_gpio_init(mcpwm_unit_t mcpwm_num, mcpwm_io_signals_t
|
||||
static void mcpwm_setup_testbench(mcpwm_unit_t group, mcpwm_timer_t timer, uint32_t pwm_freq, float pwm_duty,
|
||||
unsigned long int group_resolution, unsigned long int timer_resolution)
|
||||
{
|
||||
// PWMA <--> PCNT UNIT0
|
||||
pcnt_config_t pcnt_config = {
|
||||
.pulse_gpio_num = TEST_PWMA_GPIO,
|
||||
.ctrl_gpio_num = -1, // don't care level signal
|
||||
.channel = PCNT_CHANNEL_0,
|
||||
.unit = TEST_PWMA_PCNT_UNIT,
|
||||
.pos_mode = PCNT_COUNT_INC,
|
||||
.neg_mode = PCNT_COUNT_DIS,
|
||||
.lctrl_mode = PCNT_MODE_KEEP,
|
||||
.hctrl_mode = PCNT_MODE_KEEP,
|
||||
.counter_h_lim = 10000,
|
||||
.counter_l_lim = -10000,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_config(&pcnt_config));
|
||||
mcpwm_io_signals_t mcpwm_a = pwma[timer];
|
||||
TEST_ESP_OK(test_mcpwm_gpio_init(group, mcpwm_a, TEST_PWMA_GPIO));
|
||||
// PWMB <--> PCNT UNIT1
|
||||
pcnt_config.pulse_gpio_num = TEST_PWMB_GPIO;
|
||||
pcnt_config.unit = TEST_PWMB_PCNT_UNIT;
|
||||
TEST_ESP_OK(pcnt_unit_config(&pcnt_config));
|
||||
|
||||
mcpwm_io_signals_t mcpwm_b = pwmb[timer];
|
||||
TEST_ESP_OK(test_mcpwm_gpio_init(group, mcpwm_b, TEST_PWMB_GPIO));
|
||||
|
||||
@ -111,14 +97,53 @@ static void mcpwm_setup_testbench(mcpwm_unit_t group, mcpwm_timer_t timer, uint3
|
||||
TEST_ESP_OK(mcpwm_init(group, timer, &pwm_config));
|
||||
}
|
||||
|
||||
static uint32_t mcpwm_pcnt_get_pulse_number(pcnt_unit_t pwm_pcnt_unit, int capture_window_ms)
|
||||
static void pcnt_setup_testbench(void)
|
||||
{
|
||||
int16_t count_value = 0;
|
||||
TEST_ESP_OK(pcnt_counter_pause(pwm_pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_counter_clear(pwm_pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_counter_resume(pwm_pcnt_unit));
|
||||
// PWMA <--> PCNT UNIT0
|
||||
pcnt_unit_config_t unit_a_config = {
|
||||
.high_limit = 10000,
|
||||
.low_limit = -10000,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_a_config, &pcnt_unit_a));
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = TEST_PWMA_GPIO,
|
||||
.level_gpio_num = -1, // don't care level signal
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_channel(pcnt_unit_a, &chan_a_config, &pcnt_chan_a));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
|
||||
// PWMB <--> PCNT UNIT1
|
||||
pcnt_unit_config_t unit_b_config = {
|
||||
.high_limit = 10000,
|
||||
.low_limit = -10000,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_b_config, &pcnt_unit_b));
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = TEST_PWMB_GPIO,
|
||||
.level_gpio_num = -1, // don't care level signal
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_channel(pcnt_unit_b, &chan_b_config, &pcnt_chan_b));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
}
|
||||
|
||||
static void pcnt_tear_testbench(void)
|
||||
{
|
||||
TEST_ESP_OK(pcnt_del_channel(pcnt_chan_a));
|
||||
TEST_ESP_OK(pcnt_del_channel(pcnt_chan_b));
|
||||
TEST_ESP_OK(pcnt_del_unit(pcnt_unit_a));
|
||||
TEST_ESP_OK(pcnt_del_unit(pcnt_unit_b));
|
||||
}
|
||||
|
||||
static uint32_t pcnt_get_pulse_number(pcnt_unit_handle_t pwm_pcnt_unit, int capture_window_ms)
|
||||
{
|
||||
int count_value = 0;
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(pwm_pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_start(pwm_pcnt_unit));
|
||||
usleep(capture_window_ms * 1000);
|
||||
TEST_ESP_OK(pcnt_get_counter_value(pwm_pcnt_unit, &count_value));
|
||||
TEST_ESP_OK(pcnt_unit_stop(pwm_pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(pwm_pcnt_unit, &count_value));
|
||||
printf("count value: %d\r\n", count_value);
|
||||
return (uint32_t)count_value;
|
||||
}
|
||||
@ -163,24 +188,26 @@ static void mcpwm_start_stop_test(mcpwm_unit_t unit, mcpwm_timer_t timer)
|
||||
{
|
||||
uint32_t pulse_number = 0;
|
||||
|
||||
pcnt_setup_testbench();
|
||||
mcpwm_setup_testbench(unit, timer, 1000, 50.0, MCPWM_TEST_GROUP_CLK_HZ, MCPWM_TEST_TIMER_CLK_HZ); // Period: 1000us, 1ms
|
||||
// count the pulse number within 100ms
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMA_PCNT_UNIT, 100);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_a, 100);
|
||||
TEST_ASSERT_INT_WITHIN(2, 100, pulse_number);
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMB_PCNT_UNIT, 100);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_b, 100);
|
||||
TEST_ASSERT_INT_WITHIN(2, 100, pulse_number);
|
||||
|
||||
TEST_ESP_OK(mcpwm_set_frequency(unit, timer, 100));
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMB_PCNT_UNIT, 100);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_b, 100);
|
||||
TEST_ASSERT_INT_WITHIN(2, 10, pulse_number);
|
||||
|
||||
// stop timer, then no pwm pulse should be generating
|
||||
TEST_ESP_OK(mcpwm_stop(unit, timer));
|
||||
usleep(10000); // wait until timer stopped
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMA_PCNT_UNIT, 100);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_a, 100);
|
||||
TEST_ASSERT_INT_WITHIN(2, 0, pulse_number);
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMB_PCNT_UNIT, 100);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_b, 100);
|
||||
TEST_ASSERT_INT_WITHIN(2, 0, pulse_number);
|
||||
pcnt_tear_testbench();
|
||||
}
|
||||
|
||||
TEST_CASE("MCPWM start and stop test", "[mcpwm]")
|
||||
@ -229,6 +256,7 @@ static void mcpwm_carrier_test(mcpwm_unit_t unit, mcpwm_timer_t timer, mcpwm_car
|
||||
{
|
||||
uint32_t pulse_number = 0;
|
||||
|
||||
pcnt_setup_testbench();
|
||||
mcpwm_setup_testbench(unit, timer, 1000, 50.0, MCPWM_TEST_GROUP_CLK_HZ, MCPWM_TEST_TIMER_CLK_HZ);
|
||||
mcpwm_set_signal_high(unit, timer, MCPWM_GEN_A);
|
||||
mcpwm_set_signal_high(unit, timer, MCPWM_GEN_B);
|
||||
@ -239,14 +267,15 @@ static void mcpwm_carrier_test(mcpwm_unit_t unit, mcpwm_timer_t timer, mcpwm_car
|
||||
TEST_ESP_OK(mcpwm_carrier_oneshot_mode_enable(unit, timer, os_width));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMA_PCNT_UNIT, 10);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_a, 10);
|
||||
TEST_ASSERT_INT_WITHIN(50, 2500, pulse_number);
|
||||
usleep(10000);
|
||||
pulse_number = mcpwm_pcnt_get_pulse_number(TEST_PWMB_PCNT_UNIT, 10);
|
||||
pulse_number = pcnt_get_pulse_number(pcnt_unit_b, 10);
|
||||
TEST_ASSERT_INT_WITHIN(50, 2500, pulse_number);
|
||||
|
||||
TEST_ESP_OK(mcpwm_carrier_disable(unit, timer));
|
||||
TEST_ESP_OK(mcpwm_stop(unit, timer));
|
||||
pcnt_tear_testbench();
|
||||
}
|
||||
|
||||
TEST_CASE("MCPWM carrier test", "[mcpwm]")
|
||||
@ -382,33 +411,37 @@ TEST_CASE("MCPWM timer GPIO sync test", "[mcpwm]")
|
||||
}
|
||||
}
|
||||
|
||||
static void mcpwm_swsync_test(mcpwm_unit_t unit) {
|
||||
// used only in this area but need to be reset every time. mutex is not needed
|
||||
// store timestamps captured from ISR callback
|
||||
static uint64_t cap_timestamp[3];
|
||||
// control the start of capture to avoid unstable data
|
||||
static volatile bool log_cap;
|
||||
|
||||
// cb function, to update capture value
|
||||
// only log when channel1 comes at first, then channel2, and do not log further more.
|
||||
static bool test_mcpwm_capture_callback(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,
|
||||
void *user_data)
|
||||
{
|
||||
if (log_cap && (cap_timestamp[1] == 0 || cap_timestamp[2] == 0)) {
|
||||
if (cap_channel == MCPWM_SELECT_CAP1 && cap_timestamp[1] == 0) {
|
||||
cap_timestamp[1] = edata->cap_value;
|
||||
}
|
||||
if (cap_channel == MCPWM_SELECT_CAP2 && cap_timestamp[1] != 0) {
|
||||
cap_timestamp[2] = edata->cap_value;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mcpwm_swsync_test(mcpwm_unit_t unit)
|
||||
{
|
||||
const uint32_t test_sync_phase = 20;
|
||||
// used only in this area but need to be reset every time. mutex is not needed
|
||||
// store timestamps captured from ISR callback
|
||||
static uint64_t cap_timestamp[3];
|
||||
|
||||
cap_timestamp[0] = 0;
|
||||
cap_timestamp[1] = 0;
|
||||
cap_timestamp[2] = 0;
|
||||
// control the start of capture to avoid unstable data
|
||||
static volatile bool log_cap;
|
||||
log_cap = false;
|
||||
|
||||
// cb function, to update capture value
|
||||
// only log when channel1 comes at first, then channel2, and do not log further more.
|
||||
bool capture_callback(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,
|
||||
void *user_data) {
|
||||
if (log_cap && (cap_timestamp[1] == 0 || cap_timestamp[2] == 0)) {
|
||||
if (cap_channel == MCPWM_SELECT_CAP1 && cap_timestamp[1] == 0) {
|
||||
cap_timestamp[1] = edata->cap_value;
|
||||
}
|
||||
if (cap_channel == MCPWM_SELECT_CAP2 && cap_timestamp[1] != 0) {
|
||||
cap_timestamp[2] = edata->cap_value;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure all timer output 10% PWM
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
mcpwm_setup_testbench(unit, i, 1000, 10.0, MCPWM_TEST_GROUP_CLK_HZ, MCPWM_TEST_TIMER_CLK_HZ);
|
||||
@ -420,7 +453,7 @@ static void mcpwm_swsync_test(mcpwm_unit_t unit) {
|
||||
mcpwm_capture_config_t conf = {
|
||||
.cap_edge = MCPWM_POS_EDGE,
|
||||
.cap_prescale = 1,
|
||||
.capture_cb = capture_callback,
|
||||
.capture_cb = test_mcpwm_capture_callback,
|
||||
.user_data = NULL,
|
||||
};
|
||||
TEST_ESP_OK(test_mcpwm_gpio_init(unit, MCPWM_CAP_0, TEST_SYNC_GPIO_0));
|
||||
@ -477,7 +510,8 @@ typedef struct {
|
||||
TaskHandle_t task_hdl;
|
||||
} test_capture_callback_data_t;
|
||||
|
||||
static bool test_mcpwm_intr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata, void *arg) {
|
||||
static bool test_mcpwm_intr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata, void *arg)
|
||||
{
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
test_capture_callback_data_t *cb_data = (test_capture_callback_data_t *)arg;
|
||||
vTaskNotifyGiveFromISR(cb_data->task_hdl, &high_task_wakeup);
|
||||
@ -497,10 +531,10 @@ static void mcpwm_capture_test(mcpwm_unit_t unit, mcpwm_capture_signal_t cap_cha
|
||||
|
||||
TEST_ESP_OK(test_mcpwm_gpio_init(unit, cap_io, TEST_CAP_GPIO));
|
||||
mcpwm_capture_config_t conf = {
|
||||
.cap_edge = MCPWM_POS_EDGE,
|
||||
.cap_prescale = 1,
|
||||
.capture_cb = test_mcpwm_intr_handler,
|
||||
.user_data = &callback_data
|
||||
.cap_edge = MCPWM_POS_EDGE,
|
||||
.cap_prescale = 1,
|
||||
.capture_cb = test_mcpwm_intr_handler,
|
||||
.user_data = &callback_data
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_capture_enable_channel(unit, cap_channel, &conf));
|
||||
// generate an posage
|
||||
|
@ -2,7 +2,6 @@ set(srcs "test_app_main.c"
|
||||
"test_gptimer.c"
|
||||
"test_gptimer_iram.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES driver unity spi_flash)
|
||||
idf_component_register(SRCS ${srcs})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_gptimer" "-u test_app_include_gptimer_iram")
|
||||
|
@ -12,6 +12,6 @@ from pytest_embedded import Dut
|
||||
'release',
|
||||
], indirect=True)
|
||||
def test_gptimer(dut: Dut) -> None:
|
||||
dut.expect('Press ENTER to see the list of tests')
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output()
|
||||
|
@ -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_pcnt_driver_test)
|
2
components/driver/test_apps/legacy_pcnt_driver/README.md
Normal file
2
components/driver/test_apps/legacy_pcnt_driver/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- |
|
@ -0,0 +1,6 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_legacy_pcnt.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_legacy_pcnt")
|
@ -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 (-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)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
@ -3,24 +3,12 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/**
|
||||
* this case is used for test PCNT
|
||||
* prepare job for test environment UT_T1_PCNT:
|
||||
* We use internal signals instead of external wiring, but please keep the following IO connections, or connect nothing to prevent the signal from being disturbed.
|
||||
* 1. prepare one ESP-WROOM-32 board and connect it to PC.
|
||||
* 2. connect GPIO21 with GPIO4
|
||||
* 3. GPIO5 connect to 3.3v
|
||||
* 4. GPIO19 connect to GND
|
||||
* 5. logic analyzer will help too if possible
|
||||
*
|
||||
* the GPIO18 is the pulse producer, the GPIO4 is the input GPIO
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#if SOC_PCNT_SUPPORTED
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "driver/ledc.h"
|
||||
@ -317,7 +305,7 @@ static void count_mode_test(gpio_num_t ctl_io)
|
||||
}
|
||||
|
||||
// test PCNT basic configuration
|
||||
TEST_CASE("PCNT test config", "[pcnt]")
|
||||
TEST_CASE("PCNT_test_config", "[pcnt]")
|
||||
{
|
||||
pcnt_config_t pcnt_config = {
|
||||
.pulse_gpio_num = PCNT_INPUT_IO,
|
||||
@ -380,7 +368,7 @@ TEST_CASE("PCNT test config", "[pcnt]")
|
||||
* 2. resume counter
|
||||
* 3. clear counter
|
||||
* 4. check the counter value*/
|
||||
TEST_CASE("PCNT basic function test", "[pcnt]")
|
||||
TEST_CASE("PCNT_basic_function_test", "[pcnt]")
|
||||
{
|
||||
int16_t test_counter;
|
||||
int16_t time = 0;
|
||||
@ -469,7 +457,7 @@ TEST_CASE("PCNT basic function test", "[pcnt]")
|
||||
* 4. PCNT_EVT_H_LIM
|
||||
* 5. PCNT_EVT_L_LIM
|
||||
* */
|
||||
TEST_CASE("PCNT interrupt method test(control IO is high)", "[pcnt][timeout=120]")
|
||||
TEST_CASE("PCNT_interrupt_method_test_control_IO_high", "[pcnt][timeout=120]")
|
||||
{
|
||||
pcnt_config_t config = {
|
||||
.pulse_gpio_num = PCNT_INPUT_IO,
|
||||
@ -569,7 +557,7 @@ TEST_CASE("PCNT interrupt method test(control IO is high)", "[pcnt][timeout=120]
|
||||
vQueueDelete(pcnt_evt_queue);
|
||||
}
|
||||
|
||||
TEST_CASE("PCNT interrupt method test(control IO is low)", "[pcnt][timeout=120]")
|
||||
TEST_CASE("PCNT_interrupt_method_test_control_IO_low", "[pcnt][timeout=120]")
|
||||
{
|
||||
pcnt_config_t config = {
|
||||
.pulse_gpio_num = PCNT_INPUT_IO,
|
||||
@ -671,7 +659,7 @@ TEST_CASE("PCNT interrupt method test(control IO is low)", "[pcnt][timeout=120]"
|
||||
vQueueDelete(pcnt_evt_queue);
|
||||
}
|
||||
|
||||
TEST_CASE("PCNT counting mode test", "[pcnt]")
|
||||
TEST_CASE("PCNT_counting_mode_test", "[pcnt]")
|
||||
{
|
||||
printf("PCNT mode test for positive count\n");
|
||||
count_mode_test(PCNT_CTRL_VCC_IO);
|
||||
@ -679,4 +667,6 @@ TEST_CASE("PCNT counting mode test", "[pcnt]")
|
||||
count_mode_test(PCNT_CTRL_GND_IO);
|
||||
}
|
||||
|
||||
#endif // #if SOC_PCNT_SUPPORTED
|
||||
void test_app_include_legacy_pcnt(void)
|
||||
{
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'release',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_gptimer(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output(timeout=240)
|
@ -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_PCNT_SUPPRESS_DEPRECATE_WARN=y
|
@ -1,7 +1,6 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_legacy_timer.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES driver unity)
|
||||
idf_component_register(SRCS ${srcs})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_legacy_timer")
|
||||
|
@ -11,6 +11,6 @@ from pytest_embedded import Dut
|
||||
'release',
|
||||
], indirect=True)
|
||||
def test_legacy_timer_driver(dut: Dut) -> None:
|
||||
dut.expect('Press ENTER to see the list of tests')
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output(timeout=120)
|
||||
|
18
components/driver/test_apps/pulse_cnt/CMakeLists.txt
Normal file
18
components/driver/test_apps/pulse_cnt/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(pcnt_test)
|
||||
|
||||
if(CONFIG_COMPILER_DUMP_RTL_FILES)
|
||||
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}/pcnt_test.elf
|
||||
find-refs
|
||||
--from-sections=.iram0.text
|
||||
--to-sections=.flash.text,.flash.rodata
|
||||
--exit-code
|
||||
DEPENDS ${elf}
|
||||
)
|
||||
endif()
|
2
components/driver/test_apps/pulse_cnt/README.md
Normal file
2
components/driver/test_apps/pulse_cnt/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- |
|
@ -0,0 +1,6 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_pulse_cnt.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_pulse_cnt")
|
51
components/driver/test_apps/pulse_cnt/main/test_app_main.c
Normal file
51
components/driver/test_apps/pulse_cnt/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 pulse_cnt 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();
|
||||
}
|
408
components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c
Normal file
408
components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* 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/pulse_cnt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_attr.h"
|
||||
|
||||
#define TEST_PCNT_GPIO_A 0
|
||||
#define TEST_PCNT_GPIO_B 2
|
||||
|
||||
#if CONFIG_PCNT_ISR_IRAM_SAFE
|
||||
#define TEST_PCNT_CALLBACK_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define TEST_PCNT_CALLBACK_ATTR
|
||||
#endif // CONFIG_PCNT_ISR_IRAM_SAFE
|
||||
|
||||
// helper function to simulate several rising edges on gpio
|
||||
static void test_gpio_simulate_rising_edge(int gpio_sig, size_t times)
|
||||
{
|
||||
while (times--) {
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig, 0));
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to simulate several groups of quadrature signals
|
||||
static void test_gpio_simulate_quadrature_signals(int gpio_sig_a, int gpio_sig_b, size_t times)
|
||||
{
|
||||
while (times--) {
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_a, 1));
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_b, 0));
|
||||
vTaskDelay(1);
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_a, 0));
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_b, 0));
|
||||
vTaskDelay(1);
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_a, 0));
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_b, 1));
|
||||
vTaskDelay(1);
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_a, 1));
|
||||
TEST_ESP_OK(gpio_set_level(gpio_sig_b, 1));
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pcnt_unit_install_uninstall", "[pcnt]")
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.low_limit = -100,
|
||||
.high_limit = 100,
|
||||
};
|
||||
pcnt_unit_handle_t units[SOC_PCNT_UNITS_PER_GROUP];
|
||||
int count_value = 0;
|
||||
|
||||
printf("install pcnt units and check initial count\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &units[i]));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
|
||||
TEST_ASSERT_EQUAL(0, count_value);
|
||||
}
|
||||
// no more free pcnt units
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_new_unit(&unit_config, &units[0]));
|
||||
|
||||
printf("set glitch filter\r\n");
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_unit_set_glitch_filter(units[i], &filter_config));
|
||||
}
|
||||
// invalid glitch configuration
|
||||
filter_config.max_glitch_ns = 500000;
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_set_glitch_filter(units[0], &filter_config));
|
||||
|
||||
printf("start pcnt units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_unit_start(units[i]));
|
||||
}
|
||||
// can't uninstall unit before stop it
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_del_unit(units[0]));
|
||||
|
||||
printf("stop pcnt units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_unit_stop(units[i]));
|
||||
}
|
||||
|
||||
printf("uninstall pcnt units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_del_unit(units[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pcnt_channel_install_uninstall", "[pcnt]")
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.low_limit = -100,
|
||||
.high_limit = 100,
|
||||
};
|
||||
pcnt_chan_config_t chan_config = {
|
||||
.edge_gpio_num = TEST_PCNT_GPIO_A, // only detect edge signal in this case
|
||||
.level_gpio_num = -1,
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
pcnt_unit_handle_t units[SOC_PCNT_UNITS_PER_GROUP];
|
||||
pcnt_channel_handle_t chans[SOC_PCNT_UNITS_PER_GROUP][SOC_PCNT_CHANNELS_PER_UNIT];
|
||||
|
||||
printf("install pcnt units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &units[i]));
|
||||
}
|
||||
|
||||
printf("install pcnt channels\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
for (int j = 0; j < SOC_PCNT_CHANNELS_PER_UNIT; j++) {
|
||||
TEST_ESP_OK(pcnt_new_channel(units[i], &chan_config, &chans[i][j]));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(chans[i][j], PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(chans[i][j], PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
}
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_new_channel(units[i], &chan_config, &chans[i][0]));
|
||||
}
|
||||
|
||||
printf("start units\r\n");
|
||||
int count_value = 0;
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
// start unit
|
||||
TEST_ESP_OK(pcnt_unit_start(units[i]));
|
||||
// trigger 10 rising edge on GPIO0
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
|
||||
// each channel increases to the same unit counter
|
||||
TEST_ASSERT_EQUAL(10 * SOC_PCNT_CHANNELS_PER_UNIT, count_value);
|
||||
}
|
||||
|
||||
printf("clear counts\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(units[i]));
|
||||
TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
|
||||
TEST_ASSERT_EQUAL(0, count_value);
|
||||
}
|
||||
|
||||
printf("stop unit\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
// stop unit
|
||||
TEST_ESP_OK(pcnt_unit_stop(units[i]));
|
||||
}
|
||||
|
||||
// trigger 10 rising edge on GPIO0 shouldn't increase the counter
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
|
||||
TEST_ASSERT_EQUAL(0, count_value);
|
||||
}
|
||||
|
||||
printf("restart units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
// start unit
|
||||
TEST_ESP_OK(pcnt_unit_start(units[i]));
|
||||
// trigger 10 rising edge on GPIO
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
|
||||
// each channel increases to the same unit counter
|
||||
TEST_ASSERT_EQUAL(10 * SOC_PCNT_CHANNELS_PER_UNIT, count_value);
|
||||
}
|
||||
|
||||
printf("uninstall channels and units\r\n");
|
||||
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
|
||||
// stop unit
|
||||
TEST_ESP_OK(pcnt_unit_stop(units[i]));
|
||||
// can't uninstall unit when channel is still alive
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_del_unit(units[i]));
|
||||
for (int j = 0; j < SOC_PCNT_CHANNELS_PER_UNIT; j++) {
|
||||
TEST_ESP_OK(pcnt_del_channel(chans[i][j]));
|
||||
}
|
||||
TEST_ESP_OK(pcnt_del_unit(units[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Using this context to save the triggered watchpoints in sequence
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
int triggered_watch_values[8];
|
||||
} test_pcnt_quadrature_context_t;
|
||||
|
||||
TEST_PCNT_CALLBACK_ATTR
|
||||
static bool test_pcnt_quadrature_reach_watch_point(pcnt_unit_handle_t handle, pcnt_watch_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
test_pcnt_quadrature_context_t *user_ctx = (test_pcnt_quadrature_context_t *)user_data;
|
||||
user_ctx->triggered_watch_values[user_ctx->index++] = event_data->watch_point_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("pcnt_quadrature_decode_event", "[pcnt]")
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.low_limit = -100,
|
||||
.high_limit = 100
|
||||
};
|
||||
|
||||
printf("install pcnt unit\r\n");
|
||||
pcnt_unit_handle_t unit = NULL;
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &unit));
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_set_glitch_filter(unit, &filter_config));
|
||||
|
||||
printf("install two pcnt channels with different edge/level action\r\n");
|
||||
pcnt_chan_config_t channel_config = {
|
||||
.edge_gpio_num = TEST_PCNT_GPIO_A,
|
||||
.level_gpio_num = TEST_PCNT_GPIO_B,
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
pcnt_channel_handle_t channelA = NULL;
|
||||
TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelA));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
// switch edge gpio and level gpio, the assign to another channel in the same unit
|
||||
pcnt_channel_handle_t channelB = NULL;
|
||||
channel_config.edge_gpio_num = TEST_PCNT_GPIO_B;
|
||||
channel_config.level_gpio_num = TEST_PCNT_GPIO_A;
|
||||
TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelB));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(channelB, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
// ensure the simulation signal in a stable state
|
||||
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_A, 1));
|
||||
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_B, 1));
|
||||
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = test_pcnt_quadrature_reach_watch_point,
|
||||
};
|
||||
test_pcnt_quadrature_context_t user_data = {
|
||||
.index = 0,
|
||||
.triggered_watch_values = {}
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &user_data));
|
||||
|
||||
printf("add watchpoints\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 100));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -100));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 50));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -50));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_unit_add_watch_point(unit, 33));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_point(unit, 50));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_point(unit, 100));
|
||||
|
||||
// Clear internal counter, and make the watch points take effect
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(unit));
|
||||
TEST_ESP_OK(pcnt_unit_start(unit));
|
||||
|
||||
printf("simulating quadrature signals\r\n");
|
||||
test_gpio_simulate_quadrature_signals(TEST_PCNT_GPIO_A, TEST_PCNT_GPIO_B, 30);
|
||||
// simply wait for done
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
int count_value;
|
||||
printf("checking count value\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
printf("count_value=%d\r\n", count_value);
|
||||
TEST_ASSERT_EQUAL(-20, count_value); // 0-30*4+100
|
||||
TEST_ASSERT_EQUAL(3, user_data.index);
|
||||
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[0]);
|
||||
TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[1]);
|
||||
TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[2]);
|
||||
|
||||
printf("simulating quadrature signals in another direction\r\n");
|
||||
user_data.index = 0;
|
||||
test_gpio_simulate_quadrature_signals(TEST_PCNT_GPIO_B, TEST_PCNT_GPIO_A, 40);
|
||||
// simply wait for done
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
printf("checking count value\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
printf("count_value=%d\r\n", count_value);
|
||||
TEST_ASSERT_EQUAL(40, count_value); // -20+40*4-100
|
||||
TEST_ASSERT_EQUAL(4, user_data.index);
|
||||
TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[0]);
|
||||
TEST_ASSERT_EQUAL(50, user_data.triggered_watch_values[1]);
|
||||
TEST_ASSERT_EQUAL(100, user_data.triggered_watch_values[2]);
|
||||
TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[3]);
|
||||
|
||||
printf("remove watchpoints and uninstall channels\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 0));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 100));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, -100));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 50));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, -50));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_remove_watch_point(unit, 50));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_remove_watch_point(unit, 33));
|
||||
TEST_ESP_OK(pcnt_del_channel(channelA));
|
||||
TEST_ESP_OK(pcnt_del_channel(channelB));
|
||||
TEST_ESP_OK(pcnt_unit_stop(unit));
|
||||
TEST_ESP_OK(pcnt_del_unit(unit));
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
pcnt_unit_zero_cross_mode_t mode;
|
||||
} test_pcnt_zero_cross_context_t;
|
||||
|
||||
TEST_PCNT_CALLBACK_ATTR
|
||||
static bool test_pcnt_on_zero_cross(pcnt_unit_handle_t handle, pcnt_watch_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
test_pcnt_zero_cross_context_t *user_ctx = (test_pcnt_zero_cross_context_t *)user_data;
|
||||
user_ctx->mode = event_data->zero_cross_mode;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("pcnt_zero_cross_mode", "[pcnt]")
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.low_limit = -100,
|
||||
.high_limit = 100
|
||||
};
|
||||
|
||||
printf("install pcnt unit\r\n");
|
||||
pcnt_unit_handle_t unit = NULL;
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &unit));
|
||||
|
||||
printf("add watchpoint to detect zero cross\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0));
|
||||
|
||||
printf("register callback for zero cross event\r\n");
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = test_pcnt_on_zero_cross,
|
||||
};
|
||||
test_pcnt_zero_cross_context_t user_data = {};
|
||||
TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &user_data));
|
||||
|
||||
printf("install pcnt channels\r\n");
|
||||
pcnt_chan_config_t channel_config = {
|
||||
.edge_gpio_num = TEST_PCNT_GPIO_A,
|
||||
.level_gpio_num = -1,
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
pcnt_channel_handle_t channelA = NULL;
|
||||
pcnt_channel_handle_t channelB = NULL;
|
||||
TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelA));
|
||||
TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelB));
|
||||
|
||||
printf("Initialize pcnt actions for channels\r\n");
|
||||
// only channel will increase the counter, 0->1
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(channelB, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
|
||||
printf("start unit\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_start(unit));
|
||||
|
||||
int count_value = 0;
|
||||
printf("counter goes 0->1\r\n");
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
TEST_ASSERT_EQUAL(1, count_value);
|
||||
|
||||
printf("counter goes 1->-1\r\n");
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
TEST_ASSERT_EQUAL(-1, count_value);
|
||||
TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_POS_NEG, user_data.mode);
|
||||
|
||||
printf("counter goes -1->1\r\n");
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
TEST_ASSERT_EQUAL(1, count_value);
|
||||
TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_NEG_POS, user_data.mode);
|
||||
|
||||
printf("counter goes 1->0->-1\r\n");
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 2);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
TEST_ASSERT_EQUAL(-1, count_value);
|
||||
TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_POS_ZERO, user_data.mode);
|
||||
|
||||
printf("counter goes -1->0->1\r\n");
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 2);
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
TEST_ASSERT_EQUAL(1, count_value);
|
||||
TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_NEG_ZERO, user_data.mode);
|
||||
|
||||
TEST_ESP_OK(pcnt_unit_stop(unit));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 0));
|
||||
TEST_ESP_OK(pcnt_del_channel(channelA));
|
||||
TEST_ESP_OK(pcnt_del_channel(channelB));
|
||||
TEST_ESP_OK(pcnt_del_unit(unit));
|
||||
}
|
||||
|
||||
void test_app_include_pulse_cnt(void)
|
||||
{
|
||||
}
|
23
components/driver/test_apps/pulse_cnt/pytest_pulse_cnt.py
Normal file
23
components/driver/test_apps/pulse_cnt/pytest_pulse_cnt.py
Normal file
@ -0,0 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s2
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'iram_safe',
|
||||
'release',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_gptimer(dut: Dut) -> None:
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output()
|
@ -0,0 +1,6 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_PCNT_CTRL_FUNC_IN_IRAM=y
|
||||
CONFIG_PCNT_ISR_IRAM_SAFE=y
|
||||
|
||||
# silent the error check, as the error string are stored in rodata, causing RTL check failure
|
||||
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
|
@ -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/pulse_cnt/sdkconfig.defaults
Normal file
2
components/driver/test_apps/pulse_cnt/sdkconfig.defaults
Normal file
@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* NOTICE
|
||||
@ -22,6 +14,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "soc/pcnt_struct.h"
|
||||
@ -31,19 +24,23 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_MAX_GLITCH_WIDTH 1023
|
||||
#define PCNT_LL_MAX_LIM SHRT_MAX
|
||||
#define PCNT_LL_MIN_LIN SHRT_MIN
|
||||
|
||||
typedef enum {
|
||||
PCNT_LL_EVENT_THRES1,
|
||||
PCNT_LL_EVENT_THRES0,
|
||||
PCNT_LL_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_EVENT_MAX
|
||||
} pcnt_ll_event_id_t;
|
||||
PCNT_LL_WATCH_EVENT_INVALID = -1,
|
||||
PCNT_LL_WATCH_EVENT_THRES1,
|
||||
PCNT_LL_WATCH_EVENT_THRES0,
|
||||
PCNT_LL_WATCH_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_WATCH_EVENT_MAX
|
||||
} pcnt_ll_watch_event_id_t;
|
||||
|
||||
#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_WATCH_EVENT_MASK ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
|
||||
|
||||
/**
|
||||
* @brief Set PCNT channel edge action
|
||||
@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @return Interrupt status word
|
||||
*/
|
||||
__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
{
|
||||
return hw->int_st.val;
|
||||
}
|
||||
@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param status value to clear interrupt status
|
||||
*/
|
||||
__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
__attribute__((always_inline))
|
||||
static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
{
|
||||
hw->int_clr.val = status;
|
||||
}
|
||||
@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
|
||||
*/
|
||||
static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get PCNT count sign
|
||||
* @brief Get PCNT zero cross mode
|
||||
*
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param unit PCNT unit number
|
||||
* @return Count sign
|
||||
* @return Zero cross mode
|
||||
*/
|
||||
static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
|
||||
__attribute__((always_inline))
|
||||
static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val & 0x03;
|
||||
}
|
||||
@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
|
||||
* @param unit PCNT unit number
|
||||
* @return Event status word
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val >> 2;
|
||||
@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
|
||||
hw->conf_unit[unit].conf0.filter_en = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get interrupt status register address.
|
||||
*
|
||||
* @param hw Beginning address of the peripheral registers.
|
||||
*
|
||||
* @return Interrupt status register address
|
||||
*/
|
||||
static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
|
||||
{
|
||||
return &hw->int_st.val;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* NOTICE
|
||||
@ -22,6 +14,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "soc/pcnt_struct.h"
|
||||
@ -31,19 +24,23 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_MAX_GLITCH_WIDTH 1023
|
||||
#define PCNT_LL_MAX_LIM SHRT_MAX
|
||||
#define PCNT_LL_MIN_LIN SHRT_MIN
|
||||
|
||||
typedef enum {
|
||||
PCNT_LL_EVENT_THRES1,
|
||||
PCNT_LL_EVENT_THRES0,
|
||||
PCNT_LL_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_EVENT_MAX
|
||||
} pcnt_ll_event_id_t;
|
||||
PCNT_LL_WATCH_EVENT_INVALID = -1,
|
||||
PCNT_LL_WATCH_EVENT_THRES1,
|
||||
PCNT_LL_WATCH_EVENT_THRES0,
|
||||
PCNT_LL_WATCH_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_WATCH_EVENT_MAX
|
||||
} pcnt_ll_watch_event_id_t;
|
||||
|
||||
#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_WATCH_EVENT_MASK ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
|
||||
|
||||
/**
|
||||
* @brief Set PCNT channel edge action
|
||||
@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @return Interrupt status word
|
||||
*/
|
||||
__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
{
|
||||
return hw->int_st.val;
|
||||
}
|
||||
@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param status value to clear interrupt status
|
||||
*/
|
||||
__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
__attribute__((always_inline))
|
||||
static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
{
|
||||
hw->int_clr.val = status;
|
||||
}
|
||||
@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
|
||||
*/
|
||||
static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get PCNT count sign
|
||||
* @brief Get PCNT zero cross mode
|
||||
*
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param unit PCNT unit number
|
||||
* @return Count sign
|
||||
* @return Zero cross mode
|
||||
*/
|
||||
static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
|
||||
__attribute__((always_inline))
|
||||
static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val & 0x03;
|
||||
}
|
||||
@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
|
||||
* @param unit PCNT unit number
|
||||
* @return Event status word
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val >> 2;
|
||||
@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
|
||||
hw->conf_unit[unit].conf0.filter_en_un = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get interrupt status register address.
|
||||
*
|
||||
* @param hw Beginning address of the peripheral registers.
|
||||
*
|
||||
* @return Interrupt status register address
|
||||
*/
|
||||
static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
|
||||
{
|
||||
return &hw->int_st.val;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* NOTICE
|
||||
@ -22,6 +14,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "soc/pcnt_struct.h"
|
||||
@ -31,19 +24,23 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
|
||||
#define PCNT_LL_MAX_GLITCH_WIDTH 1023
|
||||
#define PCNT_LL_MAX_LIM SHRT_MAX
|
||||
#define PCNT_LL_MIN_LIN SHRT_MIN
|
||||
|
||||
typedef enum {
|
||||
PCNT_LL_EVENT_THRES1,
|
||||
PCNT_LL_EVENT_THRES0,
|
||||
PCNT_LL_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_EVENT_MAX
|
||||
} pcnt_ll_event_id_t;
|
||||
PCNT_LL_WATCH_EVENT_INVALID = -1,
|
||||
PCNT_LL_WATCH_EVENT_THRES1,
|
||||
PCNT_LL_WATCH_EVENT_THRES0,
|
||||
PCNT_LL_WATCH_EVENT_LOW_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
|
||||
PCNT_LL_WATCH_EVENT_ZERO_CROSS,
|
||||
PCNT_LL_WATCH_EVENT_MAX
|
||||
} pcnt_ll_watch_event_id_t;
|
||||
|
||||
#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_WATCH_EVENT_MASK ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
|
||||
|
||||
/**
|
||||
* @brief Set PCNT channel edge action
|
||||
@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @return Interrupt status word
|
||||
*/
|
||||
__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
|
||||
{
|
||||
return hw->int_st.val;
|
||||
}
|
||||
@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param status value to clear interrupt status
|
||||
*/
|
||||
__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
__attribute__((always_inline))
|
||||
static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
|
||||
{
|
||||
hw->int_clr.val = status;
|
||||
}
|
||||
@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
|
||||
*/
|
||||
static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
|
||||
hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get PCNT count sign
|
||||
* @brief Get PCNT zero cross mode
|
||||
*
|
||||
* @param hw Peripheral PCNT hardware instance address.
|
||||
* @param unit PCNT unit number
|
||||
* @return Count sign
|
||||
* @return Zero cross mode
|
||||
*/
|
||||
static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
|
||||
__attribute__((always_inline))
|
||||
static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val & 0x03;
|
||||
}
|
||||
@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
|
||||
* @param unit PCNT unit number
|
||||
* @return Event status word
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
|
||||
{
|
||||
return hw->status_unit[unit].val >> 2;
|
||||
@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
|
||||
hw->conf_unit[unit].conf0.filter_en_un = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get interrupt status register address.
|
||||
*
|
||||
* @param hw Beginning address of the peripheral registers.
|
||||
*
|
||||
* @return Interrupt status register address
|
||||
*/
|
||||
static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
|
||||
{
|
||||
return &hw->int_st.val;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* NOTICE
|
||||
@ -18,22 +10,19 @@
|
||||
* See readme.md in hal/include/hal/readme.md
|
||||
******************************************************************************/
|
||||
|
||||
// The HAL layer for PCNT.
|
||||
// There is no parameter check in the hal layer, so the caller must ensure the correctness of the parameters.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "soc/pcnt_struct.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct pcnt_dev_t *pcnt_soc_handle_t; // PCNT SOC layer handle
|
||||
|
||||
/**
|
||||
* Context that should be maintained by both the driver and the HAL
|
||||
*/
|
||||
typedef struct {
|
||||
pcnt_dev_t *dev; /*!< PCNT peripheral register base address */
|
||||
pcnt_soc_handle_t dev; // PCNT SOC layer handle
|
||||
} pcnt_hal_context_t;
|
||||
|
||||
/**
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -20,7 +12,6 @@ extern "C" {
|
||||
|
||||
/**
|
||||
* @brief PCNT channel action on control level
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_CHANNEL_LEVEL_ACTION_KEEP, /*!< Keep current count mode */
|
||||
@ -30,7 +21,6 @@ typedef enum {
|
||||
|
||||
/**
|
||||
* @brief PCNT channel action on signal edge
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_CHANNEL_EDGE_ACTION_HOLD, /*!< Hold current count value */
|
||||
@ -39,15 +29,14 @@ typedef enum {
|
||||
} pcnt_channel_edge_action_t;
|
||||
|
||||
/**
|
||||
* @brief PCNT unit counter value's sign
|
||||
*
|
||||
* @brief PCNT unit zero cross mode
|
||||
*/
|
||||
typedef enum {
|
||||
PCNT_UNIT_COUNT_SIGN_ZERO_POS, /*!< positive value to zero */
|
||||
PCNT_UNIT_COUNT_SIGN_ZERO_NEG, /*!< negative value to zero */
|
||||
PCNT_UNIT_COUNT_SIGN_NEG, /*!< counter value negative */
|
||||
PCNT_UNIT_COUNT_SIGN_POS, /*!< counter value positive */
|
||||
} pcnt_unit_count_sign_t;
|
||||
PCNT_UNIT_ZERO_CROSS_POS_ZERO, /*!< start from positive value, end to zero, i.e. +N->0 */
|
||||
PCNT_UNIT_ZERO_CROSS_NEG_ZERO, /*!< start from negative value, end to zero, i.e. -N->0 */
|
||||
PCNT_UNIT_ZERO_CROSS_NEG_POS, /*!< start from negative value, end to positive value, i.e. -N->+M */
|
||||
PCNT_UNIT_ZERO_CROSS_POS_NEG, /*!< start from positive value, end to negative value, i.e. +N->-M */
|
||||
} pcnt_unit_zero_cross_mode_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// The HAL layer for PCNT (common part)
|
||||
|
||||
|
@ -1,21 +1,13 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// 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.
|
||||
#ifndef _SOC_PCNT_REG_H_
|
||||
#define _SOC_PCNT_REG_H_
|
||||
#include <stdint.h>
|
||||
#include "soc/soc.h"
|
||||
|
||||
|
||||
#include "soc.h"
|
||||
#define PCNT_U0_CONF0_REG (DR_REG_PCNT_BASE + 0x0000)
|
||||
/* PCNT_CH1_LCTRL_MODE_U0 : R/W ;bitpos:[31:30] ;default: 2'd0 ; */
|
||||
/*description: This register is used to control the mode of channel1's low control
|
||||
@ -1517,8 +1509,3 @@
|
||||
#define PCNT_DATE_M ((PCNT_DATE_V)<<(PCNT_DATE_S))
|
||||
#define PCNT_DATE_V 0xFFFFFFFF
|
||||
#define PCNT_DATE_S 0
|
||||
|
||||
|
||||
|
||||
|
||||
#endif /*_SOC_PCNT_REG_H_ */
|
||||
|
@ -1,18 +1,9 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#ifndef _SOC_PCNT_STRUCT_H_
|
||||
#define _SOC_PCNT_STRUCT_H_
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@ -20,8 +11,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef volatile struct pcnt_dev_s {
|
||||
struct {
|
||||
typedef struct pcnt_dev_t {
|
||||
volatile struct {
|
||||
union {
|
||||
struct {
|
||||
uint32_t filter_thres: 10; /*This register is used to filter pulse whose width is smaller than this value for unit0.*/
|
||||
@ -44,27 +35,27 @@ typedef volatile struct pcnt_dev_s {
|
||||
} conf0;
|
||||
union {
|
||||
struct {
|
||||
uint32_t cnt_thres0:16; /*This register is used to configure thres0 value for unit0.*/
|
||||
uint32_t cnt_thres1:16; /*This register is used to configure thres1 value for unit0.*/
|
||||
uint32_t cnt_thres0: 16; /*This register is used to configure thres0 value for unit0.*/
|
||||
uint32_t cnt_thres1: 16; /*This register is used to configure thres1 value for unit0.*/
|
||||
};
|
||||
uint32_t val;
|
||||
} conf1;
|
||||
union {
|
||||
struct {
|
||||
uint32_t cnt_h_lim:16; /*This register is used to configure thr_h_lim value for unit0.*/
|
||||
uint32_t cnt_l_lim:16; /*This register is used to configure thr_l_lim value for unit0.*/
|
||||
uint32_t cnt_h_lim: 16; /*This register is used to configure thr_h_lim value for unit0.*/
|
||||
uint32_t cnt_l_lim: 16; /*This register is used to configure thr_l_lim value for unit0.*/
|
||||
};
|
||||
uint32_t val;
|
||||
} conf2;
|
||||
} conf_unit[8];
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_val : 16; /*This register stores the current pulse count value for unit0.*/
|
||||
uint32_t reserved16: 16;
|
||||
};
|
||||
uint32_t val;
|
||||
} cnt_unit[8];
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_thr_event_u0: 1; /*This is the interrupt raw bit for channel0 event.*/
|
||||
uint32_t cnt_thr_event_u1: 1; /*This is the interrupt raw bit for channel1 event.*/
|
||||
@ -78,7 +69,7 @@ typedef volatile struct pcnt_dev_s {
|
||||
};
|
||||
uint32_t val;
|
||||
} int_raw;
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_thr_event_u0: 1; /*This is the interrupt status bit for channel0 event.*/
|
||||
uint32_t cnt_thr_event_u1: 1; /*This is the interrupt status bit for channel1 event.*/
|
||||
@ -92,7 +83,7 @@ typedef volatile struct pcnt_dev_s {
|
||||
};
|
||||
uint32_t val;
|
||||
} int_st;
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_thr_event_u0: 1; /*This is the interrupt enable bit for channel0 event.*/
|
||||
uint32_t cnt_thr_event_u1: 1; /*This is the interrupt enable bit for channel1 event.*/
|
||||
@ -106,7 +97,7 @@ typedef volatile struct pcnt_dev_s {
|
||||
};
|
||||
uint32_t val;
|
||||
} int_ena;
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_thr_event_u0: 1; /*Set this bit to clear channel0 event interrupt.*/
|
||||
uint32_t cnt_thr_event_u1: 1; /*Set this bit to clear channel1 event interrupt.*/
|
||||
@ -120,19 +111,19 @@ typedef volatile struct pcnt_dev_s {
|
||||
};
|
||||
uint32_t val;
|
||||
} int_clr;
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_mode:2; /*0: positive value to zero; 1: negative value to zero; 2: counter value negative ; 3: counter value positive*/
|
||||
uint32_t thres1_lat:1; /* counter value equals to thresh1*/
|
||||
uint32_t thres0_lat:1; /* counter value equals to thresh0*/
|
||||
uint32_t l_lim_lat:1; /* counter value reaches h_lim*/
|
||||
uint32_t h_lim_lat:1; /* counter value reaches l_lim*/
|
||||
uint32_t zero_lat:1; /* counter value equals zero*/
|
||||
uint32_t reserved7:25;
|
||||
uint32_t cnt_mode: 2; /*0: positive value to zero; 1: negative value to zero; 2: counter value negative ; 3: counter value positive*/
|
||||
uint32_t thres1_lat: 1; /* counter value equals to thresh1*/
|
||||
uint32_t thres0_lat: 1; /* counter value equals to thresh0*/
|
||||
uint32_t l_lim_lat: 1; /* counter value reaches h_lim*/
|
||||
uint32_t h_lim_lat: 1; /* counter value reaches l_lim*/
|
||||
uint32_t zero_lat: 1; /* counter value equals zero*/
|
||||
uint32_t reserved7: 25;
|
||||
};
|
||||
uint32_t val;
|
||||
} status_unit[8];
|
||||
union {
|
||||
volatile union {
|
||||
struct {
|
||||
uint32_t cnt_rst_u0: 1; /*Set this bit to clear unit0's counter.*/
|
||||
uint32_t cnt_pause_u0: 1; /*Set this bit to pause unit0's counter.*/
|
||||
@ -173,12 +164,11 @@ typedef volatile struct pcnt_dev_s {
|
||||
uint32_t reserved_f0;
|
||||
uint32_t reserved_f4;
|
||||
uint32_t reserved_f8;
|
||||
uint32_t date; /**/
|
||||
volatile uint32_t date;
|
||||
} pcnt_dev_t;
|
||||
|
||||
extern pcnt_dev_t PCNT;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _SOC_PCNT_STRUCT_H_ */
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/pcnt_periph.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
|
@ -1,16 +1,7 @@
|
||||
/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 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.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
@ -1,16 +1,7 @@
|
||||
/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 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.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@ -388,7 +379,7 @@ typedef union {
|
||||
} pcnt_date_reg_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
typedef struct pcnt_dev_t {
|
||||
volatile struct {
|
||||
pcnt_un_conf0_reg_t conf0;
|
||||
pcnt_un_conf1_reg_t conf1;
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/pcnt_periph.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
|
@ -1,26 +0,0 @@
|
||||
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SOC_PCNT_PORT_NUM (1)
|
||||
#define SOC_PCNT_UNIT_NUM (4)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,16 +1,7 @@
|
||||
/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 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.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
@ -1,16 +1,7 @@
|
||||
/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 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.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@ -388,7 +379,7 @@ typedef union {
|
||||
} pcnt_date_reg_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
typedef struct pcnt_dev_t {
|
||||
volatile struct {
|
||||
pcnt_un_conf0_reg_t conf0;
|
||||
pcnt_un_conf1_reg_t conf1;
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/pcnt_periph.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -4,7 +4,7 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pcnt.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/rtc_io_channel.h \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
|
||||
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
|
||||
|
@ -2,7 +2,7 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/dac_channel.h \
|
||||
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/dac.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pcnt.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/rtc_io_channel.h \
|
||||
$(PROJECT_PATH)/components/driver/esp32s2/include/driver/temp_sensor.h \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
|
||||
|
@ -4,7 +4,7 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/mcpwm_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
|
||||
$(PROJECT_PATH)/components/hal/include/hal/pcnt_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pcnt.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
|
||||
$(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \
|
||||
$(PROJECT_PATH)/components/driver/$(IDF_TARGET)/include/driver/touch_sensor.h \
|
||||
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \
|
||||
|
@ -20,25 +20,19 @@ 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.
|
||||
|
||||
- `Set Up Alarm Action <#set-up-alarm-action>`__ - covers the parameters that should be set up to enable the alarm event.
|
||||
|
||||
- `Register Event Callbacks <#register-event-callbacks>`__ - covers how to hook user specific code to the alarm event callback function.
|
||||
|
||||
- `Start and Stop timer <#start-and-stop-timer>`__ - shows some typical use cases that start the timer with different alarm 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.
|
||||
|
||||
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
|
||||
- `Kconfig options <#kconfig-options>`__ - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
|
||||
|
||||
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).
|
||||
Different ESP chip might have different number of independent timer groups, and within each group, there could also be several independent timers. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#timg>`__] to find out how many hardware timers exist.
|
||||
|
||||
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.
|
||||
|
||||
@ -94,7 +88,7 @@ To make the alarm configurations take effect, one should call :cpp:func:`gptimer
|
||||
|
||||
.. note::
|
||||
|
||||
* If an alarm value is set and the timer has already crossed this value, the alarm will be triggered immediately.
|
||||
If an alarm value is set and the timer has already crossed this value, the alarm will be triggered immediately.
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -239,7 +233,7 @@ Alarm value can be updated dynamically inside the ISR handler callback, by chang
|
||||
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.
|
||||
When power management is enabled (i.e. :ref:`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.
|
||||
|
||||
@ -272,10 +266,17 @@ Thread Safety
|
||||
The factory function :cpp:func:`gptimer_new_timer` is guaranteed to be thread safe by the driver, which means, user can call it from different RTOS tasks without protection by extra locks.
|
||||
Other functions that take the :cpp:type:`gptimer_handle_t` as the first positional parameter, are not thread safe. The lifecycle of the gptimer handle is maintained by the user. So user should avoid calling them concurrently. If it has to, then one should introduce another mutex to prevent the gptimer handle being accessed concurrently.
|
||||
|
||||
Kconfig Options
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` controls where to place the GPTimer control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information.
|
||||
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see `IRAM Safe <#iram-safe>`__ for more information.
|
||||
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
Typical use cases of GPTimer are listed in the example: :example:`peripherals/timer_group/gptimer`.
|
||||
* Typical use cases of GPTimer are listed in the example: :example:`peripherals/timer_group/gptimer`.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
@ -1,101 +1,258 @@
|
||||
Pulse Counter (PCNT)
|
||||
====================
|
||||
|
||||
{IDF_TARGET_PCNT_UNIT_NUM:default="8", esp32s2="4"}
|
||||
{IDF_TARGET_PCNT_MAX_UNIT_NUM:default="7", esp32s2="3"}
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The PCNT (Pulse Counter) module is designed to count the number of rising and/or falling edges of an input signal. Each pulse counter unit has a 16-bit signed counter register and two channels that can be configured to either increment or decrement the counter. Each channel has a signal input that accepts signal edges to be detected, as well as a control input that can be used to enable or disable the signal input. The inputs have optional filters that can be used to discard unwanted glitches in the signal.
|
||||
The PCNT (Pulse Counter) module is designed to count the number of rising and/or falling edges of input signals. The {IDF_TARGET_NAME} contains multiple pulse counter units in the module. [1]_ Each unit is in effect an independent counter with multiple channels, where each channel can increment/decrement the counter on a rising/falling edge. Furthermore, each channel can be configured separately.
|
||||
|
||||
PCNT channels can react to signals of **edge** type and **level** type, however for simple applications, detecting the edge signal is usually sufficient. PCNT channels can be configured react to both pulse edges (i.e., rising and falling edge), and can be configured to increase, decrease or do nothing to the unit's counter on each edge. The level signal is the so-called **control signal**, which is used to control the counting mode of the edge signals that are attached to the same channel. By combining the usage of both edge and level signals, a PCNT unit can act as a **quadrature decoder**.
|
||||
|
||||
Functionality Overview
|
||||
----------------------
|
||||
Besides that, PCNT unit is equipped with a separate glitch filter, which is helpful to remove noise from the signal.
|
||||
|
||||
Description of functionality of this API has been broken down into four sections:
|
||||
Typically, a PCNT module can be used in scenarios like:
|
||||
|
||||
* :ref:`pcnt-api-configuration` - describes counter's configuration parameters and how to setup the counter.
|
||||
* :ref:`pcnt-api-operating-the-counter` - provides information on control functions to pause, measure and clear the counter.
|
||||
* :ref:`pcnt-api-filtering-pulses` - describes options to filtering pulses and the counter control signals.
|
||||
* :ref:`pcnt-api-using-interrupts` - presents how to trigger interrupts on specific states of the counter.
|
||||
- Calculate periodic signal's frequency by counting the pulse numbers within a time slice
|
||||
- Decode quadrature signals into speed and direction
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
|
||||
.. _pcnt-api-configuration:
|
||||
Description of the PCNT functionality is broken down into the following sections:
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
- `Resource Allocation <#resource-allocation>`__ covers how to allocate PCNT units and channels with properly set of configurations. It also covers how to recycle the resources when they finished working.
|
||||
|
||||
The PCNT module has {IDF_TARGET_PCNT_UNIT_NUM} independent counting "units" numbered from 0 to {IDF_TARGET_PCNT_MAX_UNIT_NUM}. In the API they are referred to using :cpp:type:`pcnt_unit_t`. Each unit has two independent channels numbered as 0 and 1 and specified with :cpp:type:`pcnt_channel_t`.
|
||||
- `Set Up Channel Actions <#set-up-channel-actions>`__ - covers how to configure the PCNT channel to behave on different signal edges and levels.
|
||||
- `Watch Points <#watch-points>`__ - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value).
|
||||
- `Register Event Callbacks <#register-event-callbacks>`__ - describes how to hook user specific code to the watch point event callback function.
|
||||
- `Unit IO Control <#unit-io-control>`__ - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
|
||||
- `Power Management <#power-management>`__ - describes what functionality will prevent the chip from going into low power mode.
|
||||
- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
|
||||
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
|
||||
- `Kconfig options <#kconfig-options>`__ - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
|
||||
|
||||
The configuration is provided separately per unit's channel using :cpp:type:`pcnt_config_t` and covers:
|
||||
Resource Allocation
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* The unit and the channel number this configuration refers to.
|
||||
* GPIO numbers of the pulse input and the pulse gate input.
|
||||
* Two pairs of parameters: :cpp:type:`pcnt_ctrl_mode_t` and :cpp:type:`pcnt_count_mode_t` to define how the counter reacts depending on the the status of control signal and how counting is done positive / negative edge of the pulses.
|
||||
* Two limit values (minimum / maximum) that are used to establish watchpoints and trigger interrupts when the pulse count is meeting particular limit.
|
||||
The PCNT unit and channel are represented by :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` respectively. All available units and channels are maintained by the driver in a resource pool, so the user doesn't need to know the exact underlying instance ID.
|
||||
|
||||
Setting up of particular channel is then done by calling a function :cpp:func:`pcnt_unit_config` with above :cpp:type:`pcnt_config_t` as the input parameter.
|
||||
Install PCNT Unit
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
To disable the pulse or the control input pin in configuration, provide :cpp:type:`PCNT_PIN_NOT_USED` instead of the GPIO number.
|
||||
To install a PCNT unit, there's a configuration structure that needs to be given in advance: :cpp:type:`pcnt_unit_config_t`:
|
||||
|
||||
- :cpp:member:`low_limit` and :cpp:member:`high_limit` specify the range for the internal counter. Counter will back to zero when it crosses either limit value.
|
||||
|
||||
.. _pcnt-api-operating-the-counter:
|
||||
Unit allocation and initialization is done by calling a function :cpp:func:`pcnt_new_unit` with :cpp:type:`pcnt_unit_config_t` as an input parameter. The function will return a PCNT unit handle only when it runs correctly. Specifically, when there are no more free PCNT units in the pool (i.e. unit resources have been used up), then this function will return :cpp:member:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT units is recorded by :c:macro:`SOC_PCNT_UNITS_PER_GROUP` for reference.
|
||||
|
||||
Operating the Counter
|
||||
---------------------
|
||||
If a previously created PCNT unit is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`pcnt_del_unit`. Which in return allows the underlying unit hardware to be used for other purposes. Before deleting a PCNT unit, one should ensure the following prerequisites:
|
||||
|
||||
After doing setup with :cpp:func:`pcnt_unit_config`, the counter immediately starts to operate. The accumulated pulse count can be checked by calling :cpp:func:`pcnt_get_counter_value`.
|
||||
- The unit is in a stop state, in other words, the unit either is stopped by :cpp:func:`pcnt_unit_stop` or not started yet.
|
||||
- The unit ought to stop watching any "watch point". See `Watch Points <#watch-points>`__ for how to removing a watch point.
|
||||
|
||||
There are couple of functions that allow to control the counter's operation: :cpp:func:`pcnt_counter_pause`, :cpp:func:`pcnt_counter_resume` and :cpp:func:`pcnt_counter_clear`
|
||||
.. code:: c
|
||||
|
||||
It is also possible to dynamically change the previously set up counter modes with :cpp:func:`pcnt_unit_config` by calling :cpp:func:`pcnt_set_mode`.
|
||||
#define EXAMPLE_PCNT_HIGH_LIMIT 100
|
||||
#define EXAMPLE_PCNT_LOW_LIMIT -100
|
||||
|
||||
If desired, the pulse input pin and the control input pin may be changed "on the fly" using :cpp:func:`pcnt_set_pin`. To disable particular input provide as a function parameter :cpp:type:`PCNT_PIN_NOT_USED` instead of the GPIO number.
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = EXAMPLE_PCNT_HIGH_LIMIT,
|
||||
.low_limit = EXAMPLE_PCNT_LOW_LIMIT,
|
||||
};
|
||||
pcnt_unit_handle_t pcnt_unit = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
|
||||
|
||||
Install PCNT Channel
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To install a PCNT channel, there's a configuration structure that needs to be given in advance: :cpp:type:`pcnt_chan_config_t` as well:
|
||||
|
||||
- :cpp:member:`edge_gpio_num` and :cpp:member:`level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. :cpp:member:`level_gpio_num` is optional and can be assigned with `-1` if it's not used whereas the :cpp:member:`edge_gpio_num` is mandatory.
|
||||
- :cpp:member:`invert_edge_input` and :cpp:member:`invert_level_input` are used to decide whether to invert the input signals before they going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware.
|
||||
- :cpp:member:`io_loop_back` is for debug only, which enables both the GPIO's input and output paths. This can help to simulate the pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO.
|
||||
|
||||
Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e. channel resources have been used up), then this function will return :cpp:member:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT channels within the unit is recorded by :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT` for reference.
|
||||
|
||||
If a previously created PCNT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`pcnt_del_channel`. Which in return allows the underlying channel hardware to be used for other purposes.
|
||||
|
||||
.. code:: c
|
||||
|
||||
#define EXAMPLE_CHAN_GPIO_A 0
|
||||
#define EXAMPLE_CHAN_GPIO_B 2
|
||||
|
||||
pcnt_chan_config_t chan_config = {
|
||||
.edge_gpio_num = EXAMPLE_CHAN_GPIO_A,
|
||||
.level_gpio_num = EXAMPLE_CHAN_GPIO_B,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
|
||||
|
||||
Set Up Channel Actions
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The PCNT will increase/decrease/hold its internal count value when the input pulse signal toggles. User can set different actions for edge signal and/or level signal.
|
||||
|
||||
- :cpp:func:`pcnt_channel_set_edge_action` function is to set specific actions for rising and falling edge of the signal attached to the :cpp:member:`edge_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_edge_action_t`.
|
||||
- :cpp:func:`pcnt_channel_set_level_action` function is to set specific actions for high and low level of the signal attached to the :cpp:member:`level_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_level_action_t`. This function is not mandatory if the :cpp:member:`level_gpio_num` is set to `-1` when allocating PCNT channel by :cpp:func:`pcnt_new_channel`.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// decrease the counter on rising edge, increase the counter on falling edge
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
// keep the counting mode when the control signal is high level, and reverse the counting mode when the control signal is low level
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
Watch Points
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Each PCNT unit can be configured to watch several different values that you're interested in. The value to be watched is also called **Watch Point**. The watch point itself can't exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`low_limit` and :cpp:member:`high_limit`. When the counter reaches either watch point, a watch event will be triggered and notify user by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See `Register Event Callbacks <#register-event-callbacks>`__ for how to register event callbacks.
|
||||
|
||||
The watch point can be added and removed by :cpp:func:`pcnt_unit_add_watch_point` and :cpp:func:`pcnt_unit_remove_watch_point`. The commonly used watch points are: **zero cross**, **maximum / minimum count** and other threshold values. The number of available watch point is limited, :cpp:func:`pcnt_unit_add_watch_point` will return error :c:macro:`ESP_ERR_NOT_FOUND` if it can't find any free hardware resource to save the watch point. User can't add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`.
|
||||
|
||||
It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remove_watch_point` to recycle the watch point resources. Be careful when you recycle the PCNT unit by :cpp:func:`pcnt_del_unit`, the using watch points must be removed from the unit in advance.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// add zero across watch point
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, 0));
|
||||
// add high limit watch point
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, EXAMPLE_PCNT_HIGH_LIMIT));
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When PCNT unit reaches any enabled watch point, specific event will be generated and notify the CPU by interrupt. If you have some function that want to get executed when event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`pcnt_unit_register_event_callbacks`. All supported event callbacks are listed in the :cpp:type:`pcnt_event_callbacks_t`:
|
||||
|
||||
- :cpp:member:`on_reach` sets a callback function for watch point 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:`pcnt_watch_cb_t`.
|
||||
|
||||
User can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions.
|
||||
|
||||
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
|
||||
|
||||
- :cpp:member:`watch_point_value` saves the watch point value that triggers the event.
|
||||
- :cpp:member:`zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_wakeup;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// send watch point to queue, from this interrupt callback
|
||||
xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup);
|
||||
// return whether a high priority task has been waken up by this function
|
||||
return (high_task_wakeup == pdTRUE);
|
||||
}
|
||||
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = example_pcnt_on_reach,
|
||||
};
|
||||
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
|
||||
|
||||
Unit IO Control
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Set Glitch Filter
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PCNT unit features filters to ignore possible short glitches in the signals. The parameters can to be configured for the glitch filter are listed in :cpp:type:`pcnt_glitch_filter_config_t`:
|
||||
|
||||
- :cpp:member:`max_glitch_ns` sets the maximum glitch width, in nano seconds. If a signal pulse's width is smaller than this value, then it will be treated as noise and won't increase/decrease the internal counter.
|
||||
|
||||
User can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_set_glitch_filter` with the filter configuration provided above. Particularly, user can disable the glitch filter later by calling :cpp:func:`pcnt_unit_set_glitch_filter` with a `NULL` filter configuration.
|
||||
|
||||
.. note::
|
||||
|
||||
For the counter not to miss any pulses, the pulse duration should be longer than one APB_CLK cycle (12.5 ns). The pulses are sampled on the edges of the APB_CLK clock and may be missed, if fall between the edges. This applies to counter operation with or without a :ref:`filter <pcnt-api-filtering-pulses>`.
|
||||
The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time user enables the glitch filter. For more information related to power management strategy used in PCNT driver, please see `Power Management <#power-management>`__.
|
||||
|
||||
.. code:: c
|
||||
|
||||
.. _pcnt-api-filtering-pulses:
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
|
||||
|
||||
Filtering Pulses
|
||||
----------------
|
||||
Start/Stop and Clear
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PCNT unit features filters on each of the pulse and control inputs, adding the option to ignore short glitches in the signals.
|
||||
Calling :cpp:func:`pcnt_unit_start` will make the PCNT unit start to work, increase or decrease counter according to pulse signals. On the contrary, calling :cpp:func:`pcnt_unit_stop` will stop the PCNT unit but retain current count value. Instead, clearing counter can only be done by calling :cpp:func:`pcnt_unit_clear_count`.
|
||||
|
||||
The length of ignored pulses is provided in APB_CLK clock cycles by calling :cpp:func:`pcnt_set_filter_value`. The current filter setting may be checked with :cpp:func:`pcnt_get_filter_value`. The APB_CLK clock is running at 80 MHz.
|
||||
.. code::c
|
||||
|
||||
The filter is put into operation / suspended by calling :cpp:func:`pcnt_filter_enable` / :cpp:func:`pcnt_filter_disable`.
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
|
||||
|
||||
Get Count Value
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. _pcnt-api-using-interrupts:
|
||||
User can check current count value at any time by calling :cpp:func:`pcnt_unit_get_count`.
|
||||
|
||||
Using Interrupts
|
||||
----------------
|
||||
.. note::
|
||||
|
||||
There are five counter state watch events, defined in :cpp:type:`pcnt_evt_type_t`, that are able to trigger an interrupt. The event happens on the pulse counter reaching specific values:
|
||||
The returned count value is a **signed** integer, where the sign can be used to reflect the direction. The internal counter will overflow when it reaches high or low limit, but this function doesn't compensate for that loss.
|
||||
|
||||
* Minimum or maximum count values: :cpp:member:`counter_l_lim` or :cpp:member:`counter_h_lim` provided in :cpp:type:`pcnt_config_t` as discussed in :ref:`pcnt-api-configuration`
|
||||
* Threshold 0 or Threshold 1 values set using function :cpp:func:`pcnt_set_event_value`.
|
||||
* Pulse count = 0
|
||||
.. code:: c
|
||||
|
||||
To register, enable or disable an interrupt to service the above events, call :cpp:func:`pcnt_isr_register`, :cpp:func:`pcnt_intr_enable`. and :cpp:func:`pcnt_intr_disable`. To enable or disable events on reaching threshold values, you will also need to call functions :cpp:func:`pcnt_event_enable` and :cpp:func:`pcnt_event_disable`.
|
||||
int pulse_count = 0;
|
||||
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));
|
||||
|
||||
In order to check what are the threshold values currently set, use function :cpp:func:`pcnt_get_event_value`.
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the behavior of PCNT glitch filter and leading to valid signal being treated as noise.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
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 user enables the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is started by :cpp:func:`pcnt_unit_start`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_stop` is called. This requires that the :cpp:func:`pcnt_unit_start` and :cpp:func:`pcnt_unit_stop` should appear in pairs, otherwise the power management will be out of action.
|
||||
|
||||
* Pulse counter with control signal and event interrupt example: :example:`peripherals/pcnt/pulse_count_event`.
|
||||
* Parse the signal generated from rotary encoder: :example:`peripherals/pcnt/rotary_encoder`.
|
||||
IRAM Safe
|
||||
^^^^^^^^^
|
||||
|
||||
By default, the PCNT 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_PCNT_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_PCNT_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:`pcnt_unit_start`
|
||||
- :cpp:func:`pcnt_unit_stop`
|
||||
- :cpp:func:`pcnt_unit_clear_count`
|
||||
- :cpp:func:`pcnt_unit_get_count`
|
||||
|
||||
Thread Safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The factory function :cpp:func:`pcnt_new_unit` and :cpp:func:`pcnt_new_channel` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
|
||||
Other functions that take the :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` as the first positional parameter, are not thread safe. The lifecycle of the PCNT unit and channel handle is maintained by the user. So user should avoid calling them concurrently. If it has to, another mutex should be added to prevent the them being accessed concurrently.
|
||||
|
||||
Kconfig Options
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` controls where to place the PCNT control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information.
|
||||
- :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see `IRAM Safe <#iram-safe>`__ for more information.
|
||||
- :ref:`CONFIG_PCNT_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
* Decode the quadrature signals from rotary encoder: :example:`peripherals/pcnt/rotary_encoder`.
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/pcnt.inc
|
||||
.. include-build-file:: inc/pulse_cnt.inc
|
||||
.. include-build-file:: inc/pcnt_types.inc
|
||||
|
||||
.. [1]
|
||||
Different ESP chip series might have different number of PCNT units and channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#pcnt>`__] for details. The driver won't forbid you from applying for more PCNT units and channels, but it will return error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g. :cpp:func:`pcnt_new_unit`).
|
||||
|
||||
.. [2]
|
||||
:cpp:member:`on_reach` callback and the functions invoked by itself should also be placed in IRAM, users need to take care of them by themselves.
|
||||
|
@ -30,8 +30,7 @@ The previous Kconfig option `RTCIO_SUPPORT_RTC_GPIO_DESC` has been removed, thus
|
||||
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`.
|
||||
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:
|
||||
|
||||
@ -55,11 +54,45 @@ Breaking Changes in Usage
|
||||
- Alarm will always be re-enabled by the driver if :cpp:member:`auto_reload_on_alarm` is set to true.
|
||||
|
||||
UART
|
||||
~~~~
|
||||
----
|
||||
|
||||
- :cpp:member:`uart_isr_register` and :cpp:member:`uart_isr_free` have been removed as the UART interrupt handling is closely related to the driver implementation.
|
||||
|
||||
I2C
|
||||
~~~~
|
||||
---
|
||||
|
||||
- :cpp:member:`i2c_isr_register` and :cpp:member:`i2c_isr_free` have been removed as the I2C interrupt handling is closely related to the driver implementation.
|
||||
|
||||
.. only:: SOC_PCNT_SUPPORTED
|
||||
|
||||
Pulse Counter Driver
|
||||
--------------------
|
||||
|
||||
Pulse counter driver has been redesigned (see :doc:`PCNT <../api-reference/peripherals/pcnt>`), which aims to unify and simplify the usage of PCNT peripheral. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/pcnt.h``. However, by default, including ``driver/pcnt.h`` will bring a build warning like `legacy pcnt driver is deprecated, please migrate to use driver/pulse_cnt.h`. The warning can be suppressed by the Kconfig option :ref:`CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN`.
|
||||
|
||||
The major breaking changes in concept and usage are listed as follows:
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``pcnt_port_t``, ``pcnt_unit_t`` and ``pcnt_channel_t`` which used to identify the hardware unit and channel are removed from user's code. In the new driver, PCNT unit is represented by :cpp:type:`pcnt_unit_handle_t`, likewise, PCNT channel is represented by :cpp:type:`pcnt_channel_handle_t`. Both of them are opaque pointers.
|
||||
- ``pcnt_evt_type_t`` is not used any more, they have been replaced by a universal **Watch Point Event**. In the event callback :cpp:type:`pcnt_watch_cb_t`, it's still possible to distinguish different watch points from :cpp:type:`pcnt_watch_event_data_t`.
|
||||
- ``pcnt_count_mode_t`` is replaced by :cpp:type:`pcnt_channel_edge_action_t`, and ``pcnt_ctrl_mode_t`` is replaced by :cpp:type:`pcnt_channel_level_action_t`.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- In the legacy driver, the PCNT unit configuration and channel configuration were combined into a single function: ``pcnt_unit_config``. Now this is split into two factory APIs: :cpp:func:`pcnt_new_unit` and :cpp:func:`pcnt_new_channel`. Only the count range is necessary for initializing a PCNT unit. GPIO number assignment has been moved to :cpp:func:`pcnt_new_channel`. High/Low control mode and positive/negative edge count mode are set by stand-alone functions: :cpp:func:`pcnt_channel_set_edge_action` and :cpp:func:`pcnt_channel_set_level_action`.
|
||||
- ``pcnt_get_counter_value`` is replaced by :cpp:func:`pcnt_unit_get_count`.
|
||||
- ``pcnt_counter_pause`` is replaced by :cpp:func:`pcnt_unit_stop`.
|
||||
- ``pcnt_counter_resume`` is replaced by :cpp:func:`pcnt_unit_start`.
|
||||
- ``pcnt_counter_clear`` is replaced by :cpp:func:`pcnt_unit_clear_count`.
|
||||
- ``pcnt_intr_enable`` and ``pcnt_intr_disable`` are removed. In the new driver, the interrupt is enabled by registering event callbacks :cpp:func:`pcnt_unit_register_event_callbacks`.
|
||||
- ``pcnt_event_enable`` and ``pcnt_event_disable`` are removed. In the new driver, the PCNT events are enabled/disabled by adding/removing watch points :cpp:func:`pcnt_unit_add_watch_point`, :cpp:func:`pcnt_unit_remove_watch_point`.
|
||||
- ``pcnt_set_event_value`` is removed. In the new driver, event value is also set when adding watch point by :cpp:func:`pcnt_unit_add_watch_point`.
|
||||
- ``pcnt_get_event_value`` and ``pcnt_get_event_status`` are removed. In the new driver, these information are provided by event callback :cpp:type:`pcnt_watch_cb_t` in the :cpp:type:`pcnt_watch_event_data_t`.
|
||||
- ``pcnt_isr_register`` and ``pcnt_isr_unregister`` are removed. Register of the ISR handler from user code is no longer permitted. Users should register event callbacks instead by calling :cpp:func:`pcnt_unit_register_event_callbacks`.
|
||||
- ``pcnt_set_pin`` is removed and the new driver no longer allows the switching of the GPIO at runtime. If you want to change to other GPIOs, please delete the existing PCNT channel by :cpp:func:`pcnt_del_channel` and reinstall with the new GPIO number by :cpp:func:`pcnt_new_channel`.
|
||||
- ``pcnt_filter_enable``, ``pcnt_filter_disable``, and ``pcnt_set_filter_value`` are replaced by :cpp:func:`pcnt_unit_set_glitch_filter`. Meanwhile, ``pcnt_get_filter_value`` has been removed.
|
||||
- ``pcnt_set_mode`` is replaced by :cpp:func:`pcnt_channel_set_edge_action` and :cpp:func:`pcnt_channel_set_level_action`.
|
||||
- ``pcnt_isr_service_install``, ``pcnt_isr_service_uninstall``, ``pcnt_isr_handler_add`` and ``pcnt_isr_handler_remove`` are replaced by :cpp:func:`pcnt_unit_register_event_callbacks`. The default ISR handler is lazy installed in the new driver.
|
||||
|
@ -2,8 +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/pid_ctrl"
|
||||
"$ENV{IDF_PATH}/examples/peripherals/pcnt/rotary_encoder/components")
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components/pid_ctrl")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mcpwm_brushed_dc_control)
|
||||
|
@ -9,8 +9,8 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "pid_ctrl.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
@ -23,7 +23,9 @@
|
||||
#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_PCNT_HIGH_LIMIT 100
|
||||
#define BDC_ENCODER_PCNT_LOW_LIMIT -100
|
||||
#define BDC_ENCODER_PHASEA_GPIO_NUM 36
|
||||
#define BDC_ENCODER_PHASEB_GPIO_NUM 35
|
||||
|
||||
@ -45,7 +47,8 @@ static int expect_pulses = 300;
|
||||
static int real_pulses;
|
||||
|
||||
typedef struct {
|
||||
rotary_encoder_t *encoder;
|
||||
pcnt_unit_handle_t hall_pcnt_encoder;
|
||||
int accumu_count;
|
||||
QueueHandle_t pid_feedback_queue;
|
||||
} motor_control_timer_context_t;
|
||||
|
||||
@ -54,6 +57,13 @@ typedef struct {
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
} motor_control_task_context_t;
|
||||
|
||||
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
motor_control_timer_context_t *ctx = (motor_control_timer_context_t *)user_ctx;
|
||||
ctx->accumu_count += edata->watch_point_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void brushed_motor_set_duty(float duty_cycle)
|
||||
{
|
||||
/* motor moves in forward direction, with duty cycle = duty % */
|
||||
@ -75,9 +85,12 @@ static bool motor_ctrl_timer_cb(gptimer_handle_t timer, const gptimer_alarm_even
|
||||
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;
|
||||
pcnt_unit_handle_t pcnt_unit = user_ctx->hall_pcnt_encoder;
|
||||
|
||||
int cur_pulse_count = 0;
|
||||
pcnt_unit_get_count(pcnt_unit, &cur_pulse_count);
|
||||
cur_pulse_count += user_ctx->accumu_count;
|
||||
|
||||
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);
|
||||
@ -143,6 +156,8 @@ static void register_pid_console_command(void)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static motor_control_timer_context_t my_timer_ctx = {};
|
||||
|
||||
QueueHandle_t pid_fb_queue = xQueueCreate(BDC_PID_FEEDBACK_QUEUE_LEN, sizeof(int));
|
||||
assert(pid_fb_queue);
|
||||
|
||||
@ -160,15 +175,40 @@ void app_main(void)
|
||||
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,
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = BDC_ENCODER_PCNT_HIGH_LIMIT,
|
||||
.low_limit = BDC_ENCODER_PCNT_LOW_LIMIT,
|
||||
};
|
||||
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));
|
||||
pcnt_unit_handle_t pcnt_unit = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
|
||||
.level_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_a = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
|
||||
.level_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_b = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_HIGH_LIMIT));
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_LOW_LIMIT));
|
||||
pcnt_event_callbacks_t pcnt_cbs = {
|
||||
.on_reach = example_pcnt_on_reach,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &pcnt_cbs, &my_timer_ctx));
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
|
||||
|
||||
printf("init PID control block\r\n");
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
@ -193,13 +233,12 @@ void app_main(void)
|
||||
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 = {
|
||||
my_timer_ctx.hall_pcnt_encoder = pcnt_unit;
|
||||
gptimer_event_callbacks_t gptimer_cbs = {
|
||||
.on_alarm = motor_ctrl_timer_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, &my_timer_ctx));
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &gptimer_cbs, &my_timer_ctx));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = BDC_PID_CALCULATION_PERIOD_US,
|
||||
|
@ -1,6 +0,0 @@
|
||||
# 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(pcnt_event)
|
@ -1,75 +0,0 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# Pulse Count Event Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example uses the pulse counter module (PCNT) to count the rising edges of the PWM pulses generated by the LED Controller module (LEDC).
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for power supply and programming
|
||||
|
||||
Pin connection:
|
||||
|
||||
* GPIO4 is the default output GPIO of the 1 Hz pulse generator.
|
||||
* GPIO18 is the default pulse input GPIO. We need to short GPIO4 and GPIO18.
|
||||
* GPIO5 is the default control signal, which can be left floating with internal pull up, or connected to Ground (If GPIO5 is left floating, the value of counter increases with the rising edges of the PWM pulses. If GPIO5 is connected to Ground, the value decreases).
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
|
||||
Run this example, and you can see the following output log on the serial monitor:
|
||||
(Here, GPIO5 is connected to Ground)
|
||||
|
||||
```
|
||||
Current counter value :-1
|
||||
Current counter value :-2
|
||||
Current counter value :-3
|
||||
Current counter value :-4
|
||||
Event PCNT unit[0]; cnt: -5
|
||||
THRES0 EVT
|
||||
Current counter value :-5
|
||||
Current counter value :-6
|
||||
Current counter value :-7
|
||||
Current counter value :-8
|
||||
Current counter value :-9
|
||||
Event PCNT unit[0]; cnt: 0
|
||||
L_LIM EVT
|
||||
ZERO EVT
|
||||
Current counter value :0
|
||||
Current counter value :-1
|
||||
...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* program upload failure
|
||||
|
||||
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
|
||||
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
|
||||
|
||||
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,2 +0,0 @@
|
||||
idf_component_register(SRCS "pcnt_event_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -1,197 +0,0 @@
|
||||
/* Pulse counter module - Example
|
||||
|
||||
For other examples please check:
|
||||
https://github.com/espressif/esp-idf/tree/master/examples
|
||||
|
||||
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 "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
/**
|
||||
* TEST CODE BRIEF
|
||||
*
|
||||
* Use PCNT module to count rising edges generated by LEDC module.
|
||||
*
|
||||
* Functionality of GPIOs used in this example:
|
||||
* - GPIO18 - output pin of a sample 1 Hz pulse generator,
|
||||
* - GPIO4 - pulse input pin,
|
||||
* - GPIO5 - control input pin.
|
||||
*
|
||||
* Load example, open a serial port to view the message printed on your screen.
|
||||
*
|
||||
* To do this test, you should connect GPIO18 with GPIO4.
|
||||
* GPIO5 is the control signal, you can leave it floating with internal pull up,
|
||||
* or connect it to ground. If left floating, the count value will be increasing.
|
||||
* If you connect GPIO5 to GND, the count value will be decreasing.
|
||||
*
|
||||
* An interrupt will be triggered when the counter value:
|
||||
* - reaches 'thresh1' or 'thresh0' value,
|
||||
* - reaches 'l_lim' value or 'h_lim' value,
|
||||
* - will be reset to zero.
|
||||
*/
|
||||
#define PCNT_H_LIM_VAL 10
|
||||
#define PCNT_L_LIM_VAL -10
|
||||
#define PCNT_THRESH1_VAL 5
|
||||
#define PCNT_THRESH0_VAL -5
|
||||
#define PCNT_INPUT_SIG_IO 4 // Pulse Input GPIO
|
||||
#define PCNT_INPUT_CTRL_IO 5 // Control GPIO HIGH=count up, LOW=count down
|
||||
#define LEDC_OUTPUT_IO 18 // Output GPIO of a sample 1 Hz pulse generator
|
||||
|
||||
QueueHandle_t pcnt_evt_queue; // A queue to handle pulse counter events
|
||||
|
||||
/* A sample structure to pass events from the PCNT
|
||||
* interrupt handler to the main program.
|
||||
*/
|
||||
typedef struct {
|
||||
int unit; // the PCNT unit that originated an interrupt
|
||||
uint32_t status; // information on the event type that caused the interrupt
|
||||
} pcnt_evt_t;
|
||||
|
||||
/* Decode what PCNT's unit originated an interrupt
|
||||
* and pass this information together with the event type
|
||||
* the main program using a queue.
|
||||
*/
|
||||
static void IRAM_ATTR pcnt_example_intr_handler(void *arg)
|
||||
{
|
||||
int pcnt_unit = (int)arg;
|
||||
pcnt_evt_t evt;
|
||||
evt.unit = pcnt_unit;
|
||||
/* Save the PCNT event type that caused an interrupt
|
||||
to pass it to the main program */
|
||||
pcnt_get_event_status(pcnt_unit, &evt.status);
|
||||
xQueueSendFromISR(pcnt_evt_queue, &evt, NULL);
|
||||
}
|
||||
|
||||
/* Configure LED PWM Controller
|
||||
* to output sample pulses at 1 Hz with duty of about 10%
|
||||
*/
|
||||
static void ledc_init(void)
|
||||
{
|
||||
// Prepare and then apply the LEDC PWM timer configuration
|
||||
ledc_timer_config_t ledc_timer;
|
||||
ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;
|
||||
ledc_timer.timer_num = LEDC_TIMER_1;
|
||||
ledc_timer.duty_resolution = LEDC_TIMER_10_BIT;
|
||||
ledc_timer.freq_hz = 1; // set output frequency at 1 Hz
|
||||
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
|
||||
ledc_timer_config(&ledc_timer);
|
||||
|
||||
// Prepare and then apply the LEDC PWM channel configuration
|
||||
ledc_channel_config_t ledc_channel;
|
||||
ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;
|
||||
ledc_channel.channel = LEDC_CHANNEL_1;
|
||||
ledc_channel.timer_sel = LEDC_TIMER_1;
|
||||
ledc_channel.intr_type = LEDC_INTR_DISABLE;
|
||||
ledc_channel.gpio_num = LEDC_OUTPUT_IO;
|
||||
ledc_channel.duty = 100; // set duty at about 10%
|
||||
ledc_channel.hpoint = 0;
|
||||
ledc_channel_config(&ledc_channel);
|
||||
}
|
||||
|
||||
/* Initialize PCNT functions:
|
||||
* - configure and initialize PCNT
|
||||
* - set up the input filter
|
||||
* - set up the counter events to watch
|
||||
*/
|
||||
static void pcnt_example_init(int unit)
|
||||
{
|
||||
/* Prepare configuration for the PCNT unit */
|
||||
pcnt_config_t pcnt_config = {
|
||||
// Set PCNT input signal and control GPIOs
|
||||
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
|
||||
.ctrl_gpio_num = PCNT_INPUT_CTRL_IO,
|
||||
.channel = PCNT_CHANNEL_0,
|
||||
.unit = unit,
|
||||
// What to do on the positive / negative edge of pulse input?
|
||||
.pos_mode = PCNT_COUNT_INC, // Count up on the positive edge
|
||||
.neg_mode = PCNT_COUNT_DIS, // Keep the counter value on the negative edge
|
||||
// What to do when control input is low or high?
|
||||
.lctrl_mode = PCNT_MODE_REVERSE, // Reverse counting direction if low
|
||||
.hctrl_mode = PCNT_MODE_KEEP, // Keep the primary counter mode if high
|
||||
// Set the maximum and minimum limit values to watch
|
||||
.counter_h_lim = PCNT_H_LIM_VAL,
|
||||
.counter_l_lim = PCNT_L_LIM_VAL,
|
||||
};
|
||||
/* Initialize PCNT unit */
|
||||
pcnt_unit_config(&pcnt_config);
|
||||
|
||||
/* Configure and enable the input filter */
|
||||
pcnt_set_filter_value(unit, 100);
|
||||
pcnt_filter_enable(unit);
|
||||
|
||||
/* Set threshold 0 and 1 values and enable events to watch */
|
||||
pcnt_set_event_value(unit, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL);
|
||||
pcnt_event_enable(unit, PCNT_EVT_THRES_1);
|
||||
pcnt_set_event_value(unit, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL);
|
||||
pcnt_event_enable(unit, PCNT_EVT_THRES_0);
|
||||
/* Enable events on zero, maximum and minimum limit values */
|
||||
pcnt_event_enable(unit, PCNT_EVT_ZERO);
|
||||
pcnt_event_enable(unit, PCNT_EVT_H_LIM);
|
||||
pcnt_event_enable(unit, PCNT_EVT_L_LIM);
|
||||
|
||||
/* Initialize PCNT's counter */
|
||||
pcnt_counter_pause(unit);
|
||||
pcnt_counter_clear(unit);
|
||||
|
||||
/* Install interrupt service and add isr callback handler */
|
||||
pcnt_isr_service_install(0);
|
||||
pcnt_isr_handler_add(unit, pcnt_example_intr_handler, (void *)unit);
|
||||
|
||||
/* Everything is set up, now go to counting */
|
||||
pcnt_counter_resume(unit);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
int pcnt_unit = PCNT_UNIT_0;
|
||||
/* Initialize LEDC to generate sample pulse signal */
|
||||
ledc_init();
|
||||
|
||||
/* Initialize PCNT event queue and PCNT functions */
|
||||
pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));
|
||||
pcnt_example_init(pcnt_unit);
|
||||
|
||||
int16_t count = 0;
|
||||
pcnt_evt_t evt;
|
||||
portBASE_TYPE res;
|
||||
while (1) {
|
||||
/* Wait for the event information passed from PCNT's interrupt handler.
|
||||
* Once received, decode the event type and print it on the serial monitor.
|
||||
*/
|
||||
res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);
|
||||
if (res == pdTRUE) {
|
||||
pcnt_get_counter_value(pcnt_unit, &count);
|
||||
ESP_LOGI(TAG, "Event PCNT unit[%d]; cnt: %d", evt.unit, count);
|
||||
if (evt.status & PCNT_EVT_THRES_1) {
|
||||
ESP_LOGI(TAG, "THRES1 EVT");
|
||||
}
|
||||
if (evt.status & PCNT_EVT_THRES_0) {
|
||||
ESP_LOGI(TAG, "THRES0 EVT");
|
||||
}
|
||||
if (evt.status & PCNT_EVT_L_LIM) {
|
||||
ESP_LOGI(TAG, "L_LIM EVT");
|
||||
}
|
||||
if (evt.status & PCNT_EVT_H_LIM) {
|
||||
ESP_LOGI(TAG, "H_LIM EVT");
|
||||
}
|
||||
if (evt.status & PCNT_EVT_ZERO) {
|
||||
ESP_LOGI(TAG, "ZERO EVT");
|
||||
}
|
||||
} else {
|
||||
pcnt_get_counter_value(pcnt_unit, &count);
|
||||
ESP_LOGI(TAG, "Current counter value :%d", count);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
| Supported Targets | ESP32 | ESP32-S2 |
|
||||
| ----------------- | ----- | -------- |
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- |
|
||||
|
||||
# Rotary Encoder Example
|
||||
|
||||
@ -49,6 +49,8 @@ Connection :
|
||||
+--------+ +---------------------------------+
|
||||
```
|
||||
|
||||
The GPIO used by the example can be changed according to your board by `EXAMPLE_EC11_GPIO_A` and `EXAMPLE_EC11_GPIO_B` in [source file](main/rotary_encoder_example_main.c);
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
@ -60,18 +62,42 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (181323) example: Encoder value: 0
|
||||
I (182323) example: Encoder value: 0
|
||||
I (183323) example: Encoder value: -12
|
||||
I (184323) example: Encoder value: -18
|
||||
I (185323) example: Encoder value: -24
|
||||
I (188323) example: Encoder value: 4
|
||||
I (189323) example: Encoder value: 8
|
||||
I (190323) example: Encoder value: 8
|
||||
I (191323) example: Encoder value: 8
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (325) example: install pcnt unit
|
||||
I (335) example: set glitch filter
|
||||
I (345) example: install pcnt channels
|
||||
I (395) example: set edge and level actions for pcnt channels
|
||||
I (405) example: add watch points and register callbacks
|
||||
I (405) example: clear pcnt unit
|
||||
I (415) example: start pcnt unit
|
||||
I (1415) example: Pulse count: 0
|
||||
I (2415) example: Pulse count: 8
|
||||
I (3415) example: Pulse count: 27
|
||||
I (4415) example: Pulse count: 40
|
||||
I (4705) example: Watch point event, count: 50
|
||||
I (5705) example: Pulse count: 72
|
||||
I (6705) example: Pulse count: 96
|
||||
I (6785) example: Watch point event, count: 100
|
||||
I (6785) example: Watch point event, count: 0
|
||||
I (7785) example: Pulse count: 8
|
||||
I (8785) example: Pulse count: 8
|
||||
I (9225) example: Watch point event, count: 0
|
||||
I (10225) example: Pulse count: -20
|
||||
I (11225) example: Pulse count: -28
|
||||
I (12225) example: Pulse count: -48
|
||||
I (12995) example: Watch point event, count: -50
|
||||
I (13995) example: Pulse count: -68
|
||||
I (14995) example: Pulse count: -82
|
||||
I (15995) example: Pulse count: -92
|
||||
I (16875) example: Watch point event, count: -100
|
||||
I (16875) example: Watch point event, count: 0
|
||||
I (17875) example: Pulse count: -12
|
||||
I (18875) example: Pulse count: -12
|
||||
I (19875) example: Pulse count: -12
|
||||
```
|
||||
|
||||
This example enables the 4X mode to parse the rotary signals, which means, each complete rotary step will result PCNT counter to increase/decrease by 4, depending on the direction of rotation.
|
||||
This example enables the 4X mode to parse the rotary signals, which means, each complete rotary step will result in PCNT counter increasing or decreasing by 4, depending on the direction of rotation.
|
||||
The example adds five watch points, events will be triggered when counter reaches to any watch point.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
set(component_srcs "src/rotary_encoder_pcnt_ec11.c")
|
||||
|
||||
idf_component_register(SRCS "${component_srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ""
|
||||
PRIV_REQUIRES "driver"
|
||||
REQUIRES "")
|
@ -1,127 +0,0 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Type of Rotary underlying device handle
|
||||
*
|
||||
*/
|
||||
typedef void *rotary_encoder_dev_t;
|
||||
|
||||
/**
|
||||
* @brief Type of rotary encoder configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
rotary_encoder_dev_t dev; /*!< Underlying device handle */
|
||||
int phase_a_gpio_num; /*!< Phase A GPIO number */
|
||||
int phase_b_gpio_num; /*!< Phase B GPIO number */
|
||||
int flags; /*!< Extra flags */
|
||||
} rotary_encoder_config_t;
|
||||
|
||||
/**
|
||||
* @brief Default rotary encoder configuration
|
||||
*
|
||||
*/
|
||||
#define ROTARY_ENCODER_DEFAULT_CONFIG(dev_hdl, gpio_a, gpio_b) \
|
||||
{ \
|
||||
.dev = dev_hdl, \
|
||||
.phase_a_gpio_num = gpio_a, \
|
||||
.phase_b_gpio_num = gpio_b, \
|
||||
.flags = 0, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Type of rotary encoder handle
|
||||
*
|
||||
*/
|
||||
typedef struct rotary_encoder_t rotary_encoder_t;
|
||||
|
||||
/**
|
||||
* @brief Rotary encoder interface
|
||||
*
|
||||
*/
|
||||
struct rotary_encoder_t {
|
||||
/**
|
||||
* @brief Filter out glitch from input signals
|
||||
*
|
||||
* @param encoder Rotary encoder handle
|
||||
* @param max_glitch_us Maximum glitch duration, in us
|
||||
* @return
|
||||
* - ESP_OK: Set glitch filter successfully
|
||||
* - ESP_FAIL: Set glitch filter failed because of other error
|
||||
*/
|
||||
esp_err_t (*set_glitch_filter)(rotary_encoder_t *encoder, uint32_t max_glitch_us);
|
||||
|
||||
/**
|
||||
* @brief Start rotary encoder
|
||||
*
|
||||
* @param encoder Rotary encoder handle
|
||||
* @return
|
||||
* - ESP_OK: Start rotary encoder successfully
|
||||
* - ESP_FAIL: Start rotary encoder failed because of other error
|
||||
*/
|
||||
esp_err_t (*start)(rotary_encoder_t *encoder);
|
||||
|
||||
/**
|
||||
* @brief Stop rotary encoder
|
||||
*
|
||||
* @param encoder Rotary encoder handle
|
||||
* @return
|
||||
* - ESP_OK: Stop rotary encoder successfully
|
||||
* - ESP_FAIL: Stop rotary encoder failed because of other error
|
||||
*/
|
||||
esp_err_t (*stop)(rotary_encoder_t *encoder);
|
||||
|
||||
/**
|
||||
* @brief Recycle rotary encoder memory
|
||||
*
|
||||
* @param encoder Rotary encoder handle
|
||||
* @return
|
||||
* - ESP_OK: Recycle rotary encoder memory successfully
|
||||
* - ESP_FAIL: rotary encoder memory failed because of other error
|
||||
*/
|
||||
esp_err_t (*del)(rotary_encoder_t *encoder);
|
||||
|
||||
/**
|
||||
* @brief Get rotary encoder counter value
|
||||
*
|
||||
* @param encoder Rotary encoder handle
|
||||
* @return Current counter value (the sign indicates the direction of rotation)
|
||||
*/
|
||||
int (*get_counter_value)(rotary_encoder_t *encoder);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create rotary encoder instance for EC11
|
||||
*
|
||||
* @param config Rotary encoder configuration
|
||||
* @param ret_encoder Returned rotary encoder handle
|
||||
* @return
|
||||
* - ESP_OK: Create rotary encoder instance successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create rotary encoder instance failed because of some invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create rotary encoder instance failed because there's no enough capable memory
|
||||
* - ESP_FAIL: Create rotary encoder instance failed because of other error
|
||||
*/
|
||||
esp_err_t rotary_encoder_new_ec11(const rotary_encoder_config_t *config, rotary_encoder_t **ret_encoder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,180 +0,0 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_compiler.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/pcnt.h"
|
||||
#include "sys/lock.h"
|
||||
#include "hal/pcnt_hal.h"
|
||||
#include "rotary_encoder.h"
|
||||
|
||||
static const char *TAG = "rotary_encoder";
|
||||
|
||||
#define ROTARY_CHECK(a, msg, tag, ret, ...) \
|
||||
do { \
|
||||
if (unlikely(!(a))) { \
|
||||
ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
ret_code = ret; \
|
||||
goto tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EC11_PCNT_DEFAULT_HIGH_LIMIT (100)
|
||||
#define EC11_PCNT_DEFAULT_LOW_LIMIT (-100)
|
||||
|
||||
// A flag to identify if pcnt isr service has been installed.
|
||||
static bool is_pcnt_isr_service_installed = false;
|
||||
// A lock to avoid pcnt isr service being installed twice in multiple threads.
|
||||
static _lock_t isr_service_install_lock;
|
||||
#define LOCK_ACQUIRE() _lock_acquire(&isr_service_install_lock)
|
||||
#define LOCK_RELEASE() _lock_release(&isr_service_install_lock)
|
||||
|
||||
typedef struct {
|
||||
int accumu_count;
|
||||
rotary_encoder_t parent;
|
||||
pcnt_unit_t pcnt_unit;
|
||||
} ec11_t;
|
||||
|
||||
static esp_err_t ec11_set_glitch_filter(rotary_encoder_t *encoder, uint32_t max_glitch_us)
|
||||
{
|
||||
esp_err_t ret_code = ESP_OK;
|
||||
ec11_t *ec11 = __containerof(encoder, ec11_t, parent);
|
||||
|
||||
/* Configure and enable the input filter */
|
||||
ROTARY_CHECK(pcnt_set_filter_value(ec11->pcnt_unit, max_glitch_us * 80) == ESP_OK, "set glitch filter failed", err, ESP_FAIL);
|
||||
|
||||
if (max_glitch_us) {
|
||||
pcnt_filter_enable(ec11->pcnt_unit);
|
||||
} else {
|
||||
pcnt_filter_disable(ec11->pcnt_unit);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
static esp_err_t ec11_start(rotary_encoder_t *encoder)
|
||||
{
|
||||
ec11_t *ec11 = __containerof(encoder, ec11_t, parent);
|
||||
pcnt_counter_resume(ec11->pcnt_unit);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t ec11_stop(rotary_encoder_t *encoder)
|
||||
{
|
||||
ec11_t *ec11 = __containerof(encoder, ec11_t, parent);
|
||||
pcnt_counter_pause(ec11->pcnt_unit);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int ec11_get_counter_value(rotary_encoder_t *encoder)
|
||||
{
|
||||
ec11_t *ec11 = __containerof(encoder, ec11_t, parent);
|
||||
int16_t val = 0;
|
||||
pcnt_get_counter_value(ec11->pcnt_unit, &val);
|
||||
return val + ec11->accumu_count;
|
||||
}
|
||||
|
||||
static esp_err_t ec11_del(rotary_encoder_t *encoder)
|
||||
{
|
||||
ec11_t *ec11 = __containerof(encoder, ec11_t, parent);
|
||||
free(ec11);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void ec11_pcnt_overflow_handler(void *arg)
|
||||
{
|
||||
ec11_t *ec11 = (ec11_t *)arg;
|
||||
uint32_t status = 0;
|
||||
pcnt_get_event_status(ec11->pcnt_unit, &status);
|
||||
|
||||
if (status & PCNT_EVT_H_LIM) {
|
||||
ec11->accumu_count += EC11_PCNT_DEFAULT_HIGH_LIMIT;
|
||||
} else if (status & PCNT_EVT_L_LIM) {
|
||||
ec11->accumu_count += EC11_PCNT_DEFAULT_LOW_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t rotary_encoder_new_ec11(const rotary_encoder_config_t *config, rotary_encoder_t **ret_encoder)
|
||||
{
|
||||
esp_err_t ret_code = ESP_OK;
|
||||
ec11_t *ec11 = NULL;
|
||||
|
||||
ROTARY_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG);
|
||||
ROTARY_CHECK(ret_encoder, "can't assign context to null", err, ESP_ERR_INVALID_ARG);
|
||||
|
||||
ec11 = calloc(1, sizeof(ec11_t));
|
||||
ROTARY_CHECK(ec11, "allocate context memory failed", err, ESP_ERR_NO_MEM);
|
||||
|
||||
ec11->pcnt_unit = (pcnt_unit_t)(config->dev);
|
||||
|
||||
// Configure channel 0
|
||||
pcnt_config_t dev_config = {
|
||||
.pulse_gpio_num = config->phase_a_gpio_num,
|
||||
.ctrl_gpio_num = config->phase_b_gpio_num,
|
||||
.channel = PCNT_CHANNEL_0,
|
||||
.unit = ec11->pcnt_unit,
|
||||
.pos_mode = PCNT_COUNT_DEC,
|
||||
.neg_mode = PCNT_COUNT_INC,
|
||||
.lctrl_mode = PCNT_MODE_REVERSE,
|
||||
.hctrl_mode = PCNT_MODE_KEEP,
|
||||
.counter_h_lim = EC11_PCNT_DEFAULT_HIGH_LIMIT,
|
||||
.counter_l_lim = EC11_PCNT_DEFAULT_LOW_LIMIT,
|
||||
};
|
||||
ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 0 failed", err, ESP_FAIL);
|
||||
|
||||
// Configure channel 1
|
||||
dev_config.pulse_gpio_num = config->phase_b_gpio_num;
|
||||
dev_config.ctrl_gpio_num = config->phase_a_gpio_num;
|
||||
dev_config.channel = PCNT_CHANNEL_1;
|
||||
dev_config.pos_mode = PCNT_COUNT_INC;
|
||||
dev_config.neg_mode = PCNT_COUNT_DEC;
|
||||
ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 1 failed", err, ESP_FAIL);
|
||||
|
||||
// PCNT pause and reset value
|
||||
pcnt_counter_pause(ec11->pcnt_unit);
|
||||
pcnt_counter_clear(ec11->pcnt_unit);
|
||||
|
||||
|
||||
// register interrupt handler in a thread-safe way
|
||||
LOCK_ACQUIRE();
|
||||
if (!is_pcnt_isr_service_installed) {
|
||||
ROTARY_CHECK(pcnt_isr_service_install(0) == ESP_OK, "install isr service failed", err, ESP_FAIL);
|
||||
// make sure pcnt isr service won't be installed more than one time
|
||||
is_pcnt_isr_service_installed = true;
|
||||
}
|
||||
LOCK_RELEASE();
|
||||
|
||||
pcnt_isr_handler_add(ec11->pcnt_unit, ec11_pcnt_overflow_handler, ec11);
|
||||
|
||||
pcnt_event_enable(ec11->pcnt_unit, PCNT_EVT_H_LIM);
|
||||
pcnt_event_enable(ec11->pcnt_unit, PCNT_EVT_L_LIM);
|
||||
|
||||
ec11->parent.del = ec11_del;
|
||||
ec11->parent.start = ec11_start;
|
||||
ec11->parent.stop = ec11_stop;
|
||||
ec11->parent.set_glitch_filter = ec11_set_glitch_filter;
|
||||
ec11->parent.get_counter_value = ec11_get_counter_value;
|
||||
|
||||
*ret_encoder = &(ec11->parent);
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (ec11) {
|
||||
free(ec11);
|
||||
}
|
||||
return ret_code;
|
||||
}
|
@ -1,37 +1,93 @@
|
||||
/* PCNT example -- Rotary Encoder
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
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 "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#define EXAMPLE_PCNT_HIGH_LIMIT 100
|
||||
#define EXAMPLE_PCNT_LOW_LIMIT -100
|
||||
|
||||
#define EXAMPLE_EC11_GPIO_A 0
|
||||
#define EXAMPLE_EC11_GPIO_B 2
|
||||
|
||||
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_wakeup;
|
||||
QueueHandle_t queue = (QueueHandle_t)user_ctx;
|
||||
// send event data to queue, from this interrupt callback
|
||||
xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup);
|
||||
return (high_task_wakeup == pdTRUE);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Rotary encoder underlying device is represented by a PCNT unit in this example
|
||||
uint32_t pcnt_unit = 0;
|
||||
ESP_LOGI(TAG, "install pcnt unit");
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = EXAMPLE_PCNT_HIGH_LIMIT,
|
||||
.low_limit = EXAMPLE_PCNT_LOW_LIMIT,
|
||||
};
|
||||
pcnt_unit_handle_t pcnt_unit = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
|
||||
|
||||
// Create rotary encoder instance
|
||||
rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG((rotary_encoder_dev_t)pcnt_unit, 14, 15);
|
||||
rotary_encoder_t *encoder = NULL;
|
||||
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &encoder));
|
||||
ESP_LOGI(TAG, "set glitch filter");
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
|
||||
|
||||
// Filter out glitch (1us)
|
||||
ESP_ERROR_CHECK(encoder->set_glitch_filter(encoder, 1));
|
||||
ESP_LOGI(TAG, "install pcnt channels");
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = EXAMPLE_EC11_GPIO_A,
|
||||
.level_gpio_num = EXAMPLE_EC11_GPIO_B,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_a = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = EXAMPLE_EC11_GPIO_B,
|
||||
.level_gpio_num = EXAMPLE_EC11_GPIO_A,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_b = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
|
||||
|
||||
// Start encoder
|
||||
ESP_ERROR_CHECK(encoder->start(encoder));
|
||||
ESP_LOGI(TAG, "set edge and level actions for pcnt channels");
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
ESP_LOGI(TAG, "add watch points and register callbacks");
|
||||
int watch_points[] = {EXAMPLE_PCNT_LOW_LIMIT, -50, 0, 50, EXAMPLE_PCNT_HIGH_LIMIT};
|
||||
for (size_t i = 0; i < sizeof(watch_points) / sizeof(watch_points[0]); i++) {
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, watch_points[i]));
|
||||
}
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = example_pcnt_on_reach,
|
||||
};
|
||||
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
|
||||
|
||||
ESP_LOGI(TAG, "clear pcnt unit");
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
|
||||
ESP_LOGI(TAG, "start pcnt unit");
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
|
||||
|
||||
// Report counter value
|
||||
int pulse_count = 0;
|
||||
int event_count = 0;
|
||||
while (1) {
|
||||
ESP_LOGI(TAG, "Encoder value: %d", encoder->get_counter_value(encoder));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
if (xQueueReceive(queue, &event_count, pdMS_TO_TICKS(1000))) {
|
||||
ESP_LOGI(TAG, "Watch point event, count: %d", event_count);
|
||||
} else {
|
||||
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));
|
||||
ESP_LOGI(TAG, "Pulse count: %d", pulse_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
# 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.generic
|
||||
def test_gptimer_example(dut: Dut) -> None:
|
||||
dut.expect_exact('install pcnt unit')
|
||||
dut.expect_exact('set glitch filter')
|
||||
dut.expect_exact('install pcnt channels')
|
||||
dut.expect_exact('set edge and level actions for pcnt channels')
|
||||
dut.expect_exact('add watch points and register callbacks')
|
||||
dut.expect_exact('clear pcnt unit')
|
||||
dut.expect_exact('start pcnt unit')
|
||||
res = dut.expect(r'Pulse count: (\d+)')
|
||||
count_val = res.group(1).decode('utf8')
|
||||
assert -100 <= int(count_val) <= 100
|
@ -8,29 +8,29 @@ from pytest_embedded import Dut
|
||||
@pytest.mark.supported_targets
|
||||
@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)
|
||||
dut.expect_exact('Create timer handle', timeout=5)
|
||||
dut.expect_exact('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')
|
||||
dut.expect_exact('Set count value')
|
||||
dut.expect_exact('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)
|
||||
dut.expect_exact('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')
|
||||
dut.expect_exact('Stop timer')
|
||||
dut.expect_exact('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')
|
||||
dut.expect_exact('Stop timer')
|
||||
dut.expect_exact('Delete timer')
|
||||
|
@ -818,7 +818,6 @@ components/hal/esp32/include/hal/dac_ll.h
|
||||
components/hal/esp32/include/hal/i2c_ll.h
|
||||
components/hal/esp32/include/hal/interrupt_controller_ll.h
|
||||
components/hal/esp32/include/hal/mpu_ll.h
|
||||
components/hal/esp32/include/hal/pcnt_ll.h
|
||||
components/hal/esp32/include/hal/rtc_cntl_ll.h
|
||||
components/hal/esp32/include/hal/rtc_io_ll.h
|
||||
components/hal/esp32/include/hal/rwdt_ll.h
|
||||
@ -901,7 +900,6 @@ components/hal/esp32s2/include/hal/interrupt_controller_ll.h
|
||||
components/hal/esp32s2/include/hal/memprot_ll.h
|
||||
components/hal/esp32s2/include/hal/memprot_peri_ll.h
|
||||
components/hal/esp32s2/include/hal/mpu_ll.h
|
||||
components/hal/esp32s2/include/hal/pcnt_ll.h
|
||||
components/hal/esp32s2/include/hal/rtc_cntl_ll.h
|
||||
components/hal/esp32s2/include/hal/rtc_io_ll.h
|
||||
components/hal/esp32s2/include/hal/sha_ll.h
|
||||
@ -927,7 +925,6 @@ components/hal/esp32s3/include/hal/i2c_ll.h
|
||||
components/hal/esp32s3/include/hal/interrupt_controller_ll.h
|
||||
components/hal/esp32s3/include/hal/memprot_ll.h
|
||||
components/hal/esp32s3/include/hal/mpu_ll.h
|
||||
components/hal/esp32s3/include/hal/pcnt_ll.h
|
||||
components/hal/esp32s3/include/hal/rtc_cntl_ll.h
|
||||
components/hal/esp32s3/include/hal/rwdt_ll.h
|
||||
components/hal/esp32s3/include/hal/sha_ll.h
|
||||
@ -958,8 +955,6 @@ components/hal/include/hal/mcpwm_hal.h
|
||||
components/hal/include/hal/mcpwm_types.h
|
||||
components/hal/include/hal/mpu_hal.h
|
||||
components/hal/include/hal/mpu_types.h
|
||||
components/hal/include/hal/pcnt_hal.h
|
||||
components/hal/include/hal/pcnt_types.h
|
||||
components/hal/include/hal/rmt_types.h
|
||||
components/hal/include/hal/rtc_io_types.h
|
||||
components/hal/include/hal/sdio_slave_hal.h
|
||||
@ -987,7 +982,6 @@ components/hal/interrupt_controller_hal.c
|
||||
components/hal/ledc_hal_iram.c
|
||||
components/hal/mcpwm_hal.c
|
||||
components/hal/mpu_hal.c
|
||||
components/hal/pcnt_hal.c
|
||||
components/hal/platform_port/include/hal/assert.h
|
||||
components/hal/platform_port/include/hal/check.h
|
||||
components/hal/platform_port/include/hal/log.h
|
||||
@ -1315,8 +1309,6 @@ components/soc/esp32/include/soc/ledc_reg.h
|
||||
components/soc/esp32/include/soc/ledc_struct.h
|
||||
components/soc/esp32/include/soc/mmu.h
|
||||
components/soc/esp32/include/soc/nrx_reg.h
|
||||
components/soc/esp32/include/soc/pcnt_reg.h
|
||||
components/soc/esp32/include/soc/pcnt_struct.h
|
||||
components/soc/esp32/include/soc/pid.h
|
||||
components/soc/esp32/include/soc/reset_reasons.h
|
||||
components/soc/esp32/include/soc/rmt_reg.h
|
||||
@ -1352,7 +1344,6 @@ components/soc/esp32/include/soc/wdev_reg.h
|
||||
components/soc/esp32/interrupts.c
|
||||
components/soc/esp32/ledc_periph.c
|
||||
components/soc/esp32/mcpwm_periph.c
|
||||
components/soc/esp32/pcnt_periph.c
|
||||
components/soc/esp32/rmt_periph.c
|
||||
components/soc/esp32/sdio_slave_periph.c
|
||||
components/soc/esp32/sdmmc_periph.c
|
||||
@ -1511,8 +1502,6 @@ components/soc/esp32s2/include/soc/ledc_struct.h
|
||||
components/soc/esp32s2/include/soc/memprot_defs.h
|
||||
components/soc/esp32s2/include/soc/mmu.h
|
||||
components/soc/esp32s2/include/soc/nrx_reg.h
|
||||
components/soc/esp32s2/include/soc/pcnt_reg.h
|
||||
components/soc/esp32s2/include/soc/pcnt_struct.h
|
||||
components/soc/esp32s2/include/soc/reset_reasons.h
|
||||
components/soc/esp32s2/include/soc/rtc_cntl_reg.h
|
||||
components/soc/esp32s2/include/soc/rtc_cntl_struct.h
|
||||
@ -1556,7 +1545,6 @@ components/soc/esp32s2/include/soc/usbh_struct.h
|
||||
components/soc/esp32s2/include/soc/wdev_reg.h
|
||||
components/soc/esp32s2/interrupts.c
|
||||
components/soc/esp32s2/ledc_periph.c
|
||||
components/soc/esp32s2/pcnt_periph.c
|
||||
components/soc/esp32s2/rmt_periph.c
|
||||
components/soc/esp32s2/sigmadelta_periph.c
|
||||
components/soc/esp32s2/spi_periph.c
|
||||
@ -1605,9 +1593,6 @@ components/soc/esp32s3/include/soc/ledc_struct.h
|
||||
components/soc/esp32s3/include/soc/mmu.h
|
||||
components/soc/esp32s3/include/soc/mpu_caps.h
|
||||
components/soc/esp32s3/include/soc/nrx_reg.h
|
||||
components/soc/esp32s3/include/soc/pcnt_caps.h
|
||||
components/soc/esp32s3/include/soc/pcnt_reg.h
|
||||
components/soc/esp32s3/include/soc/pcnt_struct.h
|
||||
components/soc/esp32s3/include/soc/peri_backup_reg.h
|
||||
components/soc/esp32s3/include/soc/peri_backup_struct.h
|
||||
components/soc/esp32s3/include/soc/reset_reasons.h
|
||||
@ -1663,7 +1648,6 @@ components/soc/esp32s3/include/soc/world_controller_struct.h
|
||||
components/soc/esp32s3/interrupts.c
|
||||
components/soc/esp32s3/ledc_periph.c
|
||||
components/soc/esp32s3/mcpwm_periph.c
|
||||
components/soc/esp32s3/pcnt_periph.c
|
||||
components/soc/esp32s3/rmt_periph.c
|
||||
components/soc/esp32s3/rtc_io_periph.c
|
||||
components/soc/esp32s3/sdio_slave_periph.c
|
||||
@ -1683,7 +1667,6 @@ components/soc/include/soc/i2c_periph.h
|
||||
components/soc/include/soc/interrupts.h
|
||||
components/soc/include/soc/ledc_periph.h
|
||||
components/soc/include/soc/mcpwm_periph.h
|
||||
components/soc/include/soc/pcnt_periph.h
|
||||
components/soc/include/soc/rmt_periph.h
|
||||
components/soc/include/soc/rtc_cntl_periph.h
|
||||
components/soc/include/soc/rtc_periph.h
|
||||
@ -2223,10 +2206,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro
|
||||
examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c
|
||||
examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c
|
||||
examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c
|
||||
examples/peripherals/pcnt/pulse_count_event/main/pcnt_event_example_main.c
|
||||
examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h
|
||||
examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c
|
||||
examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c
|
||||
examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h
|
||||
examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_nec.c
|
||||
examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c
|
||||
|
@ -4,6 +4,11 @@ set(srcs "ccomp_timer.c"
|
||||
"test_utils.c")
|
||||
|
||||
if(CONFIG_IDF_TARGET_ESP32)
|
||||
# ESP32's timer group doesn't have XTAL clock source,
|
||||
# so we can't implement a timekeeping that can work during DFS
|
||||
# but we can work around that by combining RMT and PCNT
|
||||
# where PCNT can count the pulses generated by RMT, and RMT is clocked from REF_TICK
|
||||
# REF_TICK won't be affected by DFS
|
||||
list(APPEND srcs "ref_clock_impl_rmt_pcnt.c")
|
||||
else()
|
||||
list(APPEND srcs "ref_clock_impl_timergroup.c")
|
||||
|
@ -20,41 +20,65 @@
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "unity.h"
|
||||
#include "test_utils.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/rmt_types.h"
|
||||
#include "hal/rmt_hal.h"
|
||||
#include "hal/rmt_ll.h"
|
||||
#include "hal/pcnt_hal.h"
|
||||
#include "hal/pcnt_ll.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
#define REF_CLOCK_RMT_CHANNEL 0 // RMT channel 0
|
||||
#define REF_CLOCK_PCNT_UNIT 0 // PCNT unit 0
|
||||
#define REF_CLOCK_PCNT_CHANNEL 0// PCNT channel 0
|
||||
#define REF_CLOCK_GPIO 21 // GPIO used to combine RMT out signal with PCNT input signal
|
||||
|
||||
#define REF_CLOCK_RMT_CHANNEL 0 // RMT channel 0
|
||||
#define REF_CLOCK_GPIO 21 // GPIO used to combine RMT out signal with PCNT input signal
|
||||
#define REF_CLOCK_PRESCALER_MS 30 // PCNT high threshold interrupt fired every 30ms
|
||||
|
||||
static void IRAM_ATTR pcnt_isr(void *arg);
|
||||
|
||||
static intr_handle_t s_intr_handle;
|
||||
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static volatile uint32_t s_milliseconds;
|
||||
|
||||
static rmt_hal_context_t s_rmt_hal;
|
||||
static pcnt_hal_context_t s_pcnt_hal;
|
||||
static pcnt_unit_handle_t s_pcnt_unit;
|
||||
static pcnt_channel_handle_t s_pcnt_chan;
|
||||
static volatile uint32_t s_milliseconds;
|
||||
|
||||
static bool on_reach_watch_point(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
s_milliseconds += REF_CLOCK_PRESCALER_MS;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ref_clock_init(void)
|
||||
{
|
||||
assert(s_intr_handle == NULL && "ref clock already initialized");
|
||||
// Initialize PCNT
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = REF_CLOCK_PRESCALER_MS * 1000,
|
||||
.low_limit = -100, // any minus value is OK, in this case, we don't count down
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &s_pcnt_unit));
|
||||
pcnt_chan_config_t chan_config = {
|
||||
.edge_gpio_num = REF_CLOCK_GPIO,
|
||||
.level_gpio_num = -1,
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_new_channel(s_pcnt_unit, &chan_config, &s_pcnt_chan));
|
||||
// increase count on both edges
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(s_pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
// don't care level change
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(s_pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
|
||||
// add watch point
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(s_pcnt_unit, REF_CLOCK_PRESCALER_MS * 1000));
|
||||
// register watch event
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = on_reach_watch_point,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_register_event_callbacks(s_pcnt_unit, &cbs, NULL));
|
||||
// start pcnt
|
||||
TEST_ESP_OK(pcnt_unit_start(s_pcnt_unit));
|
||||
|
||||
// Route RMT output to GPIO matrix
|
||||
esp_rom_gpio_connect_out_signal(REF_CLOCK_GPIO, RMT_SIG_OUT0_IDX, false, false);
|
||||
@ -71,18 +95,11 @@ void ref_clock_init(void)
|
||||
};
|
||||
|
||||
rmt_ll_enable_drive_clock(s_rmt_hal.regs, true);
|
||||
#if SOC_RMT_SUPPORT_XTAL
|
||||
rmt_ll_set_group_clock_src(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, RMT_BASECLK_XTAL, 39, 0, 0); // XTAL(40MHz), rmt_sclk => 1MHz (40/(1+39))
|
||||
#elif SOC_RMT_SUPPORT_REF_TICK
|
||||
rmt_ll_set_group_clock_src(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, RMT_BASECLK_REF, 0, 0, 0); // select REF_TICK (1MHz)
|
||||
#endif
|
||||
rmt_hal_tx_set_channel_clock(&s_rmt_hal, REF_CLOCK_RMT_CHANNEL, 1000000, 1000000); // counter clock: 1MHz
|
||||
rmt_ll_tx_enable_idle(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, true); // enable idle output
|
||||
rmt_ll_tx_set_idle_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1); // idle level: 1
|
||||
rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, true);
|
||||
#if !CONFIG_IDF_TARGET_ESP32
|
||||
rmt_ll_tx_set_carrier_always_on(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, true);
|
||||
#endif
|
||||
rmt_hal_set_carrier_clock(&s_rmt_hal, REF_CLOCK_RMT_CHANNEL, 1000000, 500000, 0.5); // set carrier to 500KHz
|
||||
rmt_ll_tx_set_carrier_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1);
|
||||
rmt_ll_enable_mem_access(s_rmt_hal.regs, true);
|
||||
@ -92,77 +109,25 @@ void ref_clock_init(void)
|
||||
rmt_ll_tx_enable_loop(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false);
|
||||
rmt_ll_tx_start(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL);
|
||||
|
||||
// Route signal to PCNT
|
||||
esp_rom_gpio_connect_in_signal(REF_CLOCK_GPIO, PCNT_SIG_CH0_IN0_IDX, false);
|
||||
if (REF_CLOCK_GPIO != 20) {
|
||||
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[REF_CLOCK_GPIO]);
|
||||
} else {
|
||||
PIN_INPUT_ENABLE(PERIPHS_IO_MUX_GPIO20_U);
|
||||
}
|
||||
|
||||
// Initialize PCNT
|
||||
periph_module_enable(PERIPH_PCNT_MODULE);
|
||||
pcnt_hal_init(&s_pcnt_hal, REF_CLOCK_PCNT_UNIT);
|
||||
|
||||
pcnt_ll_set_edge_action(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT, REF_CLOCK_PCNT_CHANNEL,
|
||||
PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE);
|
||||
pcnt_ll_set_level_action(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT, REF_CLOCK_PCNT_CHANNEL,
|
||||
PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP);
|
||||
pcnt_ll_disable_all_events(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
pcnt_ll_set_high_limit_value(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT, REF_CLOCK_PRESCALER_MS * 1000);
|
||||
pcnt_ll_enable_high_limit_event(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT, true);
|
||||
|
||||
// Enable PCNT and wait for it to start counting
|
||||
pcnt_ll_start_count(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
pcnt_ll_clear_count(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
|
||||
esp_rom_delay_us(10000);
|
||||
|
||||
// Enable interrupt
|
||||
s_milliseconds = 0;
|
||||
ESP_ERROR_CHECK(esp_intr_alloc(ETS_PCNT_INTR_SOURCE, ESP_INTR_FLAG_IRAM, pcnt_isr, NULL, &s_intr_handle));
|
||||
pcnt_ll_clear_intr_status(s_pcnt_hal.dev, BIT(REF_CLOCK_PCNT_UNIT));
|
||||
pcnt_ll_enable_intr(s_pcnt_hal.dev, 1 << REF_CLOCK_PCNT_UNIT, true);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR pcnt_isr(void *arg)
|
||||
void ref_clock_deinit(void)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&s_lock);
|
||||
pcnt_ll_clear_intr_status(s_pcnt_hal.dev, BIT(REF_CLOCK_PCNT_UNIT));
|
||||
s_milliseconds += REF_CLOCK_PRESCALER_MS;
|
||||
portEXIT_CRITICAL_ISR(&s_lock);
|
||||
}
|
||||
// Deinitialize PCNT
|
||||
TEST_ESP_OK(pcnt_unit_stop(s_pcnt_unit));
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_point(s_pcnt_unit, REF_CLOCK_PRESCALER_MS * 1000));
|
||||
TEST_ESP_OK(pcnt_del_channel(s_pcnt_chan));
|
||||
TEST_ESP_OK(pcnt_del_unit(s_pcnt_unit));
|
||||
|
||||
void ref_clock_deinit()
|
||||
{
|
||||
assert(s_intr_handle && "ref clock deinit called without init");
|
||||
|
||||
// Disable interrupt
|
||||
pcnt_ll_enable_intr(s_pcnt_hal.dev, 1 << REF_CLOCK_PCNT_UNIT, false);
|
||||
esp_intr_free(s_intr_handle);
|
||||
s_intr_handle = NULL;
|
||||
|
||||
// Disable RMT
|
||||
// Deinitialize RMT
|
||||
rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false);
|
||||
periph_module_disable(PERIPH_RMT_MODULE);
|
||||
|
||||
// Disable PCNT
|
||||
pcnt_ll_stop_count(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
periph_module_disable(PERIPH_PCNT_MODULE);
|
||||
}
|
||||
|
||||
uint64_t ref_clock_get()
|
||||
uint64_t ref_clock_get(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_lock);
|
||||
int microseconds = 0;
|
||||
microseconds = pcnt_ll_get_count(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
uint32_t milliseconds = s_milliseconds;
|
||||
uint32_t intr_status = pcnt_ll_get_intr_status(s_pcnt_hal.dev);
|
||||
if (intr_status & BIT(REF_CLOCK_PCNT_UNIT)) {
|
||||
// refresh counter value, in case the overflow has happened after reading cnt_val
|
||||
microseconds = pcnt_ll_get_count(s_pcnt_hal.dev, REF_CLOCK_PCNT_UNIT);
|
||||
milliseconds += REF_CLOCK_PRESCALER_MS;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_lock);
|
||||
return 1000 * (uint64_t)milliseconds + (uint64_t)microseconds;
|
||||
TEST_ESP_OK(pcnt_unit_get_count(s_pcnt_unit, µseconds));
|
||||
return 1000 * (uint64_t)s_milliseconds + (uint64_t)microseconds;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user