/********************************************************************** chip8_interpreter.pde Code by lingib https://www.instructables.com/member/lingib/instructables/ Last update 24 September 2020 ------ About ------ CHIP-8 is a virtual computer https://en.wikipedia.org/wiki/CHIP-8 This software interprets CHIP-8 instructions/programs/ROMs The keyboard is mapped as follows: // ----- CHIP-8 keyboard mapping 1 2 3 4 ==> 1 2 3 C Q W E R ==> 4 5 6 D A S D F ==> 7 8 9 E Z X C V ==> A 0 B F An external hexadecimal keypad may also be used. To activate this feature set "ExternalKeypad = true;" in the header The JKL & P keys have been assigned the following tasks: J (load CHIP-8 file) K (start/restart program) L (stop/single-step program) P (verify code without executing The following information is displayed in the single step mode: PC (program counter) I (index register) SP (stack pointer) SP[] (stack pointer contents) V[] (register contents V[0]..V[F]) DT[] (delay timer contents) ST[] (sound timer contents) KEY (hexadecimal key push value) The program has 4096 bytes of memory from Memory[000]..Memory[FFF] Each hexadecimal digit comprises an 8-bit by 5-line "sprite". Example: - - * - - - - - - * * - - - - - - - * - - - - - ==> hexadecimal '1' - - * - - - - - - * * * - - - - The Font library starts at Memory[050] Everthing written to screen, including the hexadecimal digits, is a sprite To create your programs use a hexadecimal text editor such as: - Notepad++ from https://notepad-plus-plus.org/downloads/ and the - hexadecimal plugin module from https://github.com/chcg/NPP_HexEdit/releases - CHIP-8 accepts file extensions of ch8 and txt --------- Features --------- This interpreter features: - plays public domain games - an (optional) external keypad - adjustable speed The interpreter also supports debugging with: - a single-step mode - display register contents - disaasemble existing code - ability to set program break-points ------------ Caveats ----------- Some existing CHIP-8 games may not run as: - there are several variants of the CHIP-8 instruction set - some interpreters start at 0x630 instead of 0x200 ---------- Copyright ---------- This code 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 software 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. If not, see . *************************************************************************/ ////////////////////// user adjustable parameters //////////////////// int BreakPoint = 0x200; // alter address to suit boolean BreakPointON = false; // set true to activate boolean ExternalKeypad = false; // set true if you have an external keypad color PixelON = color(255); // pixel ON (white) color PixelOFF = color(32, 32, 128); // pixel OFF (leaves blue trace) //color PixelOFF = color(0); // pixel OFF (black) int Speed = 5; // Speed x 60 frames/second = 300 instructions/second) ////////////////////// no user adjustable values below this line ////////////////////// // ----- serial port import processing.serial.*; //import the serial library Serial myPort; //the Serial port object final int Baud_rate = 115200; //communication speed String Input_string; //for incoming data boolean Connected = false; //flag // ----- sound import processing.sound.*; SinOsc Sine; // assign name to sinewave // ----- chip8 Screen PImage Screen; // chip8 pixel Screen int ScreenWidth = 64; // chip8 Screen width int ScreenHeight = 32; // chip8 Screen height // ----- graphics Screen int PixelSize = 20; // Canvas pixel 20 times larger than chip8 pixel // ----- graphics Font PFont MyFont; // used when single-stepping // ----- chip8 Screen Font int[] Font = { 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80, // F }; // ----- file handling String Pathname = ""; // ----- chip8 memory map int[] Memory = new int[0xFFF]; // program ram ... CHIP-8 code starts at 0x200 int[] Stack = new int[32]; // program stack int FontAddress = 0x50; // Font address // ----- chip8 CPU int PC; // program Counter int I; // index register int SP; // stack pointer int[] V = new int[16]; // 16 variables (registers) // ----- chip8 timers int DelayTimer = 0; // delay int SoundTimer = 0; // beeps unless zero // ----- chip8 keypad int KEY = -1; // -1 equals key-up int Latch; // used in wait for keypush boolean KeyDown= false; // required for remote keyboard boolean KeyValid = false; // ----- chip8 instruction set int Opcode; // 2-byte instruction int Task; // operation int NNN; // number (memory location) int NN; // number (value) int N; // number (value) int X; // register number int Y; // register number // ----- Misc int StepCounter= 20; boolean Run = false; // run the program //------------------------------------- // setup() //------------------------------------- void setup() { size(1280, 640); // 64 x 32pixels scaled by 20 background(0); // background Canvas color //// ----- install wave files //Tone1 = new SoundFile(this, "beep-07.wav"); //Tone2 = new SoundFile(this, "beep-08b.wav"); //Battle = new SoundFile(this, "gun_battle.wav"); // ----- create the sinewave oscillator. Sine = new SinOsc(this); Sine.set(1800, 1, 0, 0); //frequency, amplitude, offset, +/- panning (left/right) // ----- create drawing areas Screen = createImage(ScreenWidth, ScreenHeight, RGB); // pixel-based graphics // ----- create Screen Font MyFont = createFont("CooperBlackStd", 48); textFont(MyFont); // ----- install CHIP-8 Fonts println("CHIP=8 Fonts"); for (int i=0; i 0) { DelayTimer-- ; } // ----- decrement sound timer @ 60Hz framerate) if (SoundTimer > 0) { Sine.play(); delay(25); // set sound duration SoundTimer-- ; } else { Sine.stop(); } // ----- normal fetch execute cycle for (int speed = 0; speed < Speed; speed++) { // this line controls the program speed /* Count controls the program speed 60 frames-per-second * 5 loops = 300 instructions-per-second */ if (Run == true) { execute(); // the first instruction was fetched when the program was loaded PC += 2; fetch(); // 2nd instruction fetch etc ... // ----- set a debug break-point if (BreakPointON) { if (PC == BreakPoint) { Run = false; displayRegisters(); print("Next Instruction: "); disassemble(Opcode); println(); } } } } } //////////// END RUN CODE //////////// //image(Screen, 20, 20); // inserts 64x32 pixel chip-8 screen at top-left corner } //------------------------------------- // keypressed() //------------------------------------- void keyPressed() { /* keyPressed generates multiple events */ // ----- CHIP-8 keyboard mapping /* 1 2 3 4 ==> 1 2 3 C Q W E R ==> 4 5 6 D A S D F ==> 7 8 9 E Z X C V ==> A 0 B F */ KeyValid = false; KeyDown = keyPressed; if ((key == '1') || (key == '!')) { KEY = 0x1; } if ((key == '2') || (key == '@')) { KEY = 0x2; } if ((key == '3') || (key == '#')) { KEY = 0x3; } if ((key == '4') || (key == '$')) { KEY = 0xC; } if ((key == 'q') || (key == 'Q')) { KEY = 0x4; } if ((key == 'w') || (key == 'W')) { KEY = 0x5; } if ((key == 'e') || (key == 'E')) { KEY = 0x6; } if ((key == 'r') || (key == 'R')) { KEY = 0xD; } if ((key == 'a') || (key == 'A')) { KEY = 0x7; } if ((key == 's') || (key == 'S')) { KEY = 0x8; } if ((key == 'd') || (key == 'D')) { KEY = 0x9; } if ((key == 'f') || (key == 'F')) { KEY = 0xE; } if ((key == 'z') || (key == 'Z')) { KEY = 0xA; } if ((key == 'x') || (key == 'X')) { KEY = 0x0; } if ((key == 'c') || (key == 'C')) { KEY = 0xB; } if ((key == 'v') || (key == 'V')) { KEY = 0xF; } //////////////////////////////////////////////// // ----- load program file if ((key == 'j') || (key == 'J')) { // get program selectFile(); } //////////////////////////////////////////////// // ----- start program if ((key == 'k') || (key == 'K')) { // start program Run = true; } //////////////////////////////////////////////// // ----- single-step through program if ((key == 'l') || (key == 'L')) { // display PC and Opcode onScreen Run = false; // ----- decrement delay timer if (DelayTimer > 0) { DelayTimer-- ; } // ----- decrement sound timer if (SoundTimer > 0) { Sine.play(); delay(25); // default sound duration SoundTimer-- ; } else { Sine.stop(); } // ----- execute current opcode then fetch next opcode execute(); PC += 2; fetch(); trace(); // display current registers and decode next instruction } //////////////////////////////////////////////// // ----- step through program without executing if ((key == 'p') || (key == 'P')) { // display PC and Opcode onScreen Run = false; PC += 2; fetch(); trace(); // display current registers and decode next instruction } } //------------------------------------- // keyReleased() //------------------------------------- void keyReleased() { if ((KEY >= 0x0) && (KEY <= 0xF)) { // check for hex key press Latch = KEY; KeyDown = keyPressed; KEY = -1; KeyValid = true; //////////////////////////////////////////////////////////////////////////////////// // ----- recognize keypad in single-step mode if ((Run == false) && ((Task == 0xE) || ((Task == 0xF) && (NN == 0x0A)))) { execute(); PC += 2; fetch(); trace(); // display current registers and decode next instruction } ///////////////////////////////////////////////////////////////////////////////////// } else { KeyValid = false; } } /////////////////////////////////////////////////////////////////////// // The following functions install your CHIP-8 program into memory // /////////////////////////////////////////////////////////////////////// //------------------------------------- // select a CHIP-8 file //------------------------------------- void selectFile() { println("Select a CHIP-8 file ..."); // stop the draw() function noLoop(); // stop draw() selectInput("Select a CHIP-8 file...", "loadFile"); } //------------------------------------- // load the program into memory //------------------------------------- void loadFile(File selection) { // ----- select a CHIP-8 file if (selection == null) { println("No file selected."); } else { // ----- get file Pathname println("File selected: " + selection.getAbsolutePath()); Pathname = selection.getAbsolutePath(); // ----- load the file contents into a buffer byte buffer[] = loadBytes(Pathname); // ----- copy the buffer contents to memory for (int i = 0; i < buffer.length; i++) { /* (http://www.darksleep.com/player/JavaAndUnsignedTypes.html) Processing is written in Java which has no unsigned data types. This means Java sees 0xFE as -2 instead of 254. In the following code the bitwise & operator automatically promotes the byte to int and Java assumes leading zeros for 0xFF. Hence in the above example: 0xFFFE & 0x00FF = 0x00FE = 254 */ // ----- convert from signed to unsigned binary before copying Memory[0x200 + i] = buffer[i] & 0xFF; // all CHIP-8 programs start at 0x200 } // ----- display Opcodes for (int i = 0; i < buffer.length; i++) { if ((i % 16) == 0) println(); // 16 opcodes per line // ----- create and display Opcodes if ((i%2 == 0) && (i< buffer.length)) { // ----- combine bytes int byte1 = Memory[0x200+i] & 0xff; int byte2 = Memory[0x200+i+1] & 0xff; Opcode = byte1<<8 | byte2; // ----- display Opcodes print(hex(Opcode) + " "); } } // ----- formatting println(); // terminate last line println(); // insert blank line5 } // ----- fetch first instruction PC = 0x200; // all programs start here int byte1 = Memory[PC] & 0xFF; int byte2 = Memory[PC+1] & 0xFF; Opcode = byte1<<8 | byte2; trace(); // display registers & disassemble loop(); // restart draw() } ////////////////////////////////////////////////// // The following functions decode each Opcode // ////////////////////////////////////////////////// //------------------------------------- // fetch instruction //------------------------------------- void fetch() { // ----- assemble opcode from 2 bytes int byte1 = Memory[PC] & 0xFF; int byte2 = Memory[PC+1] & 0xFF; Opcode = byte1<<8 | byte2; } //------------------------------------- // execute instruction //------------------------------------- void execute() { // ----- decode (separate the nibbles) Task = (Opcode & 0x0000F000)>>12; // operation NNN = Opcode & 0x00000FFF; // number (memory location) NN = Opcode & 0x000000FF; // number (value) X = (Opcode & 0x00000F00)>>8; // register number Y = (Opcode & 0x000000F0)>>4; // register number N = (Opcode & 0x0000000F); // number (value) // ----- determine instruction type switch (Task) { // ======================================= case 0x0: switch(NNN) { ///////////////////////////////////////// // ----- 0000 NOP No Operation case 0x000: break; ///////////////////////////////////////// // ----- 00E0 CLS Clear the Screen case 0x0E0: Screen.loadPixels(); color c = PixelOFF; for (int x=0; x 0xFF) { V[0xF] = 1; } else { V[0xF] = 0; } V[X] &= 0xFF; break; // ======================================= case 0x8 : switch (N) { ///////////////////////////////////////// // ----- 8XY0 SET VX=VY Copy VY to VX. case 0x0: V[X] = V[Y]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY1 OR VX=VX│VY Logical OR VX with VY. case 0x1: V[X] |= V[Y]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY2 AND VX=VX.VY Logical AND VX with VY. case 0x2: V[X] &= V[Y]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY3 XOR VX=VX XOR VY Logical XOR VX with VY case 0x3: V[X] ^= V[Y]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY4 ADD VX=VX+VY Add VY to VX. If result > FF, then VF=1. case 0x4: V[X] += V[Y]; if (V[X] > 0xFF) { V[0xF] = 1; } else { V[0xF] = 0; } V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY5 SUB VX=VX-VY (VF=NOT borrow) case 0x5: if (V[X] >= V[Y]) { V[0xF] = 1; // NOT borrow } else { V[0xF] = 0; } V[X] -= V[Y]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XY6 SHR VX=Vy, VX>>1, VF=bit shifted out (Divides by 2) ........... ambiguous: VX=VY sometimes ignored !! case 0x6: V[0xF] = V[Y] & 0x1; V[X] = V[Y] >> 1; break; ///////////////////////////////////////// // ----- 8XY7 SUBN VX=VY-VX (VF=NOT borrow) case 0x7: if (V[Y] >= V[X]) { V[0xF] = 1; // NOT borrow flag } else { V[0xF] = 0; } V[X] = V[Y] - V[X]; V[X] &= 0xFF; break; ///////////////////////////////////////// // ----- 8XYE SHL VX=Vy, VX<<1, VF=bit shifted out (Multiplies by 2) ........ ambiguous: VX=VY sometimes ignored !! case 0xE: V[0xF] = (V[Y] & 0x80) >> 7; V[X] = V[Y] << 1; V[X] &= 0xFF; break; ///////////////////////////////////////// default: break; } break; // ======================================= case 0x9 : // 9XY0 SKF VX≠VY Skip next Instruction if VX!=VY if (V[X] != V[Y]) PC +=2; break; // ======================================= case 0xA : // ----- ANNN I=NNN Set memory Index Pointer to NNN. I = NNN; break; // ======================================= case 0xB : // ----- BNNN JMP NNN+V0 Jump to location NNN+V0. PC = NNN + V[0] - 2; // PC advanced +2 by next execute PC &= 0x00FFFFFF; break; // ======================================= case 0xC : // ----- CXNN VX=RND&NN Get random byte, then AND with NN. V[X] = (int(random(0, 255)) & NN); break; // ======================================= case 0xD : // ----- DXYN DISPLAY N@VX,VY Display N-byte pattern at (VX,VY). Screen.loadPixels(); // get Screen pixels // ----- flags boolean ScreenPixel = false; V[0xF] = 0x0; // clear collision flag // ----- display sprite for (int row = 0; row < N; row++) { // ----- get next sprite row int line = Memory[I + row]; // ----- scan row for (byte pixel = 0; pixel < 8; pixel++) { // ----- check Screen pixel color c = Screen.get(V[X]+pixel, V[Y]+row); // get Screen pixel if (c == PixelON) { ScreenPixel=true; // pixel is ON } else { ScreenPixel=false; // pixel is OFF } // ----- if sprite pixel is on && Screen pixel is on if (((line & (0x80 >> pixel)) != 0 ) && (ScreenPixel == true)) { Screen.set(V[X]+pixel, V[Y]+row, PixelOFF); // turn off Screen pixel V[0xF] = 1; // set collision flag // ----- if sprite pixel is on && Screen pixel is off } else if (((line & (0x80 >> pixel)) != 0 ) && (ScreenPixel == false)) { Screen.set(V[X]+pixel, V[Y]+row, PixelON); // turn on Screen pixel V[0xF] = 0; } } } Screen.updatePixels(); // apply the changes break; // ======================================= case 0xE : switch (NN) { ///////////////////////////////////////// // ----- EX9E SKP V[X] == KEY case 0x9E: /* KEY is always -1 unless "keyPressed(). */ //if (keyPressed && (KEY == V[X])) PC += 2; // skip if equal if (KeyDown && (KEY == V[X])) PC += 2; // skip if equal break; ///////////////////////////////////////// // ----- EXA1 SKNP V[X] != [KEY] case 0xA1: /* KEY is always -1 unless "keyPressed(). */ PC += 2; // skip always unless //if (keyPressed && (KEY == V[X])) PC -= 2; // KEY equals VX (unskip) if (KeyDown && (KEY == V[X])) PC -= 2; // KEY equals VX (unskip) break; ///////////////////////////////////////// default: break; } break; // ======================================= case 0xF : switch(NN) { ///////////////////////////////////////// // ----- F000 STOP Jump to Monitor (CHIPOS). case 0x00: Run = false; break; ///////////////////////////////////////// // ----- FX07 VX=TIME Get current timer value. case 0x07: V[X] = DelayTimer & 0xFF; break; ///////////////////////////////////////// // ----- FX0A VX=KEY Input Hex key code. Wait for key down. case 0x0A: // ---- wait for hex keypush /* This routine waits for the key to be released */ if (KeyValid) { KeyValid = false; V[X] = Latch; } else { PC -=2; } break; ///////////////////////////////////////// // ----- FX15 TIME=VX Initialize delay timer case 0x15: DelayTimer = V[X] & 0xFF; break; ///////////////////////////////////////// // ----- FX18 TONE=VX Initialse Sound Timer case 0x18: SoundTimer = V[X] & 0xFF; break; ///////////////////////////////////////// // ----- FX1E I=I+VX Add VX to Memory Pointer (Index register) case 0x1E: I += V[X]; I &= 0x00000FFF; break; ///////////////////////////////////////// // ----- FX29 I=DSP,VX Set Pointer to display VX (LS digit). case 0x29: I = FontAddress; I += (V[X] & 0xFF) * 5; break; ///////////////////////////////////////// // ----- FX33 MI=DEQ,VX Store 3 digit decimal equivalent (BCD) of VX. case 0x33: Memory[I] = int((V[X] & 0xFF)/100); Memory[I+1] = int((V[X] & 0xFF)/10%10); Memory[I+2] = int((V[X] & 0xFF)%10); break; ///////////////////////////////////////// // FX55 MI=V0:VX Store V0 through VX starting at I. I=I+X+1 when finished case 0x55: for (int i = 0; i < X+1; i++) { // X is inclusive Memory[I] = V[i]; I++; } break; ///////////////////////////////////////// // ----- FX65 V0:VX=MI Load V0 through VX with Memory[I]. I=I+X+1 when finished case 0x65: for (int i=0; i>12; // operation NNN = opcode & 0x00000FFF; // number (memory location) NN = opcode & 0x000000FF; // number (value) X = (opcode & 0x00000F00)>>8; // register number Y = (opcode & 0x000000F0)>>4; // register number N = (opcode & 0x0000000F); // number (value) switch(Task) { case 0: switch (NNN) { case 0x000: print("NOP"); // NOP (no operation) break; case 0x0E0: print("CLS"); // CLS (clear screen) break; case 0x0EE: print("RTS"); // RTS (return from subroutine) break; default: break; } break; case 1: print("JMP " + hex(NNN).substring(5)); // JMP addr (jump to address) break; case 2: print("JSR " + hex(NNN).substring(5)); // JSR addr (jump to subroutin) break; case 3: print("SE, V" + hex(NNN).substring(5, 6) + "," + hex(NNN).substring(6)); // SE Vx,byte (skip if equal) break; case 4: print("SNE V" + hex(NNN).substring(5, 6) + "," + hex(NNN).substring(6)); // SE Vx,byte (skip if not equal) break; case 5: print("SE V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // SE Vx,Vy (skip if equal) break; case 6: print("LD V" + hex(NNN).substring(5, 6) + "," + hex(NNN).substring(6)); // LD Vx,byte break; case 7: print("ADD V" + hex(NNN).substring(5, 6) + "," + hex(NNN).substring(6)); // ADD Vx,byte break; case 8: switch(N) { case 0x0: print("LD V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // LD Vx,Vy break; case 0x1: print("OR V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // OR Vx,Vy break; case 0x2: print("AND V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // AND Vx,Vy break; case 0x3: print("XOR V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // XOR Vx,y break; case 0x4: print("ADD V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // ADD Vx,Vy break; case 0x5: print("SUB V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7) + " (VF = NOT borrow)"); // SUB Vx,Vy break; case 0x6: print("SHR V" + hex(NNN).substring(5, 6) + "]{,V" + hex(NNN).substring(6, 7) + "}"); // SHR Vx{,Vy} (copy Vy to Vx then shift right, Vf=lsb) break; case 0x7: print("SUBN V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7) + " (VF = NOT borrow)"); // SUBN Vx,Vy (subtract Vy from Vx, Vf = NOT borrow) break; case 0xE: print("SHL V" + hex(NNN).substring(5, 6) + "{,V" + hex(NNN).substring(6, 7) + "}"); // SHL Vx{,Vy} (copy Vy to Vx then shift left, Vf=msb) break; default: break; } break; case 9: print("SNE V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7)); // SNE Vx,Vy (skip not equal) break; case 0xA: print("LD I " + hex(NNN).substring(5)); // LD I,addr (load Index register) break; case 0xB: print("JMP V0 " + hex(NNN).substring(5)); // JMP V0,addr (jump to sum of address + V0) break; case 0xC: print("RND V" + hex(NNN).substring(5, 6) + "," + hex(NNN).substring(6)); // RND Vx,byte (set Vx to random number then AND with byte) break; case 0xD: print("DRW V" + hex(NNN).substring(5, 6) + ",V" + hex(NNN).substring(6, 7) + "," + N); // DRW Vx,Vy,nibble (draw nibble-sprite-lines @ screen coordinate Vx,Vy]) break; case 0xE: switch(NN) { case 0x9E: print("SKP key==V" + hex(NNN).substring(5, 6)); // SKP Vx (skip if pushed key == Vx) break; case 0xA1: print("SKN key!=V" + hex(NNN).substring(5, 6)); // SKN Vx (skip if pushed key != Vx break; default: break; } break; case 0xF: switch (NN) { case 0x00: print("Stop"); break; case 0x07: print("LD V" + hex(NNN).substring(5, 6) + ",DelayTimer"); // LD Vx,DT (load Vx with contents of delay timer) break; case 0x0A: print("LD V" + hex(NNN).substring(5, 6) + ",Key"); //LD Vx,K (load Vx with key push value) break; case 0x15: print("LD DelayTimer,V" + hex(NNN).substring(5, 6)); // LD DT,Vx (load delay timer with contents Vx) break; case 0x18: print("LD SoundTimer,V" + hex(NNN).substring(5, 6)); // LD ST,Vx (load sound timer with contents Vx) break; case 0x1E: print("ADD I,V" + hex(NNN).substring(5, 6)); // ADD I,Vx (add contents of Vx to register I) break; case 0x29: print("Point I to sprite for V" + hex(NNN).substring(5, 6)); // LD Font,Vx (set register I to font-number in Vx break; case 0x33: print("Store BCD equiv of V" + hex(NNN).substring(5, 6) + " starting at Memory[I]"); // LD BCD,Vx (store BCD equivalent of Vx into Memory[I],Memory[I+1],Memory[I+2] break; case 0x55: print("Store V0 through V" + hex(NNN).substring(5, 6) + " starting at I"); // MI=V0:VX (store V0 through VX starting at I. I=I+X+1 when finished) break; case 0x65: print("Load V0 through V" + hex(NNN).substring(5, 6) + " starting with Memory[I]"); // V0:VX=MI (load V0 through VX with Memory[I]. I=I+X+1 when finished break; default: break; } break; } } // ======================= // serial event (called with each Arduino data string) // ======================= void serialEvent(Serial myPort) { // ----- wait for a line-feed Input_string = myPort.readStringUntil('\n'); KEY = int(trim(Input_string)); // remove line-feed then convert to int // ----- check for key up code (-1) if (KEY < 0) { KeyDown= false; KeyValid = false; KEY = -1; } else { // ----- valid key Latch = KEY; KeyValid = true; KeyDown= true; } // ----- clear receive buffer myPort.clear(); }