feat(gptimer): add wiegand interface example

Closes https://github.com/espressif/esp-idf/issues/13892
This commit is contained in:
Chen Jichang 2024-07-03 16:24:05 +08:00
parent 9bae186123
commit 5dea9413a8
15 changed files with 345 additions and 214 deletions

View File

@ -396,11 +396,11 @@ examples/peripherals/timer_group/gptimer_capture_hc_sr04:
depends_components:
- esp_driver_gptimer
examples/peripherals/timer_group/legacy_driver:
examples/peripherals/timer_group/wiegand_interface:
disable:
- if: SOC_GPTIMER_SUPPORTED != 1
- if: SOC_TIMER_GROUP_TOTAL_TIMERS < 2
depends_components:
- driver # legacy driver is still located in the "driver" component
- esp_driver_gptimer
examples/peripherals/touch_sensor:
disable:

View File

@ -1,50 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# Example: General Purpose Timer
This example uses the **legacy timer group driver** to generate timer interrupts with and without auto-reload. But we highly recommend you to use the new [GPTimer Driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gptimer.html) in your new projects.
## How to Use Example
### Hardware Required
* A development board with ESP SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [ESP-IDF Getting Started Guide](https://idf.espressif.com/) for all the steps to configure and use the ESP-IDF to build projects.
## Example Output
```text
I (0) cpu_start: Starting scheduler on APP CPU.
I (325) example: Init timer with auto-reload
I (835) example: Timer auto reloaded, count value in ISR: 3
I (1335) example: Timer auto reloaded, count value in ISR: 3
I (1835) example: Timer auto reloaded, count value in ISR: 3
I (2335) example: Timer auto reloaded, count value in ISR: 3
I (2335) example: Init timer without auto-reload
I (2835) example: Timer alarmed at 500003
I (3335) example: Timer alarmed at 1000003
I (3835) example: Timer alarmed at 1500003
I (4335) example: Timer alarmed at 2000003
```
## Functionality Overview
* Configure one timer with auto-reload enabled, alarm period set to 0.5s
* On reaching the interval value the timer will generate an alarm
* The timer will reload with initial count value on alarm, by hardware
* Reconfigure the timer with auto-reload disabled, initial alarm value set to 0.5s
* The timer keeps incrementing and in the alarm callback, the software reconfigures its alarm value by increasing 0.5s
* The main task will print the count value that captured in the alarm callback
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -1,2 +0,0 @@
idf_component_register(SRCS "timer_group_example_main.c"
INCLUDE_DIRS ".")

View File

@ -1,130 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "soc/soc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/timer.h"
#include "esp_clk_tree.h"
#include "esp_log.h"
#define TIMER_RESOLUTION_HZ 1000000 // 1MHz resolution
#define TIMER_ALARM_PERIOD_S 0.5 // Alarm period 0.5s
static const char *TAG = "example";
/**
* @brief A sample structure to pass events from the timer ISR to task
*/
typedef struct {
uint64_t timer_count_value;
} example_timer_event_t;
/**
* @brief Timer user data, will be pass to timer alarm callback
*/
typedef struct {
QueueHandle_t user_queue;
int timer_group;
int timer_idx;
int alarm_value;
bool auto_reload;
} example_timer_user_data_t;
static bool IRAM_ATTR timer_group_isr_callback(void *args)
{
BaseType_t high_task_awoken = pdFALSE;
example_timer_user_data_t *user_data = (example_timer_user_data_t *) args;
// fetch current count value
uint64_t timer_count_value = timer_group_get_counter_value_in_isr(user_data->timer_group, user_data->timer_idx);
example_timer_event_t evt = {
.timer_count_value = timer_count_value,
};
// set new alarm value if necessary
if (!user_data->auto_reload) {
user_data->alarm_value += TIMER_ALARM_PERIOD_S * TIMER_RESOLUTION_HZ;
timer_group_set_alarm_value_in_isr(user_data->timer_group, user_data->timer_idx, user_data->alarm_value);
}
// Send the event data back to the main program task
xQueueSendFromISR(user_data->user_queue, &evt, &high_task_awoken);
return high_task_awoken == pdTRUE; // return whether a task switch is needed
}
static void example_tg_timer_init(example_timer_user_data_t *user_data)
{
int group = user_data->timer_group;
int timer = user_data->timer_idx;
uint32_t clk_src_hz = 0;
ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)TIMER_SRC_CLK_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_src_hz));
timer_config_t config = {
.clk_src = TIMER_SRC_CLK_DEFAULT,
.divider = clk_src_hz / TIMER_RESOLUTION_HZ,
.counter_dir = TIMER_COUNT_UP,
.counter_en = TIMER_PAUSE,
.alarm_en = TIMER_ALARM_EN,
.auto_reload = user_data->auto_reload,
};
ESP_ERROR_CHECK(timer_init(group, timer, &config));
// For the timer counter to a initial value
ESP_ERROR_CHECK(timer_set_counter_value(group, timer, 0));
// Set alarm value and enable alarm interrupt
ESP_ERROR_CHECK(timer_set_alarm_value(group, timer, user_data->alarm_value));
ESP_ERROR_CHECK(timer_enable_intr(group, timer));
// Hook interrupt callback
ESP_ERROR_CHECK(timer_isr_callback_add(group, timer, timer_group_isr_callback, user_data, 0));
// Start timer
ESP_ERROR_CHECK(timer_start(group, timer));
}
static void example_tg_timer_deinit(int group, int timer)
{
ESP_ERROR_CHECK(timer_isr_callback_remove(group, timer));
ESP_ERROR_CHECK(timer_deinit(group, timer));
}
void app_main(void)
{
example_timer_user_data_t *user_data = calloc(1, sizeof(example_timer_user_data_t));
assert(user_data);
user_data->user_queue = xQueueCreate(10, sizeof(example_timer_event_t));
assert(user_data->user_queue);
user_data->timer_group = 0;
user_data->timer_idx = 0;
user_data->alarm_value = TIMER_ALARM_PERIOD_S * TIMER_RESOLUTION_HZ;
ESP_LOGI(TAG, "Init timer with auto-reload");
user_data->auto_reload = true;
example_tg_timer_init(user_data);
example_timer_event_t evt;
uint32_t test_count = 4;
while (test_count--) {
xQueueReceive(user_data->user_queue, &evt, portMAX_DELAY);
ESP_LOGI(TAG, "Timer auto reloaded, count value in ISR: %llu", evt.timer_count_value);
}
example_tg_timer_deinit(user_data->timer_group, user_data->timer_idx);
ESP_LOGI(TAG, "Init timer without auto-reload");
user_data->auto_reload = false;
example_tg_timer_init(user_data);
test_count = 4;
while (test_count--) {
xQueueReceive(user_data->user_queue, &evt, portMAX_DELAY);
ESP_LOGI(TAG, "Timer alarmed at %llu", evt.timer_count_value);
}
example_tg_timer_deinit(user_data->timer_group, user_data->timer_idx);
vQueueDelete(user_data->user_queue);
free(user_data);
}

