Forum: Mikrocontroller und Digitale Elektronik Arduino-ESP32 LEDC API ledcAttach() resolution


von Alexander (alecxs)


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

von Rainer W. (rawi)


Lesenswert?

Alexander schrieb:
> Ich bekomme entweder Fehler für div_param oder
> der GPIO bleibt einfach HIGH.

Wie hast du es denn probiert und welchen Wert gibt ledcAttach() zurück?

von Norbert (der_norbert)


Lesenswert?

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

von Alexander (alecxs)


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
von Rainer W. (rawi)


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
von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

siehe Bsp.

von Alexander (alecxs)


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
von Alexander (alecxs)


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
von Alexander (alecxs)


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
von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Hm, war da nicht irgendwas, daß bei den LEDC Funktionen irgendwas beim 
neuen Arduino-Core geändert wurde?

von Alexander (alecxs)


Lesenswert?

Ich hab mal bisschen gesucht, vielleicht bin ich auf dieses Problem 
gestoßen und der Pin war gar nicht HIGH.

https://forum.arduino.cc/t/ledcattach-on-esp32-for-different-frequencies/1331942

https://forum.arduino.cc/t/fighting-ledc-on-esp32/1359165

: Bearbeitet durch User
von Rainer W. (rawi)


Lesenswert?

Alexander schrieb:
> Ich hab mal bisschen gesucht, vielleicht bin ich auf dieses Problem
> gestoßen und der Pin war gar nicht HIGH.

Der Zustand eines Pins sollte sich doch herausfinden lassen. Hänge 
einfach einen Pull-Up und einen gleich großen Pull-Down Widerstand dran 
(z.B. 4k7) und miss die Spannung.

von Alexander (alecxs)


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
von Alexander (alecxs)


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
von Joachim B. (jar)


Lesenswert?

Rainer W. schrieb:
> Wie hast du es denn probiert und welchen Wert gibt ledcAttach() zurück?

keine Rückfragen, der To möchte nur Antworten und nicht lernen.

von Alexander (alecxs)


Lesenswert?

Thread zum trollen freigegeben.

von Joachim B. (jar)


Lesenswert?

mußt du nicht extra ansagen, weiß man sofort wenn man deinen Nick liest.

von Alexander (alecxs)


Lesenswert?

Blödsinn, das war Dein Auftritt hier.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.