Part 5: Using a timer
Next step is to replace the dumb delay routine with a real timer controlled interrupt routine. However, our first step is to just use a timer controlled delay loop, without interrupts.
First the timer registers, we’ll use timer 0:
#define TGLB_REG        0x10000100      /* Timers global control register */
#define T0CTL_REG       0x10000110      /* Timer 0 control register */
#define T0LMT_REG       0x10000114      /* Timer 0 limit register */
#define T0_REG          0x10000118      /* Timer 0 counter register */
#define TxCTL_Tx_PRES_OFFSET 16         /* Timer 0/1 prescale offset */
#define TxCTL_TxEN           (1 << 7)   /* Timer 0/1 enable */
And here’s the relevant code which now uses this timer:
static void timer_delay(unsigned int delay_in_ms)
{
    // start by disabling timer
    write_l(T0CTL_REG, 0);
    write_l(TGLB_REG, 0);
    write_l(T0LMT_REG, delay_in_ms);
    // enable the timer, set prescale to 1ms
    write_l(T0CTL_REG, (1000 << TxCTL_Tx_PRES_OFFSET) | TxCTL_TxEN);
    while ((read_l(T0CTL_REG) & TxCTL_TxEN) != 0) {
        // busy wait until timer expires
    }
}
/* A less dumb delay routine */
static void delay()
{
    const auto milliseconds = 500;
    const auto granularity = 100;
    static_assert(milliseconds % granularity == 0,
        "milliseconds should be a multiple of granularity");
    for (auto i = 0; i < (milliseconds / granularity); i++) {
        reset_if_button_pressed();
        serial_input();
        timer_delay(granularity);
    }
}
Note that I switched to C++ meanwhile.
Now, this still uses a busy wait for the timer, but at least the timeouts can be specified exactly.
Next step: replace the busy wait with a wait for interrupt.
To be continued…