/**********************************************************************
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();
}