MCPWM Capture High Value

Fortsetzung der Diskussion von duty cycle messen eines PWM-Signals bei 20kHz:

@agmue
Wie es scheint hast du das MCPWM ziemlich im Griff! Ich verzweifle mittlerweile fast bei einem Thema und zwar möchte ich den High-Wert von einem PWM-Signal messen.
Der Aufbau ist im groben so, dass auf einem GPIO ein PWM Signal erzeugt wird und (über ein paar widerstände dgl.) das gleiche PWM Signal wieder auf einen weiteren GPIO gelesen wird.
Das ganze sollte ja nicht schwierig sein jedoch bei einem Duty-Cycle von <10% benötigt man einen Interrupt o.ä. um genau am richtigen Zeitpunkt zu messen, da man ohne ansonsten mit Sicherheit erst irgend wann mal den High wert erwischt.

hier mal mein Code:

#include <driver/mcpwm.h>
#include <soc/mcpwm_struct.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>

static esp_adc_cal_characteristics_t adc_chars;
int test;

static void IRAM_ATTR isr_handler(void *arg){
  test++;
}

static void A_TaskCP(void *arg) {

  Serial.printf("initializing mcpwm gpio and adc for CP-PWM...\n");
//######################### Initialisieren Sie den ADC-Kanal
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); 
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);

//######################### PWM Signal mit MCPWM
    // Hier wird der Ausgangspin für das PWM-Signal konfiguriert
    mcpwm_pin_config_t pin_pwm_config;
    pin_pwm_config.mcpwm0a_out_num = 32;   
    // MCPWM-Zuweisung
    mcpwm_set_pin(MCPWM_UNIT_0, &pin_pwm_config);
    // Hier wird der PWM-Signalgenerator konfiguriert
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 1000;  // 1kHz
    pwm_config.cmpr_a = 10;       // Duty-Cycle von 50%
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);  //Set ISR Handler
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
    // Starten des PWM-Signals
     mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);
 // vTaskDelete(NULL);
  while (1) {
    //Serial.printf("\r\nTASK CP");  
    vTaskDelay(1000 / portTICK_PERIOD_MS); // verzögere den Task um 1000ms  
  }
}

static void A_Task1(void *arg) {
  while (1) {
    //  ADC-Messwert abfragen
    uint32_t raw_value = adc1_get_raw((adc1_channel_t)ADC1_CHANNEL_7);
    //  Berechnen des Spannungswerts aus dem ADC-Messwert
    uint32_t adcRead = esp_adc_cal_raw_to_voltage(raw_value, &adc_chars);
    Serial.printf("\r\nADC Read-Val %d", adcRead);  
    Serial.printf("\r\nInterrupt Test %d", test);  
    vTaskDelay(1000 / portTICK_PERIOD_MS); // verzögere den Task um 1000ms
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("\nStart Setup.......");

  xTaskCreate(A_TaskCP, "Task_CP_ReadWrite", 4096, NULL, 5, NULL);
  xTaskCreate(A_Task1, "Task_1_Normal_Operation", 4096, NULL, 5, NULL);
}

void loop() {}

Laut meiner Recherche sollte es doch genügen, dass man lediglich die Funktion mcpwm_isr_register() einfügt um den Interrupt auszuführen jedoch passiert das einfach nicht!
Was mache ich falsch?

Bitte um unterstützung.
Vielen DANK!

Hallo,

generell gilt, nutzt man einen Interrupt muss auch die entsprechende ISR vorhanden sein. Ansonsten springt das Programm beim Aufruf ins Nirwana.

?? dann bitte korrigiere meinen Code da ich den Fehler nicht sehe.

Hallo,

wie du im verlinkten Thread bestimmt gelesen hast, kenne ich mich mit ESP und dessen Hardware naher Programmierung nicht aus. Ich kann aber mein Wissen vom AVR pauschal über den ESP legen, weil die Grundregeln immer gleich sind.

Wenn du einen Interrupt nutzen möchtest und deswegen einen aktivierst, dann muss dazu auch der ensprechende Interrupthandler (ISR ... Interrupt Service Routine) vorhanden sein.

mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);  //Set ISR Handler

Hier sieht es so aus als ob du den Namen schon vergeben hast. isr_handler.
Jetzt muss es aber auch eine Funktion, sprich ISR namens isr handler geben die dann aufgerufen werden kann. Wenn du einen Interrupt aktivierst willst du ja schließlich auch was machen wenn er eintrifft. Ansonsten brauchst du auch keinen Interrupt.

