Raspberry Pi Pico má hardwarový časovač s časovou základnou 1 mikrosekunda. Umí vytvářet přerušení založená na čase pro naše použití v systému. Má následující vlastnosti:

  • Je to 64bitový čítač, který se zvětšuje o jedničku každou jednu mikrosekundu.

  • Můžeme jej číst pomocí páru registrů na 32bitové sběrnici.

  • Můžeme si pomocí něj udělat 4 budíky na nižších 32bitech registru a těm přiřadit přerušení (IRQ).

Časovač používá jednomikrosekundovou referenci vytvářenou Watchdogem, která je odvozená z referenčních hodin, které jsou připojeny přímo na krystalovýc oscilátor. 64 bitový čítač nemůže prakticky přetéct (trvalo by to přes 500 let) a proto je systémový čas kompletně monotónní.

Nejjednodušší metoda čtení času
// Nejjednodušší způsob získání 64bitového času z časovače.
// Není bezpečný při volání ze 2 jader kvůli blokování,
// takže v SDK není takto implementován.
static uint64_t get_time(void)
{
    // Čtení nízké hodnoty zablokuje vysokou
    uint32_t lo = timer_hw->timelr;
    uint32_t hi = timer_hw->timehr;
return ((uint64_t) hi << 32u) | lo;
}

Tato metoda není zcela bezpečná a proto je v C SDK přímo funkce:

uint64_t timer_time_us_64(timer_hw_t *timer)
{
    // Musíme se ujistit, že se horních 32 bitů časovače
    // nezměnilo, proto je čteme jako první
    uint32_t hi = timer->timerawh;
    uint32_t lo;
    do {
        // čteme dolních 32 bitů
        lo = timer->timerawl;
        // Teď přečteme horních 32 bitů znova a zkontrolujeme,
        // zda se nezvýšily.
        // Pokud se zvýšily, musíme přečíst spodních 32 bitů
        // znovu, abychom získali přesnou hodnotu
        uint32_t next_hi = timer->timerawh;
        if (hi == next_hi) break;
        hi = next_hi;
    } while (true);
return ((uint64_t) hi << 32u) | lo;
}

Budík

Časovač má 4 budíky (alarms) a pro každý budík je k dispozici jedno přerušení. Budík začne "zvonit" pokud je dolních 32 bitů 64bitového čítače stejné, což znamená, že ho můžeme nařídit maximálně na \(2^{32} \mu s\) do budoucnosti. To odpovídá přibližně \(\frac{2^{32}}{10^6} \approx\) 4295 sekund nebo 72 minut.

Budíky jsou určeny pro kratší časové úseky,

Nastavení budíku:

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/timer.h"
#include "hardware/irq.h"

// Použijeme budík 0
#define ALARM_NUM 0
#define ALARM_IRQ timer_hardware_alarm_get_irq_num(timer_hw, ALARM_NUM)

// Obsluha přerušení budíku
static volatile bool alarm_fired;

static void alarm_irq(void) {
    // Vynulujeme IRQ budíku
    hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);

    // A máme za to, že budík 0 byl spuštěn
    printf("Alarm IRQ fired\n");
    alarm_fired = true;
}

static void alarm_in_us(uint32_t delay_us) {
    // Enable the interrupt for our alarm (the timer outputs 4 alarm irqs)
    hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM);
    // Set irq handler for alarm irq
    irq_set_exclusive_handler(ALARM_IRQ, alarm_irq);
    // Enable the alarm irq
    irq_set_enabled(ALARM_IRQ, true);
    // Enable interrupt in block and at processor

    // Alarm is only 32 bits so if trying to delay more
    // than that need to be careful and keep track of the upper
    // bits
    uint64_t target = timer_hw->timerawl + delay_us;

    // Write the lower 32 bits of the target time to the alarm which
    // will arm it
    timer_hw->alarm[ALARM_NUM] = (uint32_t) target;
}

int main() {
    stdio_init_all();
    printf("Timer lowlevel!\n");

    // Set alarm every 2 seconds
    while (1) {
        alarm_fired = false;
        alarm_in_us(1000000 * 2);
        // Wait for alarm to fire
        while (!alarm_fired);
    }
}

Opakující se budík

link:https://github.com/raspberrypi/pico-examples/blob/master/timer/hello_timer/hello_timer.c
/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include "pico/stdlib.h"

/// \tag::timer_example[]
volatile bool timer_fired = false;

int64_t alarm_callback(alarm_id_t id, __unused void *user_data) {
    printf("Timer %d fired!\n", (int) id);
    timer_fired = true;
    // Can return a value here in us to fire in the future
    return 0;
}

bool repeating_timer_callback(__unused struct repeating_timer *t) {
    printf("Repeat at %lld\n", time_us_64());
    return true;
}

int main() {
    stdio_init_all();
    printf("Hello Timer!\n");

    // Voláme funkci alarm_callback každé 2 sekundy
    add_alarm_in_ms(2000, alarm_callback, NULL, false);

    // Wait for alarm callback to set timer_fired
    while (!timer_fired) {
        tight_loop_contents();
    }

    // Create a repeating timer that calls repeating_timer_callback.
    // If the delay is > 0 then this is the delay between the previous callback ending and the next starting.
    // If the delay is negative (see below) then the next call to the callback will be exactly 500ms after the
    // start of the call to the last callback
    struct repeating_timer timer;
    add_repeating_timer_ms(500, repeating_timer_callback, NULL, &timer);
    sleep_ms(3000);
    bool cancelled = cancel_repeating_timer(&timer);
    printf("cancelled... %d\n", cancelled);
    sleep_ms(2000);

    // Negative delay so means we will call repeating_timer_callback, and call it again
    // 500ms later regardless of how long the callback took to execute
    add_repeating_timer_ms(-500, repeating_timer_callback, NULL, &timer);
    sleep_ms(3000);
    cancelled = cancel_repeating_timer(&timer);
    printf("cancelled... %d\n", cancelled);
    sleep_ms(2000);
    printf("Done\n");
    return 0;
}
/// \end::timer_example[]