ARDUINvaders – The source code

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
    }
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *