Skip to content

cpu/esp32/periph: fix esp32c6 timer clk#22456

Open
mariemC wants to merge 1 commit into
RIOT-OS:masterfrom
mariemC:fix/cpu_esp32_timer_clk
Open

cpu/esp32/periph: fix esp32c6 timer clk#22456
mariemC wants to merge 1 commit into
RIOT-OS:masterfrom
mariemC:fix/cpu_esp32_timer_clk

Conversation

@mariemC

@mariemC mariemC commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Contribution description

This PR fixes the timer clock source used on the ESP32-C6 in the GPTimer peripheral.

The existing implementation always used rtc_clk_apb_freq_get() to calculate the timer clock divider. While this is correct for ESP32, ESP32-S2, ESP32-S3 and ESP32-C3, the ESP32-C6 GPTimer is clocked from the fixed 80 MHz PLL (PLL_F80M_CLK) instead of the APB clock.

Testing procedure

hello world application on esp32c6 board:
Test application: examples/hello-world on an ESP32-C6 board

I used the hello-world example because it is a minimal application that helps me isolate issues and eliminates the influence of other modules or application logic.
I used it the app to check the timer.

Makefile:

BOARD ?= add_your_board
FEATURES_REQUIRED = periph_timer
USEMODULE += ztimer_msec
TIMER_SPEED ?= 1000000

CFLAGS += -DTIMER_SPEED=$(TIMER_SPEED)

main.c

/*
 * SPDX-FileCopyrightText: 2014 Freie Universität Berlin
 * SPDX-License-Identifier: LGPL-2.1-only
 */

/**
 * @ingroup     examples
 * @{
 *
 * @file
 * @brief       Hello World application
 *
 * @author      Kaspar Schleiser <kaspar@schleiser.de>
 * @author      Ludwig Knüpfer <ludwig.knuepfer@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>
#include "ztimer.h"
#include "periph/timer.h"

static void cb(void *arg, int chan)
{
    (void)arg;
    (void)chan;
    puts("timer fired");
}

int main(void)
{
   
    puts("Hello World!");

    printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD);
    printf("This board features a(n) %s CPU.\n", RIOT_CPU);

    printf("Available timers: %i\n", TIMER_NUMOF);
    timer_init(TIMER_DEV(0), 1000000, cb, NULL);
    timer_stop(TIMER_DEV(0));
    
    timer_set(TIMER_DEV(0), 0, 1000000);
    timer_start(TIMER_DEV(0));
    puts("timer set for 1 second, waiting for callback...");
    while (1) {
        ztimer_sleep(ZTIMER_MSEC, 100);
    }
    return 0;
}

without the fix:

2026-07-10 10:21:10,997 # Hello World!
2026-07-10 10:21:10,998 # You are running RIOT on a(n) index_wr_tracker board.
2026-07-10 10:21:10,999 # This board features a(n) esp32 CPU.
2026-07-10 10:21:10,999 # Available timers: 1
2026-07-10 10:21:10,999 # timer set for 1 second, waiting for callback...
2026-07-10 10:21:11,499 # timer fired

with the fix:

2026-07-10 10:22:42,063 # Hello World!
2026-07-10 10:22:42,064 # You are running RIOT on a(n) index_wr_tracker board.
2026-07-10 10:22:42,064 # This board features a(n) esp32 CPU.
2026-07-10 10:22:42,064 # Available timers: 1
2026-07-10 10:22:42,065 # timer set for 1 second, waiting for callback...
2026-07-10 10:22:43,064 # timer fired

Issues/PRs references

None.

Declaration of AI-Tools / LLMs usage:

AI-Tools / LLMs that were used are:

none

@mariemC
mariemC requested a review from gschorcht as a code owner July 10, 2026 09:03
@github-actions github-actions Bot added Platform: ESP Platform: This PR/issue effects ESP-based platforms Area: cpu Area: CPU/MCU ports labels Jul 10, 2026
@mariemC
mariemC force-pushed the fix/cpu_esp32_timer_clk branch from 875c47f to 7768314 Compare July 10, 2026 09:07
@crasbe crasbe added Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors) CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jul 10, 2026
@riot-ci

riot-ci commented Jul 10, 2026

Copy link
Copy Markdown

Murdock results

✔️ PASSED

b70a616 cpu/esp32/periph: fix esp32c6 timer clk

Success Failures Total Runtime
11127 0 11128 10m:17s

Artifacts

Comment thread cpu/esp32/periph/timer.c Outdated
Comment on lines +65 to +70
/* TIMER_CLK_FREQ corresponds to APB_CLK_FREQ for ESP32, ESP32-S2, ESP32-S3,
* ESP32-C2 and ESP32-C3, which is a fixed frequency of 80 MHz. However,
* this only applies to CPU clock frequencies of 80 MHz and above.
* For lower CPU clock frequencies, the APB clock corresponds to the CPU clock
* frequency. Therefore, we need to determine the actual TIMER clock frequency
* from the actual APB clock frequency. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* TIMER_CLK_FREQ corresponds to APB_CLK_FREQ for ESP32, ESP32-S2, ESP32-S3,
* ESP32-C2 and ESP32-C3, which is a fixed frequency of 80 MHz. However,
* this only applies to CPU clock frequencies of 80 MHz and above.
* For lower CPU clock frequencies, the APB clock corresponds to the CPU clock
* frequency. Therefore, we need to determine the actual TIMER clock frequency
* from the actual APB clock frequency. */
/* For ESP32, ESP32-S2, ESP32-S3, ESP32-C2 and ESP32-C3, the TIMER_CLK_FREQ
* is based on APB_CLK_FREQ. This is fixed at 80MHz for CPU clock frequencies
* >=80MHz and equal to the CPU clock frequency for CPU clock frequencies <80Mhz. */