View File

@ -1,21 +0,0 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.supported_targets
@pytest.mark.generic
def test_timer_group_example(dut: Dut) -> None:
dut.expect(r'Init timer with auto-reload', timeout=5)
res = dut.expect(r'Timer auto reloaded, count value in ISR: (\d+)', timeout=5)
reloaded_count = res.group(1).decode('utf8')
assert 0 <= int(reloaded_count) < 10
alarm_increase_step = 500000
dut.expect(r'Init timer without auto-reload')
for i in range(1, 5):
res = dut.expect(r'Timer alarmed at (\d+)', timeout=3)
alarm_count = res.group(1).decode('utf8')
assert (i * alarm_increase_step - 10) < int(alarm_count) < (i * alarm_increase_step + 10)

View File

@ -1,7 +0,0 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN=y
# Disable nano printf, because we need to print the timer count in %llu format
CONFIG_NEWLIB_NANO_FORMAT=n

View File

@ -2,5 +2,7 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(timer_group)
project(wiegand_interface)

View File

@ -0,0 +1,55 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
# Wiegand Interface Example
This example uses the **gptimer and gpio driver** to simulate Wiegand Interface. See the [Wiegand Interface](https://en.wikipedia.org/wiki/Wiegand_interface)
In this example, we use two timers (one in one-shot mode and another in periodic mode) to trigger the interrupt and change the output state of the gpio in the interrupt.
### Note on timing requirements
Due to the tight timing requirements of wiegand protocol, the gpio control function, timer control function and timer isr handle should be placed in IRAM. This circumvents the timing variations caused by cache misses/invalidation when executing a function placed in flash.
Configure sdkconfig as follows:
```
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
```
## How to Use Example
### Hardware Required
* A development board with ESP SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [ESP-IDF Getting Started Guide](https://idf.espressif.com/) for all the steps to configure and use the ESP-IDF to build projects.
## Example Output
```text
I (0) cpu_start: Starting scheduler on APP CPU.
I (319) example: Configure wiegand interface
I (319) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (329) gpio: GPIO[15]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (339) example: send wiegand data
I (339) main_task: Returned from app_main()
```
The wiegand interface io will transfer data after the initialization.
The wiegand data are as follow:
<img align="middle" src="img/image.png">
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "wiegand_example_main.c" "wiegand_io.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_gpio esp_driver_gptimer)

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "wiegand_io.h"
#define DATA_PIN0_GPIO GPIO_NUM_0
#define DATA_PIN1_GPIO GPIO_NUM_2
static const char *TAG = "example";
uint64_t wiegand_data = 0x5a5a5a5a;
void app_main(void)
{
wiegand_io_handle_t wiegand_io_handle = NULL;
wiegand_io_config_t wiegand_io_config = {
.data_pin0 = DATA_PIN0_GPIO,
.data_pin1 = DATA_PIN1_GPIO,
.pulse_width_us = 50,
.interval_width_us = 1000,
};
ESP_LOGI(TAG, "Configure wiegand interface");
ESP_ERROR_CHECK(wiegand_new_io(&wiegand_io_config, &wiegand_io_handle));
ESP_LOGI(TAG, "send wiegand data");
ESP_ERROR_CHECK(wiegand_io_send(wiegand_io_handle, &wiegand_data, 26));
}

View File

@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "esp_check.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "wiegand_io.h"
static const char *TAG = "wiegand_io";
typedef struct wiegand_interface_t {
SemaphoreHandle_t sem;
gptimer_handle_t interval_timer;
gptimer_handle_t pulse_timer;
gpio_num_t data_pin0;
gpio_num_t data_pin1;
uint8_t pulse_width_us;
uint16_t interval_width_us;
uint64_t bit_mask;
uint64_t *current_data;
} wiegand_interface_t;
static bool IRAM_ATTR interval_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
wiegand_io_handle_t wiegand_io = (wiegand_io_handle_t)user_data;
if (wiegand_io->bit_mask == 1) {
gptimer_stop(timer);
}
gptimer_set_raw_count(wiegand_io->pulse_timer, 0);
gptimer_start(wiegand_io->pulse_timer);
if (*wiegand_io->current_data & wiegand_io->bit_mask) {
gpio_set_level(wiegand_io->data_pin1, 0);
} else {
gpio_set_level(wiegand_io->data_pin0, 0);
}
return false;
}
static bool IRAM_ATTR pulse_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
BaseType_t task_woken = pdFALSE;
wiegand_io_handle_t wiegand_io = (wiegand_io_handle_t)user_data;
gpio_set_level(wiegand_io->data_pin0, 1);
gpio_set_level(wiegand_io->data_pin1, 1);
gptimer_stop(timer);
if (!(wiegand_io->bit_mask >>= 1)) {
xSemaphoreGiveFromISR(wiegand_io->sem, &task_woken);
}
return task_woken == pdTRUE;
}
static esp_err_t set_alarm_action(gptimer_handle_t timer, uint64_t count, bool reload)
{
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = count,
.flags.auto_reload_on_alarm = reload,
};
ESP_RETURN_ON_ERROR(gptimer_set_alarm_action(timer, &alarm_config), TAG, "set alarm action failed!");
return ESP_OK;
}
static esp_err_t setup_timer(gptimer_handle_t *timer, gptimer_alarm_cb_t alarm_cb, wiegand_io_handle_t wiegand_io, uint64_t count, bool reload)
{
esp_err_t ret = ESP_OK;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000, // 1 tick = 1us
};
ESP_RETURN_ON_ERROR(gptimer_new_timer(&timer_config, timer), TAG, "add new timer failed!");
gptimer_event_callbacks_t cbs = {
.on_alarm = alarm_cb,
};
ESP_GOTO_ON_ERROR(gptimer_register_event_callbacks(*timer, &cbs, wiegand_io), err, TAG, "register callbacks failed!");
ESP_GOTO_ON_ERROR(set_alarm_action(*timer, count, reload), err, TAG, "set alarm action failed!");
ESP_GOTO_ON_ERROR(gptimer_enable(*timer), err, TAG, "enable timer failed!");
return ESP_OK;
err:
if (*timer) {
gptimer_del_timer(*timer);
}
return ret;
}
esp_err_t wiegand_new_io(const wiegand_io_config_t *config, wiegand_io_handle_t *ret_handle)
{
esp_err_t ret = ESP_OK;
wiegand_interface_t *wiegand_io = NULL;
ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
wiegand_io = heap_caps_calloc(1, sizeof(wiegand_interface_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
ESP_RETURN_ON_FALSE(wiegand_io, ESP_ERR_NO_MEM, TAG, "no mem for wiegand_io");
wiegand_io->sem = xSemaphoreCreateBinaryWithCaps(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
wiegand_io->data_pin0 = config->data_pin0;
wiegand_io->data_pin1 = config->data_pin1;
wiegand_io->pulse_width_us = config->pulse_width_us;
wiegand_io->interval_width_us = config->interval_width_us;
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << wiegand_io->data_pin0) | (1ULL << wiegand_io->data_pin1),
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "gpio config failed!");
// data pins are high by default
gpio_set_level(wiegand_io->data_pin0, 1);
gpio_set_level(wiegand_io->data_pin1, 1);
// Setup the pulse timer
ESP_GOTO_ON_ERROR(setup_timer(&wiegand_io->pulse_timer, pulse_timer_cb, wiegand_io, wiegand_io->pulse_width_us, false), err, TAG, "setup pulse timer failed!");
// Setup the interval timer
ESP_GOTO_ON_ERROR(setup_timer(&wiegand_io->interval_timer, interval_timer_cb, wiegand_io, wiegand_io->interval_width_us, true), err, TAG, "setup interval timer failed!");
// return handle
*ret_handle = wiegand_io;
xSemaphoreGive(wiegand_io->sem);
return ESP_OK;
err:
if (wiegand_io->pulse_timer) {
gptimer_disable(wiegand_io->pulse_timer);
gptimer_del_timer(wiegand_io->pulse_timer);
}
if (wiegand_io->interval_timer) {
gptimer_disable(wiegand_io->interval_timer);
gptimer_del_timer(wiegand_io->interval_timer);
}
if (wiegand_io) {
free(wiegand_io);
}
return ret;
}
esp_err_t wiegand_io_send(wiegand_io_handle_t wiegand_io, void *data, uint8_t num_of_bits)
{
ESP_RETURN_ON_FALSE(wiegand_io && num_of_bits && data, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(num_of_bits > 0 && num_of_bits <= 64, ESP_ERR_INVALID_ARG, TAG, "bits is out of range [1,64]");
xSemaphoreTake(wiegand_io->sem, portMAX_DELAY);
wiegand_io->current_data = (uint64_t *)data;
wiegand_io->bit_mask = 1ULL << (num_of_bits - 1);
ESP_RETURN_ON_ERROR(gptimer_set_raw_count(wiegand_io->interval_timer, 0), TAG, "set timer raw count failed!");
ESP_RETURN_ON_ERROR(gptimer_start(wiegand_io->interval_timer), TAG, "start timer failed!");
return ESP_OK;
}

View File

@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "driver/gpio.h"
#include "driver/gptimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Wiegand interface configuration
*/
typedef struct {
gpio_num_t data_pin0; /*!< Pin of Data0 */
gpio_num_t data_pin1; /*!< Pin of Data1 */
uint8_t pulse_width_us; /*!< Negative pulse width, in microseconds */
uint16_t interval_width_us; /*!< Wiegand data interval, a.k.a. period, in microseconds */
} wiegand_io_config_t;
/**
* @brief Type of Wiegand interface handle
*/
typedef struct wiegand_interface_t *wiegand_io_handle_t;
/**
* @brief Create a new wiegand interface io, and return the handle
*
* @param[in] config Wiegand interface configuration
* @param[out] ret_handle Returned wiegand interface handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating wiegand interface
* - ESP_OK if creating wiegand interface successfully
*/
esp_err_t wiegand_new_io(const wiegand_io_config_t *config, wiegand_io_handle_t *ret_handle);
/**
* @brief Send data via created wiegand interface io
*
* @param[in] wiegand_io Wiegand interface io handle
* @param[in] data Wiegand data
* @param[in] num_of_bits The bits number of wiegand data
* @return
* - ESP_ERR_INVALID_STATE if previous data has not sent completely
* - ESP_OK if start sending data successfully
*/
esp_err_t wiegand_io_send(wiegand_io_handle_t wiegand_io, void *data, uint8_t num_of_bits);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32c3
@pytest.mark.esp32c5
@pytest.mark.esp32c6
@pytest.mark.esp32h2
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.generic
def test_gptimer_wiegand(dut: Dut) -> None:
dut.expect_exact('example: Configure wiegand interface')
dut.expect_exact('example: send wiegand data')

View File

@ -0,0 +1,3 @@
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y