Pokémon mini Development Documentation

Timers & Oscillators

Oscillators

There are two oscillators on the board (external to the CPU) named OSC1 and OSC3.

OSC1

This is a low-power 32.768 kHz crystal resonator (passive oscillator) notably used for maintaining the real-time clock, but is also used elsewhere. There is no way to disable this oscillator outside of putting the system into sleep mode.

This oscillator is labeled Y1 on the circuit board and it does have text but it may not be visible; it’s an engraved text KDS followed by a number representing the final digit of the year it was printed in then a letter representing the month (A for January up to M for December). It’s not a surface-mount component, but is glued down.

In previous documentation and code, this oscillator was once known in the community as “oscillator 2”.

OSC1 specs

OSC3

This is the high-speed 4.00 MHz ceramic resonator (passive oscillator) used for the general purpose timers. It can be disabled by writing a 0 to the OSCC register and is also disabled when the system enters sleep mode.

This oscillator is labeled Y2 on the circuit board and has text printed on the top which looks like a curved M in a box followed by 4.00 and then a single-character serial such as L or J. It is a surface-mount component.

Disabling this oscillator when not needed can save power. To turn it off call int [74h] and to turn it back on call int [72h]. This will cause the CPU to run slower, however.

In previous documentation and code, this oscillator was once known in the community as “oscillator 1”.

OSC3 specs

These specs are unfortunately from the 2009 datasheet, when ideally we would like a 2001 datasheet.

Seconds Timer

A timer which increments once every second. It uses OSC1 as its clock source.

This timer informs the real-time clock (RTC) in commercial games. As such, if homebrew resets or pauses the timer or sleeps the console (TODO?), it will force commercial games to ask for the user to enter the time again.

There are no interrupts related to this timer.

Clock Timer

A timer which increments 256 times per second. It uses OSC1 as its clock source.

There are four interrupts tripped by this timer at different Hz.

In previous documentation and code, this timer was once known in the community as the “256Hz Timer”.

Programmable timers

The Pokémon mini offers 3 pairs of general purpose programmable timers. Each set can independently be used either as a single 16-bit timer or split into two 8-bit channels with their own interrupts. These timers count down at a configurable rate.

Each of the 6 8-bit timers can individually be configured to use either OSC1 or OSC3 as its source clock. When using a pair as a 16-bit timer, it uses the clock source of the first member of the pair (for example, PTM0) and the interupt of the second member (for example, PTM1).

In previous documentation, these timers were known as the “general purpose timers”, which is still an acceptable name, but this documentation will use “programmable timers” or PTs.

PTM0-1

16-bit timer comprised of PTM0 as the lower order 8 bits and PTM1 as the higher order 8 bits. That is, PTM1:PTM0 or given PTM1 is 0x10 and PTM0 is 0xf3, the value is 0x10f3.

In previous documentation and code, this timer has been known in the community as the “timer 1”.

PTM2-3

16-bit timer comprised of PTM2 as the lower order 8 bits and PTM3 as the higher order 8 bits. That is, PTM3:PTM2 or given PTM3 is 0x10 and PTM2 is 0xf3, the value is 0x10f3.

In previous documentation and code, this timer has been known in the community as the “timer 2”.

PTM4-5

16-bit timer comprised of PTM4 as the lower order 8 bits and PTM5 as the higher order 8 bits. That is, PTM5:PTM4 or given PTM5 is 0x10 and PTM4 is 0xf3, the value is 0x10f3.

For information on using PTM4-5 for audio, see Audio / Sound.

In previous documentation and code, this timer has been known in the community as the “timer 3”.

Programmable timer configuration

When choosing which timer to use for some purpose, you must choose whether to use some pair as a 16-bit timer or to split it and use one of the 8-bit timers. To use the 16-bit timer, write a 1 to the respective MODE16 register (see table below) and to use one of the 8-bit timers, write a 0 to it (this is the initial value).

Register pm.h Timer L Timer H Timer 16
MODE16_A TMR1_CTRL_L PTM0 PTM1 PTM0-1
MODE16_B TMR2_CTRL_L PTM2 PTM3 PTM2-3
MODE16_C TMR3_CTRL_L PTM4 PTM5 PTM4-5

To set MODE16 with pm.h, use, for example, TMR1_CTRL_L |= 0x80; To unset MODE16 with pm.h, use, for example, TMR1_CTRL_L &= ~0x80;

Before you first enable a timer, you’ll want to configure its basic settings. Set the clock source via PRTFx where x is the timer index. Write a 1 to use OSC1 and a 0 to use OSC3. You likely won’t change this again unless you intend to repurpose the timer. To make this selection, refer the table below.

For timer(s) Use OSC1 Use OSC3
PTM0 & PTM0-1 TMR1_OSC |= 1; TMR1_OSC &= ~1;
PTM1 TMR1_OSC |= 2; TMR1_OSC &= ~2;
PTM2 & PTM2-3 TMR2_OSC |= 1; TMR2_OSC &= ~1;
PTM3 TMR2_OSC |= 2; TMR2_OSC &= ~2;
PTM4 & PTM4-5 TMR3_OSC |= 1; TMR3_OSC &= ~1;
PTM5 TMR3_OSC |= 2; TMR3_OSC &= ~2;

After this you configure the division ratio (prescale), reload data (preset), and compare data (pivot). This can be changed regularly while the timer is running in order to adjust when some overflow occurs.

The prescale along with the clock source determines how quickly the counter decrements. Refer to the table below:

