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
@@ -0,0 +1,8 @@
# 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.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(wiegand_interface)
@@ -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

@@ -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)
@@ -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));
}
@@ -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;
}
@@ -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
@@ -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')
@@ -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