123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- #include <EEPROM.h>
- #include <SPI.h>
- #include <GD2.h>
- #include "chip8_assets.h"
- #define ZOOM 5 // Pixel zoom factor
- #define SCREENLOC 0x3f000 // Address of 64x32 screen in FT800 RAM
- /*
- An interpreted Chip-8 emulator.
- Copyright (C) 2015, Corey Prophitt.
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- /*
- This code was adapted from Corey Prophitt's Chip-8 emulator
- by James Bowman.
- */
- struct chip8 {
- uint16_t pc; /* program counter */
- uint16_t i; /* address pointer */
- uint8_t sp; /* stack pointer */
- uint8_t st; /* sound timer */
- uint8_t dt; /* delay timer */
- uint8_t v[16]; /* registers V0-VF */
- uint8_t keys[16];
- uint16_t s[16];
- };
- static byte game = 0; // Game number
- static struct chip8 c8;
- static void chip8_clrs()
- {
- GD.cmd_memset(SCREENLOC, 0, 64 * 32);
- GD.finish();
- }
- #define X(x) ((x & 0x0F00) >> 8)
- #define Y(x) ((x & 0x00F0) >> 4)
- #define N(x) (x & 0x000F)
- #define NN(x) (x & 0x00FF)
- #define NNN(x) (x & 0x0FFF)
- void chip8_init()
- {
- /* clear memory, registers, stack, keys buffers */
- memset(c8.v, 0, sizeof c8.v);
- memset(c8.s, 0, sizeof c8.s);
- memset(c8.keys, 0, sizeof c8.keys);
- // chip8_load_font();
- chip8_clrs();
- /* initialize program counter, stack pointer and I register */
- c8.pc = 0x200;
- c8.sp = 0;
- c8.i = 0;
- /* reset the sound timer and delay timer */
- c8.st = 0;
- c8.dt = 0;
- }
- static uint8_t c8fetch(uint16_t addr)
- {
- uint8_t v = GD.rd(((uint32_t)game << 12) + addr);
- return v;
- }
- static void c8store(uint16_t addr, uint8_t v)
- {
- return GD.wr(((uint32_t)game << 12) + addr, v);
- }
- static void ic8_error(char *msg, uint16_t opcode)
- {
- GD.Clear();
- GD.cmd_text(240, 136, 27, OPT_CENTER, "fail");
- GD.cmd_number(240, 200, 27, OPT_CENTER, opcode);
- GD.swap();
- for (;;)
- ;
- }
- static int chip8_tick()
- {
- int finish = 0;
- uint16_t opcode = (c8fetch(c8.pc) << 8) | (c8fetch(c8.pc + 1));
- // Serial.print("pc="); Serial.print(c8.pc, HEX);
- // Serial.print(" opcode="); Serial.print(opcode, HEX);
- // Serial.println();
- /* convert this to a jump table? */
- switch (opcode & 0xF000) {
- /* double branch */
- case 0x0000:
- switch (opcode & 0x00FF) {
- /* 00E0: Clears the screen */
- case 0x00E0:
- chip8_clrs();
- c8.pc += 2;
- break;
- /* 00EE: Returns from a subroutine */
- case 0x00EE:
- c8.sp -= 1;
- c8.pc = c8.s[c8.sp];
- c8.pc += 2;
- break;
- default:
- ic8_error("Unsupported instruction (%X).\n", opcode);
- c8.pc += 2;
- break;
- }
- break;
- /* 1NNN: Jumps to address NNN */
- case 0x1000:
- c8.pc = NNN(opcode);
- break;
- /* 2NNN: Calls subroutine at NNN */
- case 0x2000:
- c8.s[c8.sp] = c8.pc;
- c8.sp += 1;
- c8.pc = NNN(opcode);
- break;
- /* 3XNN: Skips the next instruction if VX equals NN */
- case 0x3000:
- c8.pc += c8.v[X(opcode)] == NN(opcode) ? 4 : 2;
- break;
- /* 4XNN: Skips the next instruction if VX doesn't equal NN */
- case 0x4000:
- c8.pc += c8.v[X(opcode)] != NN(opcode) ? 4 : 2;
- break;
- /* 5XY0: Skips the next instruction if VX equals VY */
- case 0x5000:
- c8.pc += c8.v[X(opcode)] == c8.v[Y(opcode)] ? 4 : 2;
- break;
- /* 6XNN: Sets VX to NN */
- case 0x6000:
- c8.v[X(opcode)] = NN(opcode);
- c8.pc += 2;
- break;
- /* 7XNN: Adds NN to VX */
- case 0x7000:
- c8.v[X(opcode)] += NN(opcode);
- c8.pc += 2;
- break;
- /* double branch */
- case 0x8000:
- switch (opcode & 0x000F) {
- /* 8XY0: Sets VX to the value of VY */
- case 0x0000:
- c8.v[X(opcode)] = c8.v[Y(opcode)];
- c8.pc += 2;
- break;
- /* 8XY1: Sets VX to VX or VY */
- case 0x0001:
- c8.v[X(opcode)] |= c8.v[Y(opcode)];
- c8.pc += 2;
- break;
- /* 8XY2: Sets VX to VX and VY */
- case 0x0002:
- c8.v[X(opcode)] &= c8.v[Y(opcode)];
- c8.pc += 2;
- break;
- /* 8XY3: Sets VX to VX xor VY */
- case 0x0003:
- c8.v[X(opcode)] ^= c8.v[Y(opcode)];
- c8.pc += 2;
- break;
- /*
- * 8XY4: Adds VY to VX. VF is set to 1 when there's a carry, and to
- * 0 when there isn't
- * */
- case 0x0004:
- {
- int vx = c8.v[X(opcode)];
- int vy = c8.v[Y(opcode)];
- int results = vx + vy;
- c8.v[0xF] = results > 255 ? 1 : 0;
- c8.v[X(opcode)] = results & 0xFF;
- c8.pc += 2;
- }
- break;
- /*
- * 8XY5: VY is subtracted from VX. VF is set to 0 when there's a
- * borrow, and 1 when there isn't
- * */
- case 0x0005:
- {
- int vx = c8.v[X(opcode)];
- int vy = c8.v[Y(opcode)];
- c8.v[0xF] = vy > vx ? 0 : 1;
- c8.v[X(opcode)] = vx - vy;
- c8.pc += 2;
- }
- break;
- /*
- * 8XY6: Shifts VX right by one. VF is set to the value of the
- * least significant bit of VX before the shift
- * */
- case 0x0006:
- c8.v[0xF] = c8.v[X(opcode)] & 0x01;
- c8.v[X(opcode)] >>= 1;
- c8.pc += 2;
- break;
- /*
- * 8XY7: Sets VX to VY minus VX. VF is set to 0 when there's a
- * borrow, and 1 when there isn't
- * */
- case 0x0007:
- {
- int vx = c8.v[X(opcode)];
- int vy = c8.v[Y(opcode)];
- c8.v[0xF] = vx > vy ? 0 : 1;
- c8.v[X(opcode)] = vy - vx;
- c8.pc += 2;
- }
- break;
- /*
- * 8XYE: Shifts VX left by one. VF is set to the value of the
- * most significant bit of VX before the shift
- * */
- case 0x000E:
- c8.v[0xF] = (c8.v[X(opcode)] & 0x80) >> 7;
- c8.v[X(opcode)] <<= 1;
- c8.pc += 2;
- break;
- default:
- ic8_error("Unsupported instruction (%X).\n", opcode);
- c8.pc += 2;
- break;
- }
- break;
- /* 9XY0: Skips the next instruction if VX doesn't equal VY */
- case 0x9000:
- c8.pc += c8.v[X(opcode)] != c8.v[Y(opcode)] ? 4 : 2;
- break;
- /* ANNN: Sets I to the address NNN */
- case 0xA000:
- c8.i = NNN(opcode);
- c8.pc += 2;
- break;
- /* BNNN: Jumps to the address NNN plus V0 */
- case 0xB000:
- c8.pc = NNN(opcode) + c8.v[0x00];
- break;
- /* CXNN: Sets VX to a random number, masked by NN */
- case 0xC000:
- c8.v[X(opcode)] = GD.random(256) & NN(opcode);
- c8.pc += 2;
- break;
- /*
- * DXYN: Sprites stored in memory at location in index register (I),
- * maximum 8bits wide. Wraps around the screen. If when drawn,
- * clears a pixel, register VF is set to 1 otherwise it is zero.
- * All drawing is XOR drawing (i.e. it toggles the screen pixels)
- * */
- case 0xD000:
- {
- uint8_t vx = c8.v[X(opcode)];
- uint8_t vy = c8.v[Y(opcode)];
- c8.v[0xF] = 0;
- uint8_t pixel = 0;
- int w = 0;
- int h = 0;
- for (uint8_t y = 0; y < N(opcode); ++y) {
- pixel = c8fetch(c8.i + y);
- for (uint8_t x = 0; x < 8; ++x) {
- if ((pixel & (0x80 >> x)) != 0) {
- uint8_t old;
- /* clamp cooridnates, don't draw off the screen
- * - Needed for VERS
- * */
- w = (vx + x) > 64 ? 64 : vx + x;
- h = (vy + y) > 32 ? 32 : vy + y;
- old = GD.rd(SCREENLOC + (w + (h * 64)));
- if (old == 255)
- c8.v[0xF] = 1;
- GD.wr(SCREENLOC + w + (h * 64), old ^ 255);
- }
- }
- }
- c8.pc += 2;
- finish = 1;
- }
- break;
- /* double branch */
- case 0xE000:
- switch (opcode & 0x00FF) {
- /* EX9E: Skips the next instruction if the key stored in VX is pressed */
- case 0x9E:
- c8.pc += c8.keys[c8.v[X(opcode)]] ? 4 : 2;
- break;
- /* EXA1: Skips the next instruction if the key stored in VX isn't pressed */
- case 0xA1:
- c8.pc += c8.keys[c8.v[X(opcode)]] ? 2 : 4;
- break;
- default:
- ic8_error("Unsupported instruction (%X).\n", opcode);
- c8.pc += 2;
- break;
- }
- break;
- /* double branch */
- case 0xF000:
- switch (opcode & 0x00FF) {
- /* FX07: Sets VX to the value of the delay timer */
- case 0x0007:
- c8.v[X(opcode)] = c8.dt;
- c8.pc += 2;
- break;
- /* FX0A: A key press is awaited, and then stored in VX */
- case 0x000A:
- GD.get_inputs();
- {
- uint8_t i;
- for (i = 0; i < 16; i++) {
- if (c8.keys[i]) {
- c8.v[X(opcode)] = i;
- c8.pc += 2;
- finish = 2;
- break;
- }
- }
- }
- break;
- /* FX15: Sets the delay timer to VX */
- case 0x0015:
- c8.dt = c8.v[X(opcode)];
- c8.pc += 2;
- break;
- /* FX18: Sets the sound timer to VX */
- case 0x0018:
- c8.st = c8.v[X(opcode)];
- c8.pc += 2;
- break;
- /* FX1E: Adds VX to I */
- case 0x001E:
- /* Spaceflight 2019 feature, wrap player around world */
- c8.v[0xF] = 0;
- if ((c8.i + c8.v[X(opcode)]) > 4095) {
- c8.v[0xF] = 1;
- }
- c8.i += c8.v[X(opcode)];
- c8.pc += 2;
- break;
- /*
- * FX29: Sets I to the location of the sprite for the character
- * in VX. Characters 0-F (in hexadecimal) are represented by a
- * 4x5 font.
- * */
- case 0x0029:
- c8.i = c8.v[X(opcode)] * 5;
- c8.pc += 2;
- break;
- /*
- * FX33: Stores the Binary-coded decimal representation of VX, with
- * the most significant of three digits at the address in I,
- * the middle digit at I plus 1, and the least significant digit
- * at I plus 2. (In other words, take the decimal representation
- * of VX, place the hundreds digit in memory at location in I,
- * the tens digit at location I+1, and the ones digit at location
- * I+2.)
- * */
- case 0x0033:
- c8store(c8.i, c8.v[X(opcode)] / 100);
- c8store(c8.i + 1, (c8.v[X(opcode)] % 100) / 10);
- c8store(c8.i + 2, c8.v[X(opcode)] % 10);
- c8.pc += 2;
- break;
- /* FX55: Stores V0 to VX in memory starting at address I */
- case 0x0055:
- for (int i = 0; i <= X(opcode); i++) {
- c8store(c8.i + i, c8.v[i]);
- }
- c8.pc += 2;
- break;
- /* FX65: Fills V0 to VX with values from memory starting at address I */
- case 0x0065:
- for (int i = 0; i <= X(opcode); i++) {
- c8.v[i] = c8fetch(c8.i + i);
- }
- c8.pc += 2;
- break;
- default:
- ic8_error("Unsupported instruction (%X).\n", opcode);
- c8.pc += 2;
- break;
- }
- break;
- default:
- ic8_error("Unknown opcode 0x%X\n", opcode);
- c8.pc += 2;
- }
- return finish;
- }
- static void chip8_update_timers()
- {
- if (c8.dt > 0) {
- c8.dt -= 1;
- }
- if (c8.st > 0) {
- GD.wr(REG_VOL_SOUND, 255);
- c8.st -= 1;
- } else {
- GD.wr(REG_VOL_SOUND, 0);
- }
- }
- static char title[48];
- static char hint[128];
- static void set_game(byte g)
- {
- game = g;
- chip8_init();
- for (byte i = 0; i < 48; i++)
- title[i] = c8fetch(0xf00 + i);
- for (byte i = 0; i < 128; i++)
- hint[i] = c8fetch(0xf30 + i);
- }
- void setup()
- {
- Serial.begin(1000000);
- GD.begin();
- LOAD_ASSETS();
- GD.finish();
- GD.play(SQUAREWAVE); // continuous beep
- GD.wr(REG_VOL_SOUND, 0);
- GD.BitmapHandle(0);
- GD.BitmapSource(SCREENLOC);
- GD.BitmapSize(NEAREST, BORDER, BORDER, ZOOM * 64, ZOOM * 32);
- GD.BitmapLayout(L8, 64, 32);
- GD.BitmapHandle(1);
- GD.BitmapSource(0);
- GD.BitmapSize(NEAREST, BORDER, BORDER, 256, 15);
- GD.BitmapLayout(RGB332, 256, 16);
- set_game(0);
- }
- static uint16_t t;
- void loop()
- {
- GD.get_inputs();
- GD.ClearColorRGB(0x172580);
- GD.Clear();
- // Draw title and hint
- GD.PointSize(19 * 16);
- GD.Begin(POINTS);
- GD.ColorRGB(0x000000);
- GD.Vertex2ii(20, 20);
- GD.ColorRGB(0xffffff);
- GD.cmd_number(20, 20, 27, OPT_CENTER | 2, game);
- GD.cmd_text(42, 20, 27, OPT_CENTERY, title);
- GD.ColorRGB(0xd0d0d0);
- GD.cmd_text(42, 40, 26, OPT_CENTERY, hint);
- // Draw the - + buttons for game selection
- GD.ColorRGB(0x909070);
- GD.Begin(POINTS);
- GD.Vertex2ii(420, 20);
- GD.Vertex2ii(460, 20);
- GD.ColorRGB(0x000000);
- GD.Tag('-');
- GD.cmd_text(420, 20, 29, OPT_CENTER, "-");
- GD.Tag('+');
- GD.cmd_text(460, 20, 29, OPT_CENTER, "+");
- // Draw the hex keypad
- static uint8_t keys[] = {
- 0x1, 0x2, 0x3, 0xc,
- 0x4, 0x5, 0x6, 0xd,
- 0x7, 0x8, 0x9, 0xe,
- 0xa, 0x0, 0xb, 0xf,
- };
- for (byte i = 0; i < 16; i++) {
- char cc[2];
- byte h = keys[i];
- if (h < 10)
- cc[0] = '0' + h;
- else
- cc[0] = 'A' - 10 + h;
- cc[1] = 0;
- int x = 340 + 40 * (i % 4);
- int y = 80 + 40 * (i / 4);
- byte touch = GD.inputs.tag == (0x80 + h);
- if (touch)
- GD.ColorRGB(0xc04000);
- else
- GD.ColorRGB(0xc0c0c0);
- c8.keys[h] = touch;
- GD.Begin(POINTS);
- GD.Vertex2ii(x, y);
- GD.Tag(0x80 + h);
- GD.ColorRGB(0x202020);
- GD.cmd_text(x, y, 30, OPT_CENTER, cc);
- }
- GD.ColorRGB(0xffffff);
- GD.Begin(BITMAPS);
- GD.BlendFunc(SRC_ALPHA, ZERO);
- GD.Vertex2ii((480 - 256) / 2, 272 - 20, 1, game);
- GD.cmd_scale(F16(ZOOM), F16(ZOOM));
- GD.cmd_setmatrix();
- GD.Vertex2ii(0, 61, 0);
- GD.swap();
- GD.finish();
- for (byte i = (2000 / 60); i; i--) {
- int fincode = chip8_tick();
- if (fincode == 2) {
- do {
- GD.get_inputs();
- } while (GD.inputs.tag != 0);
- }
- if (fincode)
- break;
- }
- chip8_update_timers();
- static byte prev_tag;
- if (prev_tag == 0) {
- if (GD.inputs.tag == '+')
- set_game((game + 1) % NGAMES);
- if (GD.inputs.tag == '-')
- set_game((game ? game : NGAMES) - 1);
- }
- // if ((t % 240) == 239)
- // set_game((game + 1) % NGAMES);
- // if ((t % 2) == 0)
- // GD.dumpscreen(), delay(100);
- // t++;
- prev_tag = GD.inputs.tag;
- }
|