/*
 * @Author: wzh
 * @Date: 2024-11-18 09:43:50
 * @LastEditors: mypx mypx_coder@163.com
 * @LastEditTime: 2025-09-03 13:44:52
 * @Description: Encoder driver for 2-phase encoder and 3-phase encoder through GPIO interrupts.
 *
 * Copyright (c) 2024 by wzh, All Rights Reserved.
 */
#ifndef __ET_ENCODER_H__
#define __ET_ENCODER_H__
#include "etk_utils.h"
#include <stdint.h>
#include <stdbool.h>

// User global config start

/**
 * Pulses Per Revolution (PPR)
 * 2x or 4x counting:
 * 
 * In many cases, to increase resolution, a frequency multiplication technique (e.g., quadrature encoder) is used.
 * This technique detects the rising and falling edges of A/B phase signals, thereby improving the encoder's resolution.

 * 1x mode: Only one edge of a single channel is counted, resolution remains unchanged.
 * 2x mode: Both rising and falling edges of channel A are counted, doubling the resolution.
 * 4x mode: All edges (rising + falling) of both A and B channels are counted, quadrupling the resolution.

 * If the original resolution is PPR (Pulses Per Revolution), using 4x counting increases the resolution to 4 * PPR.

 * The meaning of multi-turn encoder or "Octave":

 * "Octave" usually refers to the enhanced resolution after multiple stages of frequency multiplication.
 * For example, "2 octaves" may indicate two stages of frequency multiplication, resulting in the resolution being doubled twice.
*/

#ifndef ET_ENCODER_PPR
#ifdef CONFIG_ET_ENCODER_PPR
#define ET_ENCODER_PPR CONFIG_ET_ENCODER_PPR
#else
#define ET_ENCODER_PPR 128
#endif
#endif

/**
 * Quadrature encoder mode 1(TI1):
 * - Configures the timer in x2 mode.
 * - Counting is based on the edges (rising or falling) of the TI1FP1 signal.
 * - The counting direction is determined by the level of the TI2FP2 signal.
 * - Only the TI1 channel is used for counting.
 *
 * Quadrature encoder mode 2(TI2):
 * - Configures the timer in x2 mode.
 * - Counting is based on the edges (rising or falling) of the TI2FP2 signal.
 * - The counting direction is determined by the level of the TI1FP1 signal.
 * - Only the TI2 channel is used for counting.
 *
 * Quadrature encoder mode 3(TI12):
 * - Combines encoder mode 1 and mode 2 for x4 mode.
 * - Counting is based on the edges (rising or falling) of both TI1FP1 and TI2FP2 signals.
 * - The counting direction is determined by the relative states of TI1 and TI2 signals.
 * - Both TI1 and TI2 channels are used for counting.
*/
#ifndef ET_ENCODER_MODE
#ifdef CONFIG_ET_ENCODER_MODE
#define ET_ENCODER_MODE CONFIG_ET_ENCODER_MODE
#else
#define ET_ENCODER_MODE 4
#endif
#endif

#define ET_ENCODER_TOTAL_PPR (ET_ENCODER_PPR * ET_ENCODER_MODE)

#ifndef ET_ENCODER_MAX_COUNT
#ifdef CONFIG_ET_ENCODER_MAX_COUNT
#define ET_ENCODER_MAX_COUNT CONFIG_ET_ENCODER_MAX_COUNT
#else
#define ET_ENCODER_MAX_COUNT (ETK_UINT16_MAX)
#endif
#endif

typedef enum
{
    ET_ENCODER_DIR_UNKNOWN = -1,
    ET_ENCODER_DIR_REVERSE = 0,
    ET_ENCODER_DIR_FORWARD = 1,
} ET_EncoderDir;

/* Encoder data structure */
typedef struct
{
    int32_t       position;    // Current position count
    ET_EncoderDir direction;
    bool          initialized; // Initialization flag
    uint8_t       last_state;
} et_encoder_t;

typedef struct
{
    int32_t       position;    // Current position count
    ET_EncoderDir direction;
    uint8_t       last_state;  // Last encoder state (A, B, C combined as 3 bits)
    bool          initialized; // Initialization flag
} et_encoder_3phase_t;

#ifdef __cplusplus
extern "C"
{
#endif

/**
 * @brief Initialize the encoder structure.
 * @param encoder Pointer to the encoder structure to be initialized
 */
void et_encoder_init(et_encoder_t *encoder);

/**
 * @brief Encoder GPIO interrupt service routine
 * @param encoder Pointer to the encoder structure
 * @param channel_a State of Channel A (true for high, false for low)
 */
void et_encoder_isr(et_encoder_t *encoder, bool channel_a, bool channel_b);

ET_EncoderDir et_encoder_get_dir(et_encoder_t *encoder);

/**
 * @brief Get the current encoder position
 * @param encoder Pointer to the encoder structure
 */
int32_t et_encoder_get_position(et_encoder_t *encoder);

/**
 * @brief Reset the encoder position to zero.
 * @param encoder Pointer to the encoder structure
 */
void et_encoder_reset(et_encoder_t *encoder);

/**
 * @brief Set the encoder position to a specific value
 * @param encoder Pointer to the encoder structure
 * @param position The desired encoder position
 */
void et_encoder_set_position(et_encoder_t *encoder, int32_t position);

/**
 * @brief Initialize the 3-phase encoder structure
 * @param encoder Pointer to the encoder structure to be initialized
 */
void et_encoder_3phase_init(et_encoder_3phase_t *encoder);

/**
 * @brief Encoder GPIO interrupt service routine for 3-phase signals.
 * @param encoder Pointer to the encoder structure
 * @param a State of Channel A (true for high, false for low)
 * @param b State of Channel B (true for high, false for low)
 * @param c State of Channel C (true for high, false for low)
 */
void et_encoder_3phase_isr(et_encoder_3phase_t *encoder, bool a, bool b, bool c);

ET_EncoderDir et_encoder_3phase_get_dir(et_encoder_3phase_t *encoder);
/**
 * @brief Get the current encoder position.
 * @param encoder Pointer to the encoder structure
 */
int32_t et_encoder_3phase_get_position(et_encoder_3phase_t *encoder);

/**
 * @brief Reset the encoder position to zero.
 * @param encoder Pointer to the encoder structure
 */
void et_encoder_3phase_reset(et_encoder_3phase_t *encoder);

/**
 * @brief Set the encoder position to a specific value
 * @param encoder Pointer to the encoder structure
 * @param position The desired encoder position
 */
void et_encoder_3phase_set_position(et_encoder_3phase_t *encoder, int32_t position);
#ifdef __cplusplus
}
#endif

#endif // @page end
