giovedì 2 maggio 2019

Emulatore CHIP-8 su Arduino Mega

Partendo dal precedente post ho portato il codice su Arduino

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

Alpine Linux 2024 su IBM A31

Ho provato a far resuscitare un IBM A31 destinato alla discarica. La macchina ha processore P4, 256 Mb di RAM, la batteria CMOS morta ed e&#...