Arduino-ESP32 LEDC API ledcAttach() resolution

#7952549
Lesenswert?

Wie muss ich den ESP32 konfigurieren damit ich ein PWM von 1Hz mit 50 % Tastverhältnis erreiche? Ich bekomme entweder Fehler für div_param oder der GPIO bleibt einfach HIGH.

https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/ledc.html#arduino-esp32-ledc-api

#7952577
Lesenswert?

Aus dem setup() heraus. Dachte schon es liegt an falschen Datentypen, habe auch mal direkt alle Werte hardcoded eingetragen. Hab schon ein paar verschiedene Auflösungen ausprobiert. Alles unter res < 10 gibt Fehlermeldung mit Return Code false, ab res = 10 true. Duty immer 2^res / 2 eingesetzt, auch wenn das die Doku nicht so explizit hergibt.

E (622) ledc: requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=0

1
#define WO GPIO_NUM_32
2
void startWatchdog(gpio_num_t wpin) {
3
  const unsigned long wfreq = 1;    // 1 Hz
4
  const unsigned char wres = 11;    // resolution 2048
5
  const unsigned long wduty = 1024; // 50%
6
  ledcAttach((uint8_t) wpin, wfreq, wres);
7
  ledcWrite((uint8_t) wpin, wduty);
8
}
9
void setup() {
10
  pinMode(WO, OUTPUT);
11
  startWatchdog(WO);
12
}
13
void loop() {
14
  // main
15
}

ledcRead() und ledcReadFreq() geben den gesetzen Wert zurück bei höheren Frequenzen, aber mit 1 Hz kommt Wert 0 trotz Returncode true.

Kann es sein dass ledcWrite() in die main loop() muss?

: Bearbeitet durch User
#7952936
Lesenswert?

Alexander schrieb:

Kann es sein dass ledcWrite() in die main loop() muss?

Nein, warum willst du das Tastverhältnis mehrere tausend Mal pro Sekunde neu setzen?

ledcRead() und ledcReadFreq() geben den gesetzen Wert zurück bei höheren Frequenzen, aber mit 1 Hz kommt Wert 0 trotz Returncode true.

Worauf werden die übergebenen Parameter denn überprüft, um den Returncode zu generieren? Wie sieht das erzeugte Signal bei freq=2 und eingestelltem Tastverhältnis 50% aus? Sonst probiere einfach einmal eine für LEDs angemessene PWM-Frequenz, z.B. 1 kHz.

: Bearbeitet durch User
#7952997
Lesenswert?

Hallo Veit,

danke, mit 5 kHz hab ich kein Problem (meine 4 kHz PWM funktionieren bei mir).

Rainer W. schrieb:

Wie sieht das erzeugte Signal bei freq=2 und eingestelltem Tastverhältnis 50% aus?

Je nach resolution entweder return false oder Pin hängt auf HIGH. Ab 10 Hz funktioniert es.

Rainer W. schrieb:

Worauf werden die übergebenen Parameter denn überprüft, um den Returncode zu generieren?

Die Funktionen ledcRead() und ledcReadFreq() geben die gesetzte wfreq = ledc_get_freq(group, timer); und wduty = ledc_get_duty(group, channel); aus, so wie ich es mit ledcAttach() und ledcWrite() eingegeben habe.

ledcAttach() ist ein Wrapper und reicht den Returncode von ledcAttachChannel() durch. Wenn Du dort durchblickst sag Bescheid.

https://github.com/espressif/arduino-esp32/blob/release/v3.0.x/cores/esp32/esp32-hal-ledc.c#L94

Norbert schrieb:

Ohne den ESP im Detail zu kennen, manchmal reicht der interne Vorteiler einfach nicht aus für diese extrem niedrigen Raten.

Angeblich soll die Clock automatisch umschalten, es stehen 80 MHz und 1 Mhz zur Auswahl (LEDC_REF_TICK). Der GPIO32 ist ein RTC Pin und kann nur LEDC_LOW_SPEED_MODE.

