145 lines
4.9 KiB
C++
145 lines
4.9 KiB
C++
/**
|
||
* @file YMF262-HAL.hpp
|
||
* @brief Hardware Abstraction Layer for the Yamaha YMF262 (OPL3).
|
||
*/
|
||
|
||
#ifndef YMF262_HAL_HPP
|
||
#define YMF262_HAL_HPP
|
||
|
||
#include <cstdint>
|
||
#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 GPIOPolicy>
|
||
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 |