Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

processClocks() #3

Closed
EstebanFuentealba opened this issue Sep 22, 2023 · 3 comments
Closed

processClocks() #3

EstebanFuentealba opened this issue Sep 22, 2023 · 3 comments

Comments

@EstebanFuentealba
Copy link

EstebanFuentealba commented Sep 22, 2023

Hello, I believe I've arrived a bit late to the project, I hope you're still around. I'm working on a project with Flipper Zero and an ESP32-S2, which has enough pins to read a Game Boy cartridge.

I've successfully managed to read the images stored in the cartridge's RAM and display them on the Flipper's 128x64 screen.

Game Boy Flipper Zero

Now, I wanted to capture photos from the FZ or maybe even do a live video with the GAME BOY Camera, but half of the time, I'm not sure what I'm doing, and I get lost in many parts.

Could you please provide a detailed explanation of what the processClocks method does?

void processClocks(void)
{
setAddress(0xA000);
setReadMode(0xA000 < 0x8000);
asm volatile (
".equ PORTB,0x05 \n"
".equ PIND,0x09 \n"
"cli \n" // Disable interrupts
"L_%=: \n"
"sbi PORTB,5 \n" // 2 Cycles | PORTB.5 = PHI pin = PIN 13
"nop \n" // 1 Cycle
"nop \n"
"nop \n"
"nop \n"
"nop \n"
"nop \n"
"cbi PORTB,5 \n" // 2 Cycles
//"nop \n" // Comment 1 nop: 16 MHz / 15 = 1066667 Hz (closer to 1048576 Hz than 16 MHz / 16 = 1000000 Hz)
"nop \n"
"nop \n"
"sbic PIND,2 \n" // 1 Cycle if set | Skip next instruction if bit cleared.
"rjmp L_%= \n" // 2 Cycles | PIND.2 = data[0] = PIN 2
"sei \n" // Enable interrupts
::);
setWaitMode();
}

Or, if it's possible, could it be written in Arduino code instead of assembly, like in the following?

// Address pins
const static uint32_t ADDRESS_GB_GBC_PINS[16] = 
{
    GAMEBOY_A00,
    GAMEBOY_A01,
    GAMEBOY_A02,
    GAMEBOY_A03,
    GAMEBOY_A04,
    GAMEBOY_A05,
    GAMEBOY_A06,
    GAMEBOY_A07,

    GAMEBOY_A08,
    GAMEBOY_A09,
    GAMEBOY_A10,
    GAMEBOY_A11,
    GAMEBOY_A12,
    GAMEBOY_A13,
    GAMEBOY_A14,
    GAMEBOY_A15
};
// Data pins
const static uint32_t DATA_GB_GBC_PINS[8] = 
{
    GAMEBOY_D00,
    GAMEBOY_D01,
    GAMEBOY_D02,
    GAMEBOY_D03,
    GAMEBOY_D04,
    GAMEBOY_D05,
    GAMEBOY_D06,
    GAMEBOY_D07
};

void generatePulse() {
  digitalWrite(phi_pin, HIGH);
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");
  digitalWrite(phi_pin, LOW);
  __asm__("nop\n\t"
          "nop\n\t");

}
// ... bla
noInterrupts();
generatePulse();
int raw_data = digitalRead(DATA_GB_GBC_PINS[0]);
while (raw_data == 1) {
    generatePulse();
    raw_data = digitalRead(DATA_GB_GBC_PINS[0]);
}
interrupts();

I hope you can assist me; I believe I'm very close to achieving it 🙏 ... or maybe quite far, haha. I say "quite far" because the CPU frequency of the ESP32 is much higher than that of an Arduino (240MHz), and I'm not sure if it will work 🤔.

@EstebanFuentealba
Copy link
Author

EstebanFuentealba commented Sep 22, 2023

I believe that with the following block of code I borrowed from the client and server, I could achieve it, although there are some Arduino assembly instructions that are not in ESP32.

unsigned char picturedata[16*14*8*8]; // max( 16*8*14*8, 16*14*16 ) sensor pixels , tile bytes