Ich denke, das stellt das attachISR dar, mit dem die Funktion isr_handler mit dem Interrupt verknüft wird. Und diese Funktion ist oben definiert:

Korrekt :+1: !
Die Funktion

mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL); //Set ISR Handler

sollte diese Funktion

static void IRAM_ATTR isr_handler(void *arg){ test++; }

bei einem High-Signal ausführen. Zumindest hätte ich das so verstanden. Aber leider passiert garnichts!?
Ich habe auch schon mit der Capture-Funktion gespielt so wie im hervorragendem Beispiel von @agmue
und dieses liefert auch Werte aber, so wie ich das sehe, nur für das messen von Dauer des PWM-signals. Eine gleichzeitige ADC-Wertabfrage…. also Interrupt mit GPIO 35 via Capture anstoßen und in der Interrupt-Funktion den analogen ADC-Wert von GPIO 35 auslesen ist denke ich nicht möglich… aber vielleicht irre ich mich ja auch.:man_shrugging:

Hallo,

diese
static void IRAM_ATTR isr_handler(void *arg){ test++; }
habe ich vor lauter static übersehen. Okay.

Betrachten wir uns den Sketch weiter.
In der ISR wird test je Aufruf inkrementiert.
Dann hast du in A_TaskCP und A_Task1 jeweils eine Endlosschleifen drin. Warum?
Damit wäre für mich klar das dann nichts weiter passiert.
Wenn der Timer richtig konfiguriert ist wird er laufen und test fleißig inkrementieren, aber du siehst nichts, weil das Programm nicht weiter kommt ...
nach Ausgabe von Setup sollte dein Terminal leer bleiben.
Vom fehlenden volatile und atomaren Zugriff auf test reden wir noch gar nicht.
Und was hat es mit dem Funktionsparameter *arg auf sich? Der kann doch weg, weil nicht verwendet.

Dann hast du in A_TaskCP und A_Task1 jeweils eine Endlosschleifen drin. Warum?
Damit wäre für mich klar das dann nichts weiter passiert.
Wenn der Timer richtig konfiguriert ist wird er laufen und test fleißig inkrementieren, aber du siehst nichts, weil das Programm nicht weiter kommt ...
nach Ausgabe von Setup sollte dein Terminal leer bleiben.

Das ganze ist ein Test-Projekt mit RTOS, da diese dann beim finalen Projekt benötigt wird und die Task bleiben dann natürlich nicht leer. Ich habe anderen Sachen lediglich entfernt, damit sich jemand anderer leichter zurecht findet.
Das Projekt funktioniert sehr wohl! und alle Task werden auch ordnungsgemäß durchlaufen du kannst das auch gerne mal in einem ESP Laden und probieren da bleibt nichts hängen das Projekt läuft sauber und ordnungsgemäß durch von Schleife zu Schleife wie es sein soll.

Vom fehlenden volatile und atomaren Zugriff auf test reden wir noch gar nicht.

  1. dann ist´s ja gut, kann man gerne nachrüsten oder auch nicht bei diesem TEST-PROJEKT aber es ändert nichts... ansonsten bitte wie schon erwähnt selber Testen.

Und was hat es mit dem Funktionsparameter *arg auf sich? Der kann doch weg, weil nicht verwendet.

nein kann er leider nicht, da dies von der mcpwm_isr_register() Funktion benötigt wird. ansonsten meckert er beim Kompilieren. Ich habe mich bei diesem Test-Projekt auch an @agmue Vorlage gehalten und diese funktioniert problemlos.

Also:
Das Projekt läuft! Man kann es jederzeit kopieren und in einen ESP Laden. wenn man die interrupt Funktion isr_handler mit anderer Bedingung triggert wird diese auch problemlos ausgeführt und die variable "test" hochgezählt das habe ich natürlich schon alles getestet. Jedoch wird einfach diese isr_handler Funktion von der mcpwm_isr_register() nicht getriggert und ich weiß nicht warum.

THX

Hallo,

alles klar, ich habe keine weiteren Fragen und auch keine Antworten.

1 Like

Nö, leider nicht.

Verstehe ich Dich richtig, daß Du den 10 % Highpuls einer 1 kHz Signals mit dem ADC des ESP32 messen willst? Dann müßte die ADC-Wandlung in weniger als 10 µs fertig sein, richtig? Hast Du ins Datenblatt geschaut, wie lange eine ADC-Messung dauert?

Weil das eine Task ist. Vor while (1) ist das setup der Task, danach loop.

Drei unabhängig blinkende LEDs:

static void gpio_blinken_1(void *arg)
{
  gpio_config_t gp;
  gp.intr_type = GPIO_INTR_DISABLE;
  gp.mode = GPIO_MODE_OUTPUT;
  gp.pin_bit_mask = GPIO_SEL_12;
  gpio_config(&gp);
  while (1) {
    gpio_set_level(GPIO_NUM_12, 1); //Set high
    vTaskDelay(500);             //delay of 500ms
    gpio_set_level(GPIO_NUM_12, 0); //Set low
    vTaskDelay(1500);         //delay of 1500ms
  }
}
static void gpio_blinken_2(void *arg)
{
  gpio_config_t gp;
  gp.intr_type = GPIO_INTR_DISABLE;
  gp.mode = GPIO_MODE_OUTPUT;
  gp.pin_bit_mask = GPIO_SEL_13;
  gpio_config(&gp);
  while (1) {
    gpio_set_level(GPIO_NUM_13, 1); //Set high
    vTaskDelay(222);             //delay of 222ms
    gpio_set_level(GPIO_NUM_13, 0); //Set low
    vTaskDelay(150);         //delay of 150ms
  }
}

void setup(void)
{
  pinMode(14, OUTPUT);
  xTaskCreate(gpio_blinken_1, "gpio_blinken_1", 4096, NULL, 5, NULL);
  xTaskCreate(gpio_blinken_2, "gpio_blinken_2", 4096, NULL, 5, NULL);
}

void loop () {
  digitalWrite(14, !digitalRead(14));
  delay(400);
}

Ist doch schräg, oder? Paßt zum närrischen Tag :partying_face:

Verstehe ich Dich richtig, daß Du den 10 % Highpuls einer 1 kHz Signals mit dem ADC des ESP32 messen willst? Dann müßte die ADC-Wandlung in weniger als 10 µs fertig sein, richtig? Hast Du ins Datenblatt geschaut, wie lange eine ADC-Messung dauert?

ich will ja nicht jede ms also jedes mal wenn das PWM-Signal erzeugt wird den Wert des ADC´s abfragen sondern natürlich ... keine Ahnung ... alle paar Millisekunden mal. wichtig wäre, dass eben genau dann die Messung startet sobald das PWM-Signal erzeugt wird. Wenn danach wieder ein paar Impulse nicht gemessen werden dann ist das nicht tragisch aber das bekomme ich schon hin mit einem Counter oder mills odgl. dass der interrupt nicht durchgängig läuft.
Es geht wie gesagt darum, dass ich eine ADC-Messung starten möchte wenn das PWM Signal auf high geht.... notfalls wenn sich das nicht ausgeht dann hald ein paar Micro-Sekunden vorher wenn du tatsächlich meinst, dass es schwierig wird überhaupt den High wert zu erwischen.

Ich spiele auch gerade mit DMA aber das erfordert doch noch viel mehr Expertise dies in der Arduino IDE hinzubekommen... zumindest für mich deshalb wäre es der einfachste weg mal wenn es mit dieser Sch..... mcpwm_isr_register() Funktion funktionieren würde dann dafür ist diese ja auch da.... zumindest denke ich das.

Ich bleibe hinsichtlich Deines Vorhabens skeptisch, aber Du wirst mir ja berichten :slightly_smiling_face:

Also Start der ADC-Messung bei positiver Flanke.

Mein Programm aus dem anderen Thema, etwas abgewandelt
#include "soc/rtc.h"
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"
#include <driver/adc.h>
#include <esp_adc_cal.h>

#define CAP_SIG_NUM 2   //Two capture signals

#define CAP0_INT_EN BIT(27)  //Capture 0 interrupt bit
#define CAP1_INT_EN BIT(28)  //Capture 1 interrupt bit
#define GPIO_CAP0_IN   gpio_num_t(23)   //Set GPIO 23 as  CAP0
#define GPIO_CAP1_IN   gpio_num_t(25)   //Set GPIO 25 as  CAP1

