Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
460 changes: 460 additions & 0 deletions src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.c

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions src/current_sense/hardware_specific/esp32/esp32_adc_digi_driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* MIT License
*
* Copyright (c) 2021 Felipe Neves
*
* Digital ADC + DMA driver for SimpleFOC ESP32 low-side current sense.
* See esp32_adc_digi_internal.h for chip-specific trigger/DMA behaviour.
*/
#pragma once

#if defined(ARDUINO_ARCH_ESP32)

#include "esp_err.h"
#include "hal/adc_types.h"
#include "esp32_adc_digi_internal.h"

#if SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
adc_channel_t channels[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS];
adc_unit_t unit;
int *adc_buffer;
int no_adc_channels;
} esp32_adc_digi_config_t;

typedef enum {
SIMPLEFOC_ESP32_ADC_TRIG_SOFTWARE = 0,
#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED
SIMPLEFOC_ESP32_ADC_TRIG_ETM,
#endif
} esp32_adc_digi_trigger_t;

esp_err_t esp32_adc_digi_init(const esp32_adc_digi_config_t *cfg);
esp_err_t esp32_adc_digi_deinit(void);
esp_err_t esp32_adc_digi_set_trigger(esp32_adc_digi_trigger_t mode);
esp_err_t esp32_adc_digi_trigger_software(void);

int esp32_adc_digi_read_raw(const void *adc_buffer, int index);

bool esp32_adc_digi_supported(void);

#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED
esp_err_t esp32_adc_digi_set_etm_source(const esp32_adc_digi_etm_config_t *cfg);
bool esp32_adc_digi_etm_supported(void);
#endif

#ifdef __cplusplus
}
#endif

#endif /* SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED */
#endif /* ARDUINO_ARCH_ESP32 */
110 changes: 110 additions & 0 deletions src/current_sense/hardware_specific/esp32/esp32_adc_digi_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* MIT License
*
* Copyright (c) 2021 Felipe Neves
*/
#pragma once

#if !defined(ARDUINO_ARCH_ESP32)

#define SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED 0
#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 0

#else

#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "hal/adc_types.h"
#include "hal/dma_types.h"
#include "soc/soc_caps.h"

/*
* Low-side current sense on ESP32 uses the SAR **digital** controller (pattern
* sequencer) plus a chip-specific DMA shim — same approach as espFoC isensor_adc.
*
* Triggering (when to start one pattern conversion):
* - ESP32 / ESP32-S2: **software** only. MCPWM ISR (comparator or timer event)
* must call esp32_adc_digi_trigger_software() at the desired PWM phase.
* - ESP32-S3 and newer (C3, C6, …): optional **ETM** wires MCPWM timer TEZ/TEP
* to ADC_TASK_START0 with no CPU in the trigger path.
*
* DMA backend (where SOC moves ADC results):
* - ESP32: I2S peripheral (see esp32_adc_dma_esp32.c)
* - ESP32-S2: SPI3 (esp32_adc_dma_esp32s2.c)
* - ESP32-S3+: GDMA (esp32_adc_dma_gdma.c)
*/

#define SIMPLEFOC_ESP32_ADC_DIGI_SUPPORTED 1

#if CONFIG_IDF_TARGET_ESP32
#define SIMPLEFOC_ESP32_ADC_USE_I2S_DMA 1
#elif CONFIG_IDF_TARGET_ESP32S2
#define SIMPLEFOC_ESP32_ADC_USE_SPI3_DMA 1
#elif SOC_GDMA_SUPPORTED
#define SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA 1
#endif

#if SOC_ETM_SUPPORTED && defined(SIMPLEFOC_ESP32_ADC_USE_GDMA_DMA)
#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 1
#else
#define SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED 0
#endif

#define SIMPLEFOC_ESP32_ADC_PATTERN_HZ 80000
#define SIMPLEFOC_ESP32_ADC_NUM_CHANNELS 2
#define SIMPLEFOC_ESP32_ADC_CONVERT_LIMIT 2

#define ESP32_ADC_DIGI_FRAME_BYTES \
(SIMPLEFOC_ESP32_ADC_NUM_CHANNELS * SOC_ADC_DIGI_RESULT_BYTES)

#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define ESP32_ADC_DIGI_SAMPLE_RAW(p) ((int32_t)((p)->type1.data))
#else
#define ESP32_ADC_DIGI_SAMPLE_RAW(p) ((int32_t)((p)->type2.data))
#endif

static inline int esp32_adc_digi_raw_at(const void *adc_buffer, int index)
{
const adc_digi_output_data_t *samples = (const adc_digi_output_data_t *)adc_buffer;
return (int)ESP32_ADC_DIGI_SAMPLE_RAW(&samples[index]);
}

typedef struct esp32_adc_digi_dma_ctx esp32_adc_digi_dma_ctx_t;

