espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
12.63k stars 7.04k forks source link

请问MCPWM capture的回调函数可以放在C++的类中么 (IDFGH-12800) #13777

Closed murarduino closed 2 weeks ago

murarduino commented 2 weeks ago

Answers checklist.

General issue report

我需要将mcpwm的capture功能类化。所以我尝试编写组件:

DccDetetor.h

pragma once

include

include

include "sdkconfig.h"

include <driver/gpio.h>

include <driver/mcpwm_cap.h>

include <esp_adc/adc_oneshot.h>

const static char *TAG = "DCCDetector";

define CAP_RESOLUTION_HZ (110001000)

define DETECTOR_ADC_ATTEN ADC_ATTEN_DB_12

ifdef __cplusplus

extern "C" {

endif

class DCCDetector { private: int _dcc_pin; int analog_pin; uint16_t _analog_value; adc_oneshot_unit_handle_t _adc_handle = NULL; mcpwm_cap_timer_handle_t _cap_timer_handle= NULL; mcpwm_cap_channel_handle_t _cap_channel_handle=NULL; adc_unit_t _adc_unit = ADC_UNIT_1; adc_channel_t _adc1_channel; void _detector_cap_init(int dcc_pin); void _detector_analog_init(int analog_pin); bool IRAM_ATTR _capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t edata, void user_data);

public: DCCDetector(int dcc_pin,int analog_pin); ~DCCDetector(); uint16_t analog_read(); };

ifdef __cplusplus

}

endif

DccDetector.cpp:

include

include <freertos/FreeRTOS.h>

include <freertos/task.h>

include "DCCDetector.h"

DCCDetector::DCCDetector(int dcc_pin, int analog_pin) { _detector_cap_init(dcc_pin); _detector_analog_init(analog_pin); }

DCCDetector::~DCCDetector() { } void DCCDetector::_detector_cap_init(int dcc_pin) { //------- Install capture timer mcpwm_capture_timer_config_t cap_conf = { .group_id = 0, .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT, .resolution_hz = CAP_RESOLUTION_HZ, };

mcpwm_new_capture_timer(&cap_conf, &_cap_timer_handle);

//-----Install capture channel

mcpwm_capture_channel_config_t cap_channel_conf = {
    .gpio_num = dcc_pin,
    .intr_priority = 0,
    .prescale = 1,
    .flags = {
        .pos_edge = true,              // 上升沿有效
        .neg_edge = true,              // 下降沿有效
        .pull_up = true,               // 内部上拉有效
        .pull_down = false,            // 内部下拉无效
        .invert_cap_signal = false,    // 反向信号无效
        .io_loop_back = false,         // 禁用回环模式
        .keep_io_conf_at_exit = false, // 删除通道时不保留GPIO相关配置
    }};
mcpwm_new_capture_channel(_cap_timer_handle, &cap_channel_conf, &_cap_channel_handle); // 新建mcpwm capture通道

//----Register capture callback
mcpwm_capture_event_callbacks_t cap_cb = {
    .on_cap = _capture_callback,
};
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, cur_task);//注册capture通道的事件回调

// ------Enable capture channel
mcpwm_capture_channel_enable(_cap_channel_handle);//开启capture通道

}

void DCCDetector::_detector_analog_init(int analog_pin) { //-----Install oneshot adc adc_oneshot_io_to_channel(analog_pin, &_adc_unit, &_adc1_channel); adc_oneshot_unit_init_cfg_t adc_init_config = { .unit_id = _adc_unit, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE, }; adc_oneshot_new_unit(&adc_init_config, &_adc_handle); adc_oneshot_chan_cfg_t adc_config = { .atten = DETECTOR_ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; ESP_ERROR_CHECK(adc_oneshot_config_channel(_adc_handle, _adc1_channel, &adc_config)); }

uint16_t DCCDetector::analog_read() { adc_oneshot_read(_adc_handle, _adc1_channel, (int *)&_analog_value); return _analog_value; }

bool DCCDetector::_capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t edata, void user_data) { static uint32_t cap_val_begin_of_sample = 0; static uint32_t cap_val_end_of_sample = 0; static uint16_t cap_begin_analog_value = 0; TaskHandle_t task_to_notify = (TaskHandle_t)user_data; BaseType_t high_task_wakeup = pdFALSE; // calculate the interval in the ISR, // so that the interval will be always correct even when capture_queue is not handled in time and overflow. if (edata->cap_edge == MCPWM_CAP_EDGE_POS) { // 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; cap_begin_analog_value = analog_read();//?回调函数可以放在 } else { cap_val_end_of_sample = edata->cap_value; uint32_t tof_ticks = cap_val_end_of_sample - cap_val_begin_of_sample;

    // notify the task to calculate the distance
    xTaskNotifyFromISR(task_to_notify, tof_ticks, eSetValueWithOverwrite, &high_task_wakeup);
}

return high_task_wakeup == pdTRUE;

}

但在注册capture的Callback时,报如下错误: "bool (DCCDetector::)(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t edata, void user_data)" 类型的值不能用于初始化 "mcpwm_capture_event_cb_t" (aka "bool ()(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t edata, void user_ctx)") 类型的实体C/C++(144)

我该如何处理?

