There are two oscillators on the board (external to the CPU) named OSC1 and OSC3.
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”.
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”.
These specs are unfortunately from the 2009 datasheet, when ideally we would like a 2001 datasheet.
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.
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.
IRQ_ENA2 |= IRQ2_1HZ; to enable, IRQ_ENA2 &= ~IRQ2_1HZ; to disable.IRQ_ACT2 |= IRQ2_1HZ; to reset, use IRQ_ACT2 & IRQ2_1HZ as a boolean to read.IRQ_PRI2 &= ~PRI2_TIM256(3); to clear it then IRQ_PRI2 |= PRI2_TIM256(x); to set the new priority, where x can be 0-3 (0 = disabled).
IRQ_PRI2 = PRI2_TIM256(x) | ...; where ... is anything else that needs to be set in IRQ_PRI2.In previous documentation and code, this timer was once known in the community as the “256Hz Timer”.
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.
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”.
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”.
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”.
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.
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.
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.