mirror of
https://gitea.tendokyu.moe/TeamTofuShop/segatools.git
synced 2026-09-22 22:37:59 +03:00
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:
+9
-1
@@ -241,13 +241,21 @@ static HRESULT io4_handle_write(struct irp *irp)
|
||||
return S_OK;
|
||||
|
||||
case IO4_CMD_SET_PWM_OUTPUT:
|
||||
dprintf("USB I/O: PWM Out\n");
|
||||
// dprintf("USB I/O: PWM Out\n");
|
||||
|
||||
if (io4_ops->write_pwm != NULL) {
|
||||
return io4_ops->write_pwm(out.payload, IO4_REPORT_OUT_PAYLOAD_LEN);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
|
||||
case IO4_CMD_SET_UNIQUE_OUTPUT:
|
||||
// dprintf("USB I/O: Unique Out\n");
|
||||
|
||||
if (io4_ops->write_unique != NULL) {
|
||||
return io4_ops->write_unique(out.payload, IO4_REPORT_OUT_PAYLOAD_LEN);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
|
||||
case IO4_CMD_UPDATE_FIRMWARE:
|
||||
|
||||
@@ -30,6 +30,8 @@ struct io4_ops {
|
||||
HRESULT (*poll)(void* ctx, struct io4_state* state);
|
||||
|
||||
HRESULT (*write_gpio)(uint8_t* payload, size_t len);
|
||||
HRESULT (*write_pwm)(uint8_t* payload, size_t len);
|
||||
HRESULT (*write_unique)(uint8_t* payload, size_t len);
|
||||
};
|
||||
|
||||
HRESULT io4_hook_init(
|
||||
|
||||
+112
-28
@@ -71,6 +71,7 @@ static uint16_t led15070_fw_sum;
|
||||
static uint8_t led15070_host_adr = 0x01;
|
||||
|
||||
#define led15070_nboards 2
|
||||
#define led15070_nleds 32
|
||||
|
||||
typedef struct {
|
||||
CRITICAL_SECTION lock;
|
||||
@@ -79,8 +80,10 @@ typedef struct {
|
||||
struct uart boarduart;
|
||||
uint8_t written_bytes[520];
|
||||
uint8_t readable_bytes[520];
|
||||
uint8_t gs[32][4];
|
||||
uint8_t dc[32][3];
|
||||
uint8_t gs[led15070_nleds][4];
|
||||
uint8_t gs_fade[led15070_nleds][4];
|
||||
bool gs_fade_pending[led15070_nleds];
|
||||
uint8_t dc[led15070_nleds][3];
|
||||
uint8_t fet[3];
|
||||
uint8_t gs_palette[8][3];
|
||||
wchar_t eeprom_path[MAX_PATH];
|
||||
@@ -150,6 +153,8 @@ HRESULT led15070_hook_init(
|
||||
v->boarduart.readable.nbytes = sizeof(v->readable_bytes);
|
||||
|
||||
memset(v->gs, 0, sizeof(v->gs));
|
||||
memset(v->gs_fade, 0, sizeof(v->gs_fade));
|
||||
memset(v->gs_fade_pending, 0, sizeof(v->gs_fade_pending));
|
||||
memset(v->dc, 0, sizeof(v->dc));
|
||||
memset(v->fet, 0, sizeof(v->fet));
|
||||
memset(v->gs_palette, 0, sizeof(v->gs_palette));
|
||||
@@ -454,6 +459,8 @@ static HRESULT led15070_req_set_normal_8bit(int board, const struct led15070_req
|
||||
led15070_per_board_vars[board].gs[idx][0] = req->payload[1]; // R
|
||||
led15070_per_board_vars[board].gs[idx][1] = req->payload[2]; // G
|
||||
led15070_per_board_vars[board].gs[idx][2] = req->payload[3]; // B
|
||||
led15070_per_board_vars[board].gs[idx][3] = 0;
|
||||
led15070_per_board_vars[board].gs_fade_pending[idx] = false;
|
||||
|
||||
if (!led15070_per_board_vars[board].enable_response)
|
||||
return S_OK;
|
||||
@@ -473,31 +480,65 @@ static HRESULT led15070_req_set_normal_8bit(int board, const struct led15070_req
|
||||
return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
|
||||
}
|
||||
|
||||
static void led15070_calc_range(
|
||||
uint8_t start,
|
||||
uint8_t count,
|
||||
uint8_t skip,
|
||||
uint8_t *out_start,
|
||||
uint8_t *out_end)
|
||||
{
|
||||
uint16_t s = start;
|
||||
uint16_t c = count;
|
||||
|
||||
if (c == 0) {
|
||||
*out_start = 0;
|
||||
*out_end = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c >= led15070_nleds) {
|
||||
c = led15070_nleds;
|
||||
}
|
||||
|
||||
if (skip > 0 && skip <= c) {
|
||||
s += skip;
|
||||
c -= skip;
|
||||
}
|
||||
|
||||
if (s >= led15070_nleds || c == 0) {
|
||||
*out_start = led15070_nleds;
|
||||
*out_end = led15070_nleds;
|
||||
return;
|
||||
}
|
||||
|
||||
*out_start = (uint8_t) s;
|
||||
*out_end = (s + c > led15070_nleds) ? led15070_nleds : (uint8_t) (s + c);
|
||||
}
|
||||
|
||||
static HRESULT led15070_req_set_multi_flash_8bit(int board, const struct led15070_req_any *req)
|
||||
{
|
||||
uint8_t idx_start = req->payload[0];
|
||||
uint8_t idx_end = req->payload[1];
|
||||
uint8_t idx_count = req->payload[1];
|
||||
uint8_t idx_skip = req->payload[2];
|
||||
uint8_t start;
|
||||
uint8_t end;
|
||||
|
||||
// TODO: useful?
|
||||
#if defined(LOG_LED15070)
|
||||
dprintf("LED 15070: Set LED - Multi flash 8bit (board %u, start %u, end %u, skip %u)\n",
|
||||
board, idx_start, idx_end, idx_skip);
|
||||
dprintf("LED 15070: Set LED - Multi flash 8bit (board %u, start %u, count %u, skip %u)\n",
|
||||
board, idx_start, idx_count, idx_skip);
|
||||
#endif
|
||||
|
||||
if (idx_skip > 0 && idx_skip <= (idx_end - idx_start + 1)) {
|
||||
idx_start += idx_skip;
|
||||
}
|
||||
led15070_calc_range(idx_start, idx_count, idx_skip, &start, &end);
|
||||
|
||||
int i = idx_start;
|
||||
do {
|
||||
for (int i = start; i < end; i++) {
|
||||
led15070_per_board_vars[board].gs[i][0] = req->payload[3]; // R
|
||||
led15070_per_board_vars[board].gs[i][1] = req->payload[4]; // G
|
||||
led15070_per_board_vars[board].gs[i][2] = req->payload[5]; // B
|
||||
/* Always 0, tells the controller to immediately change to this color */
|
||||
led15070_per_board_vars[board].gs[i][3] = req->payload[6]; // Speed
|
||||
i++;
|
||||
} while (i < idx_end);
|
||||
led15070_per_board_vars[board].gs_fade_pending[i] = false;
|
||||
}
|
||||
|
||||
if (!led15070_per_board_vars[board].enable_response)
|
||||
return S_OK;
|
||||
@@ -520,25 +561,24 @@ static HRESULT led15070_req_set_multi_flash_8bit(int board, const struct led1507
|
||||
static HRESULT led15070_req_set_multi_fade_8bit(int board, const struct led15070_req_any *req)
|
||||
{
|
||||
uint8_t idx_start = req->payload[0];
|
||||
uint8_t idx_end = req->payload[1];
|
||||
uint8_t idx_count = req->payload[1];
|
||||
uint8_t idx_skip = req->payload[2];
|
||||
uint8_t start;
|
||||
uint8_t end;
|
||||
#if defined(LOG_LED15070)
|
||||
dprintf("LED 15070: Set LED - Multi fade 8bit (board %u, start %u, end %u, skip %u)\n",
|
||||
board, idx_start, idx_end, idx_skip);
|
||||
dprintf("LED 15070: Set LED - Multi fade 8bit (board %u, start %u, count %u, skip %u)\n",
|
||||
board, idx_start, idx_count, idx_skip);
|
||||
#endif
|
||||
|
||||
if (idx_skip > 0 && idx_skip <= (idx_end - idx_start + 1)) {
|
||||
idx_start += idx_skip;
|
||||
}
|
||||
led15070_calc_range(idx_start, idx_count, idx_skip, &start, &end);
|
||||
|
||||
int i = idx_start;
|
||||
do {
|
||||
led15070_per_board_vars[board].gs[i][0] = req->payload[3]; // R
|
||||
led15070_per_board_vars[board].gs[i][1] = req->payload[4]; // G
|
||||
led15070_per_board_vars[board].gs[i][2] = req->payload[5]; // B
|
||||
led15070_per_board_vars[board].gs[i][3] = req->payload[6]; // Speed
|
||||
i++;
|
||||
} while (i < idx_end);
|
||||
for (int i = start; i < end; i++) {
|
||||
led15070_per_board_vars[board].gs_fade[i][0] = req->payload[3]; // R
|
||||
led15070_per_board_vars[board].gs_fade[i][1] = req->payload[4]; // G
|
||||
led15070_per_board_vars[board].gs_fade[i][2] = req->payload[5]; // B
|
||||
led15070_per_board_vars[board].gs_fade[i][3] = req->payload[6]; // Speed
|
||||
led15070_per_board_vars[board].gs_fade_pending[i] = true;
|
||||
}
|
||||
|
||||
if (!led15070_per_board_vars[board].enable_response)
|
||||
return S_OK;
|
||||
@@ -767,12 +807,56 @@ static HRESULT led15070_req_dc_update(int board, const struct led15070_req_any *
|
||||
|
||||
static HRESULT led15070_req_gs_update(int board, const struct led15070_req_any *req)
|
||||
{
|
||||
_led15070_per_board_vars *v = &led15070_per_board_vars[board];
|
||||
#if defined(LOG_LED15070)
|
||||
dprintf("LED 15070: GS update (board %u)\n", board);
|
||||
#endif
|
||||
|
||||
if (led_gs_update)
|
||||
led_gs_update(board, (const uint8_t*)led15070_per_board_vars[board].gs);
|
||||
if (led_gs_update) {
|
||||
bool has_fade = false;
|
||||
uint8_t payload[led15070_nleds][4];
|
||||
|
||||
for (int i = 0; i < led15070_nleds; i++) {
|
||||
if (v->gs_fade_pending[i]) {
|
||||
has_fade = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_fade) {
|
||||
for (int i = 0; i < led15070_nleds; i++) {
|
||||
payload[i][0] = v->gs[i][0];
|
||||
payload[i][1] = v->gs[i][1];
|
||||
payload[i][2] = v->gs[i][2];
|
||||
payload[i][3] = 0;
|
||||
}
|
||||
|
||||
led_gs_update(board, (const uint8_t*)payload);
|
||||
|
||||
for (int i = 0; i < led15070_nleds; i++) {
|
||||
if (v->gs_fade_pending[i]) {
|
||||
payload[i][0] = v->gs_fade[i][0];
|
||||
payload[i][1] = v->gs_fade[i][1];
|
||||
payload[i][2] = v->gs_fade[i][2];
|
||||
payload[i][3] = v->gs_fade[i][3];
|
||||
v->gs_fade_pending[i] = false;
|
||||
} else {
|
||||
payload[i][0] = v->gs[i][0];
|
||||
payload[i][1] = v->gs[i][1];
|
||||
payload[i][2] = v->gs[i][2];
|
||||
payload[i][3] = v->gs[i][3];
|
||||
}
|
||||
}
|
||||
|
||||
led_gs_update(board, (const uint8_t*)payload);
|
||||
} else {
|
||||
led_gs_update(board, (const uint8_t*)v->gs);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < led15070_nleds; i++) {
|
||||
v->gs_fade_pending[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!led15070_per_board_vars[board].enable_response)
|
||||
return S_OK;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user