Timer

Author: Alexander Beck, Christian Zellinger 2025

The Timer module provides programmable timer functionality for the PicoNut processor. It is a versatile hardware timer component designed specifically for time-critical applications such as FreeRTOS tick generation and other real-time applications.

The m_timer peripheral is a comprehensive timer implementation that offers advanced configuration options and is fully accessible through the Wishbone interface.

Timer Registers

The m_timer module implements an STM32 TIM2 compatible register layout with the following memory-mapped registers (all addresses are offsets from the timer base address).

Note: The canonical base address and per-register offsets are defined in hw/peripherals/timer/timer_defs.h as PN_CFG_TIMER_BASE_ADDRESS and the M_TIMER_REG_* enum constants. Use those constants in code to avoid hard-coded addresses.

Register

Offset

Width

Access

Description

CR1

0x00

32

RW

Control Register 1 (M_TIMER_REG_CR1)

CR2

0x04

32

RW

Control Register 2 (dummy) (M_TIMER_REG_CR2)

SMCR

0x08

32

RW

Slave Mode Control Register (dummy) (M_TIMER_REG_SMCR)

DIER

0x0C

32

RW

Interrupt Enable Register (M_TIMER_REG_DIER)

SR

0x10

32

RW

Status Register (M_TIMER_REG_SR)

EGR

0x14

32

WO

Event Generation Register (dummy)

CCMR1

0x18

32

RW

Capture/Compare Mode Register 1 (dummy)

CCMR2

0x1C

32

RW

Capture/Compare Mode Register 2 (dummy)

CCER

0x20

32

RW

Capture/Compare Enable Register (dummy)

CNT

0x24

32

RW

Counter Register (M_TIMER_REG_CNT)

PSC

0x28

32

RW

Prescaler Register (M_TIMER_REG_PSC)

ARR

0x2C

32

RW

Auto-Reload Register (M_TIMER_REG_ARR)

CCR1

0x34

32

RW

Capture/Compare Register 1 (M_TIMER_REG_CCR1)

CCR2

0x38

32

RW

Capture/Compare Register 2

CCR3

0x3C

32

RW

Capture/Compare Register 3

CCR4

0x40

32

RW

Capture/Compare Register 4

Control Register 1 (CR1)

The Control Register 1 configures the basic timer operation:

Bit

Name

Description

0

CEN

Counter Enable - Start/stop the timer

1

UDIS

Update Disable - Disable update events

2

URS

Update Request Source - Source of update events

3

OPM

One Pulse Mode - Timer stops after one period

4

DIR

Direction - 0: Up-counting, 1: Down-counting

6:5

CMS

Center-aligned Mode Selection

7

ARPE

Auto-reload Preload Enable

Status Register (SR)

The Status Register contains interrupt flags:

Bit

Name

Description

0

UIF

Update Interrupt Flag - Set on counter overflow/underflow

1

CC1IF

Capture/Compare 1 Interrupt Flag

2

CC2IF

Capture/Compare 2 Interrupt Flag

3

CC3IF

Capture/Compare 3 Interrupt Flag

4

CC4IF

Capture/Compare 4 Interrupt Flag

5

TIF

Trigger Interrupt Flag

6

BIF

Break Interrupt Flag

Interrupt Enable Register (DIER)

The Interrupt Enable Register controls which interrupts are enabled:

Bit

Name

Description

0

UIE

Update Interrupt Enable

1

CC1IE

Capture/Compare 1 Interrupt Enable

2

CC2IE

Capture/Compare 2 Interrupt Enable

3

CC3IE

Capture/Compare 3 Interrupt Enable

4

CC4IE

Capture/Compare 4 Interrupt Enable

5

TIE

Trigger Interrupt Enable

6

BIE

Break Interrupt Enable

Timer Operation

The m_timer provides several key functionalities:

Counter Operation

The timer includes a 32-bit counter that can operate in different modes:

  • Up-counting: Counter increments from 0 to the auto-reload value

  • Down-counting: Counter decrements from the auto-reload value to 0

  • One-pulse mode: Timer automatically stops after one complete cycle

Prescaler

The 32-bit prescaler divides the input clock frequency:

f_timer = f_clock / (Prescaler + 1)

A prescaler value of 0 runs the timer at full clock frequency, while higher values create proportionally slower timer frequencies.

Auto-Reload

The auto-reload register defines the timer period:

  • Up-counting: When counter reaches ARR value, it resets to 0

  • Down-counting: When counter reaches 0, it reloads with ARR value

  • One-pulse mode: Timer stops after reload event

Capture/Compare

The timer provides four capture/compare channels (CCR1-CCR4) that can:

  • Generate interrupts when the counter matches the compare value

  • Trigger events for external peripherals

  • Measure input signal timing (capture mode)

Timer Module

The Timer module consists of the following components:

  • Wishbone Slave Interface: Handles Wishbone protocol transactions for register access

  • Timer Logic: Implements the core timer functionality with configurable counting direction

  • Prescaler Unit: 32-bit prescaler for clock frequency division

  • Capture/Compare Units: Four independent capture/compare channels

  • Interrupt Controller: STM32-compatible interrupt generation logic

Interrupt Generation

The timer generates interrupts for the following events:

  • Update Interrupt (UIF): Generated on counter overflow/underflow

  • Capture/Compare Interrupts (CC1IF-CC4IF): Generated when counter matches compare values

