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