typedef struct {
  uint32_t capture_signal;
  mcpwm_capture_signal_t sel_cap_signal;
} capture;

uint32_t *current_cap_value = NULL;
uint32_t *previous_cap_value = NULL;
static esp_adc_cal_characteristics_t adc_chars;
uint32_t adcRead = 0;

xQueueHandle cap_queue;
static mcpwm_dev_t *MCPWM[2] = {&MCPWM0, &MCPWM1};

static void mcpwm_example_gpio_initialize(void)
{
  Serial.printf("initializing mcpwm gpio...\n");
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, GPIO_CAP0_IN);
  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_1, GPIO_CAP1_IN);
  gpio_pulldown_en(GPIO_CAP0_IN);    //Enable pull down on CAP0   signal
  gpio_pulldown_en(GPIO_CAP1_IN);    //Enable pull down on CAP1   signal
}
static void mcpwm_example_adc_initialize(void)
{
  Serial.printf("initializing mcpwm adc...\n");
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); 
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
}

/**
   @brief Set gpio 12 as our test signal that generates high-low waveform continuously, connect this gpio to capture pin.
*/
static void gpio_test_signal(void *arg)
{
  Serial.printf("intializing test signal...\n");
  gpio_config_t gp;
  gp.intr_type = GPIO_INTR_DISABLE;
  gp.mode = GPIO_MODE_OUTPUT;
  gp.pin_bit_mask = GPIO_SEL_12;
  gpio_config(&gp);
  while (1) {
    //here the period of test signal is 20ms
    gpio_set_level(GPIO_NUM_12, 1); //Set high
    vTaskDelay(5);             //delay of 10ms
    gpio_set_level(GPIO_NUM_12, 0); //Set low
    vTaskDelay(15);         //delay of 10ms
  }
}

/**
   @brief When interrupt occurs, we receive the counter value and display the time between two rising edge
*/
static void disp_captured_signal(void *arg)
{
  capture evt;
  static uint32_t wert_low = 1;
  while (1) {
    xQueueReceive(cap_queue, &evt, portMAX_DELAY);
    if (evt.sel_cap_signal == MCPWM_SELECT_CAP0) {
      Serial.printf("CAP0 : %d us\t", evt.capture_signal);
      wert_low = evt.capture_signal;
    }
    if (evt.sel_cap_signal == MCPWM_SELECT_CAP1) {
      Serial.printf("CAP1 : %d us\t", evt.capture_signal);
      Serial.printf("%d Prozent\t", 100 * evt.capture_signal / wert_low);
      Serial.printf("ADC Read-Val %d\n", adcRead);      

    }
  }
}

/**
   @brief this is ISR handler function, here we check for interrupt that triggers rising edge on CAP0 signal and according take action
*/
static void IRAM_ATTR isr_handler(void *arg)
{
  uint32_t mcpwm_intr_status;
  capture evt;
  mcpwm_intr_status = MCPWM[MCPWM_UNIT_0]->int_st.val; //Read interrupt status
  //calculate the interval in the ISR,
  //so that the interval will be always correct even when cap_queue is not handled in time and overflow.
  if (mcpwm_intr_status & CAP0_INT_EN) { //Check for interrupt on rising edge on CAP0 signal
    current_cap_value[0] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP0); //get capture signal counter value
    //evt.capture_signal = (current_cap_value[0] - previous_cap_value[0]) / (rtc_clk_apb_freq_get() / 1000000);
    evt.capture_signal = (current_cap_value[0] - previous_cap_value[1]) / (rtc_clk_apb_freq_get() / 1000000);
    previous_cap_value[0] = current_cap_value[0];
    evt.sel_cap_signal = MCPWM_SELECT_CAP0;
    xQueueSendFromISR(cap_queue, &evt, NULL);
    //  ADC-Messwert abfragen
    uint32_t raw_value = adc1_get_raw((adc1_channel_t)ADC1_CHANNEL_7);
    //  Berechnen des Spannungswerts aus dem ADC-Messwert
    adcRead = esp_adc_cal_raw_to_voltage(raw_value, &adc_chars);
  }
  if (mcpwm_intr_status & CAP1_INT_EN) { //Check for interrupt on rising edge on CAP0 signal
    current_cap_value[1] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP1); //get capture signal counter value
    //evt.capture_signal = (current_cap_value[1] - previous_cap_value[1]) / (rtc_clk_apb_freq_get() / 1000000);
    evt.capture_signal = (current_cap_value[1] - previous_cap_value[0]) / (rtc_clk_apb_freq_get() / 1000000);
    previous_cap_value[1] = current_cap_value[1];
    evt.sel_cap_signal = MCPWM_SELECT_CAP1;
    xQueueSendFromISR(cap_queue, &evt, NULL);
  }
  MCPWM[MCPWM_UNIT_0]->int_clr.val = mcpwm_intr_status;
}

/**
   @brief Configure whole MCPWM module
*/
static void mcpwm_example_config(void *arg)
{
  mcpwm_example_gpio_initialize();
  mcpwm_example_adc_initialize();
  
  //configure CAP0 signal to start capture counter on rising edge
  //we generate a gpio_test_signal on GPIO 12 and connect it to one of the capture signal, the disp_captured_function displays the time between rising edge
  //In general practice you can connect Capture  to external signal, measure time between rising edge or falling edge and take action accordingly
  mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, MCPWM_POS_EDGE, 0);  //capture signal on rising edge, prescale = 0 i.e. 800,000,000 counts is equal to one second
  mcpwm_capture_enable(MCPWM_UNIT_0, MCPWM_SELECT_CAP1, MCPWM_NEG_EDGE, 0);  //capture signal on falling edge, prescale = 0 i.e. 800,000,000 counts is equal to one second
  //enable interrupt, so each this a rising edge occurs interrupt is triggered
  MCPWM[MCPWM_UNIT_0]->int_ena.val = CAP0_INT_EN | CAP1_INT_EN;  //Enable interrupt on  CAP0 and CAP1 signal
  mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);  //Set ISR Handler

  vTaskDelete(NULL);
}

void setup(void)
{
  delay(500);
  Serial.begin(115200);
  Serial.println("\nTesting MCPWM...");
  cap_queue = xQueueCreate(1, sizeof(capture)); //comment if you don't want to use capture module
  current_cap_value = (uint32_t *)malloc(CAP_SIG_NUM * sizeof(uint32_t));
  previous_cap_value = (uint32_t *)malloc(CAP_SIG_NUM * sizeof(uint32_t));
  xTaskCreate(disp_captured_signal, "mcpwm_config", 4096, NULL, 5, NULL);
  xTaskCreate(gpio_test_signal, "gpio_test_signal", 4096, NULL, 5, NULL);
  xTaskCreate(mcpwm_example_config, "mcpwm_example_config", 4096, NULL, 5, NULL);
}

void loop () {}

Anm.: Manche Kommentare können falsch sein!

Anzeige
CAP0 : 14999 us	CAP1 : 5000 us	33 Prozent	ADC Read-Val 3145

So, nur mit PWM?

Möglicherweise hast Du eine Fehlermeldung übersehen:

E (1162) mcpwm: mcpwm_gpio_init(139): MCPWM GPIO NUM ERROR

Vermutliche Quelle: mcpwm_set_pin(MCPWM_UNIT_0, &pin_pwm_config);

Versuche mal: mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, gpio_num_t(12));

von welchem Projekt redest du? also bei meinem Projekt von post #1 bekomme ich keine Fehlermeldung und lt. Espressif Doku kann man ja beide Versionen verwenden.

mcpwm_set_pin()
mcpwm_gpio_init()

Ich verwende übrigens das Board Node32s oder ESP32DevModule oder auch ESP32WROOM macht alles keinen Unterschied und die ESP Bibliothek 2.0.7.

Also ich habe dein Projekt nun zum laufen gebracht aber leider mit dem gleichen Erkenntnissen wie meine Versuche auch schon ergeben haben:

