chip8.ino 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. #include <EEPROM.h>
  2. #include <SPI.h>
  3. #include <GD2.h>
  4. #include "chip8_assets.h"
  5. #define ZOOM 5 // Pixel zoom factor
  6. #define SCREENLOC 0x3f000 // Address of 64x32 screen in FT800 RAM
  7. /*
  8. An interpreted Chip-8 emulator.
  9. Copyright (C) 2015, Corey Prophitt.
  10. This program is free software: you can redistribute it and/or modify
  11. it under the terms of the GNU General Public License as published by
  12. the Free Software Foundation, either version 3 of the License, or
  13. (at your option) any later version.
  14. This program is distributed in the hope that it will be useful,
  15. but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. GNU General Public License for more details.
  18. You should have received a copy of the GNU General Public License
  19. along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /*
  22. This code was adapted from Corey Prophitt's Chip-8 emulator
  23. by James Bowman.
  24. */
  25. struct chip8 {
  26. uint16_t pc; /* program counter */
  27. uint16_t i; /* address pointer */
  28. uint8_t sp; /* stack pointer */
  29. uint8_t st; /* sound timer */
  30. uint8_t dt; /* delay timer */
  31. uint8_t v[16]; /* registers V0-VF */
  32. uint8_t keys[16];
  33. uint16_t s[16];
  34. };
  35. static byte game = 0; // Game number
  36. static struct chip8 c8;
  37. static void chip8_clrs()
  38. {
  39. GD.cmd_memset(SCREENLOC, 0, 64 * 32);
  40. GD.finish();
  41. }
  42. #define X(x) ((x & 0x0F00) >> 8)
  43. #define Y(x) ((x & 0x00F0) >> 4)
  44. #define N(x) (x & 0x000F)
  45. #define NN(x) (x & 0x00FF)
  46. #define NNN(x) (x & 0x0FFF)
  47. void chip8_init()
  48. {
  49. /* clear memory, registers, stack, keys buffers */
  50. memset(c8.v, 0, sizeof c8.v);
  51. memset(c8.s, 0, sizeof c8.s);
  52. memset(c8.keys, 0, sizeof c8.keys);
  53. // chip8_load_font();
  54. chip8_clrs();
  55. /* initialize program counter, stack pointer and I register */
  56. c8.pc = 0x200;
  57. c8.sp = 0;
  58. c8.i = 0;
  59. /* reset the sound timer and delay timer */
  60. c8.st = 0;
  61. c8.dt = 0;
  62. }
  63. static uint8_t c8fetch(uint16_t addr)
  64. {
  65. uint8_t v = GD.rd(((uint32_t)game << 12) + addr);
  66. return v;
  67. }
  68. static void c8store(uint16_t addr, uint8_t v)
  69. {
  70. return GD.wr(((uint32_t)game << 12) + addr, v);
  71. }
  72. static void ic8_error(char *msg, uint16_t opcode)
  73. {
  74. GD.Clear();
  75. GD.cmd_text(240, 136, 27, OPT_CENTER, "fail");
  76. GD.cmd_number(240, 200, 27, OPT_CENTER, opcode);
  77. GD.swap();
  78. for (;;)
  79. ;
  80. }
  81. static int chip8_tick()
  82. {
  83. int finish = 0;
  84. uint16_t opcode = (c8fetch(c8.pc) << 8) | (c8fetch(c8.pc + 1));
  85. // Serial.print("pc="); Serial.print(c8.pc, HEX);
  86. // Serial.print(" opcode="); Serial.print(opcode, HEX);
  87. // Serial.println();
  88. /* convert this to a jump table? */
  89. switch (opcode & 0xF000) {
  90. /* double branch */
  91. case 0x0000:
  92. switch (opcode & 0x00FF) {
  93. /* 00E0: Clears the screen */
  94. case 0x00E0:
  95. chip8_clrs();
  96. c8.pc += 2;
  97. break;
  98. /* 00EE: Returns from a subroutine */
  99. case 0x00EE:
  100. c8.sp -= 1;
  101. c8.pc = c8.s[c8.sp];
  102. c8.pc += 2;
  103. break;
  104. default:
  105. ic8_error("Unsupported instruction (%X).\n", opcode);
  106. c8.pc += 2;
  107. break;
  108. }
  109. break;
  110. /* 1NNN: Jumps to address NNN */
  111. case 0x1000:
  112. c8.pc = NNN(opcode);
  113. break;
  114. /* 2NNN: Calls subroutine at NNN */
  115. case 0x2000:
  116. c8.s[c8.sp] = c8.pc;
  117. c8.sp += 1;
  118. c8.pc = NNN(opcode);
  119. break;
  120. /* 3XNN: Skips the next instruction if VX equals NN */
  121. case 0x3000:
  122. c8.pc += c8.v[X(opcode)] == NN(opcode) ? 4 : 2;
  123. break;
  124. /* 4XNN: Skips the next instruction if VX doesn't equal NN */
  125. case 0x4000:
  126. c8.pc += c8.v[X(opcode)] != NN(opcode) ? 4 : 2;
  127. break;
  128. /* 5XY0: Skips the next instruction if VX equals VY */
  129. case 0x5000:
  130. c8.pc += c8.v[X(opcode)] == c8.v[Y(opcode)] ? 4 : 2;
  131. break;
  132. /* 6XNN: Sets VX to NN */
  133. case 0x6000:
  134. c8.v[X(opcode)] = NN(opcode);
  135. c8.pc += 2;
  136. break;
  137. /* 7XNN: Adds NN to VX */
  138. case 0x7000:
  139. c8.v[X(opcode)] += NN(opcode);
  140. c8.pc += 2;
  141. break;
  142. /* double branch */
  143. case 0x8000:
  144. switch (opcode & 0x000F) {
  145. /* 8XY0: Sets VX to the value of VY */
  146. case 0x0000:
  147. c8.v[X(opcode)] = c8.v[Y(opcode)];
  148. c8.pc += 2;
  149. break;
  150. /* 8XY1: Sets VX to VX or VY */
  151. case 0x0001:
  152. c8.v[X(opcode)] |= c8.v[Y(opcode)];
  153. c8.pc += 2;
  154. break;
  155. /* 8XY2: Sets VX to VX and VY */
  156. case 0x0002:
  157. c8.v[X(opcode)] &= c8.v[Y(opcode)];
  158. c8.pc += 2;
  159. break;
  160. /* 8XY3: Sets VX to VX xor VY */
  161. case 0x0003:
  162. c8.v[X(opcode)] ^= c8.v[Y(opcode)];
  163. c8.pc += 2;
  164. break;
  165. /*
  166. * 8XY4: Adds VY to VX. VF is set to 1 when there's a carry, and to
  167. * 0 when there isn't
  168. * */
  169. case 0x0004:
  170. {
  171. int vx = c8.v[X(opcode)];
  172. int vy = c8.v[Y(opcode)];
  173. int results = vx + vy;
  174. c8.v[0xF] = results > 255 ? 1 : 0;
  175. c8.v[X(opcode)] = results & 0xFF;
  176. c8.pc += 2;
  177. }
  178. break;
  179. /*
  180. * 8XY5: VY is subtracted from VX. VF is set to 0 when there's a
  181. * borrow, and 1 when there isn't
  182. * */
  183. case 0x0005:
  184. {
  185. int vx = c8.v[X(opcode)];
  186. int vy = c8.v[Y(opcode)];
  187. c8.v[0xF] = vy > vx ? 0 : 1;
  188. c8.v[X(opcode)] = vx - vy;
  189. c8.pc += 2;
  190. }
  191. break;
  192. /*
  193. * 8XY6: Shifts VX right by one. VF is set to the value of the
  194. * least significant bit of VX before the shift
  195. * */
  196. case 0x0006:
  197. c8.v[0xF] = c8.v[X(opcode)] & 0x01;
  198. c8.v[X(opcode)] >>= 1;
  199. c8.pc += 2;
  200. break;
  201. /*
  202. * 8XY7: Sets VX to VY minus VX. VF is set to 0 when there's a
  203. * borrow, and 1 when there isn't
  204. * */
  205. case 0x0007:
  206. {
  207. int vx = c8.v[X(opcode)];
  208. int vy = c8.v[Y(opcode)];
  209. c8.v[0xF] = vx > vy ? 0 : 1;
  210. c8.v[X(opcode)] = vy - vx;
  211. c8.pc += 2;
  212. }
  213. break;
  214. /*
  215. * 8XYE: Shifts VX left by one. VF is set to the value of the
  216. * most significant bit of VX before the shift
  217. * */
  218. case 0x000E:
  219. c8.v[0xF] = (c8.v[X(opcode)] & 0x80) >> 7;
  220. c8.v[X(opcode)] <<= 1;
  221. c8.pc += 2;
  222. break;
  223. default:
  224. ic8_error("Unsupported instruction (%X).\n", opcode);
  225. c8.pc += 2;
  226. break;
  227. }
  228. break;
  229. /* 9XY0: Skips the next instruction if VX doesn't equal VY */
  230. case 0x9000:
  231. c8.pc += c8.v[X(opcode)] != c8.v[Y(opcode)] ? 4 : 2;
  232. break;
  233. /* ANNN: Sets I to the address NNN */
  234. case 0xA000:
  235. c8.i = NNN(opcode);
  236. c8.pc += 2;
  237. break;
  238. /* BNNN: Jumps to the address NNN plus V0 */
  239. case 0xB000:
  240. c8.pc = NNN(opcode) + c8.v[0x00];
  241. break;
  242. /* CXNN: Sets VX to a random number, masked by NN */
  243. case 0xC000:
  244. c8.v[X(opcode)] = GD.random(256) & NN(opcode);
  245. c8.pc += 2;
  246. break;
  247. /*
  248. * DXYN: Sprites stored in memory at location in index register (I),
  249. * maximum 8bits wide. Wraps around the screen. If when drawn,
  250. * clears a pixel, register VF is set to 1 otherwise it is zero.
  251. * All drawing is XOR drawing (i.e. it toggles the screen pixels)
  252. * */
  253. case 0xD000:
  254. {
  255. uint8_t vx = c8.v[X(opcode)];
  256. uint8_t vy = c8.v[Y(opcode)];
  257. c8.v[0xF] = 0;
  258. uint8_t pixel = 0;
  259. int w = 0;
  260. int h = 0;
  261. for (uint8_t y = 0; y < N(opcode); ++y) {
  262. pixel = c8fetch(c8.i + y);
  263. for (uint8_t x = 0; x < 8; ++x) {
  264. if ((pixel & (0x80 >> x)) != 0) {
  265. uint8_t old;
  266. /* clamp cooridnates, don't draw off the screen
  267. * - Needed for VERS
  268. * */
  269. w = (vx + x) > 64 ? 64 : vx + x;
  270. h = (vy + y) > 32 ? 32 : vy + y;
  271. old = GD.rd(SCREENLOC + (w + (h * 64)));
  272. if (old == 255)
  273. c8.v[0xF] = 1;
  274. GD.wr(SCREENLOC + w + (h * 64), old ^ 255);
  275. }
  276. }
  277. }
  278. c8.pc += 2;
  279. finish = 1;
  280. }
  281. break;
  282. /* double branch */
  283. case 0xE000:
  284. switch (opcode & 0x00FF) {
  285. /* EX9E: Skips the next instruction if the key stored in VX is pressed */
  286. case 0x9E:
  287. c8.pc += c8.keys[c8.v[X(opcode)]] ? 4 : 2;
  288. break;
  289. /* EXA1: Skips the next instruction if the key stored in VX isn't pressed */
  290. case 0xA1:
  291. c8.pc += c8.keys[c8.v[X(opcode)]] ? 2 : 4;
  292. break;
  293. default:
  294. ic8_error("Unsupported instruction (%X).\n", opcode);
  295. c8.pc += 2;
  296. break;
  297. }
  298. break;
  299. /* double branch */
  300. case 0xF000:
  301. switch (opcode & 0x00FF) {
  302. /* FX07: Sets VX to the value of the delay timer */
  303. case 0x0007:
  304. c8.v[X(opcode)] = c8.dt;
  305. c8.pc += 2;
  306. break;
  307. /* FX0A: A key press is awaited, and then stored in VX */
  308. case 0x000A:
  309. GD.get_inputs();
  310. {
  311. uint8_t i;
  312. for (i = 0; i < 16; i++) {
  313. if (c8.keys[i]) {
  314. c8.v[X(opcode)] = i;
  315. c8.pc += 2;
  316. finish = 2;
  317. break;
  318. }
  319. }
  320. }
  321. break;
  322. /* FX15: Sets the delay timer to VX */
  323. case 0x0015:
  324. c8.dt = c8.v[X(opcode)];
  325. c8.pc += 2;
  326. break;
  327. /* FX18: Sets the sound timer to VX */
  328. case 0x0018:
  329. c8.st = c8.v[X(opcode)];
  330. c8.pc += 2;
  331. break;
  332. /* FX1E: Adds VX to I */
  333. case 0x001E:
  334. /* Spaceflight 2019 feature, wrap player around world */
  335. c8.v[0xF] = 0;
  336. if ((c8.i + c8.v[X(opcode)]) > 4095) {
  337. c8.v[0xF] = 1;
  338. }
  339. c8.i += c8.v[X(opcode)];
  340. c8.pc += 2;
  341. break;
  342. /*
  343. * FX29: Sets I to the location of the sprite for the character
  344. * in VX. Characters 0-F (in hexadecimal) are represented by a
  345. * 4x5 font.
  346. * */
  347. case 0x0029:
  348. c8.i = c8.v[X(opcode)] * 5;
  349. c8.pc += 2;
  350. break;
  351. /*
  352. * FX33: Stores the Binary-coded decimal representation of VX, with
  353. * the most significant of three digits at the address in I,
  354. * the middle digit at I plus 1, and the least significant digit
  355. * at I plus 2. (In other words, take the decimal representation
  356. * of VX, place the hundreds digit in memory at location in I,
  357. * the tens digit at location I+1, and the ones digit at location
  358. * I+2.)
  359. * */
  360. case 0x0033:
  361. c8store(c8.i, c8.v[X(opcode)] / 100);
  362. c8store(c8.i + 1, (c8.v[X(opcode)] % 100) / 10);
  363. c8store(c8.i + 2, c8.v[X(opcode)] % 10);
  364. c8.pc += 2;
  365. break;
  366. /* FX55: Stores V0 to VX in memory starting at address I */
  367. case 0x0055:
  368. for (int i = 0; i <= X(opcode); i++) {
  369. c8store(c8.i + i, c8.v[i]);
  370. }
  371. c8.pc += 2;
  372. break;
  373. /* FX65: Fills V0 to VX with values from memory starting at address I */
  374. case 0x0065:
  375. for (int i = 0; i <= X(opcode); i++) {
  376. c8.v[i] = c8fetch(c8.i + i);
  377. }
  378. c8.pc += 2;
  379. break;
  380. default:
  381. ic8_error("Unsupported instruction (%X).\n", opcode);
  382. c8.pc += 2;
  383. break;
  384. }
  385. break;
  386. default:
  387. ic8_error("Unknown opcode 0x%X\n", opcode);
  388. c8.pc += 2;
  389. }
  390. return finish;
  391. }
  392. static void chip8_update_timers()
  393. {
  394. if (c8.dt > 0) {
  395. c8.dt -= 1;
  396. }
  397. if (c8.st > 0) {
  398. GD.wr(REG_VOL_SOUND, 255);
  399. c8.st -= 1;
  400. } else {
  401. GD.wr(REG_VOL_SOUND, 0);
  402. }
  403. }
  404. static char title[48];
  405. static char hint[128];
  406. static void set_game(byte g)
  407. {
  408. game = g;
  409. chip8_init();
  410. for (byte i = 0; i < 48; i++)
  411. title[i] = c8fetch(0xf00 + i);
  412. for (byte i = 0; i < 128; i++)
  413. hint[i] = c8fetch(0xf30 + i);
  414. }
  415. void setup()
  416. {
  417. Serial.begin(1000000);
  418. GD.begin();
  419. LOAD_ASSETS();
  420. GD.finish();
  421. GD.play(SQUAREWAVE); // continuous beep
  422. GD.wr(REG_VOL_SOUND, 0);
  423. GD.BitmapHandle(0);
  424. GD.BitmapSource(SCREENLOC);
  425. GD.BitmapSize(NEAREST, BORDER, BORDER, ZOOM * 64, ZOOM * 32);
  426. GD.BitmapLayout(L8, 64, 32);
  427. GD.BitmapHandle(1);
  428. GD.BitmapSource(0);
  429. GD.BitmapSize(NEAREST, BORDER, BORDER, 256, 15);
  430. GD.BitmapLayout(RGB332, 256, 16);
  431. set_game(0);
  432. }
  433. static uint16_t t;
  434. void loop()
  435. {
  436. GD.get_inputs();
  437. GD.ClearColorRGB(0x172580);
  438. GD.Clear();
  439. // Draw title and hint
  440. GD.PointSize(19 * 16);
  441. GD.Begin(POINTS);
  442. GD.ColorRGB(0x000000);
  443. GD.Vertex2ii(20, 20);
  444. GD.ColorRGB(0xffffff);
  445. GD.cmd_number(20, 20, 27, OPT_CENTER | 2, game);
  446. GD.cmd_text(42, 20, 27, OPT_CENTERY, title);
  447. GD.ColorRGB(0xd0d0d0);
  448. GD.cmd_text(42, 40, 26, OPT_CENTERY, hint);
  449. // Draw the - + buttons for game selection
  450. GD.ColorRGB(0x909070);
  451. GD.Begin(POINTS);
  452. GD.Vertex2ii(420, 20);
  453. GD.Vertex2ii(460, 20);
  454. GD.ColorRGB(0x000000);
  455. GD.Tag('-');
  456. GD.cmd_text(420, 20, 29, OPT_CENTER, "-");
  457. GD.Tag('+');
  458. GD.cmd_text(460, 20, 29, OPT_CENTER, "+");
  459. // Draw the hex keypad
  460. static uint8_t keys[] = {
  461. 0x1, 0x2, 0x3, 0xc,
  462. 0x4, 0x5, 0x6, 0xd,
  463. 0x7, 0x8, 0x9, 0xe,
  464. 0xa, 0x0, 0xb, 0xf,
  465. };
  466. for (byte i = 0; i < 16; i++) {
  467. char cc[2];
  468. byte h = keys[i];
  469. if (h < 10)
  470. cc[0] = '0' + h;
  471. else
  472. cc[0] = 'A' - 10 + h;
  473. cc[1] = 0;
  474. int x = 340 + 40 * (i % 4);
  475. int y = 80 + 40 * (i / 4);
  476. byte touch = GD.inputs.tag == (0x80 + h);
  477. if (touch)
  478. GD.ColorRGB(0xc04000);
  479. else
  480. GD.ColorRGB(0xc0c0c0);
  481. c8.keys[h] = touch;
  482. GD.Begin(POINTS);
  483. GD.Vertex2ii(x, y);
  484. GD.Tag(0x80 + h);
  485. GD.ColorRGB(0x202020);
  486. GD.cmd_text(x, y, 30, OPT_CENTER, cc);
  487. }
  488. GD.ColorRGB(0xffffff);
  489. GD.Begin(BITMAPS);
  490. GD.BlendFunc(SRC_ALPHA, ZERO);
  491. GD.Vertex2ii((480 - 256) / 2, 272 - 20, 1, game);
  492. GD.cmd_scale(F16(ZOOM), F16(ZOOM));
  493. GD.cmd_setmatrix();
  494. GD.Vertex2ii(0, 61, 0);
  495. GD.swap();
  496. GD.finish();
  497. for (byte i = (2000 / 60); i; i--) {
  498. int fincode = chip8_tick();
  499. if (fincode == 2) {
  500. do {
  501. GD.get_inputs();
  502. } while (GD.inputs.tag != 0);
  503. }
  504. if (fincode)
  505. break;
  506. }
  507. chip8_update_timers();
  508. static byte prev_tag;
  509. if (prev_tag == 0) {
  510. if (GD.inputs.tag == '+')
  511. set_game((game + 1) % NGAMES);
  512. if (GD.inputs.tag == '-')
  513. set_game((game ? game : NGAMES) - 1);
  514. }
  515. // if ((t % 240) == 239)
  516. // set_game((game + 1) % NGAMES);
  517. // if ((t % 2) == 0)
  518. // GD.dumpscreen(), delay(100);
  519. // t++;
  520. prev_tag = GD.inputs.tag;
  521. }