1
0
mirror of https://github.com/whowechina/iidx_pico.git synced 2024-11-12 01:10:50 +01:00

Big update (analog and effect mode removed)

This commit is contained in:
whowechina 2023-12-10 18:36:21 +08:00
parent 1f6b78b141
commit dedc9c452b
25 changed files with 365 additions and 392 deletions

Binary file not shown.

View File

@ -12,7 +12,6 @@ Features:
* Key color theme and customization. * Key color theme and customization.
* Multiple turntable effects. * Multiple turntable effects.
* Many live settings. * Many live settings.
* Analog channels.
* Konami device mode for IIDX Ultimate Mobile. * Konami device mode for IIDX Ultimate Mobile.
* All source files open. * All source files open.
@ -72,7 +71,6 @@ I made this project in my personal time with no financial benefit or sponsorship
* 2x SN74LV1T34DBVR (SOT-23-5) level shifter, optional, for better voltage tolerance. * 2x SN74LV1T34DBVR (SOT-23-5) level shifter, optional, for better voltage tolerance.
* 2x 0603 10ohm resistors to bypass said level shifters, optional. * 2x 0603 10ohm resistors to bypass said level shifters, optional.
* 1x 0603 5.1kohm resistors for USB. * 1x 0603 5.1kohm resistors for USB.
* 1x 0805 1nF capacitor for filtering analog signal, optional.
* 5x 0805 1uF capacitors. * 5x 0805 1uF capacitors.
* 4x Kailh low-profile keycaps. * 4x Kailh low-profile keycaps.
* 4x M3*6mm screws and hex nuts to fix parts together. * 4x M3*6mm screws and hex nuts to fix parts together.
@ -139,28 +137,16 @@ It's very small and requires higher accuracy.
You need to scrape off some solder mask to expose the ground copper (don't scrape the solder mask under 5V pin). I found a good place to mount the REF3030, this is how I handled it: You need to scrape off some solder mask to expose the ground copper (don't scrape the solder mask under 5V pin). I found a good place to mount the REF3030, this is how I handled it:
<img src="doc/ref3030.jpg" width="300px"> <img src="doc/ref3030.jpg" width="300px">
* Before proceeding further, it's important to note that the silicone hinge used in my IIDX Teeny has proven to be the most effective and stable. Therefore, it's highly recommended to opt for the hinge option, rather than the subsequent digital (pogopin) or analog (3.5mm headphone jack) options. * Before proceeding further, it's important to note that the silicone hinge used in my IIDX Teeny has proven to be the most effective and stable. Therefore, it's highly recommended to opt for the hinge option, rather than the subsequent digital (pogopin). I've also removed the analog (3.5mm headphone jack) option simply because it's not good.
* If you go with digital (magnetic pogo pin connector) * If you go with digital (magnetic pogo pin connector)
There're a set of I2C and a WS2812B signal line together in the cable that connects turntable and the keyboard. Unfortunately, these signals crosstalk. So, we have to use shield cables for them. Two I2C lines should have a shield cable, and the WS2812B signal should have another shield cable. Good thing is, an HDMI cable has 4 shield cables and bunch of other small cables. We can make use of it. There're a set of I2C and a WS2812B signal line together in the cable that connects turntable and the keyboard. Unfortunately, these signals crosstalk. So, we have to use shield cables for them. Two I2C lines should have a shield cable, and the WS2812B signal should have another shield cable. Good thing is, an HDMI cable has 4 shield cables and bunch of other small cables. We can make use of it.
<img src="doc/pogopin_wiring.jpg" width="300px"> <img src="doc/pogopin_wiring.jpg" width="300px">
* If you go with analog (3.5mm headphone jack)
Here's the pin definition.
<img src="doc/headphone_jack_wiring.png" width="200px">
The "ANGLE" connects to the AS5600 analog OUT. You need to remove a resistor from as5600 board to get OUT pin working.
<img src="doc/as5600_mod.png" width="300px">
Note: For analog, crosstalk maybe no longer an issue, but ground level becomes a new concern. When driving the turntable LED ring, there's a considerable amount of current travelling through the ground cable which lifts AS5600 ground level. There're two ways to handle this.
* One is to use a 5 wire cable. You need to separate LED ground from the sensor ground. A typical Type-C to Type-C cable has 5 wires inside. You can use red and black to power the LED and others to serve sensor ground, sensor analog out and LED signal.
<img src="doc/5A_usb.jpg" width="300px">
<img src="doc/analog_wiring.png" width="500px">
* The other one is to minimize the ground wire resistance. A metal braid shielding cable can be used with the metal shield serving as the ground line. Or you can find a 4 wire cable with thick core copper.
* Turntable LED Ring * Turntable LED Ring
I recommend to use LED strip over LED board, becuase the LED board's light is facing up, but using strip, the light can spread out pefectly. I recommend to use LED strip over LED board, becuase the LED board's light is facing up, but using strip, the light can spread out pefectly.
Use transparent double-sided tape to stick the LED around the turntable inner wall. Use transparent double-sided tape to stick the LED around the turntable inner wall.
### Step 4 - Assemble ### Step 4 - Assemble
* Assemble the turntable * Assemble the turntable
I don't know how to draw an explosion diagram, this is done by coding in OpenSCAD: I don't know how to draw an explosion diagram, this is done by coding in OpenSCAD:
@ -189,7 +175,7 @@ It's very small and requires higher accuracy.
* If it is already running my IIDX firmware, hold two small AUX buttons together will do the same as the BOOTSEL button. * If it is already running my IIDX firmware, hold two small AUX buttons together will do the same as the BOOTSEL button.
* You need to setup your configuration such as AS5600 connection mode and LED ring. * You need to setup your configuration such as AS5600 connection mode and LED ring.
* Also you can setup the key color theme and turntable effects at runtime. I think I made the configuration too showey and complicated. Check out the manual. * Also you can setup the key color theme and turntable effects at runtime. I think I made the configuration too showey and complicated. Check out the manual.
[Nice Looking Manual Here](doc/Firmware_Manual.pdf) [Nice Looking Manual Here](doc/Firmware_manual.pdf)
### What If? ### What If?
* I can't find pogopin connector. * I can't find pogopin connector.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

