Compare commits
19
Commits
297cf1a201
...
master
@@ -0,0 +1,2 @@
|
|||||||
|
build
|
||||||
|
Docs
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
+6
-1
@@ -29,7 +29,10 @@ project(${CMAKE_PROJECT_NAME})
|
|||||||
message("Build type: " ${CMAKE_BUILD_TYPE})
|
message("Build type: " ${CMAKE_BUILD_TYPE})
|
||||||
|
|
||||||
# Enable CMake support for ASM and C languages
|
# Enable CMake support for ASM and C languages
|
||||||
enable_language(C ASM)
|
enable_language(C CXX ASM)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Create an executable object type
|
# Create an executable object type
|
||||||
add_executable(${CMAKE_PROJECT_NAME})
|
add_executable(${CMAKE_PROJECT_NAME})
|
||||||
@@ -45,11 +48,13 @@ target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
|
|||||||
# Add sources to executable
|
# Add sources to executable
|
||||||
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
||||||
# Add user sources here
|
# Add user sources here
|
||||||
|
"Lib/YMF262-HAL/Src/YMF262-HAL.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add include paths
|
# Add include paths
|
||||||
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
|
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
|
||||||
# Add user defined include paths
|
# Add user defined include paths
|
||||||
|
"Lib/YMF262-HAL/Inc"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add project symbols (macros)
|
# Add project symbols (macros)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @file GPIO.hpp
|
||||||
|
* @brief Logical types for the YMF262 pin-control interface (used by GPIO policies).
|
||||||
|
*/
|
||||||
|
#ifndef GPIO_HPP
|
||||||
|
#define GPIO_HPP
|
||||||
|
|
||||||
|
/** @brief Logical activation state of a control line (CS, WR, IC...). */
|
||||||
|
enum class State{
|
||||||
|
ACTIVE, ///< Line asserted (the policy maps this to the correct electrical level)
|
||||||
|
INACTIVE ///< Line deasserted
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Selects what the data bus carries (drives the A0 line). */
|
||||||
|
enum class Port{
|
||||||
|
DATA, ///< Bus carries a data value
|
||||||
|
ADDRESS ///< Bus carries a register address
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Selects the register bank / array (drives the A1 line). */
|
||||||
|
enum class Bank{
|
||||||
|
BANK_0, ///< First register array
|
||||||
|
BANK_1, ///< Second register array (OPL3 extended registers)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef YMF262_HAL_H
|
||||||
|
#define YMF262_HAL_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct YMF262_HAL_Handle YMF262_HAL_Handle;
|
||||||
|
|
||||||
|
YMF262_HAL_Handle* YMF262_HAL_Create(uint32_t opl_clock, uint32_t cpu_clock);
|
||||||
|
void YMF262_HAL_Destroy(YMF262_HAL_Handle* handle);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* @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.delay_ticks(_t_ww_ticks-_t_wds_ticks);
|
||||||
|
_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_ah_ticks);
|
||||||
|
_gpio.delay_ticks(_t_wdh_ticks-_t_ah_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
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @file YMF262-Types.hpp
|
||||||
|
* @brief Chip-level types for the YMF262 (operating mode, and future chip concepts).
|
||||||
|
*/
|
||||||
|
#ifndef YMF262_TYPES_HPP
|
||||||
|
#define YMF262_TYPES_HPP
|
||||||
|
|
||||||
|
/** @brief YMF262 operating mode. */
|
||||||
|
enum class OPLMode {
|
||||||
|
OPL3, ///< Full OPL3: 4-operator channels and extended waveforms
|
||||||
|
OPL2 ///< OPL2 compatibility mode (chip default after reset)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#include "YMF262-HAL.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# YMF-262 Firmware for Nucleo F722ZE Board and STM32F7 MCU
|
||||||
|
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build a Flashable .elf file issue the following commands:
|
||||||
|
|
||||||
|
- Debug:
|
||||||
|
|
||||||
|
```cmake --preset Debug && cmake --build --preset Debug```
|
||||||
|
|
||||||
|
- Release:
|
||||||
|
|
||||||
|
```cmake --preset Release && cmake --build --preset Release```
|
||||||
|
|
||||||
|
## Flashing:
|
||||||
|
To flash the .elf file please issue the following command:
|
||||||
|
|
||||||
|
```openocd -f interface/stlink.cfg -f target/stm32f7x.cfg -c "program YMF262_Devboard_Firmware.elf verify reset exit"```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The HAL logic is unit-tested on the host (x86) using GoogleTest, with a `FakeGpio` mock that records the GPIO operations instead of touching hardware. This verifies the YMF262 write choreography (two-cycle address/data sequence and timing) without needing the physical chip.
|
||||||
|
|
||||||
|
To build and run the tests:
|
||||||
|
|
||||||
|
````cd Tests
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build
|
||||||
|
./build/run_tests```
|
||||||
|
|
||||||
|
GoogleTest is fetched automatically via CMake FetchContent on the first configure (requires internet).
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
project(YMF262_Tests CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||||
|
GIT_TAG v1.15.2
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
|
||||||
|
add_executable(run_tests test_write.cpp)
|
||||||
|
|
||||||
|
target_include_directories(run_tests PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../Lib/YMF262-HAL/Inc
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(run_tests PRIVATE gtest_main)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(run_tests)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef FAKE_GPIO_HPP
|
||||||
|
#define FAKE_GPIO_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "GPIO.hpp"
|
||||||
|
|
||||||
|
struct FakeGpio {
|
||||||
|
std::vector<std::string> operations;
|
||||||
|
std::vector<uint8_t> bus_values;
|
||||||
|
std::vector<uint32_t> delays;
|
||||||
|
|
||||||
|
|
||||||
|
void set_data_bus(uint8_t val) {
|
||||||
|
operations.push_back("data_bus");
|
||||||
|
bus_values.push_back(val);
|
||||||
|
}
|
||||||
|
void set_a0(Port port) {
|
||||||
|
operations.push_back(port == Port::ADDRESS ? "a0 ADDRESS" : "a0 DATA");
|
||||||
|
}
|
||||||
|
void set_a1(Bank bank) {
|
||||||
|
operations.push_back(bank == Bank::BANK_0 ? "a1 BANK_0" : "a1 BANK_1");
|
||||||
|
}
|
||||||
|
void set_cs(State s) {
|
||||||
|
operations.push_back(s == State::ACTIVE ? "cs ACTIVE" : "cs INACTIVE");
|
||||||
|
}
|
||||||
|
void set_wr(State s) {
|
||||||
|
operations.push_back(s == State::ACTIVE ? "wr ACTIVE" : "wr INACTIVE");
|
||||||
|
}
|
||||||
|
void set_ic(State s) {
|
||||||
|
operations.push_back(s == State::ACTIVE ? "ic ACTIVE" : "ic INACTIVE");
|
||||||
|
}
|
||||||
|
void set_rd(State s) {
|
||||||
|
operations.push_back(s == State::ACTIVE ? "rd ACTIVE" : "rd INACTIVE");
|
||||||
|
}
|
||||||
|
void delay_ticks(uint32_t n) {
|
||||||
|
operations.push_back("delay_ticks");
|
||||||
|
delays.push_back(n);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include "YMF262-HAL.hpp"
|
||||||
|
#include "FakeGPIO.hpp"
|
||||||
|
|
||||||
|
class YMF262_HALTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
FakeGpio fake;
|
||||||
|
YMF262_HAL<FakeGpio> hal{fake, 14318000, 216000000};
|
||||||
|
// ^ fake e hal criados ANTES de cada teste, prontos pra usar
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest, TestWriteRun) {
|
||||||
|
hal.write(Bank::BANK_0, 0x40, 0x20);
|
||||||
|
|
||||||
|
EXPECT_THAT(fake.operations, testing::ElementsAre(
|
||||||
|
"a0 ADDRESS",
|
||||||
|
"a1 BANK_0",
|
||||||
|
"delay_ticks",
|
||||||
|
"cs ACTIVE",
|
||||||
|
"wr ACTIVE",
|
||||||
|
"delay_ticks",
|
||||||
|
"data_bus",
|
||||||
|
"delay_ticks",
|
||||||
|
"wr INACTIVE",
|
||||||
|
"cs INACTIVE",
|
||||||
|
"delay_ticks",
|
||||||
|
"delay_ticks",
|
||||||
|
"delay_ticks",
|
||||||
|
"a0 DATA",
|
||||||
|
"a1 BANK_0",
|
||||||
|
"delay_ticks",
|
||||||
|
"cs ACTIVE",
|
||||||
|
"wr ACTIVE",
|
||||||
|
"delay_ticks",
|
||||||
|
"data_bus",
|
||||||
|
"delay_ticks",
|
||||||
|
"wr INACTIVE",
|
||||||
|
"cs INACTIVE",
|
||||||
|
"delay_ticks",
|
||||||
|
"delay_ticks",
|
||||||
|
"delay_ticks"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest, TestWriteDataBusValues) {
|
||||||
|
hal.write(Bank::BANK_0, 0x40, 0x20);
|
||||||
|
EXPECT_THAT(fake.bus_values, testing::ElementsAre(0x40, 0x20));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest, TestWriteBankValue) {
|
||||||
|
hal.write(Bank::BANK_1, 0x40, 0x20);
|
||||||
|
// verifica que aparece "a1 BANK_1" nas operações, não "a1 BANK_0"
|
||||||
|
EXPECT_THAT(fake.operations, testing::Contains("a1 BANK_1"));
|
||||||
|
EXPECT_THAT(fake.operations, testing::Not(testing::Contains("a1 BANK_0")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest,TestWriteSetInitialClear) {
|
||||||
|
|
||||||
|
hal.initial_clear();
|
||||||
|
EXPECT_THAT(fake.operations, testing::ElementsAre(
|
||||||
|
"ic ACTIVE",
|
||||||
|
"delay_ticks",
|
||||||
|
"ic INACTIVE",
|
||||||
|
"delay_ticks"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest, TestSetOPLModeToOPL3) {
|
||||||
|
hal.set_OPL_Mode(OPLMode::OPL3);
|
||||||
|
EXPECT_THAT(fake.bus_values, testing::ElementsAre(0x05, 0x01));
|
||||||
|
EXPECT_THAT(fake.operations, testing::Contains("a1 BANK_1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(YMF262_HALTest, TestSetOPLModeToOPL2) {
|
||||||
|
hal.set_OPL_Mode(OPLMode::OPL2);
|
||||||
|
EXPECT_THAT(fake.bus_values, testing::ElementsAre(0x05, 0x00));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user