Prepare your compiler !
There is not that much text in this blog post. You find there the source code of the entire project. The code can be used “as is” and compiled on Arduino IDE or Visual Studio Code with Platformio. Before compiling you need to insert into the code (search “Put here your ROM dumps”) the dumps of the four ROMs of the original game, plus optionally the Test ROM, that we obviously cannot provide here.
For example you can use a dump of the original ROM or take the file from ROMs compatibile with MAME.
If you get four files with the dump of the ROMs you can convert the binary files to C source code using the following python2 snippet. You have to cut & paste this snippet into a file called “convert.py” and then use the command “python convert.py romfilename csourcefilename arrayname”.
import os import sys if len(sys.argv) != 4: print("\n\nMissing arguments <input file name> <output file name> <array name>") exit(0) try: binFile = open(str(sys.argv[1]), 'rb') except: print("\n\nCould not open file:", str(sys.argv[1])) sys.exit() try: txtFile = open(str(sys.argv[2]), 'w+') except: print("\n\nCould not create file:", str(sys.argv[2])) sys.exit() size = os.path.getsize(str(sys.argv[1])) binaryData = binFile.read(size) a = 0 b = 0 txtFile.write("const uint8_t %s [%d] = {\n" % (str(sys.argv[3]), size)) for c in binaryData: txtFile.write("0x%02X" % ord(c)) a = a+1 b = b+1 if b < size: txtFile.write(", ") else: txtFile.write("\n};") if a == 16: a = 0 txtFile.write("\n") print("\n\nRomMaker %d byte parsed and created file " % size, str(sys.argv[2])) print("\nwww.xnor.it") binFile.close() txtFile.close()
From the output of the python script (contents of the generatde csourcefilename files) you should cut & paste only the data part (rows starting with 0x, no brackets or array namex) and put those parts in the source code., for example where you find “// H ROM – Put H ROM dump here”.
Once you’ve inserted the four ROM dumps (ROM H, ROM F, ROM G, ROM E, and optionally ROM Test) you can compile and flash to the Arduino the following source code.
We hope that the source code is readable, commented, and clear enough.
/******************************************************************************** * * SPACE ARDUIN.VADERS - Version 0.16s * * This is a software emulator written to run the ORIGINAL bytecode of * Taito's (TM) Space Invaders arcade on Arduino Nano v.3 with an ILI9341 LCD * display. This project has been done to contribute to the 2019 edition of * Once Upon a Sprite italian yearly retroprogrmming event. * * A project done with Arduino IDE and VSCode in 2018/2019 by: * Maurizio Damiani (audiosalda) - m.damiani@xnor.it * Giuliano C. Peritore (bugtimizer) - g.peritore@xnor.it * Vittorio Signorelli (spiman) - v.signorelli@xnor.it * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * *******************************************************************************/ /* ELECTRICAL CONNECTIONS - LCD DISPLAY * ==================================== * TFT Vcc --------- 5V * TFT GND --------- GND * TFT RESET ------- PIN 8 [with divider] * TFT D/C --------- PIN 9 [with divider] * TFT CS --------- PIN 10 [with divider] * TFT SDI(MOSI) --- PIN 11 [with divider] * TFT SDO(MISO) --- PIN 12 [without divider] * TFT SCK --------- PIN 13 [with divider] * TFT LED -resistor- 5V * * Resistor divider between Arduino Nano, ILI9341 and GND * * Display color scheme: * Resolution: 320x240 pixels * Bits per pixel: 5 bits RED, 6 bits GREEN, 5 bits BLUE * * ELECTRICAL CONNECTIONS - CONTROLLERS * ==================================== * 1P START--------- ANLG 0 (pulldown resistor, switch to +5V) * 2P START--------- ANLG 1 (pulldown resistor, switch to +5V) * FIRE1 ----------- ANLG 2 (pulldown resistor, switch to +5V) * LEFT1 ----------- ANLG 3 (pulldown resistor, switch to +5V) * RIGHT1 ---------- ANLG 4 (pulldown resistor, switch to +5V) * CREDIT ---------- ANLG 5 (pulldown resistor, switch to +5V) * * ELECTRICAL CONNECTIONS - SOUND (rough) * ============================== * AUDIO1 ---------- PIN 2 (resistor to speaker to GND) - UNUSED * AUDIO2 ---------- PIN 3 (resistor to speaker to GND) - ALL SOUNDS * AUDIO3 ---------- PIN 4 (resistor to speaker to GND) - WALKER * AUDIO4 ---------- PIN 5 (resistor to speaker to GND) - UNUSED * Add as a filter an electrolitical capacitor on speaker. * * * Notes: * - 8080 original clock: 2 MHz * - to display the EASTER EGG during demo mode press: * LEFT+RIGHT+FIRE+2PSTART and then: * LEFT+ FIRE+1PSTART * - to generate a BMP HEX dump of the screen press: * 1PSTART+2PSTART * - to Tilt the game press: * CREDIT+1PSTART+2PSTART */ #pragma GCC push_options #pragma GCC optimize ("O2") // O3 is better but you need to remove TEST ROM #include <stdio.h> #include <stdbool.h> #include <EEPROM.h> #include <SPI.h> // You can enable some debugging messages on serial port //#define DEBUG TRUE // Hardware circuit defines #define BTN_1P_START A0 // controls #define BTN_2P_START A1 // controls #define BTN_FIRE1 A2 // controls #define BTN_LEFT1 A3 // controls #define BTN_RIGHT1 A4 // controls #define BTN_CREDIT A5 // controls #define TFT_RST 8 // display #define TFT_DC 9 // display #define TFT_CS 10 // display #define TFT_SDI 11 // display #define TFT_SDO 12 // display #define TFT_SCK 13 // display #define TFT_DC_PIN 0x02 // PB1 #define TFT_CS_PIN 0x04 // PB2 // Display offset (the display is bigger and an offset is needed) #define SCREEN_CENTER_X 32 #define SCREEN_CENTER_Y 8 // Color scheme versions #define COL_VERSION_SV 0 // SV Version (black and white) #define COL_VERSION_TV 1 // TV & Midway version (black white and green) #define COL_VERSION_CV 2 // CV Version (colors) #define COL_VERSION_GP 3 // Ghost Psychedelic Version (DPS) #define COL_VERSION_RT 4 // Enable TEST ROM // Audio section defines #define SND_UFO 0x01 // port 3 #define SND_SHOT 0x02 // port 3 - Fire #define SND_FLASH 0x04 // port 3 - Player dies #define SND_INV_DIE 0x08 // port 3 - Invader dies #define SND_EXT_PLAY 0x10 // port 3 - Extended play //#define SND_AMP_ENA 0x20 // port 3 - Amplifier (unused) #define SND_FLEET_1 0x01 // port 5 - Walkers #define SND_FLEET_2 0x02 // port 5 #define SND_FLEET_3 0x04 // port 5 #define SND_FLEET_4 0x08 // port 5 #define SND_UFO_HIT 0x10 // port 5 - Ufo dies #define PWM_OUT_PIN 3 // Can be either 3 or 11, two PWM outputs connected to Timer 2 #define AUDIO_WALKER 0x10 // pin 4 for Walkers generator #define WALK_FREQ_75_Hz 421 // Walker frequency 75 Hz #define WALK_FREQ_66_Hz 387 // Walker frequency 66 Hz #define WALK_FREQ_62_Hz 363 // Walker frequency 62 Hz #define WALK_FREQ_57_Hz 320 // Walker frequency 57 Hz #define UFO_SWEEP_RAMP_MIN 4 // Ufo sweep ramp sample start point #define UFO_SWEEP_RAMP_MAX 40 // Ufo sweep ramp sample end point #define UFO_SWEEP_FREQUENCY 50 // Ufo sweep frequency #define UFO_HIT_SWEEP_FREQUENCY 30 // Ufo hit sweep frequency #define FLASH_NOISE_FREQUENCY 8 // Flash Noise frequency #define FLASH_EXPLOSION_TIME 4000 // Noise generator duration for Flash explosion #define SHOT_FREQUENCY 7 // Shot #define SHOT_SOUND_TIME 15000 // Shot sound duration #define INVADERS_DIE_SWEEP_FREQUENCY 50 // Invaders Die sound frequency #define EXTENDED_PLAY_FREQUENCY_H 100 // Divider to get a 960 Hz tone for Extended Play #define EXTENDED_PLAY_FREQUENCY_L 50 // Defines for the DIP switch settings interface // Number of ships: L=4, R=5, RL=6 (pressed on boot) #define DIP3_SHIPS 0x01 // Changed via LEFT button #define DIP5_SHIPS 0x02 // Changed via RIGHT button // Extra ship at 1000 if FIRE set on boot #define DIP6_EXTRA_SHIP 0x08 // Changed via FIRE button // Coin info shown on demo screen (OFF if CREDIT pressed on boot) #define DIP7_COIN_INFO 0x80 // Changed via CREDIT button #define ANTIBUMP_MSECS 50 // Anti-bounce msecs // Defines for ILI9431 display (BGR mode) #define COL_BLACK 0x0000 #define COL_WHITE 0xFFFF #define COL_BLUE 0xF800 #define COL_GREEN 0x07E0 #define COL_RED 0x001F #define COL_YELLOW 0x07FF #define COL_CYAN 0xFFE0 #define COL_PURPLE 0xF81F #define ILI9341_NOP 0x00 #define ILI9341_SOFTRESET 0x01 #define ILI9341_POWERMODE 0x0A #define ILI9341_SELFDIAG 0x0F #define ILI9341_SLEEPIN 0x10 #define ILI9341_SLEEPOUT 0x11 #define ILI9341_NORMALDISP 0x13 #define ILI9341_INVERTOFF 0x20 #define ILI9341_INVERTON 0x21 #define ILI9341_GAMMASET 0x26 #define ILI9341_DISPLAYOFF 0x28 #define ILI9341_DISPLAYON 0x29 #define ILI9341_COLADDRSET 0x2A #define ILI9341_PAGEADDRSET 0x2B #define ILI9341_MEMORYWRITE 0x2C #define ILI9341_MEMORYREAD 0x2E #define ILI9341_MEMCONTROL 0x36 #define ILI9341_PIXELFORMAT 0x3A #define ILI9341_BRIGHTNESS 0x51 #define ILI9341_FRAMECONTROL 0xB1 #define ILI9341_DISPLAYFUNC 0xB6 #define ILI9341_ENTRYMODE 0xB7 #define ILI9341_POWERCONTROL1 0xC0 #define ILI9341_POWERCONTROL2 0xC1 #define ILI9341_VCOMCONTROL1 0xC5 #define ILI9341_VCOMCONTROL2 0xC7 #define ILI9341_READ_ID4 0xD3 #define ILI9341_MADCTL_MY 0x80 #define ILI9341_MADCTL_MX 0x40 #define ILI9341_MADCTL_MV 0x20 #define ILI9341_MADCTL_ML 0x10 #define ILI9341_MADCTL_RGB 0x00 #define ILI9341_MADCTL_BGR 0x08 #define ILI9341_MADCTL_MH 0x04 // Put here your ROM dumps (H, G, E, F, and H_TEST if available) const PROGMEM uint8_t invaders_rom_HGFE [8192]={ // H ROM - Put H ROM dump here // G ROM - Put G ROM dump here // F ROM - Put F ROM dump here // E ROM - Put E ROM dump here }; const PROGMEM uint8_t invaders_rom_H_TEST [2048]={ // H TEST ROM - Put H TEST ROM dump here }; // 5x8 font for credits and DIP switch interface const PROGMEM uint8_t font[] = { 0x00, 0x00, 0x00, 0x00, 0x00, // " " 0x00, 0x00, 0x4f, 0x00, 0x00, // ! 0x00, 0x07, 0x00, 0x07, 0x00, // " 0x14, 0x7f, 0x14, 0x7f, 0x14, // # 0x24, 0x2a, 0x7f, 0x2a, 0x12, // $ 0x23, 0x13, 0x08, 0x64, 0x62, // % 0x36, 0x49, 0x55, 0x22, 0x50, // & 0x00, 0x05, 0x03, 0x00, 0x00, // ' 0x00, 0x1c, 0x22, 0x41, 0x00, // ( 0x00, 0x41, 0x22, 0x1c, 0x00, // ) 0x14, 0x08, 0x3e, 0x08, 0x14, // * 0x08, 0x08, 0x3e, 0x08, 0x08, // + 0x00, 0x50, 0x30, 0x00, 0x00, // , 0x08, 0x08, 0x08, 0x08, 0x08, // - 0x00, 0x60, 0x60, 0x00, 0x00, // . 0x20, 0x10, 0x08, 0x04, 0x02, // / 0x3e, 0x51, 0x49, 0x45, 0x3e, // 0 0x00, 0x42, 0x7f, 0x40, 0x00, // 1 0x42, 0x61, 0x51, 0x49, 0x46, // 2 0x21, 0x41, 0x45, 0x4b, 0x31, // 3 0x18, 0x14, 0x12, 0x7f, 0x10, // 4 0x27, 0x45, 0x45, 0x45, 0x39, // 5 0x3c, 0x4a, 0x49, 0x49, 0x30, // 6 0x01, 0x71, 0x09, 0x05, 0x03, // 7 0x36, 0x49, 0x49, 0x49, 0x36, // 8 0x06, 0x49, 0x49, 0x29, 0x1e, // 9 0x00, 0x36, 0x36, 0x00, 0x00, // : 0x00, 0x56, 0x36, 0x00, 0x00, // ; 0x08, 0x14, 0x22, 0x41, 0x00, // < 0x14, 0x14, 0x14, 0x14, 0x14, // = 0x00, 0x41, 0x22, 0x14, 0x08, // > 0x02, 0x01, 0x51, 0x09, 0x06, // ? 0x32, 0x49, 0x79, 0x41, 0x3e, // @ 0x7e, 0x11, 0x11, 0x11, 0x7e, // A 0x7f, 0x49, 0x49, 0x49, 0x36, // B 0x3e, 0x41, 0x41, 0x41, 0x22, // C 0x7f, 0x41, 0x41, 0x22, 0x1c, // D 0x7f, 0x49, 0x49, 0x49, 0x41, // E 0x7f, 0x09, 0x09, 0x09, 0x01, // F 0x3e, 0x41, 0x49, 0x49, 0x7a, // G 0x7f, 0x08, 0x08, 0x08, 0x7f, // H 0x00, 0x41, 0x7f, 0x41, 0x00, // I 0x20, 0x40, 0x41, 0x3f, 0x01, // J 0x7f, 0x08, 0x14, 0x22, 0x41, // K 0x7f, 0x40, 0x40, 0x40, 0x40, // L 0x7f, 0x02, 0x0c, 0x02, 0x7f, // M 0x7f, 0x04, 0x08, 0x10, 0x7f, // N 0x3e, 0x41, 0x41, 0x41, 0x3e, // O 0x7f, 0x09, 0x09, 0x09, 0x06, // P 0x3e, 0x41, 0x51, 0x21, 0x5e, // Q 0x7f, 0x09, 0x19, 0x29, 0x46, // R 0x46, 0x49, 0x49, 0x49, 0x31, // S 0x01, 0x01, 0x7f, 0x01, 0x01, // T 0x3f, 0x40, 0x40, 0x40, 0x3f, // U 0x1f, 0x20, 0x40, 0x20, 0x1f, // V 0x3f, 0x40, 0x38, 0x40, 0x3f, // W 0x63, 0x14, 0x08, 0x14, 0x63, // X 0x07, 0x08, 0x70, 0x08, 0x07, // Y 0x61, 0x51, 0x49, 0x45, 0x43, // Z 0x00, 0x7f, 0x41, 0x41, 0x00, // [ 0x02, 0x04, 0x08, 0x10, 0x20, // "\" 0x00, 0x41, 0x41, 0x7f, 0x00, // ] 0x04, 0x02, 0x01, 0x02, 0x04, // ^ 0x40, 0x40, 0x40, 0x40, 0x40, // _ 0x00, 0x01, 0x02, 0x04, 0x00, // ` 0x20, 0x54, 0x54, 0x54, 0x78, // a 0x7f, 0x48, 0x44, 0x44, 0x38, // b 0x38, 0x44, 0x44, 0x44, 0x20, // c 0x38, 0x44, 0x44, 0x48, 0x7f, // d 0x38, 0x54, 0x54, 0x54, 0x18, // e 0x08, 0x7e, 0x09, 0x01, 0x02, // f 0x0c, 0x52, 0x52, 0x52, 0x3e, // g 0x7f, 0x08, 0x04, 0x04, 0x78, // h 0x00, 0x44, 0x7d, 0x40, 0x00, // i 0x20, 0x40, 0x44, 0x3d, 0x00, // j 0x7f, 0x10, 0x28, 0x44, 0x00, // k 0x00, 0x41, 0x7f, 0x40, 0x00, // l 0x7c, 0x04, 0x18, 0x04, 0x78, // m 0x7c, 0x08, 0x04, 0x04, 0x78, // n 0x38, 0x44, 0x44, 0x44, 0x38, // o 0x7c, 0x14, 0x14, 0x14, 0x08, // p 0x08, 0x14, 0x14, 0x18, 0x7c, // q 0x7c, 0x08, 0x04, 0x04, 0x08, // r 0x48, 0x54, 0x54, 0x54, 0x20, // s 0x04, 0x3f, 0x44, 0x40, 0x20, // t 0x3c, 0x40, 0x40, 0x20, 0x7c, // u 0x1c, 0x20, 0x40, 0x20, 0x1c, // v 0x3c, 0x40, 0x30, 0x40, 0x3c, // w 0x44, 0x28, 0x10, 0x28, 0x44, // x 0x0c, 0x50, 0x50, 0x50, 0x3c, // y 0x44, 0x64, 0x54, 0x4c, 0x44, // z 0x00, 0x08, 0x36, 0x41, 0x00, // { 0x00, 0x00, 0x7f, 0x00, 0x00, // | 0x00, 0x41, 0x36, 0x08, 0x00, // } 0x02, 0x01, 0x02, 0x04, 0x02, // ~ 0x00, 0x00, 0x00, 0x00, 0x00 }; // Authors martians bitmaps for emulstor credits screen const PROGMEM uint8_t GIU1[]={0x00, 0x00, 0x39, 0x79, 0x7A, 0x6E, 0xEC, 0xFA, 0xFA, 0xEC, 0x6E, 0x7A, 0x79, 0x39, 0x00, 0x00}; const PROGMEM uint8_t MAU1[]={0x00, 0x00, 0x00, 0x78, 0x1D, 0xBE, 0x6C, 0x3C, 0x3C, 0x3C, 0x6C, 0xBE, 0x1D, 0x78, 0x00, 0x00}; const PROGMEM uint8_t VIT1[]={0x00, 0x00, 0x00, 0x00, 0x19, 0x3A, 0x6D, 0xFA, 0xFA, 0x6D, 0x3A, 0x19, 0x00, 0x00, 0x00, 0x00}; const PROGMEM uint8_t GIU2[]={0x00, 0x00, 0x38, 0x7A, 0x7F, 0x6D, 0xEC, 0xFA, 0xFA, 0xEC, 0x6D, 0x7F, 0x7A, 0x38, 0x00, 0x00}; const PROGMEM uint8_t MAU2[]={0x00, 0x00, 0x00, 0x0E, 0x18, 0xBE, 0x6D, 0x3D, 0x3C, 0x3D, 0x6D, 0xBE, 0x18, 0x0E, 0x00, 0x00}; const PROGMEM uint8_t VIT2[]={0x00, 0x00, 0x00, 0x00, 0x1A, 0x3D, 0x68, 0xFC, 0xFC, 0x68, 0x3D, 0x1A, 0x00, 0x00, 0x00, 0x00}; // Game global variables uint8_t invadersRAM[1024]; // RAM space uint8_t loop_cnt = 0; // Interrupt counter (Arduino audio interrupts counter) bool vblank = false; // Simulates VBLANK latch uint8_t port2_in = 0; // Used for DIP switches uint8_t port2_out = 0; // Port 2 out (shift register amount) uint8_t port3_out = 0; // Port 3 out (sounds) uint8_t port4_lsb = 0; // Port 4 out (shift register data) uint8_t port4_msb = 0; // Port 4 out (shift register data) uint8_t port5_out = 0; // Port 3 out (sounds) uint8_t color_version = 0x00; // Defaults to BW color scheme bool test_ROM_enable = false; // Defaults to not use test ROM bool tilt_enable = false; // Defaults to not tilted // Audio variables volatile uint16_t WalkerFrequency; volatile uint16_t WalkerFrequencyCounter = 0; volatile uint8_t UfoFrequencyCounter = 0; volatile uint8_t UfoSweepValue; volatile uint8_t UfoSweepFrequencyCounter; volatile uint8_t UfoSweepFrequency = UFO_SWEEP_RAMP_MIN; volatile bool UfoSweepRampDirection = false; volatile bool FlashSoundActive = false; volatile uint8_t FlashNoiseFrequencyCounter = 0; volatile uint8_t FlashNoiseEnvelopeCounter = 0; volatile uint16_t FlashNoiseEnvelope = 0xffff; volatile uint16_t FlashShotTime = 0; volatile uint16_t FlashNoise = 0xace1; volatile uint16_t FlashNoise8 = 0; volatile bool ShotSoundActive = false; volatile uint8_t ShotFrequencyCounter = 0; volatile uint16_t ShotFrequencyDecay = 0; volatile uint16_t ShotTime = 0; volatile uint16_t ShotEnvelope = 0xffff; volatile uint16_t ShotNoise = 0xace1; volatile uint16_t ShotNoise8 = 0; volatile bool InvadersDieSoundActivate = false; volatile uint8_t InvadersDieSweepFrequencyCounter = 0; volatile uint8_t InvadersDieSweepFrequency = 0; volatile uint8_t InvadersDieSoundTime = 0; volatile uint8_t InvadersDieFrequencyCounter = 0; volatile bool ExtendedPlaySoundActivate = false; volatile uint8_t ExtendedPlayDoubleTone = 0; volatile uint8_t ExtendedPlayDoubleTime = 0; volatile uint8_t ExtendedPlayFrequencyCounter = 0; volatile uint8_t ExtendedPlayFrequency = 0; volatile uint16_t ExtendedPlaySoundTime = 0; volatile uint8_t TemporaryByte; // 8080 CPU emulator global variables (flags) #define CarryFlag 0x01 #define ParityFlag 0x04 #define AuxCarryFlag 0x10 #define ZeroFlag 0x40 #define SignFlag 0x80 /* Note for dummies: * * The UNION allows to access the same memory space in different modes. * Actually HL16 is overlying on L,H and thus you can write 16 bit words * and read it back either as a 16 bit word or as two 8 bit halves and * viceversa. * * You have to define a variable this wqy: * reg_HL registroHL; * and you can read either with: * registerHL.HL16 = 0x0000; * or with: * registerHL.HL.8.L = 0x00; * registerHL.HL.8.H = 0x00; * * We use different similar UNIONs just to have a more readable code */ typedef union { uint16_t BC16; struct { uint8_t C; uint8_t B; } BC8; } reg_BC; typedef union { uint16_t DE16; struct { uint8_t E; uint8_t D; } DE8; } reg_DE; typedef union { uint16_t HL16; struct { uint8_t L; uint8_t H; } HL8; } reg_HL; /* 16-bit registers pairs */ reg_BC BC = { 0x0000 }; reg_DE DE = { 0x0000 }; reg_HL HL = { 0x0000 }; /** 8-bit accumulator register A. */ uint8_t A; /** 8-bit flag register F. */ uint8_t F; /* 16-bit program counter. */ union { uint16_t PC16; struct { uint8_t L; uint8_t H; } PC8; } PC; /** 16-bit stack pointer. */ uint16_t SP_; bool cpu8080_in_halt; bool InterruptFlag; // Variables for switched emulator (tmp opcode variables) uint8_t a_tmp; uint16_t pc_tmp; uint16_t hl_tmp; uint16_t de_tmp; uint16_t bit16_tmp; uint32_t bit32_tmp; const PROGMEM uint8_t cpu8080_Flag_Zero_Signed_Parity[256] = { 0x44,0x00,0x00,0x04,0x00,0x04,0x04,0x00,0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04, 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04,0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00, 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04,0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00, 0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00,0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04, 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04,0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00, 0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00,0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04, 0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00,0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04, 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04,0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00, 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84,0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80, 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80,0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84, 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80,0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84, 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84,0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80, 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80,0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84, 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84,0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80, 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84,0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80, 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80,0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84 }; // Function definitions uint8_t cpu8080_readByte( uint16_t address ); void cpu8080_writePort(uint8_t addr, uint8_t data); uint8_t cpu8080_readPort(uint8_t port); void cpu8080_writeByte( uint16_t address, uint8_t b ); uint16_t cpu8080_readWord( uint16_t address ); // 8080 emulator functions void cpu8080_addByte(uint8_t op) { uint16_t x = A + op; // F &= 0x28; F = 0; if( ! (x & 0xFF) ) F |= ZeroFlag; else if( x & 0x80) F |= SignFlag; if( x >= 0x100) F |=CarryFlag; if( (A ^ op ^ x) & 0x10) F |= AuxCarryFlag; if( ~(A ^ op) & (x ^ op) & 0x80) F |= ParityFlag; A = x; } void cpu8080_addByteCarry(uint8_t op, uint8_t cf) { uint16_t x = A + op; if(cf) x++; // F &= 0x28; F = 0; if( ! (x & 0xFF) ) F |= ZeroFlag; else if( x & 0x80) F |= SignFlag; if( x >= 0x100) F |= CarryFlag; if( (A ^ op ^ x) & 0x10) F |= AuxCarryFlag; if( ~(A ^ op) & (x ^ op) & 0x80) F |= ParityFlag; A = x; } uint8_t cpu8080_subByte(uint8_t op) { uint8_t x = A - op; // F = 0x02 | (F & 0x28); F = 0; if(x == 0) F |= ZeroFlag; else if(x & 0x80) F |= SignFlag; if( (x >= A) && op) F |= CarryFlag; if( (A ^ op ^ x) & 0x10) F |= AuxCarryFlag; if( (A ^ op) & (x ^ A) & 0x80) F |= ParityFlag; return x; } uint8_t cpu8080_subByteCarry(uint8_t op, uint8_t cf) { uint8_t x = A - op; if(cf) x--; // F = 0x02 | (F & 0x28); F = 0; if(x == 0) F |= ZeroFlag; else if( x & 0x80) F |= SignFlag; if( (x >= A) && (op | cf)) F |= CarryFlag; if( (A ^ op ^ x) & 0x10) F |= AuxCarryFlag; if( (A ^ op) & (x ^ A) & 0x80) F |= ParityFlag; return x; } uint8_t cpu8080_decByte( uint8_t b ) { F = (F & ~(ZeroFlag | SignFlag | AuxCarryFlag | ParityFlag)); if( (b & 0x0F) == 0) F |= AuxCarryFlag; --b; if(b==0) F |= ZeroFlag; else { if(b == 0x7F) F |= ParityFlag; else if(b & 0x80) F |= SignFlag; } return b; } uint8_t cpu8080_incByte(uint8_t b) { ++b; // F &= ~(0x02 | ZeroFlag | SignFlag | AuxCarryFlag | ParityFlag); F &= ~(ZeroFlag | SignFlag | AuxCarryFlag | ParityFlag); if(b==0) F |= ZeroFlag; else { if( ! (b & 0x0F) ) F |= AuxCarryFlag; if(b & 0x80) { F |= SignFlag; if(b == 0x80) F |= ParityFlag; } } return b; } void cpu8080_callSub( uint16_t addr ) { SP_--; SP_--; invadersRAM[SP_ & 0x3ff ] = PC.PC8.L; invadersRAM[(SP_+1) & 0x3ff] = PC.PC8.H; PC.PC16 = addr; } void cpu8080_retFromSub(void) { PC.PC8.L = invadersRAM[SP_ & 0x3ff]; PC.PC8.H = invadersRAM[(SP_+1) & 0x3ff]; SP_++; SP_++; } void cpu8080_reset(void) { InterruptFlag = false; cpu8080_in_halt = false; BC.BC16 = 0x0000; DE.DE16 = 0x0000; HL.HL16 = 0x0000; A = 0; F = 0; PC.PC16 = 0; SP_ = 0xF000; } // This function executes one 8080 instruction void invadersStep(void) { static bool intState = false; // Two interrupts per video frame // Go on until an interrupt occurs (vblank every 8 msecs) do { // Thanks to Giovanni Bajo for the jumptable hint switch(cpu8080_readByte(PC.PC16++)) { case 0x00: // NOP case 0x08: // NOP case 0x10: // NOP case 0x18: // NOP case 0x20: // NOP case 0x28: // NOP case 0x30: // NOP case 0x38: // NOP case 0xcb: // NOP case 0xd9: // NOP case 0xdd: // NOP case 0xed: // NOP case 0xfd: // NOP case 0x40: // MOV B,B case 0x49: // MOV C,C case 0x52: // MOV D,D case 0x5B: // MOV E,E case 0x64: // MOV H,H case 0x6D: // MOV L,L case 0x7F: // MOV A,A break; case 0x76: // HLT cpu8080_in_halt = true; PC.PC16--; break; case 0xfb: // EI InterruptFlag = true; break; case 0xf3: // DI InterruptFlag = false; break; case 0xc7: // RST 0 cpu8080_callSub(0x00); break; case 0xcf: // RST 1 cpu8080_callSub(0x08); break; case 0xd7: // RST 2 cpu8080_callSub(0x10); break; case 0xdf: // RST 3 cpu8080_callSub(0x18); break; case 0xe7: // RST 4 cpu8080_callSub(0x20); break; case 0xef: // RST 5 cpu8080_callSub(0x28); break; case 0xf7: // RST 6 cpu8080_callSub(0x30); break; case 0xff: // RST 7 cpu8080_callSub(0x38); break; case 0xd3: // OUT n cpu8080_writePort(cpu8080_readByte(PC.PC16++), A); break; case 0xdb: // IN n A = cpu8080_readPort(cpu8080_readByte(PC.PC16++)); break; case 0x41: // MOV B,C BC.BC8.B = BC.BC8.C; break; case 0x42: // MOV B,D BC.BC8.B = DE.DE8.D; break; case 0x43: // MOV B,E BC.BC8.B = DE.DE8.E; break; case 0x44: // MOV B,H BC.BC8.B = HL.HL8.H; break; case 0x45: // MOV B,L BC.BC8.B = HL.HL8.L; break; case 0x46: // MOV B,M BC.BC8.B = cpu8080_readByte(HL.HL16); break; case 0x47: // MOV B,A BC.BC8.B = A; break; case 0x48: // MOV C,B BC.BC8.C = BC.BC8.B; break; case 0x4a: // MOV C,D BC.BC8.C = DE.DE8.D; break; case 0x4b: // MOV C,E BC.BC8.C = DE.DE8.E; break; case 0x4c: // MOV C,H BC.BC8.C = HL.HL8.H; break; case 0x4d: // MOV C,L BC.BC8.C = HL.HL8.L; break; case 0x4e: // MOV C,M BC.BC8.C = cpu8080_readByte(HL.HL16); break; case 0x4f: // MOV C,A BC.BC8.C = A; break; case 0x50: // MOV D,B DE.DE8.D = BC.BC8.B; break; case 0x51: // MOV D,C DE.DE8.D = BC.BC8.C; break; case 0x53: // MOV D,E DE.DE8.D = DE.DE8.E; break; case 0x54: // MOV D,H DE.DE8.D = HL.HL8.H; break; case 0x55: // MOV D,L DE.DE8.D = HL.HL8.L; break; case 0x56: // MOV D,M DE.DE8.D = cpu8080_readByte(HL.HL16); break; case 0x57: // MOV D,A DE.DE8.D = A; break; case 0x58: // MOV E,B DE.DE8.E = BC.BC8.B; break; case 0x59: // MOV E,C DE.DE8.E = BC.BC8.C; break; case 0x5a: // MOV E,D DE.DE8.E = DE.DE8.D; break; case 0x5c: // MOV E,H DE.DE8.E = HL.HL8.H; break; case 0x5d: // MOV E,L DE.DE8.E = HL.HL8.L; break; case 0x5e: // MOV E,M DE.DE8.E = cpu8080_readByte(HL.HL16); break; case 0x5f: // MOV E,A DE.DE8.E = A; break; case 0x60: // MOV H,B HL.HL8.H = BC.BC8.B; break; case 0x61: // MOV H,C HL.HL8.H = BC.BC8.C; break; case 0x62: // MOV H,D HL.HL8.H = DE.DE8.D; break; case 0x63: // MOV H,E HL.HL8.H = DE.DE8.E; break; case 0x65: // MOV H,L HL.HL8.H = HL.HL8.L; break; case 0x66: // MOV H,M HL.HL8.H = cpu8080_readByte(HL.HL16); break; case 0x67: // MOV H,A HL.HL8.H = A; break; case 0x68: // MOV L,B HL.HL8.L = BC.BC8.B; break; case 0x69: // MOV L,C HL.HL8.L = BC.BC8.C; break; case 0x6a: // MOV L,D HL.HL8.L = DE.DE8.D; break; case 0x6b: // MOV L,E HL.HL8.L = DE.DE8.E; break; case 0x6c: // MOV L,H HL.HL8.L = HL.HL8.H; break; case 0x6e: // MOV L,M HL.HL8.L = cpu8080_readByte(HL.HL16); break; case 0x6f: // MOV L,A HL.HL8.L = A; break; case 0x70: // MOV M,B cpu8080_writeByte(HL.HL16, BC.BC8.B); break; case 0x71: // MOV M,C cpu8080_writeByte(HL.HL16, BC.BC8.C); break; case 0x72: // MOV M,D cpu8080_writeByte(HL.HL16, DE.DE8.D); break; case 0x73: // MOV M,E cpu8080_writeByte(HL.HL16, DE.DE8.E); break; case 0x74: // MOV M,H cpu8080_writeByte(HL.HL16, HL.HL8.H); break; case 0x75: // MOV M,L cpu8080_writeByte(HL.HL16, HL.HL8.L); break; case 0x77: // MOV M,A cpu8080_writeByte(HL.HL16, A); break; case 0x78: // MOV A,B A = BC.BC8.B; break; case 0x79: // MOV A,C A = BC.BC8.C; break; case 0x7a: // MOV A,D A = DE.DE8.D; break; case 0x7b: // MOV A,E A = DE.DE8.E; break; case 0x7c: // MOV A,H A = HL.HL8.H; break; case 0x7d: // MOV A,L A = HL.HL8.L; break; case 0x7e: // MOV A,M A = cpu8080_readByte(HL.HL16); break; case 0x80: // ADD A,B cpu8080_addByte(BC.BC8.B); break; case 0x81: // ADD A,C cpu8080_addByte(BC.BC8.C); break; case 0x82: // ADD A,D cpu8080_addByte(DE.DE8.D); break; case 0x83: // ADD A,E cpu8080_addByte(DE.DE8.E); break; case 0x84: // ADD A,H cpu8080_addByte(HL.HL8.H); break; case 0x85: // ADD A,L cpu8080_addByte(HL.HL8.L); break; case 0x86: // ADD A,M cpu8080_addByte(cpu8080_readByte(HL.HL16)); break; case 0x87: // ADD A,A cpu8080_addByte(A); break; case 0x88: // ADC A,B cpu8080_addByteCarry(BC.BC8.B, F & CarryFlag); break; case 0x89: // ADC A,C cpu8080_addByteCarry(BC.BC8.C, F & CarryFlag); break; case 0x8a: // ADC A,D cpu8080_addByteCarry(DE.DE8.D, F & CarryFlag); break; case 0x8b: // ADC A,E cpu8080_addByteCarry(DE.DE8.E, F & CarryFlag); break; case 0x8c: // ADC A,H cpu8080_addByteCarry(HL.HL8.H, F & CarryFlag); break; case 0x8d: // ADC A,L cpu8080_addByteCarry(HL.HL8.L, F & CarryFlag); break; case 0x8e: // ADC A,M cpu8080_addByteCarry(cpu8080_readByte(HL.HL16), F & CarryFlag); break; case 0x8f: // ADC A,A cpu8080_addByteCarry(A, F & CarryFlag); break; case 0x90: // SUB B A = cpu8080_subByte(BC.BC8.B); break; case 0x91: // SUB C A = cpu8080_subByte(BC.BC8.C); break; case 0x92: // SUB D A = cpu8080_subByte(DE.DE8.D); break; case 0x93: // SUB E A = cpu8080_subByte(DE.DE8.E); break; case 0x94: // SUB H A = cpu8080_subByte(HL.HL8.H); break; case 0x95: // SUB L A = cpu8080_subByte(HL.HL8.L); break; case 0x96: // SUB M A = cpu8080_subByte(cpu8080_readByte(HL.HL16)); break; case 0x97: // SUB A A = cpu8080_subByte(A); break; case 0x98: // SBB A,B A = cpu8080_subByteCarry(BC.BC8.B, F & CarryFlag); break; case 0x99: // SBB A,C A = cpu8080_subByteCarry(BC.BC8.C, F & CarryFlag); break; case 0x9a: // SBB A,D A = cpu8080_subByteCarry(DE.DE8.D, F & CarryFlag); break; case 0x9b: // SBB A,E A = cpu8080_subByteCarry(DE.DE8.E, F & CarryFlag); break; case 0x9c: // SBB A,H A = cpu8080_subByteCarry(HL.HL8.H, F & CarryFlag); break; case 0x9d: // SBB A,L A = cpu8080_subByteCarry(HL.HL8.L, F & CarryFlag); break; case 0x9e: // SBB A,M A = cpu8080_subByteCarry(cpu8080_readByte(HL.HL16), F & CarryFlag); break; case 0x9f: // SBB A,A A = cpu8080_subByteCarry(A, F & CarryFlag); break; case 0xa0: // ANA B A &= BC.BC8.B; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa1: // ANA C A &= BC.BC8.C; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa2: // ANA D A &= DE.DE8.D; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa3: // ANA E A &= DE.DE8.E; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa4: // ANA H A &= HL.HL8.H; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa5: // ANA L A &= HL.HL8.L; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa6: // ANA M A &= cpu8080_readByte(HL.HL16); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa7: // ANA A F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa8: // XRA B A ^= BC.BC8.B; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xa9: // XRA C A ^= BC.BC8.C; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xaa: // XRA D A ^= DE.DE8.D; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xab: // XRA E A ^= DE.DE8.E; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xac: // XRA H A ^= HL.HL8.H; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xad: // XRA L A ^= HL.HL8.L; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xae: // XRA M A ^= cpu8080_readByte(HL.HL16); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xaf: // XRA A A = 0; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb0: // ORA B A |= BC.BC8.B; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb1: // ORA C A |= BC.BC8.C; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb2: // ORA D A |= DE.DE8.D; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb3: // ORA E A |= DE.DE8.E; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb4: // ORA H A |= HL.HL8.H; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb5: // ORA L A |= HL.HL8.L; F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb6: // ORA M A |= cpu8080_readByte(HL.HL16); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb7: // ORA A F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xb8: // CMP B cpu8080_subByte(BC.BC8.B); break; case 0xb9: // CMP C cpu8080_subByte(BC.BC8.C); break; case 0xba: // CMP D cpu8080_subByte(DE.DE8.D); break; case 0xbb: // CMP E cpu8080_subByte(DE.DE8.E); break; case 0xbc: // CMP H cpu8080_subByte(HL.HL8.H); break; case 0xbd: // CMP L cpu8080_subByte(HL.HL8.L); break; case 0xbe: // CMP M cpu8080_subByte( cpu8080_readByte(HL.HL16)); break; case 0xbf: // CMP A cpu8080_subByte(A); break; case 0xc1: // POP B BC.BC8.C = invadersRAM[(SP_++) & 0x3ff]; BC.BC8.B = invadersRAM[(SP_++) & 0x3ff]; break; case 0xc5: // PUSH B invadersRAM[(--SP_) & 0x3ff] = BC.BC8.B; invadersRAM[(--SP_ )& 0x3ff] = BC.BC8.C; break; case 0xd1: // POP D DE.DE8.E = invadersRAM[(SP_++) & 0x3ff]; DE.DE8.D = invadersRAM[(SP_++) & 0x3ff]; break; case 0xd5: // PUSH D invadersRAM[(--SP_) & 0x3ff] = DE.DE8.D; invadersRAM[(--SP_) & 0x3ff] = DE.DE8.E; break; case 0xe1: // POP H HL.HL8.L = invadersRAM[(SP_++) & 0x3ff]; HL.HL8.H = invadersRAM[(SP_++) & 0x3ff]; break; case 0xe5: // PUSH H invadersRAM[(--SP_) & 0x3ff] = HL.HL8.H; invadersRAM[(--SP_) & 0x3ff] = HL.HL8.L; break; case 0xf1: // POP PSW F = invadersRAM[(SP_++) & 0x3ff]; A = invadersRAM[(SP_++) & 0x3ff]; break; case 0xf5: // PUSH PSW invadersRAM[(--SP_) & 0x3ff] = A; invadersRAM[(--SP_) & 0x3ff] = F; break; case 0x04: // INR B BC.BC8.B = cpu8080_incByte(BC.BC8.B); break; case 0x05: // DCR B BC.BC8.B = cpu8080_decByte(BC.BC8.B); break; case 0x06: // MVI B,n BC.BC8.B = cpu8080_readByte(PC.PC16++); break; case 0x0c: // INR C BC.BC8.C = cpu8080_incByte(BC.BC8.C); break; case 0x0d: // DCR C BC.BC8.C = cpu8080_decByte(BC.BC8.C); break; case 0x0e: // MVI C,n BC.BC8.C = cpu8080_readByte(PC.PC16++); break; case 0x14: // INR D DE.DE8.D = cpu8080_incByte(DE.DE8.D); break; case 0x15: // DCR D DE.DE8.D = cpu8080_decByte(DE.DE8.D); break; case 0x16: // MVI D,n DE.DE8.D = cpu8080_readByte(PC.PC16++); break; case 0x1c: // INR E DE.DE8.E = cpu8080_incByte(DE.DE8.E); break; case 0x1d: // DCR E DE.DE8.E = cpu8080_decByte(DE.DE8.E); break; case 0x1e: // MVI E,n DE.DE8.E = cpu8080_readByte(PC.PC16++); break; case 0x24: // INR H HL.HL8.H = cpu8080_incByte(HL.HL8.H); break; case 0x25: // DCR H HL.HL8.H = cpu8080_decByte(HL.HL8.H); break; case 0x26: // MVI H,n HL.HL8.H = cpu8080_readByte(PC.PC16++); break; case 0x2c: // INR L HL.HL8.L = cpu8080_incByte(HL.HL8.L); break; case 0x2d: // DCR L HL.HL8.L = cpu8080_decByte(HL.HL8.L); break; case 0x2e: // MVI L,n HL.HL8.L = cpu8080_readByte(PC.PC16++); break; case 0x34: // INR M cpu8080_writeByte(HL.HL16, cpu8080_incByte(cpu8080_readByte(HL.HL16) ) ); break; case 0x35: // DEX M cpu8080_writeByte(HL.HL16, cpu8080_decByte(cpu8080_readByte(HL.HL16) ) ); break; case 0x36: // MVI M,n cpu8080_writeByte(HL.HL16, cpu8080_readByte(PC.PC16++) ); break; case 0x3c: // INR A A = cpu8080_incByte(A); break; case 0x3d: // DCR A A = cpu8080_decByte(A); break; case 0x3e: // MVI A,n A = cpu8080_readByte(PC.PC16++); break; case 0x03: // INX B if(++BC.BC8.C == 0) ++BC.BC8.B; break; case 0x13: // INX D if(++DE.DE8.E == 0) ++DE.DE8.D; break; case 0x23: // INX H if(++HL.HL8.L == 0) ++HL.HL8.H; break; case 0x33: // INX SP SP_ ++; break; case 0x0b: // DEX B if(BC.BC8.C-- == 0) --BC.BC8.B; break; case 0x1b: // DEX D if(DE.DE8.E-- == 0) --DE.DE8.D; break; case 0x2b: // DEX H if(HL.HL8.L-- == 0) --HL.HL8.H; break; case 0x3b: // DEX SP SP_--; break; case 0x0a: // LDAX B A = cpu8080_readByte(BC.BC16); break; case 0x1a: // LDAX D A = cpu8080_readByte(DE.DE16); break; case 0x02: // STAX B cpu8080_writeByte(BC.BC16, A); break; case 0x12: // STAX D cpu8080_writeByte(DE.DE16, A); break; case 0x01: // LXI B,nn // BC.BC16 = cpu8080_readWord(PC.PC16); if(test_ROM_enable) BC.BC16 = pgm_read_word_near(&invaders_rom_H_TEST[PC.PC16]); // Rom H_TEST 0000-07FF else BC.BC16 = pgm_read_word_near(&invaders_rom_HGFE[PC.PC16]); // Rom H 0000-1FFF PC.PC16+=2; break; case 0x07: // RLC if(A & 0x80) { A = (A << 1) | 0x01; F &= ~(AuxCarryFlag); F |= CarryFlag; } else { A = (A << 1); F &= ~(AuxCarryFlag | CarryFlag); } break; case 0x09: // DAD B hl_tmp = HL.HL16; bit32_tmp = hl_tmp + BC.BC16; F &= (SignFlag | ZeroFlag | ParityFlag); if(bit32_tmp & 0xFFFF0000) F |= CarryFlag; if( ((hl_tmp & 0xFFF) + (BC.BC16 & 0xFFF)) > 0xFFF ) F |= AuxCarryFlag; HL.HL16 = bit32_tmp; break; case 0x0f: // RRC if(A & 0x01) { A = (A >> 1) | 0x80; F &= ~(0x02 | AuxCarryFlag); F |= CarryFlag; } else { A = (A >> 1); F &= ~(0x02 | AuxCarryFlag | CarryFlag); } break; case 0x11: // LXI D,nn DE.DE16 = cpu8080_readWord(PC.PC16); PC.PC16+=2; break; case 0x17: // RAL a_tmp = A; A <<= 1; if(F & CarryFlag) A |= 0x01; F &= ~(AuxCarryFlag | CarryFlag); if(a_tmp & 0x80) F |= CarryFlag; break; case 0x19: // DAD D hl_tmp = HL.HL16; de_tmp = DE.DE16; bit32_tmp = hl_tmp + de_tmp; F &= (SignFlag | ZeroFlag | ParityFlag); if( bit32_tmp > 0xFFFF ) F |= CarryFlag; if( ((hl_tmp & 0xFFF) + (de_tmp & 0xFFF)) > 0xFFF ) F |= AuxCarryFlag; HL.HL16 = bit32_tmp; break; case 0x1f: // RAR a_tmp = A; A >>= 1; if(F &CarryFlag) A |= 0x80; F &= ~(AuxCarryFlag |CarryFlag); if(a_tmp & 0x01) F |=CarryFlag; break; case 0x21: // LXI H,nn HL.HL16 = cpu8080_readWord(PC.PC16); PC.PC16+=2; break; case 0x22: // SHLD pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; cpu8080_writeByte(pc_tmp, HL.HL8.L); cpu8080_writeByte(pc_tmp+1, HL.HL8.H); break; case 0x27: // DAA if( ((A & 0x0F) > 9) || (F & AuxCarryFlag) ) { A += 0x06; F |= AuxCarryFlag; } else F &= ~AuxCarryFlag; if( (A > 0x9F) || (F &CarryFlag) ) { A += 0x60; F |=CarryFlag; } else F &= ~CarryFlag; F = (F & ~(ParityFlag | SignFlag | ZeroFlag)) | pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0x29: // DAD H hl_tmp = HL.HL16; bit16_tmp = hl_tmp; bit32_tmp = hl_tmp + bit16_tmp; F &= (SignFlag | ZeroFlag | ParityFlag); if( bit32_tmp > 0xFFFF ) F |=CarryFlag; if( ((hl_tmp & 0xFFF) + (bit16_tmp & 0xFFF)) > 0xFFF ) F |= AuxCarryFlag; HL.HL16 = bit32_tmp; break; case 0x2a: // LHLD pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; HL.HL8.L = cpu8080_readByte(pc_tmp); HL.HL8.H = cpu8080_readByte(pc_tmp+1); break; case 0x2f: // CMA A ^= 0xFF; F |= 0x02 | AuxCarryFlag; break; case 0x31: // LXI SP SP_ = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; break; case 0x32: // STA nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; cpu8080_writeByte(pc_tmp, A); break; case 0x37: // STC F |=CarryFlag; break; case 0x39: // DAD SP hl_tmp = HL.HL16; bit32_tmp = hl_tmp + SP_; F &= (SignFlag | ZeroFlag | ParityFlag); if( bit32_tmp > 0xFFFF ) F |=CarryFlag; if( ((hl_tmp & 0xFFF) + (SP_ & 0xFFF)) > 0xFFF ) F |= AuxCarryFlag; HL.HL16 = bit32_tmp; break; case 0x3a: //LDA // pc_tmp = cpu8080_readWord(PC.PC16); if(test_ROM_enable) pc_tmp = pgm_read_word_near(&invaders_rom_H_TEST[PC.PC16]); // Rom H_TEST 0000-07FF else pc_tmp = pgm_read_word_near(&invaders_rom_HGFE[PC.PC16]); // Rom H 0000-1FFF PC.PC16++; PC.PC16++; A = cpu8080_readByte(pc_tmp); break; case 0x3f: // CMC F ^=CarryFlag; break; case 0xc0: // RNZ if(! (F & ZeroFlag) ) cpu8080_retFromSub(); break; case 0xc2: // JNZ nn if(F & ZeroFlag) { PC.PC16++; PC.PC16++; } else { if(test_ROM_enable) PC.PC16 = pgm_read_word_near(&invaders_rom_H_TEST[PC.PC16]); // Rom H_TEST 0000-07FF else PC.PC16 = pgm_read_word_near(&invaders_rom_HGFE[PC.PC16]); // Rom H 0000-1FFF } break; case 0xc3: // JMP nn PC.PC16 = cpu8080_readWord(PC.PC16); break; case 0xc4: // CNZ nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(! (F & ZeroFlag) ) cpu8080_callSub(pc_tmp); break; case 0xc6: // ADI n cpu8080_addByte(cpu8080_readByte(PC.PC16++)); break; case 0xc8: // RZ if(F & ZeroFlag) cpu8080_retFromSub(); break; case 0xc9: // RET cpu8080_retFromSub(); break; case 0xca: // JZ nn if(F & ZeroFlag) { if(test_ROM_enable) PC.PC16 = pgm_read_word_near(&invaders_rom_H_TEST[PC.PC16]); // Rom H_TEST 0000-07FF else PC.PC16 = pgm_read_word_near(&invaders_rom_HGFE[PC.PC16]); // Rom H 0000-1FFF } else { PC.PC16++; PC.PC16++; } break; case 0xcc: // CZ nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(F & ZeroFlag) cpu8080_callSub(pc_tmp); break; case 0xcd: // CALL nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; cpu8080_callSub(pc_tmp); break; case 0xce: // ACI n cpu8080_addByteCarry(cpu8080_readByte(PC.PC16++), F & CarryFlag); break; case 0xd0: // RNC if(! (F & CarryFlag) ) cpu8080_retFromSub(); break; case 0xd2: // JNC nn if(F & CarryFlag) { PC.PC16++; PC.PC16++; } else { PC.PC16 = cpu8080_readWord(PC.PC16); } break; case 0xd4: // CNC nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(! (F & CarryFlag) ) cpu8080_callSub(pc_tmp); break; case 0xd6: // SUI n A = cpu8080_subByte(cpu8080_readByte(PC.PC16++)); break; case 0xd8: // RC if(F & CarryFlag) cpu8080_retFromSub(); break; case 0xda: // JC nn if(F & CarryFlag) PC.PC16 = cpu8080_readWord(PC.PC16); else { PC.PC16++; PC.PC16++; } break; case 0xdc: // CC nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(F & CarryFlag) cpu8080_callSub(pc_tmp); break; case 0xde: // SBI n A = cpu8080_subByteCarry(cpu8080_readByte(PC.PC16++), F & CarryFlag); break; case 0xe0: // RPO if(! (F & ParityFlag) ) cpu8080_retFromSub(); break; case 0xe2: // JPO nn if(F & ParityFlag) { PC.PC16++; PC.PC16++; } else { PC.PC16 = cpu8080_readWord(PC.PC16); } break; case 0xe3: // XTHL a_tmp = invadersRAM[SP_ & 0x3ff ]; invadersRAM[SP_ & 0x3ff ] = HL.HL8.L; HL.HL8.L = a_tmp; a_tmp = invadersRAM[(SP_+1) & 0x3ff]; invadersRAM[(SP_+1) & 0x3ff] = HL.HL8.H; HL.HL8.H = a_tmp; break; case 0xe4: // CPO nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(! (F & ParityFlag) ) cpu8080_callSub(pc_tmp); break; case 0xe6: // ANI n A &= cpu8080_readByte(PC.PC16++); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xe8: // RPE if(F & ParityFlag) cpu8080_retFromSub(); break; case 0xe9: // PCHL PC.PC16 = HL.HL16; break; case 0xea: // JPE nn if(F & ParityFlag) PC.PC16 = cpu8080_readWord(PC.PC16); else { PC.PC16++; PC.PC16++; } break; case 0xeb: // XCHG de_tmp=DE.DE16; DE.DE16=HL.HL16; HL.HL16=de_tmp; break; case 0xec: // CPE nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(F & ParityFlag) cpu8080_callSub(pc_tmp); break; case 0xee: // XRI n A ^= cpu8080_readByte(PC.PC16++); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xf0: // RP if(! (F & SignFlag) ) cpu8080_retFromSub(); break; case 0xf2: // JP nn if(F & SignFlag) { PC.PC16++; PC.PC16++; } else PC.PC16 = cpu8080_readWord(PC.PC16); break; case 0xf4: // CALL P,nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(! (F & SignFlag) ) cpu8080_callSub(pc_tmp); break; case 0xf6: // ORI n A |= cpu8080_readByte(PC.PC16++); F = pgm_read_byte_near(&cpu8080_Flag_Zero_Signed_Parity[A]); break; case 0xf8: // RM if(F & SignFlag ) cpu8080_retFromSub(); break; case 0xf9: // SPHL SP_ = HL.HL16; break; case 0xfa: // JM nn if(F & SignFlag) PC.PC16 = cpu8080_readWord(PC.PC16); else { PC.PC16++; PC.PC16++; } break; case 0xfc: // CM nn pc_tmp = cpu8080_readWord(PC.PC16); PC.PC16++; PC.PC16++; if(F & SignFlag ) cpu8080_callSub(pc_tmp); break; case 0xfe: // CPI n cpu8080_subByte(cpu8080_readByte(PC.PC16++)); break; } } while(!vblank); vblank = false; // Call the Interrupt at 0x08 and 0x10 vector if(InterruptFlag) { InterruptFlag = false; // Disable intterupt flag if(cpu8080_in_halt) { PC.PC16++; cpu8080_in_halt = false; } invadersRAM[(--SP_) & 0x3ff] = PC.PC16 >> 8; invadersRAM[(--SP_) & 0x3ff] = PC.PC16; if(intState) { PC.PC16 = 0x08; intState = false; } else { PC.PC16 = 0x10; intState = true; } } } // Invaders board function void invadersReset(void) { // Reset the CPU and the other machine settings cpu8080_reset(); port2_out = 0x00; port3_out = 0x00; port4_lsb = 0x00; port4_msb = 0x00; port5_out = 0x00; } // This function has been inlined in LDA, JNZ, JZ uint16_t cpu8080_readWord( uint16_t address ) { if(test_ROM_enable) return pgm_read_word_near(&invaders_rom_H_TEST[address]); // Rom H_TEST 0000-07FF else return pgm_read_word_near(&invaders_rom_HGFE[address]); // Rom H 0000-1FFF } uint8_t cpu8080_readByte( uint16_t address ) { if(address < 0x2000) { if (test_ROM_enable) return pgm_read_word_near(&invaders_rom_H_TEST[address]); // Rom H_TEST 0000-07FF else return pgm_read_word_near(&invaders_rom_HGFE[address]); // Rom H 0000-1FFF } else if(address < 0x2400) return invadersRAM[address & 0x3FF]; // RAM 1024K memory space uint8_t d = 0x00; uint8_t xL,yL,xL1,xH1; uint16_t x,y; address -= 0x2400; x = SCREEN_CENTER_X + ((address & 0x001f) << 3); y = SCREEN_CENTER_Y + ( address >> 5); xL = (uint8_t) (x >> 8); yL = (uint8_t) (y >> 8); xL1 = (x+7) >> 8; xH1 = (uint8_t) (x+7); PORTB &= ~(TFT_DC_PIN | TFT_CS_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(ILI9341_COLADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(yL); SPI.transfer((uint8_t)y); SPI.transfer(yL); SPI.transfer((uint8_t)y); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(ILI9341_PAGEADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(xL); SPI.transfer((uint8_t)x); SPI.transfer(xL1); SPI.transfer(xH1); PORTB &= ~(TFT_DC_PIN | TFT_CS_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(ILI9341_MEMORYREAD); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(0x00); // Consumes dummy byte if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d = 0x01; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x02; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x04; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x08; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x10; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x20; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x40; if(SPI.transfer(0x00) | SPI.transfer(0x00) | SPI.transfer(0x00)) d |= 0x80; PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); return d; } void cpu8080_writeByte( uint16_t address, uint8_t b ) { uint8_t xL,yL,xL1,xH1; uint16_t x,y,xc,yc; uint8_t col1=0xFF, col2=0xFF; if(address < 0x2400) invadersRAM[address & 0x3FF] = b; else { address -= 0x2400; xc = ((address & 0x001f) << 3); // Keep those coordinates to calculate colors yc = ( address >> 5); // Keep those coordinates to calculate colors x = xc + SCREEN_CENTER_X; y = yc + SCREEN_CENTER_Y; xL = (uint8_t) (x >> 8); yL = (uint8_t) (y >> 8); xL1 = (x+7) >> 8; xH1 = (uint8_t) (x+7); PORTB &= ~(TFT_DC_PIN | TFT_CS_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(ILI9341_COLADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(yL); SPI.transfer((uint8_t)y); SPI.transfer(yL); SPI.transfer((uint8_t)y); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC, LOW); SPI.transfer(ILI9341_PAGEADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(xL); SPI.transfer((uint8_t)x); SPI.transfer(xL1); SPI.transfer(xH1); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC|TFT0_CS, LOW); SPI.transfer(ILI9341_MEMORYWRITE); // Handle colors switch(color_version) { case COL_VERSION_SV: // No colors col1 = 0xFF; // White col2 = 0xFF; break; case COL_VERSION_TV: // TV and and Midway versions if(xc < 72) { if((xc >= 16) || ((yc>=24) && (yc<136))) { col1 = 0x07; col2 = 0xE0; // Green } } else if(xc >= 192) { if(xc < 224) { col1 = 0x00; col2 = 0x1F; // Red } } break; // This case can be highly optimized case COL_VERSION_CV: // CV Version col1 = 0xFF; col2 = 0xE0; // Cyan // Purple and Green moved on top because are more frequent if((xc >= 96) && (xc < 128)) { col1 = 0xF8; col2 = 0x1F; // Purple break; } if((xc >= 160) && (xc < 192)) { col1 = 0x07; col2 = 0xE0; // Green break; } if((xc >= 8) && (xc < 16)) { if((yc>=136) && (yc<192)) { col1 = 0xF8; col2 = 0x1F; // Purple } } if((xc >= 16) && (xc < 24)) { col1 = 0x00; col2 = 0x1F; // Red } if((xc >= 40) && (xc < 64)) { col1 = 0x00; col2 = 0x1F; // Red } if((xc >= 64) && (xc < 96)) { col1 = 0x07; col2 = 0xFF; // Yellow } if((xc >= 192) && (xc < 208)) { col1 = 0xF8; col2 = 0x00; // Blue } if((xc >= 208) && (xc < 216)) { col1 = 0xF8; col2 = 0x1F; // Purple } if((xc >= 216) && (xc < 224)) { col1 = 0x00; col2 = 0x1F; // Red } if((xc >= 224) && (xc < 232)) { if((yc>=0) && (yc<72)) { col1 = 0xFF; col2 = 0xFF; // White } if((yc>=72) && (yc<152)) { col1 = 0xF8; col2 = 0x00; // Blue } if((yc>=152) && (yc<224)) { col1 = 0x07; col2 = 0xFF; // Yellow } } if((xc >= 232) && (xc < 248)) { if((yc>=72) && (yc<152)) { col1 = 0xF8; col2 = 0x00; // Blue } if((yc>=152) && (yc<224)) { col1 = 0x07; col2 = 0xFF; // Yellow } } break; case COL_VERSION_GP: switch(random(6)) { case 0: col1 = 0xF8; col2 = 0x00; break; // BLUE case 1: col1 = 0x07; col2 = 0xE0; break; // GREEN case 2: col1 = 0x00; col2 = 0x1F; break; // RED case 3: col1 = 0x07; col2 = 0xFF; break; // YELLOW case 4: col1 = 0xF8; col2 = 0x1F; break; // PURPLE case 5: col1 = 0xFF; col2 = 0xE0; break; // CYAN } break; } PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); if(b & 0x01) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x02) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x04) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x08) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x10) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x20) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x40) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } if(b & 0x80) { SPI.transfer(col1);SPI.transfer(col2); } else { SPI.transfer(0x00);SPI.transfer(0x00); } PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } } uint8_t cpu8080_readPort(uint8_t port) { uint8_t data = 0; switch(port) { #ifdef DEBUG case 0: Serial.print(F("Read port: ")); Serial.println(port,HEX); break; #endif case 1: data = 0x08; // Bit3 is always set, Bit 7 is not connected // BTN_CREDIT if(PINC & 0b00100000 ) data |= 0x01; // BTN_2P_START if(PINC & 0b00000010) data |= 0x02; // BTN_1P_START if(PINC & 0b00000001) data |= 0x04; // BTN_FIRE1 - Player 1 if(PINC & 0b00000100) data |= 0x10; // BTN_LEFT1 - Player 1 if(PINC & 0b00001000) data |= 0x20; // BTN_RIGHT1 - Player 1 if(PINC & 0b00010000) data |= 0x40; break; case 2: data = port2_in & 0x8F; // Bit 0,1 ships, 2 tilt, 3 extraship, 7 coin in demo screen if(tilt_enable) data |= 0x04; // TILT contact // Force the use of Player1 controls for Player2 // BTN_FIRE1 if(PINC & 0b00000100) data |= 0x10; // BTN_LEFT1 if(PINC & 0b00001000) data |= 0x20; // BTN_RIGHT1 if(PINC & 0b00010000) data |= 0x40; break; case 3: // Shift register data = (uint8_t)((((port4_msb << 8) | port4_lsb) << port2_out) >> 8); break; } return data; } void cpu8080_writePort(uint8_t addr, uint8_t data) { switch(addr) { case 2: // Shift register amount port2_out = data; break; case 3: // Port 3 controls some sounds if((data & SND_SHOT) && !(port3_out & SND_SHOT)) ShotSoundActive = true; if((data & SND_FLASH) && !(port3_out & SND_FLASH)) FlashSoundActive = true; if((data & SND_INV_DIE) && !(port3_out & SND_INV_DIE)) InvadersDieSoundActivate = true; if((data & SND_EXT_PLAY) && !(port3_out & SND_EXT_PLAY)) ExtendedPlaySoundActivate = true; port3_out = data; break; case 4: // Shift register data port4_lsb = port4_msb; port4_msb = data; break; case 5: // Bit 0x20 is Cocktail mode control to flip screen port5_out = data; break; // case 6: // Watchdog (unused) // break; } } /*** DISPLAY FUNCTIONS ***************************************************/ // Send a command to SPI device void sendCMD(uint8_t index) { // Set DC for command PORTB &= ~(TFT_DC_PIN | TFT_CS_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(index); PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } // Send data to SPI device void sendData8(uint8_t index) { // Set DC for data PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); PORTB &= ~(TFT_CS_PIN); // digitalWrite(TFT_CS, LOW); SPI.transfer(index); PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } void sendData16(uint8_t data1, uint8_t data2) { // Set DC for data PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); PORTB &= ~(TFT_CS_PIN); // digitalWrite(TFT_CS, LOW); SPI.transfer(data1); SPI.transfer(data2); PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } // Read register/parameter from SPI device uint8_t readReg(uint8_t addr, uint8_t param) { uint8_t data=0; PORTB &= ~(TFT_CS_PIN | TFT_DC_PIN); // digitalWrite(TFT_CS | TFT_DC, LOW); SPI.transfer(0xD9); // Extended magic command PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(0x10+param); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC, LOW); SPI.transfer(addr); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); data = SPI.transfer(0x00); PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); return data; } void SetMemoryRect(uint16_t x1, uint16_t y1, uint16_t size_x, uint16_t size_y) { PORTB &= ~(TFT_DC_PIN | TFT_CS_PIN); // digitalWrite(TFT_DC|TFT_CS, LOW); SPI.transfer(ILI9341_COLADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer((uint8_t) (x1 >> 8)); SPI.transfer((uint8_t) x1); SPI.transfer((uint8_t)((x1+size_x-1) >> 8)); SPI.transfer((uint8_t) (x1+size_x-1)); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC, LOW); SPI.transfer(ILI9341_PAGEADDRSET); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer((uint8_t) (y1 >> 8)); SPI.transfer((uint8_t) y1); SPI.transfer((uint8_t)((y1+size_y-1) >> 8)); SPI.transfer((uint8_t) (y1+size_y-1)); } void FillRect(uint16_t x1, uint16_t y1, uint16_t size_x, uint16_t size_y, uint16_t color) { SetMemoryRect(x1,y1,size_x,size_y); sendCMD(ILI9341_MEMORYWRITE); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); PORTB &= ~(TFT_CS_PIN); // digitalWrite(TFT_CS, LOW); for(uint16_t x=x1; x<x1+size_x; x++) for(uint16_t y=y1; y<y1+size_y; y++) { // BGR bbbbbggg gggrrrrr SPI.transfer(color >> 8); SPI.transfer(color); } PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } void DrawPixel(int16_t x, int16_t y, uint16_t color) { SetMemoryRect(x,y,1,1); PORTB &= ~(TFT_DC_PIN); // digitalWrite(TFT_DC|TFT0_CS, LOW); SPI.transfer(ILI9341_MEMORYWRITE); PORTB |= TFT_DC_PIN; // digitalWrite(TFT_DC,HIGH); SPI.transfer(color >> 8); SPI.transfer(color); PORTB |= TFT_CS_PIN; // digitalWrite(TFT_CS, HIGH); } // Draw a character void DrawChar(int16_t x, int16_t y, uint8_t c, uint16_t color, uint16_t bg, uint8_t stroke) { if(c < ' ' || c > '~') c = 0; else c -= ' '; for (int8_t i=0; i<6; i++ ) { uint8_t line; if (i == 5) line = 0x0; else line = pgm_read_byte_near(&font[c*5+i]); for(int8_t j = 0; j<8; j++) { if(line & 0x80) { if(stroke == 1) // default stroke DrawPixel(x+i, y+j, color); else // big stroke FillRect(x+(i*stroke), y+(j*stroke), stroke, stroke, color); } else if (bg != color) { if (stroke == 1) // default stroke DrawPixel(x+i, y+j, bg); else // big stroke FillRect(x+i*stroke, y+j*stroke, stroke, stroke, bg); } line <<= 1; } } } void WriteStringRam(uint16_t x, uint16_t y, uint8_t *str, uint16_t color, uint16_t bg, uint8_t stroke) { while(*str != 0) { DrawChar(x, y, *str++, color, bg, stroke); x += 6*stroke; } } void WriteStringFlash(uint16_t x, uint16_t y, const __FlashStringHelper* str, uint16_t color, uint16_t bg, uint8_t stroke) { PGM_P p = reinterpret_cast<PGM_P>(str); while(pgm_read_byte(p) != 0) { DrawChar(x, y, pgm_read_byte(p), color, bg, stroke); x += 6*stroke; p++; } } // Draw a martian starting from a 90 degrees rotated array (16x8) void DrawMartian(uint8_t *martian, uint16_t x, uint16_t y, uint16_t color) { uint8_t i,z; for(i=0;i<16;i++) { for(z=0;z<8;z++) { if(pgm_read_byte(martian+i) & (1<<z)) FillRect(x+i*2, y+z*2, 2, 2, color); else FillRect(x+i*2, y+z*2, 2, 2, COL_BLACK); } } } // Init ILI9341 TFT SPI Display void initDisplay (void){ sendCMD(ILI9341_SOFTRESET); delay(5); sendCMD(ILI9341_NOP); // Execute a NOP, just because it's fun :-) sendCMD(ILI9341_DISPLAYOFF); sendCMD(ILI9341_POWERCONTROL1); sendData8(0x23); // 00100011 4.60 Volt sendCMD(ILI9341_POWERCONTROL2); sendData8(0x00); // 00010000 VCI x 4 (Was 0x10) sendCMD(ILI9341_VCOMCONTROL1); sendData16(0x2B, 0x2B); // 00101011 VCOMH voltage 4.875, VCOML 1.425 sendCMD(ILI9341_VCOMCONTROL2); sendData8(0xC0); // 11000000 Allow VCOMH/VCOML adjustment sendCMD(ILI9341_MEMCONTROL); sendData8(ILI9341_MADCTL_RGB); // 0=RGB color filter panel sendCMD(ILI9341_PIXELFORMAT); sendData8(0x55); // 01010101 16 bit, 16 bit sendCMD(ILI9341_FRAMECONTROL); sendData16(0x00, 0x1B); // 00000000, 00011011 fosc, 70 hz frame rate sendCMD(ILI9341_ENTRYMODE); sendData8(0x07); // 00000111 GAS lvd, Normal Display sendCMD(ILI9341_SLEEPOUT); delay(5); sendCMD(ILI9341_DISPLAYON); } /*** SERVICES FUNCTIONS ***************************************************/ uint8_t PrintWelcome() { uint16_t gradient = 0; bool dip_loop = true; // Wait for FIRE for DIP settings uint32_t anim_delay = 0; // Delay for animation uint8_t enter_dip_settings = 0; // Flag enter in DIP settings uint8_t dip_time = 28; // Time to enter in DIP settings // Welcome screen FillRect(0, 272, 240, 48, COL_WHITE); // White (3 rows) WriteStringFlash( 0,288,F(" SPACE ArduINVADERS"),COL_BLACK,COL_WHITE,2); WriteStringFlash( 0,256,F("HARDWARE EMULATOR FOR ORIGINAL BYTECODE."), COL_GREEN, COL_BLACK, 1); WriteStringFlash(114,240,F("BY"), COL_WHITE, COL_BLACK, 1); WriteStringFlash( 12,230,F("M.Damiani, G.C.Peritore V.Signorelli"), COL_WHITE, COL_BLACK, 1); for(gradient=0; gradient<32; gradient++) { FillRect(0, 128+gradient, 240, 2, gradient); // RED FillRect(0, 32+gradient, 240, 2, gradient << 11); // BLUE } for(gradient=0; gradient<64; gradient++) FillRect(0, 64+gradient, 240, 2, gradient << 5); // GREEN // Handle DIP settings via graphical interface WriteStringFlash(0,16,F("Press FIRE in 9 secs to set DIP switches"), COL_WHITE, COL_BLACK, 1); anim_delay = millis(); while(dip_loop) { if(digitalRead(BTN_FIRE1)) { enter_dip_settings = 1; while(digitalRead(BTN_FIRE1)) { } delay(ANTIBUMP_MSECS); break; } if(millis()-anim_delay > 250) { DrawMartian((uint8_t *) MAU1, 33-8, 206, COL_YELLOW); DrawMartian((uint8_t *) GIU1, 105-8, 206, COL_CYAN); DrawMartian((uint8_t *) VIT1, 186-8, 206, COL_PURPLE); } if(millis()-anim_delay > 500) { DrawMartian((uint8_t *) MAU2, 33-8, 206, COL_YELLOW); DrawMartian((uint8_t *) GIU2, 105-8, 206, COL_CYAN); DrawMartian((uint8_t *) VIT2, 186-8, 206, COL_PURPLE); anim_delay = millis(); dip_time--; if(dip_time == 0) dip_loop = 0; else DrawChar(84, 16, '0'+dip_time/3, COL_WHITE, COL_BLACK, 1); } } return(enter_dip_settings); } void PrintDIP() { WriteStringFlash(0,304,F("DIP switch settings "),COL_BLACK,COL_WHITE,2); WriteStringFlash(0,284,F("DIP3 DIP5 DIP6 DIP7"),COL_GREEN,COL_BLACK,2); WriteStringFlash(0,260,F("1PStart 2PStart Left Right"),COL_YELLOW,COL_BLACK,1); WriteStringFlash(0, 0,F("Press FIRE to exit DIPs settings."),COL_WHITE,COL_BLACK,1); if(port2_in & DIP3_SHIPS) { WriteStringFlash( 0,268,F("ON "),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,238,F("DIP3: +1 ship "),COL_GREEN,COL_BLACK,2); } else { WriteStringFlash( 0,268,F("OFF"),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,238,F("DIP3: no add. ship"),COL_GREEN,COL_BLACK,2); } if(port2_in & DIP5_SHIPS) { WriteStringFlash( 60,268,F("ON "),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,218,F("DIP5: +2 ships "),COL_GREEN,COL_BLACK,2); } else { WriteStringFlash( 60,268,F("OFF"),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,218,F("DIP5: no add. ships"),COL_GREEN,COL_BLACK,2); } if(port2_in & DIP6_EXTRA_SHIP) { WriteStringFlash(120,268,F("ON "),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,198,F("DIP6: ext ship 1000"),COL_GREEN,COL_BLACK,2); } else { WriteStringFlash(120,268,F("OFF"),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,198,F("DIP6: ext ship 1500"),COL_GREEN,COL_BLACK,2); } if(port2_in & DIP7_COIN_INFO) { WriteStringFlash(180,268,F("ON "),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,178,F("DIP7: no coin demo"),COL_GREEN,COL_BLACK,2); } else { WriteStringFlash(180,268,F("OFF"),COL_GREEN,COL_BLACK,2); WriteStringFlash( 0,178,F("DIP7: coin on demo"),COL_GREEN,COL_BLACK,2); } WriteStringFlash( 0,148,F("Color version [ROM]:"),COL_BLACK,COL_WHITE,2); WriteStringFlash( 0,120,F("Use COIN buttpn to change colors."),COL_YELLOW,COL_BLACK,1); switch(color_version) { case COL_VERSION_SV: WriteStringFlash( 0,128,F("SV (black and white)"),COL_GREEN,COL_BLACK,2); break; case COL_VERSION_TV: WriteStringFlash( 0,128,F("TV (white and green)"),COL_GREEN,COL_BLACK,2); break; case COL_VERSION_CV: WriteStringFlash( 0,128,F("CV (colors) "),COL_GREEN,COL_BLACK,2); break; case COL_VERSION_GP: WriteStringFlash( 0,128,F("Psychedelic version "),COL_GREEN,COL_BLACK,2); break; case COL_VERSION_RT: WriteStringFlash( 0,128,F("Test ROM enabled "),COL_GREEN,COL_BLACK,2); break; } } void Handle_DIP_Settings() { bool changes = true; FillRect(0, 0, 240, 320, COL_BLACK); // Black while(!digitalRead(BTN_FIRE1)) { if(changes) { PrintDIP(); delay(ANTIBUMP_MSECS); changes = false; } if(digitalRead(BTN_1P_START)) { port2_in ^= DIP3_SHIPS; changes = true; } if(digitalRead(BTN_2P_START)) { port2_in ^= DIP5_SHIPS; changes = true; } if(digitalRead(BTN_LEFT1)) { port2_in ^= DIP6_EXTRA_SHIP; changes = true; } if(digitalRead(BTN_RIGHT1)) { port2_in ^= DIP7_COIN_INFO; changes = true; } if(digitalRead(BTN_CREDIT)) { color_version++; changes = true; if(color_version > 4) color_version = 0; } } // This is a bad hack. Since we had no more buttons available // we used a stub color scheme to enable test ROM mode if(color_version == COL_VERSION_RT) test_ROM_enable = true; } /*** AUDIO Functions *************************************************************/ // Timer 1 Interrupt Service Routine used for generate invaders sound // This routine is called 24000 time at second ISR(TIMER1_COMPA_vect) { // This part of the interrupt has nothing to do with audio. // This is a simple 8 bit counter which synthesizes a circa 120 Hz // signal out of the 24KHz interrupt (divisor = 200) loop_cnt++; if(loop_cnt == 200) { loop_cnt = 0; vblank = true; } // Here starts the sounds part of the interrupt // Walker Sound Generation if (port5_out & 0x0f) { // Choose Walker Frequency switch (port5_out) { case SND_FLEET_1: WalkerFrequency = WALK_FREQ_75_Hz; break; case SND_FLEET_2: WalkerFrequency = WALK_FREQ_66_Hz; break; case SND_FLEET_3: WalkerFrequency = WALK_FREQ_62_Hz; break; case SND_FLEET_4: WalkerFrequency = WALK_FREQ_57_Hz; break; } // Generate Walker Frequency if(++WalkerFrequencyCounter < WalkerFrequency/2) PORTD |= AUDIO_WALKER; // Set walker pin else PORTD &= ~AUDIO_WALKER; // Set walker pin if(WalkerFrequencyCounter > WalkerFrequency) WalkerFrequencyCounter = 0; } // Ufo Sound Generation UfoSweepValue = (port3_out & SND_UFO) | (port5_out & SND_UFO_HIT); if(UfoSweepValue) { // Choose sweep Frequency switch (UfoSweepValue) { case SND_UFO: UfoSweepValue = UFO_SWEEP_FREQUENCY; break; case SND_UFO_HIT: UfoSweepValue = UFO_HIT_SWEEP_FREQUENCY; break; default: UfoSweepValue = UFO_HIT_SWEEP_FREQUENCY; break; } // Low frequency sweep generator if(++UfoSweepFrequencyCounter > UfoSweepValue) { UfoSweepFrequencyCounter = 0; if(UfoSweepRampDirection == false) { if(--UfoSweepFrequency <= UFO_SWEEP_RAMP_MIN) UfoSweepRampDirection = true; } else { if(++UfoSweepFrequency >= UFO_SWEEP_RAMP_MAX) UfoSweepRampDirection = false; } } // Ufo frequency generator if(UfoFrequencyCounter++ < UfoSweepFrequency/2) OCR2B = 0x00; // Set PWM out to 0x00 else OCR2B = 0x80; // Set PWM to out 0x80 if(UfoFrequencyCounter > UfoSweepFrequency) UfoFrequencyCounter = 0; } // Flash Sound Section if(FlashSoundActive) { if(++FlashNoiseFrequencyCounter > FLASH_NOISE_FREQUENCY) { // Reduce Noise Frequency FlashNoiseFrequencyCounter = 0; if(++FlashNoiseEnvelopeCounter > 128) { // Flash Explosion Decay counter FlashNoiseEnvelope -= 8; FlashNoiseEnvelopeCounter = 0; } FlashNoise = (FlashNoise>>1)^(-(FlashNoise & 1) & 0xb400); // Flash Noise Pseudo Random Generator FlashNoise8 = FlashNoise >> 8; OCR2B= FlashNoise8 & FlashNoiseEnvelope; // Write on PWM enveloped noise value Timeslot 2 FlashShotTime ++; } if(FlashShotTime > FLASH_EXPLOSION_TIME) { // End flash explosion time, reload default parameter and disable sound FlashSoundActive = false; FlashShotTime = 0; FlashNoiseEnvelope = 0xffff; } } // Shot Sound Section if(ShotSoundActive) { if(++ShotFrequencyCounter <= SHOT_FREQUENCY) TemporaryByte = 0xff; // Generate shot frequency sound else TemporaryByte = 0x00; if(ShotFrequencyCounter > SHOT_FREQUENCY) ShotFrequencyCounter = 0; ShotNoise = (ShotNoise>>1)^(-(ShotNoise & 1) & 0xb400); // Shot Noise Pseudo Random Generator ShotNoise8 = ShotNoise >> 8; OCR2B= (TemporaryByte | ShotNoise8)& ShotEnvelope; // Write on PWM enveloped frequency and noise value Timeslot 3 if(++ShotFrequencyDecay > 1000) { ShotFrequencyDecay = 0; ShotEnvelope -= 0x10; // decremento di step di 16 } if(++ShotTime > SHOT_SOUND_TIME) { // End shot missile time, reload default parameter and disable sound ShotTime = 0; ShotEnvelope = 0xffff; ShotSoundActive = false; } } // Invaders Die Sound Section if(InvadersDieSoundActivate) { if(++InvadersDieSweepFrequencyCounter > INVADERS_DIE_SWEEP_FREQUENCY) { InvadersDieSweepFrequencyCounter = 0; if(++InvadersDieSweepFrequency >= 40) { if(++InvadersDieSoundTime > 1) { InvadersDieSoundActivate = false; InvadersDieSoundTime = 0; } InvadersDieSweepFrequency = 0; } } if(InvadersDieFrequencyCounter++ < InvadersDieSweepFrequency/2) OCR2B = 0xff; else OCR2B = 0x00; if(InvadersDieFrequencyCounter >= InvadersDieSweepFrequency) InvadersDieFrequencyCounter=0; } // Extended Play Sound Section if(ExtendedPlaySoundActivate) { if(ExtendedPlayDoubleTone) ExtendedPlayFrequency = EXTENDED_PLAY_FREQUENCY_H; else ExtendedPlayFrequency= EXTENDED_PLAY_FREQUENCY_L; if(++ExtendedPlayFrequencyCounter < ExtendedPlayFrequency/2) OCR2B = 0x80; else OCR2B = 0x00; if(ExtendedPlayFrequencyCounter >= ExtendedPlayFrequency) ExtendedPlayFrequencyCounter=0; if(++ExtendedPlayDoubleTime > 150) { ExtendedPlayDoubleTone ^= 1; ExtendedPlayDoubleTime = 0; } if(++ExtendedPlaySoundTime > 24000) { ExtendedPlaySoundTime = 0; ExtendedPlaySoundActivate = false; } } }// close ISR void startSoundGenerator() { // Modified http://playground.arduino.cc/Code/PCMAudio // Use internal clock (datasheet p.160) ASSR &= ~(_BV(EXCLK) | _BV(AS2)); // Set fast PWM mode (p.157) TCCR2A |= _BV(WGM21) | _BV(WGM20); TCCR2B &= ~_BV(WGM22); // Do non-inverting PWM on pin OC2B (p.155) // On the Arduino this is pin 3. TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0); TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0)); // No prescaler (p.158) TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // Set up Timer 1 to send a sample every interrupt. cli(); // Set CTC mode (Clear Timer on Compare Match) (p.133) // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10)); // No prescaler (p.134) TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. // OCR1A = F_CPU / SAMPLE_RATE; OCR1A = F_CPU / 24000; // 16e6 / 8000 = 2000 // Enable interrupt when TCNT1 == OCR1A (p.136) TIMSK1 |= _BV(OCIE1A); sei(); } /*** MAIN PROGRAM *************************************************************/ void setup() { // Init Serial Interface with baudrate 9600 Serial.begin(9600); // Init SPI interface at maximum speed clk/2 SPI.begin(); SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0)); SPI.setClockDivider(SPI_CLOCK_DIV2); // Pin ports setup DDRD = 0b00111100; // Set the Serial Tx Rx pin 0 in input and 1 in output, the Audio pin 2-3-4 in out and the Buttons pin 5-6-7 in input DDRC &= 0b00011111; // Set the Buttons pin A0-A1-A2 in input DDRB |= 0b00101111; // Set the Display pin 8-9-10-11-13 in output DDRB &= 0b11101111; // Set the Display pin 12 in input digitalWrite(TFT_SCK, LOW); // Set Display Pin SCK LOW digitalWrite(TFT_SDI, LOW); // Set Display Pin SDI LOW digitalWrite(TFT_DC, LOW); // Set Display Pin DC LOW digitalWrite(TFT_CS, HIGH); // Set Display Pin CS HIGH // Display Reset Sequence digitalWrite(TFT_RST, LOW); delay(10); digitalWrite(TFT_RST, HIGH); delay(150); // Init Display initDisplay(); // Init EEPROM (Search for magic code 'MF') if( (EEPROM.read(0) != 'M') && (EEPROM.read(1) != 'F') ) { #ifdef DEBUG Serial.println(F("Formatting EEPROM...")); #endif // Format and write default settings EEPROM.write(0x0000, 'M'); // Magic word EEPROM.write(0x0001, 'F'); // Magic word EEPROM.write(0x0002, 0x00); // port2_in default value EEPROM.write(0x0003, 0x00); // default color version } else { #ifdef DEBUG Serial.println(F("Reading settings from EEPROM")); #endif // Read settings port2_in = EEPROM.read(0x0002); color_version = EEPROM.read(0x0003); } // Start Emulator #ifdef DEBUG Serial.println(); Serial.println(F("*** Space Invaders ROM on Arduino Nano with ILI9341 ***")); Serial.println(); #endif // Fill screen initial demo FillRect(0, 0, 240, 320, COL_BLUE); // Blue FillRect(0, 0, 240, 320, COL_GREEN); // Green FillRect(0, 0, 240, 320, COL_RED); // Red FillRect(0, 0, 240, 320, COL_BLACK); // Black if(PrintWelcome() == 1) { Handle_DIP_Settings(); // Save settings if there are changes if(EEPROM.read(2) != port2_in) EEPROM.write(0x0002, port2_in); if(EEPROM.read(3) != color_version) EEPROM.write(0x0003, color_version); } if(test_ROM_enable) color_version = COL_VERSION_SV; // BOOT THE GAME #ifdef DEBUG Serial.println(F("Booting Space Invaders...")); #endif FillRect(0, 0, 240, 320, COL_BLACK); // Black invadersReset(); // Start sound generator startSoundGenerator(); } void loop() { invadersStep(); // Run Invaders machine // Press 1P-Start and 2P-Start to dump scree on serial if((PINC & 0b00000001) && (PINC & 0b00000010)) { // 1PSTART & 2PSTART uint8_t pixelsbyte; uint8_t col; uint16_t row; if(PINC & 0b00100000) { // BTN_CREDIT tilt_enable = true; } else { // This is avoided when DEBUG is true to save space and allow compiling #ifndef DEBUG // Serial.println(F("# BMP image dump")); // Serial.println(F("# You can convert with https://tomeko.net/online_tools/hex_to_file.php?lang=en")); Serial.print(F("424D46000000000000003E0000002800")); Serial.print(F("000000010000E0000000010001000000")); // E000=224 0001=256 (swapped) Serial.print(F("00000800000000000000000000000000")); Serial.println(F("00000000000000000000FFFFFF00")); // Color table at position 0x36 for(row=0; row<224; row++) { for(col=0; col<256/8; col++) { pixelsbyte = (cpu8080_readByte(0x2400 + 32-col + (row*32))); if(pixelsbyte < 16) Serial.print("0"); // Missing padding 0 Serial.print(pixelsbyte, HEX); } Serial.println(); } #endif } } }