View File

@ -4,7 +4,7 @@ set(LWIP_ROOT ${PICO_SDK_PATH}/lib/lwip)
function(make_firmware board board_def) function(make_firmware board board_def)
pico_sdk_init() pico_sdk_init()
add_executable(${board} add_executable(${board}
main.c buttons.c rgb.c save.c config.c setup.c main.c cli.c commands.c buttons.c rgb.c save.c config.c setup.c
turntable.c tt_rainbow.c tt_blade.c turntable.c tt_rainbow.c tt_blade.c
usb_descriptors.c) usb_descriptors.c)
target_compile_definitions(${board} PUBLIC ${board_def}) target_compile_definitions(${board} PUBLIC ${board_def})

193
firmware/src/cli.c Normal file
View File

@ -0,0 +1,193 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "pico/bootrom.h"
#include "cli.h"
#include "save.h"
#define MAX_COMMANDS 32
#define MAX_PARAMETERS 6
#define MAX_PARAMETER_LENGTH 20
const char *cli_prompt = "cli>";
const char *cli_logo = "CLI";
static const char *commands[MAX_COMMANDS];
static const char *helps[MAX_COMMANDS];
static cmd_handler_t handlers[MAX_COMMANDS];
static int max_cmd_len = 0;
static int num_commands = 0;
void cli_register(const char *cmd, cmd_handler_t handler, const char *help)
{
if (num_commands < MAX_COMMANDS) {
commands[num_commands] = cmd;
handlers[num_commands] = handler;
helps[num_commands] = help;
num_commands++;
if (strlen(cmd) > max_cmd_len) {
max_cmd_len = strlen(cmd);
}
}
}
// return -1 if not matched, return -2 if ambiguous
int cli_match_prefix(const char *str[], int num, const char *prefix)
{
int match = -1;
bool found = false;
for (int i = 0; (i < num) && str[i]; i++) {
if (strncasecmp(str[i], prefix, strlen(prefix)) == 0) {
if (found) {
return -2;
}
found = true;
match = i;
}
}
return match;
}
static void handle_help(int argc, char *argv[])
{
printf("%s", cli_logo);
printf("\tSN: %016llx\n\n", board_id_64());
printf("Available commands:\n");
for (int i = 0; i < num_commands; i++) {
printf("%*s: %s\n", max_cmd_len + 2, commands[i], helps[i]);
}
}
static int fps[2];
void cli_fps_count(int core)
{
static uint32_t last[2] = {0};
static int counter[2] = {0};
counter[core]++;
uint32_t now = time_us_32();
if (now - last[core] < 1000000) {
return;
}
last[core] = now;
fps[core] = counter[core];
counter[core] = 0;
}
static void handle_fps(int argc, char *argv[])
{
printf("FPS: core 0: %d, core 1: %d\n", fps[0], fps[1]);
}
static void handle_update(int argc, char *argv[])
{
printf("Boot into update mode.\n");
fflush(stdout);
sleep_ms(100);
reset_usb_boot(0, 2);
}
int cli_extract_non_neg_int(const char *param, int len)
{
if (len == 0) {
len = strlen(param);
}
int result = 0;
for (int i = 0; i < len; i++) {
if (!isdigit((uint8_t)param[i])) {
return -1;
}
result = result * 10 + param[i] - '0';
}
return result;
}
static char cmd_buf[256];
static int cmd_len = 0;
static void process_cmd()
{
char *argv[MAX_PARAMETERS];
int argc;
char *cmd = strtok(cmd_buf, " \n");
if (strlen(cmd) == 0) {
return;
}
argc = 0;
while ((argc < MAX_PARAMETERS) &&
(argv[argc] = strtok(NULL, " ,\n")) != NULL) {
argc++;
}
int match = cli_match_prefix(commands, num_commands, cmd);
if (match == -2) {
printf("Ambiguous command.\n");
return;
} else if (match == -1) {
printf("Unknown command.\n");
handle_help(0, NULL);
return;
}
handlers[match](argc, argv);
}
void cli_run()
{
int c = getchar_timeout_us(0);
if (c == EOF) {
return;
}
if (c == '\b' || c == 127) { // both backspace and delete
if (cmd_len > 0) {
cmd_len--;
printf("\b \b");
}
return;
}
if ((c != '\n') && (c != '\r')) {
if (cmd_len < sizeof(cmd_buf) - 2) {
cmd_buf[cmd_len] = c;
printf("%c", c);
cmd_len++;
}
return;
}
cmd_buf[cmd_len] = '\0';
cmd_len = 0;
printf("\n");
process_cmd();
printf(cli_prompt);
}
void cli_init(const char *prompt, const char *logo)
{
if (prompt) {
cli_prompt = prompt;
}
if (logo) {
cli_logo = logo;
}
cli_register("?", handle_help, "Display this help message.");
cli_register("fps", handle_fps, "Display FPS.");
cli_register("update", handle_update, "Update firmware.");
}

