Per il progetto ho usato una Arduino Mega con un schermo Oled 0.91" di 128x32 pixels (il CHIP-8 aveva uno schermo 64x32 monocromatico basato su U8G2) ed un keypad a 16 pulsanti (CHIP-8 gestiva una tastiera a 16 tasti)
il primo passo e' stato quello di usare la classe chip8 per creare una libreria Arduino.
Si crea un sottodirectory chip8 in /libraries e si creano i file chip8.cpp e chip8.h
Rispetto alla versione NCurses sono stati rimossi dei comandi che hanno riscontro in Arduino (per esempio iostream o sizeof ) Credits James Griffin
chip8.cpp
-----------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
//#include <iostream>
//#include <random>
#include "time.h"
#include "chip8.h"
unsigned char chip8_fontset[80] =
{
0xF0, 0x90, 0x90, 0x90, 0xF0, //0
0x20, 0x60, 0x20, 0x20, 0x70, //1
0xF0, 0x10, 0xF0, 0x80, 0xF0, //2
0xF0, 0x10, 0xF0, 0x10, 0xF0, //3
0x90, 0x90, 0xF0, 0x10, 0x10, //4
0xF0, 0x80, 0xF0, 0x10, 0xF0, //5
0xF0, 0x80, 0xF0, 0x90, 0xF0, //6
0xF0, 0x10, 0x20, 0x40, 0x40, //7
0xF0, 0x90, 0xF0, 0x90, 0xF0, //8
0xF0, 0x90, 0xF0, 0x10, 0xF0, //9
0xF0, 0x90, 0xF0, 0x90, 0x90, //A
0xE0, 0x90, 0xE0, 0x90, 0xE0, //B
0xF0, 0x80, 0x80, 0x80, 0xF0, //C
0xE0, 0x90, 0x90, 0x90, 0xE0, //D
0xF0, 0x80, 0xF0, 0x80, 0xF0, //E
0xF0, 0x80, 0xF0, 0x80, 0x80 //F
};
Chip8::Chip8() {}
Chip8::~Chip8() {}
// Initialise
void Chip8::init() {
pc = 0x200; // Set program counter to 0x200
opcode = 0; // Reset op code
I = 0; // Reset I
sp = 0; // Reset stack pointer
// Clear the display
for (int i = 0; i < 2048; ++i) {
gfx[i] = 0;
}
// Clear the stack, keypad, and V registers
for (int i = 0; i < 16; ++i) {
stack[i] = 0;
key[i] = 0;
V[i] = 0;
}
// Clear memory
for (int i = 0; i < 4096; ++i) {
memory[i] = 0;
}
// Load font set into memory
for (int i = 0; i < 80; ++i) {
memory[i] = chip8_fontset[i];
}
// Reset timers
delay_timer = 0;
sound_timer = 0;
// Seed rng
srand (time(NULL));
}
// Initialise and load ROM into memory
bool Chip8::load(unsigned char programma[],int size) {
// Initialise
init();
//long rom_size = sizeof(programma);
int rom_size = size;
// Copy buffer to memory
if ((4096-512) > rom_size){
for (int i = 0; i < rom_size; ++i) {
memory[i + 512] = (uint8_t)programma[i]; // Load into memory starting
// at 0x200 (=512)
}
}
else {
//std::cerr << "ROM too large to fit in memory" << std::endl;
return false;
}
return true;
}
// Emulate one cycle
void Chip8::emulate_cycle() {
// Fetch op code
opcode = memory[pc] << 8 | memory[pc + 1]; // Op code is two bytes
switch(opcode & 0xF000){
// 00E_
case 0x0000:
switch (opcode & 0x000F) {
// 00E0 - Clear screen
case 0x0000:
for (int i = 0; i < 2048; ++i) {
gfx[i] = 0;
}
drawFlag = true;
pc+=2;
break;
// 00EE - Return from subroutine
case 0x000E:
--sp;
pc = stack[sp];
pc += 2;
break;
default:
//printf("\nUnknown op code: %.4X\n", opcode);
exit(3);
}
break;
// 1NNN - Jumps to address NNN
case 0x1000:
pc = opcode & 0x0FFF;
break;
// 2NNN - Calls subroutine at NNN
case 0x2000:
stack[sp] = pc;
++sp;
pc = opcode & 0x0FFF;
break;
// 3XNN - Skips the next instruction if VX equals NN.
case 0x3000:
if (V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF))
pc += 4;
else
pc += 2;
break;
// 4XNN - Skips the next instruction if VX does not equal NN.
case 0x4000:
if (V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF))
pc += 4;
else
pc += 2;
break;
// 5XY0 - Skips the next instruction if VX equals VY.
case 0x5000:
if (V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4])
pc += 4;
else
pc += 2;
break;
// 6XNN - Sets VX to NN.
case 0x6000:
V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
pc += 2;
break;
// 7XNN - Adds NN to VX.
case 0x7000:
V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
pc += 2;
break;
// 8XY_
case 0x8000:
switch (opcode & 0x000F) {
// 8XY0 - Sets VX to the value of VY.
case 0x0000:
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY1 - Sets VX to (VX OR VY).
case 0x0001:
V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY2 - Sets VX to (VX AND VY).
case 0x0002:
V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY3 - Sets VX to (VX XOR VY).
case 0x0003:
V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 8XY4 - Adds VY to VX. VF is set to 1 when there's a carry,
// and to 0 when there isn't.
case 0x0004:
V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
if(V[(opcode & 0x00F0) >> 4] > (0xFF - V[(opcode & 0x0F00) >> 8]))
V[0xF] = 1; //carry
else
V[0xF] = 0;
pc += 2;
break;
// 8XY5 - VY is subtracted from VX. VF is set to 0 when
// there's a borrow, and 1 when there isn't.
case 0x0005:
if(V[(opcode & 0x00F0) >> 4] > V[(opcode & 0x0F00) >> 8])
V[0xF] = 0; // there is a borrow
else
V[0xF] = 1;
V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
pc += 2;
break;
// 0x8XY6 - Shifts VX right by one. VF is set to the value of
// the least significant bit of VX before the shift.
case 0x0006:
V[0xF] = V[(opcode & 0x0F00) >> 8] & 0x1;
V[(opcode & 0x0F00) >> 8] >>= 1;
pc += 2;
break;
// 0x8XY7: Sets VX to VY minus VX. VF is set to 0 when there's
// a borrow, and 1 when there isn't.
case 0x0007:
if(V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4]) // VY-VX
V[0xF] = 0; // there is a borrow
else
V[0xF] = 1;
V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// 0x8XYE: Shifts VX left by one. VF is set to the value of
// the most significant bit of VX before the shift.
case 0x000E:
V[0xF] = V[(opcode & 0x0F00) >> 8] >> 7;
V[(opcode & 0x0F00) >> 8] <<= 1;
pc += 2;
break;
default:
//printf("\nUnknown op code: %.4X\n", opcode);
exit(3);
}
break;
// 9XY0 - Skips the next instruction if VX doesn't equal VY.
case 0x9000:
if (V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4])
pc += 4;
else
pc += 2;
break;
// ANNN - Sets I to the address NNN.
case 0xA000:
I = opcode & 0x0FFF;
pc += 2;
break;
// BNNN - Jumps to the address NNN plus V0.
case 0xB000:
pc = (opcode & 0x0FFF) + V[0];
break;
// CXNN - Sets VX to a random number, masked by NN.
case 0xC000:
V[(opcode & 0x0F00) >> 8] = (rand() % (0xFF + 1)) & (opcode & 0x00FF);
pc += 2;
break;
// DXYN: Draws a sprite at coordinate (VX, VY) that has a width of 8
// pixels and a height of N pixels.
// Each row of 8 pixels is read as bit-coded starting from memory
// location I;
// I value doesn't change after the execution of this instruction.
// VF is set to 1 if any screen pixels are flipped from set to unset
// when the sprite is drawn, and to 0 if that doesn't happen.
case 0xD000:
{
unsigned short x = V[(opcode & 0x0F00) >> 8];
unsigned short y = V[(opcode & 0x00F0) >> 4];
unsigned short height = opcode & 0x000F;
unsigned short pixel;
V[0xF] = 0;
for (int yline = 0; yline < height; yline++)
{
pixel = memory[I + yline];
for(int xline = 0; xline < 8; xline++)
{
if((pixel & (0x80 >> xline)) != 0)
{
if(gfx[(x + xline + ((y + yline) * 64))] == 1)
{
V[0xF] = 1;
}
gfx[x + xline + ((y + yline) * 64)] ^= 1;
}
}
}
drawFlag = true;
pc += 2;
}
break;
// EX__
case 0xE000:
switch (opcode & 0x00FF) {
// EX9E - Skips the next instruction if the key stored
// in VX is pressed.
case 0x009E:
if (key[V[(opcode & 0x0F00) >> 8]] != 0)
pc += 4;
else
pc += 2;
break;
// EXA1 - Skips the next instruction if the key stored
// in VX isn't pressed.
case 0x00A1:
if (key[V[(opcode & 0x0F00) >> 8]] == 0)
pc += 4;
else
pc += 2;
break;
default:
//printf("\nUnknown op code: %.4X\n", opcode);
exit(3);
}
break;
// FX__
case 0xF000:
switch(opcode & 0x00FF)
{
// FX07 - Sets VX to the value of the delay timer
case 0x0007:
V[(opcode & 0x0F00) >> 8] = delay_timer;
pc += 2;
break;
// FX0A - A key press is awaited, and then stored in VX
case 0x000A:
{
bool key_pressed = false;
for(int i = 0; i < 16; ++i)
{
if(key[i] != 0)
{
V[(opcode & 0x0F00) >> 8] = i;
key_pressed = true;
}
}
// If no key is pressed, return and try again.
if(!key_pressed)
return;
pc += 2;
}
break;
// FX15 - Sets the delay timer to VX
case 0x0015:
delay_timer = V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX18 - Sets the sound timer to VX
case 0x0018:
sound_timer = V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX1E - Adds VX to I
case 0x001E:
// VF is set to 1 when range overflow (I+VX>0xFFF), and 0
// when there isn't.
if(I + V[(opcode & 0x0F00) >> 8] > 0xFFF)
V[0xF] = 1;
else
V[0xF] = 0;
I += V[(opcode & 0x0F00) >> 8];
pc += 2;
break;
// FX29 - Sets I to the location of the sprite for the
// character in VX. Characters 0-F (in hexadecimal) are
// represented by a 4x5 font
case 0x0029:
I = V[(opcode & 0x0F00) >> 8] * 0x5;
pc += 2;
break;
// FX33 - Stores the Binary-coded decimal representation of VX
// at the addresses I, I plus 1, and I plus 2
case 0x0033:
memory[I] = V[(opcode & 0x0F00) >> 8] / 100;
memory[I + 1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;
memory[I + 2] = V[(opcode & 0x0F00) >> 8] % 10;
pc += 2;
break;
// FX55 - Stores V0 to VX in memory starting at address I
case 0x0055:
for (int i = 0; i <= ((opcode & 0x0F00) >> 8); ++i)
memory[I + i] = V[i];
// On the original interpreter, when the
// operation is done, I = I + X + 1.
I += ((opcode & 0x0F00) >> 8) + 1;
pc += 2;
break;
case 0x0065:
for (int i = 0; i <= ((opcode & 0x0F00) >> 8); ++i)
V[i] = memory[I + i];
// On the original interpreter,
// when the operation is done, I = I + X + 1.
I += ((opcode & 0x0F00) >> 8) + 1;
pc += 2;
break;
default:
break;
//printf ("Unknown opcode [0xF000]: 0x%X\n", opcode);
}
break;
default:
//printf("\nUnimplemented op code: %.4X\n", opcode);
exit(3);
}
// Update timers
if (delay_timer > 0)
--delay_timer;
if (sound_timer > 0)
if(sound_timer == 1);
// TODO: Implement sound
--sound_timer;
}
-----------------------------------------------------------------
chip8.h
-----------------------------------------------------------------
#ifndef CHIP_8_H
#define CHIP_8_H
#include <stdint.h>
class Chip8 {
private:
uint16_t stack[16]; // Stack
uint16_t sp; // Stack pointer
uint8_t memory[4096]; // Memory (4k)
uint8_t V[16]; // V registers (V0-VF)
uint16_t pc; // Program counter
uint16_t opcode; // Current op code
uint16_t I; // Index register
uint8_t delay_timer; // Delay timer
uint8_t sound_timer; // Sound timer
void init();
public:
uint8_t gfx[64 * 32]; // Graphics buffer
uint8_t key[16]; // Keypad
bool drawFlag; // Indicates a draw has occurred
Chip8();
~Chip8();
void emulate_cycle(); // Emulate one cycle
bool load(unsigned char programma[],int size); // Load application
};
#endif // CHIP_8_H
-----------------------------------------------------------------
chip8_pong.ino
-----------------------------------------------------------------
#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include <Keypad.h>
#include <chip8.h>
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0);
Chip8 chip8 = Chip8();
unsigned char prg[] =
{
0x6A,0x02, 0x6B, 0x0C, 0x6C, 0x3F, 0x6D, 0x0C,
0xA2,0xEA, 0xDA, 0xB6, 0xDC, 0xD6, 0x6E, 0x00,
0x22,0xD4, 0x66, 0x03, 0x68, 0x02, 0x60, 0x60,
0xF0,0x15, 0xF0, 0x07, 0x30, 0x00, 0x12, 0x1A,
0xC7,0x17, 0x77, 0x08,
0x69,0xFF, 0xA2, 0xF0, 0xD6, 0x71, 0xA2, 0xEA,
0xDA,0xB6, 0xDC, 0xD6, 0x60, 0x01, 0xE0, 0xA1,
0x7B,0xFE, 0x60, 0x04, 0xE0, 0xA1, 0x7B, 0x02,
0x60,0x1F, 0x8B, 0x02, 0xDA, 0xB6, 0x60, 0x0C,
0xE0,0xA1, 0x7D, 0xFE,
0x60,0x0D, 0xE0, 0xA1, 0x7D, 0x02, 0x60, 0x1F,
0x8D,0x02, 0xDC, 0xD6, 0xA2, 0xF0, 0xD6, 0x71,
0x86,0x84, 0x87, 0x94, 0x60, 0x3F, 0x86, 0x02,
0x61,0x1F, 0x87, 0x12, 0x46, 0x02, 0x12, 0x78,
0x46,0x3F, 0x12, 0x82,
0x47,0x1F, 0x69, 0xFF, 0x47, 0x00, 0x69, 0x01,
0xD6,0x71, 0x12, 0x2A, 0x68, 0x02, 0x63, 0x01,
0x80,0x70, 0x80, 0xB5, 0x12, 0x8A, 0x68, 0xFE,
0x63,0x0A, 0x80, 0x70, 0x80, 0xD5, 0x3F, 0x01,
0x12,0xA2, 0x61, 0x02,
0x80,0x15, 0x3F, 0x01, 0x12, 0xBA, 0x80, 0x15,
0x3F,0x01, 0x12, 0xC8, 0x80, 0x15, 0x3F, 0x01,
0x12,0xC2, 0x60, 0x20, 0xF0, 0x18, 0x22, 0xD4,
0x8E,0x34, 0x22, 0xD4, 0x66, 0x3E, 0x33, 0x01,
0x66,0x03, 0x68, 0xFE,
0x33,0x01, 0x68, 0x02, 0x12, 0x16, 0x79, 0xFF,
0x49,0xFE, 0x69, 0xFF, 0x12, 0xC8, 0x79, 0x01,
0x49,0x02, 0x69, 0x01, 0x60, 0x04, 0xF0, 0x18,
0x76,0x01, 0x46, 0x40, 0x76, 0xFE, 0x12, 0x6C,
0xA2,0xF2, 0xFE, 0x33,
0xF2,0x65, 0xF1, 0x29, 0x64, 0x14, 0x65, 0x00,
0xD4,0x55, 0x74, 0x15, 0xF2, 0x29, 0xD4, 0x55,
0x00,0xEE, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80,0x00, 0x00, 0x00, 0x00, 0x00
};
const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {9,8,7,6}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5,4,3,2}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
void setup() {
Serial.begin(9600);
u8g2.begin();
chip8.load(prg,246); // non c'e' la funzione sizeof
}
void loop() {
chip8.emulate_cycle();
char key = keypad.getKey();
if (key){
for (int f=0; f<16; f++)
{
chip8.key[f] = 0;
}
Serial.println(key);
switch(key)
{
case '1':
chip8.key[1]=1;
break;
case '4':
chip8.key[4]=1;
break;
case '7':
chip8.key[7]=1;
break;
case '*':
chip8.key[10]=1;
break;
/////////////////////////////////////
case '2':
chip8.key[2]=1;
break;
case '5':
chip8.key[5]=1;
break;
case '8':
chip8.key[8]=1;
break;
case '0':
chip8.key[0]=1;
break;
/////////////////////////////////////
case '3':
chip8.key[3]=1;
break;
case '6':
chip8.key[6]=1;
break;
case '9':
chip8.key[9]=1;
break;
case '#':
chip8.key[11]=1;
break;
/////////////////////////////////////
case 'A':
chip8.key[12]=1;
break;
case 'B':
chip8.key[13]=1;
break;
case 'C':
chip8.key[14]=1;
break;
case 'D':
chip8.key[15]=1;
break;
default:
break;
}
}
if (chip8.drawFlag) {
chip8.drawFlag = false;
u8g2.clearBuffer();
for (int i = 0; i < 2048; ++i) {
uint8_t pixel = chip8.gfx[i];
int k = (int)i/64;
int j = i%64;
if (pixel == 1)
{
//u8g2.clearBuffer();
u8g2.drawPixel(j,k);
//move(k,j);
//addch(ACS_CKBOARD);
}
}
//refresh();
//u8g2.clearBuffer();
u8g2.sendBuffer();
}
delay(2);
}
Nessun commento:
Posta un commento