BojanJurca / Esp32_oscilloscope

ESP32 oscilloscope - see the signals through Web browser the way ESP32 sees them
Creative Commons Zero v1.0 Universal
741 stars 84 forks source link

Read from PWM #19

Closed gin66 closed 1 month ago

gin66 commented 1 year ago

Nice project and thanks for sharing. In regard to this comment of yours:

I do not know how to digitalRead PWM signals directly from GPIOs that output them. Any useful idea would be greatly appreciated.

In my project FastAccelStepper, the mcpwm are used to output stepper signals. Those pulses are read back from the pin and fed into the pcnt modules. Perhaps similar approach works for your issue. The relevant code is:

void StepperQueue::connect_mcpwm_pcnt() {
  const struct mapping_s *mapping = (const struct mapping_s *)driver_data;
  mcpwm_unit_t mcpwm_unit = mapping->mcpwm_unit;
  mcpwm_gpio_init(mcpwm_unit, mapping->pwm_output_pin, _step_pin);
  // Doesn't work with gpio_matrix_in
  //  gpio_matrix_in(step_pin, mapping->input_sig_index, false);
  gpio_iomux_in(_step_pin, mapping->input_sig_index);
}

The key is gpio_iomux_in(). Via github search could not see, that you use this function. Perhaps new to you and a direction towards a solution. even though, no turn-key solution.

BojanJurca commented 1 year ago

Thank you. I've taken a look into your FastAccelStepper, I may need to use it some day. You're right, I'm not familiar with gpio_iomux_in function. I tried to find some documentation but it is rather scarce. If I understand correctly it establishes an internal connection to another GPIO and the result would be similar to using a wire? Do you have an idea which one of the signals from gpio_sig_map.h (https://github.com/pycom/esp-idf-2.0/blob/master/components/esp32/include/soc/gpio_sig_map.h) should I use?

Bojan

gin66 commented 1 year ago

Quite good documentation is here from page 45: (https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)

Eventually it is even easier:

4.2.3 Simple GPIO Input
The GPIO_IN_REG/GPIO_IN1_REG register holds the input values of each GPIO pad.
The input value of any GPIO pin can be read at any time without configuring the 
GPIO Matrix for a particular peripheral signal. However, it is necessary to enable 
the input in the IO_MUX by setting the FUN_IE bit in the IO_MUX_x_REG register 
corresponding to pad X, as mentioned in Section 4.2.2.
gin66 commented 1 year ago

The respective macro for setting FUN_IE seems to be: `

define PIN_INPUT_ENABLE(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME,FUN_IE)`

BojanJurca commented 1 year ago

I'm still having difficulties getting it to work with Arduino. If I PIN_INPUT_ENABLE weather in setup () or in loop () the result is the same - ESP32 reboots with:

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

The code I used for testing:

#include <WiFi.h>
#include <soc/gpio_sig_map.h>

void setup () {
  Serial.begin (115200);

  #define PIN_INPUT_ENABLE(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME,FUN_IE)
  PIN_INPUT_ENABLE (2);

  // start PWM on built-in LED
  ledcSetup (0, 1000, 10);
  ledcAttachPin (2, 0); // built-in LED
  ledcWrite (0, 307); // 1/3 duty cycle
}

void loop () {
  delay (1000);
  Serial.printf ("%i\n", digitalRead (2)); // expected: 1/3 of 1 and 2/3 of 0 on built-in LED
}

Do you have any ideas?

gin66 commented 1 year ago

Try this:

#include <soc/gpio_sig_map.h>
#include <soc/io_mux_reg.h>

#define LED 2

void setup () {
  Serial.begin (115200);

  // start PWM on built-in LED
  ledcSetup (0, 12, 10);
  ledcAttachPin (LED, 0); // built-in LED
  ledcWrite (0, 307); // 1/3 duty cycle

  PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG[2]);
}

void loop () {
  delay(10);
  Serial.printf("%i", digitalRead(LED));
//  Serial.printf ("%i\n", gpio_input_get()); // This reads all 32 port bits at once
}
gin66 commented 1 year ago

Two more comments:

BojanJurca commented 1 year ago

Indeed. There are some combinations that just don't work well. For example 1000 Hz PWM and 10 ms sampling. It is strange though why 1200 Hz with 12 ms sampling works.

On the other hand oscilloscope normally does not use "delay" but rather "delayMicroseconds" at higher frequencies which I believe is not RTOS but an Arduino function so it doesn't have to do much with interrupters and everything works just fine. I'll include your solution to the Oscilloscope. Thank you.

image

gin66 commented 1 year ago

"For example 1000 Hz PWM and 10 ms sampling." => 1000 Hz = 1ms, which is 1/10th with 10ms

"It is strange though why 1200 Hz with 12 ms sampling works." => 1200Hz = 0.833ms, which 1:14.4=5:72 of 12ms. So not strange at all

BojanJurca commented 1 month ago

Hi gin66.

I'm sorry to bother you again with the same topic. PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG[...]) worked very well with IDF 4.x. After the upgrade to IDF 5.x I've been struggling for days to get it working, with no success. Although the code compiles without any problems, digitalRead only returns 0 if GPIO is not configured in INPUT mode (if it is configured as OUTPUT or PWM for example). Can you help me with this, please?

gin66 commented 1 month ago

Sorry, but the IDF5 gives me headaches, too, so I only can give you link to migration guide.

My lib is absolutely broken with IDF5 and porting mcpwm/pcnt/rmt drivers is close to a new development….for the EXACT same hardware. And your experience tells me, that there is chance, that this is not even possible anymore….on the EXACT same hardware.

BojanJurca commented 1 month ago

Thank you for your information. I hope we'll find something out.

BojanJurca commented 1 month ago

This is just a partial success:

Using gpio_get_level (gpio_pin) instead of digitalRead (gpio_pin) helps reading pins configured as OUTPUT, but doesn't help with PWM signals at all.

I tried writing a modified version of gpio_get_level function that basically returns (_gpio_hal.dev->out >> gpio_num) & 0x1, instead original one that returns  (_gpio_hal.dev->in>> gpio_num) & 0x1, but this didn't help either.

I guess I'll go with this for now, until a better solution is found.

BojanJurca commented 1 month ago

This seems to be a full solution. Basically, instead of using PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG[... we can now use gpio_hal_input_enable (...

Here is a whole test script:

#include "driver/gpio.h"
#include "hal/gpio_hal.h"

void setup () {
    Serial.begin (115200);

    #define PWM_PIN 16

    // generate PWM signal on PWM_PIN ...
    ledcAttach (PWM_PIN, 1000, 10);
    ledcWrite (PWM_PIN, 307); // 307 out of 1024 (10 bit resolution) = 1/3

    // ... or just set the output value of PWM_PIN
    // pinMode (PWM_PIN, OUTPUT);
    // digitalWrite (PWM_PIN, HIGH);

    // enable input on PWM_PIN
    static gpio_hal_context_t _gpio_hal = {
        .dev = GPIO_HAL_GET_HW (GPIO_PORT_0)
    };    
    gpio_hal_input_enable (&_gpio_hal, PWM_PIN);

    // test reading PWM_PIN
    for (int i = 0; i < 100; i ++) {
        delayMicroseconds (111);
        Serial.println (gpio_get_level ((gpio_num_t) PWM_PIN));
    }

}

void loop () {

}
gin66 commented 1 month ago

great News! Thanks for figuring out and sharing the solution. So I can make use of this in my lib, too.