20
firmware/src/cli.h Normal file
View File

@ -0,0 +1,20 @@
/*
* Chu Controller Command Line Framework
* WHowe <github.com/whowechina>
*/
#ifndef CLI_H
#define CLI_H
typedef void (*cmd_handler_t)(int argc, char *argv[]);
void cli_init(const char *prompt, const char *logo);
void cli_register(const char *cmd, cmd_handler_t handler, const char *help);
void cli_run();
void cli_fps_count(int core);
int cli_extract_non_neg_int(const char *param, int len);
int cli_match_prefix(const char *str[], int num, const char *prefix);
#endif

29
firmware/src/commands.c Normal file
View File

@ -0,0 +1,29 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "config.h"
#include "save.h"
#include "cli.h"
static void handle_save()
{
save_request(true);
}
static void handle_factory_reset()
{
config_factory_reset();
printf("Factory reset done.\n");
}
void commands_init()
{
cli_register("save", handle_save, "Save config to flash.");
cli_register("factory", handle_factory_reset, "Reset everything to default.");
}

11
firmware/src/commands.h Normal file
View File

@ -0,0 +1,11 @@
/*
* Chu Controller Command Line Commands
* WHowe <github.com/whowechina>
*/
#ifndef COMMANDS_H
#define COMMANDS_H
void commands_init();
#endif

View File

@ -23,16 +23,10 @@ static iidx_cfg_t default_cfg = {
.mode = 0, .mode = 0,
}, },
.tt_sensor = { .tt_sensor = {
.mode = 2, .reversed = false,
.deadzone = 1, .deadzone = 1,
.ppr = 1, .ppr = 1,
}, },
.effects = {
.e1 = 255,
.e2 = 128,
.e3 = 128,
.e4 = 128,
},
.level = 128, .level = 128,
.konami = false, .konami = false,
}; };
@ -57,10 +51,6 @@ static void config_loaded()
iidx_cfg->tt_led.mode = 0; iidx_cfg->tt_led.mode = 0;
config_changed(); config_changed();
} }
if (iidx_cfg->tt_sensor.mode > 3) {
iidx_cfg->tt_sensor.mode = 2;
config_changed();
}
if (iidx_cfg->tt_sensor.ppr > 3) { if (iidx_cfg->tt_sensor.ppr > 3) {
iidx_cfg->tt_sensor.ppr = 1; iidx_cfg->tt_sensor.ppr = 1;
config_changed(); config_changed();

View File

@ -26,16 +26,10 @@ typedef struct __attribute ((packed)) {
uint8_t mode; /* 0: on, 1: reversed, 2: off */ uint8_t mode; /* 0: on, 1: reversed, 2: off */
} tt_led; } tt_led;
struct { struct {
uint8_t mode; /* 0: analog, 1: analog reversed, 2: i2c, 3: i2c reversed */ bool reversed;
uint8_t deadzone; /* only for analog */ uint8_t deadzone; /* only for analog */
uint8_t ppr; /* 0: 256, 1: 128, 2: 96, 3: 64, other: 256 */ uint8_t ppr; /* 0: 256, 1: 128, 2: 96, 3: 64, other: 256 */
} tt_sensor; } tt_sensor;
struct {
uint8_t e1;
uint8_t e2;
uint8_t e3;
uint8_t e4;
} effects;
uint8_t level; /* led brightness limit */ uint8_t level; /* led brightness limit */
bool konami; /* konami spoof */ bool konami; /* konami spoof */
} iidx_cfg_t; } iidx_cfg_t;

View File

