Timer Interrupts, Input Capture and IrDA Decode on the ME32F030 (TIM6/TIM7)

Peripheral Programming · 7 min read · September 14, 2026

Most ME32F030 projects start with a blinking LED driven by a basic timer — and stop there. The chip’s advanced timers, TIM6 and TIM7, can do far more: four match channels, four capture channels, PWM output, and eight independent interrupt sources. This article walks through the three patterns we use most often in customer projects, then closes with the pitfalls that cost us time so they don’t cost you any.

TIM6/TIM7 vs the basic timers (TIM0–TIM3)

FeatureTIM0–3 (basic)TIM6/7 (advanced)
Resolution16-bit16-bit
Match channels14 (Match0–3)
Capture channelsnone4 (Capture0–3)
PWM outputnone3 channels (from Match0–2)
Counting modestimer onlytimer / counter / quadrature / gated / triggered
Interrupt sources1 (Match0)8 (4 match + 4 capture)

One thing that confuses newcomers: TIM6 and TIM7 share the same API. Every TIM6_xxx() function works on TIM7 too — just pass the TIM7 pointer instead.

Pattern 1: Periodic interrupt (the workhorse)

A 1-second tick used for scheduled tasks. The two-step idea: TIM6_Init() sets how fast the counter counts; TIM6_ConfigMatch0() sets how far it counts before interrupting.

#include "CMSDK_CM0.h"
#include "sys.h"
#include "timer.h"
#include "ioconfig.h"

volatile uint32_t tick_count = 0;

int main(void)
{
    SYS_SystemInitial();

    /* Count at 1000 Hz: one tick per millisecond.
       PR = SystemCoreClock / tickpersecond - 1 = 20000000/1000 - 1 */
    TIM6_Init(TIM6, 1000);
    TIM6_SetTimerType(TIM6, 0);          /* 0 = plain timer mode */

    /* Fire an interrupt and auto-reset the counter every 1000 ticks = 1 s */
    TIM6_ConfigMatch0(TIM6, 1000,
                      TIM_MATCH_TRIGGER_INT | TIM_MATCH_RESET_COUNTER,
                      TIM_MATCH_OUT_DO_NOTHING);

    NVIC_EnableIRQ(TIM6_IRQn);           /* the NVIC enable is on YOU */
    TIM6_START;

    while (1) { __WFI(); }               /* sleep until interrupt */
}

void TIM6_IRQHandler(void)
{
    uint8_t int_status = TIM6_GetIntStatus(TIM6);

    if (int_status & TIM_MATCH_0_INT) {  /* 0x01 */
        tick_count++;
        /* periodic work here */
    }

    TIM6_ClearIntFlag(TIM6);             /* MANDATORY — see pitfall 2 */
}

The timing math (default 20 MHz system clock):

count frequency = SystemCoreClock / (PR + 1)
interrupt period = match value / count frequency

TIM6_Init(TIM6, 1000)      -> 1 kHz tick
TIM6_ConfigMatch0(...,1000) -> 1000 ticks / 1 kHz = 1 s per interrupt

TIM6_Init(TIM6, 1000000)   -> 1 MHz tick (1 µs resolution)
TIM6_ConfigMatch0(...,1000) -> 1 ms per interrupt

Pattern 2: Input capture (measure pulse width or frequency)

Catch rising edges on PB4 and measure the time between them. At a 2 MHz count rate you get 0.5 µs resolution — plenty for IR and most protocol work.

volatile uint16_t capture_value = 0;
volatile uint8_t  capture_flag = 0;

int main(void)
{
    SYS_SystemInitial();

    PB_4_INIT(PB_4_TIM6_CAP0);           /* PB4 -> capture channel 0 */

    TIM6_Init(TIM6, 2000000);            /* 2 MHz: 0.5 µs per tick */
    TIM6_SetTimerType(TIM6, 0);

    TIM6_ConfigCapture0(TIM6, RISE_EDGE, TIM_CAPTURE_TRIGGER_INT);

    NVIC_EnableIRQ(TIM6_IRQn);
    TIM6_START;

    while (1) {
        if (capture_flag) {
            capture_flag = 0;
            /* period  = capture_value / 2000000  seconds
               freq    = 2000000 / capture_value  Hz          */
        }
    }
}

void TIM6_IRQHandler(void)
{
    if (TIM6_GetIntStatus(TIM6) & TIM_CAPTURE_0_INT)   /* 0x10 */
        capture_value = TIM6_GetCapture0Value(TIM6),
        capture_flag = 1;

    TIM6_ClearIntFlag(TIM6);
}

