Troubleshooting a Segmentation Fault in My Code: Seeking Advice and Solutions

Hi guys,

I am currently developing a CHIP 8 Interpreter to use in a console project I am working on for a project. The problem that is occurring is that the program immediately seg faults as we enter main. This was verified in a debugger. All code in the N5110 library has been confirmed to be working as intended.

The actual implementation of the CHIP 8 Interpreter was previously written by me using C and SDL2. I have taken it from there and tried to interface it with the NUCLEO L476RG but somehow it begins to seg fault here but my other implementation does not. Please Check out the Interpreter here:
GitHub - Vermaaaaaa/CHIP-8-Interpreter: CHIP-8 Interpreter in C using SDL2.

This is my implementation here:

#include "mbed.h"
#include "N5110.h"
#include <chrono>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <cmath>


N5110 lcd(PC_7, PA_9, PB_10, PB_5, PB_3, PA_10);

Timer t;

typedef enum{
    COSMAC,
    AMIGA, 
}emu_type;

//Config Object
typedef struct {
    emu_type choice;
    FillType bg_colour;
    FillType fg_colour;
    int res_x;
    int res_y;
    int insts_per_sec;
    unsigned int sf_x;
    unsigned int sf_y;
} config_type;

//Enum for emulation state
typedef enum{
    QUIT,
    RUNNING,
    PAUSED,
} emu_state;

typedef union{
    uint16_t full_op;
    uint8_t data[2];
} opcode_t;

typedef struct{
    opcode_t opcode;
    uint16_t NNN;   //Constants in instruction set, declaring like this would decrease space complexity
    uint8_t NN;     
    uint8_t N;      
    uint8_t X;      
    uint8_t Y;      
} instr_type;

//Chip 8 object
typedef struct{
    emu_state state;
    uint8_t ram[4096]; //Ram for the chip 8
    bool display[64*32]; //Decided to have seperate arrays for display instead of a pointer to ram. May change if I move onto super chip
    uint16_t stack[48]; 
    uint16_t *stkptr;
    uint8_t V[16]; //Registers from V0-Vf
    uint16_t I; //Index Register  
    uint16_t pc;
    uint8_t delay_timer;
    uint8_t sound_timer; //60Hz timers in chip 8
    bool keypad[16]; //Check if keypad is in off or on state
    instr_type inst;
    bool draw;
} chip8_type;

void init_chip8(chip8_type *chip8){
    const uint32_t entry = 0x200; //Entry point for roms to be loaded into memory
    const uint8_t font[] = {
        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
    }; //Fonts used by the CHIP 8
    const uint8_t rom_data[] = {
        0x00,0xe0,0x61,0x01,0x60,0x08,0xa2,0x50,0xd0,0x1f,0x60,0x10,0xa2,0x5f,0xd0,0x1f,
        0x60,0x18,0xa2,0x6e,0xd0,0x1f,0x60,0x20,0xa2,0x7d,0xd0,0x1f,0x60,0x28,0xa2,0x8c,
        0xd0,0x1f,0x60,0x30,0xa2,0x9b,0xd0,0x1f,0x61,0x10,0x60,0x08,0xa2,0xaa,0xd0,0x1f,
        0x60,0x10,0xa2,0xb9,0xd0,0x1f,0x60,0x18,0xa2,0xc8,0xd0,0x1f,0x60,0x20,0xa2,0xd7,
        0xd0,0x1f,0x60,0x28,0xa2,0xe6,0xd0,0x1f,0x60,0x30,0xa2,0xf5,0xd0,0x1f,0x12,0x4e,
        0x0f,0x02,0x02,0x02,0x02,0x02,0x00,0x00,0x1f,0x3f,0x71,0xe0,0xe5,0xe0,0xe8,0xa0,
        0x0d,0x2a,0x28,0x28,0x28,0x00,0x00,0x18,0xb8,0xb8,0x38,0x38,0x3f,0xbf,0x00,0x19,
        0xa5,0xbd,0xa1,0x9d,0x00,0x00,0x0c,0x1d,0x1d,0x01,0x0d,0x1d,0x9d,0x01,0xc7,0x29,
        0x29,0x29,0x27,0x00,0x00,0xf8,0xfc,0xce,0xc6,0xc6,0xc6,0xc6,0x00,0x49,0x4a,0x49,
        0x48,0x3b,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x01,0xf0,0x30,0x90,0x00,0x00,0x80,
        0x00,0x00,0x00,0xfe,0xc7,0x83,0x83,0x83,0xc6,0xfc,0xe7,0xe0,0xe0,0xe0,0xe0,0x71,
        0x3f,0x1f,0x00,0x00,0x07,0x02,0x02,0x02,0x02,0x39,0x38,0x38,0x38,0x38,0xb8,0xb8,
        0x38,0x00,0x00,0x31,0x4a,0x79,0x40,0x3b,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,
        0x00,0x00,0xa0,0x38,0x20,0xa0,0x18,0xce,0xfc,0xf8,0xc0,0xd4,0xdc,0xc4,0xc5,0x00,
        0x00,0x30,0x44,0x24,0x14,0x63,0xf1,0x03,0x07,0x07,0x27,0x67,0x23,0x71,0x00,0x00,
        0x28,0x8e,0xa8,0xa8,0xa6,0xce,0x87,0x03,0x03,0x03,0x87,0xfe,0xfc,0x00,0x00,0x60,
        0x90,0xf0,0x80,0x70
    };


    //memset(chip8, 0, sizeof(chip8_type));

    //Load Font
    memcpy(chip8->ram, font, sizeof(font));

    memcpy(chip8->ram + entry, rom_data, sizeof(rom_data));


    chip8->pc = entry;
    chip8->state = RUNNING;
    chip8->stkptr = &chip8->stack[0];

     //Success
}

