Adding controller.

Adding logging.
Fixing lots of bugs.
This commit is contained in:
Selim Mustafaev 2023-09-18 00:10:41 +03:00
parent 40c7ff5882
commit 796828fa01
17 changed files with 299 additions and 46 deletions

View File

@ -22,7 +22,7 @@ add_executable(nes
src/Window.cpp src/Window.cpp
src/Window.h src/Window.h
src/Shifter.cpp src/Shifter.cpp
src/Shifter.h) src/Shifter.h src/Logger.cpp src/Logger.h src/Controller.cpp src/Controller.h src/SdlKeyboardController.cpp src/SdlKeyboardController.h)
find_package(SDL2 CONFIG REQUIRED) find_package(SDL2 CONFIG REQUIRED)
find_package(fmt REQUIRED) find_package(fmt REQUIRED)

View File

@ -1,5 +1,6 @@
#include "src/Nes.h" #include "src/Nes.h"
#include "src/Window.h" #include "src/Window.h"
#include "src/SdlKeyboardController.h"
#include <functional> #include <functional>
#include <chrono> #include <chrono>
@ -14,9 +15,10 @@ int main() {
window.setSize(nes::Ppu::SCREEN_WIDTH * 4, nes::Ppu::SCREEN_HEIGHT * 4); window.setSize(nes::Ppu::SCREEN_WIDTH * 4, nes::Ppu::SCREEN_HEIGHT * 4);
device.setNewFrameCallback(std::bind(&nes::SdlWindow::drawFrame, &window, _1)); device.setNewFrameCallback(std::bind(&nes::SdlWindow::drawFrame, &window, _1));
//device.insertCartridge("/home/selim/Downloads/nestest.nes"); device.connect(std::make_shared<SdlKeyboardController>());
device.insertCartridge("/home/selim/Downloads/smb.nes");
//device.insertCartridge("/Users/selim/Documents/nestest.nes"); //device.insertCartridge("/Users/selim/Documents/nestest.nes");
device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes"); //device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes");
SDL_Event e; SDL_Event e;
uint64_t cycles = 0; uint64_t cycles = 0;

View File

@ -23,6 +23,9 @@ namespace nes {
else if(address >= 0x8000) { else if(address >= 0x8000) {
return _cartridge->readPrg(address); return _cartridge->readPrg(address);
} }
else if(address == 0x4016) {
return _controller1->read();
}
return 0; return 0;
} }
@ -37,6 +40,9 @@ namespace nes {
else if(address >= 0x8000) { else if(address >= 0x8000) {
std::cout << "Cartridge write at address: " << address << std::endl; std::cout << "Cartridge write at address: " << address << std::endl;
} }
else if(address == 0x4016) {
_controller1->poll();
}
} }
void Bus::connect(std::shared_ptr<Cartridge> cartridge) { void Bus::connect(std::shared_ptr<Cartridge> cartridge) {
@ -46,4 +52,19 @@ namespace nes {
void Bus::connect(std::shared_ptr<Ppu> ppu) { void Bus::connect(std::shared_ptr<Ppu> ppu) {
_ppu = std::move(ppu); _ppu = std::move(ppu);
} }
void Bus::connect(std::shared_ptr<Controller> controller) {
_controller1 = std::move(controller);
}
// For debug
// Calc "hash" - just sum of the bytes of first page
// Can be useful for detecting change in ram
uint32_t Bus::zpHash() const {
uint32_t sum = 0;
for(size_t i = 0; i < 2048; ++i) {
sum += _ram[i];
}
return sum;
}
} }

View File

@ -7,6 +7,7 @@
#include "Cartridge.h" #include "Cartridge.h"
#include "Ppu.h" #include "Ppu.h"
#include "Controller.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -20,11 +21,14 @@ namespace nes {
void write(uint16_t address, uint8_t value); void write(uint16_t address, uint8_t value);
void connect(std::shared_ptr<Cartridge> cartridge); void connect(std::shared_ptr<Cartridge> cartridge);
void connect(std::shared_ptr<Ppu> ppu); void connect(std::shared_ptr<Ppu> ppu);
void connect(std::shared_ptr<Controller> controller);
[[nodiscard]] uint32_t zpHash() const;
private: private:
std::unique_ptr<uint8_t[]> _ram; std::unique_ptr<uint8_t[]> _ram;
std::shared_ptr<Cartridge> _cartridge; std::shared_ptr<Cartridge> _cartridge;
std::shared_ptr<Ppu> _ppu; std::shared_ptr<Ppu> _ppu;
std::shared_ptr<Controller> _controller1;
}; };
} }