https://github.com/espressif/esp-idf/blob/632e0c2a/components/hal/include/hal/ledc_types.h#L75

Leider habe ich diesen div_param noch nicht in den Sources gefunden. Ich lass dann mal grep über den gesamten Rechner laufen..

1
E (622) ledc: requested frequency and duty resolution can not be 
2
achieved, try reducing freq_hz or duty_resolution. div_param=0
: Bearbeitet durch User
#7953209
Lesenswert?

Nüscht gefunden. Dafür so eine Art Bruteforce Sketch. Fängt mit 1 Hz an.

https://github.com/espressif/arduino-esp32/blob/release/v3.0.x/libraries/ESP32/examples/AnalogOut/ledcFrequency/ledcFrequency.ino

1
Bit resolution | Min Frequency [Hz] | Max Frequency [Hz]
2
             1 |                489 |           40078277
3
             2 |                245 |           20039138
4
             3 |                123 |           10019569
5
             4 |                 62 |            5009784
6
             5 |                 31 |            2504892
7
             6 |                 16 |            1252446
8
             7 |                  8 |             626223
9
             8 |                  4 |             313111
10
             9 |                  2 |             156555
11
            10 |                  1 |              78277
12
            11 |                  1 |              39138
13
            12 |                  1 |              19569
14
            13 |                  1 |               9784
15
            14 |                  1 |               4892
16
            15 |                  1 |               2446
17
            16 |                  1 |               1223
18
            17 |                  1 |                611
19
            18 |                  1 |                305
20
            19 |                  1 |                152
21
            20 |                  1 |                 76
: Bearbeitet durch User
#7953261
Lesenswert?

Basierend auf den structs aus <driver/ledc.h> habe ich nun die Clock auf LEDC_REF_TICK manuell gesetzt. Das funktioniert soweit auch mit 10 Bit Auflösung und nun endlich mit 1 Hz.

https://github.com/espressif/esp-idf/blob/632e0c2a/components/driver/ledc/include/driver/ledc.h#L61

1
void startWatchdog(gpio_num_t wpin, const unsigned long wfreq) {
2
  ledc_timer_config_t ledc_timer = {
3
    .speed_mode      = LEDC_LOW_SPEED_MODE,
4
    .duty_resolution = LEDC_TIMER_10_BIT,
5
    .timer_num       = LEDC_TIMER_0,
6
    .freq_hz         = wfreq,
7
    .clk_cfg         = (ledc_clk_cfg_t) LEDC_REF_TICK
8
  };
9
  ledc_timer_config(&ledc_timer);
10
  ledc_channel_config_t ledc_channel = {
11
    .gpio_num    = wpin,
12
    .speed_mode  = LEDC_LOW_SPEED_MODE,
13
    .channel     = LEDC_CHANNEL_0,
14
    .intr_type   = LEDC_INTR_DISABLE,
15
    .timer_sel   = LEDC_TIMER_0,
16
    .duty        = 512,
17
    .hpoint      = 0,
18
    .flags       = { .output_invert = 0 }
19
  };
20
  ledc_channel_config(&ledc_channel);
21
  ledc_update_duty(ledc_channel.speed_mode, ledc_channel.channel);
22
}

Allerdings kann ich nicht aus Arduino heraus auf die Logik mit ledc_handle.used_channels zugreifen. Mir ist nicht klar ob ledcAttachChannel() überhaupt erkennen kann dass ich bereits einen C̶h̶a̶n̶n̶e̶l̶ a̶t̶t̶a̶c̶h̶e̶d̶ habe / Kanal belegt (für Falk)... neues Problem. ledcAttach() würde ich ja für die übrigen PWM (4 kHz) trotzdem gerne nutzen.

Wo das div_param=0 herkommt ist mir noch ein Rätsel.

: Bearbeitet durch User
#7953488
Lesenswert?

Ich hatte auch das neue

Alexander schrieb:

Tooltop ET828Pro für €27,99

kurz dran aber das Gerät ist irgendwie komisch, da muss man jedesmal die Range Taste drücken und dann analysiert es erstmal 2 Sekunden bevor es skaliert. Hab dann auf LED_BUILTIN gewechselt da ich keine Hand frei hatte für die Taste und zu faul es ordentlich aufzubauen, und die LED hatte halt nur geleuchtet nicht geblinkt.

Ich hab das nun nachgeholt, aber kann nicht bestätigen dass es sich um o.g. genanntes Problem, der Channel gehöre zur selben Group und würde daher die Frequenz durch nachfolgende ledcAttach() mit 4 kHz überschreiben, handelt; der Pin bleibt wohl tatsächlich einfach HIGH. Oder das Oszi rafft es nicht.

Das Gute ist, zumindest funktioniert die manuelle Methode mit hardcoded timer=0 und channel=0 für 1 Hz in einem Sketch zusammen mit den ledcAttach() mit 4 kHz. Ob das nun Zufall ist oder Glück, zufrieden bin ich damit noch nicht.

Laut Log wird der Channel angeblich überschrieben. Aber man weiß ja nicht wie die Debugausgaben zusammengeschustert sind, oder ob ledc_handle.used_channels einfach woanders anfängt mit zählen.

1
#define WO   GPIO_NUM_32
2
#define PWM1 GPIO_NUM_12
3
#define PWM2 GPIO_NUM_13
4
#define PWM3 GPIO_NUM_14
5
#define PWM4 GPIO_NUM_27
6
void startWatchdog(...) {...} // s.o. www.mikrocontroller.net/topic/goto_post/7953261
7
void setup() {
8
  const int freq = 4000;
9
  const uint8_t res = 8;
10
  uint8_t duty = 128;
11
  startWatchdog(WO, 1);
12
  ledcAttach(PWM1, freq, res);
13
  ledcAttach(PWM2, freq, res);
14
  ledcAttach(PWM3, freq, res);
15
  ledcAttach(PWM4, freq, res);
16
  ledcWrite(PWM1, duty);
17
  ledcWrite(PWM2, duty);
18
  ledcWrite(PWM3, duty);
19
  ledcWrite(PWM4, duty);
20
}
1
[  1699][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type LEDC (12) successfully set to 0x400ecdf0
2
[  1710][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 12 successfully set to type LEDC (12) with bus 0x3ffb5744
3
[  1721][I][esp32-hal-ledc.c:166] ledcAttachChannel(): LEDC attached to pin 12 (channel 0, resolution 8)
4
[  1730][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type LEDC (12) successfully set to 0x400ecdf0
5
[  1741][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 13 successfully set to type LEDC (12) with bus 0x3ffb5764
6
[  1751][I][esp32-hal-ledc.c:166] ledcAttachChannel(): LEDC attached to pin 13 (channel 1, resolution 8)
7
[  1761][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type LEDC (12) successfully set to 0x400ecdf0
8
[  1772][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 14 successfully set to type LEDC (12) with bus 0x3ffb5784
9
[  1782][I][esp32-hal-ledc.c:166] ledcAttachChannel(): LEDC attached to pin 14 (channel 2, resolution 8)
10
[  1791][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type LEDC (12) successfully set to 0x400ecdf0
11
[  1802][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 27 successfully set to type LEDC (12) with bus 0x3ffb57a4
12
[  1813][I][esp32-hal-ledc.c:166] ledcAttachChannel(): LEDC attached to pin 27 (channel 3, resolution 8)
13
------------------------------------------
14
GPIO Info:
15
------------------------------------------
16
  GPIO : BUS_TYPE[bus/unit][chan]
17
  --------------------------------------
18
    12 : LEDC[0][0]
19
    13 : LEDC[0][1]
20
    14 : LEDC[0][2]
21
    27 : LEDC[0][3]
22
============ After Setup End =============
: Bearbeitet durch User
#7953691
Lesenswert?

Ich hab mir jetzt die Kommentare in den Sources noch mal durchgelesen. In esp32-hal-ledc.c steht zum einen "Need to be fixed" das ist schon mal etwas beunruhigend.

1
//Use XTAL clock if possible to avoid timer frequency error when setting APB clock < 80 Mhz
2
//Need to be fixed in ESP-IDF
3
#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
4
#define LEDC_DEFAULT_CLK LEDC_USE_XTAL_CLK
5
#else
6
#define LEDC_DEFAULT_CLK LEDC_AUTO_CLK
7
#endif

Dazu hab ich noch die passende Doku gefunden (ist nicht der gleiche Link wie im Eröffnungspost) da gibt es noch eine Funktion zur Auswahl der Clock, wobei da nicht steht auf was das angewendet wird:

ledcSetCLockSource(LEDC_REF_CLK);

https://docs.espressif.com/projects/arduino-esp32/en/latest/api/ledc.html

Aus den Kommentaren in ledc.h geht hervor dass die Clock für alle Timer gilt, also global, aber so ganz eindeutig ist das noch nicht da wiederum steht "except esp32"

1
/*!< Configure LEDC source clock from ledc_clk_cfg_t.
2
     Note that LEDC_USE_RC_FAST_CLK and LEDC_USE_XTAL_CLK are
3
     non-timer-specific clock sources. You can not have one LEDC timer uses
4
     RC_FAST_CLK as the clock source and have another LEDC timer uses XTAL_CLK
5
     as its clock source. All chips except esp32 and esp32s2 do not have
6
     timer-specific clock sources, which means clock source for all timers
7
     must be the same one. */

In ledc_types.h ist allerdings auch von "LEDC timer-specific clock sources" die Rede, weswegen ich unsicher bin ob meine Einstellung nun global gilt oder nicht.

1
/**
2
 * @brief LEDC timer-specific clock sources
3
 *
4
 * Note: Setting numeric values to match ledc_clk_cfg_t values are a hack to avoid collision with
5
 * LEDC_AUTO_CLK in the driver, as these enums have very similar names and user may pass
6
 * one of these by mistake.
7
 */

Auf jeden Fall ist erstmal geklärt dass es keinen Konflikt mit dem Channel 0 gibt, da es nur zweimal 8x Channels gibt (0-7) und nicht einmal 16x Channels (0-15) so wie der Wrapper ledcAttach() das in ledc_handle.used_channels für sich trackt. Mein hardcoded LEDC_LOW_SPEED_MODE + LEDC_CHANNEL_0 entspricht also dem Arduino Channel 8. Das erklärt warum es überhaupt funktioniert.

In den in ledc.h "brief LEDC update channel parameters" liest man noch, nicht alle GPIO können den Highspeed Mode. Ich nehme an das bezieht sich nur auf GPIO32 + GPIO33, nicht auf alle RTC Pins? Wären sonst ziemlich viele..

1
/**
2
 * @param speed_mode Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode.
3
 */

Die Wrapper Funktion ledcAttach() unterscheidet jedenfalls nicht nach High speed / Low Speed, die weißt einfach den nächstbesten freien Channel 0-15 zu, ohne Einschränkung des GPIO. Das ist meiner Meinung nach ein Bug.

1
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) {
2
  int free_channel = ~ledc_handle.used_channels & (ledc_handle.used_channels + 1);
3
}

Das Channel Mapping in ledcAttachChannel() ist wiederum ziemlich eindeutig. Das ergibt eine fixe Zuordungstabelle.

1
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) {
2
  uint8_t group = (channel / 8), timer = ((channel / 2) % 4);
3
}

Ich dachte daran eine eigene ledcAttach() Wrapper-Funktion als Override im Sketch zu hinterlegen, da ich ja ein eigenes Channel-Tracking brauche wenn ich da die GPIO nach Lowspeed/Highspeed korrekt zuordnen möchte, aber da ich nicht weiß welche GPIO das betrifft und ich nur den einen GPIO32 als Lowspeed brauche, lasse ich das.

Ich werde mich nun also mit ledcAttachChannel() begnügen und den Channel 8 selbst zuweisen, notfalls noch dazu mit ledcSetCLockSource()

: Bearbeitet durch User

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren