You are on page 1of 6

codeslinger.co.

uk
H o mB e a s Zi u c k sM o e g a D r M i a v e s /t G e re n S e y s Cs i ht s ie Bpm l 8 o Gamebo y g

codeslinger.co.uk
Gamebo y - The Timers.

Timer Explanation:
This sectio n sho uld no t be co nfused with the timing explained in the Getting Started sectio n. Tho se timers were abo ut getting the speed o f the emulatio n right and synchro nizing the cpu with graphics. The gamebo y has a timer in memo ry which co unts up at a certain frequency and requests an interupt when it o verflo ws. This is the timer this sectio n is abo ut. The timer is lo cated in memo y address 0 xFF0 5 and will co unt up at a set frequency. The frequency it co unts up at is set by the timer co ntro ller in memo ry address 0 xFF0 7. Whenever the timer o verflo ws (remember the memo ry elements are all unsigned bytes so o verflo wing means go ing greater than 255) it requests a timer interupt and then resets itself to the value o f the timer mo dulato r in memo ry address 0 xFF0 6 . This gives us the fo llo wing definitio ns:

Gameboy Emulation:
Getting Started The Hardware Memory Control and Map ROM and RAM Banking The Timers Interupts LCD DMA Transfer Graphic Emulation Joypad Emulation

#define TIMA 0 xFF0 5 #define TMA 0 xFF0 6 #define TMC 0 xFF0 7

Emulating Time:

Opcode Examples Finished Product PDFmyURL.com

There are 4 frequencies the timer co ntro ller (TMA) can set the timer (TIMA) to co unt up at. These are:

Finished Product

40 9 6 Hz 26 2144 Hz 6 5536 Hz 16 38 4 Hz

If we take the first o ne fo r example (40 9 6 Hz) this means that the timer sho uld increment its value 40 9 6 times a seco nd. Which also means assuming that the timer mo dulato r is always 0 (meaning whenever the timer o verflo ws it will start co unting fro m 0 again) then the timer wo uld o verflo w 16 times a seco nd (40 9 6 /256 ). This is impo rtant to mo nito r so we kno w o ur timer emulatio n is accurate and will be causing timer interupts at the co rrect rate (assuming the timer interupt is enabled which I'll discuss in the next chapter). So using frequency 40 9 6 as an example ho w wo uld we emulate this in co de? Well fo rtunately fo r us we are already accurately emulating the CPU timing by keeping a running to tal o f each o pco de clo ck cycles we have executed this frame. As explained earlier the cpu clo ck speed runs at 419 430 4Hz so if we kno w the current timer frequency we can wo rk o ut ho w many clo ck cycles need to o f passed until we increment o ur timer register. Our fo rmula will be the fo llo wing: #define CLOCKSPEED 4194304 ; m_TimerCounter = CLOCKSPEED/frequency ;

Where m_TimerCo unter is an int. This sho uld be set to 10 24 at the start o f game emulatio n (explained belo w) So if the current frequency is 40 9 6 Hz then o ur timer co unter will be 10 24 (CLOCKSPEED/40 9 6 ). This means that "fo r every 10 24 clo ck cycles o ur cpu do es o ur timer sho uld increment itself o nce". Ho wever if o ur frequency was 16 38 4 then o ur co unter wo uld be 256 (CLOCKSPEED/16 38 4) which means "Fo r every 256 clo ck cycles o ur cpu do es o ur timer sho uld increment itself o nce". If yo u lo o k at the main emulatio n update lo o p which is called 6 0 times a seco nd (discussed in the Getting Started sectio n) yo u can see that I've already taken into acco unt the timers with the UpdateTimers funcatio n and as yo u can see Im passing in the clo ck cycle fo r the last o pco de. void Emulator::Update( ) { const int MAXCYCLES = 69905 ; int cyclesThisUpdate = 0 ; while (cyclesThisUpdate < MAXCYCLES) { int cycles = ExecuteNextOpcode( ) ; cyclesThisUpdate+=cycles ;
PDFmyURL.com

UpdateTimers(cycles) ; UpdateGraphics(cycles) ; DoInterupts( ) ; } RenderScreen( ) ; }

