Timers - mriksman/esp-idf-homekit GitHub Wiki
Timers
FreeRTOS System Tick (CCOMPARE0)
ESP8266 RTOS SDK start-up;
- Calls
call_start_cpu()instartup.c.- Starts
uiTtask and callsuser_init_entrywhich- Initialises the
WDEV TSF0interrupt 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);- Goes on to start
app_main
- Initialises the
- Starts
vTaskStartSchedulerintasks.c.- Starts
IDLEtask - Starts
Tmr Svctask - Calls
xPortStartSchedulerinport.c. - 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); - Starts
- Starts
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_timerset of APIs provides one-shot and periodic timers, microsecond time resolution, and 64-bit range. Internally,esp_timeruses a 64-bit hardware timerCONFIG_ESP_TIMER_IMPL:FRC2 (legacy)implementation has been used in ESP-IDF v2.x - v4.1.LAC timer of Timer Group 0implementation is simpler and has smaller run time overhead because software handling of timer overflow is not needed.SYSTIMERimplementation 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.