From b7a901951e51e7333b4f62375480ff085d443ca7 Mon Sep 17 00:00:00 2001 From: wanlei Date: Sat, 6 May 2023 19:22:51 +0800 Subject: [PATCH] mcpwm: add foc svpwm generation open loop example --- .../mcpwm_foc_svpwm_open_loop/CMakeLists.txt | 8 + .../mcpwm/mcpwm_foc_svpwm_open_loop/README.md | 107 ++++++++++++ .../mcpwm_foc_svpwm_open_loop/img/6pwm.png | Bin 0 -> 14138 bytes .../img/line_diff.png | Bin 0 -> 14174 bytes .../img/phase_wave.png | Bin 0 -> 11734 bytes .../img/vector_coord.png | Bin 0 -> 11996 bytes .../main/CMakeLists.txt | 5 + .../main/Kconfig.projbuild | 12 ++ .../mcpwm_foc_svpwm_open_loop/main/app_main.c | 165 ++++++++++++++++++ .../main/foc/esp_foc.c | 152 ++++++++++++++++ .../main/foc/esp_foc.h | 73 ++++++++ .../main/idf_component.yml | 6 + .../main/svpwm/esp_svpwm.c | 113 ++++++++++++ .../main/svpwm/esp_svpwm.h | 80 +++++++++ .../pytest_foc_open_loop.py | 18 ++ 15 files changed, 739 insertions(+) create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/CMakeLists.txt create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/README.md create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/6pwm.png create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/line_diff.png create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/phase_wave.png create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/vector_coord.png create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/CMakeLists.txt create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/Kconfig.projbuild create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/app_main.c create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.c create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.h create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/idf_component.yml create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.c create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.h create mode 100644 examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/pytest_foc_open_loop.py diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/CMakeLists.txt b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/CMakeLists.txt new file mode 100644 index 0000000000..300e9dc2a4 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mcpwm_foc_svpwm_generate) diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/README.md b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/README.md new file mode 100644 index 0000000000..1be28172d0 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/README.md @@ -0,0 +1,107 @@ +| Supported Targets | ESP32 | ESP32-C6 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# MCPWM FOC SVPWM Generation Open Loop Example + +## Principle +This example shows how to use three pairs of PWM signals to realize FOC (Field-Oriented Control) via the MCPWM peripheral. With the internal dead time submodule, each pair of signals can drive one half-bridge circuit, so that they can be combined to drive a BLDC or PMSM motor, or a three-phase power inverter. + +This example realizes an open-loop FOC algorithm to calculate the target voltages for three phases, and then modulates these voltages into three pairs of PWM signals to form the SVPWM signal (or SPWM if build with menu config `ESP_FOC_USE_SVPWM` off), and finally inputs this SVPWM into six power MOSFETs to get the three-phase power system. +``` + ┌─────────┐ ┌───────┐ Va + Vq │ │ Valpha │ ├──────► + ──────►│ Inverse ├───────►│ │ + │ │ │ SVPWM │ Vb + Vd │ │ Vbeta │ ├──────► + ──────►│ Park ├───────►│ │ Vc + │ │ │ ├──────► + └────▲────┘ └───────┘ + │ + │theta +``` +The FOC sectors assignment and coord systems are as follow: + + +### Risks +These three-phase sine signals are generated at 50Hz by **open loop FOC**, please set a proper power supply, or don't let it work for a long time to avoid any potential **crash or damages** when using it to drive the motor. + +## How to Use + +### Hardware Required + +1. An ESP board with MCPWM peripheral supported (e.g. ESP32-S3-Motor-DevKit) +2. A three-phase gate driver, for example, the [DRV8302](https://www.ti.com.cn/product/zh-cn/DRV8302) +3. Six N-MOSFETs, for example, the [IRF540NS](https://www.infineon.com/cms/en/product/power/mosfet/12v-300v-n-channel-power-mosfet/irf540ns/) +4. A USB cable for Power supply and programming + +### Connection +Using only `delta/triangle` connect to the output. +``` + POWER + │ + ┌────────────────────────┐ ┌───────▼───────┐ + │ Enable│ │ │ + │ │ │ │ R1 + │ EXAMPLE_FOC_PWM_UH_GPIO├───────────┤ Bridge Driver │_________┌────┐____ + │ │ │ │ └────┘ | + │ EXAMPLE_FOC_PWM_UL_GPIO├───────────┤ │ | + │ │ │ and │ R2 |neutral line + │ EXAMPLE_FOC_PWM_VH_GPIO├───────────┤ │_________┌────┐____| + │ │ │ │ └────┘ | + │ EXAMPLE_FOC_PWM_VL_GPIO├───────────┤ MOSFET │ | + │ │ │ │ R3 | + │ EXAMPLE_FOC_PWM_WH_GPIO├───────────┤ │_________┌────┐____| + │ │ │ │ └────┘ + │ EXAMPLE_FOC_PWM_WL_GPIO├───────────┤ │ + └────────────────────────┘ └───────────────┘ +``` + + +### Build and Flash + +Select project Kconfig option `ESP_FOC_USE_SVPWM` by `idf.py menuconfig`: +- True: Using SVPWM modem, output saddle wave, the neutral point level is not const zero. +- False: Using SPWM modem, output standard sin wave, phases and lines level are all sin wave. + +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 + +Run the example, you will see the following output log: +``` +... +Hi ESP_SV +I (327) bsp_mcpwm: Disable MOSFET gate +I (337) gpio: GPIO[46]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (347) bsp_mcpwm: Create MCPWM timer +I (347) bsp_mcpwm: Create MCPWM operator +I (357) bsp_mcpwm: Connect operators to the same timer +I (357) bsp_mcpwm: Create comparators +I (367) bsp_mcpwm: Create PWM generators +I (367) gpio: GPIO[47]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (377) gpio: GPIO[21]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (387) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (397) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (407) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (417) gpio: GPIO[11]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (427) bsp_mcpwm: Set generator actions +I (427) bsp_mcpwm: Setup deadtime +I (437) bsp_mcpwm: Start the MCPWM timer +I (437) bsp_mcpwm: Enable MOSFET gate +``` + +If you have an oscilloscope or a logic analyzer, you can see there are 7 segments in one PWM signal and all six PWM signals are center-aligned: + + +And if you monitor any output after the low-pass filter with the reference of the GROUND, there will be a saddle wave (or sin wave if build with the menu config `ESP_FOC_USE_SVPWM` off), two of them are as follow: + + +Do not surprise if you find it is not a `sin wave` while taking the GROUND as the reference, it is the characteristic of `SVPWM` modulation, because the voltage of `neutral line` is not zero for `SVPWM`. But if you monitor any `line-to-line` voltage (taking another signal as the reference) in this three-phase power system, you can still get the sin signal: + + +Please turn off config `ESP_FOC_USE_SVPWM` if the `neutral line` is required, then there will be a standard three-phase sin system. diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/6pwm.png b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/6pwm.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a1858b1f4d4e715deb9de80f4529bd5f2585a2 GIT binary patch literal 14138 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 literal 0 HcmV?d00001 diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/vector_coord.png b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/img/vector_coord.png new file mode 100644 index 0000000000000000000000000000000000000000..1514716fa7c79572025ced32fc25a8f99f7e9d85 GIT binary patch literal 11996 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 literal 0 HcmV?d00001 diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/CMakeLists.txt b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/CMakeLists.txt new file mode 100644 index 0000000000..2cb3a1d20b --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS app_main.c svpwm/esp_svpwm.c foc/esp_foc.c + INCLUDE_DIRS "svpwm" "foc" + REQUIRES driver +) diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/Kconfig.projbuild b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/Kconfig.projbuild new file mode 100644 index 0000000000..39f7777ee3 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/Kconfig.projbuild @@ -0,0 +1,12 @@ +menu "ESP FOC Settings" + + config ESP_FOC_USE_SVPWM + bool "Use SVPWM modulation, if not, use SPWM" + default y + help + SVPWM will output saddle wave, which have higher power usage ratio, + But not follow 'sin signal' on phase voltage, and neutral line not zero. + + SPWM output a standard 3-phase sin signal modulated pwm, but not fully use the + input power. Please turn off this config if the `neutral line` is required. +endmenu diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/app_main.c b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/app_main.c new file mode 100644 index 0000000000..1e6f14e5df --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/app_main.c @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "driver/gpio.h" +#include "foc/esp_foc.h" +#include "svpwm/esp_svpwm.h" + +static const char *TAG = "example_foc"; + +#if CONFIG_IDF_TARGET_ESP32 +#define EXAMPLE_FOC_DRV_EN_GPIO 4 +#define EXAMPLE_FOC_DRV_FAULT_GPIO 5 +#define EXAMPLE_FOC_PWM_UH_GPIO 12 +#define EXAMPLE_FOC_PWM_UL_GPIO 13 +#define EXAMPLE_FOC_PWM_VH_GPIO 14 +#define EXAMPLE_FOC_PWM_VL_GPIO 15 +#define EXAMPLE_FOC_PWM_WH_GPIO 16 +#define EXAMPLE_FOC_PWM_WL_GPIO 17 + +#elif CONFIG_IDF_TARGET_ESP32S3 +#define EXAMPLE_FOC_DRV_EN_GPIO 46 +#define EXAMPLE_FOC_DRV_FAULT_GPIO 10 +#define EXAMPLE_FOC_PWM_UH_GPIO 47 +#define EXAMPLE_FOC_PWM_UL_GPIO 21 +#define EXAMPLE_FOC_PWM_VH_GPIO 14 +#define EXAMPLE_FOC_PWM_VL_GPIO 13 +#define EXAMPLE_FOC_PWM_WH_GPIO 12 +#define EXAMPLE_FOC_PWM_WL_GPIO 11 + +#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 +#define EXAMPLE_FOC_DRV_EN_GPIO 1 +#define EXAMPLE_FOC_DRV_FAULT_GPIO 2 +#define EXAMPLE_FOC_PWM_UH_GPIO 3 +#define EXAMPLE_FOC_PWM_UL_GPIO 4 +#define EXAMPLE_FOC_PWM_VH_GPIO 5 +#define EXAMPLE_FOC_PWM_VL_GPIO 10 +#define EXAMPLE_FOC_PWM_WH_GPIO 11 +#define EXAMPLE_FOC_PWM_WL_GPIO 13 +#endif + +#define EXAMPLE_FOC_MCPWM_TIMER_RESOLUTION_HZ 10000000 // 10MHz, 1 tick = 0.1us +#define EXAMPLE_FOC_MCPWM_PERIOD 1000 // 1000 * 0.1us = 100us, 10KHz + +#define EXAMPLE_FOC_WAVE_FREQ 10 // 50Hz 3 phase AC wave +#define EXAMPLE_FOC_WAVE_AMPL 100 // Wave amplitude, Use up-down timer mode, max value should be (EXAMPLE_FOC_MCPWM_PERIOD/2) + + +void bsp_bridge_driver_init(void) +{ + gpio_config_t drv_en_config = { + .pin_bit_mask = 1ULL << EXAMPLE_FOC_DRV_EN_GPIO, + .mode = GPIO_MODE_OUTPUT, + }; + ESP_ERROR_CHECK(gpio_config(&drv_en_config)); +} + +void bsp_bridge_driver_enable(bool enable) +{ + ESP_LOGI(TAG, "%s MOSFET gate", enable ? "Enable" : "Disable"); + gpio_set_level(EXAMPLE_FOC_DRV_EN_GPIO, enable); +} + +bool inverter_update_cb(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_ctx) +{ + BaseType_t task_yield = pdFALSE; + xSemaphoreGiveFromISR(*((SemaphoreHandle_t *)user_ctx), &task_yield); + return task_yield; +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Hello FOC"); + // counting semaphore used to sync update foc calculation when mcpwm timer updated + SemaphoreHandle_t update_semaphore = xSemaphoreCreateCounting(1, 0); + + foc_dq_coord_t dq_out = {_IQ(0), _IQ(0)}; + foc_ab_coord_t ab_out; + foc_uvw_coord_t uvw_out; + int uvw_duty[3]; + float elec_theta_deg = 0; + _iq elec_theta_rad; + + inverter_config_t cfg = { + .timer_config = { + .group_id = 0, + .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, + .resolution_hz = EXAMPLE_FOC_MCPWM_TIMER_RESOLUTION_HZ, + .count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN, //UP_DOWN mode will generate center align pwm wave, which can reduce MOSFET switch times on same effect, extend life + .period_ticks = EXAMPLE_FOC_MCPWM_PERIOD, + }, + .operator_config = { + .group_id = 0, + }, + .compare_config = { + .flags.update_cmp_on_tez = true, + }, + .gen_gpios = { + {EXAMPLE_FOC_PWM_UH_GPIO, EXAMPLE_FOC_PWM_UL_GPIO}, + {EXAMPLE_FOC_PWM_VH_GPIO, EXAMPLE_FOC_PWM_VL_GPIO}, + {EXAMPLE_FOC_PWM_WH_GPIO, EXAMPLE_FOC_PWM_WL_GPIO}, + }, + .dt_config = { + .posedge_delay_ticks = 5, + }, + .inv_dt_config = { + .negedge_delay_ticks = 5, + .flags.invert_output = true, + }, + }; + inverter_handle_t inverter1; + ESP_ERROR_CHECK(svpwm_new_inverter(&cfg, &inverter1)); + ESP_LOGI(TAG, "Inverter init OK"); + + mcpwm_timer_event_callbacks_t cbs = { + .on_full = inverter_update_cb, + }; + ESP_ERROR_CHECK(svpwm_inverter_register_cbs(inverter1, &cbs, &update_semaphore)); + ESP_ERROR_CHECK(svpwm_inverter_start(inverter1, MCPWM_TIMER_START_NO_STOP)); + ESP_LOGI(TAG, "Inverter start OK"); + + // Enable gate driver chip + bsp_bridge_driver_init(); + bsp_bridge_driver_enable(true); + + ESP_LOGI(TAG, "Start FOC"); + while (true) { + xSemaphoreTake(update_semaphore, portMAX_DELAY); + + // Calculate elec_theta_deg increase step of 50Hz output on 10000Hz call + elec_theta_deg += (EXAMPLE_FOC_WAVE_AMPL * 360.f) / (EXAMPLE_FOC_MCPWM_TIMER_RESOLUTION_HZ / EXAMPLE_FOC_WAVE_FREQ); + if (elec_theta_deg > 360) { + elec_theta_deg -= 360; + } + elec_theta_rad = _IQmpy(_IQ(elec_theta_deg), _IQ(M_PI / 180.f)); + + // In FOC motor control, we usually set Vd for alignment or weak-meg control, and set Vq for torque control. + // As here is open loop output, use Vd is enough, and coord aligned + dq_out.d = _IQ(EXAMPLE_FOC_WAVE_AMPL); + foc_inverse_park_transform(elec_theta_rad, &dq_out, &ab_out); + +#if CONFIG_ESP_FOC_USE_SVPWM + foc_svpwm_duty_calculate(&ab_out, &uvw_out); +#else // Use spwm (sin pwm) instead. (see menuconfig help to know difference between SVPWM and SPWM) + foc_inverse_clarke_transform(&ab_out, &uvw_out); +#endif + // Regular uvw data to (0 ~ (EXAMPLE_FOC_MCPWM_PERIOD/2)) + uvw_duty[0] = _IQtoF(_IQdiv2(uvw_out.u)) + (EXAMPLE_FOC_MCPWM_PERIOD / 4); + uvw_duty[1] = _IQtoF(_IQdiv2(uvw_out.v)) + (EXAMPLE_FOC_MCPWM_PERIOD / 4); + uvw_duty[2] = _IQtoF(_IQdiv2(uvw_out.w)) + (EXAMPLE_FOC_MCPWM_PERIOD / 4); + + // output pwm duty + ESP_ERROR_CHECK(svpwm_inverter_set_duty(inverter1, uvw_duty[0], uvw_duty[1], uvw_duty[2])); + } + + bsp_bridge_driver_enable(false); + ESP_ERROR_CHECK(svpwm_inverter_start(inverter1, MCPWM_TIMER_STOP_EMPTY)); + ESP_ERROR_CHECK(svpwm_del_inverter(inverter1)); +} diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.c b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.c new file mode 100644 index 0000000000..10ce368440 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.c @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_foc.h" + + +/** + * alpha = u - (v + w)sin(30) * (2/3), (2/3): Equal amplitude transformation const + * beta = (v - w)cos(30) * (2/3) + */ +void foc_clarke_transform (const foc_uvw_coord_t *v_uvw, foc_ab_coord_t *v_ab) +{ + const _iq foc_clark_k1_iq = _IQ(2.0 / 3.0); + const _iq foc_clark_k2_iq = _IQ(1.0 / 3.0); + const _iq foc_clark_k3_iq = _IQ(M_SQRT3 / 3.0); + + v_ab->alpha = _IQmpy(v_uvw->u, foc_clark_k1_iq) - _IQmpy(v_uvw->v + v_uvw->w, foc_clark_k2_iq); + v_ab->beta = _IQmpy(v_uvw->v - v_uvw->w, foc_clark_k3_iq); +} + +void foc_inverse_clarke_transform (const foc_ab_coord_t *v_ab, foc_uvw_coord_t *v_uvw) +{ + v_uvw->u = v_ab->alpha; + v_uvw->v = _IQdiv2(_IQmpy(v_ab->beta, _IQ(M_SQRT3)) - v_ab->alpha); + v_uvw->w = -v_uvw->u - v_uvw->v; +} + +void foc_park_transform (_iq theta_rad, const foc_ab_coord_t *v_ab, foc_dq_coord_t *v_dq) +{ + _iq sin = _IQsin(theta_rad); + _iq cos = _IQcos(theta_rad); + + v_dq->d = _IQmpy(v_ab->alpha, cos) + _IQmpy(v_ab->beta, sin); + v_dq->q = _IQmpy(v_ab->beta, cos) - _IQmpy(v_ab->alpha, sin); +} + +void foc_inverse_park_transform (_iq theta_rad, const foc_dq_coord_t *v_dq, foc_ab_coord_t *v_ab) +{ + _iq sin = _IQsin(theta_rad); + _iq cos = _IQcos(theta_rad); + + v_ab->alpha = _IQmpy(v_dq->d, cos) - _IQmpy(v_dq->q, sin); + v_ab->beta = _IQmpy(v_dq->q, cos) + _IQmpy(v_dq->d, sin); +} + +void foc_svpwm_duty_calculate(const foc_ab_coord_t *v_ab, foc_uvw_coord_t *out_uvw) +{ + int sextant; + if (v_ab->beta > 0.0f) { + if (v_ab->alpha > 0.0f) { + //quadrant I + if (v_ab->beta > _IQmpy(v_ab->alpha, _IQ(M_SQRT3))) { + sextant = 2; //sextant v2-v3 + } else { + sextant = 1; //sextant v1-v2 + } + } else { + //quadrant II + if (-v_ab->beta > _IQmpy(v_ab->alpha, _IQ(M_SQRT3))) { + sextant = 3; //sextant v3-v4 + } else { + sextant = 2; //sextant v2-v3 + } + } + } else { + if (v_ab->alpha > 0.0f) { + //quadrant IV + if (-v_ab->beta > _IQmpy(v_ab->alpha, _IQ(M_SQRT3))) { + sextant = 5; //sextant v5-v6 + } else { + sextant = 6; //sextant v6-v1 + } + } else { + //quadrant III + if (v_ab->beta > _IQmpy(v_ab->alpha, _IQ(M_SQRT3))) { + sextant = 4; //sextant v4-v5 + } else { + sextant = 5; //sextant v5-v6 + } + } + } + + switch (sextant) { + // sextant v1-v2 + case 1: { + _iq t1 = -_IQmpy(v_ab->alpha, _IQ(M_SQRT3)) + v_ab->beta; + _iq t2 = -_IQmpy2(v_ab->beta); + + // PWM timings + out_uvw->u = _IQdiv2(_IQ(1.F) - t1 - t2); + out_uvw->v = out_uvw->u + t1; + out_uvw->w = out_uvw->v + t2; + } break; + + // sextant v2-v3 + case 2: { + _iq t2 = -_IQmpy(v_ab->alpha, _IQ(M_SQRT3)) - v_ab->beta; + _iq t3 = _IQmpy(v_ab->alpha, _IQ(M_SQRT3)) - v_ab->beta; + + // PWM timings + out_uvw->v = _IQdiv2(_IQ(1.F) - t2 - t3); + out_uvw->u = out_uvw->v + t3; + out_uvw->w = out_uvw->u + t2; + } break; + + // sextant v3-v4 + case 3: { + _iq t3 = -_IQmpy2(v_ab->beta); + _iq t4 = _IQmpy(v_ab->alpha, _IQ(M_SQRT3)) + v_ab->beta; + + // PWM timings + out_uvw->v = _IQdiv2(_IQ(1.F) - t3 - t4); + out_uvw->w = out_uvw->v + t3; + out_uvw->u = out_uvw->w + t4; + } break; + + // sextant v4-v5 + case 4: { + _iq t4 = _IQmpy(v_ab->alpha, _IQ(M_SQRT3)) - v_ab->beta; + _iq t5 = _IQmpy2(v_ab->beta); + + // PWM timings + out_uvw->w = _IQdiv2(_IQ(1.F) - t4 - t5); + out_uvw->v = out_uvw->w + t5; + out_uvw->u = out_uvw->v + t4; + } break; + + // sextant v5-v6 + case 5: { + _iq t5 = _IQmpy(v_ab->alpha, _IQ(M_SQRT3)) + v_ab->beta; + _iq t6 = -_IQmpy(v_ab->alpha, _IQ(M_SQRT3)) + v_ab->beta; + + // PWM timings + out_uvw->w = _IQdiv2(_IQ(1.F) - t5 - t6); + out_uvw->u = out_uvw->w + t5; + out_uvw->v = out_uvw->u + t6; + } break; + + // sextant v6-v1 + case 6: { + _iq t6 = _IQmpy2(v_ab->beta); + _iq t1 = -_IQmpy(v_ab->alpha, _IQ(M_SQRT3)) - v_ab->beta; + + // PWM timings + out_uvw->u = _IQdiv2(_IQ(1.F) - t6 - t1); + out_uvw->w = out_uvw->u + t1; + out_uvw->v = out_uvw->w + t6; + } break; + } +} diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.h b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.h new file mode 100644 index 0000000000..c478a80d66 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/foc/esp_foc.h @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +// Use IQ18 type, range [-8,192 8,191.999 996 185] +// This definition should be added before including "IQmathLib.h" +#define GLOBAL_IQ 18 +#include "IQmathLib.h" + + +// 3-phase uvw coord data type +typedef struct foc_uvw_coord { + _iq u; // U phase data in IQ type + _iq v; // V phase data in IQ type + _iq w; // W phase data in IQ type +} foc_uvw_coord_t; + +// alpha-beta axis static coord data type +typedef struct foc_ab_coord { + _iq alpha; // alpha axis data in IQ type + _iq beta; // beta axis data in IQ type +} foc_ab_coord_t; + +//d-q (direct-quadrature) axis rotate coord data type +typedef struct foc_dq_coord { + _iq d; // direct axis data in IQ type + _iq q; // quadrature axis data in IQ type +} foc_dq_coord_t; + +/** + * @brief clark transform, to transform value in 3phase uvw system to static alpha_beta system + * + * @param[in] v_uvw data in 3-phase coord to be transformed + * @param[out] v_ab output data in alpha-beta coord + */ +void foc_clarke_transform (const foc_uvw_coord_t *v_uvw, foc_ab_coord_t *v_ab); + +/** + * @brief inverse clark transform, to transform value in alpha_beta system to 3phase uvw system + * + * @param[in] v_ab data in alpha-beta coord to be transformed + * @param[out] v_uvw output data in 3-phase coord + */ +void foc_inverse_clarke_transform (const foc_ab_coord_t *v_ab, foc_uvw_coord_t *v_uvw); + +/** + * @brief park transform, to transform value in static alpha_beta system to rotate d-q system + * + * @param[in] theta_rad theta of dq_coord refer to alpha-beta coord, in rad + * @param[in] v_ab data in alpha-beta coord to be transformed + * @param[out] v_dq output data in dq coord + */ +void foc_park_transform (_iq theta_rad, const foc_ab_coord_t *v_ab, foc_dq_coord_t *v_dq); + +/** + * @brief inverse park transform, to transform value in rotate d-q system to alpha_beta system + * + * @param[in] theta_rad theta of dq_coord refer to alpha-beta coord, in rad + * @param[in] v_dq data in dq coord to be transformed + * @param[out] v_ab output data in alpha-beta coord + */ +void foc_inverse_park_transform (_iq theta_rad, const foc_dq_coord_t *v_dq, foc_ab_coord_t *v_ab); + +/** + * @brief 7-segment svpwm modulation + * + * @param v_ab[in] input value in alpha-beta coord + * @param out_uvw[out] output modulated pwm duty in IQ type + */ +void foc_svpwm_duty_calculate (const foc_ab_coord_t *v_ab, foc_uvw_coord_t *out_uvw); diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/idf_component.yml b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/idf_component.yml new file mode 100644 index 0000000000..086ad7d5ab --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/iqmath: "^1.11.0" + ## Required IDF version + idf: + version: ">=5.0" diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.c b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.c new file mode 100644 index 0000000000..f05a1643f3 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_check.h" +#include "esp_svpwm.h" + +static const char *TAG = "esp_svpwm"; + +// mcpwm handler type +typedef struct mcpwm_svpwm_ctx { + mcpwm_timer_handle_t timer; + mcpwm_oper_handle_t operators[3]; + mcpwm_cmpr_handle_t comparators[3]; + mcpwm_gen_handle_t generators[3][2]; +} mcpwm_svpwm_ctx_t; + + +esp_err_t svpwm_new_inverter(const inverter_config_t *config, inverter_handle_t *ret_inverter) +{ + esp_err_t ret; + ESP_RETURN_ON_FALSE(config && ret_inverter, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + mcpwm_svpwm_ctx_t *svpwm_dev = calloc(1, sizeof(mcpwm_svpwm_ctx_t)); + if (!svpwm_dev) { + ESP_LOGE(TAG, "no memory"); + return ESP_ERR_NO_MEM; + } + ESP_GOTO_ON_ERROR(mcpwm_new_timer(&config->timer_config, &svpwm_dev->timer), err, TAG, "Create MCPWM timer failed"); + + for (int i = 0; i < 3; i++) { + ESP_GOTO_ON_ERROR(mcpwm_new_operator(&config->operator_config, &svpwm_dev->operators[i]), err, TAG, "Create MCPWM operator failed"); + ESP_GOTO_ON_ERROR(mcpwm_operator_connect_timer(svpwm_dev->operators[i], svpwm_dev->timer), err, TAG, "Connect operators to the same timer failed"); + } + + for (int i = 0; i < 3; i++) { + ESP_GOTO_ON_ERROR(mcpwm_new_comparator(svpwm_dev->operators[i], &config->compare_config, &svpwm_dev->comparators[i]), err, TAG, "Create comparators failed"); + ESP_GOTO_ON_ERROR(mcpwm_comparator_set_compare_value(svpwm_dev->comparators[i], 0), err, TAG, "Set comparators failed"); + } + + mcpwm_generator_config_t gen_config = {}; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 2; j++) { + gen_config.gen_gpio_num = config->gen_gpios[i][j]; + ESP_GOTO_ON_ERROR(mcpwm_new_generator(svpwm_dev->operators[i], &gen_config, &svpwm_dev->generators[i][j]), err, TAG, "Create PWM generator pin %d failed", gen_config.gen_gpio_num); + } + } + + for (int i = 0; i < 3; i++) { + ESP_GOTO_ON_ERROR(mcpwm_generator_set_actions_on_compare_event(svpwm_dev->generators[i][0], + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, svpwm_dev->comparators[i], MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, svpwm_dev->comparators[i], MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION_END()), err, TAG, "Set generator actions failed"); + } + + for (int i = 0; i < 3; i++) { + ESP_GOTO_ON_ERROR(mcpwm_generator_set_dead_time(svpwm_dev->generators[i][0], svpwm_dev->generators[i][0], &config->dt_config), err, TAG, "Setup deadtime failed"); + ESP_GOTO_ON_ERROR(mcpwm_generator_set_dead_time(svpwm_dev->generators[i][0], svpwm_dev->generators[i][1], &config->inv_dt_config), err, TAG, "Setup inv deadtime failed"); + } + + *ret_inverter = svpwm_dev; + return ESP_OK; + +err: + free(svpwm_dev); + return ret; +} + +esp_err_t svpwm_inverter_register_cbs(inverter_handle_t handle, const mcpwm_timer_event_callbacks_t *event, void *user_ctx) +{ + ESP_RETURN_ON_FALSE(handle && event, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_ERROR(mcpwm_timer_register_event_callbacks(handle->timer, event, user_ctx), TAG, "register callbacks failed"); + return ESP_OK; +} + +esp_err_t svpwm_inverter_start(inverter_handle_t handle, mcpwm_timer_start_stop_cmd_t command) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + if ((command != MCPWM_TIMER_STOP_EMPTY) && (command != MCPWM_TIMER_STOP_FULL)) { + ESP_RETURN_ON_ERROR(mcpwm_timer_enable(handle->timer), TAG, "mcpwm timer enable failed"); + } + ESP_RETURN_ON_ERROR(mcpwm_timer_start_stop(handle->timer, command), TAG, "mcpwm timer start failed"); + return ESP_OK; +} + +esp_err_t svpwm_inverter_set_duty(inverter_handle_t handle, uint16_t u, uint16_t v, uint16_t w) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(handle->comparators[0], u), TAG, "set duty failed"); + ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(handle->comparators[1], v), TAG, "set duty failed"); + ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(handle->comparators[2], w), TAG, "set duty failed"); + return ESP_OK; +} + +esp_err_t svpwm_del_inverter(inverter_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + ESP_RETURN_ON_ERROR(mcpwm_timer_disable(handle->timer), TAG, "mcpwm timer disable failed"); + for (int i = 0; i < 3; i++) { + ESP_RETURN_ON_ERROR(mcpwm_del_generator(handle->generators[i][0]), TAG, "free mcpwm positive generator failed"); + ESP_RETURN_ON_ERROR(mcpwm_del_generator(handle->generators[i][1]), TAG, "free mcpwm negative generator failed"); + ESP_RETURN_ON_ERROR(mcpwm_del_comparator(handle->comparators[i]), TAG, "free mcpwm comparator failed"); + ESP_RETURN_ON_ERROR(mcpwm_del_operator(handle->operators[i]), TAG, "free mcpwm operator failed"); + } + ESP_RETURN_ON_ERROR(mcpwm_del_timer(handle->timer), TAG, "free mcpwm timer failed"); + free(handle); + return ESP_OK; +} diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.h b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.h new file mode 100644 index 0000000000..c4f50c68f3 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/main/svpwm/esp_svpwm.h @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "driver/mcpwm_prelude.h" + +/** + * @brief svpwm inverter config struct type + */ +typedef struct inverter_config { + mcpwm_timer_config_t timer_config; // pwm timer and timing config + mcpwm_operator_config_t operator_config; // mcpwm operator config + mcpwm_comparator_config_t compare_config; // mcpwm comparator config + int gen_gpios[3][2]; // 6 GPIO pins for generator config + mcpwm_dead_time_config_t dt_config; // dead time config for positive pwm output + mcpwm_dead_time_config_t inv_dt_config; // dead time config for negative pwm output +} inverter_config_t; + +// inverter handler type +typedef struct mcpwm_svpwm_ctx *inverter_handle_t; + +/** + * @brief Config mcpwm as a inverter with corresponding config value + * + * @param config config value for mcpwm peripheral + * @param ret_inverter return handler for corresponding mcpwm + * + * @return - ESP_OK: Create invertor successfully + * - ESP_ERR_INVALID_ARG: NULL arguments + * - ESP_ERR_NO_MEM: no free memory + */ +esp_err_t svpwm_new_inverter(const inverter_config_t *config, inverter_handle_t *ret_inverter); + +/** + * @brief register update callbacks for a mcpwm peripheral + * + * @param handle svpwm invertor handler + * @param event callbacks config + * @param user_ctx pointer to user data to be passed to callbacks + * + * @return - ESP_OK: register callbacks successfully + * - ESP_ERR_INVALID_ARG: NULL arguments + */ +esp_err_t svpwm_inverter_register_cbs(inverter_handle_t handle, const mcpwm_timer_event_callbacks_t *event, void *user_ctx); + +/** + * @brief start/stop a svpwm invertor + * + * @param handle svpwm invertor handler + * @param command see "mcpwm_timer_start_stop_cmd_t" + * + * @return - ESP_OK: start inverter successfully + * - ESP_ERR_INVALID_ARG: NULL arguments + */ +esp_err_t svpwm_inverter_start(inverter_handle_t handle, mcpwm_timer_start_stop_cmd_t command); + +/** + * @brief set 3 channels pwm comparator value for invertor + * + * @param handle svpwm invertor handler + * @param u comparator value for channel UH and UL + * @param v comparator value for channel VH and VL + * @param w comparator value for channel WH and WL + * + * @return - ESP_OK: set compare value successfully + * - ESP_ERR_INVALID_ARG: NULL arguments + */ +esp_err_t svpwm_inverter_set_duty(inverter_handle_t handle, uint16_t u, uint16_t v, uint16_t w); + +/** + * @brief free a svpwm invertor + * + * @param handle svpwm invertor handler + * + * @return - ESP_OK: free inverter successfully + * - ESP_ERR_INVALID_ARG: NULL arguments + */ +esp_err_t svpwm_del_inverter(inverter_handle_t handle); diff --git a/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/pytest_foc_open_loop.py b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/pytest_foc_open_loop.py new file mode 100644 index 0000000000..1b8f901822 --- /dev/null +++ b/examples/peripherals/mcpwm/mcpwm_foc_svpwm_open_loop/pytest_foc_open_loop.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s3 +@pytest.mark.esp32c6 +@pytest.mark.esp32h2 +@pytest.mark.generic +def test_open_foc(dut: Dut) -> None: + dut.expect_exact('example_foc: Hello FOC') + dut.expect_exact('example_foc: Inverter init OK') + dut.expect_exact('example_foc: Inverter start OK') + dut.expect_exact('example_foc: Enable MOSFET gate') + dut.expect_exact('example_foc: Start FOC')