-
Notifications
You must be signed in to change notification settings - Fork 4
rtc for pokemon emerald
high level guide to the rtc for pokemon emerald (info likely not accurate).
feel free to reference my implementation of rtc here (remember to give credit):
-
https://github.com/ITotalJustice/notorious_beeg/blob/master/src/core/rtc.cpp
-
https://github.com/ITotalJustice/notorious_beeg/blob/master/src/core/rtc.hpp
-
https://github.com/ITotalJustice/notorious_beeg/blob/master/src/core/gpio.hpp
- todo:
u32 gpio_read(u32 addr)
{
switch (addr)
{
case 0x80000C4: // I/O Port Data (rw or W)
return gpio.data & gpio.read_mask;
case 0x80000C6: // I/O Port Direction (rw or W)
return gpio.write_mask; // remember we modify the rmask
case 0x80000C8: // I/O Port Control (rw or W)
return gpio.rw;
default:
return rom_read(addr);
}
}
void gpio_write(u32 addr, u8 value)
{
switch (addr)
{
case 0x80000C4: // I/O Port Data (rw or W)
gpio.data = value & gpio.write_mask;
rtc_write(gpio.data);
break;
case 0x80000C6: // I/O Port Direction (rw or W)
// the direction port acts as a mask for r/w bits
// bitX = 0, read only (in)
// bitX = 1, write only (out)
gpio.read_mask = ~value & 0xF;
gpio.write_mask = value & 0xF;
break;
case 0x80000C8: // I/O Port Control (rw or W)
gpio.rw = value & 0x1;
break;
}
}
as explained above, data is written to using port 0x80000C4
.
// todo: explain more here, add intro to rtc an list some supported games etc
i found it better to view the rtc as a sort of state machine:
enum State
{
State_init1, // waits until CS=0 and SCK=1
State_init2, // waits until CS=1 and SCK=1 (CS rises)
State_command, // waits until 8 bits have been transferred
State_read, // transfers X amount of bits
State_write, // transfers X amount of bits
};
void rtc_write(u8 data)
{
const bool SCK = data & 0x1; // bit0
const bool SIO = data & 0x2; // bit1
const bool CS = data & 0x4; // bit2
switch (state)
{
// this part is explained below!
}
}
remain in this state until CS
=0 and SCK
=1, then, advance to State_init2
.
if (CS == 0 && SCK == 1)
{
state = State_init2;
}
remain in this state until CS
=1 and SCK
=1, then, advance to State_command
.
NOTE: i have noticed that emerald
will often do an invalid write in this state, such that CS
=0 and SCK
=1. This could be a bug in my handling of the rtc, however, it seems to still work in emerald
so I've ignored it (for now).
if (CS == 1 && SCK == 1)
{
state = State_command;
}
the goal of this state is to transfer 8-bits (1 byte) of data. Part of that data is used as the command
.
The transfer works like this, when SCK
=0, save SIO
. when SCK
=0, that saved bit is then written to an internal buffer, starting from bit0, up until bit7. eg, first bit is written to bit0, then the second bit would be written to bit1.
A C example of the above:
assert(CS); // CS should stay high throughout this transfer
if (SCK == 0)
{
pending_bit = SIO; // save the bit
}
else if (SCK == 1)
{
buffer |= pending_bit << bit_counter; // store that saved bit
bit_counter++; // advance the bit counter
if (bit_counter == 8) // are we done?
{
// this part is explained below!
}
}
once all 8bits have been written, you now have your command code!
however! first you need to check if the data you have is in the correct order, as, sometimes the data can be sent with the bits the wrong way round. this is simple to check and fix.
there is a fixed value of 0b0110
at bits 4-7 of that data. if the value isn't there, then you have to reverse the bits.0x60
Here is an example in C:
u8 bit_reverse(const u8 data)
{
u8 output = 0;
for (int i = 0; i < 8; i++)
{
const bool bit = data & (1 << (7 - i));
output |= bit << i;
}
return output;
}
// this is from above!
if (bit_counter == 8) // are we done?
{
if ((buffer & 0x60) != 0x60) // 0b0110'0000
{
assert((buffer & 0x6) == 0x6); // make sure we have the code
buffer = bit_reverse(buffer); // reverse the bits
}
}
now we can get the command code.
enum Command
{
Command_reset = 0,
Command_control = 1,
Command_date = 2,
Command_time = 3,
Command_alarm1 = 4, // dsi only
Command_alarm2 = 5, // dsi only
Command_irq = 6,
Command_unused = 7,
};
// this is from above!
if (bit_counter == 8) // are we done?
{
if ((buffer & 0x60) != 0x60) // 0b0110'0000
{
assert((buffer & 0x6) == 0x6); // make sure we have the code
buffer = bit_reverse(buffer); // reverse the bits
}
if (buffer & 0x1) // if bit0 = 1, this is a read command
{
state = State_read;
}
else // else if bit0 = 0, this is a write command
{
state = State_write;
}
command = buffer & 0x7;
switch (command)
{
// this part is explained below!
}
}
NOTE: i will only explain the control
, date
and time
commands!
if you're following along with emerald, the value of buffer should be 0xC6
and the buffer shouldn't need bit_reverse()
.
the first command is control
and it's a read
. this means that the game will read from control, 1 bit at a time.
// todo: finish the rest