bool check_keypad(chip8_type *chip8, uint8_t *key_value){
    for(uint8_t i = 0; i < 16 && *key_value == 0xFF ; i++){if(chip8->keypad[i]){*key_value = i; return true;}}
    return false;
}

void emulate(chip8_type *chip8, config_type *config){
    //bool carry; // Set our carry flag

    //Have to or 2 bytes as one opcode is 2 bytes long 
    chip8->inst.opcode.data[0] = chip8->ram[chip8->pc+1];
    chip8->inst.opcode.data[1] = chip8->ram[chip8->pc];

    chip8->pc += 2;

    chip8->inst.NNN = chip8->inst.opcode.full_op & 0x0FFF; // Immediate Memory address, we want to mask of the last 3 nibbles
    chip8->inst.NN = chip8->inst.opcode.full_op & 0x0FF; // 8bit immediate number 
    chip8->inst.N = chip8->inst.opcode.full_op & 0x0F; //N nibble 
    chip8->inst.X = (chip8->inst.opcode.full_op >> 8) & 0x0F; // X register 
    chip8->inst.Y = (chip8->inst.opcode.full_op >> 4) & 0x0F; // Y register

    switch((chip8->inst.opcode.full_op & 0xF000) >> 12){ //Masks opcode so we only get 0xA000 where A is our Opcode
        case 0x0:{
            switch(chip8->inst.NN){
                case 0xE0:{memset(&chip8->display[0], false, sizeof(chip8->display)); chip8->draw = true; break;} //Clear display
                case 0xEE:{chip8->pc = *--chip8->stkptr; break;} //Pop off current subroutine and set pc to that subroutine
            }
            break;
        }
        case(0x1):{chip8->pc = chip8->inst.NNN; break;} // Jump to address NNN
        case(0x2):{*chip8->stkptr++ = chip8->pc; chip8->pc = chip8->inst.NNN; break;}
        case(0x3):{if(chip8->V[chip8->inst.X] == chip8->inst.NN){chip8->pc += 2;} break;} //If VX is equal to NN increment PC
        case(0x4):{if(chip8->V[chip8->inst.X] != chip8->inst.NN){chip8->pc += 2;} break;} //If VX is not equal to NN increment PC
        case(0x5):{if(chip8->V[chip8->inst.X] == chip8->V[chip8->inst.Y]){chip8->pc += 2;} break;} // If VX == VY increment PC
        case(0x6):{chip8->V[chip8->inst.X] = chip8->inst.NN; break;} //Set VX = NN
        case(0x7):{chip8->V[chip8->inst.X] += chip8->inst.NN; break;} // Increment VX by the value NN
        case(0x8):{
            switch((chip8->inst.opcode.full_op & 0x000F)){
                case(0x0):{chip8->V[chip8->inst.X] = chip8->V[chip8->inst.Y]; break;}
                case(0x1):{chip8->V[chip8->inst.X] = chip8->V[chip8->inst.X] | chip8->V[chip8->inst.Y]; break;}
                case(0x2):{chip8->V[chip8->inst.X] = chip8->V[chip8->inst.X] & chip8->V[chip8->inst.Y]; break;}
                case(0x3):{chip8->V[chip8->inst.X] = chip8->V[chip8->inst.X] ^ chip8->V[chip8->inst.Y]; break;}
                case(0x4):{
                    uint16_t result = chip8->V[chip8->inst.X] + chip8->V[chip8->inst.Y];
                    chip8->V[0xF] = (result > 0xFF) ? 1 : 0;
                    chip8->V[chip8->inst.X] = static_cast<uint8_t>(result);
                    break;
                }
                case(0x5):{
                    chip8->V[chip8->inst.X] -= chip8->V[chip8->inst.Y];
                    chip8->V[0xF] = (chip8->V[chip8->inst.X] >= chip8->V[chip8->inst.Y]) ? 1 : 0;
                    break; 
                    }
                case(0x6):{
                    switch(config->choice){
                        case(COSMAC):{
                                chip8->V[0xF] = (chip8->V[chip8->inst.Y] & 0x1);
                                chip8->V[chip8->inst.X] = chip8->V[chip8->inst.Y] >> 1;
                                break;
                        }
                        case(AMIGA):{
                                chip8->V[0xF] = (chip8->V[chip8->inst.X] & 0x1);
                                chip8->V[chip8->inst.X] >>= 1;
                                break;
                        }
                    }
                    break;
                }
                case(0x7):{
                    chip8->V[chip8->inst.X] = chip8->V[chip8->inst.Y] - chip8->V[chip8->inst.X];
                    chip8->V[0xF] = (chip8->V[chip8->inst.Y] >= chip8->V[chip8->inst.X]) ? 1 : 0;
                    break;
                }
                case(0xE):{
                    switch(config->choice){
                            case(COSMAC):{
                                chip8->V[0xF] = (chip8->V[chip8->inst.Y] & 0x80) >> 7;
                                chip8->V[chip8->inst.X] = chip8->V[chip8->inst.Y] << 1;
                                break;
                        }
                        case(AMIGA):{
                                chip8->V[0xF] = (chip8->V[chip8->inst.X] & 0x80) >> 7;
                                chip8->V[chip8->inst.X] <<= 1;
                                break;
                        }

                    }
                    break;
                }

            }
        }
        break;
        case(0x9):{if(chip8->V[chip8->inst.X] != chip8->V[chip8->inst.Y]){chip8->pc += 2; }break;}
        case(0xA):{chip8->I = chip8->inst.NNN; break;}
        case(0xB):{
            switch(config->choice){
                case(COSMAC):{
                    chip8->pc = chip8->inst.NNN + chip8->V[0];
                    break;
                }
                case(AMIGA):{
                    chip8->pc = chip8->inst.NNN + chip8->V[chip8->inst.X];
                    break;
                }
            }   
            break;
        }
        case(0xC):{srand(time(NULL)); uint8_t random = rand(); chip8->V[chip8->inst.X] = random & chip8->inst.NN; break;}
        case(0xD):{
             // 0xDXYN: Draw N-height sprite at coords X,Y; Read from memory location I;
            //   Screen pixels are XOR'd with sprite bits, 
            //   VF (Carry flag) is set if any screen pixels are set off; This is useful
            //   for collision detection or other reasons.
            
            uint8_t X_coord = chip8->V[chip8->inst.X] % 64;
            uint8_t Y_coord = chip8->V[chip8->inst.Y] % 32;
            const uint8_t orig_X = X_coord; // Original X value

            chip8->V[0xF] = 0;  // Initialize carry flag to 0   

            // Loop over all N rows of the sprite
            for (uint8_t i = 0; i < chip8->inst.N; i++) {
                // Get next byte/row of sprite data
                const uint8_t sprite_data = chip8->ram[chip8->I + i];
                X_coord = orig_X;   // Reset X for next row to draw

                for (int8_t j = 7; j >= 0; j--) {
                    // If sprite pixel/bit is on and display pixel is on, set carry flag
                    bool *pixel = &chip8->display[Y_coord * 64 + X_coord]; 
                    const bool sprite_bit = (sprite_data & (1 << j));

                    if (sprite_bit && *pixel) {
                        chip8->V[0xF] = 1;  
                    }

                    // XOR display pixel with sprite pixel/bit to set it on or off
                    *pixel ^= sprite_bit;

                    // Stop drawing this row if hit right edge of screen
                    if (++X_coord >= 64) break;
                }

                // Stop drawing entire sprite if hit bottom edge of screen
                if (++Y_coord >= 32) break;
            }
            chip8->draw = true; // Will update screen on next 60hz tick
            break;
        }
        case(0xE):{
            switch(chip8->inst.NN){
                case(0x9E):{if(chip8->keypad[chip8->V[chip8->inst.X]]){chip8->pc += 2;} break;}
                case(0xA1):{if(!chip8->keypad[chip8->V[chip8->inst.X]]){chip8->pc += 2;} break;}
            }
        }
            break;
        case(0xF):{
            switch(chip8->inst.NN){
                case(0x07):{chip8->V[chip8->inst.X] = chip8->delay_timer; break;}
                case(0x0A):{
                    uint8_t key_value = 0xFF;
                    bool key_pressed = false;
                    key_pressed = check_keypad(chip8, &key_value);

                    if(!key_pressed){chip8->pc -= 2; break;}
                    chip8->V[chip8->inst.X] = key_value;
                    break;
                }
                case(0x15):{chip8->delay_timer = chip8->V[chip8->inst.X]; break;}
                case(0x18):{chip8->sound_timer = chip8->V[chip8->inst.X]; break;}
                case(0x1E):{
                    switch(config->choice){
                        case 0:{chip8->I += chip8->V[chip8->inst.X]; break;}
                        case 1:{
                            uint32_t result = chip8->I + chip8->V[chip8->inst.X]; 
                            chip8->V[0xF] = (result > 0xFFF) ? 1 : 0;
                            chip8->I = (uint16_t)result;
                            break;
                        }
                    }
                    break;
                }
                case(0x29):{chip8->I = chip8->V[chip8->inst.X] * 5; break;}
                case(0x33):{
                    uint8_t va = chip8->V[chip8->inst.X];
                    chip8->ram[chip8->I+2] = va % 10;
                    va /= 10;
                    chip8->ram[chip8->I+1] = va % 10;
                    va /= 10;
                    chip8->ram[chip8->I] = va;
                    break;
                }
            case(0x55):{
                switch(config->choice){
                    case(COSMAC):{for(int i = 0; i <= chip8->inst.X; i++){chip8->ram[chip8->I+i] = chip8->V[i];} chip8->I = chip8->inst.X + 1; break;}
                    case(AMIGA):{for(int i = 0; i <= chip8->inst.X; i++){chip8->ram[chip8->I+i] = chip8->V[i];} break;}
                }
                break;
            }
            case(0x65):{
                switch(config->choice){
                    case(COSMAC):{for(int i = 0; i <= chip8->inst.X; i++){chip8->V[i] = chip8->ram[chip8->I+i];} chip8->I = chip8->inst.X + 1; break;}
                    case(AMIGA):{for(int i = 0; i <= chip8->inst.X; i++){chip8->V[i] = chip8->ram[chip8->I+i];} break;}
                }
                break;
            }
            
            }
        }

    }
}

void update_timers(chip8_type *chip8){
    if(chip8->delay_timer > 0){chip8->delay_timer--;}

    if(chip8->sound_timer > 0){chip8->sound_timer--;}
}

void draw(chip8_type *chip8, const config_type config){
    // Loop through display pixels, draw a rectangle per pixel to the screen
    for (uint32_t i = 0; i < sizeof chip8->display; i++) {
        const unsigned int x0 = (i % config.res_x) * config.sf_x;
        const unsigned int y0 = (i / config.res_x) * config.sf_y;
        if (chip8->display[i]) {
            // Pixel is on, draw foreground color
            lcd.drawRect(x0, y0, config.sf_x, config.sf_y, config.fg_colour);
        } 
        else {
            lcd.drawRect(x0, y0, config.sf_x, config.sf_y, config.bg_colour);
        }
    }
    lcd.refresh();
}


int main(){
    lcd.init(LPH7366_1);
    lcd.setContrast(0.55);      //set contrast to 55%
    lcd.setBrightness(0.5);     //set brightness to 50% (utilises the PWM)
    lcd.clear();
    config_type config = {AMIGA, FILL_BLACK, FILL_WHITE, 64, 32, 700, 1, 2};
    chip8_type chip8 = {RUNNING, {0}, {0}, {0}, nullptr, {0}, 0, 0, 0, 0, {0}, 0, false};

    init_chip8(&chip8);

    while(chip8.state != QUIT){
        if(chip8.state  == PAUSED){continue;}

        t.start();

        for(int i = 0; i < config.insts_per_sec / 60; i++){
            emulate(&chip8,&config);
            printf("Emulating\n");
        }

        t.stop();

        const std::chrono::milliseconds elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(t.elapsed_time());
        const std::chrono::milliseconds emu_timing = 17ms;


        ThisThread::sleep_for(emu_timing > elapsed_time ? emu_timing - elapsed_time : std::chrono::milliseconds(0));


        if(chip8.draw){
            draw(&chip8, config); 
            chip8.draw = false;
        }
        update_timers(&chip8);
    }

    return 0;
}

Any Help with this is very much appreciated :).