Although this part is copy&pasted from the UART code, I think we can optimize the wording a bit nevertheless.

@crasbe

crasbe commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Can you please give some more details about how you tested the changes? The hello world application shouldn't print anything about timers 🤔

Also, please add the AI statement back to your original PR description.

@mariemC
mariemC force-pushed the fix/cpu_esp32_timer_clk branch from 7768314 to c3da5f2 Compare July 10, 2026 11:41
@crasbe

crasbe commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Without the fix:

2026-07-13 11:36:43,058 # ESP-ROM:esp32c6-20220919
2026-07-13 11:36:43,064 # Build:Sep 19 2022
2026-07-13 11:36:43,070 # rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
2026-07-13 11:36:43,070 # SPIWP:0xee
2026-07-13 11:36:43,071 # mode:DIO, clock div:2
2026-07-13 11:36:43,075 # load:0x4086c110,len:0xa60
2026-07-13 11:36:43,076 # load:0x4086e610,len:0x2644
2026-07-13 11:36:43,077 # load:0x40875720,len:0x6f0
2026-07-13 11:36:43,078 # entry 0x4086c110
2026-07-13 11:36:43,120 # Unicore app
2026-07-13 11:36:43,131 #
2026-07-13 11:36:43,133 # main(): This is RIOT! (Version: 2026.10-devel-47-gb6f2b)
2026-07-13 11:36:43,137 # Hello World!
2026-07-13 11:36:43,138 # You are running RIOT on a(n) esp32c6-devkit board.
2026-07-13 11:36:43,143 # This board features a(n) esp32 CPU.
2026-07-13 11:36:43,143 # Available timers: 1
2026-07-13 11:36:43,147 # timer set for 1 second, waiting for callback...
2026-07-13 11:36:43,633 # timer fired

With the fix:

2026-07-13 11:37:57,614 # ESP-ROM:esp32c6-20220919
2026-07-13 11:37:57,621 # Build:Sep 19 2022
2026-07-13 11:37:57,627 # rst:0x1 (POWERON),boot:0x1c (SPI_FAST_FLASH_BOOT)
2026-07-13 11:37:57,627 # SPIWP:0xee
2026-07-13 11:37:57,628 # mode:DIO, clock div:2
2026-07-13 11:37:57,632 # load:0x4086c110,len:0xa60
2026-07-13 11:37:57,633 # load:0x4086e610,len:0x2644
2026-07-13 11:37:57,633 # load:0x40875720,len:0x6f0
2026-07-13 11:37:57,634 # entry 0x4086c110
2026-07-13 11:37:57,677 # Unicore app
2026-07-13 11:37:57,689 #
2026-07-13 11:37:57,694 # main(): This is RIOT! (Version: 2026.10-devel-46-gc3da5f-fix/cpu_esp32_timer_clk)
2026-07-13 11:37:57,695 # Hello World!
2026-07-13 11:37:57,700 # You are running RIOT on a(n) esp32c6-devkit board.
2026-07-13 11:37:57,700 # This board features a(n) esp32 CPU.
2026-07-13 11:37:57,706 # Available timers: 1
2026-07-13 11:37:57,707 # timer set for 1 second, waiting for callback...
2026-07-13 11:37:58,693 # timer fired