void UpdateMatrixRegisters(int dithering)
{
    //const unsigned char matrix[] = // high light
    //{
    //    0x89, 0x92, 0xA2, 0x8F, 0x9E, 0xC6, 0x8A, 0x95, 0xAB, 0x91, 0xA1, 0xCF,
    //    0x8D, 0x9A, 0xBA, 0x8B, 0x96, 0xAE, 0x8F, 0x9D, 0xC3, 0x8C, 0x99, 0xB7,
    //    0x8A, 0x94, 0xA8, 0x90, 0xA0, 0xCC, 0x89, 0x93, 0xA5, 0x90, 0x9F, 0xC9,
    //    0x8E, 0x9C, 0xC0, 0x8C, 0x98, 0xB4, 0x8E, 0x9B, 0xBD, 0x8B, 0x97, 0xB1
    //};

    const unsigned char matrix[48] = // low light
    {
        0x8C, 0x98, 0xAC, 0x95, 0xA7, 0xDB, 0x8E, 0x9B, 0xB7, 0x97, 0xAA, 0xE7,
        0x92, 0xA2, 0xCB, 0x8F, 0x9D, 0xBB, 0x94, 0xA5, 0xD7, 0x91, 0xA0, 0xC7,
        0x8D, 0x9A, 0xB3, 0x96, 0xA9, 0xE3, 0x8C, 0x99, 0xAF, 0x95, 0xA8, 0xDF,
        0x93, 0xA4, 0xD3, 0x90, 0x9F, 0xC3, 0x92, 0xA3, 0xCF, 0x8F, 0x9E, 0xBF
    };

    int i;
    for(i = 0; i < 48; i++)
    {
        if(dithering)
        {
            writeCartByte(0xA006+i,matrix[i]);
        }
        else
        {
            switch(i%3)
            {
                case 0: writeCartByte(0xA006+i,c1); break;
                case 1: writeCartByte(0xA006+i,c2); break;
                case 2: writeCartByte(0xA006+i,c3); break;
            }
            //writeCartByte(0xA006+i,matrix[i%3]);
        }
    }
}
void processClocks(void)
{
  setAddress(0xA000);
  setReadMode(0xA000 < 0x8000);

  asm volatile (
      ".equ PORTB,0x05    \n"
      ".equ PIND,0x09     \n"

      "cli                \n" // Disable interrupts

        "L_%=:            \n"
        "sbi PORTB,5      \n" // 2 Cycles | PORTB.5 = PHI pin = PIN 13
        "nop              \n" // 1 Cycle
        "nop              \n"
        "nop              \n"
        "nop              \n"
        "nop              \n"
        "nop              \n"
        "cbi PORTB,5      \n" // 2 Cycles
        //"nop              \n" // Comment 1 nop: 16 MHz / 15 = 1066667 Hz (closer to 1048576 Hz than 16 MHz / 16 = 1000000 Hz)
        "nop              \n"
        "nop              \n"
        "sbic PIND,2      \n" // 1 Cycle if set | Skip next instruction if bit cleared.
        "rjmp L_%=        \n" // 2 Cycles       | PIND.2 = data[0] = PIN 2

      "sei                \n" // Enable interrupts
      ::);

  setWaitMode();
}
//TakePictureAndTransfer(0x03,0xE4,0,0x07,0xBF,1,0); //Base
void TakePictureAndTransfer(u8 trigger, u8 unk1, u16 exposure_time, u8 unk2, u8 unk3,
                            int dithering, int thumbnail)
{
    // "Taking picture..."
    //ramEnable();
    writeCartByte(0x0000,0x0A)
    // setRegisterMode(); -> SerialWriteData("Z.",2); //set register mode
    writeCartByte(0x4000,0x10);

    writeCartByte(0xA000,0x00);

    writeCartByte(0xA001,unk1);

    writeCartByte(0xA002,(exposure_time>>8)&0xFF);
    writeCartByte(0xA003,exposure_time&0xFF);

    writeCartByte(0xA004,unk2);

    writeCartByte(0xA005,unk3);

    UpdateMatrixRegisters(dithering);

    // setRamModeBank0(); //set ram mode (bank 0)
    writeCartByte(0x4000,0);
    //  Reading Picture (trigger&0xFF)
    //  ### takePicture
    writeCartByte(0x0000,0x0A); // Enable RAM
    writeCartByte(0x4000,0x10); // Set register mode
    writeCartByte(0xA000,trigger); // Trigger
    processClocks(); // Process <- TODO
    writeCartByte(0x4000,0x00); // Set RAM mode, bank 0
    unsigned int addr = 0xA100;
    unsigned int _size = 16 * 14 * 16;
    // ## setReadMode(0xA100 < 0x8000);
    int i;
    for(i = 0; i < 8; i++)
        pinMode(data_pins[i], INPUT);
    
    digitalWrite(nwr_pin, HIGH);
    digitalWrite(nrd_pin, LOW);
    digitalWrite(ncs_pin, LOW);
    //  ### readPicture
    int i;
    while(_size--)
    {
        setAddress(addr++);
        unsigned char value = getData();
        // Serial.write(value);
        picturedata[i] = value;
        i++;
    }
    //  setWaitMode();
    digitalWrite(ncs_pin, HIGH);
    digitalWrite(nwr_pin, HIGH);
    digitalWrite(nrd_pin, HIGH);
    int i;
    for(i = 0; i < 8; i++)
        pinMode(data_pins[i], INPUT);


    // ramDisable();
    writeCartByte(0x0000,0x00)

    // ConvertTilesToBitmap();
}

TakePictureAndTransfer(0x03,0xE4,0,0x07,0xBF,1,0); //Base

@AntonioND
Copy link
Owner

The problem is that digitalWrite() is way too slow to do that job (check the implementation, seriously!).

That function tries to get as close as possible to the regular speed of the clock of the GB. Basically, after you write the registers to initialte the capture, you need the camera to do its job in the same time it would take in a regular GB. So you send a bunch of pulses at the speed that it would see on a regular GB, more or less.

I used a multimeter to check that the frequency of the pulse was about the one that I wanted, and left it as assembly with interrupts disabled to make sure that nothing would disturb the process.

You will need to find an equivalent way of doing that in your CPU.

@AntonioND
Copy link
Owner

I assume that this question has been answered, so I'm closing this issue. Feel free to reopen it if that's not the case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants