diff --git a/components/driver/include/driver/mcpwm.h b/components/driver/include/driver/mcpwm.h index d79541cc0f..db8816069d 100644 --- a/components/driver/include/driver/mcpwm.h +++ b/components/driver/include/driver/mcpwm.h @@ -152,6 +152,16 @@ typedef enum { MCPWM_BOTH_EDGE = BIT(1) | BIT(0), /*!hal.dev); + mcpwm_ll_intr_clear_capture_status(curr_context->hal.dev, intr_status); + bool need_yield = false; + for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; ++i) { + if ((intr_status >> i) & 0x1) { + if (curr_context->cap_isr_func[i].fn != NULL) { + cap_event_data_t edata; + edata.cap_edge = mcpwm_ll_capture_is_negedge(curr_context->hal.dev, i) ? MCPWM_NEG_EDGE + : MCPWM_POS_EDGE; + edata.cap_value = mcpwm_ll_capture_get_value(curr_context->hal.dev, i); + if (curr_context->cap_isr_func[i].fn(curr_context->group_id, i, &edata, + curr_context->cap_isr_func[i].args)) { + need_yield = true; + } + } + } + } + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + esp_err_t mcpwm_capture_enable(mcpwm_unit_t mcpwm_num, mcpwm_capture_signal_t cap_sig, mcpwm_capture_on_edge_t cap_edge, uint32_t num_of_pulse) { @@ -746,6 +794,85 @@ esp_err_t mcpwm_capture_disable(mcpwm_unit_t mcpwm_num, mcpwm_capture_signal_t c return ESP_OK; } +esp_err_t mcpwm_capture_enable_channel(mcpwm_unit_t mcpwm_num, mcpwm_capture_channel_id_t cap_channel, const mcpwm_capture_config_t *cap_conf) +{ + ESP_RETURN_ON_FALSE(mcpwm_num < SOC_MCPWM_GROUPS, ESP_ERR_INVALID_ARG, TAG, MCPWM_GROUP_NUM_ERROR); + ESP_RETURN_ON_FALSE(cap_channel < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER, ESP_ERR_INVALID_ARG, TAG, MCPWM_CAPTURE_ERROR); + ESP_RETURN_ON_FALSE(context[mcpwm_num].cap_isr_func[cap_channel].fn == NULL, ESP_ERR_INVALID_STATE, TAG, + MCPWM_CAP_EXIST_ERROR); + mcpwm_hal_context_t *hal = &context[mcpwm_num].hal; + + // enable MCPWM module incase user don't use `mcpwm_init` at all. always increase reference count + periph_module_enable(mcpwm_periph_signals.groups[mcpwm_num].module); + + mcpwm_hal_init_config_t init_config = { + .host_id = mcpwm_num + }; + mcpwm_hal_init(hal, &init_config); + mcpwm_critical_enter(mcpwm_num); + mcpwm_ll_group_set_clock_prescale(hal->dev, context[mcpwm_num].group_pre_scale); + mcpwm_ll_capture_enable_timer(hal->dev, true); + mcpwm_ll_capture_enable_channel(hal->dev, cap_channel, true); + mcpwm_ll_capture_enable_negedge(hal->dev, cap_channel, cap_conf->cap_edge & MCPWM_NEG_EDGE); + mcpwm_ll_capture_enable_posedge(hal->dev, cap_channel, cap_conf->cap_edge & MCPWM_POS_EDGE); + mcpwm_ll_capture_set_prescale(hal->dev, cap_channel, cap_conf->cap_prescale); + // capture feature should be used with interupt, so enable it by default + mcpwm_ll_intr_enable_capture(hal->dev, cap_channel, true); + mcpwm_ll_intr_clear_capture_status(hal->dev, 1 << cap_channel); + mcpwm_critical_exit(mcpwm_num); + + mcpwm_mutex_lock(mcpwm_num); + context[mcpwm_num].cap_isr_func[cap_channel].fn = cap_conf->capture_cb; + context[mcpwm_num].cap_isr_func[cap_channel].args = cap_conf->user_data; + esp_err_t ret = ESP_OK; + if (context[mcpwm_num].mcpwm_intr_handle == NULL) { + ret = esp_intr_alloc(mcpwm_periph_signals.groups[mcpwm_num].irq_id, 0, + mcpwm_default_isr_handler, + (void *) (context + mcpwm_num), &(context[mcpwm_num].mcpwm_intr_handle)); + } + mcpwm_mutex_unlock(mcpwm_num); + + return ret; +} + +esp_err_t mcpwm_capture_disable_channel(mcpwm_unit_t mcpwm_num, mcpwm_capture_channel_id_t cap_channel) +{ + ESP_RETURN_ON_FALSE(mcpwm_num < SOC_MCPWM_GROUPS, ESP_ERR_INVALID_ARG, TAG, MCPWM_GROUP_NUM_ERROR); + ESP_RETURN_ON_FALSE(cap_channel < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER, ESP_ERR_INVALID_ARG, TAG, MCPWM_CAPTURE_ERROR); + + mcpwm_hal_context_t *hal = &context[mcpwm_num].hal; + + mcpwm_critical_enter(mcpwm_num); + mcpwm_ll_capture_enable_channel(hal->dev, cap_channel, false); + mcpwm_ll_intr_enable_capture(hal->dev, cap_channel, false); + mcpwm_critical_exit(mcpwm_num); + + mcpwm_mutex_lock(mcpwm_num); + context[mcpwm_num].cap_isr_func[cap_channel].fn = NULL; + context[mcpwm_num].cap_isr_func[cap_channel].args = NULL; + // if all user defined ISR callback is disabled, free the handle + bool should_free_handle = true; + for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; ++i) { + if (context[mcpwm_num].cap_isr_func[i].fn != NULL) { + should_free_handle = false; + break; + } + } + esp_err_t ret = ESP_OK; + if (should_free_handle) { + ret = esp_intr_free(context[mcpwm_num].mcpwm_intr_handle); + if (ret != ESP_OK){ + ESP_LOGE(TAG, "failed to free interrupt handle"); + } + context[mcpwm_num].mcpwm_intr_handle = NULL; + } + mcpwm_mutex_unlock(mcpwm_num); + + // always decrease reference count + periph_module_disable(mcpwm_periph_signals.groups[mcpwm_num].module); + return ret; +} + uint32_t mcpwm_capture_signal_get_value(mcpwm_unit_t mcpwm_num, mcpwm_capture_signal_t cap_sig) { ESP_RETURN_ON_FALSE(mcpwm_num < SOC_MCPWM_GROUPS, ESP_ERR_INVALID_ARG, TAG, MCPWM_GROUP_NUM_ERROR); diff --git a/components/driver/test/test_pwm.c b/components/driver/test/test_pwm.c index 4174757daf..d7fdac1569 100644 --- a/components/driver/test/test_pwm.c +++ b/components/driver/test/test_pwm.c @@ -29,8 +29,6 @@ #define MCPWM_TEST_GROUP_CLK_HZ (SOC_MCPWM_BASE_CLK_HZ / 16) #define MCPWM_TEST_TIMER_CLK_HZ (MCPWM_TEST_GROUP_CLK_HZ / 10) - -static mcpwm_dev_t *MCPWM[2] = {&MCPWM0, &MCPWM1}; // interrupt handling still lacks API to get/clear pending event, currently we have to read/write interrupt register const static mcpwm_io_signals_t pwma[] = {MCPWM0A, MCPWM1A, MCPWM2A}; const static mcpwm_io_signals_t pwmb[] = {MCPWM0B, MCPWM1B, MCPWM2B}; const static mcpwm_fault_signal_t fault_sig_array[] = {MCPWM_SELECT_F0, MCPWM_SELECT_F1, MCPWM_SELECT_F2}; @@ -383,18 +381,13 @@ static void mcpwm_capture_test(mcpwm_unit_t unit, mcpwm_capture_signal_t cap_cha TaskHandle_t task_hdl; } test_capture_callback_data_t; - void test_mcpwm_intr_handler(void *arg) { + bool test_mcpwm_intr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata, void *arg) { BaseType_t high_task_wakeup = pdFALSE; test_capture_callback_data_t *cb_data = (test_capture_callback_data_t *)arg; - uint32_t status = MCPWM[cb_data->unit]->int_st.val; - MCPWM[cb_data->unit]->int_clr.val = status; vTaskNotifyGiveFromISR(cb_data->task_hdl, &high_task_wakeup); - if (high_task_wakeup == pdTRUE) { - portYIELD_FROM_ISR(); - } + return high_task_wakeup == pdTRUE; } - intr_handle_t mcpwm_intr = NULL; test_capture_callback_data_t callback_data = { .unit = unit, .task_hdl = xTaskGetCurrentTaskHandle(), @@ -402,17 +395,23 @@ static void mcpwm_capture_test(mcpwm_unit_t unit, mcpwm_capture_signal_t cap_cha //each timer test the capture sig with the same id with it. mcpwm_io_signals_t cap_io = cap_io_sig_array[cap_chan]; - mcpwm_capture_signal_t cap_sig = cap_sig_array[cap_chan]; + mcpwm_capture_channel_id_t cap_channel = cap_sig_array[cap_chan]; TEST_ESP_OK(test_mcpwm_gpio_init(unit, cap_io, TEST_CAP_GPIO)); - TEST_ESP_OK(mcpwm_capture_enable(unit, cap_sig, MCPWM_POS_EDGE, 0)); - TEST_ESP_OK(mcpwm_isr_register(unit, test_mcpwm_intr_handler, &callback_data, 0, &mcpwm_intr)); + mcpwm_capture_config_t conf = { + .cap_edge = MCPWM_POS_EDGE, + .cap_prescale = 1, + .capture_cb = test_mcpwm_intr_handler, + .user_data = &callback_data + }; + TEST_ESP_OK(mcpwm_capture_enable_channel(unit, cap_channel, &conf)); // generate an posage gpio_set_level(TEST_CAP_GPIO, 0); gpio_set_level(TEST_CAP_GPIO, 1); vTaskDelay(pdMS_TO_TICKS(100)); TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(40))); uint32_t cap_val0 = mcpwm_capture_signal_get_value(unit, cap_chan); + // generate another posage gpio_set_level(TEST_CAP_GPIO, 0); gpio_set_level(TEST_CAP_GPIO, 1); @@ -421,8 +420,7 @@ static void mcpwm_capture_test(mcpwm_unit_t unit, mcpwm_capture_signal_t cap_cha // capture clock source is APB (80MHz), 100ms means 8000000 ticks TEST_ASSERT_UINT_WITHIN(100000, 8000000, cap_val1 - cap_val0); - TEST_ESP_OK(mcpwm_capture_disable(unit, cap_sig)); - TEST_ESP_OK(esp_intr_free(mcpwm_intr)); + TEST_ESP_OK(mcpwm_capture_disable_channel(unit, cap_channel)); } TEST_CASE("MCPWM capture test", "[mcpwm]") diff --git a/docs/en/api-reference/peripherals/mcpwm.rst b/docs/en/api-reference/peripherals/mcpwm.rst index 0e9234a144..174084017b 100644 --- a/docs/en/api-reference/peripherals/mcpwm.rst +++ b/docs/en/api-reference/peripherals/mcpwm.rst @@ -23,7 +23,7 @@ More detailed block diagram of the MCPWM unit is shown below. Each A/B pair may MCPWM Block Diagram -Description of this API starts with configuration of MCPWM's **Timer** and **Generator** submodules to provide the basic motor control functionality. Then it discusses more advanced submodules and functionalities of a **Fault Handler**, signal **Capture**, **Carrier** and **Interrupts**. +Description of this API starts with configuration of MCPWM's **Timer** and **Generator** submodules to provide the basic motor control functionality. Then it discusses more advanced submodules and functionalities of a **Fault Handler**, signal **Capture** and **Carrier**. Contents -------- @@ -34,7 +34,6 @@ Contents * `Capture`_ external signals to provide additional control over the outputs * Use `Fault Handler`_ to detect and manage faults * Add a higher frequency `Carrier`_, if output signals are passed through an isolation transformer -* Configuration and handling of `Interrupts`_. * Extra configuration of `Resolution`_. @@ -109,11 +108,13 @@ One of requirements of BLDC (Brushless DC, see figure below) motor control is se The capture functionality may be used for other types of motors or tasks. The functionality is enabled in two steps: 1. Configuration of GPIOs to act as the capture signal inputs by calling functions :cpp:func:`mcpwm_gpio_init` or :cpp:func:`mcpwm_set_pin`, that were described in section `Configure`_. -2. Enabling of the functionality itself by invoking :cpp:func:`mcpwm_capture_enable`, selecting desired signal input from :cpp:type:`mcpwm_capture_signal_t`, setting the signal edge with :cpp:type:`mcpwm_capture_on_edge_t` and the signal count prescaler. +2. Enabling of the functionality itself by invoking :cpp:func:`mcpwm_capture_enable_channel`, selecting desired signal input from :cpp:type:`mcpwm_capture_channel_id_t`, setting the signal edge, signal count prescaler and user callback within :cpp:type:`mcpwm_capture_config_t` -Within the second step above a 32-bit capture timer is enabled. The timer runs continuously driven by the APB clock. The clock frequency is typically 80 MHz. On each capture event the capture timer’s value is stored in time-stamp register that may be then checked by calling :cpp:func:`mcpwm_capture_signal_get_value`. The edge of the last signal may be checked with :cpp:func:`mcpwm_capture_signal_get_edge`. +Within the second step above a 32-bit capture timer is enabled. The timer runs continuously driven by the APB clock. The clock frequency is typically 80 MHz. On each capture event the capture timer’s value is stored in time-stamp register that may be then checked by calling :cpp:func:`mcpwm_capture_signal_get_value`. The edge of the last signal may be checked with :cpp:func:`mcpwm_capture_signal_get_edge`. Those data are also provided inside callback function as event data :cpp:type:`cap_event_data_t` -If not required anymore, the capture functionality may be disabled with :cpp:func:`mcpwm_capture_disable`. +If not required anymore, the capture functionality may be disabled with :cpp:func:`mcpwm_capture_disable_channel`. + +Capture prescale is different from other modules as it is applied to the input signal, not the timer source. Prescaler has maintained its own level state with the initial value set to low and is detecting the positive edge of the input signal to change its internal state. That means if two pairs of positive and negative edges are passed to input, the prescaler's internal state will change twice. ISR will report on this internal state change, not the input signal. For example, setting prescale to 2 will generate ISR callback on each positive edge of input if both edge is selected via :cpp:type:`mcpwm_capture_config_t`. Or each 2 positive edges of input if only one edge is selected though :cpp:type:`mcpwm_capture_config_t`. Fault Handler @@ -158,7 +159,7 @@ To disable carrier functionality call :cpp:func:`mcpwm_carrier_disable`. Interrupts ---------- -Registering of the MCPWM interrupt handler is possible by calling :cpp:func:`mcpwm_isr_register`. +Registering of the MCPWM interrupt handler is possible by calling :cpp:func:`mcpwm_isr_register`. Note if :cpp:func:`mcpwm_capture_enable_channel` is used then a default ISR routine will be installed hence please do not call this function to register any more. Resolution @@ -171,15 +172,17 @@ Note that, these two APIs won't update the frequency and duty automatically, to To get PWM pulse that is below 15Hz, please set the resolution to a lower value. For high frequency PWM with limited step range, please set them with higher value. + Application Example ------------------- -Examples of using MCPWM for motor control: :example:`peripherals/mcpwm`: +MCPWM example are located under: :example:`peripherals/mcpwm`: * Demonstration how to use each submodule of the MCPWM - :example:`peripherals/mcpwm/mcpwm_basic_config` * Control of BLDC (brushless DC) motor with hall sensor feedback - :example:`peripherals/mcpwm/mcpwm_bldc_control` * Brushed DC motor control - :example:`peripherals/mcpwm/mcpwm_brushed_dc_control` * Servo motor control - :example:`peripherals/mcpwm/mcpwm_servo_control` +* HC-SR04 sensor with capture - :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` API Reference diff --git a/examples/peripherals/mcpwm/mcpwm_basic_config/main/mcpwm_basic_config_example.c b/examples/peripherals/mcpwm/mcpwm_basic_config/main/mcpwm_basic_config_example.c index 4a89a6a990..7dc043e082 100644 --- a/examples/peripherals/mcpwm/mcpwm_basic_config/main/mcpwm_basic_config_example.c +++ b/examples/peripherals/mcpwm/mcpwm_basic_config/main/mcpwm_basic_config_example.c @@ -18,23 +18,13 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" -#include "esp_attr.h" -#include "soc/rtc.h" #include "driver/mcpwm.h" -#include "soc/mcpwm_periph.h" #define MCPWM_EN_CARRIER 0 //Make this 1 to test carrier submodule of mcpwm, set high frequency carrier parameters #define MCPWM_EN_DEADTIME 0 //Make this 1 to test deadtime submodule of mcpwm, set deadtime value and deadtime mode #define MCPWM_EN_FAULT 0 //Make this 1 to test fault submodule of mcpwm, set action on MCPWM signal on fault occurence like overcurrent, overvoltage, etc #define MCPWM_EN_SYNC 0 //Make this 1 to test sync submodule of mcpwm, sync timer signals -#define MCPWM_EN_CAPTURE 0 //Make this 1 to test capture submodule of mcpwm, measure time between rising/falling edge of captured signal #define MCPWM_GPIO_INIT 0 //select which function to use to initialize gpio signals -#define CAP_SIG_NUM 3 //Three capture signals - -#define CAP0_INT_EN BIT(27) //Capture 0 interrupt bit -#define CAP1_INT_EN BIT(28) //Capture 1 interrupt bit -#define CAP2_INT_EN BIT(29) //Capture 2 interrupt bit - #define GPIO_PWM0A_OUT 19 //Set GPIO 19 as PWM0A #define GPIO_PWM0B_OUT 18 //Set GPIO 18 as PWM0B @@ -42,9 +32,6 @@ #define GPIO_PWM1B_OUT 16 //Set GPIO 16 as PWM1B #define GPIO_PWM2A_OUT 15 //Set GPIO 15 as PWM2A #define GPIO_PWM2B_OUT 14 //Set GPIO 14 as PWM2B -#define GPIO_CAP0_IN 23 //Set GPIO 23 as CAP0 -#define GPIO_CAP1_IN 25 //Set GPIO 25 as CAP1 -#define GPIO_CAP2_IN 26 //Set GPIO 26 as CAP2 #define GPIO_SYNC0_IN 2 //Set GPIO 02 as SYNC0 #define GPIO_SYNC1_IN 4 //Set GPIO 04 as SYNC1 #define GPIO_SYNC2_IN 5 //Set GPIO 05 as SYNC2 @@ -52,19 +39,6 @@ #define GPIO_FAULT1_IN 33 //Set GPIO 33 as FAULT1 #define GPIO_FAULT2_IN 34 //Set GPIO 34 as FAULT2 -typedef struct { - uint32_t capture_signal; - mcpwm_capture_signal_t sel_cap_signal; -} capture; - -uint32_t *current_cap_value = NULL; -uint32_t *previous_cap_value = NULL; - -xQueueHandle cap_queue; -#if MCPWM_EN_CAPTURE -static mcpwm_dev_t *MCPWM[2] = {&MCPWM0, &MCPWM1}; -#endif - static void mcpwm_example_gpio_initialize(void) { printf("initializing mcpwm gpio...\n"); @@ -97,16 +71,10 @@ static void mcpwm_example_gpio_initialize(void) .mcpwm_sync2_in_num = GPIO_SYNC2_IN, .mcpwm_fault0_in_num = GPIO_FAULT0_IN, .mcpwm_fault1_in_num = GPIO_FAULT1_IN, - .mcpwm_fault2_in_num = GPIO_FAULT2_IN, - .mcpwm_cap0_in_num = GPIO_CAP0_IN, - .mcpwm_cap1_in_num = GPIO_CAP1_IN, - .mcpwm_cap2_in_num = GPIO_CAP2_IN + .mcpwm_fault2_in_num = GPIO_FAULT2_IN }; mcpwm_set_pin(MCPWM_UNIT_0, &pin_config); #endif - gpio_pulldown_en(GPIO_CAP0_IN); //Enable pull down on CAP0 signal - gpio_pulldown_en(GPIO_CAP1_IN); //Enable pull down on CAP1 signal - gpio_pulldown_en(GPIO_CAP2_IN); //Enable pull down on CAP2 signal gpio_pulldown_en(GPIO_SYNC0_IN); //Enable pull down on SYNC0 signal gpio_pulldown_en(GPIO_SYNC1_IN); //Enable pull down on SYNC1 signal gpio_pulldown_en(GPIO_SYNC2_IN); //Enable pull down on SYNC2 signal @@ -115,82 +83,6 @@ static void mcpwm_example_gpio_initialize(void) gpio_pulldown_en(GPIO_FAULT2_IN); //Enable pull down on FAULT2 signal } -/** - * @brief Set gpio 12 as our test signal that generates high-low waveform continuously, connect this gpio to capture pin. - */ -static void gpio_test_signal(void *arg) -{ - printf("intializing test signal...\n"); - gpio_config_t gp; - gp.intr_type = GPIO_INTR_DISABLE; - gp.mode = GPIO_MODE_OUTPUT; - gp.pin_bit_mask = GPIO_SEL_12; - gpio_config(&gp); - while (1) { - //here the period of test signal is 20ms - gpio_set_level(GPIO_NUM_12, 1); //Set high - vTaskDelay(10); //delay of 10ms - gpio_set_level(GPIO_NUM_12, 0); //Set low - vTaskDelay(10); //delay of 10ms - } -} - -/** - * @brief When interrupt occurs, we receive the counter value and display the time between two rising edge - */ -static void disp_captured_signal(void *arg) -{ - capture evt; - while (1) { - xQueueReceive(cap_queue, &evt, portMAX_DELAY); - if (evt.sel_cap_signal == MCPWM_SELECT_CAP0) { - printf("CAP0 : %d us\n", evt.capture_signal); - } - if (evt.sel_cap_signal == MCPWM_SELECT_CAP1) { - printf("CAP1 : %d us\n", evt.capture_signal); - } - if (evt.sel_cap_signal == MCPWM_SELECT_CAP2) { - printf("CAP2 : %d us\n", evt.capture_signal); - } - } -} - -#if MCPWM_EN_CAPTURE -/** - * @brief this is ISR handler function, here we check for interrupt that triggers rising edge on CAP0 signal and according take action - */ -static void IRAM_ATTR isr_handler(void* arg) -{ - uint32_t mcpwm_intr_status; - capture evt; - mcpwm_intr_status = MCPWM[MCPWM_UNIT_0]->int_st.val; //Read interrupt status - //calculate the interval in the ISR, - //so that the interval will be always correct even when cap_queue is not handled in time and overflow. - if (mcpwm_intr_status & CAP0_INT_EN) { //Check for interrupt on rising edge on CAP0 signal - current_cap_value[0] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP0); //get capture signal counter value - evt.capture_signal = (current_cap_value[0] - previous_cap_value[0]) / (rtc_clk_apb_freq_get() / 1000000); - previous_cap_value[0] = current_cap_value[0]; - evt.sel_cap_signal = MCPWM_SELECT_CAP0; - xQueueSendFromISR(cap_queue, &evt, NULL); - } - if (mcpwm_intr_status & CAP1_INT_EN) { //Check for interrupt on rising edge on CAP0 signal - current_cap_value[1] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP1); //get capture signal counter value - evt.capture_signal = (current_cap_value[1] - previous_cap_value[1]) / (rtc_clk_apb_freq_get() / 1000000); - previous_cap_value[1] = current_cap_value[1]; - evt.sel_cap_signal = MCPWM_SELECT_CAP1; - xQueueSendFromISR(cap_queue, &evt, NULL); - } - if (mcpwm_intr_status & CAP2_INT_EN) { //Check for interrupt on rising edge on CAP0 signal - current_cap_value[2] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP2); //get capture signal counter value - evt.capture_signal = (current_cap_value[2] - previous_cap_value[2]) / (rtc_clk_apb_freq_get() / 1000000); - previous_cap_value[2] = current_cap_value[2]; - evt.sel_cap_signal = MCPWM_SELECT_CAP2; - xQueueSendFromISR(cap_queue, &evt, NULL); - } - MCPWM[MCPWM_UNIT_0]->int_clr.val = mcpwm_intr_status; -} -#endif - /** * @brief Configure whole MCPWM module */ @@ -266,29 +158,11 @@ static void mcpwm_example_config(void *arg) mcpwm_sync_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_SELECT_SYNC0, 200); //Load counter value with 20% of period counter of mcpwm timer 1 when sync 0 occurs #endif -#if MCPWM_EN_CAPTURE - //7. Capture configuration - //comment if you don't want to use capture submodule, also u can comment the capture gpio signals - //configure CAP0, CAP1 and CAP2 signal to start capture counter on rising edge - //we generate a gpio_test_signal of 20ms on GPIO 12 and connect it to one of the capture signal, the disp_captured_function displays the time between rising edge - //In general practice you can connect Capture to external signal, measure time between rising edge or falling edge and take action accordingly - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP2, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP1, MCPWM_POS_EDGE, 0); //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second - //enable interrupt, so each this a rising edge occurs interrupt is triggered - MCPWM[MCPWM_UNIT_0]->int_ena.val = CAP0_INT_EN | CAP1_INT_EN | CAP2_INT_EN; //Enable interrupt on CAP0, CAP1 and CAP2 signal - mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler -#endif vTaskDelete(NULL); } void app_main(void) { printf("Testing MCPWM...\n"); - cap_queue = xQueueCreate(1, sizeof(capture)); //comment if you don't want to use capture module - current_cap_value = (uint32_t *)malloc(CAP_SIG_NUM*sizeof(uint32_t)); //comment if you don't want to use capture module - previous_cap_value = (uint32_t *)malloc(CAP_SIG_NUM*sizeof(uint32_t)); //comment if you don't want to use capture module - xTaskCreate(disp_captured_signal, "mcpwm_config", 4096, NULL, 5, NULL); //comment if you don't want to use capture module - xTaskCreate(gpio_test_signal, "gpio_test_signal", 4096, NULL, 5, NULL); //comment if you don't want to use capture module xTaskCreate(mcpwm_example_config, "mcpwm_example_config", 4096, NULL, 5, NULL); } diff --git a/examples/peripherals/mcpwm/mcpwm_bldc_control/main/mcpwm_bldc_control_hall_sensor_example.c b/examples/peripherals/mcpwm/mcpwm_bldc_control/main/mcpwm_bldc_control_hall_sensor_example.c index 7e9e0f27ba..290b1fb2fa 100644 --- a/examples/peripherals/mcpwm/mcpwm_bldc_control/main/mcpwm_bldc_control_hall_sensor_example.c +++ b/examples/peripherals/mcpwm/mcpwm_bldc_control/main/mcpwm_bldc_control_hall_sensor_example.c @@ -20,10 +20,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" -#include "esp_attr.h" #include "soc/rtc.h" #include "driver/mcpwm.h" -#include "soc/mcpwm_periph.h" #define INITIAL_DUTY 10.0 //initial duty cycle is 10.0% #define MCPWM_GPIO_INIT 0 //select which function to use to initialize gpio signals @@ -56,8 +54,6 @@ static uint32_t hall_sensor_previous = 0; xQueueHandle cap_queue; -static mcpwm_dev_t *MCPWM[2] = {&MCPWM0, &MCPWM1}; - static void mcpwm_example_gpio_initialize(void) { printf("initializing mcpwm bldc control gpio...\n"); @@ -162,27 +158,25 @@ static void disp_captured_signal(void *arg) /** * @brief this is ISR handler function, here we check for interrupt that triggers rising edge on CAP0 signal and according take action */ -static void IRAM_ATTR isr_handler(void *arg) +static bool isr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata, void *arg) { - uint32_t mcpwm_intr_status; capture evt; - mcpwm_intr_status = MCPWM[MCPWM_UNIT_0]->int_st.val; //Read interrupt status - if (mcpwm_intr_status & CAP0_INT_EN) { //Check for interrupt on rising edge on CAP0 signal - evt.capture_signal = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP0); //get capture signal counter value + if (cap_sig == MCPWM_SELECT_CAP0) { //Check for interrupt on rising edge on CAP0 signal + evt.capture_signal = edata->cap_value; //get capture signal counter value evt.sel_cap_signal = MCPWM_SELECT_CAP0; xQueueSendFromISR(cap_queue, &evt, NULL); } - if (mcpwm_intr_status & CAP1_INT_EN) { //Check for interrupt on rising edge on CAP1 signal - evt.capture_signal = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP1); //get capture signal counter value + if (cap_sig == MCPWM_SELECT_CAP1) { //Check for interrupt on rising edge on CAP1 signal + evt.capture_signal = edata->cap_value; //get capture signal counter value evt.sel_cap_signal = MCPWM_SELECT_CAP1; xQueueSendFromISR(cap_queue, &evt, NULL); } - if (mcpwm_intr_status & CAP2_INT_EN) { //Check for interrupt on rising edge on CAP2 signal - evt.capture_signal = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP2); //get capture signal counter value + if (cap_sig == MCPWM_SELECT_CAP2) { //Check for interrupt on rising edge on CAP2 signal + evt.capture_signal = edata->cap_value; //get capture signal counter value evt.sel_cap_signal = MCPWM_SELECT_CAP2; xQueueSendFromISR(cap_queue, &evt, NULL); } - MCPWM[MCPWM_UNIT_0]->int_clr.val = mcpwm_intr_status; + return false; } #if CHANGE_DUTY_CONTINUOUSLY @@ -228,12 +222,15 @@ static void mcpwm_example_bldc_control(void *arg) //configure CAP0, CAP1 and CAP2 signal to start capture counter on rising edge //we generate a gpio_test_signal of 20ms on GPIO 12 and connect it to one of the capture signal, the disp_captured_function displays the time between rising edge //In general practice you can connect Capture to external signal, measure time between rising edge or falling edge and take action accordingly - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE, 0); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP1, MCPWM_POS_EDGE, 0); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second - mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP2, MCPWM_POS_EDGE, 0); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second - //enable interrupt, so each this a rising edge occurs interrupt is triggered - MCPWM[MCPWM_UNIT_0]->int_ena.val = (CAP0_INT_EN | CAP1_INT_EN | CAP2_INT_EN); //Enable interrupt on CAP0, CAP1 and CAP2 signal - mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler + mcpwm_capture_config_t conf = { + .cap_edge = MCPWM_POS_EDGE, + .cap_prescale = 1, + .capture_cb = isr_handler, + .user_data = NULL, + }; + mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, &conf); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second + mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP1, &conf); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second + mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP2, &conf); //capture signal on rising edge, pulse num = 0 i.e. 800,000,000 counts is equal to one second //According to the hall sensor input value take action on PWM0A/0B/1A/1B/2A/2B while (1) { hall_sensor_value = (gpio_get_level(GPIO_NUM_27) * 1) + (gpio_get_level(GPIO_NUM_26) * 2) + (gpio_get_level(GPIO_NUM_25) * 4); diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/CMakeLists.txt b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/CMakeLists.txt new file mode 100644 index 0000000000..2c97a7b58a --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mcpwm_capture_hc_sr04) diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/Makefile b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/Makefile new file mode 100644 index 0000000000..588de3b21e --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := mcpwm_capture_hc_sr04 + +include $(IDF_PATH)/make/project.mk diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/README.md b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/README.md new file mode 100644 index 0000000000..4df1e96735 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/README.md @@ -0,0 +1,81 @@ +| Supported Targets | ESP32 | ESP32-S3 | +| ----------------- | ----- | -------- | + +# HC-SR04 Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The capture module in MCPWM peripheral is designed to accurately log the time stamp on the hardware side when an event happens (compared to GPIO ISR which requires a software-based logging method). Each capture unit has three channels, which can be used together to capture IO events parallelly. +This example shows how to make use of the HW features to decode the pulse width signals generated from a common HC-SR04 sonar range finder -- [HC-SR04](https://www.sparkfun.com/products/15569). + +The signal that HC-SR04 produces (and what can be handled by this example) is a simple pulse whose width indicates the measured distance. A pulse is required to send to HC-SR04 on `Trig` pin to begin a new measurement. Then the pulse described above will be sent back on `Echo` pin for decoding. + +Typical signals: + +``` +Trig +-----+ + | | + | | + -----+ +----------------------- +Echo +-----+ + | | + | | + -----------------+ +----------- + + +---------------------------------------> + Timeline +``` + +## How to Use Example + +### Hardware Required + +* An ESP development board +* HC-SR04 module + +Connection : + +``` + +------+ +---------------------------------+ ++-------+ | | | +| | VCC +--------------+ 5V | ++-------+ | | | + + Echo +----=====>----+ GPIO18 (internal pull up) | + | | | | + + Trig +----<=====----+ GPIO19 | ++-------| | | | +| | GND +--------------+ GND | ++-------| | | | + +------+ +---------------------------------+ +``` + +### 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 [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (314) hc-sr04: HC-SR04 example based on capture function from MCPWM +I (324) hc-sr04: Echo pin configured +I (324) gpio: GPIO[19]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (334) hc-sr04: Trig pin configured +I (344) hc-sr04: trig task started +I (444) hc-sr04: Pulse width: 419us, Measured distance: 7.22cm +I (544) hc-sr04: Pulse width: 419us, Measured distance: 7.22cm +I (644) hc-sr04: Pulse width: 416us, Measured distance: 7.17cm +I (744) hc-sr04: Pulse width: 415us, Measured distance: 7.16cm +I (844) hc-sr04: Pulse width: 415us, Measured distance: 7.16cm +I (944) hc-sr04: Pulse width: 416us, Measured distance: 7.17cm +I (1044) hc-sr04: Pulse width: 391us, Measured distance: 6.74cm +``` + +This example runs at 10Hz sampling rate. out of range data is dropped and only valid measurement is printed. + +## 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. diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/CMakeLists.txt b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/CMakeLists.txt new file mode 100644 index 0000000000..450a211ce5 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "mcpwm_capture_hc_sr04.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/component.mk b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c new file mode 100644 index 0000000000..44363ee0ad --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c @@ -0,0 +1,132 @@ +/* MCPWM capture example: HC-SR04 + + 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. +*/ + +/* + * This example will show you how to use capture function to read HC-SR04 sonar sensor. + * + * HC_SR04_SAMPLE_PERIOD_MS should be at least 50ms + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "esp_check.h" +#include "soc/rtc.h" +#include "driver/mcpwm.h" + +const static char *TAG = "hc-sr04"; + +#define HC_SR04_SAMPLE_PERIOD_MS 100 +_Static_assert(HC_SR04_SAMPLE_PERIOD_MS > 50, "Sample period too short!"); +#define HC_SR04_PIN_ECHO GPIO_NUM_18 +#define HC_SR04_PIN_TRIG GPIO_NUM_19 + +#define TRIGGER_THREAD_STACK_SIZE 512 +#define TRIGGER_THREAD_PRIORITY 5 + +typedef struct { + uint32_t capture_signal; + mcpwm_capture_signal_t sel_cap_signal; +} capture; + +static uint32_t cap_val_begin_of_sample = 0; +static uint32_t cap_val_end_of_sample = 0; + +static xQueueHandle cap_queue; + +/** + * @brief generate single pulse on Trig pin to activate a new sample + */ +static void gen_trig_output(void *arg) { + TickType_t xLastWakeTime = xTaskGetTickCount(); + while (true) { + vTaskDelayUntil(&xLastWakeTime, HC_SR04_SAMPLE_PERIOD_MS / portTICK_PERIOD_MS); + ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 1)); // set high + esp_rom_delay_us(10); + ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 0)); // set low + } +} + +/** + * @brief this is an ISR callback, we take action according to the captured edge + */ +static bool sr04_echo_isr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata, + void *arg) { + //calculate the interval in the ISR, + //so that the interval will be always correct even when cap_queue is not handled in time and overflow. + BaseType_t high_task_wakeup = pdFALSE; + if (edata->cap_edge == MCPWM_POS_EDGE) { + // store the timestamp when pos edge is detected + cap_val_begin_of_sample = edata->cap_value; + cap_val_end_of_sample = cap_val_begin_of_sample; + } else { + cap_val_end_of_sample = edata->cap_value; + // following formula refers to: https://www.elecrow.com/download/HC_SR04%20Datasheet.pdf + uint32_t pulse_count = cap_val_end_of_sample - cap_val_begin_of_sample; + // send measurement back though queue + xQueueSendFromISR(cap_queue, &pulse_count, &high_task_wakeup); + } + return high_task_wakeup == pdTRUE; +} + +void app_main(void) { + ESP_LOGI(TAG, "HC-SR04 example based on capture function from MCPWM"); + + /* configure Echo pin */ + // set CAP_0 on GPIO + ESP_ERROR_CHECK(mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, HC_SR04_PIN_ECHO)); + // enable pull down CAP0, to reduce noise + ESP_ERROR_CHECK(gpio_pulldown_en(HC_SR04_PIN_ECHO)); + // enable both edge capture on CAP0 + mcpwm_capture_config_t conf = { + .cap_edge = MCPWM_BOTH_EDGE, + .cap_prescale = 1, + .capture_cb = sr04_echo_isr_handler, + .user_data = NULL + }; + ESP_ERROR_CHECK(mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, &conf)); + ESP_LOGI(TAG, "Echo pin configured"); + + /* configure Trig pin */ + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pin_bit_mask = BIT64(HC_SR04_PIN_TRIG), + }; + ESP_ERROR_CHECK(gpio_config(&io_conf)); + ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 0)); // drive low by default + ESP_LOGI(TAG, "Trig pin configured"); + + // the queue where we read data + cap_queue = xQueueCreate(1, sizeof(uint32_t)); + if (cap_queue == 0) { + ESP_LOGE(TAG, "failed to alloc cap_queue"); + } + + // start generating trig signal + xTaskCreate(gen_trig_output, "gen_trig_output", TRIGGER_THREAD_STACK_SIZE, NULL, TRIGGER_THREAD_PRIORITY, NULL); + ESP_LOGI(TAG, "trig task started"); + // forever loop + while (true) { + uint32_t pulse_count; + // block and wait for new measurement + xQueueReceive(cap_queue, &pulse_count, portMAX_DELAY); + uint32_t pulse_width_us = pulse_count * (1000000.0 / rtc_clk_apb_freq_get()); + // following formula is based on: https://www.elecrow.com/download/HC_SR04%20Datasheet.pdf + if (pulse_width_us > 35000) { + // out of range + continue; + } + float distance = (float) pulse_width_us / 58; + ESP_LOGI(TAG, "Pulse width: %uus, Measured distance: %.2fcm", pulse_width_us, distance); + } +}