In deinem Projekt triggerst du den Interrupt via GPIO_CAP0_IN und GPIO_CAP1_IN und hier verwendest du GPIO 23 und GPIO25.
Weiter hast du jetzt noch eingebaut, dass du dann bei Trigger von GPIO_CAP0_IN den Wert vom GPIO35 (ADC1_CHANNEL_7) liest Das funktioniert auch!.... so weit so gut.
Problem ist nur, dass in meinem Projekt GPIO_CAP0_IN (Trigger für Interrupt) gleichzeitig ADC1_CHANNEL_7 sein soll. Konfiguriert man ein und denselben GPIO mit diesen 2 Funktionen dann funktioniert entweder das eine nicht oder das andere nicht... ob man im Betrieb einen GPIO umkonfigurieren kann weiß ich nicht und das habe ich auch leider noch nicht geschafft aber ich habe mich auch nicht speziell damit auseinander gesetzt.

  1. Mein Ansatz hat deshalb darauf abgezielt, den Interrupt mit dem "PWM-Erzeugungssignal" zu triggern. In deinem Projekt GPIO_SEL_12. So wie du das machst mit dem 20ms / 50Hz Signal wäre das auch absolut kein Problem, da der ESP ja genügend Zeit hätte nur habe ich eben ein 1kHz Signal mit einem Duty-Cycle von min. 10% und für so ein PWM-Signal benötigt man (denke ich) unbedingt eine Funktion z.B. mcpwm oder ledcAttachPin() andere Möglichkeiten kenne ich nicht. Bei mcpwm gibt es dann lt. doku die Funktion mcpwm_isr_register() die du ja auch verwendest jedoch für die mcpwm_capture_enable() Funktion.
    Ich möchte aber mit der mcpwm_isr_register() den Interrupt mit (in deinem Projekt) GPIO_SEL_12 registrieren und das gelingt leider auch nicht.

Ideen?

Danke für deine Unterstützung!

Ich schon:

Start Setup.......
initializing mcpwm gpio and adc for CP-PWM...

ADC Read-Val 0
Interrupt Test 0E (1162) mcpwm: mcpwm_gpio_init(139): MCPWM GPIO NUM ERROR

ADC Read-Val 142
Interrupt Test 0

Win10, IDE 1.8.19, ESP Core 2.0.6

Hast Du die Pins 12, 23 und 25 verbunden?

GPIO zu verwenden, ist natürlich eine Krücke, aber wenn der direkte Weg nicht geht, kann man ja mal einen Umweg probieren :slightly_smiling_face:

Langfristig mußt Du auch diese Warnung berücksichtigen:

warning: 'esp_err_t mcpwm_capture_enable(mcpwm_unit_t, mcpwm_capture_signal_t, mcpwm_capture_on_edge_t, uint32_t)' is deprecated: please use mcpwm_capture_enable_channel instead [-Wdeprecated-declarations]

Wow sorry ich wusste nicht dass du um diese Uhrzeit auch noch fleißig bist... ich habe eben meine Erkenntnisse in post#14 editiert.

Ich weiß jetzt nicht wie ich mit diesem Fehler umgehen soll, da ich diesen nicht reproduzieren kann:

Interrupt Test 0E (1162) mcpwm: mcpwm_gpio_init(139): MCPWM GPIO NUM ERROR

.. fällt dir zu meinem Erkenntnissen zu post#14 noch was ein?

Edit:
Ich kann den Fehler reproduzieren! Habe aber keine Ahnung warum dieser sporadisch beim Kompilieren kommt, da er bei mir nicht jedes mal kommt, jedoch jedes mal das PWM Signal sauber erzeugt wird.... ich habe jetzt einfach mal ein delay(1000); hinzugefügt und interessanterweise ist der Fehler nun nicht mehr vorhanden.

Code mit delay(1000)
//######################### PWM Signal mit MCPWM
    // Hier wird der Ausgangspin für das PWM-Signal konfiguriert
    mcpwm_pin_config_t pin_pwm_config;
    pin_pwm_config.mcpwm0a_out_num = 32;      
    // MCPWM-Zuweisung
    mcpwm_set_pin(MCPWM_UNIT_0, &pin_pwm_config);
    // Hier wird der PWM-Signalgenerator konfiguriert
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 1000;  // 1kHz
    pwm_config.cmpr_a = 50;       // Duty-Cycle von 50%
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);  //Set ISR Handler
    delay(1000);
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
    // Starten des PWM-Signals
     mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);

ich habe das jetzt hinbekommen und zwar mit den Arduino Funktionen. Hier mein Simpler Code.

#include <Arduino.h>
#include <driver/ledc.h>

#define LEDC_CHANNEL_0 0
#define LEDC_TIMER_13 13
#define LEDC_BASE_FREQ 1000
#define LEDC_GPIO_NUM 32

int count;


void IRAM_ATTR onInterrupt() {
  count = analogRead(35);
}

