feat: initial

This commit is contained in:
2026-01-02 12:06:32 +03:00
parent cc6a5b4ac8
commit a6bb36a50f
8 changed files with 282 additions and 3 deletions

View File

@@ -1 +1 @@
idf_component_register(SRCS "main.c" INCLUDE_DIRS "include")
idf_component_register(SRCS "zh_inclinometer.c" INCLUDE_DIRS "include" REQUIRES driver)

View File

@@ -1,3 +1,56 @@
# esp_component_template
# ESP32 ESP-IDF component for inclinometer (via rotary encoder)
esp_component_template
## Tested on
1. [ESP32 ESP-IDF v5.5.1](https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32/index.html)
## SAST Tools
[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
## Attention
1. For correct operation, please enable the following settings in the menuconfig:
```text
GPIO_CTRL_FUNC_IN_IRAM
```
## Using
In an existing project, run the following command to install the components:
```text
cd ../your_project/components
git clone http://git.zh.com.ru/esp_components/zh_inclinometer
```
In the application, add the component:
```c
#include "zh_inclinometer.h"
```
## Examples
```c
#include "zh_inclinometer.h"
double inclinometer_position = 0;
void app_main(void)
{
esp_log_level_set("zh_inclinometer", ESP_LOG_ERROR);
zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT();
config.a_gpio_number = GPIO_NUM_26;
config.b_gpio_number = GPIO_NUM_27;
config.encoder_pulses = 3600;
zh_inclinometer_init(&config);
for (;;)
{
zh_inclinometer_get(&inclinometer_position);
printf("Inclinometer position is %0.2f degrees.\n", inclinometer_position);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
```

View File

View File

76
include/zh_inclinometer.h Normal file
View File

@@ -0,0 +1,76 @@
/**
* @file zh_inclinometer.h
*/
#pragma once
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
/**
* @brief Inclinometer initial default values.
*/
#define ZH_INCLINOMETER_INIT_CONFIG_DEFAULT() \
{ \
.a_gpio_number = GPIO_NUM_MAX, \
.b_gpio_number = GPIO_NUM_MAX, \
.encoder_pulses = 0}
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Structure for initial initialization of inclinometer.
*/
typedef struct
{
uint8_t a_gpio_number; /*!< Encoder A GPIO number. */
uint8_t b_gpio_number; /*!< Encoder B GPIO number. */
uint16_t encoder_pulses; /*!< Number of pulses per one rotation. */
} zh_inclinometer_init_config_t;
/**
* @brief Initialize inclinometer.
*
* @param[in] config Pointer to inclinometer initialized configuration structure. Can point to a temporary variable.
*
* @note Before initialize the inclinometer recommend initialize zh_inclinometer_init_config_t structure with default values.
*
* @code zh_inclinometer_init_config_t config = ZH_INCLINOMETER_INIT_CONFIG_DEFAULT() @endcode
*
* @return ESP_OK if success or an error code otherwise.
*/
esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config);
/**
* @brief Deinitialize inclinometer.
*
* @return ESP_OK if success or an error code otherwise.
*/
esp_err_t zh_inclinometer_deinit(void);
/**
* @brief Get inclinometer position.
*
* @param[out] position inclinometer position.
*
* @return ESP_OK if success or an error code otherwise.
*/
esp_err_t zh_inclinometer_get(double *position);
/**
* @brief Reset inclinometer position.
*
* @note The inclinometer will be set to 0 position.
*
* @return ESP_OK if success or an error code otherwise.
*/
esp_err_t zh_inclinometer_reset(void);
#ifdef __cplusplus
}
#endif

0
main.c
View File

View File

@@ -0,0 +1 @@
1.0.0

149
zh_inclinometer.c Normal file
View File

