Skip to content

rtc for pokemon emerald

ITotalJustice edited this page Jul 14, 2022 · 2 revisions

rtc

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):

gpio

  • 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;
    }
}

rtc

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!
    }
}

State_init1

remain in this state until CS=0 and SCK=1, then, advance to State_init2.

if (CS == 0 && SCK == 1)
{
    state = State_init2;
}

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;
}

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

Clone this wiki locally