andylinpersonal commented 2 weeks ago

只有靜態方法可以存入C的函數指針 所以需要一個額外的參數存this void* user_ctx 通常可以用來達到此目的


class DCCDetector {
    static bool IRAM_ATTR DCCDetector::_capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t *edata, void *user_data);
...
    TaskHandle_t m_task = nullptr;
};

bool DCCDetector::_capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t *edata, void *user_data) {
    auto& self = *(DCCDetector*)user_data;
    TaskHandle_t cur_task = self.m_task;
    // do something
}

void DCCDetector::_detector_cap_init(int dcc_pin) {
    // ...
    // 註冊回調
    mcpwm_capture_event_callbacks_t cap_cb = {
        .on_cap = _capture_callback,
    };
    m_task = xTaskGetCurrentTaskHandle();
    mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, this};
    // ...
}
murarduino commented 2 weeks ago

只有靜態方法可以存入C的函數指針 所以需要一個額外的參數存this void* user_ctx 通常可以用來達到此目的


class DCCDetector {
  static bool IRAM_ATTR DCCDetector::_capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t *edata, void *user_data);
...
  TaskHandle_t m_task = nullptr;
};

bool DCCDetector::_capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t *edata, void *user_data) {
  auto& self = *(DCCDetector*)user_data;
  TaskHandle_t cur_task = self.m_task;
  // do something
}

void DCCDetector::_detector_cap_init(int dcc_pin) {
  // ...
  // 註冊回調
  mcpwm_capture_event_callbacks_t cap_cb = {
      .on_cap = _capture_callback,
  };
  m_task = xTaskGetCurrentTaskHandle();
  mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, this};
  // ...
}

我在慢慢理解你给出建议。 我的改动如下: 在DCCDetector.h将回调函数定义为一个静态成员(是否回调函数必需是静态成员,我还不是很理解) static bool IRAM_ATTR _capture_callback(mcpwm_cap_channel_handle_t _cap_channel_handle, const mcpwm_capture_event_data_t *edata, void *user_data); 并在类中定义回调函数的句柄 TaskHandle_t cb_task= nullptr;

在回调函数内将user_data进行地址转换 auto &self = *(DCCDetector *)user_data; 但是为什么需要将成员变量(回调函数句柄)赋值给另任务钉句柄cur_task,我不是很理解。因为cur_task没有被其它部分调用。 TaskHandle_t cur_task = self.cb_task; 正巧在回调函数中还需要调用对象函数。所以 cap_begin_analog_value = self.analog_read();可以这样应用。对么?

void DCCDetector::_detector_cap_init(int dcc_pin) { ... mcpwm_capture_event_callbacks_t cap_cb = { .on_cap = _capture_callback, }; cb_task = xTaskGetCurrentTaskHandle(); mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, this); ... }

andylinpersonal commented 2 weeks ago

回调函数必需是静态成员,我还不是很理解

C++的官網有解釋成員函數指針和普通函數指針 及 靜態/非靜態成員函數的差異 https://isocpp.org/wiki/faq/pointers-to-members

GCC有提供轉換非靜態成員函數為普通函數指針的擴展,但此為non portable,clang等其他編譯器不支持,慎用之 https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

C++23後,使用explicit object parameter的成員函數也可取出函數指針,不過須待ESP-IDF使用的GCC升級到14+才可使用。當前的GCC13尚未支持此特性。

并在类中定义回调函数的句柄 (恕刪)...

TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, cur_task);//注册> > capture通道的事件回调

... 因为cur_task没有被其它部分调用。

因為看你的代碼有把任務句柄傳入回調 才增加新成員TaskHandle_t m_task;作為傳參使用 如果用不到可以刪去 這非必要😅

在回调函数中还需要调用对象函数。所以 cap_begin_analog_value = self.analog_read();可以这样应用。对么?

是的

此外,在github的issue頁面貼代碼時可以考慮使用此markdown語法啟用語法高亮 image

murarduino commented 2 weeks ago

回调函数必需是静态成员,我还不是很理解

C++的官網有解釋成員函數指針和普通函數指針 及 靜態/非靜態成員函數的差異 https://isocpp.org/wiki/faq/pointers-to-members

GCC有提供轉換非靜態成員函數為普通函數指針的擴展,但此為non portable,clang等其他編譯器不支持,慎用之 https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

C++23後,使用explicit object parameter的成員函數也可取出函數指針,不過須待ESP-IDF使用的GCC升級到14+才可使用。當前的GCC13尚未支持此特性。

并在类中定义回调函数的句柄 (恕刪)...

TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
mcpwm_capture_channel_register_event_callbacks(_cap_channel_handle, &cap_cb, cur_task);//注册> > capture通道的事件回调

... 因为cur_task没有被其它部分调用。

因為看你的代碼有把任務句柄傳入回調 才增加新成員TaskHandle_t m_task;作為傳參使用 如果用不到可以刪去 這非必要😅

在回调函数中还需要调用对象函数。所以 cap_begin_analog_value = self.analog_read();可以这样应用。对么?

是的

此外,在github的issue頁面貼代碼時可以考慮使用此markdown語法啟用語法高亮 image

谢谢您的指导。这个问题应该可以解决了。