typedef void (*esp32_adc_digi_dma_done_fn_t)(void *user);

struct esp32_adc_digi_dma_ctx {
esp32_adc_digi_dma_done_fn_t on_done;
void *on_done_arg;
volatile intptr_t eof_desc_addr;
};

esp_err_t esp32_adc_digi_dma_init(esp32_adc_digi_dma_ctx_t *ctx,
esp32_adc_digi_dma_done_fn_t on_done,
void *user);
esp_err_t esp32_adc_digi_dma_deinit(esp32_adc_digi_dma_ctx_t *ctx);
esp_err_t esp32_adc_digi_dma_start(esp32_adc_digi_dma_ctx_t *ctx, dma_descriptor_t *desc);
esp_err_t esp32_adc_digi_dma_stop(esp32_adc_digi_dma_ctx_t *ctx);
esp_err_t esp32_adc_digi_dma_reset(esp32_adc_digi_dma_ctx_t *ctx);

#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED

typedef enum {
SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ = 0,
SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEP,
} esp32_adc_digi_mcpwm_event_t;

typedef struct {
uint8_t mcpwm_timer;
esp32_adc_digi_mcpwm_event_t event;
} esp32_adc_digi_etm_config_t;

esp_err_t esp32_adc_digi_etm_connect(const esp32_adc_digi_etm_config_t *cfg);
esp_err_t esp32_adc_digi_etm_enable(bool enable);
void esp32_adc_digi_etm_disconnect(void);

#endif /* SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED */

#endif /* ARDUINO_ARCH_ESP32 */
209 changes: 209 additions & 0 deletions src/current_sense/hardware_specific/esp32/esp32_adc_digi_lowside.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include "esp32_mcu.h"

#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED) \
&& !defined(SIMPLEFOC_ESP32_USELEDC)

#include "esp32_adc_digi_lowside.h"

#include "esp32_adc_digi_driver.h"
#include "../../../drivers/hardware_specific/esp32/esp32_driver_mcpwm.h"
#include "../../../drivers/hardware_specific/esp32/mcpwm_private.h"
#include "driver/mcpwm_prelude.h"

#ifndef SIMPLEFOC_CS_PRETRIGGER_US
#define SIMPLEFOC_CS_PRETRIGGER_US 5
#endif

static bool esp32_pin_to_adc1_channel(int pin, adc_channel_t *out_ch)
{
int8_t ch = digitalPinToAnalogChannel(pin);
if (ch < 0 || ch >= SOC_ADC_MAX_CHANNEL_NUM) {
return false;
}
*out_ch = (adc_channel_t)ch;
return true;
}

static esp_err_t esp32_adc_digi_bind_params(ESP32CurrentSenseParams *params)
{
if (params->no_adc_channels < 1 || params->no_adc_channels > SIMPLEFOC_ESP32_ADC_NUM_CHANNELS) {
return ESP_ERR_INVALID_ARG;
}

adc_channel_t channels[SIMPLEFOC_ESP32_ADC_NUM_CHANNELS] = {};
for (int i = 0; i < params->no_adc_channels; i++) {
if (!esp32_pin_to_adc1_channel(params->pins[i], &channels[i])) {
return ESP_ERR_INVALID_ARG;
}
}

esp32_adc_digi_config_t cfg = {
.channels = { channels[0], channels[1] },
.unit = ADC_UNIT_1,
.adc_buffer = params->adc_buffer,
.no_adc_channels = params->no_adc_channels,
};

return esp32_adc_digi_init(&cfg);
}

ESP32AdcLowsidePath esp32_adc_lowside_configure(ESP32CurrentSenseParams *params)
{
if (params == NULL || !esp32_adc_digi_supported()) {
return ESP32_ADC_LOWSIDE_ADC_READ;
}

if (esp32_adc_digi_bind_params(params) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("WARN: ADC digi+DMA init failed, using ADC_READ (adcRead) path");
return ESP32_ADC_LOWSIDE_ADC_READ;
}

params->adc_lowside_path = ESP32_ADC_LOWSIDE_DIGI_SW;
SIMPLEFOC_ESP32_CS_DEBUG("ADC digi+DMA ready (software trigger via MCPWM ISR on ESP32/S2)");
return ESP32_ADC_LOWSIDE_DIGI_SW;
}

bool esp32_adc_lowside_uses_mcpwm_isr(const ESP32CurrentSenseParams *params)
{
if (params == NULL) {
return false;
}
return params->adc_lowside_path == ESP32_ADC_LOWSIDE_DIGI_SW;
}

void esp32_adc_lowside_start_conversion(void)
{
esp32_adc_digi_trigger_software();
}

#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED
static void *esp32_adc_lowside_sync_etm(void *driver_params, ESP32CurrentSenseParams *cs)
{
ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params;
mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0];
int group_id = p->group_id;