@@ -0,0 +1,149 @@
#include "zh_inclinometer.h"
#define TAG "zh_inclinometer"
#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \
if (!(cond)) \
{ \
ZH_LOGE(msg, err, ##__VA_ARGS__); \
cleanup; \
return err; \
}
#define ZH_ENCODER_DIRECTION_CW 0x10
#define ZH_ENCODER_DIRECTION_CCW 0x20
static const uint8_t _encoder_matrix[7][4] = {
{0x03, 0x02, 0x01, 0x00},
{0x23, 0x00, 0x01, 0x00},
{0x13, 0x02, 0x00, 0x00},
{0x03, 0x05, 0x04, 0x00},
{0x03, 0x03, 0x04, 0x00},
{0x03, 0x05, 0x03, 0x00},
};
static portMUX_TYPE _spinlock = portMUX_INITIALIZER_UNLOCKED;
static double _encoder_step;
volatile static double _encoder_position;
volatile static uint8_t _encoder_state;
static uint8_t _a_gpio_number;
static uint8_t _b_gpio_number;
static bool _is_initialized = false;
static bool _is_prev_gpio_isr_handler = false;
static esp_err_t _zh_inclinometer_validate_config(const zh_inclinometer_init_config_t *config);
static esp_err_t _zh_inclinometer_gpio_init(const zh_inclinometer_init_config_t *config);
static void _zh_inclinometer_isr_handler(void *arg);
esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config)
{
ZH_LOGI("Inclinometer initialization started.");
esp_err_t err = _zh_inclinometer_validate_config(config);
ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Inclinometer initialization failed. Initial configuration check failed.");
err = _zh_inclinometer_gpio_init(config);
ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Inclinometer initialization failed. GPIO initialization failed.");
_encoder_position = 0;
_encoder_step = 360.0 / (double)config->encoder_pulses;
printf("_encoder_step is %0.2f degrees.\n", _encoder_step);
_a_gpio_number = config->a_gpio_number;
_b_gpio_number = config->b_gpio_number;
_is_initialized = true;
ZH_LOGI("Inclinometer initialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_inclinometer_deinit(void)
{
ZH_LOGI("Inclinometer deinitialization started.");
ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer deinitialization failed. Inclinometer not initialized.");
gpio_isr_handler_remove((gpio_num_t)_a_gpio_number);
gpio_isr_handler_remove((gpio_num_t)_b_gpio_number);
gpio_reset_pin((gpio_num_t)_a_gpio_number);
gpio_reset_pin((gpio_num_t)_b_gpio_number);
if (_is_prev_gpio_isr_handler == false)
{
gpio_uninstall_isr_service();
}
_is_initialized = false;
ZH_LOGI("Inclinometer deinitialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_inclinometer_get(double *position)
{
ZH_LOGI("Inclinometer get position started.");
ZH_ERROR_CHECK(position != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer get position failed. Invalid argument.");
ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer get position failed. Inclinometer not initialized.");
*position = _encoder_position;
ZH_LOGI("Inclinometer get position completed successfully.");
return ESP_OK;
}
esp_err_t zh_inclinometer_reset(void)
{
ZH_LOGI("Inclinometer reset started.");
ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer reset failed. Inclinometer not initialized.");
taskENTER_CRITICAL(&_spinlock);
_encoder_position = 0;
taskEXIT_CRITICAL(&_spinlock);
ZH_LOGI("Inclinometer reset completed successfully.");
return ESP_OK;
}
static esp_err_t _zh_inclinometer_validate_config(const zh_inclinometer_init_config_t *config)
{
ZH_ERROR_CHECK(config != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid configuration.");
ZH_ERROR_CHECK(config->encoder_pulses > 0, ESP_ERR_INVALID_ARG, NULL, "Invalid encoder pulses.");
return ESP_OK;
}
static esp_err_t _zh_inclinometer_gpio_init(const zh_inclinometer_init_config_t *config) // -V2008
{
ZH_ERROR_CHECK(config->a_gpio_number < GPIO_NUM_MAX && config->b_gpio_number < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.")
ZH_ERROR_CHECK(config->a_gpio_number != config->b_gpio_number, ESP_ERR_INVALID_ARG, NULL, "Encoder A and B GPIO is same.")
gpio_config_t pin_config = {
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << config->a_gpio_number) | (1ULL << config->b_gpio_number),
.pull_up_en = GPIO_PULLUP_ENABLE,
.intr_type = GPIO_INTR_ANYEDGE};
esp_err_t err = gpio_config(&pin_config);
ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "GPIO initialization failed.");
err = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED);
ZH_ERROR_CHECK(err == ESP_OK || err == ESP_ERR_INVALID_STATE, err, gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Failed install isr service.");
if (err == ESP_ERR_INVALID_STATE)
{
_is_prev_gpio_isr_handler = true;
}
err = gpio_isr_handler_add((gpio_num_t)config->a_gpio_number, _zh_inclinometer_isr_handler, NULL);
ZH_ERROR_CHECK(err == ESP_OK, err, gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Interrupt initialization failed.");
err = gpio_isr_handler_add((gpio_num_t)config->b_gpio_number, _zh_inclinometer_isr_handler, NULL);
if (_is_prev_gpio_isr_handler == true)
{
ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Interrupt initialization failed.");
}
else
{
ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_uninstall_isr_service(); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Interrupt initialization failed.");
}
return ESP_OK;
}
static void IRAM_ATTR _zh_inclinometer_isr_handler(void *arg)
{
_encoder_state = _encoder_matrix[_encoder_state & 0x0F][(gpio_get_level((gpio_num_t)_b_gpio_number) << 1) | gpio_get_level((gpio_num_t)_a_gpio_number)];
switch (_encoder_state & 0x30)
{
case ZH_ENCODER_DIRECTION_CW:
_encoder_position = _encoder_position + _encoder_step;
break;
case ZH_ENCODER_DIRECTION_CCW:
_encoder_position = _encoder_position - _encoder_step;
break;
default:
break;
}
}