Преглед изворни кода

Merge branch 'bugfix/esp_timer_stucks_into_esp_timer_impl_set_alarm_v4.0' into 'release/v4.0'

esp_timer/esp32: Fix esp_timer_impl_set_alarm() when CPU and APB freqs are changed (v4.0)

See merge request espressif/esp-idf!7304
Angus Gratton пре 6 година
родитељ
комит
a61fd1e42b
2 измењених фајлова са 40 додато и 3 уклоњено
  1. 30 3
      components/esp32/esp_timer_esp32.c
  2. 10 0
      components/esp32/test/test_esp_timer.c

+ 30 - 3
components/esp32/esp_timer_esp32.c

@@ -216,10 +216,14 @@ void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
     // Note that if by the time we update ALARM_REG, COUNT_REG value is higher,
     // interrupt will not happen for another ALARM_OVERFLOW_VAL timer ticks,
     // so need to check if alarm value is too close in the future (e.g. <2 us away).
-    const int32_t offset = s_timer_ticks_per_us * 2;
+    int32_t offset = s_timer_ticks_per_us * 2;
     do {
         // Adjust current time if overflow has happened
-        if (timer_overflow_happened()) {
+        if (timer_overflow_happened() ||
+            ((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
+            ((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0))) {
+            // 1. timer_overflow_happened() checks overflow with the interrupt flag.
+            // 2. During several loops, the counter can be higher than the alarm and even step over ALARM_OVERFLOW_VAL boundary (the interrupt flag is not set).
             timer_count_reload();
             s_time_base_us += s_timer_us_per_overflow;
         }
@@ -236,7 +240,19 @@ void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
             alarm_reg_val = (uint32_t) compare_val;
         }
         REG_WRITE(FRC_TIMER_ALARM_REG(1), alarm_reg_val);
-    } while (REG_READ(FRC_TIMER_ALARM_REG(1)) <= REG_READ(FRC_TIMER_COUNT_REG(1)));
+        int64_t delta = (int64_t)alarm_reg_val - (int64_t)REG_READ(FRC_TIMER_COUNT_REG(1));
+        if (delta <= 0) {
+            /*
+                When the timestamp is a bit less than the current counter then the alarm = current_counter + offset.
+                But due to CPU_freq in some case can be equal APB_freq the offset time can not exceed the overhead
+                (the alarm will be less than the counter) and it leads to the infinity loop.
+                To exclude this behavior to the offset was added the delta to have the opportunity to go through it.
+            */
+            offset += abs((int)delta) + s_timer_ticks_per_us * 2;
+        } else {
+            break;
+        }
+    } while (1);
     portEXIT_CRITICAL(&s_time_update_lock);
 }
 
@@ -254,6 +270,17 @@ static void IRAM_ATTR timer_alarm_isr(void *arg)
     // Set alarm to the next overflow moment. Later, upper layer function may
     // call esp_timer_impl_set_alarm to change this to an earlier value.
     REG_WRITE(FRC_TIMER_ALARM_REG(1), ALARM_OVERFLOW_VAL);
+    if ((REG_READ(FRC_TIMER_COUNT_REG(1)) > ALARM_OVERFLOW_VAL) &&
+        ((REG_READ(FRC_TIMER_CTRL_REG(1)) & FRC_TIMER_INT_STATUS) == 0)) {
+        /*
+            This check excludes the case when the alarm can be less than the counter.
+            Without this check, it is possible because DPORT uses 4-lvl, and users can use the 5 Hi-interrupt,
+            they can interrupt this function between FRC_TIMER_INT_CLR and setting the alarm = ALARM_OVERFLOW_VAL
+            that lead to the counter will go ahead leaving the alarm behind.
+        */
+        timer_count_reload();
+        s_time_base_us += s_timer_us_per_overflow;
+    }
     portEXIT_CRITICAL_ISR(&s_time_update_lock);
     // Call the upper layer handler
     (*s_alarm_handler)(arg);

+ 10 - 0
components/esp32/test/test_esp_timer.c

@@ -798,3 +798,13 @@ TEST_CASE("Test case when esp_timer_impl_set_alarm needs set timer < now_time",
     printf("alarm_reg = 0x%x, count_reg 0x%x\n", alarm_reg, count_reg);
     TEST_ASSERT(alarm_reg <= (count_reg + offset));
 }
+
+TEST_CASE("Test esp_timer_impl_set_alarm when the counter is near an overflow value", "[esp_timer]")
+{
+    for (int i = 0; i < 1024; ++i) {
+        uint32_t count_reg = 0xeffffe00 + i;
+        REG_WRITE(FRC_TIMER_LOAD_REG(1), count_reg);
+        printf("%d) count_reg = 0x%x\n", i, count_reg);
+        esp_timer_impl_set_alarm(1); // timestamp is expired
+    }
+}