This is ho w to implement the UpdateTimers functio n: void Emulator::UpdateTimers( int cycles ) { DoDividerRegister(cycles); // the clock must be enabled to update the clock if (IsClockEnabled()) { m_TimerCounter -= cycles ; // enough cpu clock cycles have happened to update the timer if (m_TimerCounter <= 0) { // reset m_TimerTracer to the correct value SetClockFreq( ) ; // timer about to overflow if (ReadMemory(TIMA) == 255) { WriteMemory(TIMA,ReadMemory(TMA)) ; RequestInterupt(2) ; } else { WriteMemory(TIMA, ReadMemory(TIMA)+1) ; } } } }

PDFmyURL.com

The Do DividerRegister functio n and the IsClo ckEnabled() functio n will be implemented in a minute. The IsClo ckEnabled functio n basically it is a setting in the timer co ntro ller (TMC) which pauses o r resumes the timer co unting. If IsClo ckEnabled() returns false then the timer do es no t reset itself, neither do es the timerco unter but they bo th just pause until it is enabled again. SetClo ckFrequency will also be implemented in a minute. Its purpo se will be to reset m_TimerCo unter upo n reaching zero to the co rrect value fo r the current clo ck frequency so it can start co unting do wn at the co rrect rate again. The rest o f the co de just increments the current timer (TIMA) value and checks to see if it is abo ut to o verflo w. If it do es o verflo w then it resets the timer(TIMA) to the value in the timer mo dulato r (TMA) and requests a timer interupt. The timer interupt is bit 2 o f ther interupt request register which will be explained fully in the next chapter.

The Timer Controller:


The timer co ntro ller (TMC) is a 3 bit register which co ntro lls the timer (DUH!). Bit 1 and 0 co mbine to gether to specify which frequency the timer sho uld increment at. This is the mapping:

0 0 : 40 9 6 Hz 0 1: 26 2144 Hz 10 : 6 5536 Hz 11: 16 38 4 Hz

Bit 2 specifies whether the timer is enabled(1) o r disabled(0 ). With this info rmatio n we can write the IsClo ckEnabled() functio n like so : bool Emulator::IsClockEnabled() const { return TestBit(ReadMemory(TMC),2)?true:false ; }

As stated earlier the frequency defaults to 40 9 6 Hz but we need to mo nito r a way o f checking if it has changed. The easyest way to do this is by editing o ur WriteMemo ry functio n to detect if the game is trying to change the timer co ntro ller. If the game is changing the timer co ntro ller then we need to check if the current clo ck frequency is different to what the game is trying to change it to and if it is then we much reset the timer co unter so it co unts at the new frequency. This is simple to do by adding the fo lling co de to the WriteMemo ry functio n: else if (TMC == address) { BYTE currentfreq = GetClockFreq() ; m_GameMemory[TMC] = data ; BYTE newfreq = GetClockFreq();
PDFmyURL.com

if (currentfreq != newfreq) { SetClockFreq(); } }

GetClo ckFreq and SetClo ckFreq are defined as fo llo ws: // remember the clock frequency is a combination of bit 1 and 0 of TMC BYTE Emulator::GetClockFreq( )const { return ReadMemory(TMC) & 0x3 ; } /////////////////////////////////////////// void Emulator::SetClockFreq() { BYTE freq = GetClockFreq( ) ; switch (freq) { case 0: m_TimerCounter = 1024 ; break ; // freq 4096 case 1: m_TimerCounter = 16 ; break ;// freq 262144 case 2: m_TimerCounter = 64 ; break ;// freq 65536 case 3: m_TimerCounter = 256 ; break ;// freq 16382 } }

As stated earlier the m_TimerCo unter is set to the value o f CLOCKSPEED/frequency.

Divider Register:
The final timing related area that needs emulating is the Divider Register. It wo rks very similar to the timers which is why I have included it in this sectio n aswell as put the co de to emulate it inside the UpdateTimers functio n. The way it wo rks is it co ntinually co unts up fro m 0 to 255 and then when it o verflo ws it starts fro m 0 again. It do es no t cause an interupt when it o verflo ws and it canno t be paused like the timers. It co unts up at a frequency o f 16 38 2 which means every 256 CPU clo ck cycles the divider register needs to
PDFmyURL.com

increment. We need ano ther int co unter like m_TimerCo unter to keep track o f when it needs to increment, this is called m_DividerCo unter which initially is set to 0 and co nstantly increments to 255 then starts again. The Divider Register is fo und at register address 0 xFF0 4. The Do DividerRegister functio n called in UpdateTimers is emulated like so :

void Emulator::DoDividerRegister(int cycles) { m_DividerRegister+=cycles; if (m_DividerCounter >= 255) { m_DividerCounter = 0 ; m_Rom[0xFF04]++ ; } }

Yo u may be wo ndering why I am incrementing the divider register directly and no t using WriteMemo ry. The answer is that the gamebo y hardware do es no t allo w writing to the divider register and when ever the game tries to do so it resets the divider register to 0 . We need to implement this functio nality o urselves in o ur WriteMemo ry functio n. This is why we canno t use this functio n to increment the divider register because it wo uld always reset it to 0 ! Speaking o f which we need to edit o ur write memo ry functio n to trap the divider register. //trap the divider register else if (0xFF04 == address) m_Rom[0xFF04] = 0 ;

Thats everything related to timers. Head o ver to the next sectio n o n Interupts

Copyright 2008 codeslinger.co.uk

PDFmyURL.com

You might also like