void setup() {
    Serial.begin(115200);
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, 8);
  ledcAttachPin(LEDC_GPIO_NUM, LEDC_CHANNEL_0);

  ledcWrite(LEDC_CHANNEL_0, 128);

  attachInterrupt(digitalPinToInterrupt(LEDC_GPIO_NUM), onInterrupt, RISING);
}

void loop() {

      ledcWrite(LEDC_CHANNEL_0, 20);
   Serial.printf("\nCounter %d", count);
   delay(500);
}

ist jetzt zwar mal ohne RTOS aber das scheint schonmal zu funktionieren.... Ich muss das jetzt mal in meinem Original-Projekt testen und natürlich wären mir ESP-IDF Funktionen lieber aber.. is so :wink:

Ich sehe den immer, auch mit dem Programm aus #16. Bitte ergänze mal diese Verzögerung:

void setup() {
  delay(500);
  Serial.begin(115200);
  Serial.println("\nStart Setup.......");

Ausgabe:

Start Setup.......
initializing mcpwm gpio and adc for CP-PWM...

ADC Read-Val 0
Interrupt Test 0E (1162) mcpwm: mcpwm_gpio_init(139): MCPWM GPIO NUM ERROR

ADC Read-Val 142
Interrupt Test 0

soo aber jetzt kommt kein fehler mehr...

<Code>
#include <driver/mcpwm.h>
#include <soc/mcpwm_struct.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>

static esp_adc_cal_characteristics_t adc_chars;
int test;

static void IRAM_ATTR isr_handler(void *arg){
  test++;
}

static void A_TaskCP(void *arg) {

  Serial.printf("initializing mcpwm gpio and adc for CP-PWM...\n");
//######################### Initialisieren Sie den ADC-Kanal
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); 
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);

//######################### PWM Signal mit MCPWM
    // Hier wird der Ausgangspin für das PWM-Signal konfiguriert
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, 32);
  //  mcpwm_pin_config_t pin_pwm_config;
  //  pin_pwm_config.mcpwm0a_out_num = 32;      
    // MCPWM-Zuweisung
  //  mcpwm_set_pin(MCPWM_UNIT_0, &pin_pwm_config);
    // Hier wird der PWM-Signalgenerator konfiguriert
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 1000;  // 1kHz
    pwm_config.cmpr_a = 50;       // Duty-Cycle von 50%
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_isr_register(MCPWM_UNIT_0, isr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);  //Set ISR Handler
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
    // Starten des PWM-Signals
     mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);
 // vTaskDelete(NULL);
  while (1) {
    //Serial.printf("\r\nTASK CP");  
    vTaskDelay(1000 / portTICK_PERIOD_MS); // verzögere den Task um 1000ms  
  }
}

static void A_Task1(void *arg) {
  while (1) {
    //  ADC-Messwert abfragen
    uint32_t raw_value = adc1_get_raw((adc1_channel_t)ADC1_CHANNEL_7);
    //  Berechnen des Spannungswerts aus dem ADC-Messwert
    uint32_t adcRead = esp_adc_cal_raw_to_voltage(raw_value, &adc_chars);
    Serial.printf("\r\nADC Read-Val %d", adcRead);  
    Serial.printf("\r\nInterrupt Test %d", test);  
    vTaskDelay(1000 / portTICK_PERIOD_MS); // verzögere den Task um 1000ms
  }
}

void setup() {
  delay(500);
  Serial.begin(115200);
  Serial.println("\nStart Setup.......");

  xTaskCreate(A_TaskCP, "Task_CP_ReadWrite", 4096, NULL, 5, NULL);
  xTaskCreate(A_Task1, "Task_1_Normal_Operation", 4096, NULL, 5, NULL);
}

void loop() {}


aber der Interrupt wird dennoch nicht ausgeführt!? PWM-Signal wird (so wie vorhin und auch mit dem Fehler) ordnungsgemäß erzeugt..... ideen?

Das kann ich bestätigen.

Diese Zeile schafft eine Verbindung zur Interrupt Service Routine mit Namen isr_handler. Es fehlt aber eine Freigabe und was den Interrupt auslösen soll.

Bei den GPIOs sind das mcpwm_capture_enable und MCPWM[MCPWM_UNIT_0]->int_ena.val.

Das folgende Beispiel habe ich lauffähig gemacht:

Testprogramm
// Quelle: https://esp32.com/viewtopic.php?t=23878
#include <driver/gpio.h>
#include "driver/mcpwm.h"
#include "soc/mcpwm_reg.h"
#include "soc/mcpwm_struct.h"


//Pins

  #define DIR_PIN   14 //direction
  #define STEP_PIN  27 //step
  #define EN_PIN    26 //enable

// cannot be updated while timer is running => fix it to 0
#define TIMER_PRESCALER 0

static mcpwm_dev_t *mcpwm = &MCPWM0;

void breakpoint(String txt){
  Serial.println(txt);
  Serial.println("Press enter...");
  while (!Serial.available()){delay(1);}
  while (Serial.available()){Serial.read();}
}

volatile int count=0;

static void IRAM_ATTR toto(void *) {
  uint32_t mcpwm_intr_status = mcpwm->int_st.val; //Read interrupt status
  count++;
  mcpwm->int_clr.val = mcpwm_intr_status;
}

void setup() {
  delay(500);
  Serial.begin(115200);
  Serial.println("\nHello!");

  //pre-config
  mcpwm->int_ena.val = 0;  // disable all interrupts
  mcpwm->int_clr.val = 0;  // clear all interrupts
  mcpwm_isr_register(MCPWM_UNIT_0, toto, NULL, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, NULL);  //Set ISR Handler (active interrupts will be set later)

  mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, STEP_PIN);
  mcpwm_config_t pwm_config;
    pwm_config.frequency = 1000;    //frequency,
    pwm_config.cmpr_a = 0;        //duty cycle of PWMxA = 0
    pwm_config.cmpr_b = 0;        //duty cycle of PWMxb = 0
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;

  mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A & PWM0B with above settings

  //stop
  mcpwm_set_signal_high(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A);

  Serial.println (mcpwm->int_raw.val,BIN);

  mcpwm->int_ena.val = BIT(0); //Interrupt when timer 0 stops//
  Serial.println (mcpwm->int_ena.val,BIN);

  breakpoint("mcpwm ready, output should be HIGH");
  
  //send 1kHz freq - duty80%
  mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, 80);
  mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); //call this each time, if operator was previously in low/high state

  Serial.println (mcpwm->int_raw.val,BIN);

  breakpoint("1kHz - duty 80%");

  mcpwm_set_frequency(MCPWM_UNIT_0, MCPWM_TIMER_0,10000);

  Serial.println (mcpwm->int_raw.val,BIN);

  breakpoint("10kHz - duty 80%");

  mcpwm_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);

  Serial.println (mcpwm->int_raw.val,BIN);

  breakpoint("stopped?");

  mcpwm_start(MCPWM_UNIT_0, MCPWM_TIMER_0);

  Serial.println (mcpwm->int_raw.val,BIN);

  breakpoint("restarted?");

  Serial.println (mcpwm->int_raw.val,BIN);
  unsigned long t=micros();
  Serial.println(String(count));

  breakpoint("read again?");

  t=micros()-t;
  Serial.println(String(count)+":"+String(t));
  
}

void loop() {
  // put your main code here, to run repeatedly:
}
Ausgabe

Hello!
111111000000000001000
1
mcpwm ready, output should be HIGH
Press enter...
111111000000001001000
1kHz - duty 80%
Press enter...
111111000000001001000
10kHz - duty 80%
Press enter...
111111000000001001000
stopped?
Press enter...
111111000000001001000
restarted?
Press enter...
111111000000001001000
1
read again?
Press enter...
1:2863960

Wichtig ist die "1", denn sie zeigt einen Interrupt an. Die Millisekunden dahinter werden schwanken, je nach Deiner Schnelligkeit.

Diese Zeile ist die für Dich spannende, denn sie gibt an, wann der Interrupt ausgelöst werden soll:

mcpwm->int_ena.val = BIT(0); //Interrupt when timer 0 stops//

Du willst eine andere Bedingung, die Du in der Datei soc\mcpwm_struct.h im Bereich "Type of int_ena register" findest. Leider verstehe ich "TEZ, TEP, TEA und TEB event" nicht. Da hoffe ich mal auf @Doc_Arduino oder @MicroBahner oder andere Timer-Spezialisten. Alle mit Timer 0 erzeugen aber anstelle der "1" eine große Zahl, also viele Interrupts. Eins davon müßte für Dich passen.

Soweit meine Ideen.

1 Like