Pattern 3: Match + Capture together (IrDA-style decoding)

The SDK’s Demo-Timer - Irda example is the best reference for combining both mechanisms: Capture measures the gap between edges, Match0 acts as a timeout detector — if no edge arrives for ~6.5 s the counter overflows and the decoder resynchronizes to the start bit.

TIM6_Init(TIM6, 10000);                            /* 100 µs per tick */
TIM6_SelectTimerClearSignal(TIM6, 0, RISE_EDGE);   /* CAP0 edge clears counter */
TIM6_ConfigMatch0(TIM6, 0xFFFF,                    /* overflow ≈ 6.5 s of silence */
                  TIM_MATCH_TRIGGER_INT,
                  TIM_MATCH_OUT_DO_NOTHING);
TIM6_ConfigCapture0(TIM6, FALL_EDGE, TIM_CAPTURE_TRIGGER_INT);

In the ISR: a Match0 flag means “signal lost, wait for start bit”; anything else is a capture event whose value tells you whether the bit was a 0 or a 1.

API cheat sheet

TaskAPINotes
InitTIM6_Init(TIM6, freq)freq ≤ SystemCoreClock
ModeTIM6_SetTimerType(TIM6, m)0=timer, 1=edge count, 4=quadrature, 5=trigger, 7=gated
Match0–3TIM6_ConfigMatch0..3(TIM6, ticks, int_act, out_act)SDK subtracts 1 internally
Capture0–3TIM6_ConfigCapture0..3(TIM6, edge, int_req)RISE / FALL / BOTH_EDGE
Read captureTIM6_GetCapture0..3Value(TIM6)16-bit register value
Interrupt statusTIM6_GetIntStatus(TIM6)bit0–3 = Match0–3, bit4–7 = Capture0–3
Clear flagsTIM6_ClearIntFlag(TIM6)writes 0xFF — clears all eight
Start / stopTIM6_START / TIM6_STOPmacros on the TCR register
PWMTIM6_PWMInit(TIM6, freq) + TIM6_SetPWM0..2Duty(TIM6, d)Match3 is the period; duty 0–100

Interrupt status byte:

bit7  bit6  bit5  bit4  bit3  bit2  bit1  bit0
CAP3  CAP2  CAP1  CAP0  MAT3  MAT2  MAT1  MAT0
0x80  0x40  0x20  0x10  0x08  0x04  0x02  0x01

Match actions (combine with |): TIM_MATCH_TRIGGER_INT (0x1), TIM_MATCH_RESET_COUNTER (0x2), TIM_MATCH_STOP_COUNTER (0x4). Pin output actions: do-nothing / reset low / set high / toggle.

Seven pitfalls from real projects

  1. Match values are +1. The SDK computes MR = ticks - 1. To interrupt after 1000 counts, pass 1000 — not 999.
  2. You must clear the interrupt flag. TIM6_ClearIntFlag() writes 0xFF and clears all eight flags at once. Forget it and the ISR re-enters forever.
  3. TIM6 and TIM7 share everything — including this article’s entire API. The IRQ handler names differ though: TIM6_IRQHandler vs TIM7_IRQHandler.
  4. Handler names are fixed. They’re exported [WEAK] from the startup file; define your own with exactly that name to override.
  5. The SDK’s buzz.c hijacks TIM0_IRQHandler. If you use the buzzer demo and your own TIM0 interrupt, they collide. TIM6/TIM7 have no such conflict — one more reason to prefer them.
  6. Configuring match interrupts is not enough. TIM6_ConfigMatch0() only sets the timer-local MCR bits. You still need NVIC_EnableIRQ(TIM6_IRQn) for the Cortex-M0 core to see the interrupt.
  7. The system clock changes your math. All the numbers above assume the default 20 MHz SystemCoreClock. Bump the clock to 40 or 48 MHz and your tick rates scale with it.

Using the ME32F030 in a new design, or migrating onto it from another part? Ask us for a quote or free samples — and if your question is about timer configuration specifically, you now know exactly which registers to mention.

← SPI NOR Flash vs I2C EEPROM: When to Use Each in Your Design ME32S003 vs STM8S003: The Migration Pitfalls Nobody Tells You About →

Need help applying this to your design?

Ask our FAE team — free migration review, sample support and honest advice.

Contact Our Engineers