From 2f7f0a6bdfc63a2e41878f9637b03b0baa8a0255 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sun, 17 Dec 2023 12:57:06 +0800 Subject: [PATCH] example: add maple vibrator example --- example/example.mk | 10 ++ example/maple_controller.cpp | 12 +-- example/maple_vibrator.cpp | 192 ++++++++++++++++++++++++++++++++++ maple/maple_bus_ft8.hpp | 14 ++- regs/gen/maple_data_format.py | 2 + regs/maple_bus_ft8.csv | 10 +- regs/maple_bus_ft8.ods | Bin 9731 -> 11094 bytes 7 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 example/maple_vibrator.cpp diff --git a/example/example.mk b/example/example.mk index da693a3..55f2cdd 100644 --- a/example/example.mk +++ b/example/example.mk @@ -107,3 +107,13 @@ MAPLE_WINK_OBJ = \ example/maple_wink.elf: LDSCRIPT = $(LIB)/alt.lds example/maple_wink.elf: $(START_OBJ) $(MAPLE_WINK_OBJ) + +MAPLE_VIBRATOR_OBJ = \ + example/maple_vibrator.o \ + vga.o \ + rgb.o \ + serial.o \ + maple/maple.o + +example/maple_vibrator.elf: LDSCRIPT = $(LIB)/alt.lds +example/maple_vibrator.elf: $(START_OBJ) $(MAPLE_VIBRATOR_OBJ) diff --git a/example/maple_controller.cpp b/example/maple_controller.cpp index 8e831d3..33fe8bf 100644 --- a/example/maple_controller.cpp +++ b/example/maple_controller.cpp @@ -15,12 +15,6 @@ uint32_t _receive_buf[1024 / 4 + 32] = {0}; static uint32_t * command_buf; static uint32_t * receive_buf; -struct port_state { - bool controller_connected; -}; - -static port_state state[4] = {0}; - void do_get_condition(uint32_t port) { uint32_t destination_port; @@ -56,15 +50,14 @@ void do_get_condition(uint32_t port) using response_type = struct maple::command_response>; auto response = reinterpret_cast(receive_buf); auto& bus_data = response->bus_data; - auto& data_fields = response->bus_data.data_fields; if (bus_data.command_code != data_transfer::command_code) { return; } + auto& data_fields = bus_data.data_fields; if ((data_fields.function_type & std::byteswap(function_type::controller)) == 0) { return; } - state[port].controller_connected = 1; bool a = ft0::data_transfer::digital_button::a(data_fields.data.digital_button); if (a == 0) { serial::string("port "); @@ -92,7 +85,6 @@ void do_device_request() auto& data_fields = response->bus_data.data_fields; if (bus_data.command_code != device_status::command_code) { // the controller is disconnected - state[port].controller_connected = 0; } else { if ((data_fields.device_id.ft & std::byteswap(function_type::controller)) != 0) { //serial::string("is controller: "); @@ -108,6 +100,8 @@ void main() command_buf = align_32byte(_command_buf); receive_buf = align_32byte(_receive_buf); + // flycast needs this in HLE mode, or else it won't start the vcount + // counter. vga(); while (1) { diff --git a/example/maple_vibrator.cpp b/example/maple_vibrator.cpp new file mode 100644 index 0000000..b4d5d8b --- /dev/null +++ b/example/maple_vibrator.cpp @@ -0,0 +1,192 @@ +#include + +#include "vga.hpp" +#include "align.hpp" + +#include "maple/maple.hpp" +#include "maple/maple_bus_bits.hpp" +#include "maple/maple_bus_commands.hpp" +#include "maple/maple_bus_ft8.hpp" +#include "serial.hpp" + +uint32_t _command_buf[1024 / 4 + 32] = {0}; +uint32_t _receive_buf[1024 / 4 + 32] = {0}; + +static uint32_t * command_buf; +static uint32_t * receive_buf; + +void do_lm_request(uint8_t port, uint8_t lm) +{ + uint32_t destination_port; + uint32_t destination_ap; + + switch (port) { + case 0: + destination_port = host_instruction::port_select::a; + destination_ap = ap::de::expansion_device | ap::port_select::a | lm; + break; + case 1: + destination_port = host_instruction::port_select::b; + destination_ap = ap::de::expansion_device | ap::port_select::b | lm; + break; + case 2: + destination_port = host_instruction::port_select::c; + destination_ap = ap::de::expansion_device | ap::port_select::c | lm; + break; + case 3: + destination_port = host_instruction::port_select::d; + destination_ap = ap::de::expansion_device | ap::port_select::d | lm; + break; + default: + return; + } + + /* + get media info + */ + + maple::init_host_command(command_buf, receive_buf, + destination_port, + destination_ap, get_media_info::command_code, (sizeof (struct get_media_info::data_fields)), + true); + + using command_type = struct maple::host_command; + auto host_command = reinterpret_cast(command_buf); + auto& fields = host_command->bus_data.data_fields; + fields.function_type = std::byteswap(function_type::vibration); + fields.pt = std::byteswap(1 << 24); + + maple::dma_start(command_buf); + + using response_type = struct maple::command_response>; + auto response = reinterpret_cast(receive_buf); + auto& bus_data = response->bus_data; + if (bus_data.command_code != data_transfer::command_code) { + serial::string("lm did not reply to vibration get_media_info: "); + serial::integer(lm); + return; + } + serial::string("lm replied to vibration get_media_info: "); + serial::integer(lm); + + auto& data_fields = bus_data.data_fields; + + { + using namespace ft8::data_transfer::vset; + serial::string("vn: "); + serial::integer(vn(data_fields.data.vset)); + serial::string("vp: "); + serial::integer(vp(data_fields.data.vset)); + serial::string("vd: "); + serial::integer(vd(data_fields.data.vset)); + serial::string("pf: "); + serial::integer(pf(data_fields.data.vset)); + serial::string("cv: "); + serial::integer(cv(data_fields.data.vset)); + serial::string("pd: "); + serial::integer(pd(data_fields.data.vset)); + serial::string("owf: "); + serial::integer(owf(data_fields.data.vset)); + serial::string("va: "); + serial::integer(va(data_fields.data.vset)); + serial::string("\nfm0 (fmin): "); + serial::integer(data_fields.data.fm0); + serial::string("fm1 (fmax): "); + serial::integer(data_fields.data.fm1); + } + + /* + set condition + */ + { + using data_field_type = set_condition::data_fields; + + maple::init_host_command(command_buf, receive_buf, + destination_port, + destination_ap, set_condition::command_code, (sizeof (data_field_type)), + true); + + using command_type = struct maple::host_command; + auto host_command = reinterpret_cast(command_buf); + auto& fields = host_command->bus_data.data_fields; + fields.function_type = std::byteswap(function_type::vibration); + fields.write_in_data.ctrl = 0x11; + fields.write_in_data.pow = 0x70; + fields.write_in_data.freq = 0x27; + fields.write_in_data.inc = 0x00; + + maple::dma_start(command_buf); + + using response_type = struct maple::command_response; + auto response = reinterpret_cast(receive_buf); + auto& bus_data = response->bus_data; + + if (bus_data.command_code != device_reply::command_code) { + serial::string("lm did not reply to vibration set_condition: "); + serial::integer(lm); + } else { + serial::string("lm replied to vibration set_condition: "); + serial::integer(lm); + } + + } +} + +void do_lm_requests(uint8_t port, uint8_t lm) +{ + if (lm & ap::lm_bus::_0) + do_lm_request(port, lm & ap::lm_bus::_0); + if (lm & ap::lm_bus::_1) + do_lm_request(port, lm & ap::lm_bus::_1); + if (lm & ap::lm_bus::_2) + do_lm_request(port, lm & ap::lm_bus::_2); + if (lm & ap::lm_bus::_3) + do_lm_request(port, lm & ap::lm_bus::_3); + if (lm & ap::lm_bus::_4) + do_lm_request(port, lm & ap::lm_bus::_4); +} + +void do_device_request() +{ + using response_type = struct maple::command_response; + constexpr uint32_t response_size = align_32byte(sizeof (response_type)); + + maple::init_host_command_all_ports(command_buf, receive_buf, + device_request::command_code, + (sizeof (device_request::data_fields)), // command_data_size + (sizeof (device_status::data_fields))); // response_data_size + maple::dma_start(command_buf); + + for (uint8_t port = 0; port < 4; port++) { + auto response = reinterpret_cast(&receive_buf[response_size * port / 4]); + + auto& bus_data = response->bus_data; + auto& data_fields = response->bus_data.data_fields; + if (bus_data.command_code != device_status::command_code) { + // the controller is disconnected + } else { + if ((data_fields.device_id.ft & std::byteswap(function_type::controller)) != 0) { + serial::string("controller: "); + serial::integer(port); + //serial::integer(bus_data.source_ap & ap::lm_bus::bit_mask); + do_lm_requests(port, bus_data.source_ap & ap::lm_bus::bit_mask); + } + } + } +} + +void main() +{ + command_buf = align_32byte(_command_buf); + receive_buf = align_32byte(_receive_buf); + + vga(); + + while (1) { + for (int i = 0; i < 120; i++) { + v_sync_out(); + v_sync_in(); + } + do_device_request(); + }; +} diff --git a/maple/maple_bus_ft8.hpp b/maple/maple_bus_ft8.hpp index 5e7fa50..0be21b9 100644 --- a/maple/maple_bus_ft8.hpp +++ b/maple/maple_bus_ft8.hpp @@ -29,9 +29,21 @@ namespace ft8 { struct data_format { uint16_t vset; - uint16_t fm; + uint8_t fm0; + uint8_t fm1; }; static_assert((sizeof (struct data_format)) == 4); } + + namespace set_condition { + struct data_format { + uint8_t ctrl; + uint8_t pow; + uint8_t freq; + uint8_t inc; + }; + static_assert((sizeof (struct data_format)) == 4); + } + } diff --git a/regs/gen/maple_data_format.py b/regs/gen/maple_data_format.py index 3cae87c..4ca5a52 100644 --- a/regs/gen/maple_data_format.py +++ b/regs/gen/maple_data_format.py @@ -130,6 +130,8 @@ def render_formats(name, formats): yield f"namespace {name} {{" for format in formats: yield from render_format(format) + yield "" + yield "}" if __name__ == "__main__": diff --git a/regs/maple_bus_ft8.csv b/regs/maple_bus_ft8.csv index aa8c13e..402a169 100644 --- a/regs/maple_bus_ft8.csv +++ b/regs/maple_bus_ft8.csv @@ -1,5 +1,11 @@ "data_transfer",7,6,5,4,3,2,1,0 "vset","vn","vn","vn","vn","vp","vp","vd","vd" "vset","pf","cv","pd","owf","va","va","va","va" -"fm",,,,,,,, -"fm",,,,,,,, +"fm0",,,,,,,, +"fm1",,,,,,,, +,,,,,,,, +"set_condition",7,6,5,4,3,2,1,0 +"ctrl",,,,,,,, +"pow",,,,,,,, +"freq",,,,,,,, +"inc",,,,,,,, diff --git a/regs/maple_bus_ft8.ods b/regs/maple_bus_ft8.ods index 7b878e61fe2ae9f0b0edcd9e21c9c6f962dd6b41..79143f0a297afb5cdee8c661bb47798941881de7 100644 GIT binary patch delta 7062 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 E09Uh)qW}N^ delta 5690 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 JNdyT3{R2B`pmP8K