Compare commits

..
17 Commits
Author SHA1 Message Date
Gustavo Henrique Santos Souza de Miranda 86e13c1c32 Add unit tests for writing order, bus values, bank, initial_clear and OPL mode; fix OPL register address 2026-06-05 03:14:05 -03:00
Gustavo Henrique Santos Souza de Miranda 997fd7fb6e Improve the Fake GPIO Class and started writing tests for the Write Function of the HAL 2026-06-05 02:03:40 -03:00
Gustavo Henrique Santos Souza de Miranda 4e4e25d005 Refine write_bus timing: position data within the CS / WR window using datasheet intervals 2026-06-04 21:06:50 -03:00
Gustavo Henrique Santos Souza de Miranda 30bfe2bc00 Add Doxygen Configuration to generate documentation and also update the gitignore to exclude Docs folder generated by the Doxygen 2026-06-03 18:25:41 -03:00
Gustavo Henrique Santos Souza de Miranda 9909c9f222 Add Doxygen documentation comments to the HAL, GPIO and chip types 2026-06-03 18:10:58 -03:00
Gustavo Henrique Santos Souza de Miranda 638883bf39 Add a Initialize method on the YMF262-HAL.hpp to initialize he IC with the proper order 2026-06-03 15:07:04 -03:00
Gustavo Henrique Santos Souza de Miranda 53f5251f3d Update the YMF262-HAL.hpp to add the initial_clear function to clear the YMF262 ic 2026-06-03 15:00:38 -03:00
Gustavo Henrique Santos Souza de Miranda ec589d929a Update the timing for the delays and also create the Write method on the HAL to choreograph the writing process in the YMF262 and create the Set_OPL_Mode , and also created the YMF262-Types.hpp to hold types used by the YMF262 2026-06-03 14:13:50 -03:00
Gustavo Henrique Santos Souza de Miranda 17b754c864 Update README.md with testing instructions and some grammar improvements 2026-06-03 12:32:22 -03:00
Gustavo Henrique Santos Souza de Miranda 2172ed0735 Create write unit test to check the proper choreography and delays, and add CMakeLists.txt for compiling the unit tests 2026-06-03 12:25:03 -03:00
Gustavo Henrique Santos Souza de Miranda 8e4fceec7e Create the FakeGPIO.hpp to be used as Mock for testing and Update the timing for the delays and also create the Write method on the HAL to choreograph the writing process in the YMF262 2026-06-03 12:23:00 -03:00
Gustavo Henrique Santos Souza de Miranda 7898563f39 Add the write method on the YMF262 hal and cretead a policy based system to manipulate gpio 2026-06-03 08:59:15 -03:00
Gustavo Henrique Santos Souza de Miranda 682f8e1753 Updated the gitignore to properly ignore the build folder 2026-06-03 02:10:52 -03:00
Gustavo Henrique Santos Souza de Miranda 8010c9355b Started Adding Template on YMF262 HAL to allow GPIO Manipulation thru policies 2026-06-01 20:01:49 -03:00
Gustavo Henrique Santos Souza de Miranda 92b9f02577 Start Implenting the YMF262 HAL layer 2026-04-29 22:55:41 -03:00
Gustavo Henrique Santos Souza de Miranda 49fd6664cc Create The Basic Skeleton for the YMF262 HAL 2026-04-29 17:40:09 -03:00
Gustavo Henrique Santos Souza de Miranda e7d5b47ddf Update README.md 2026-04-29 17:02:23 -03:00
13 changed files with 3428 additions and 7 deletions
+2 -1
View File
@@ -1 +1,2 @@
./build build
Docs
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -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)
+3037
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -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
+20
View File
@@ -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
+147
View File
@@ -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
+14
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#include "YMF262-HAL.hpp"
+21 -4
View File
@@ -1,7 +1,4 @@
# YMF-262 Firmware for Nucleo F722RE Board and STM32F7 MCU # YMF-262 Firmware for Nucleo F722ZE Board and STM32F7 MCU
## Building ## Building
@@ -16,3 +13,23 @@ To build a Flashable .elf file issue the following commands:
```cmake --preset Release && cmake --build --preset 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).
````
+25
View File
@@ -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)
+43
View File
@@ -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
+80
View File
@@ -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));
}