Skip to content

Latest commit

 

History

History
300 lines (228 loc) · 16.7 KB

Timers.md

File metadata and controls

300 lines (228 loc) · 16.7 KB

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

  • Part number: DT-26 (datasheet)
    • D - Daishinku Corp (company name)
    • T - Tuning fork crystal resonator
    • 2 - 2 mm diameter
    • 6 - 6 mm height
  • Load Capacitance: unconfirmed
  • Drive Level: 1.0μW (2.0μW max.)
  • Frequency Tolerance: ±20 ppm max. (at 25℃)
  • Series Resistance: unconfirmed
  • Turnover Temperature: +25℃±5℃
  • Parabolic Coefficient: -0.04 ppm/℃² max.
  • Operating Temperature Range: -10 to +60℃
  • Storage Temperature Range: -20 to +70℃
  • Shunt Capacitance: 1.1pF typ.
  • Aging: ±5 ppm/year

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.

  • Part number: EFOS4004E5 (datasheet)
    • EFO - Ceramic resonator
    • S - 2 to 13 MHz type with built-in capacitors and 3 terminals
    • 4004 - 4.00 MHz nominal oscillation frequency
    • E - Embossed taping style packaging
    • 5 - ±0.5% frequency tolerance
  • Built-in Capacitors: 33 pF
  • Oscillation frequency drift: ±0.2% overall stability
    • -20°C ≈ -0.1
    • 20°C ≈ 0.0
    • 40°C ≈ 0.02
    • 60°C ≈ 0.0
    • 80°C ≈ -0.04

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.

  • Write 0 to STRUN to pause this timer or 1 to start it.
    • When reading, 0 means paused and 1 means running.
    • In pm.h, STRST is bit 0 of SEC_CTRL
  • Write 1 to STRST to reset this timer.
    • In pm.h, STRST is bit 1 of SEC_CTRL
  • Read the count from the STD register
    • In pm.h, STD is called SEC_CNT

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.

  • Write 0 to TMRUN to pause this timer or 1 to start it.
    • When reading, 0 means paused and 1 means running.
    • In pm.h, TMRUN is bit 0 of TMR256_CTRL
  • Write 1 to TMRST to reset this timer.
    • In pm.h, TMRST is bit 1 of TMR256_CTRL
  • Read the count from the TMD register
    • The individual bits decompose into different counts for different Hz. That is, each bit increments at a different Hz by nature of the whole byte incrementing 256 times per second.
      • TMD7 - overflows every 1 Hz, increments every 2 Hz
      • TMD6 - overflows every 2 Hz, increments every 4 Hz
      • TMD5 - overflows every 4 Hz, increments every 8 Hz
      • TMD4 - overflows every 8 Hz, increments every 16 Hz
      • TMD3 - overflows every 16 Hz, increments every 32 Hz
      • TMD2 - overflows every 32 Hz, increments every 64 Hz
      • TMD1 - overflows every 64 Hz, increments every 128 Hz
      • TMD0 - overflows every 128 Hz, increments every 256 Hz
    • In pm.h, TMD is called TMR256_CNT

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

  • Clock timer 1 Hz interrupt
    • Triggers every second, when TMD7 overflows.
    • Write 1 to ECTM1 to enable, 0 to disable.
      • When reading, 1 means enabled and 0 means disabled.
      • With pm.h, use IRQ_ENA2 |= IRQ2_1HZ; to enable, IRQ_ENA2 &= ~IRQ2_1HZ; to disable.
    • Write 1 to FCTM1 to reset the activation.
      • When reading, 1 means the interrupt is active (ready to trigger), 0 otherwise.
      • With pm.h, use IRQ_ACT2 |= IRQ2_1HZ; to reset, use IRQ_ACT2 & IRQ2_1HZ as a boolean to read.
  • Clock timer 2 Hz interrupt
    • Triggers every 500 ms, when TMD6 overflows.
    • Registers are: ECTM2, FCTM2, and IRQ2_2HZ in pm.h
  • Clock timer 8 Hz interrupt
    • Triggers every 125 ms, when TMD4 overflows.
    • Registers are: ECTM8, FCTM8, and IRQ2_8HZ in pm.h
  • Clock timer 32 Hz interrupt
    • Triggers every 31250 µs, when TMD2 overflows.
    • Registers are: ECTM32, FCTM32, and IRQ2_32HZ in pm.h
  • Write to PCTM to set the priority for all clock timer interrupts.
    • You can read out the current priority as well.
    • With pm.h, use 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).
      • When setting priorities in initial setup, you don't need to clear anything and can instead use 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".

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;`
PTM1 `TMR1_OSC = 2;`
PTM2 & PTM2-3 `TMR2_OSC = 1;`
PTM3 `TMR2_OSC = 2;`
PTM4 & PTM4-5 `TMR3_OSC = 1;`
PTM5 `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):

  • CKSELx should already be set to 0 by default, do not change it
    • In pm.h, this is bit 0 in TMRa_CTRL_hl registers.
  • Set PRPRTx register to 1
    • As mentioned in the configuration section, in pm.h these are contained in the TMRa_SCALE registers as bit 7 and 3.
  • Set PTRUNx register to 1
    • In pm.h, this is bit 2 in TMRa_CTRL_hl registers.

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.