The timer_irq output is asserted when any enabled interrupt condition occurs.

Note: Currently only UIF and CC1IF-CC4IF interrupts are functionally implemented in the hardware. TIF and BIF are defined in the register structure for STM32 compatibility but do not generate interrupts.

Programming Guide

This section demonstrates how to use the m_timer peripheral with practical examples based on the timer_demo application.

Basic Timer Setup

To configure the timer from C code prefer using the constants from timer_defs.h:

#include <stdio.h>
#include <stdint.h>
#include <timer_defs.h>

// Use PN_CFG_TIMER_BASE_ADDRESS and M_TIMER_REG_* offsets from timer_defs.h
#define TIMER_CR1  (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_CR1)
#define TIMER_DIER (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_DIER)
#define TIMER_SR   (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_SR)
#define TIMER_CNT  (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_CNT)
#define TIMER_PSC  (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_PSC)
#define TIMER_ARR  (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_ARR)
#define TIMER_CCR1 (PN_CFG_TIMER_BASE_ADDRESS + M_TIMER_REG_CCR1)

// Status and interrupt enable bit definitions (unchanged)
#define TIMER_STATUS_UIF   (1 << 0)
#define TIMER_STATUS_CC1IF (1 << 1)
#define TIMER_IE_UIE       (1 << 0)
#define TIMER_IE_CC1IE     (1 << 1)

Timer Configuration

Replace previous hard-coded-offset examples with the constants:

void configure_timer(void) {
    volatile uint32_t *timer_arr = (volatile uint32_t *)TIMER_ARR;
    volatile uint32_t *timer_psc = (volatile uint32_t *)TIMER_PSC;
    volatile uint32_t *timer_ccr1 = (volatile uint32_t *)TIMER_CCR1;
    volatile uint32_t *timer_cr1 = (volatile uint32_t *)TIMER_CR1;
    volatile uint32_t *timer_dier = (volatile uint32_t *)TIMER_DIER;

    // Set auto-reload value (timer period)
    *timer_arr = 1000;     // Count from 0 to 1000

    // Set prescaler (clock division)
    *timer_psc = 1;        // Divide clock by (1 + prescaler) -> here 2

    // Set compare value for capture/compare interrupt
    *timer_ccr1 = 500;     // Interrupt at half period

    // Enable interrupts
    *timer_dier = TIMER_IE_UIE | TIMER_IE_CC1IE; // Update + Compare interrupts

    // Start timer
    *timer_cr1 = 0x01;     // CEN=1 (Counter Enable)
}

Interrupt Handling

Implement interrupt service routines to handle timer events:

// Global interrupt counters
volatile uint32_t update_interrupt_count = 0;
volatile uint32_t compare_interrupt_count = 0;

// Interrupt Service Routine
void timer_isr(void) {
    volatile uint32_t *timer_sr = (volatile uint32_t *)TIMER_SR;
    uint32_t status = *timer_sr;
    
    // Handle update interrupt (overflow)
    if (status & TIMER_STATUS_UIF) {
        update_interrupt_count++;
        printf("Timer overflow: %lu\n", update_interrupt_count);
    }
    
    // Handle capture/compare interrupt
    if (status & TIMER_STATUS_CC1IF) {
        compare_interrupt_count++;
        printf("Compare match: %lu\n", compare_interrupt_count);
    }
    
    // Clear interrupt flags (write-1-to-clear)
    *timer_sr = status & (TIMER_STATUS_UIF | TIMER_STATUS_CC1IF);
}

// Top-level interrupt handler
void __attribute__((interrupt)) handle_interrupt(void) {
    timer_isr();
}

RISC-V Interrupt Setup

Enable global interrupts and configure the interrupt vector:

void enable_interrupts(void) {
    // Set trap vector
    void* trap_vector = handle_interrupt;
    __asm__ volatile("csrw mtvec, %0" : : "r"(trap_vector));

    // Enable global interrupts (MIE in MSTATUS)
    uint32_t mstatus;
    __asm__ volatile("csrr %0, mstatus" : "=r"(mstatus));
    mstatus |= (1 << 3); // MIE bit
    __asm__ volatile("csrw mstatus, %0" : : "r"(mstatus));

    // Enable external interrupts (MEIE in MIE)
    uint32_t mie;
    __asm__ volatile("csrr %0, mie" : "=r"(mie));
    mie |= (1 << 11); // MEIE bit
    __asm__ volatile("csrw mie, %0" : : "r"(mie));
}

Complete Example

Here’s a complete example demonstrating timer usage:

int main() {
    printf("=== m_timer Demo ===\n");
    
    // Configure timer
    configure_timer();
    
    // Enable interrupts
    enable_interrupts();
    
    printf("Timer started. Waiting for interrupts...\n");
    
    // Main loop
    while (1) {
        // Display status
        printf("Status: Update: %lu, Compare: %lu\n", 
               update_interrupt_count, compare_interrupt_count);
        
        // Stop after 10 total interrupts
        if ((update_interrupt_count + compare_interrupt_count) >= 10) {
            volatile uint32_t *timer_cr1 = (volatile uint32_t *)TIMER_CR1;
            *timer_cr1 = 0x00; // Stop timer
            break;
        }
        
        // Delay
        for (volatile int i = 0; i < 10000; i++) {
            __asm__ volatile("nop");
        }
    }
    
    printf("Timer demo completed!\n");
    return 0;
}