Merge branch 'example/mcpwm_svm_example' into 'master'

mcpwm: add foc svpwm example

Closes IDF-3662

See merge request espressif/esp-idf!23645
This commit is contained in:
Wan Lei 2023-08-02 12:46:15 +08:00
commit 2975cc52b8
15 changed files with 739 additions and 0 deletions

View File

@ -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)

View File

@ -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:
<img align="middle" src="img/vector_coord.png">
### 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:
<img align="middle" height="200" src="img/6pwm.png">
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:
<img align="middle" src="img/phase_wave.png">
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:
<img src="img/line_diff.png">
Please turn off config `ESP_FOC_USE_SVPWM` if the `neutral line` is required, then there will be a standard three-phase sin system.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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
)

View File

@ -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

View File

@ -0,0 +1,165 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#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));
}

View File

@ -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;
}
}

View File

@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <math.h>
// 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);

View File

@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/iqmath: "^1.11.0"
## Required IDF version
idf:
version: ">=5.0"

View File

@ -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;
}

View File

@ -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);

View File

@ -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')