There are a variety of components that need to be emulated to fully emulate the Arduboy. I decided to start with the display, since without that I wouldn’t be able to see much of anything.
The SSD1306 display is a standalone device with its own commands and data. I figured this would be a good warmup for writing the CPU emulator.
The SSD1306 supports at least three different ways of being addressed, but I decided to write my initial implementation from a higher level by focusing on the types of input it can receive: commands and data.
The commands allow for some pretty interesting stuff, including some effects that are handled at a “hardware” level. I’m unsure how many Arduboy games actually use these effects, though, as it appears that ProjectABE doesn’t support anything other than simple “horizontal” mode.
I wrote my implementation as a class with a bunch of private variables representing the various registers/state flags that seemed to be documented. I kept the public interface intentionally minimal, focusing on the methods pushCommand, pushData, tick (for an as yet unimplemented scrolling feature), getContrast, and getFrameBuffer. getFrameBuffer exposes the screen as a bitset, since that’s ultimately the only output the screen has.
I decided to make it the responsibility of libretro.cpp to convert that bitset into RGB pixels for actual display:
void update_video() {
uint16_t fb[FRAME_WIDTH * FRAME_HEIGHT];
memset(fb, BLACK, sizeof(uint16_t) * FRAME_WIDTH * FRAME_HEIGHT);
auto bit_fb = arduous->getFrameBuffer();
for (int y = 0; y < FRAME_HEIGHT; y++) {
for (int x = 0; x < FRAME_WIDTH; x++) {
fb[y * FRAME_WIDTH + x] = bit_fb[y * FRAME_WIDTH + x] ? WHITE : BLACK;
}
}
video_cb((void*)fb, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH * sizeof(uint16_t));
}
I took a look at the various commands listed in the datasheet document, and wrote methods that could process these commands, along with any additional data passed along. I think the code is pretty readable on its own.
To verify it worked, I hardcoded some simple commands and data to send as part of the retro_run calls.
std::vector<uint8_t> SCREEN_TEST_COMMANDS = {
0x20, 0x00 // horizontal addressing mode
};
std::vector<std::vector<uint8_t>> SCREEN_TEST_DATA = {
{0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF},
};
Arduous::Arduous() {
cpu = Atcore();
screen = SSD1306();
for (uint8_t command : SCREEN_TEST_COMMANDS) {
screen.pushCommand(command);
}
cpuTicksPerFrame = cpu.getDesc().clock / TIMING_FPS;
}
void Arduous::emulateFrame() {
for (int i = 0; i < cpuTicksPerFrame; i++) {
cpu.tick();
}
for (uint8_t data : SCREEN_TEST_DATA[SCREEN_TEST_DATA_PTR]) {
screen.pushData(data);
}
SCREEN_TEST_DATA_PTR++;
SCREEN_TEST_DATA_PTR %= SCREEN_TEST_DATA.size();
screen.tick();
}
make and run with RetroArch, and I got some output:

UPDATE
One oddity I want to document that is probably obvious to anyone with more experience in C/C++ programming: when pausing the emulator, I got some funky garbage data showing on the screen instead of my nice stairstep pattern:

It turns out this was due to my code allocating the fb array fresh inside each call of update_video. Aside from being pretty inefficient, this meant that RetroArch was hanging onto a reference to some deallocated memory. Moving the declaration of the array outside of the update_video function fixed the issue!
uint16_t fb[FRAME_WIDTH * FRAME_HEIGHT];
void update_video() {
memset(fb, BLACK, sizeof(uint16_t) * FRAME_WIDTH * FRAME_HEIGHT);
auto bit_fb = arduous->getFrameBuffer();
for (int y = 0; y < FRAME_HEIGHT; y++) {
for (int x = 0; x < FRAME_WIDTH; x++) {
fb[y * FRAME_WIDTH + x] = bit_fb[y * FRAME_WIDTH + x] ? WHITE : BLACK;
}
}
video_cb((void*)fb, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH * sizeof(uint16_t));
}

Leave a Reply to An Arduous Endeavor (Part 3): CPU Emulation – James RoederCancel reply