The DSi camera adventure
I mentioned the DSi cameras in my previous post, and that's what I was working on lately. Mostly trying to get the cameras going on the DSi itself, so I could test the transfer hardware.

Well, it's not been that easy.

I had started work in the dsi_camera branch, but so far it was a large trainwreck. I couldn't really understand how camera transfers work and how everything interacts together. My attempt at a guessed implementation was getting nowhere, which meant it was time for some hardware research.

So I started work on a DSi camera test homebrew. I first went and implemented the initialization procedure found in GBAtek, only to be rewarded with a hang when trying to activate a camera. I tried many things, taking the init procedure from some open-source Aptina MT9V113 driver (the model of camera the DSi uses), reverse-engineering the DSi camera app to use its exact init procedure, all to no avail.

I felt stuck there. I even tried looking for existing examples using the DSi cameras, found this one by Epicpkmn11, but at the time it seemed to have the same issue I was having.

I eventually went out and asked for help on several places. A side effect is that I'm now found in some Discord servers. I also posted a thread at nesdev, knowing nocash hangs around there. The documentation in GBAtek implied he did get the cameras working, so I figured he'd be able to help. And he did, thanks there.

I first looked at the code he provided, checking for any meaningful differences in the init procedure, but it looked like I had all the essential stuff right. I was stumped.

It eventually occured to me that maybe I should try initializing both cameras simultaneously, like Nintendo does, rather than only initializing one camera. You know how it is, when you're desperate, anything can look like a valid solution. Anyway, that didn't cut it, but it revealed something interesting when I tried to read some registers from both cameras. Some reads were getting corrupted. So I knew something was up with the I2C code.

Looking at nocash's I2C code, I was able to spot and fix the issue. Turns out that during an I2C read, you don't raise an ack when reading the last byte. This fixed the corruption I was observing, and finally allowed the camera to activate successfully. At the same time, Epicpkmn11 happened to be in the same Discord server I was in, so they could fix their code too (turns out it did have the same issue as mine).

Next thing I did was enable a camera transfer, and, lo and behold, I was able to display camera input on my DSi. From this, I tested the camera transfer hardware in several ways, to try figuring out how it works.

Details are still hazy, but this time I was able to make a working implementation in melonDS.

I made the data register return a fixed value, hence the red/blue stripes. But now that we have a working base, next step is feeding an image buffer into this, and ironing out the remaining issues (for example, taking a picture causes a system error).

But after that, I felt like chilling some, and figured I would try finding out why ZXDS was running abysmally slow in melonDS. Basically, ZXDS is a ZX Spectrum emulator for the DS. I'm not into Spectrum emulation, but from what I could read, the emulator is quite impressive technically, and I enjoyed reading the author's developer diary.

Anyway, ZXDS abuses the DS's writable VCount to limit the framerate to 50FPS. To quote its author:

Having this common master frequency, it was now simple to use it for converting T cycles passed to amount of samples to generate, as well as to use it to set up a timer which would count the 50Hz which would drive the LCD, and still keep everything in perfect sync. The refresh rate of the LCD itself can't be set directly, though, however the DS features a writable VCOUNT register which can be used to delay the start of the next frame as needed. It is normally intended to be used to synchronize display of machines participating in multiplayer games, but it was trivial to abuse it for holding the retrace after each frame displayed until the interrupt handler driven by the 50Hz timer allowed it to go, effectively slowing the LCD refresh rate to 50Hz as well.

Technically, ZXDS uses two cascading timers to achieve this. Timer 2 is set to an interval of 16 cycles, and drives timer 3 which is set so that its IRQ will fire every 20ms. ZXDS will then hold VCount at a certain value until the IRQ fires. All fine and dandy.

Of course, as far as melonDS is concerned, this is where the problem was lying. ZXDS would hold VCount for way too long, causing each frame to last absurdly long. Sure enough, the issue came from the timers. What, a timer issue in melonDS in 2020? Madness!

In particular, it came from timer 2 and its tight interval. melonDS updates its timers more loosely than the actual DS, but it assumed a timer would only overflow once after each update. What happened here was that updates were far enough apart that timer 2 had the time to overflow more than once. The assumption that it would only overflow once caused it to start behaving wrong, and that is why the timer 3 IRQs were so far apart.

So I did something similar to audio timers, which are updated every 1024 cycles (and do take into account the fact that they could overflow several times in one update). This fixed the issue, ZXDS now runs at the expected 50FPS.

Woodcode says:
Jan 24th 2022
I hope we get to use our webcam for the DSi camera soon! (or maybe i'm missing something)
Paulo Henrique Rodri says:
Oct 28th 2023
nicolas e paulo
Paulo Henrique Rodri says:
Oct 28th 2023
sonic ruhs
Post a comment