Prescale fOSC1 / div = Hz fOSC3 / div = Hz
0 32768 / 1 = 32768 4M / 2 = 2M
1 32768 / 2 = 16384 4M / 8 = 500k
2 32768 / 4 = 8192 4M / 32 = 125k
3 32768 / 8 = 4096 4M / 64 = 62500
4 32768 / 16 = 2048 4M / 128 = 31250
5 32768 / 32 = 1024 4M / 256 = 15625
6 32768 / 64 = 512 4M / 1024 = 3906.25
7 32768 / 128 = 256 4M / 4096 = 976.5625

When a timer underflows, it loads the preset into the counter as the starting value to count down from. It can also be reset to this value manually by writing a 1 to PSETx where x is the timer index.

When the pivot is matched, a compare match interrupt is triggered but the timer is not reset at that point. On matching PTM4-5 (TODO: or just PTM5?), an output signal is sent to the speaker.

The table below lists the timers and which registers control these three values. The use of a colon between two registers indicates the values are concatenated together such that 0x10:0xa5 would become 0x10a5. The register using the timer’s name (for example, PTM0) is the data register which contains the count value for reading; for the 16-bit timers this is PTMy:PTMx for any PTMx-y.

Timer Prescale Preset Pivot
PTM0 PST0 RDR0 CDR0
PTM1 PST1 RDR1 CDR1
PTM2 PST2 RDR2 CDR2
PTM3 PST3 RDR3 CDR3
PTM4 PST4 RDR4 CDR4
PTM5 PST5 RDR5 CDR5
PTM0-1 PST0 RDR1:RDR0 CDR1:CDR0
PTM2-3 PST2 RDR3:RDR2 CDR3:CDR2
PTM4-5 PST4 RDR5:RDR4 CDR5:CDR4

With pm.h:

Timer Prescale Preset Pivot Data
PTM0 TMR1_SCALE TMR1_PRE_L TMR1_PVT_L TMR1_CNT_L
PTM1 TMR1_SCALE TMR1_PRE_H TMR1_PVT_H TMR1_CNT_H
PTM2 TMR2_SCALE TMR2_PRE_L TMR2_PVT_L TMR2_CNT_L
PTM3 TMR2_SCALE TMR2_PRE_H TMR2_PVT_H TMR2_CNT_H
PTM4 TMR3_SCALE TMR3_PRE_L TMR3_PVT_L TMR3_CNT_L
PTM5 TMR3_SCALE TMR3_PRE_H TMR3_PVT_H TMR3_CNT_H
PTM0-1 TMR1_SCALE TMR1_PRE TMR1_PVT TMR1_CNT
PTM2-3 TMR2_SCALE TMR2_PRE TMR2_PVT TMR2_CNT
PTM4-5 TMR3_SCALE TMR3_PRE TMR3_PVT TMR3_CNT

The scale registers are constructed as PRPRTy:PSTy:PRPRTx:PSTx where each PST register is 3 bits and y = x + 1. This means that in order to set the prescale for PTM0 you must do TMR1_SCALE &= ~0x07; to clear then TMR1_SCALE |= prescale; to assign it. For PTM1 you must do TMR1_SCALE &= ~0x70; to clear then TMR1_SCALE |= prescale << 4; to assign it. They are initialized to 0 so there’s no need to clear it in startup code. PRPRT registers should always be set to 1.

Programmable timer interrupts

There are two potential interrupts for each timer: the underflow and the compare data interrupts. Underflow occurs the tick after a count reaches 0, causing it to preset. The compare data interrupt occurs when the count is equal to the value stored in the relevant CDR register.

Although all of these interrupts can exist, not all of them are known to be mapped. The following table lists which ones are known to be accessible.

Timer interrupt Factor Enable Priority Software entry address
PTM0 underflow FTU0 ETU0 PPT0-1 $2126
PTM1 underflow FTU1 ETU1 PPT0-1 $2120
PTM2 underflow FTU2 ETU2 PPT2-3 $211a
PTM3 underflow FTU3 ETU3 PPT2-3 $2114
PTM5 underflow FTU5 ETU5 PPT4-5 $212c
PTM5 CDR match FTC5 ETC5 PPT4-5 $2132
PTM0-1 underflow FTU1 ETU1 PPT0-1 $2120
PTM2-3 underflow FTU3 ETU3 PPT2-3 $2114
PTM4-5 underflow FTU5 ETU5 PPT4-5 $212c
PTM4-5 CDR match FTC5 ETC5 PPT4-5 $2132

With pm.h, the factor flags are all in IRQ_ACT1, the enable flags in IRQ_ENA1, and the priority flags in IRQ_PRI1.

Timer Underflow flag CDR flag Priority macro
PTM0 IRQ1_TIM1_LO_UF n/a PRI_TIM1
PTM1 IRQ1_TIM1_HI_UF n/a PRI_TIM1
PTM2 IRQ1_TIM2_LO_UF n/a PRI_TIM2
PTM3 IRQ1_TIM2_HI_UF n/a PRI_TIM2
PTM5 IRQ1_TIM3_HI_UF IRQ1_TIM3_PIVOT PRI_TIM3
PTM0-1 IRQ1_TIM1_HI_UF n/a PRI_TIM1
PTM2-3 IRQ1_TIM2_HI_UF n/a PRI_TIM2
PTM4-5 IRQ1_TIM3_HI_UF IRQ1_TIM3_PIVOT PRI_TIM3

For more information about how interrupts work, see Interrupts.

Enabling and pausing programmable timers

In order to turn a timer on, the following must be done, where x is some timer index (use 0 for PTM0-1, etc):

To pause the timer, reset PTRUNx register to 0. The timer will decrement once more before pausing. To resume the timer, set PTRUNx register back to 1. To preset the timer count, write 1 to PSETx.

When using the HALT operation, the timers are not paused. When using the SLP operation, the timers will be paused and will resume from where they left off when the console reawakens.