Timers - mriksman/esp-idf-homekit GitHub Wiki

Timers

FreeRTOS System Tick (CCOMPARE0)

ESP8266 RTOS SDK start-up;

  1. Calls call_start_cpu() in startup.c.
    1. Starts uiT task and calls user_init_entry which
      1. Initialises the WDEV TSF0 interrupt for PWM
      /*enable tsf0 interrupt for pwm*/
      REG_WRITE(PERIPHS_DPORT_BASEADDR, (REG_READ(PERIPHS_DPORT_BASEADDR) & ~0x1F) | 0x1);
      REG_WRITE(INT_ENA_WDEV, REG_READ(INT_ENA_WDEV) | WDEV_TSF0_REACH_INT);
      
      1. Goes on to start app_main
    2. Starts vTaskStartScheduler in tasks.c.
      1. Starts IDLE task
      2. Starts Tmr Svc task
      3. Calls xPortStartScheduler in port.c.
      4. Runs the following code
      /*******software isr*********/
      _xt_isr_attach(ETS_SOFT_INUM, SoftIsrHdl, NULL);
      _xt_isr_unmask(1 << ETS_SOFT_INUM);
      _xt_isr_attach(ETS_MAX_INUM, xPortSysTickHandle, NULL);
      

ETS_MAX_INUM is defined as 6 in rom/ets_sys.h. This is the CCOUNT CCOMPARE0 timer.

    /* Initialize system tick timer interrupt and schedule the first tick. */
    _xt_tick_divisor = xtbsp_clock_freq_hz() / XT_TICK_PER_SEC;
    g_esp_boot_ccount = soc_get_ccount();
    soc_set_ccount(0);
    _xt_tick_timer_init();

By default, XT_TICK_PER_SEC is set to CONFIG_FREERTOS_HZ which is set to 100Hz in menuconfig.

In components/esp8266/include/xtensa/config/core-isa.h

#define XCHAL_HAVE_INTERRUPTS		1	/* interrupt option 		 */
#define XCHAL_HAVE_HIGHPRI_INTERRUPTS	1	/* med/high-pri. interrupts 	 */
#define XCHAL_HAVE_NMI			1	/* non-maskable interrupt 	 */
#define XCHAL_HAVE_CCOUNT		1	/* CCOUNT reg. (timer option) */
#define XCHAL_NUM_TIMERS		1	/* number of CCOMPAREn regs 	 */
#define XCHAL_NUM_INTERRUPTS		15	/* number of interrupts 	 */
#define XCHAL_NUM_EXTINTERRUPTS		13	/* num of external interrupts */
#define XCHAL_NUM_INTLEVELS		2	/* number of interrupt levels */

/*  Interrupt numbers assigned to specific interrupt sources:  */
#define XCHAL_TIMER0_INTERRUPT		6	/* CCOMPARE0 */
#define XCHAL_TIMER1_INTERRUPT		XTHAL_TIMER_UNCONFIGURED
#define XCHAL_TIMER2_INTERRUPT		XTHAL_TIMER_UNCONFIGURED
#define XCHAL_TIMER3_INTERRUPT		XTHAL_TIMER_UNCONFIGURED
#define XCHAL_NMI_INTERRUPT		14	/* non-maskable interrupt */

In components/freertos/port/esp8266/include/freertos/xtensa_timer.h

/* Select timer to use for periodic tick, and determine its interrupt number and priority. ... */  
  ...
  #if XCHAL_TIMER0_INTERRUPT != XTHAL_TIMER_UNCONFIGURED
    #if XCHAL_INT_LEVEL(XCHAL_TIMER0_INTERRUPT) <= XCHAL_EXCM_LEVEL
      #define XT_TIMER_INDEX    0
    #endif
  #endif

#define XT_CCOMPARE             (CCOMPARE + XT_TIMER_INDEX)

In components/freertos/port/esp8266/os_cpu_a.S

_xt_tick_timer_init:
    /* Set up the periodic tick timer (assume enough time to complete init). */
    movi    a2,  _xt_tick_divisor
    l32i    a3,  a2,  0
    rsr     a2,  CCOUNT              /* current cycle count */
    add     a2,  a2,  a3             /* time of first timer interrupt */
    wsr     a2,  XT_CCOMPARE         /* set the comparator */

So ESP8266 RTOS SDK uses TIMER0 (CCOUNT CCOUNTER0) to interrupt on interrupt number 6 to perform the periodic tick timer.

FRC1 and FRC2 Timers

FRC1 is a 24-bit countdown timer which triggers an interrupt when it reaches zero. FRC2 is a 32-bit countup timer which can set a match value to trigger an interrupt.

The original esp-homekit PWM example code uses functions from esp-open-rtos.

ESP-OPEN-RTOS

The PWM code used in esp-homekit seems to use INUM_TIMER_FRC1;

/* set up ISRs */
_xt_isr_attach(INUM_TIMER_FRC1, frc1_interrupt_handler, NULL);

typedef enum {
    ...
    INUM_TICK = 6, /* RTOS timer tick, possibly xtensa CPU CCOMPARE0(?) */
    INUM_SOFT = 7, 
    INUM_WDT = 8,
    INUM_TIMER_FRC1 = 9,
    /* FRC2 default handler. Configured by sdk_ets_timer_init, which
       runs as part of default libmain.a (now called libcore.a) startup code, 
assigns interrupt handler to sdk_vApplicationTickHook+0x68
     */
    INUM_TIMER_FRC2 = 10,
} xt_isr_num_t;

ESP8266 RTOS SDK

#define hw_timer_intr_register(a, b) _xt_isr_attach(ETS_FRC_TIMER1_INUM, (a), (b))

In rom/ets_sys.h

#define ETS_SLC_INUM        1
#define ETS_SPI_INUM        2
#define ETS_GPIO_INUM       4
#define ETS_UART_INUM       5
#define ETS_MAX_INUM        6
#define ETS_SOFT_INUM       7
#define ETS_WDT_INUM        8
#define ETS_FRC_TIMER1_INUM 9
#define ETS_INT_MAX         14

So _xt_isr_attach(INUM_TIMER_FRC1, frc1_interrupt_handler, NULL) can be replaced with hw_timer_intr_register(frc1_interrupt_handler, NULL).

Hardware Timer hw_timer library in ESP8266 uses FRC1.

A comment from an ESP representative states (from before os_timer was renamed to esp_timer) that for ESP8266;

FRC2 is used for the system software timer (os_timer)

FreeRTOS software timers (xTimerCreate) rely on the System Tick. Looking at the esp_timer code, it appears in ESP8266 RTOS SDK it is just a wrapper for the FreeRTOS timers. It does not appear to use FRC2.

However. Looking at the enabled interrupts:

ESP_LOGW(TAG, "INTENABLE %x", xthal_get_intenable() );

0	ETS_WDEV_INUM		WDEV process FIQ interrupt. PWM? Wi-Fi?	0
1	ETS_SLC_INUM								0
2	ETS_SPI_INUM								0
3	ETS_RTC_INUM								0
4	ETS_GPIO_INUM								0
5	ETS_UART_INUM 								0
6	ETS_MAX_INUM 		CCOUNT CCOMPARE0 System Tick			1
7	ETS_SOFT_INUM 		Software ISR SoftIsrHdl in port.c		1
8	ETS_WDT_INUM 								1
9	ETS_FRC_TIMER1_INUM 	FRC1 used in hw_timer.c				0
10	ETS_FRC_TIMER2_INUM 	FRC2 ?						1
									      5c0

FRC2 is enabled. But disabling ETS_FRC_TIMER2_INUM with _xt_isr_mask(1 << 10) has no impact to operation. Perhaps at one stage, os_timer and/or esp_timer did use FRC2; as it does in ESP-IDF.

The ESP8266 configuration ESP8266_TIME_SYSCALL defaults to the following

choice ESP8266_TIME_SYSCALL
        prompt "Timers used for gettimeofday function"
        default ESP8266_TIME_SYSCALL_USE_FRC1
        help
            This setting defines which hardware timers are used to
            implement 'gettimeofday' and 'time' functions in C library.

            - If high-resolution timer is used, gettimeofday will
              provide time at microsecond resolution.
              Time will not be preserved when going into deep sleep mode.
            - If no timers are used, gettimeofday and time functions
              return -1 and set errno to ENOSYS.

However, I do not think it is using FRC1 at all. When CONFIG_ESP8266_TIME_SYSCALL is defined in time.c, it uses esp_timer_get_time(). This function uses soc_get_ccount() function, which returns the CCOUNT register.

int64_t esp_timer_get_time(void) {
    extern uint64_t g_esp_os_us;
    return (int64_t)(g_esp_os_us + soc_get_ccount() / g_esp_ticks_per_us);
}

FRC1 has also been left available for the Hardware Timer library.

ESP-IDF SDK

In ESP-IDF, the documentation notes the following:

Although FreeRTOS provides software timers, these timers have a few limitations:

  • Maximum resolution is equal to RTOS tick period
  • Timer callbacks are dispatched from a low-priority task esp_timer set of APIs provides one-shot and periodic timers, microsecond time resolution, and 64-bit range. Internally, esp_timer uses a 64-bit hardware timer CONFIG_ESP_TIMER_IMPL:
  • FRC2 (legacy) implementation has been used in ESP-IDF v2.x - v4.1.
  • LAC timer of Timer Group 0 implementation is simpler and has smaller run time overhead because software handling of timer overflow is not needed.
  • SYSTIMER implementation is similar to LAC timer of Timer Group 0 but for ESP32-S2 chip.

ESP-OPEN-RTOS

#define TIMER_BASE 0x60000600
#define TIMER(i) (*(struct TIMER_REGS *)(TIMER_BASE + (i)*0x20))
#define TIMER_FRC1 TIMER(0)
#define TIMER_FRC2 TIMER(1)

/* TIMER registers
 * ESP8266 has two hardware timer counters, FRC1 and FRC2.
 * FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero.
 * FRC2 is a 32-bit countup timer, can set a match value to trigger an interrupt.
 * FreeRTOS tick timer appears to come from XTensa core tick timer0,
 * not either of these.  FRC2 is used in the FreeRTOS SDK however. It
 * is set to free-run, interrupting periodically via updates to the
 * ALARM register. sdk_ets_timer_init configures FRC2 and assigns FRC2
 * interrupt handler at sdk_vApplicationTickHook+0x68
 */

struct TIMER_REGS {            // FRC1  FRC2
    uint32_t volatile LOAD;    // 0x00  0x20
    uint32_t volatile COUNT;   // 0x04  0x24
    uint32_t volatile CTRL;    // 0x08  0x28
    uint32_t volatile STATUS;  // 0x0c  0x2c
    uint32_t volatile ALARM;   //       0x30
};

PWM

The pwm.c code in the library uses wDev_MacTimSetFunc. This function sets the first timer built into the wDev/MAC hardware to fire after the specified delay (measured in ticks). When it fires, it will trigger an NMI, and bit 27 of [0x3ff20c20] will be set. wDev is assumed to mean ‘Wi-Fi Device’? The function wDev_MacTimSetFunc must be inside the binary files, as it cannot be found with search, and little is documented on it.