Also very useful for testing this is tests/sys/ztimer_msg:

Current master, clearly running on 2x speed:

2026-07-13 11:58:49,794 # ESP-ROM:esp32c6-20220919
2026-07-13 11:58:49,799 # Build:Sep 19 2022
2026-07-13 11:58:49,804 # rst:0x1 (POWERON),boot:0x7d (SPI_FAST_FLASH_BOOT)
2026-07-13 11:58:49,805 # SPIWP:0xee
2026-07-13 11:58:49,806 # mode:DIO, clock div:2
2026-07-13 11:58:49,810 # load:0x4086c110,len:0xa60
2026-07-13 11:58:49,811 # load:0x4086e610,len:0x2644
2026-07-13 11:58:49,812 # load:0x40875720,len:0x6f0
2026-07-13 11:58:49,813 # entry 0x4086c110
2026-07-13 11:58:49,855 # Unicore app
2026-07-13 11:58:49,868 #
2026-07-13 11:58:49,868 # Help: Press s to start test, r to print it is ready
s
2026-07-13 11:58:51,606 # START
2026-07-13 11:58:51,606 # main(): This is RIOT! (Version: 2026.10-devel-47-gb6f2b)
2026-07-13 11:58:51,611 # This is thread 4
2026-07-13 11:58:51,611 # sending 1st msg
2026-07-13 11:58:51,617 # now=3:479993 -> every 2.0s: Hello World
2026-07-13 11:58:51,617 # sending 2nd msg
2026-07-13 11:58:51,620 # now=3:483276 -> every 5.0s: This is a Test
2026-07-13 11:58:51,621 # This is thread 5
2026-07-13 11:58:52,113 # sec=4 min=0 hour=0
2026-07-13 11:58:52,607 # now=5:480502 -> every 2.0s: Hello World
2026-07-13 11:58:52,611 # sec=5 min=0 hour=0
2026-07-13 11:58:53,113 # sec=6 min=0 hour=0
2026-07-13 11:58:53,608 # now=7:480977 -> every 2.0s: Hello World
2026-07-13 11:58:53,612 # sec=7 min=0 hour=0
2026-07-13 11:58:54,114 # now=8:490745 -> every 5.0s: This is a Test
2026-07-13 11:58:54,114 # sec=8 min=0 hour=0
2026-07-13 11:58:54,607 # now=9:481452 -> every 2.0s: Hello World
2026-07-13 11:58:54,613 # sec=9 min=0 hour=0
2026-07-13 11:58:55,112 # sec=10 min=0 hour=0
2026-07-13 11:58:55,608 # now=11:481926 -> every 2.0s: Hello World
2026-07-13 11:58:55,613 # sec=11 min=0 hour=0
2026-07-13 11:58:56,113 # sec=12 min=0 hour=0
2026-07-13 11:58:56,610 # now=13:482409 -> every 2.0s: Hello World
2026-07-13 11:58:56,615 # now=13:491247 -> every 5.0s: This is a Test

With the fix:

2026-07-13 11:57:18,339 # uild:Sep 19 2022
2026-07-13 11:57:18,344 # rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
2026-07-13 11:57:18,345 # SPIWP:0xee
2026-07-13 11:57:18,345 # mode:DIO, clock div:2
2026-07-13 11:57:18,349 # load:0x4086c110,len:0xa60
2026-07-13 11:57:18,350 # load:0x4086e610,len:0x2644
2026-07-13 11:57:18,350 # load:0x40875720,len:0x6f0
2026-07-13 11:57:18,353 # entry 0x4086c110
2026-07-13 11:57:18,395 # Unicore app
2026-07-13 11:57:18,407 #
2026-07-13 11:57:18,414 # Help: Press s to start test, r to print it is ready
s
2026-07-13 11:57:20,778 # START
2026-07-13 11:57:20,783 # main(): This is RIOT! (Version: 2026.10-devel-46-gc3da5f-fix/cpu_esp32_timer_clk)
2026-07-13 11:57:20,784 # This is thread 4
2026-07-13 11:57:20,784 # sending 1st msg
2026-07-13 11:57:20,788 # now=2:370369 -> every 2.0s: Hello World
2026-07-13 11:57:20,789 # sending 2nd msg
2026-07-13 11:57:20,794 # now=2:374275 -> every 5.0s: This is a Test
2026-07-13 11:57:20,795 # This is thread 5
2026-07-13 11:57:21,786 # sec=3 min=0 hour=0
2026-07-13 11:57:22,782 # now=4:372878 -> every 2.0s: Hello World
2026-07-13 11:57:22,784 # sec=4 min=0 hour=0
2026-07-13 11:57:23,786 # sec=5 min=0 hour=0
2026-07-13 11:57:24,781 # now=6:373107 -> every 2.0s: Hello World
2026-07-13 11:57:24,785 # sec=6 min=0 hour=0
2026-07-13 11:57:25,787 # now=7:378000 -> every 5.0s: This is a Test
2026-07-13 11:57:25,788 # sec=7 min=0 hour=0
2026-07-13 11:57:26,780 # now=8:373336 -> every 2.0s: Hello World
2026-07-13 11:57:26,785 # sec=8 min=0 hour=0
2026-07-13 11:57:27,786 # sec=9 min=0 hour=0
2026-07-13 11:57:28,782 # now=10:373565 -> every 2.0s: Hello World
2026-07-13 11:57:28,785 # sec=10 min=0 hour=0
2026-07-13 11:57:29,785 # sec=11 min=0 hour=0
2026-07-13 11:57:30,781 # now=12:373797 -> every 2.0s: Hello World
2026-07-13 11:57:30,788 # now=12:378242 -> every 5.0s: This is a Test

Out of curiosity, I added two printfs to the timer_init function:

2026-07-13 11:57:18,408 # TIMER_CLK_FREQ: 80000000
2026-07-13 11:57:18,408 # rtc_clk_apb_freq_get(): 40000000

It is quite obvious why the timer was running 2x too quickly, it assumed it ran with 40MHz while it actually ran with 80MHz.

Comment thread cpu/esp32/periph/timer.c
@@ -60,6 +61,19 @@

/* hardware timer modules used */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* The ESP32 SDK (incorrectly) defines TIMER_CLK_FREQ, but does not use it
* itself. The definition has been removed in Version 6.0 too. */
#ifdef TIMER_CLK_FREQ
# undef TIMER_CLK_FREQ
#endif

I think you'll have to add this too. Usually #undef is not a good style, but TIMER_CLK_FREQ is not used in the ESP32 SDK and the definitions seem to be incorrect (and even one comment is incorrect lol):

cbuec@W11nMate:~/RIOTstuff/riot-vanillaice/RIOT/build/pkg/esp32_sdk$ grep -RnwI TIMER_CLK_FREQ
components/soc/esp32c3/include/soc/soc.h:146:#define  TIMER_CLK_FREQ                              (80000000>>4) //80MHz divided by 16
components/soc/esp32s3/include/soc/soc.h:162:#define  TIMER_CLK_FREQ                              (80000000>>4) //80MHz divided by 16
components/soc/esp32s2/include/soc/soc.h:152:#define  TIMER_CLK_FREQ                              (80000000>>4) //80MHz divided by 16
components/soc/esp32/include/soc/soc.h:166:#define  TIMER_CLK_FREQ                              (80000000>>4) //80MHz divided by 16
components/soc/esp32c2/include/soc/soc.h:152:#define  TIMER_CLK_FREQ                              (80000000>>4) //80MHz divided by 4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also just use a different name, this is just an internal define after all.

@crasbe crasbe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind "backporting" the changes to the UART code too? That way we have the same code and docs in both drivers.

Comment thread cpu/esp32/periph/timer.c Outdated
Comment on lines +70 to +72
# define TIMER_CLK_FREQ (CLK_LL_PLL_80M_FREQ_MHZ * MHZ) /* PLL_F80M_CLK is used */
#elif CPU_FAM_ESP32H2
# define TIMER_CLK_FREQ (CLK_LL_PLL_48M_FREQ_MHZ * MHZ) /* PLL_F48M_CLK is used */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# define TIMER_CLK_FREQ (CLK_LL_PLL_80M_FREQ_MHZ * MHZ) /* PLL_F80M_CLK is used */
#elif CPU_FAM_ESP32H2
# define TIMER_CLK_FREQ (CLK_LL_PLL_48M_FREQ_MHZ * MHZ) /* PLL_F48M_CLK is used */
# define TIMER_CLK_FREQ ((uint32_t)CLK_LL_PLL_80M_FREQ_MHZ * MHZ) /* PLL_F80M_CLK is used */
#elif CPU_FAM_ESP32H2
# define TIMER_CLK_FREQ ((uint32_t)CLK_LL_PLL_48M_FREQ_MHZ * MHZ) /* PLL_F48M_CLK is used */

The return value of rtc_clk_apb_freq_get() is an uint32_t, so we want to have the same data type for all variants.

@crasbe

crasbe commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

This will also fix the timer setting on the ESP32H2:

2026-07-13 12:31:35,071 #     TIMER_CLK_FREQ: 48000000
2026-07-13 12:31:35,071 # rtc_clk_apb_freq_get(): 32000000

Behavior on master with the ESP32H2, the 2s interval only takes 1.3s.

2026-07-13 12:33:33,796 # START
2026-07-13 12:33:33,798 # main(): This is RIOT! (Version: 2026.10-devel-47-gb6f2b)
2026-07-13 12:33:33,801 # This is thread 4
2026-07-13 12:33:33,802 # sending 1st msg
2026-07-13 12:33:33,806 # now=4:125525 -> every 2.0s: Hello World
2026-07-13 12:33:33,806 # sending 2nd msg
2026-07-13 12:33:33,810 # now=4:127953 -> every 5.0s: This is a Test
2026-07-13 12:33:33,810 # This is thread 5
2026-07-13 12:33:34,467 # sec=5 min=0 hour=0
2026-07-13 12:33:35,130 # now=6:125874 -> every 2.0s: Hello World
2026-07-13 12:33:35,134 # sec=6 min=0 hour=0
2026-07-13 12:33:35,801 # sec=7 min=0 hour=0
2026-07-13 12:33:36,463 # now=8:126181 -> every 2.0s: Hello World
2026-07-13 12:33:36,467 # sec=8 min=0 hour=0
2026-07-13 12:33:37,137 # now=9:133555 -> every 5.0s: This is a Test
2026-07-13 12:33:37,138 # sec=9 min=0 hour=0
2026-07-13 12:33:37,796 # now=10:126488 -> every 2.0s: Hello World

With this PR:

2026-07-13 12:32:41,878 # START
2026-07-13 12:32:41,883 # main(): This is RIOT! (Version: 2026.10-devel-46-gc3da5f-fix/cpu_esp32_timer_clk)
2026-07-13 12:32:41,884 # This is thread 4
2026-07-13 12:32:41,884 # sending 1st msg
2026-07-13 12:32:41,889 # now=2:836265 -> every 2.0s: Hello World
2026-07-13 12:32:41,889 # sending 2nd msg
2026-07-13 12:32:41,894 # now=2:840171 -> every 5.0s: This is a Test
2026-07-13 12:32:41,894 # This is thread 5
2026-07-13 12:32:42,886 # sec=3 min=0 hour=0
2026-07-13 12:32:43,881 # now=4:838781 -> every 2.0s: Hello World
2026-07-13 12:32:43,885 # sec=4 min=0 hour=0
2026-07-13 12:32:44,885 # sec=5 min=0 hour=0
2026-07-13 12:32:45,882 # now=6:838981 -> every 2.0s: Hello World
2026-07-13 12:32:45,885 # sec=6 min=0 hour=0
2026-07-13 12:32:46,890 # now=7:843901 -> every 5.0s: This is a Test
2026-07-13 12:32:46,891 # sec=7 min=0 hour=0
2026-07-13 12:32:47,881 # now=8:839181 -> every 2.0s: Hello World
2026-07-13 12:32:47,886 # sec=8 min=0 hour=0
2026-07-13 12:32:48,886 # sec=9 min=0 hour=0
2026-07-13 12:32:49,882 # now=10:839381 -> every 2.0s: Hello World

@AnnsAnns AnnsAnns added the AI: None Stated AI was not (stated to be) used in this PR/Issue label Jul 13, 2026
@mariemC
mariemC force-pushed the fix/cpu_esp32_timer_clk branch from c3da5f2 to b70a616 Compare July 16, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: None Stated AI was not (stated to be) used in this PR/Issue Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Platform: ESP Platform: This PR/issue effects ESP-based platforms Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants