mbed

Fork of mbed-dev by mbed official

Revision:
181:96ed750bd169
Parent:
176:af195413fb11
Child:
182:57724642e740
--- a/hal/mbed_ticker_api.c	Thu Dec 07 14:01:42 2017 +0000
+++ b/hal/mbed_ticker_api.c	Wed Jan 17 15:23:54 2018 +0000
@@ -17,6 +17,7 @@
 #include <stddef.h>
 #include "hal/ticker_api.h"
 #include "platform/mbed_critical.h"
+#include "mbed_assert.h"
 
 static void schedule_interrupt(const ticker_data_t *const ticker);
 static void update_present_time(const ticker_data_t *const ticker);
@@ -33,9 +34,31 @@
     }
 
     ticker->interface->init();
-    
+
+    const ticker_info_t *info = ticker->interface->get_info();
+    uint32_t frequency = info->frequency;
+    if (info->frequency == 0) {
+        MBED_ASSERT(0);
+        frequency = 1000000;
+    }
+
+    uint32_t bits = info->bits;
+    if ((info->bits > 32) || (info->bits < 4)) {
+        MBED_ASSERT(0);
+        bits = 32;
+    }
+    uint32_t max_delta = 0x7 << (bits - 4); // 7/16th
+    uint64_t max_delta_us =
+            ((uint64_t)max_delta * 1000000 + frequency - 1) / frequency;
+
     ticker->queue->event_handler = NULL;
     ticker->queue->head = NULL;
+    ticker->queue->tick_last_read = ticker->interface->read();
+    ticker->queue->tick_remainder = 0;
+    ticker->queue->frequency = frequency;
+    ticker->queue->bitmask = ((uint64_t)1 << bits) - 1;
+    ticker->queue->max_delta = max_delta;
+    ticker->queue->max_delta_us = max_delta_us;
     ticker->queue->present_time = 0;
     ticker->queue->initialized = true;
     
@@ -86,53 +109,143 @@
  * Update the present timestamp value of a ticker.
  */
 static void update_present_time(const ticker_data_t *const ticker)
-{ 
-    ticker->queue->present_time = convert_timestamp(
-        ticker->queue->present_time, 
-        ticker->interface->read()
-    );
+{
+
+    ticker_event_queue_t *queue = ticker->queue;
+    uint32_t ticker_time = ticker->interface->read();
+    if (ticker_time == ticker->queue->tick_last_read) {
+        // No work to do
+        return;
+    }
+
+    uint64_t elapsed_ticks = (ticker_time - queue->tick_last_read) & queue->bitmask;
+    queue->tick_last_read = ticker_time;
+
+    uint64_t elapsed_us;
+    if (1000000 == queue->frequency) {
+        // Optimized for 1MHz
+
+        elapsed_us = elapsed_ticks;
+    } else if (32768 == queue->frequency) {
+        // Optimized for 32KHz
+
+        uint64_t us_x_ticks = elapsed_ticks * 1000000;
+        elapsed_us = us_x_ticks >> 15;
+
+        // Update remainder
+        queue->tick_remainder += us_x_ticks - (elapsed_us << 15);
+        if (queue->tick_remainder >= queue->frequency) {
+            elapsed_us += 1;
+            queue->tick_remainder -= queue->frequency;
+        }
+    } else {
+        // General case
+
+        uint64_t us_x_ticks = elapsed_ticks * 1000000;
+        elapsed_us = us_x_ticks / queue->frequency;
+
+        // Update remainder
+        queue->tick_remainder += us_x_ticks - elapsed_us * queue->frequency;
+        if (queue->tick_remainder >= queue->frequency) {
+            elapsed_us += 1;
+            queue->tick_remainder -= queue->frequency;
+        }
+    }
+
+    // Update current time
+    queue->present_time += elapsed_us;
+}
+
+/**
+ * Given the absolute timestamp compute the hal tick timestamp.
+ */
+static timestamp_t compute_tick(const ticker_data_t *const ticker, us_timestamp_t timestamp)
+{
+    ticker_event_queue_t *queue = ticker->queue;
+    us_timestamp_t delta_us = timestamp - queue->present_time;
+
+    timestamp_t delta = ticker->queue->max_delta;
+    if (delta_us <=  ticker->queue->max_delta_us) {
+        // Checking max_delta_us ensures the operation will not overflow
+
+        if (1000000 == queue->frequency) {
+            // Optimized for 1MHz
+
+            delta = delta_us;
+            if (delta > ticker->queue->max_delta) {
+                delta = ticker->queue->max_delta;
+            }
+        } else if (32768 == queue->frequency) {
+            // Optimized for 32KHz
+
+            delta = (delta_us << 15) / 1000000;
+            if (delta > ticker->queue->max_delta) {
+                delta = ticker->queue->max_delta;
+            }
+        } else {
+            // General case
+
+            delta = delta_us * queue->frequency / 1000000;
+            if (delta > ticker->queue->max_delta) {
+                delta = ticker->queue->max_delta;
+            }
+        }
+    }
+    return (queue->tick_last_read + delta) & queue->bitmask;
+}
+
+/**
+ * Return 1 if the tick has incremented to or past match_tick, otherwise 0.
+ */
+int _ticker_match_interval_passed(timestamp_t prev_tick, timestamp_t cur_tick, timestamp_t match_tick)
+{
+    if (match_tick > prev_tick) {
+        return (cur_tick >= match_tick) || (cur_tick < prev_tick);
+    } else {
+        return (cur_tick < prev_tick) && (cur_tick >= match_tick);
+    }
 }
 
 /**
  * Compute the time when the interrupt has to be triggered and schedule it.  
  * 
  * If there is no event in the queue or the next event to execute is in more 
- * than MBED_TICKER_INTERRUPT_TIMESTAMP_MAX_DELTA us from now then the ticker 
- * irq will be scheduled in MBED_TICKER_INTERRUPT_TIMESTAMP_MAX_DELTA us.
- * Otherwise the irq will be scheduled to happen when the running counter reach 
- * the timestamp of the first event in the queue.
+ * than ticker.queue.max_delta ticks from now then the ticker irq will be
+ * scheduled in ticker.queue.max_delta ticks. Otherwise the irq will be
+ * scheduled to happen when the running counter reach the timestamp of the
+ * first event in the queue.
  * 
  * @note If there is no event in the queue then the interrupt is scheduled to 
- * in MBED_TICKER_INTERRUPT_TIMESTAMP_MAX_DELTA. This is necessary to keep track 
+ * in ticker.queue.max_delta. This is necessary to keep track
  * of the timer overflow.
  */
 static void schedule_interrupt(const ticker_data_t *const ticker)
 {
+    ticker_event_queue_t *queue = ticker->queue;
     update_present_time(ticker);
-    uint32_t relative_timeout = MBED_TICKER_INTERRUPT_TIMESTAMP_MAX_DELTA;
 
     if (ticker->queue->head) {
         us_timestamp_t present = ticker->queue->present_time;
-        us_timestamp_t next_event_timestamp = ticker->queue->head->timestamp;
+        us_timestamp_t match_time = ticker->queue->head->timestamp;
 
         // if the event at the head of the queue is in the past then schedule
         // it immediately.
-        if (next_event_timestamp <= present) {
+        if (match_time <= present) {
             ticker->interface->fire_interrupt();
             return;
-        } else if ((next_event_timestamp - present) < MBED_TICKER_INTERRUPT_TIMESTAMP_MAX_DELTA) {
-            relative_timeout = next_event_timestamp - present;
         }
-    } 
+
+        timestamp_t match_tick = compute_tick(ticker, match_time);
+        ticker->interface->set_interrupt(match_tick);
+        timestamp_t cur_tick = ticker->interface->read();
 
-    us_timestamp_t new_match_time = ticker->queue->present_time + relative_timeout;
-    ticker->interface->set_interrupt(new_match_time);
-    // there could be a delay, reread the time, check if it was set in the past
-    // As result, if it is already in the past, we fire it immediately
-    update_present_time(ticker);
-    us_timestamp_t present = ticker->queue->present_time;
-    if (present >= new_match_time) {
-        ticker->interface->fire_interrupt();
+        if (_ticker_match_interval_passed(queue->tick_last_read, cur_tick, match_tick)) {
+            ticker->interface->fire_interrupt();
+        }
+    } else {
+        uint32_t match_tick =
+                (queue->tick_last_read + queue->max_delta) & queue->bitmask;
+        ticker->interface->set_interrupt(match_tick);
     }
 }