/* 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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/periph_ctrl.h"
#include "driver/ledc.h"
#include "driver/gpio.h"
#include "driver/pcnt.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "soc/gpio_sig_map.h"

/**
 * TEST CODE BRIEF
 * Use PCNT module to count rising edges generated by LEDC module.
 * GPIO18 is used as output pin, GPIO4 is used as pulse input pin and GPIO5 is used as control input pin
 *
 * Open 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 pulled up, or connect it to ground.
 * If you connect gpio5 to GND ,you will found the count value decreasing.
 *
 * When counter value reaches thresh1 or thresh0 value, it will trigger interrupt.
 * When counter value reaches l_lim value or h_lim value, counter value will be reset to zero and trigger interrupt.
 */
#define PCNT_TEST_UNIT     PCNT_UNIT_0
#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 */

xQueueHandle pcnt_evt_queue;  /*A queue to handle pulse counter event*/


typedef struct {
    int unit;        /*pulse counter unit*/
    uint32_t status; /*pulse counter internal status*/
} pcnt_evt_t;

static void IRAM_ATTR pcnt_example_intr_handler(void* arg)
{
    uint32_t intr_status = PCNT.int_st.val;
    int i;
    pcnt_evt_t evt;
    portBASE_TYPE HPTaskAwoken = pdFALSE;

    for(i = 0; i < PCNT_UNIT_MAX; i++) {
        if(intr_status & (BIT(i))) {
            evt.unit = i;
            evt.status = PCNT.status_unit[i].val;
            PCNT.int_clr.val = BIT(i);
            /*H LIM EVT*/
            if(PCNT.status_unit[i].h_lim_lat) {
                //do something
            }
            /*L LIM EVT*/
            if(PCNT.status_unit[i].l_lim_lat) {
                //do something
            }
            /*THRES0 EVT*/
            if(PCNT.status_unit[i].thres0_lat) {
                //do something
            }
            /*THRES1 EVT*/
            if(PCNT.status_unit[i].thres1_lat) {
                //do something
            }
            /*ZERO EVT*/
            if(PCNT.status_unit[i].zero_lat) {
                //do something
            }
            xQueueSendFromISR(pcnt_evt_queue, &evt, &HPTaskAwoken);
            if(HPTaskAwoken == pdTRUE) {
                portYIELD_FROM_ISR();
            }
        }
    }
}

static void ledc_init(void)
{
    ledc_channel_config_t ledc_channel;
    /*use LEDC_OUTPUT_IO as output pin*/
    ledc_channel.gpio_num = LEDC_OUTPUT_IO;
    /*LEDC high speed mode */
    ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;
    /*use LEDC channel 1*/
    ledc_channel.channel = LEDC_CHANNEL_1;
    /*Disable LEDC interrupt*/
    ledc_channel.intr_type = LEDC_INTR_DISABLE;
    /*Select LEDC timer 1 */
    ledc_channel.timer_sel = LEDC_TIMER_1;
    /*Set duty 100 */
    ledc_channel.duty = 100;
    ledc_channel_config(&ledc_channel); //ledc config

    ledc_timer_config_t ledc_timer;
    /*LEDC timer high speed mode*/
    ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
    /*10 bit PWM*/
    ledc_timer.bit_num = LEDC_TIMER_10_BIT;
    /*Select timer 1*/
    ledc_timer.timer_num = LEDC_TIMER_1;
    /*Set frequency 1 Hz  */
    ledc_timer.freq_hz = 1;
    ledc_timer_config(&ledc_timer);
}

static void pcnt_example_init(void)
{
    pcnt_config_t pcnt_config = {
        /*Set PCNT_INPUT_SIG_IO as pulse input gpio */
        .pulse_gpio_num = PCNT_INPUT_SIG_IO,
        /*set PCNT_INPUT_CTRL_IO as control gpio */
        .ctrl_gpio_num = PCNT_INPUT_CTRL_IO,
        /*Choose channel 0 */
        .channel = PCNT_CHANNEL_0,
        /*Choose unit 0 */
        .unit = PCNT_TEST_UNIT,
        /*Set counter and control mode*/
        /*Counter increase for positive edge on pulse input GPIO*/
        .pos_mode = PCNT_COUNT_INC,
        /*Counter decrease for negative edge on pulse input GPIO*/
        .neg_mode = PCNT_COUNT_DIS,      //keep the counter value
        /*Counter mode reverse when control input is low level*/
        .lctrl_mode = PCNT_MODE_REVERSE,
        /*Counter mode does not change when control input is high level*/
        .hctrl_mode = PCNT_MODE_KEEP,      //when control signal is high,keep the primary counter mode
        /*Set maximum value for increasing counter*/
        .counter_h_lim = PCNT_H_LIM_VAL,
        /*Set minimum value for decreasing counter*/
        .counter_l_lim = PCNT_L_LIM_VAL,
    };
    /*Initialize PCNT unit */
    pcnt_unit_config(&pcnt_config);

    /*Configure input filter value*/
    pcnt_set_filter_value(PCNT_TEST_UNIT, 100);
    /*Enable input filter*/
    pcnt_filter_enable(PCNT_TEST_UNIT);

    /*Set value for watch point thresh1*/
    pcnt_set_event_value(PCNT_TEST_UNIT, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL);
    /*Enable watch point event of thresh1*/
    pcnt_event_enable(PCNT_TEST_UNIT, PCNT_EVT_THRES_1);
    /*Set value for watch point thresh0*/
    pcnt_set_event_value(PCNT_TEST_UNIT, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL);
    /*Enable watch point event of thresh0*/
    pcnt_event_enable(PCNT_TEST_UNIT, PCNT_EVT_THRES_0);
    /*Enable watch point event of h_lim*/
    pcnt_event_enable(PCNT_TEST_UNIT, PCNT_EVT_H_LIM);
    /*Enable watch point event of l_lim*/
    pcnt_event_enable(PCNT_TEST_UNIT, PCNT_EVT_L_LIM);
    /*Enable watch point event of zero*/
    pcnt_event_enable(PCNT_TEST_UNIT, PCNT_EVT_ZERO);

    /*Pause counter*/
    pcnt_counter_pause(PCNT_TEST_UNIT);
    /*Reset counter value*/
    pcnt_counter_clear(PCNT_TEST_UNIT);
    /*Register ISR handler*/
    pcnt_isr_register(pcnt_example_intr_handler, NULL, 0, NULL);
    /*Enable interrupt for PCNT unit*/
    pcnt_intr_enable(PCNT_TEST_UNIT);
    /*Resume counting*/
    pcnt_counter_resume(PCNT_TEST_UNIT);
}

void app_main()
{
    /*Init LEDC for pulse input signal */
    ledc_init();
    /*Init PCNT event queue */
    pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));
    /*Init PCNT functions*/
    pcnt_example_init();

    int16_t count = 0;
    pcnt_evt_t evt;
    portBASE_TYPE res;
    while(1)
    {
        res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);
        if(res == pdTRUE) {
            pcnt_get_counter_value(PCNT_TEST_UNIT, &count);
            printf("Event PCNT unit[%d]; cnt: %d\n", evt.unit, count);
            if(evt.status & PCNT_STATUS_THRES1_M) {
                printf("THRES1 EVT\n");
            }
            if(evt.status & PCNT_STATUS_THRES0_M) {
                printf("THRES0 EVT\n");
            }
            if(evt.status & PCNT_STATUS_L_LIM_M) {
                printf("L_LIM EVT\n");
            }
            if(evt.status & PCNT_STATUS_H_LIM_M) {
                printf("H_LIM EVT\n");
            }
            if(evt.status & PCNT_STATUS_ZERO_M) {
                printf("ZERO EVT\n");
            }
        } else {
            pcnt_get_counter_value(PCNT_TEST_UNIT, &count);
            printf("Current counter value :%d\n", count);
        }
    }
}