18
src/Controller.cpp Normal file
View File

@ -0,0 +1,18 @@
//
// Created by selim on 9/17/23.
//
#include "Controller.h"
namespace nes {
Controller::Controller(): _data{} {
}
uint8_t Controller::read() {
uint8_t result = (_data & 0x80) > 0;
_data <<= 1;
return result;
}
}

36
src/Controller.h Normal file
View File

@ -0,0 +1,36 @@
//
// Created by selim on 9/17/23.
//
#ifndef NES_CONTROLLER_H
#define NES_CONTROLLER_H
#include <cstdint>
namespace nes {
class Controller {
public:
enum Key: uint8_t {
A = 7,
B = 6,
Select = 5,
Start = 4,
Up = 3,
Down = 2,
Left = 1,
Right = 0
};
public:
Controller();
uint8_t read();
virtual void poll() = 0;
protected:
uint8_t _data;
};
}
#endif //NES_CONTROLLER_H

View File

@ -75,7 +75,7 @@ namespace nes {
_instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false}; _instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false};
_instructions[0x81] = {"STA", &Cpu::STA, &Cpu::IZX, 6, false}; _instructions[0x81] = {"STA", &Cpu::STA, &Cpu::IZX, 6, false};
_instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false}; _instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false};
_instructions[0x91] = {"STA", &Cpu::STA, &Cpu::IZY, 5, true}; _instructions[0x91] = {"STA", &Cpu::STA, &Cpu::IZY, 6, false};
_instructions[0x95] = {"STA", &Cpu::STA, &Cpu::ZPX, 4, false}; _instructions[0x95] = {"STA", &Cpu::STA, &Cpu::ZPX, 4, false};
_instructions[0x99] = {"STA", &Cpu::STA, &Cpu::ABY, 5, false}; _instructions[0x99] = {"STA", &Cpu::STA, &Cpu::ABY, 5, false};
_instructions[0x9D] = {"STA", &Cpu::STA, &Cpu::ABX, 5, false}; _instructions[0x9D] = {"STA", &Cpu::STA, &Cpu::ABX, 5, false};
@ -174,34 +174,36 @@ namespace nes {
uint16_t hi = _bus->read(0xFFFD); uint16_t hi = _bus->read(0xFFFD);
PC = (hi << 8) | lo; PC = (hi << 8) | lo;
_ticks = 8;
} }
void Cpu::tick() { bool Cpu::tick() {
bool executed = false;
if(_ticks == 0) { if(_ticks == 0) {
uint8_t opcode = _bus->read(PC++); uint8_t opcode = _bus->read(PC++);
auto instruction = _instructions[opcode]; auto instruction = _instructions[opcode];
_currentOpcode = opcode;
if(instruction.getAddress == nullptr) { if(instruction.getAddress == nullptr) {
std::cout << "Unknown instruction: " << (int)opcode << std::endl; std::cout << "Unknown instruction: " << (int)opcode << std::endl;
return; return false;
} }
auto args = (this->*instruction.getAddress)(); auto args = (this->*instruction.getAddress)();
(this->*instruction.process)(args); (this->*instruction.process)(args);
// auto str = std::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}]", instruction.name, opcode, PC, SP, A, X, Y, _ticks += instruction.cycles;
// (int)getFlag(Negative), (int)getFlag(Overflow), (int)getFlag(Break), (int)getFlag(DecimalMode), (int)getFlag(InterruptDisable), (int)getFlag(Zero), (int)getFlag(Carry));
// std::cout << str << std::endl;
//std::cout << instruction.name << std::hex << ", OpCode: " << (int)opcode << ", PC: " << (int)PC << ", SP: " << (int)SP << ", A: " << (int)A << ", X: " << (int)X << ", Y: " << (int)Y << std::endl;
_ticks = instruction.cycles;
if(instruction.variableCycles) { if(instruction.variableCycles) {
_ticks += args.cycles; _ticks += args.cycles;
} }
executed = true;
} }
_ticks--; _ticks--;
return executed;
} }
void Cpu::nmi() { void Cpu::nmi() {
@ -220,6 +222,21 @@ namespace nes {
_ticks = 8; _ticks = 8;
} }
std::string Cpu::state() const {
return std::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}], H: {:08X}",
_instructions[_currentOpcode].name,
_currentOpcode,
PC, SP, A, X, Y,
(int)getFlag(Negative),
(int)getFlag(Overflow),
(int)getFlag(Break),
(int)getFlag(DecimalMode),
(int)getFlag(InterruptDisable),
(int)getFlag(Zero),
(int)getFlag(Carry),
_bus->zpHash());
}
void Cpu::setFlag(CpuFlags flag, bool value) { void Cpu::setFlag(CpuFlags flag, bool value) {
if(value) { if(value) {
flags |= flag; flags |= flag;

View File

@ -42,12 +42,13 @@ namespace nes {
public: public:
Cpu(); Cpu();
void reset(); void reset();
void tick(); bool tick();
void setFlag(CpuFlags flag, bool value); void setFlag(CpuFlags flag, bool value);
bool getFlag(CpuFlags flag) const; bool getFlag(CpuFlags flag) const;
void setStartAddress(uint16_t address); void setStartAddress(uint16_t address);
Bus* bus() const; Bus* bus() const;
void nmi(); void nmi();
std::string state() const;
private: private:
size_t _ticks; size_t _ticks;
@ -64,6 +65,10 @@ namespace nes {
uint8_t SP; // Stack Pointer uint8_t SP; // Stack Pointer
uint8_t flags; uint8_t flags;
private:
// Debug info
uint8_t _currentOpcode;
private: private:
void branch(InstructionArgs args); void branch(InstructionArgs args);

35
src/Logger.cpp Normal file
View File

@ -0,0 +1,35 @@
//
// Created by selim on 9/16/23.
//
#include "Logger.h"
#include <cstring>
#include <fstream>
#include <format>
namespace nes {
Logger::Logger(size_t size): _size{size}, _offset{0} {
_data = std::make_unique<uint8_t[]>(size);
}
void Logger::addLine(uint64_t cycle, std::string_view line) {
if((_offset + line.size() + 10) >= _size) {
return;
}
std::string cycleString = std::format("[{:06d}] ", cycle);
std::memcpy(_data.get() + _offset, cycleString.data(), cycleString.size());
_offset += cycleString.size();
std::memcpy(_data.get() + _offset, line.data(), line.size());
_offset += line.size();
_data[_offset++] = '\n';
}
void Logger::dump(const fs::path &path) {
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<char*>(_data.get()), static_cast<std::streamsize>(_offset));
}
}

30
src/Logger.h Normal file
View File

@ -0,0 +1,30 @@
//
// Created by selim on 9/16/23.
//
#ifndef NES_LOGGER_H
#define NES_LOGGER_H
#include <cstddef>
#include <memory>
#include <filesystem>
namespace nes {
namespace fs = std::filesystem;
class Logger {
public:
explicit Logger(size_t size);
void addLine(uint64_t cycle, std::string_view line);
void dump(const fs::path& path);
private:
size_t _size;
size_t _offset;
std::unique_ptr<uint8_t[]> _data;
};
}
#endif //NES_LOGGER_H

View File

@ -10,7 +10,7 @@
namespace nes { namespace nes {
Nes::Nes(): _cycles{} { Nes::Nes(): _cycles{}, _logger(100*1024*1024) {
_cpu = std::make_unique<Cpu>(); _cpu = std::make_unique<Cpu>();
_ppu = std::make_shared<Ppu>(); _ppu = std::make_shared<Ppu>();
} }
@ -23,6 +23,10 @@ namespace nes {
reset(address); reset(address);
} }
void Nes::connect(std::shared_ptr<Controller> controller) {
_cpu->bus()->connect(std::move(controller));
}
void Nes::reset(std::optional<uint16_t> address) { void Nes::reset(std::optional<uint16_t> address) {
_cpu->reset(); _cpu->reset();
_ppu->reset(); _ppu->reset();
@ -36,15 +40,27 @@ namespace nes {
} }
void Nes::tick() { void Nes::tick() {
bool needInterrupt = _ppu->tick(); bool needInterrupt = _ppu->tick();
// _logger.addLine(_cycles, _ppu->state());
if(_cycles % 3 == 0) { if(_cycles % 3 == 0) {
_cpu->tick(); bool instructionExecuted = _cpu->tick();
// if(instructionExecuted) {
// _logger.addLine(_cycles, _cpu->state());
// }
} }
if(needInterrupt) { if(needInterrupt) {
_cpu->nmi(); _cpu->nmi();
// _logger.addLine(_cycles, "NMI");
// _logger.addLine(_cycles, _cpu->state());
} }
// if(_cycles == 500000) {
// _logger.dump("/home/selim/Documents/log.txt");
// }
_cycles++; _cycles++;
} }

View File

@ -8,6 +8,7 @@
#include "Cpu.h" #include "Cpu.h"
#include "Cartridge.h" #include "Cartridge.h"
#include "Ppu.h" #include "Ppu.h"
#include "Logger.h"
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
@ -20,6 +21,7 @@ namespace nes {
public: public:
Nes(); Nes();
void insertCartridge(const fs::path& path, std::optional<uint16_t> address = std::nullopt); void insertCartridge(const fs::path& path, std::optional<uint16_t> address = std::nullopt);
void connect(std::shared_ptr<Controller> controller);
void reset(std::optional<uint16_t> address = std::nullopt); void reset(std::optional<uint16_t> address = std::nullopt);
void setNewFrameCallback(std::function<void(const Pixel*)> onNewFrame); void setNewFrameCallback(std::function<void(const Pixel*)> onNewFrame);
void tick(); void tick();
@ -29,6 +31,7 @@ namespace nes {
std::unique_ptr<Cpu> _cpu; std::unique_ptr<Cpu> _cpu;
std::shared_ptr<Ppu> _ppu; std::shared_ptr<Ppu> _ppu;
std::shared_ptr<Cartridge> _cartridge; std::shared_ptr<Cartridge> _cartridge;
Logger _logger;
}; };
} }

View File

@ -3,6 +3,7 @@
// //
#include "Ppu.h" #include "Ppu.h"
#include <format>
namespace nes { namespace nes {
@ -80,8 +81,13 @@ namespace nes {
bool Ppu::tick() { bool Ppu::tick() {
_needEmitNmi = false;
if(_scanline == 241 && _column == 1) { if(_scanline == 241 && _column == 1) {
_status.verticalBlank = 1; _status.verticalBlank = 1;
if(_control.enableNmi) {
_needEmitNmi = true;
}
} }
// All visible scanlines // All visible scanlines
@ -97,8 +103,10 @@ namespace nes {
// Preloading some data ahead of time // Preloading some data ahead of time
if ((_column >= 2 && _column < 258) || (_column >= 321 && _column < 338)) { if ((_column >= 2 && _column < 258) || (_column >= 321 && _column < 338)) {
_bgPatternShifter.shift(); if(_mask.renderBackground) {
_bgAttribShifter.shift(); _bgPatternShifter.shift();
_bgAttribShifter.shift();
}
prepareNextBgTile(_column); prepareNextBgTile(_column);
} }
@ -125,21 +133,15 @@ namespace nes {
uint8_t colorIndex = 0; uint8_t colorIndex = 0;
uint8_t palette = 0; uint8_t palette = 0;
if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) { if(_mask.renderBackground) {
colorIndex = _bgPatternShifter.getValue(_fineX);
palette = _bgAttribShifter.getValue(_fineX);
Pixel pixel = getColor(palette, colorIndex);
if(_mask.renderBackground) { _curPalette = palette;
colorIndex = _bgPatternShifter.getValue(_fineX); _curColorIndex = colorIndex;
palette = _bgAttribShifter.getValue(_fineX);
Pixel pixel = getColor(palette, colorIndex);
// For debugging
if (colorIndex != 0) {
pixel = Pixel(0xFF, 0xFF, 0xFF);
}
else {
pixel = Pixel();
}
if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) {
setPixel(_scanline, _column, pixel); setPixel(_scanline, _column, pixel);
} }
} }
@ -155,7 +157,7 @@ namespace nes {
} }
} }
return _status.verticalBlank && _control.enableNmi; return _needEmitNmi;
} }
uint8_t Ppu::read(uint16_t address) { uint8_t Ppu::read(uint16_t address) {
@ -204,7 +206,7 @@ namespace nes {
_tRamAddress.value = (_tRamAddress.value & 0xFF00) | value; _tRamAddress.value = (_tRamAddress.value & 0xFF00) | value;
_vRamAddress.value = _tRamAddress.value; _vRamAddress.value = _tRamAddress.value;
} else { } else {
_tRamAddress.value = ((value & 0x3F) << 8) | (_tRamAddress.value & 0x00FF); _tRamAddress.value = (((value & 0x3F) << 8) | (_tRamAddress.value & 0x00FF));
} }
_addressWriteInProgress = !_addressWriteInProgress; _addressWriteInProgress = !_addressWriteInProgress;
break; break;
@ -273,9 +275,6 @@ namespace nes {
else if(address >= 0x2000 && address < 0x3F00) { else if(address >= 0x2000 && address < 0x3F00) {
address &= 0x0FFF; address &= 0x0FFF;
_nameTable[address & 0x03FF] = value; _nameTable[address & 0x03FF] = value;
if (value == 32) {
int x = 0;
}
} }
else if(address >= 0x3F00 && address < 0x4000) { else if(address >= 0x3F00 && address < 0x4000) {
address &= 0x1F; address &= 0x1F;
@ -325,7 +324,7 @@ namespace nes {
uint16_t address = (_control.patternBackground << 12) uint16_t address = (_control.patternBackground << 12)
| ((uint16_t)_nextBgTile.id << 4) | ((uint16_t)_nextBgTile.id << 4)
| (_vRamAddress.fineY + 8); | (_vRamAddress.fineY + 8);
_nextBgTile.lsb = internalRead(address); _nextBgTile.msb = internalRead(address);
} }
break; break;
case 7: case 7:
@ -367,17 +366,43 @@ namespace nes {
} }
void Ppu::updateScrollX() { void Ppu::updateScrollX() {
if(_mask.renderBackground && !_mask.renderSprites) { if(_mask.renderBackground || _mask.renderSprites) {
_vRamAddress.nameTableX = _tRamAddress.nameTableX; _vRamAddress.nameTableX = _tRamAddress.nameTableX;
_vRamAddress.coarseX = _tRamAddress.coarseX; _vRamAddress.coarseX = _tRamAddress.coarseX;
} }
} }
void Ppu::updateScrollY() { void Ppu::updateScrollY() {
if(_mask.renderBackground && !_mask.renderSprites) { if(_mask.renderBackground || _mask.renderSprites) {
_vRamAddress.nameTableY =_tRamAddress.nameTableY; _vRamAddress.nameTableY =_tRamAddress.nameTableY;
_vRamAddress.coarseY = _tRamAddress.coarseY; _vRamAddress.coarseY = _tRamAddress.coarseY;
_vRamAddress.fineY = _tRamAddress.fineY; _vRamAddress.fineY = _tRamAddress.fineY;
} }
} }
std::string Ppu::state() const {
auto palettes = (uint32_t*)_paletteTable.get();
return std::format("X:{:03d}, Y:{:03d}, V:{:d}, vR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], tR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], FX:{:d}, AL:{:d}, PI:{:02X}, CI:{:02X}, ASL:{:04X}, ASH:{:04X}",
_column,
_scanline,
_status.verticalBlank,
_vRamAddress.coarseX,
_vRamAddress.coarseY,
_vRamAddress.nameTableX,
_vRamAddress.nameTableY,
_vRamAddress.fineY,
_tRamAddress.coarseX,
_tRamAddress.coarseY,
_tRamAddress.nameTableX,
_tRamAddress.nameTableY,
_tRamAddress.fineY,
_fineX,
_addressWriteInProgress,
_curPalette,
_curColorIndex,
_bgAttribShifter._lo,
_bgAttribShifter._hi);
}
} }

View File

@ -80,12 +80,12 @@ namespace nes {
union LoopyRegister { union LoopyRegister {
struct { struct {
uint8_t coarseX : 5; uint16_t coarseX : 5;
uint8_t coarseY : 5; uint16_t coarseY : 5;
uint8_t nameTableX : 1; uint16_t nameTableX : 1;
uint8_t nameTableY : 1; uint16_t nameTableY : 1;
uint8_t fineY : 3; uint16_t fineY : 3;
uint8_t unused : 1; uint16_t unused : 1;
}; };
uint16_t value; uint16_t value;
}; };
@ -105,6 +105,7 @@ namespace nes {
void setPixel(uint16_t row, uint16_t column, Pixel pixel); void setPixel(uint16_t row, uint16_t column, Pixel pixel);
void connect(std::shared_ptr<Cartridge> cartridge); void connect(std::shared_ptr<Cartridge> cartridge);
void reset(); void reset();
std::string state() const;
public: public:
std::function<void(const Pixel*)> onNewFrame; std::function<void(const Pixel*)> onNewFrame;
@ -134,6 +135,7 @@ namespace nes {
TileInfo _nextBgTile; TileInfo _nextBgTile;
Shifter _bgPatternShifter; Shifter _bgPatternShifter;
Shifter _bgAttribShifter; Shifter _bgAttribShifter;
bool _needEmitNmi;
private: private:
std::unique_ptr<uint8_t[]> _nameTable; std::unique_ptr<uint8_t[]> _nameTable;
@ -143,6 +145,10 @@ namespace nes {
private: private:
std::unique_ptr<Pixel[]> _frameBuffer; std::unique_ptr<Pixel[]> _frameBuffer;
std::shared_ptr<Cartridge> _cartridge; std::shared_ptr<Cartridge> _cartridge;
private: // For debug
uint8_t _curPalette;
uint8_t _curColorIndex;
}; };
} }

View File

@ -0,0 +1,19 @@
//
// Created by selim on 9/17/23.
//
#include "SdlKeyboardController.h"
#include <SDL.h>
void SdlKeyboardController::poll() {
auto state = SDL_GetKeyboardState(nullptr);
_data = 0;
_data |= state[SDL_SCANCODE_Z] << Key::A;
_data |= state[SDL_SCANCODE_X] << Key::B;
_data |= state[SDL_SCANCODE_TAB] << Key::Select;
_data |= state[SDL_SCANCODE_RETURN] << Key::Start;
_data |= state[SDL_SCANCODE_UP] << Key::Up;
_data |= state[SDL_SCANCODE_DOWN] << Key::Down;
_data |= state[SDL_SCANCODE_LEFT] << Key::Left;
_data |= state[SDL_SCANCODE_RIGHT] << Key::Right;
}

View File

@ -0,0 +1,16 @@
//
// Created by selim on 9/17/23.
//
#ifndef NES_SDLKEYBOARDCONTROLLER_H
#define NES_SDLKEYBOARDCONTROLLER_H
#include "Controller.h"
class SdlKeyboardController: public nes::Controller {
public:
void poll() override;
};
#endif //NES_SDLKEYBOARDCONTROLLER_H

View File

@ -16,7 +16,7 @@ namespace nes {
void shift(); void shift();
uint16_t getValue(uint8_t offset); uint16_t getValue(uint8_t offset);
private: public:
uint16_t _lo; uint16_t _lo;
uint16_t _hi; uint16_t _hi;
}; };