Skip to content

Commit

Permalink
Merge branch 'feature/add_mcpwm_carrier_clk_source' into 'master'
Browse files Browse the repository at this point in the history
feat(MCPWM): Add mcpwm carrier clk source

Closes IDF-8029

See merge request espressif/esp-idf!25342
  • Loading branch information
Kainarx committed Aug 15, 2023
2 parents 5a81e00 + 97d5db3 commit b39fd79
Show file tree
Hide file tree
Showing 17 changed files with 124 additions and 35 deletions.
1 change: 1 addition & 0 deletions components/driver/mcpwm/include/driver/mcpwm_oper.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ esp_err_t mcpwm_operator_register_event_callbacks(mcpwm_oper_handle_t oper, cons
* @brief MCPWM carrier configuration structure
*/
typedef struct {
mcpwm_carrier_clock_source_t clk_src; /*!< MCPWM carrier clock source */
uint32_t frequency_hz; /*!< Carrier frequency in Hz */
uint32_t first_pulse_duration_us; /*!< The duration of the first PWM pulse, in us */
float duty_cycle; /*!< Carrier duty cycle */
Expand Down
11 changes: 8 additions & 3 deletions components/driver/mcpwm/mcpwm_oper.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,14 @@ esp_err_t mcpwm_operator_apply_carrier(mcpwm_oper_handle_t oper, const mcpwm_car
float real_duty = 0.0;

if (config && config->frequency_hz) {
uint8_t pre_scale = group->resolution_hz / 8 / config->frequency_hz;
mcpwm_ll_carrier_set_prescale(hal->dev, oper_id, pre_scale);
real_frequency = group->resolution_hz / 8 / pre_scale;
// select the clock source
mcpwm_carrier_clock_source_t clk_src = config->clk_src ? config->clk_src : MCPWM_CARRIER_CLK_SRC_DEFAULT;
ESP_RETURN_ON_ERROR(mcpwm_select_periph_clock(group, (soc_module_clk_t)clk_src), TAG, "set group clock failed");

uint8_t prescale = group->resolution_hz / 8 / config->frequency_hz;
ESP_RETURN_ON_FALSE(prescale > 0 && prescale <= MCPWM_LL_MAX_CARRIER_PRESCALE, ESP_ERR_INVALID_STATE, TAG, "group clock cannot match the frequency");
mcpwm_ll_carrier_set_prescale(hal->dev, oper_id, prescale);
real_frequency = group->resolution_hz / 8 / prescale;

uint8_t duty = (uint8_t)(config->duty_cycle * 8);
mcpwm_ll_carrier_set_duty(hal->dev, oper_id, duty);
Expand Down
1 change: 1 addition & 0 deletions components/driver/mcpwm/mcpwm_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle
mcpwm_hal_timer_reset(hal, timer_id);
// set timer resolution
uint32_t prescale = group->resolution_hz / config->resolution_hz;
ESP_RETURN_ON_FALSE(prescale > 0 && prescale <= MCPWM_LL_MAX_TIMER_PRESCALE, ESP_ERR_INVALID_STATE, TAG, "group clock cannot match the resolution");
mcpwm_ll_timer_set_clock_prescale(hal->dev, timer_id, prescale);
timer->resolution_hz = group->resolution_hz / prescale;
if (timer->resolution_hz != config->resolution_hz) {
Expand Down
41 changes: 14 additions & 27 deletions components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,59 +64,46 @@ TEST_CASE("mcpwm_operator_install_uninstall", "[mcpwm]")

TEST_CASE("mcpwm_operator_carrier", "[mcpwm]")
{
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 20000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));

mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t oper = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &oper));
TEST_ESP_OK(mcpwm_operator_connect_timer(oper, timer));

mcpwm_generator_config_t generator_config = {
.gen_gpio_num = 0,
};
mcpwm_gen_handle_t generator = NULL;
TEST_ESP_OK(mcpwm_new_generator(oper, &generator_config, &generator));

TEST_ESP_OK(mcpwm_generator_set_action_on_timer_event(generator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE)));

printf("add carrier to PWM wave\r\n");
mcpwm_carrier_config_t carrier_config = {
.clk_src = MCPWM_CARRIER_CLK_SRC_DEFAULT,
.frequency_hz = 1000000, // 1MHz carrier
.duty_cycle = 0.5,
.first_pulse_duration_us = 10,
};
TEST_ESP_OK(mcpwm_operator_apply_carrier(oper, &carrier_config));

TEST_ESP_OK(mcpwm_timer_enable(timer));

TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_operator_apply_carrier(oper, &carrier_config));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
vTaskDelay(pdMS_TO_TICKS(10));

printf("remove carrier from PWM wave\r\n");
carrier_config.frequency_hz = 0;
TEST_ESP_OK(mcpwm_operator_apply_carrier(oper, &carrier_config));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(200));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
vTaskDelay(pdMS_TO_TICKS(10));

TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(generator));
TEST_ESP_OK(mcpwm_del_operator(oper));
TEST_ESP_OK(mcpwm_del_timer(timer));
}

static bool IRAM_ATTR test_cbc_brake_on_gpio_fault_callback(mcpwm_oper_handle_t oper, const mcpwm_brake_event_data_t *edata, void *user_data)
Expand Down
2 changes: 2 additions & 0 deletions components/hal/esp32/include/hal/mcpwm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ extern "C" {
#define MCPWM_LL_EVENT_CAPTURE(cap) (1 << ((cap) + 27))

// Maximum values due to limited register bit width
#define MCPWM_LL_MAX_TIMER_PRESCALE 256
#define MCPWM_LL_MAX_CARRIER_PRESCALE 16
#define MCPWM_LL_MAX_CARRIER_ONESHOT 16
#define MCPWM_LL_MAX_CAPTURE_PRESCALE 256
#define MCPWM_LL_MAX_DEAD_DELAY 65536
Expand Down
2 changes: 2 additions & 0 deletions components/hal/esp32c6/include/hal/mcpwm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ extern "C" {
#define MCPWM_LL_EVENT_CAPTURE(cap) (1 << ((cap) + 27))

// Maximum values due to limited register bit width
#define MCPWM_LL_MAX_TIMER_PRESCALE 256
#define MCPWM_LL_MAX_CARRIER_PRESCALE 16
#define MCPWM_LL_MAX_CARRIER_ONESHOT 16
#define MCPWM_LL_MAX_CAPTURE_PRESCALE 256
#define MCPWM_LL_MAX_DEAD_DELAY 65536
Expand Down
2 changes: 2 additions & 0 deletions components/hal/esp32h2/include/hal/mcpwm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ extern "C" {
#define MCPWM_LL_EVENT_CAPTURE(cap) (1 << ((cap) + 27))

// Maximum values due to limited register bit width
#define MCPWM_LL_MAX_TIMER_PRESCALE 256
#define MCPWM_LL_MAX_CARRIER_PRESCALE 16
#define MCPWM_LL_MAX_CARRIER_ONESHOT 16
#define MCPWM_LL_MAX_CAPTURE_PRESCALE 256
#define MCPWM_LL_MAX_DEAD_DELAY 65536
Expand Down
2 changes: 2 additions & 0 deletions components/hal/esp32p4/include/hal/mcpwm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ extern "C" {
#define MCPWM_LL_EVENT_CAPTURE(cap) (1 << ((cap) + 27))

// Maximum values due to limited register bit width
#define MCPWM_LL_MAX_TIMER_PRESCALE 256
#define MCPWM_LL_MAX_CARRIER_PRESCALE 16
#define MCPWM_LL_MAX_CARRIER_ONESHOT 16
#define MCPWM_LL_MAX_CAPTURE_PRESCALE 256
#define MCPWM_LL_MAX_DEAD_DELAY 65536
Expand Down
2 changes: 2 additions & 0 deletions components/hal/esp32s3/include/hal/mcpwm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ extern "C" {
#define MCPWM_LL_EVENT_CAPTURE(cap) (1 << ((cap) + 27))

// Maximum values due to limited register bit width
#define MCPWM_LL_MAX_TIMER_PRESCALE 256
#define MCPWM_LL_MAX_CARRIER_PRESCALE 16
#define MCPWM_LL_MAX_CARRIER_ONESHOT 16
#define MCPWM_LL_MAX_CAPTURE_PRESCALE 256
#define MCPWM_LL_MAX_DEAD_DELAY 65536
Expand Down
9 changes: 9 additions & 0 deletions components/hal/include/hal/mcpwm_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ typedef soc_periph_mcpwm_capture_clk_src_t mcpwm_capture_clock_source_t;
typedef int mcpwm_capture_clock_source_t;
#endif // SOC_MCPWM_SUPPORTED

/**
* @brief MCPWM carrier clock source
*/
#if SOC_MCPWM_SUPPORTED
typedef soc_periph_mcpwm_carrier_clk_src_t mcpwm_carrier_clock_source_t;
#else
typedef int mcpwm_carrier_clock_source_t;
#endif // SOC_MCPWM_SUPPORTED

/**
* @brief MCPWM timer count direction
*/
Expand Down
13 changes: 13 additions & 0 deletions components/soc/esp32/include/soc/clk_tree_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,19 @@ typedef enum {
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB, /*!< Select APB as the default clock choice */
} soc_periph_mcpwm_capture_clk_src_t;

/**
* @brief Array initializer for all supported clock sources of MCPWM Carrier
*/
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_PLL_F160M}

/**
* @brief Type of MCPWM carrier clock source
*/
typedef enum {
MCPWM_CARRIER_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default clock choice */
} soc_periph_mcpwm_carrier_clk_src_t;

///////////////////////////////////////////////////I2S//////////////////////////////////////////////////////////////////
/**
* @brief Array initializer for all supported clock sources of I2S
Expand Down
14 changes: 14 additions & 0 deletions components/soc/esp32c6/include/soc/clk_tree_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,20 @@ typedef enum {
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default clock choice */
} soc_periph_mcpwm_capture_clk_src_t;

/**
* @brief Array initializer for all supported clock sources of MCPWM Carrier
*/
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_XTAL}

/**
* @brief Type of MCPWM carrier clock source
*/
typedef enum {
MCPWM_CARRIER_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_CARRIER_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default clock choice */
} soc_periph_mcpwm_carrier_clk_src_t;

///////////////////////////////////////////////////// I2S //////////////////////////////////////////////////////////////

/**
Expand Down
14 changes: 14 additions & 0 deletions components/soc/esp32h2/include/soc/clk_tree_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,20 @@ typedef enum {
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F96M, /*!< Select PLL_F96M as the default clock choice */
} soc_periph_mcpwm_capture_clk_src_t;

/**
* @brief Array initializer for all supported clock sources of MCPWM Carrier
*/
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_PLL_F96M, SOC_MOD_CLK_XTAL}