esp32_adc_digi_etm_config_t etm = {
.mcpwm_timer = (uint8_t)t->timer_id,
.event = SIMPLEFOC_ESP32_ADC_MCPWM_EVT_TIMER_TEZ,
};

if (esp32_adc_digi_set_etm_source(&etm) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: ETM source setup failed");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}
if (esp32_adc_digi_set_trigger(SIMPLEFOC_ESP32_ADC_TRIG_ETM) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: ETM ADC trigger enable failed");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}

cs->adc_lowside_path = ESP32_ADC_LOWSIDE_DIGI_ETM;
SIMPLEFOC_ESP32_CS_DEBUG("MCPWM" + String(group_id) + " timer " + String(t->timer_id) +
" -> ETM -> ADC (TEZ), no MCPWM ADC ISR");
return cs;
}
#endif

static bool IRAM_ATTR esp32_adc_mcpwm_sw_trigger_cb(mcpwm_cmpr_handle_t cmpr,
const mcpwm_compare_event_data_t *edata,
void *user_data)
{
(void)cmpr;
(void)user_data;
if (edata->direction != MCPWM_TIMER_DIRECTION_UP) {
return true;
}
esp32_adc_digi_trigger_software();
return true;
}

static bool IRAM_ATTR esp32_adc_mcpwm_sw_trigger_timer_cb(mcpwm_timer_handle_t tim,
const mcpwm_timer_event_data_t *edata,
void *user_data)
{
(void)tim;
(void)edata;
(void)user_data;
esp32_adc_digi_trigger_software();
return true;
}

static void *esp32_adc_lowside_sync_mcpwm_sw(void *driver_params, ESP32CurrentSenseParams *cs)
{
ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams *)driver_params;
mcpwm_timer_t *t = (mcpwm_timer_t *)p->timers[0];
int group_id = p->group_id;

SIMPLEFOC_ESP32_CS_DEBUG("MCPWM comparator -> software ADC digi trigger (ESP32/S2 style)");

mcpwm_comparator_config_t cmp_config = {};
cmp_config.flags.update_cmp_on_tez = true;
for (int i = 2; i >= 0; i--) {
if (p->oper[i] == nullptr) {
continue;
}
if (mcpwm_new_comparator(p->oper[i], &cmp_config, (mcpwm_cmpr_handle_t *)&cs->pretrig_comparator) == ESP_OK) {
break;
}
}

if (cs->pretrig_comparator) {
uint32_t pwm_duty_cycle = p->mcpwm_period * (0.75f - ((float)p->pwm_frequency * SIMPLEFOC_CS_PRETRIGGER_US) / 1e6f / 2.0f);
if (mcpwm_comparator_set_compare_value((mcpwm_cmpr_handle_t)cs->pretrig_comparator, pwm_duty_cycle) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: comparator compare value");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}
mcpwm_comparator_event_callbacks_t cmp_cbs = {
.on_reach = esp32_adc_mcpwm_sw_trigger_cb,
};
if (mcpwm_comparator_register_event_callbacks((mcpwm_cmpr_handle_t)cs->pretrig_comparator, &cmp_cbs, cs) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: comparator callback");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}
_notifyLowSideUsingComparator(group_id);
SIMPLEFOC_ESP32_CS_DEBUG("Comparator pre-trigger -> esp32_adc_digi_trigger_software()");
return cs;
}

SIMPLEFOC_ESP32_CS_DEBUG("WARN: no comparator; MCPWM on_full -> software ADC digi trigger");
if (t->on_full != nullptr) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: timer on_full already in use");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}

auto cbs = mcpwm_timer_event_callbacks_t{
.on_full = esp32_adc_mcpwm_sw_trigger_timer_cb,
};
t->fsm = MCPWM_TIMER_FSM_INIT;
if (mcpwm_timer_register_event_callbacks(t, &cbs, cs) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: timer callback");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}
t->fsm = MCPWM_TIMER_FSM_ENABLE;
if (esp_intr_enable(t->intr) != ESP_OK) {
SIMPLEFOC_ESP32_CS_DEBUG("ERROR: enable timer intr");
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}
return cs;
}

void *esp32_adc_lowside_sync_mcpwm(void *driver_params, ESP32CurrentSenseParams *cs)
{
if (cs == NULL || driver_params == NULL) {
return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}

#if SIMPLEFOC_ESP32_ADC_ETM_SUPPORTED
if (esp32_adc_digi_etm_supported() && cs->adc_lowside_path != ESP32_ADC_LOWSIDE_ADC_READ) {
return esp32_adc_lowside_sync_etm(driver_params, cs);
}
#endif

if (cs->adc_lowside_path == ESP32_ADC_LOWSIDE_DIGI_SW) {
return esp32_adc_lowside_sync_mcpwm_sw(driver_params, cs);
}

return SIMPLEFOC_CURRENT_SENSE_INIT_FAILED;
}

#endif
Loading
Loading