/** * @file YMF262-HAL.hpp * @brief Hardware Abstraction Layer for the Yamaha YMF262 (OPL3). */ #ifndef YMF262_HAL_HPP #define YMF262_HAL_HPP #include #include "GPIO.hpp" #include "YMF262-Types.hpp" /** * @brief Hardware Abstraction Layer for the Yamaha YMF262 (OPL3) integrated circuit. * * Choreographs the control protocol between the synthesizer IC and the * microcontroller. The electrical handling of the pins is delegated to the * GPIOPolicy (template parameter), allowing the logic to be tested with a * mock without real hardware. * * @tparam GPIOPolicy Class type implementing the verbs for electrical pin control * (set_a0, set_a1, set_cs, set_wr, set_ic, set_data_bus, delay_ticks). */ template class YMF262_HAL{ private: GPIOPolicy& _gpio; ///< Reference (not a copy) so tests can inspect the same policy instance uint32_t _t_icw_ticks; ///< Reset Pulse Width uint32_t _t_as_ticks; ///< Address Setup Time uint32_t _t_ah_ticks; ///< Address Hold Time uint32_t _t_csw_ticks; ///< Chip Select Write Width uint32_t _t_csr_ticks; ///< Chip Select Read Width uint32_t _t_ww_ticks; ///< Write Pulse Width uint32_t _t_wds_ticks; ///< Write Data Setup Time uint32_t _t_wdh_ticks; ///< Write Data Hold Time uint32_t _t_rw_ticks; ///< Read Pulse Width uint32_t _t_acc_ticks; ///< Read Data Access Time uint32_t _t_rdh_ticks; ///< Read Data Hold Time uint32_t _t_recovery_ticks; ///< Recovery between writes (32 φM cycles of the chip) /** * @brief Performs a single bus write cycle (one of the two in a register write). * @param bank Register bank (drives A1). * @param port ADDRESS or DATA phase (drives A0). * @param data Byte to place on the data bus. */ void write_bus(Bank bank, Port port, uint8_t data){ _gpio.set_a0(port); _gpio.set_a1(bank); _gpio.delay_ticks(_t_as_ticks); _gpio.set_cs(State::ACTIVE); _gpio.set_wr(State::ACTIVE); _gpio.set_data_bus(data); _gpio.delay_ticks(_t_wds_ticks); _gpio.set_wr(State::INACTIVE); _gpio.set_cs(State::INACTIVE); _gpio.delay_ticks(_t_wdh_ticks); _gpio.delay_ticks(_t_recovery_ticks); }; public: YMF262_HAL(GPIOPolicy& policy, uint32_t opl_clock, uint32_t cpu_clock):_gpio(policy){ _t_icw_ticks = 400 * (cpu_clock/ opl_clock); _t_recovery_ticks = 32 * (cpu_clock / opl_clock); double ticks_per_ns = (double)cpu_clock / 1.0e9; _t_as_ticks = (uint32_t)(10 * ticks_per_ns); _t_ah_ticks = (uint32_t)(10 * ticks_per_ns); _t_csw_ticks = (uint32_t)(100 * ticks_per_ns); _t_ww_ticks = (uint32_t)(100 * ticks_per_ns); _t_wds_ticks = (uint32_t)(10 * ticks_per_ns); _t_wdh_ticks = (uint32_t)(20 * ticks_per_ns); _t_csr_ticks = (uint32_t)(150 * ticks_per_ns); _t_rw_ticks = (uint32_t)(150 * ticks_per_ns); _t_acc_ticks = (uint32_t)(150 * ticks_per_ns); _t_rdh_ticks = (uint32_t)(10 * ticks_per_ns); }; /** * @brief Boots the chip: resets it, then selects the operating mode. * * Order matters: the chip must be reset first (known state), then the * mode is set explicitly. After reset the chip defaults to OPL2, so the * mode is always set deliberately rather than relying on the default. * * @param mode Operating mode to configure after reset. */ void initialize(OPLMode mode){ initial_clear(); set_OPL_Mode(mode); } /** * @brief Writes a value to a YMF262 register. * * Performs the chip's two-cycle write protocol: first the register * address is latched, then the data value. * * @param bank Register bank to target (BANK_0 or BANK_1). * @param reg Register address (0x00–0xFF). * @param data Value to write. */ void write(Bank bank, uint8_t reg, uint8_t data){ write_bus(bank,Port::ADDRESS,reg); write_bus(bank,Port::DATA,data); }; /** * @brief Selects OPL2 (compatibility) or OPL3 mode. * * Writes the OPL3-enable bit in register 0x105 (bank ARRAY1). * OPL3 mode unlocks the 4-operator channels and extra waveforms. * * @param mode OPLMode::OPL2 or OPLMode::OPL3. */ void set_OPL_Mode(OPLMode mode){ write(Bank::BANK_1, 0x105, (mode == OPLMode::OPL3) ? 0x01 : 0x00); }; /** * @brief Resets the chip via the /IC (Initial Clear) line. * * Holds /IC active for the required Initial Clear Width, then releases it. * Leaves the chip in a known default state (OPL2 mode). */ void initial_clear(){ _gpio.set_ic(State::ACTIVE); _gpio.delay_ticks(_t_icw_ticks); _gpio.set_ic(State::INACTIVE); _gpio.delay_ticks(_t_recovery_ticks); }; }; #endif