mai2io/led15070: add LED control APIs and PWM/fade handling (#89)

## Summary

- feat(io4): implement PWM/GPIO support for Billboard and Code Reader lights
- feat(mai2io): expose LED control APIs to support new io4 features
- fix(led15070): correct multi-LED color logic and add fade support

## Description

**io4 & mai2io Updates**
Introduced PWM control and GPIO write support in `io4`. These additions allow `mai2io` to properly drive the **Billboard LEDs** and **Code Reader / Player Camera lights**. The APIs have been exposed through `mai2hook` to facilitate scriptable control of these peripherals.

**led15070 Improvements**
Fixed an issue with the multi-LED color changing logic in the `led15070` driver. This ensures correct color output sequence and includes improvements for fade calculation.

Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/89
Co-authored-by: グローランプ <130208311+Gl0w1amp@users.noreply.github.com>
Co-committed-by: グローランプ <130208311+Gl0w1amp@users.noreply.github.com>
This commit is contained in:
グローランプ
2025-12-25 20:10:59 +00:00
committed by Dniel97
parent c0f9b8f1ad
commit 41d7ce111d
9 changed files with 221 additions and 32 deletions
+63
View File
@@ -11,11 +11,17 @@
#include "util/dprintf.h"
static HRESULT mai2_io4_poll(void* ctx, struct io4_state* state);
static HRESULT mai2_io4_write_gpio(uint8_t* payload, size_t len);
static HRESULT mai2_io4_write_pwm(uint8_t* payload, size_t len);
static HRESULT mai2_io4_write_unique(uint8_t* payload, size_t len);
static uint16_t coins;
static const struct io4_ops mai2_io4_ops = {
.poll = mai2_io4_poll,
.write_gpio = mai2_io4_write_gpio,
.write_pwm = mai2_io4_write_pwm,
.write_unique = mai2_io4_write_unique,
};
HRESULT mai2_io4_hook_init(const struct io4_config* cfg) {
@@ -148,5 +154,62 @@ static HRESULT mai2_io4_poll(void* ctx, struct io4_state* state) {
state->buttons[1] |= 1 << 4;
}
return S_OK;
}
static HRESULT mai2_io4_write_gpio(uint8_t* payload, size_t len) {
if (len >= 4) {
char bin0[9];
char bin1[9];
for (int i = 0; i < 8; i++) {
bin0[7 - i] = (payload[0] & (1 << i)) ? '1' : '0';
bin1[7 - i] = (payload[1] & (1 << i)) ? '1' : '0';
}
bin0[8] = '\0';
bin1[8] = '\0';
#if defined(LOG_IO4)
dprintf("IO4 LED: [%02X %02X %02X %02X] (Byte0: %s, Byte1: %s)\n",
payload[0], payload[1], payload[2], payload[3], bin0, bin1);
dprintf(" 1P Code Reader: %s\n", (payload[0] & 0x20) ? "ON" : "OFF");
dprintf(" 2P Code Reader: %s\n", (payload[0] & 0x04) ? "ON" : "OFF");
dprintf(" Camera Ring: %s\n", (payload[1] & 0x80) ? "ON" : "OFF");
dprintf(" Camera Rec: %s\n", (payload[1] & 0x40) ? "ON" : "OFF");
#endif
}
return S_OK;
}
static HRESULT mai2_io4_write_pwm(uint8_t* payload, size_t len) {
if (len >= 16) {
#if defined(LOG_IO4)
dprintf("IO4 PWM: %02X %02X %02X %02X %02X %02X %02X %02X ...\n",
payload[0], payload[1], payload[2], payload[3],
payload[4], payload[5], payload[6], payload[7]);
#endif
}
return S_OK;
}
static HRESULT mai2_io4_write_unique(uint8_t* payload, size_t len) {
if (len < 8) {
return S_OK;
}
#if defined(LOG_IO4)
dprintf("IO4 Unique: %02X %02X %02X %02X %02X %02X %02X %02X ...\n",
payload[0], payload[1], payload[2], payload[3],
payload[4], payload[5], payload[6], payload[7]);
#endif
if (mai2_dll.led_set_leds) {
uint8_t rgb_1p[3] = { payload[2], payload[4], payload[6] };
uint8_t rgb_2p[3] = { payload[3], payload[5], payload[7] };
mai2_dll.led_set_leds(0, rgb_1p);
mai2_dll.led_set_leds(1, rgb_2p);
}
return S_OK;
}
+14 -1
View File
@@ -8,6 +8,11 @@
#include "util/dll-bind.h"
#include "util/dprintf.h"
enum {
MAI2_DLL_SYM_COUNT_V101 = 11,
MAI2_DLL_SYM_COUNT_V102 = 12,
};
const struct dll_bind_sym mai2_dll_syms[] = {
{
.sym = "mai2_io_init",
@@ -42,6 +47,9 @@ const struct dll_bind_sym mai2_dll_syms[] = {
}, {
.sym = "mai2_io_led_gs_update",
.off = offsetof(struct mai2_dll, led_gs_update),
}, {
.sym = "mai2_io_led_billboard_set",
.off = offsetof(struct mai2_dll, led_set_leds),
},
};
@@ -60,6 +68,7 @@ HRESULT mai2_dll_init(const struct mai2_dll_config *cfg, HINSTANCE self)
HINSTANCE owned;
HINSTANCE src;
HRESULT hr;
size_t sym_count;
assert(cfg != NULL);
assert(self != NULL);
@@ -104,7 +113,11 @@ HRESULT mai2_dll_init(const struct mai2_dll_config *cfg, HINSTANCE self)
}
sym = mai2_dll_syms;
hr = dll_bind(&mai2_dll, src, &sym, _countof(mai2_dll_syms));
sym_count = (mai2_dll.api_version < 0x0102)
? MAI2_DLL_SYM_COUNT_V101
: MAI2_DLL_SYM_COUNT_V102;
hr = dll_bind(&mai2_dll, src, &sym, sym_count);
if (FAILED(hr)) {
if (src != self) {
+1
View File
@@ -17,6 +17,7 @@ struct mai2_dll {
void (*led_set_fet_output)(uint8_t board, const uint8_t *rgb);
void (*led_dc_update)(uint8_t board, const uint8_t *rgb);
void (*led_gs_update)(uint8_t board, const uint8_t *rgb);
void (*led_set_leds)(uint8_t board, const uint8_t *rgb);
};
struct mai2_dll_config {
+1
View File
@@ -23,3 +23,4 @@ EXPORTS
mai2_io_led_set_fet_output
mai2_io_led_dc_update
mai2_io_led_gs_update
mai2_io_led_billboard_set
+9 -1
View File
@@ -19,7 +19,7 @@ static bool mai2_io_touch_1p_stop_flag;
static HANDLE mai2_io_touch_2p_thread;
static bool mai2_io_touch_2p_stop_flag;
uint16_t mai2_io_get_api_version(void) { return 0x0101; }
uint16_t mai2_io_get_api_version(void) { return 0x0102; }
HRESULT mai2_io_init(void) {
mai2_io_config_load(&mai2_io_cfg, get_config_path());
@@ -265,3 +265,11 @@ void mai2_io_led_gs_update(uint8_t board, const uint8_t *rgb) {
#endif
return;
}
void mai2_io_led_billboard_set(uint8_t board, const uint8_t *rgb) {
#if 0
uint8_t player = board + 1;
dprintf("Mai2 LED %dP: Billboard set R:%02X G:%02X B:%02X\n", player, rgb[0], rgb[1], rgb[2]);
#endif
return;
}
+10 -1
View File
@@ -28,7 +28,7 @@ enum {
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0100. */
The latest API version as of this writing is 0x0102. */
uint16_t mai2_io_get_api_version(void);
@@ -190,3 +190,12 @@ void mai2_io_led_dc_update(uint8_t board, const uint8_t *rgb);
Minimum API version: 0x0101 */
void mai2_io_led_gs_update(uint8_t board, const uint8_t *rgb);
/* Update the Billboard LEDs. rgb is a pointer to an array.
maimai DX uses two boards. Board 0 is for the player 1 side (left) and board 1
is for the player 2 side (right).
Minimum API version: 0x0102 */
void mai2_io_led_billboard_set(uint8_t board, const uint8_t *rgb);