SimpleFOClibrary 2.4.0
Loading...
Searching...
No Matches
current_sense/hardware_specific/esp32/esp32_mcpwm_mcu.cpp
Go to the documentation of this file.
1#include "esp32_mcu.h"
2
3#if defined(ESP_H) && defined(ARDUINO_ARCH_ESP32) && defined(SOC_MCPWM_SUPPORTED) && !defined(SIMPLEFOC_ESP32_USELEDC)
4
5// check the version of the ESP-IDF
6#include "esp_idf_version.h"
7
8#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
9#error SimpleFOC: ESP-IDF version 4 or lower detected. Please update to ESP-IDF 5.x and Arduino-esp32 3.0 (or higher)
10#endif
11
12#include "../../../drivers/hardware_specific/esp32/esp32_driver_mcpwm.h"
13#include "../../../drivers/hardware_specific/esp32/mcpwm_private.h"
14
15#include "driver/mcpwm_prelude.h"
16#include "soc/mcpwm_reg.h"
17#include "soc/mcpwm_struct.h"
18
19
20
21// adding a debug toggle pin to measure the time of the interrupt with oscilloscope
22// #define SIMPLEFOC_ESP32_INTERRUPT_DEBUG
23
24#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG
25#include "driver/gpio.h"
26
27#ifndef DEBUGPIN
28#ifdef CONFIG_IDF_TARGET_ESP32S3
29#define DEBUGPIN 16
30#else
31#define DEBUGPIN 19
32#endif
33#endif
34
35#define GPIO_NUM (gpio_num_t)((int)GPIO_NUM_0 + DEBUGPIN)
36#endif
37
38#ifndef SIMPLEFOC_CS_PRETRIGGER_US
39#define SIMPLEFOC_CS_PRETRIGGER_US 5 // 5 us because ADC read takes around 10us
40#endif
41
42
43
44/**
45 * Low side adc reading implementation
46*/
47
48
49// function reading an ADC value and returning the read voltage
50float IRAM_ATTR _readADCVoltageLowSide(const int pin, const void* cs_params){
51 ESP32CurrentSenseParams* p = (ESP32CurrentSenseParams*)cs_params;
52 int no_channel = 0;
53 for(int i=0; i < 3; i++){
54 if(!_isset(p->pins[i])) continue;
55 if(pin == p->pins[i]) // found in the buffer
56 return p->adc_buffer[no_channel] * p->adc_voltage_conv;
57 else no_channel++;
58 }
59 SIMPLEFOC_DEBUG("ERROR: ADC pin not found in the buffer!");
60 // not found
61 return 0;
62}
63
64
65// function configuring low-side current sensing
66void* IRAM_ATTR _configureADCLowSide(const void* driver_params, const int pinA,const int pinB,const int pinC){
67 // check if driver timer is already running
68 // fail if it is
69 // the easiest way that I've found to check if timer is running
70 // is to start it and stop it
71 ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams*)driver_params;
72 mcpwm_timer_t* t = (mcpwm_timer_t*) p->timers[0];
73
74 // check if low side callback is already set
75 // if it is, return error
76 if(t->on_full != nullptr){
77 SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Low side callback is already set. Cannot set it again for timer: "+String(t->timer_id)+", group: "+String(t->group->group_id));
79 }
80
81
82 ESP32CurrentSenseParams* params = new ESP32CurrentSenseParams{};
83 int no_adc_channels = 0;
84
85 // initialize the ADC pins
86 // fail if the pin is not an ADC pin
87 int adc_pins[3] = {pinA, pinB, pinC};
88 for (int i = 0; i < 3; i++){
89 if(_isset(adc_pins[i])){
90 if(!adcInit(adc_pins[i])){
91 SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Failed to initialise ADC pin: "+String(adc_pins[i]) + String(", maybe not an ADC pin?"));
93 }
94 params->pins[no_adc_channels++] = adc_pins[i];
95 }
96 }
97
98 t->user_data = params;
99 params->adc_voltage_conv = (_ADC_VOLTAGE)/(_ADC_RESOLUTION);
100 params->no_adc_channels = no_adc_channels;
101 return params;
102}
103
104static bool IRAM_ATTR _mcpwmTriggerADCCallback(mcpwm_timer_handle_t tim, const mcpwm_timer_event_data_t* edata, void* user_data){
105 ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams*)user_data;
106#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG // debugging toggle pin to measure the time of the interrupt with oscilloscope
107 gpio_set_level(GPIO_NUM,1); //cca 250ns for on+off
108#endif
109
110 // sample the phase currents one at a time
111 // ESP's adc read takes around 10us which is very long
112 // so we are sampling one phase per call
113 p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]);
114
115 // increment buffer index
116 p->buffer_index++;
117 if(p->buffer_index >= p->no_adc_channels){
118 p->buffer_index = 0;
119 }
120
121#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG // debugging toggle pin to measure the time of the interrupt with oscilloscope
122 gpio_set_level(GPIO_NUM,0); //cca 250ns for on+off
123#endif
124 return true;
125}
126
127// Comparator on_reach callback: sample ADC pre-trigger
128// In center-aligned mode, comparator fires twice per cycle (up and down)
129// Only sample on down-count to get one sample per PWM period
130static bool IRAM_ATTR _mcpwmComparatorADCCallback(mcpwm_cmpr_handle_t cmpr, const mcpwm_compare_event_data_t* edata, void* user_data){
131 // Only trigger on down-count direction
132 if(edata->direction != MCPWM_TIMER_DIRECTION_UP){
133 return true;
134 }
135
136 ESP32CurrentSenseParams *p = (ESP32CurrentSenseParams*)user_data;
137#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG
138 gpio_set_level(GPIO_NUM,1);
139#endif
140
141 // sample the phase currents one at a time
142 // ESP's adc read takes around 10us which is very long
143 // so we are sampling one phase per call
144 p->adc_buffer[p->buffer_index] = adcRead(p->pins[p->buffer_index]);
145
146 // increment buffer index
147 p->buffer_index++;
148 if(p->buffer_index >= p->no_adc_channels){
149 p->buffer_index = 0;
150 }
151
152#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG
153 gpio_set_level(GPIO_NUM,0);
154#endif
155 return true;
156}
157
158void* IRAM_ATTR _driverSyncLowSide(void* driver_params, void* cs_params){
159#ifdef SIMPLEFOC_ESP32_INTERRUPT_DEBUG
160 pinMode(DEBUGPIN, OUTPUT);
161#endif
162 ESP32MCPWMDriverParams *p = (ESP32MCPWMDriverParams*)driver_params;
163 mcpwm_timer_t* t = (mcpwm_timer_t*) p->timers[0];
164 int group_id = p->group_id;
165
166
167 ESP32CurrentSenseParams *cs = (ESP32CurrentSenseParams*)cs_params;
168 if(!cs){
169 SIMPLEFOC_ESP32_CS_DEBUG("ERROR: cs_params is null");
171 }
172
173 SIMPLEFOC_ESP32_CS_DEBUG("Configuring sync with comparator...");
174
175 // Create a spare comparator on the first operator for ADC pre-trigger
176 // This comparator will fire ~5 µs before on_reach (peak)
177 mcpwm_comparator_config_t cmp_config = {0};
178 cmp_config.flags.update_cmp_on_tez = true;
179 for(int i=2; i>=0; i--){ // start from the end as first operators are more likely to be used fully
180 if(p->oper[i] == nullptr) continue;
181 if(mcpwm_new_comparator(p->oper[i], &cmp_config, (mcpwm_cmpr_handle_t*)&cs->pretrig_comparator) == ESP_OK){
182 break;
183 }
184 }
185
186 // if comparator creation failed, fall back to on_full callback
187 if (cs->pretrig_comparator){
188 // Calculate pwm duty cycle ticks for pre-trigger channel
189 // TODO: verify the timing it seems to be correct between 15 and 20kHz (but needs better testing)
190 uint32_t pwm_duty_cycle = p->mcpwm_period * (0.75 - ((float)p->pwm_frequency*SIMPLEFOC_CS_PRETRIGGER_US)/1e6/2.0);
191 // set up the comparator duty cycle
192 CHECK_CS_ERR(mcpwm_comparator_set_compare_value((mcpwm_cmpr_handle_t)cs->pretrig_comparator, pwm_duty_cycle),
193 "Failed to set pretrigger compare value");
194
195 // Register comparator on_reach callback for ADC sampling
196 mcpwm_comparator_event_callbacks_t cmp_cbs = {
197 .on_reach = _mcpwmComparatorADCCallback
198 };
199 // register the callback
200 CHECK_CS_ERR(mcpwm_comparator_register_event_callbacks((mcpwm_cmpr_handle_t)cs->pretrig_comparator, &cmp_cbs, cs_params),
201 "Failed to register comparator callback");
202
203 SIMPLEFOC_ESP32_CS_DEBUG("MCPWM"+String(group_id)+" Timer "+String(t->timer_id)+" pretrigger comparator configured.");
204 // notify the driver code that low side is uses one comparator
205 _notifyLowSideUsingComparator(group_id);
206
207 }else{
208
209 SIMPLEFOC_ESP32_CS_DEBUG("WARN: Failed to create comparator!");
210 SIMPLEFOC_ESP32_CS_DEBUG("Configuring sync with on_full callback (less accurate)...");
211
212 // check if low side callback is already set
213 // if it is, return error
214 if(t->on_full != nullptr){
215 SIMPLEFOC_ESP32_CS_DEBUG("ERROR: Low side callback is already set. Cannot set it again for timer: "+String(t->timer_id)+", group: "+String(t->group->group_id));
217 }
218 // set the callback for the low side current sensing
219 // mcpwm_timer_event_callbacks_t can be used to set the callback
220 // for three timer events
221 // - on_full - low-side
222 // - on_empty - high-side
223 // - on_sync - sync event (not used with simplefoc)
224 auto cbs = mcpwm_timer_event_callbacks_t{
225 .on_full = _mcpwmTriggerADCCallback,
226 };
227 SIMPLEFOC_ESP32_CS_DEBUG("Timer "+String(t->timer_id)+" enable interrupt callback.");
228 // set the timer state to init (so that we can call the `mcpwm_timer_register_event_callbacks` )
229 // this is a hack, as this function is not supposed to be called when the timer is running
230 // the timer does not really go to the init state!
231 t->fsm = MCPWM_TIMER_FSM_INIT;
232 // set the callback
233 CHECK_CS_ERR(mcpwm_timer_register_event_callbacks(t, &cbs, cs_params), "Failed to set low side callback");
234 // set the timer state to enabled again
235 t->fsm = MCPWM_TIMER_FSM_ENABLE;
236 CHECK_CS_ERR(esp_intr_enable(t->intr), "Failed to enable low-side interrupts!");
237
238 SIMPLEFOC_ESP32_CS_DEBUG("MCPWM"+String(group_id)+" Timer "+String(t->timer_id)+" on_full callback configured.");
239 }
240
241
242 return cs_params;
243}
244
245
246#endif
#define SIMPLEFOC_DEBUG(msg,...)
void * _driverSyncLowSide(void *driver_params, void *cs_params)
#define SIMPLEFOC_CURRENT_SENSE_INIT_FAILED
void * _configureADCLowSide(const void *driver_params, const int pinA, const int pinB, const int pinC=NOT_SET)
float _readADCVoltageLowSide(const int pinA, const void *cs_params)
const int const int const int pinC
GenericCurrentSenseParams * params
#define _isset(a)
Definition foc_utils.h:13