@ -24,26 +24,19 @@
#include "tt_blade.h" #include "tt_blade.h"
#include "tt_rainbow.h" #include "tt_rainbow.h"
#include "config.h"
#include "save.h" #include "save.h"
#include "config.h"
/* Measure the time of a function call */ #include "cli.h"
#define RUN_TIME(func) \ #include "commands.h"
{ uint64_t _t = time_us_64(); func; \
printf(#func ":%lld\n", time_us_64() - _t); }
struct { struct {
uint16_t buttons; uint16_t buttons;
uint8_t joy[6]; uint8_t joy[2];
} hid_report; } hid_report;
void report_usb_hid() void report_usb_hid()
{ {
if (tud_hid_ready()) { if (tud_hid_ready()) {
hid_report.joy[2] = iidx_cfg->effects.e1;
hid_report.joy[3] = iidx_cfg->effects.e2;
hid_report.joy[4] = iidx_cfg->effects.e3;
hid_report.joy[5] = iidx_cfg->effects.e4;
tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_report, sizeof(hid_report)); tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_report, sizeof(hid_report));
} }
} }
@ -76,20 +69,9 @@ void mode_check()
} }
} }
static bool request_core1_pause = false; static mutex_t core1_io_lock;
static void pause_core1(bool pause)
{
request_core1_pause = pause;
if (pause) {
sleep_ms(5); /* wait for any IO ops to finish */
}
}
static void core1_loop() static void core1_loop()
{ {
#define RUN_EVERY_N_MS(a, ms) { if (frame % ms == 0) a; }
uint32_t frame = 0;
while (true) { while (true) {
uint32_t angle = turntable_raw(); uint32_t angle = turntable_raw();
rgb_set_angle(angle); rgb_set_angle(angle);
@ -98,37 +80,28 @@ static void core1_loop()
hid_report.joy[0] = angle8; hid_report.joy[0] = angle8;
hid_report.joy[1] = 255 - angle8; hid_report.joy[1] = 255 - angle8;
RUN_EVERY_N_MS(rgb_update(), 2); if (mutex_try_enter(&core1_io_lock, NULL)) {
turntable_update(); rgb_update();
frame++; mutex_exit(&core1_io_lock);
do {
sleep_ms(1);
} while (request_core1_pause);
}
}
static void boot_usb_check(uint16_t buttons)
{
uint16_t usb_boot_keys = 0x1855; /* YES, NO, 1, 3, 5, 7 */
if (buttons == usb_boot_keys) {
reset_usb_boot(0, 2); // usb boot to flash
} }
uint16_t factory_default_keys = 0x182a; /* YES, NO, 2, 4, 6 */ cli_fps_count(1);
if (buttons == factory_default_keys) { sleep_us(500);
config_factory_reset();
watchdog_enable(1, 1);
while(1); // just reboot
} }
} }
static void core0_loop() static void core0_loop()
{ {
uint64_t next_frame = 0;
while (true) while (true)
{ {
tud_task(); tud_task();
cli_run();
turntable_update();
uint16_t buttons = button_read(); uint16_t buttons = button_read();
boot_usb_check(buttons);
uint16_t angle = turntable_raw() >> 4; uint16_t angle = turntable_raw() >> 4;
if (setup_run(buttons, angle)) { if (setup_run(buttons, angle)) {
rgb_force_display(setup_led_button, setup_led_tt); rgb_force_display(setup_led_button, setup_led_tt);
@ -138,6 +111,10 @@ static void core0_loop()
save_loop(); save_loop();
} }
report_usb_hid(); report_usb_hid();
cli_fps_count(0);
sleep_until(next_frame);
next_frame = time_us_64() + 1000; // 1KHz
} }
} }
@ -157,19 +134,20 @@ void init()
setup_init(); setup_init();
config_init(); config_init();
save_init(pause_core1); mutex_init(&core1_io_lock);
save_init(0xca341234, &core1_io_lock);
cli_init("iidx_pico>", "\n << IIDX Pico|Teeny Controller >>\n"
" https://github.com/whowechina\n\n");
commands_init();
mode_check(); mode_check();
} }
int main(void) void main(void)
{ {
init(); init();
multicore_launch_core1(core1_loop); multicore_launch_core1(core1_loop);
core0_loop(); core0_loop();
return 0;
} }
// Invoked when received GET_REPORT control request // Invoked when received GET_REPORT control request

View File