/**
* @brief Type of MCPWM carrier clock source
*/
typedef enum {
MCPWM_CARRIER_CLK_SRC_PLL96M = SOC_MOD_CLK_PLL_F96M, /*!< Select PLL_F96M as the source clock */
MCPWM_CARRIER_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F96M, /*!< Select PLL_F96M as the default clock choice */
} soc_periph_mcpwm_carrier_clk_src_t;

///////////////////////////////////////////////////// I2S //////////////////////////////////////////////////////////////

/**
Expand Down
30 changes: 25 additions & 5 deletions components/soc/esp32p4/include/soc/clk_tree_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ typedef enum {
* @brief Array initializer for all supported clock sources of MCPWM Timer
*/
#if SOC_CLK_TREE_SUPPORTED
#define SOC_MCPWM_TIMER_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_RC_FAST, SOC_MOD_CLK_XTAL}
#define SOC_MCPWM_TIMER_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_XTAL}
#else
#define SOC_MCPWM_TIMER_CLKS {SOC_MOD_CLK_XTAL}
#endif
Expand All @@ -242,7 +242,6 @@ typedef enum {
*/
typedef enum {
MCPWM_TIMER_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_TIMER_CLK_SRC_RC_FAST = SOC_MOD_CLK_RC_FAST, /*!< Select RC_FAST as the source clock */
MCPWM_TIMER_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
#if SOC_CLK_TREE_SUPPORTED
MCPWM_TIMER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
Expand All @@ -255,7 +254,7 @@ typedef enum {
* @brief Array initializer for all supported clock sources of MCPWM Capture Timer
*/
#if SOC_CLK_TREE_SUPPORTED
#define SOC_MCPWM_CAPTURE_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_RC_FAST, SOC_MOD_CLK_XTAL}
#define SOC_MCPWM_CAPTURE_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_XTAL}
#else
#define SOC_MCPWM_CAPTURE_CLKS {SOC_MOD_CLK_XTAL}
#endif
Expand All @@ -265,15 +264,36 @@ typedef enum {
*/
typedef enum {
MCPWM_CAPTURE_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_CAPTURE_CLK_SRC_RC_FAST = SOC_MOD_CLK_RC_FAST, /*!< Select RC_FAST as the source clock */
MCPWM_CAPTURE_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
#if SOC_CLK_TREE_SUPPORTED
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
#else
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the default choice */
#endif // SOC_CLK_TREE_SUPPORTED
} soc_periph_mcpwm_capture_clk_src_t;

/**
* @brief Array initializer for all supported clock sources of MCPWM Carrier
*/
#if SOC_CLK_TREE_SUPPORTED
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_XTAL}
#else
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_XTAL}
#endif

/**
* @brief Type of MCPWM carrier clock source
*/
typedef enum {
MCPWM_CARRIER_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_CARRIER_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
#if SOC_CLK_TREE_SUPPORTED
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
#else
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the default choice */
#endif // SOC_CLK_TREE_SUPPORTED
} soc_periph_mcpwm_carrier_clk_src_t;

///////////////////////////////////////////////// I2S //////////////////////////////////////////////////////////////

/////////////////////////////////////////////////I2C////////////////////////////////////////////////////////////////////
Expand Down
13 changes: 13 additions & 0 deletions components/soc/esp32s3/include/soc/clk_tree_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,19 @@ typedef enum {
MCPWM_CAPTURE_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB, /*!< Select APB as the default clock choice */
} soc_periph_mcpwm_capture_clk_src_t;

/**
* @brief Array initializer for all supported clock sources of MCPWM Carrier
*/
#define SOC_MCPWM_CARRIER_CLKS {SOC_MOD_CLK_PLL_F160M}

/**
* @brief Type of MCPWM carrier clock source
*/
typedef enum {
MCPWM_CARRIER_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
MCPWM_CARRIER_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default clock choice */
} soc_periph_mcpwm_carrier_clk_src_t;

///////////////////////////////////////////////////// I2S //////////////////////////////////////////////////////////////

/**
Expand Down
1 change: 1 addition & 0 deletions docs/en/api-reference/peripherals/mcpwm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ The MCPWM operator has a carrier submodule that can be used if galvanic isolatio

To configure the carrier submodule, you can call :cpp:func:`mcpwm_operator_apply_carrier`, and provide configuration structure :cpp:type:`mcpwm_carrier_config_t`:

- :cpp:member:`mcpwm_carrier_config_t::clk_src` sets the clock source of the carrier.
- :cpp:member:`mcpwm_carrier_config_t::frequency_hz` indicates carrier frequency in Hz.
- :cpp:member:`mcpwm_carrier_config_t::duty_cycle` indicates the duty cycle of the carrier. Note that, the supported choices of the duty cycle are discrete, the driver will search for the nearest one based on your configuration.
- :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us` indicates the duration of the first pulse in microseconds. The resolution of the first pulse duration is determined by the carrier frequency you set in the :cpp:member:`mcpwm_carrier_config_t::frequency_hz`. The first pulse duration can't be zero, and it has to be at least one period of the carrier. A longer pulse width can help conduct the inductance quicker.
Expand Down
1 change: 1 addition & 0 deletions docs/zh_CN/api-reference/peripherals/mcpwm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ MCPWM 操作器具有载波子模块,可以根据需要(例如隔离式数

调用 :cpp:func:`mcpwm_operator_apply_carrier`,并提供配置结构体 :cpp:type:`mcpwm_carrier_config_t`,配置载波子模块:

- :cpp:member:`mcpwm_carrier_config_t::clk_src` 设置载波的时钟源。
- :cpp:member:`mcpwm_carrier_config_t::frequency_hz` 表示载波频率,单位为赫兹。
- :cpp:member:`mcpwm_carrier_config_t::duty_cycle` 表示载波的占空比。需注意,支持的占空比选项并不连续,驱动程序将根据配置查找最接近的占空比。
- :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us` 表示第一个脉冲的脉宽,单位为微秒。该脉冲的分辨率由 :cpp:member:`mcpwm_carrier_config_t::frequency_hz` 中的配置决定。第一个脉冲的脉宽不能为零,且至少为一个载波周期。脉宽越长,电感传导越快。
Expand Down

0 comments on commit b39fd79

Please sign in to comment.