@ -292,7 +292,15 @@ static void follow_mode_change()
void rgb_update() void rgb_update()
{ {
static uint64_t last = 0;
uint64_t now = time_us_64();
if (now - last < 4000) { // no faster than 250Hz
return;
}
last = now;
follow_mode_change(); follow_mode_change();
set_effect(iidx_cfg->tt_led.effect); set_effect(iidx_cfg->tt_led.effect);
if (time_us_64() > force_expire_time) { if (time_us_64() > force_expire_time) {
effect_update(); effect_update();

View File

@ -1,8 +1,8 @@
/* /*
* Controller Save Save and Load * Controller Config Save and Load
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
* *
* Save is stored in last sector of flash * Config is stored in last sector of flash
*/ */
#include "save.h" #include "save.h"
@ -18,7 +18,8 @@
#include "pico/stdio.h" #include "pico/stdio.h"
#include "hardware/flash.h" #include "hardware/flash.h"
#include "hardware/sync.h" #include "pico/multicore.h"
#include "pico/unique_id.h"
static struct { static struct {
size_t size; size_t size;
@ -27,7 +28,10 @@ static struct {
} modules[8] = {0}; } modules[8] = {0};
static int module_num = 0; static int module_num = 0;
#define SAVE_PAGE_MAGIC 0xcafe4321 static uint32_t my_magic = 0xcafecafe;
#define SAVE_TIMEOUT_US 5000000
#define SAVE_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) #define SAVE_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE)
typedef struct __attribute ((packed)) { typedef struct __attribute ((packed)) {
@ -43,15 +47,15 @@ static int data_page = -1;
static bool requesting_save = false; static bool requesting_save = false;
static uint64_t requesting_time = 0; static uint64_t requesting_time = 0;
static io_locker_func io_lock; static mutex_t *io_lock;
static void save_program() static void save_program()
{ {
old_data = new_data; old_data = new_data;
data_page = (data_page + 1) % (FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE); data_page = (data_page + 1) % (FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE);
printf("Program Flash %d %8lx\n", data_page, old_data.magic); if (mutex_enter_timeout_us(io_lock, 200000)) {
io_lock(true); sleep_ms(20); /* wait for all io operations to finish */
uint32_t ints = save_and_disable_interrupts(); uint32_t ints = save_and_disable_interrupts();
if (data_page == 0) { if (data_page == 0) {
flash_range_erase(SAVE_SECTOR_OFFSET, FLASH_SECTOR_SIZE); flash_range_erase(SAVE_SECTOR_OFFSET, FLASH_SECTOR_SIZE);
@ -59,14 +63,18 @@ static void save_program()
flash_range_program(SAVE_SECTOR_OFFSET + data_page * FLASH_PAGE_SIZE, flash_range_program(SAVE_SECTOR_OFFSET + data_page * FLASH_PAGE_SIZE,
(uint8_t *)&old_data, FLASH_PAGE_SIZE); (uint8_t *)&old_data, FLASH_PAGE_SIZE);
restore_interrupts(ints); restore_interrupts(ints);
io_lock(false); mutex_exit(io_lock);
printf("\nProgram Flash %d %8lx done.\n", data_page, old_data.magic);
} else {
printf("Program Flash failed.\n");
}
} }
static void load_default() static void load_default()
{ {
printf("Load Default\n"); printf("Load Default\n");
new_data = default_data; new_data = default_data;
new_data.magic = SAVE_PAGE_MAGIC; new_data.magic = my_magic;
} }
static const page_t *get_page(int id) static const page_t *get_page(int id)
@ -78,7 +86,7 @@ static const page_t *get_page(int id)
static void save_load() static void save_load()
{ {
for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) { for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) {
if (get_page(i)->magic != SAVE_PAGE_MAGIC) { if (get_page(i)->magic != my_magic) {
break; break;
} }
data_page = i; data_page = i;
@ -102,8 +110,30 @@ static void save_loaded()
} }
} }
void save_init(io_locker_func locker) static union __attribute__((packed)) {
pico_unique_board_id_t id;
struct {
uint32_t id32h;
uint32_t id32l;
};
uint64_t id64;
} board_id;
uint32_t board_id_32()
{ {
pico_get_unique_board_id(&board_id.id);
return board_id.id32h ^ board_id.id32l;
}
uint64_t board_id_64()
{
pico_get_unique_board_id(&board_id.id);
return board_id.id64;
}
void save_init(uint32_t magic, mutex_t *locker)
{
my_magic = magic;
io_lock = locker; io_lock = locker;
save_load(); save_load();
save_loop(); save_loop();
@ -112,17 +142,13 @@ void save_init(io_locker_func locker)
void save_loop() void save_loop()
{ {
if (requesting_save && (time_us_64() - requesting_time > 1000000)) { if (requesting_save && (time_us_64() - requesting_time > SAVE_TIMEOUT_US)) {
requesting_save = false; requesting_save = false;
printf("Time to save.\n");
/* only when data is actually changed */ /* only when data is actually changed */
for (int i = 0; i < sizeof(old_data); i++) { if (memcmp(&old_data, &new_data, sizeof(old_data)) == 0) {
if (((uint8_t *)&old_data)[i] != ((uint8_t *)&new_data)[i]) {
save_program();
return; return;
} }
} save_program();
printf("No change.\n");
} }
} }
@ -140,9 +166,9 @@ void *save_alloc(size_t size, void *def, void (*after_load)())
void save_request(bool immediately) void save_request(bool immediately)
{ {
if (!requesting_save) { if (!requesting_save) {
printf("Save marked.\n"); printf("Save requested.\n");
requesting_save = true; requesting_save = true;
new_data.magic = SAVE_PAGE_MAGIC; new_data.magic = my_magic;
requesting_time = time_us_64(); requesting_time = time_us_64();
} }
if (immediately) { if (immediately) {

View File

@ -7,11 +7,17 @@
#define SAVE_H #define SAVE_H
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "pico/multicore.h"
uint32_t board_id_32();
uint64_t board_id_64();
/* It's safer to lock other I/O ops during saving, so we need a locker */ /* It's safer to lock other I/O ops during saving, so we need a locker */
typedef void (*io_locker_func)(bool pause); typedef void (*io_locker_func)(bool pause);
void save_init(io_locker_func locker); void save_init(uint32_t magic, mutex_t *lock);
void save_loop(); void save_loop();

View File

@ -33,7 +33,6 @@ uint32_t setup_led_button[BUTTON_RGB_NUM];
typedef enum { typedef enum {
MODE_NONE, MODE_NONE,
MODE_TURNTABLE, MODE_TURNTABLE,
MODE_ANALOG,
MODE_LEVEL, MODE_LEVEL,
MODE_TT_THEME, MODE_TT_THEME,
MODE_KEY_THEME, MODE_KEY_THEME,
@ -129,7 +128,6 @@ static int16_t input_delta(int16_t start_angle)
static setup_mode_t key_to_mode[11] = { static setup_mode_t key_to_mode[11] = {
MODE_KEY_THEME, MODE_TT_THEME, MODE_KEY_ON, MODE_KEY_OFF, MODE_KEY_THEME, MODE_TT_THEME, MODE_KEY_ON, MODE_KEY_OFF,
MODE_NONE, MODE_NONE, MODE_NONE, MODE_NONE, MODE_NONE, MODE_NONE,
MODE_ANALOG, MODE_ANALOG, MODE_ANALOG, MODE_ANALOG,
}; };
static struct { static struct {
@ -203,7 +201,7 @@ static void tt_key_change()
} else if (JUST_PRESSED(E3)) { } else if (JUST_PRESSED(E3)) {
iidx_cfg->tt_led.mode = (iidx_cfg->tt_led.mode + 1) % 3; iidx_cfg->tt_led.mode = (iidx_cfg->tt_led.mode + 1) % 3;
} else if (JUST_PRESSED(E4)) { } else if (JUST_PRESSED(E4)) {
iidx_cfg->tt_sensor.mode = (iidx_cfg->tt_sensor.mode + 1) % 4; iidx_cfg->tt_sensor.reversed = !iidx_cfg->tt_sensor.reversed;
} else if (JUST_PRESSED(KEY_2)) { } else if (JUST_PRESSED(KEY_2)) {
iidx_cfg->tt_sensor.deadzone = 0; iidx_cfg->tt_sensor.deadzone = 0;
} else if (JUST_PRESSED(KEY_4)) { } else if (JUST_PRESSED(KEY_4)) {
@ -287,20 +285,8 @@ static void tt_loop()
break; break;
} }
switch (iidx_cfg->tt_sensor.mode) { setup_led_button[LED_E4] = iidx_cfg->tt_sensor.reversed ? CYAN : YELLOW;
case 0:
setup_led_button[LED_E4] = GREEN;
break;
case 1:
setup_led_button[LED_E4] = RED;
break;
case 2:
setup_led_button[LED_E4] = CYAN;
break;
default:
setup_led_button[LED_E4] = YELLOW;
break;
}
setup_led_button[LED_KEY_2] = iidx_cfg->tt_sensor.deadzone == 0 ? SILVER : 0; setup_led_button[LED_KEY_2] = iidx_cfg->tt_sensor.deadzone == 0 ? SILVER : 0;
setup_led_button[LED_KEY_4] = iidx_cfg->tt_sensor.deadzone == 1 ? SILVER : 0; setup_led_button[LED_KEY_4] = iidx_cfg->tt_sensor.deadzone == 1 ? SILVER : 0;
setup_led_button[LED_KEY_6] = iidx_cfg->tt_sensor.deadzone == 2 ? SILVER : 0; setup_led_button[LED_KEY_6] = iidx_cfg->tt_sensor.deadzone == 2 ? SILVER : 0;
@ -357,107 +343,6 @@ static void level_loop()
} }
} }
static struct {
uint8_t channel; /* 0:E1(Start), 1:E2(Effect), 2:E3(VEFX), 3:E4 */
volatile uint8_t *value;
int16_t start_angle;
} analog_ctx;
static void analog_key_change()
{
if (JUST_PRESSED(E1)) {
analog_ctx.channel = 0;
analog_ctx.value = &iidx_cfg->effects.e1;
} else if (JUST_PRESSED(E2)) {
analog_ctx.channel = 1;
analog_ctx.value = &iidx_cfg->effects.e2;
} else if (JUST_PRESSED(E3)) {
analog_ctx.channel = 2;
analog_ctx.value = &iidx_cfg->effects.e3;
} else if (JUST_PRESSED(E4)) {
analog_ctx.channel = 3;
analog_ctx.value = &iidx_cfg->effects.e4;
} else if (JUST_PRESSED(KEY_1)) {
*analog_ctx.value = 0;
} else if (JUST_PRESSED(KEY_2)) {
*analog_ctx.value = 43;
} else if (JUST_PRESSED(KEY_3)) {
*analog_ctx.value = 85;
} else if (JUST_PRESSED(KEY_4)) {
*analog_ctx.value = 128;
} else if (JUST_PRESSED(KEY_5)) {
*analog_ctx.value = 170;
} else if (JUST_PRESSED(KEY_6)) {
*analog_ctx.value = 213;
} else if (JUST_PRESSED(KEY_7)) {
*analog_ctx.value = 255;
}
check_exit();
}
static void analog_enter()
{
analog_key_change();
}
static void analog_rotate()
{
int16_t new_value = *analog_ctx.value;
new_value += input.rotate;
if (new_value < 0) {
new_value = 0;
} else if (new_value > 255) {
new_value = 255;
}
*analog_ctx.value = new_value;
}
static uint32_t scale_color(uint32_t color, uint8_t value, uint8_t factor)
{
uint8_t r = (color >> 16) & 0xff;
uint8_t g = (color >> 8) & 0xff;
uint8_t b = color & 0xff;
r = (r * value) / factor;
g = (g * value) / factor;
b = (b * value) / factor;
return (r << 16) | (g << 8) | b;
}
static void analog_loop()
{
uint32_t colors[4] = { RED, GREEN, CYAN, YELLOW};
uint32_t tt_colors[4] = { TT_RED, TT_GREEN, TT_CYAN, TT_YELLOW };
for (int i = 0; i < 4; i++) {
uint32_t color = colors[i];
if (analog_ctx.channel == i) {
color &= blink_fast;
}
setup_led_button[LED_E1 + i] = color;
}
int tt_split = (int)*analog_ctx.value * iidx_cfg->tt_led.num / 255;
for (int i = 1; i < iidx_cfg->tt_led.num - 1; i++) {
setup_led_tt[i] = i < tt_split ? tt_colors[analog_ctx.channel] : 0;
}
int button_split = *analog_ctx.value / 37;
int scale = *analog_ctx.value % 37;
for (int i = 0; i < 7; i++) {
uint32_t color = colors[analog_ctx.channel];
if (i == button_split) {
color = scale_color(color, scale, 37);
} else if (i > button_split) {
color = 0;
}
setup_led_button[LED_KEY_1 + i] = color;
}
}
static struct { static struct {
uint8_t phase; /* 0:H, 1:S, 2:V */ uint8_t phase; /* 0:H, 1:S, 2:V */
hsv_t hsv; hsv_t hsv;
@ -657,7 +542,6 @@ static struct {
} mode_defs[] = { } mode_defs[] = {
[MODE_NONE] = { nop, none_rotate, none_loop, nop}, [MODE_NONE] = { nop, none_rotate, none_loop, nop},
[MODE_TURNTABLE] = { tt_key_change, tt_rotate, tt_loop, tt_enter}, [MODE_TURNTABLE] = { tt_key_change, tt_rotate, tt_loop, tt_enter},
[MODE_ANALOG] = { analog_key_change, analog_rotate, analog_loop, analog_enter},
[MODE_LEVEL] = { level_key_change, level_rotate, level_loop, nop}, [MODE_LEVEL] = { level_key_change, level_rotate, level_loop, nop},
[MODE_TT_THEME] = { tt_theme_key_change, nop, tt_theme_loop, nop}, [MODE_TT_THEME] = { tt_theme_key_change, nop, tt_theme_loop, nop},
[MODE_KEY_THEME] = { key_theme_key_change, nop, key_theme_loop, nop}, [MODE_KEY_THEME] = { key_theme_key_change, nop, key_theme_loop, nop},
@ -672,18 +556,19 @@ static void join_mode(setup_mode_t new_mode)
memset(&setup_led_button, 0, sizeof(setup_led_button)); memset(&setup_led_button, 0, sizeof(setup_led_button));
current_mode = new_mode; current_mode = new_mode;
mode_defs[current_mode].enter(); mode_defs[current_mode].enter();
printf("Entering setup %d\n", new_mode); printf("Entering setup mode %d\n", new_mode);
} }
static void quit_mode(bool apply) static void quit_mode(bool apply)
{ {
printf("Setup %s\n", apply ? "accepted." : "discarded.");
if (apply) { if (apply) {
config_changed(); config_changed();
} else { } else {
*iidx_cfg = cfg_save; *iidx_cfg = cfg_save;
} }
current_mode = MODE_NONE; current_mode = MODE_NONE;
printf("Quit setup %s\n", apply ? "saved." : "discarded.");
} }
bool setup_run(uint16_t keys, uint16_t angle) bool setup_run(uint16_t keys, uint16_t angle)
@ -693,17 +578,11 @@ bool setup_run(uint16_t keys, uint16_t angle)
input.angle = angle; input.angle = angle;
input.just_pressed = keys & ~input.last_keys; input.just_pressed = keys & ~input.last_keys;
input.just_released = ~keys & input.last_keys; input.just_released = ~keys & input.last_keys;
input.rotate = input_delta(input.last_angle); input.rotate = input_delta(input.last_angle);
if (input.rotate != 0) { if (input.rotate != 0) {
printf("@ %3d %2x\n", input.rotate, input.angle);
mode_defs[current_mode].rotate(); mode_defs[current_mode].rotate();
} }
if (input.just_pressed) {
printf("+ %04x\n", input.just_pressed);
}
if (input.just_released) {
printf("- %04x\n", input.just_released);
}
if (input.just_pressed || input.just_released) { if (input.just_pressed || input.just_released) {
mode_defs[current_mode].key_change(); mode_defs[current_mode].key_change();

View File

@ -19,7 +19,6 @@
#include "config.h" #include "config.h"
static uint16_t angle = 0; static uint16_t angle = 0;
static uint8_t current_mode = 0;
static void init_i2c() static void init_i2c()
{ {
@ -32,130 +31,12 @@ static void init_i2c()
gpio_pull_up(TT_AS5600_SDA); gpio_pull_up(TT_AS5600_SDA);
} }
static void init_analog()
{
adc_init();
adc_gpio_init(TT_AS5600_ANALOG);
adc_select_input(TT_AS5600_ANALOG - 26);
}
static void follow_mode_change()
{
if (current_mode != iidx_cfg->tt_sensor.mode) {
turntable_init();
}
}
void turntable_init() void turntable_init()
{ {
current_mode = iidx_cfg->tt_sensor.mode;
if (current_mode > 1) {
init_i2c(); init_i2c();
} else {
init_analog();
}
} }
static uint32_t min_adc = 0; /* idealy [0..3740] */ void turntable_update()
static uint32_t max_adc = 3740;
static bool min_touched = false;
static bool max_touched = false;
static inline void adjust_max(uint32_t value)
{
if (value > max_adc) {
max_adc += (value - max_adc + 1) / 2;
printf("Auto adc max: %4lu %4lu\n", min_adc, max_adc);
}
max_touched = true;
}
static inline void adjust_min(uint32_t value)
{
if (value < min_adc) {
min_adc -= (min_adc - value + 1) / 2;
printf("Auto adc min: %4lu %4lu\n", min_adc, max_adc);
}
min_touched = true;
}
static void auto_adjust_adc()
{
if (!min_touched || !max_touched) {
return;
}
min_touched = false;
max_touched = false;
if (max_adc > 3540) {
max_adc--;
}
if (min_adc < 200) {
min_adc++;
}
printf("Auto adc adj: %4lu %4lu\n", min_adc, max_adc);
}
static uint16_t read_average(uint16_t size)
{
uint32_t large_cnt = 0;
uint32_t small_cnt = 0;
uint32_t large = 0;
uint32_t small = 0;
uint32_t medium = 0;
for (int i = 0; i < size; i++) {
uint32_t sample = adc_read();
if (sample > 3540) {
large_cnt++;
large += sample;
} else if (sample < 200) {
small_cnt++;
small += sample;
} else {
medium += sample;
}
}
if (large_cnt > 50) {
adjust_max(large / large_cnt);
}
if (small_cnt > 50) {
adjust_min(small / small_cnt);
}
uint32_t all = large + small + medium;
if (large_cnt && small_cnt) {
all += small_cnt * max_adc;
}
return (all / size) % max_adc;
}
static void update_analog()
{
static uint16_t sample = 0;
auto_adjust_adc();
uint16_t deadzone = (iidx_cfg->tt_sensor.deadzone + 1) * 16;
int new_value = read_average(200);
int delta = abs(new_value - sample);
if ((abs(delta) < deadzone) || (abs(delta) > 4096 - deadzone)) {
return;
}
sample = new_value;
if (sample < min_adc) {
angle = 0;
return;
}
angle = (sample - min_adc) * 4095 / (max_adc - min_adc);
}
static void update_i2c()
{ {
const uint8_t as5600_addr = 0x36; const uint8_t as5600_addr = 0x36;
uint8_t buf[2] = {0x0c, 0x00}; uint8_t buf[2] = {0x0c, 0x00};
@ -167,20 +48,9 @@ static void update_i2c()
angle = ((uint16_t)buf[0] & 0x0f) << 8 | buf[1]; angle = ((uint16_t)buf[0] & 0x0f) << 8 | buf[1];
} }
void turntable_update()
{
follow_mode_change();
if (current_mode > 1) {
update_i2c();
} else {
update_analog();
}
}
uint16_t turntable_raw() uint16_t turntable_raw()
{ {
bool reversed = iidx_cfg->tt_sensor.mode & 0x01; return iidx_cfg->tt_sensor.reversed ? 4095 - angle : angle; // 12bit
return reversed ? 4095 - angle : angle; // 12bit
} }
uint8_t turntable_read() uint8_t turntable_read()

View File

@ -7,8 +7,6 @@
enum { enum {
REPORT_ID_JOYSTICK = 1, REPORT_ID_JOYSTICK = 1,
REPORT_ID_LIGHTS, REPORT_ID_LIGHTS,
REPORT_ID_KEYBOARD,
REPORT_ID_MOUSE,
}; };
// because they are missing from tusb_hid.h // because they are missing from tusb_hid.h
@ -34,9 +32,7 @@ enum {
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_LOGICAL_MIN(0x00), \ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_LOGICAL_MIN(0x00), \
HID_LOGICAL_MAX_N(0x00ff, 2), /* Below is Joystick/analog */ \ HID_LOGICAL_MAX_N(0x00ff, 2), /* Below is Joystick/analog */ \
HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \ HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \
HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX), \ HID_REPORT_COUNT(2), HID_REPORT_SIZE(8), \
HID_USAGE(HID_USAGE_DESKTOP_RY), HID_USAGE(HID_USAGE_DESKTOP_RZ), \
HID_REPORT_COUNT(6), HID_REPORT_SIZE(8), \
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), HID_COLLECTION_END HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), HID_COLLECTION_END
// Light Map // Light Map
@ -52,19 +48,6 @@ enum {
HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \
HID_COLLECTION_END HID_COLLECTION_END
// NKRO Descriptor
#define GAMECON_REPORT_DESC_NKRO(...) \
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(HID_USAGE_PAGE_KEYBOARD), \
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
__VA_ARGS__ HID_REPORT_SIZE(1), HID_REPORT_COUNT(8), \
HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), HID_USAGE_MIN(224), \
HID_USAGE_MAX(231), HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \
HID_INPUT(HID_VARIABLE), HID_REPORT_SIZE(1), HID_REPORT_COUNT(31 * 8), \
HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \
HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), HID_USAGE_MIN(0), \
HID_USAGE_MAX(31 * 8 - 1), HID_INPUT(HID_VARIABLE), HID_COLLECTION_END
/* Enable Konami spoof mode */ /* Enable Konami spoof mode */
void konami_mode(); void konami_mode();