diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index fe4a13d..f619c43 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -1,11 +1,12 @@ -cmake_minimum_required(VERSION 3.12) - -# Pull in SDK (must set be before project) -include(pico_sdk_import.cmake) - -project(popn60 C CXX ASM) -set(CMAKE_C_STANDARD 11) - -pico_sdk_init() - -add_subdirectory(src) +cmake_minimum_required(VERSION 3.12) + +# Pull in SDK (must set be before project) +include(pico_sdk_import.cmake) + +project(popn_pico C CXX ASM) + +set(CMAKE_C_STANDARD 11) + +pico_sdk_init() + +add_subdirectory(src) diff --git a/firmware/README.md b/firmware/README.md deleted file mode 100644 index ba00cb2..0000000 --- a/firmware/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Popn Pico Firmware - -Features: -* 1000Hz polling rate. -* HID lights (9 button lights and Logo RGB). -* RGB rainbow effects. -* Light brightness control. -* Light fade in/out control. -* Configuration save. -* Customizable through board_defs.h - -Note: -Old (v1.1 and earlier) PCB of Popn Pico controller needs a small modification to use this firmware. diff --git a/firmware/popn_ctrl.uf2 b/firmware/popn_ctrl.uf2 deleted file mode 100644 index b1221f4..0000000 Binary files a/firmware/popn_ctrl.uf2 and /dev/null differ diff --git a/firmware/popn_pico.uf2 b/firmware/popn_pico.uf2 deleted file mode 100644 index e9ebbf1..0000000 Binary files a/firmware/popn_pico.uf2 and /dev/null differ diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index cff1f4d..484b6bd 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -1,19 +1,30 @@ function(make_firmware board board_def) - add_executable(${board} main.c buttons.c rgb.c config.c usb_descriptors.c) + pico_sdk_init() + add_executable(${board} + main.c light.c button.c save.c config.c commands.c cli.c + usb_descriptors.c) target_compile_definitions(${board} PUBLIC ${board_def}) pico_enable_stdio_usb(${board} 1) pico_enable_stdio_uart(${board} 0) pico_generate_pio_header(${board} ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) + target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3) target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + target_link_libraries(${board} PRIVATE - pico_multicore pico_stdlib hardware_pio hardware_pwm hardware_flash + pico_multicore pico_stdlib hardware_uart hardware_pio hardware_pwm + hardware_flash hardware_adc hardware_i2c hardware_watchdog tinyusb_device tinyusb_board) - # Make a UF2 binary pico_add_extra_outputs(${board}) + + add_custom_command(TARGET ${board} PRE_BUILD + COMMAND touch ${CMAKE_CURRENT_SOURCE_DIR}/cli.c) + + add_custom_command(TARGET ${board} POST_BUILD + COMMAND cp ${board}.uf2 ${CMAKE_CURRENT_LIST_DIR}/..) endfunction() -make_firmware(popn_pico BOARD_POPN_PICO) -make_firmware(popn_ctrl BOARD_GENERAL) +make_firmware(popn_pico2 BOARD_POPN_PICO2) + diff --git a/firmware/src/board_defs.h b/firmware/src/board_defs.h index 640143e..26bf717 100644 --- a/firmware/src/board_defs.h +++ b/firmware/src/board_defs.h @@ -1,60 +1,15 @@ /* - * Pico Popn Controller Board Definitions + * Popn Pico 2 Board Definitions * WHowe */ -#if defined BOARD_POPN_PICO -/* This is for my Pico Popn Controller */ +#if defined BOARD_POPN_PICO2 -/* A button consists of a switch and an LED - 9 main buttons + 2 aux buttons. */ -#define BUTTON_DEF { \ - {0, 1}, \ - {2, 3}, \ - {4, 5}, \ - {6, 7}, \ - {8, 9}, \ - {10, 11}, \ - {12, 13}, \ - {14, 15}, \ - {16, 18}, \ - {26, -1}, \ - {27, -1}, \ - {21, -1}, \ - {22, -1} \ -} +/* 9 Main buttons + 2 Aux buttons */ +#define BUTTON_DEF { 0, 1, 2, 3, 4, 7, 8, 9, 10, 5, 6 } -#define RGB_LED_PIN 28 -#define RGB_LED_NUM 10 -#define RGB_LOGO_LEDS {0, 1, 2} -#define RGB_RAINBOW_LEDS {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - -#else -/* This is for general Pico-based Popn Controller - * Define these according to your board configuration. - */ - -/* A button consists of a switch and an LED - 9 main buttons + 2 aux buttons + [if more buttons]. */ -#define BUTTON_DEF { \ - {0, 1}, \ - {2, 3}, \ - {4, 5}, \ - {6, 7}, \ - {8, 9}, \ - {10, 11}, \ - {12, 13}, \ - {14, 15}, \ - {17, 16}, \ - {20, -1}, \ - {19, -1}, \ - {21, -1}, \ - {22, -1} \ -} - -#define RGB_LED_PIN 18 -#define RGB_LED_NUM 60 -#define RGB_LOGO_LEDS {18, 19, 20, 21} -#define RGB_RAINBOW_LEDS {46, 47, 48, 49, 50, 51, 52, 53} +#define RGB_BUTTON_MAP { 0, 8, 1, 7, 2, 6, 3, 5, 4 } +#define RGB_BUTTON_PIN 28 +#define RGB_CAB_PIN 27 #endif diff --git a/firmware/src/button.c b/firmware/src/button.c new file mode 100644 index 0000000..1e6a333 --- /dev/null +++ b/firmware/src/button.c @@ -0,0 +1,84 @@ +/* + * Controller Buttons + * WHowe + * + */ + +#include "button.h" + +#include +#include + +#include "hardware/gpio.h" +#include "hardware/timer.h" +#include "hardware/pwm.h" + +#include "config.h" +#include "board_defs.h" + +static const uint8_t button_gpio[] = BUTTON_DEF; + +#define BUTTON_NUM (sizeof(button_gpio)) + +static bool sw_val[BUTTON_NUM]; /* true if pressed */ +static uint64_t sw_freeze_time[BUTTON_NUM]; + +static uint16_t button_reading; + +void button_init() +{ + for (int i = 0; i < BUTTON_NUM; i++) { + sw_val[i] = false; + sw_freeze_time[i] = 0; + int8_t gpio = button_gpio[i]; + gpio_init(gpio); + gpio_set_function(gpio, GPIO_FUNC_SIO); + gpio_set_dir(gpio, GPIO_IN); + gpio_pull_up(gpio); + } + + /* make valid initial reading */ + button_reading = 0; + for (int i = 0; i < BUTTON_NUM; i++) { + sw_val[i] = !gpio_get(button_gpio[i]); + if (sw_val[i]) { + button_reading |= (1 << i); + } + } +} + +uint8_t button_num() +{ + return BUTTON_NUM; +} + +/* If a switch flips, it freezes for a while */ +#define DEBOUNCE_FREEZE_TIME_US 3000 +void button_update() +{ + uint64_t now = time_us_64(); + uint16_t buttons = 0; + + for (int i = BUTTON_NUM - 1; i >= 0; i--) { + bool sw_pressed = !gpio_get(button_gpio[i]); + + if (now >= sw_freeze_time[i]) { + if (sw_pressed != sw_val[i]) { + sw_val[i] = sw_pressed; + sw_freeze_time[i] = now + DEBOUNCE_FREEZE_TIME_US; + } + } + + buttons <<= 1; + if (sw_val[i]) { + buttons |= 1; + } + } + + button_reading = buttons; +} + +uint16_t button_read() +{ + return button_reading; +} diff --git a/firmware/src/buttons.h b/firmware/src/button.h similarity index 53% rename from firmware/src/buttons.h rename to firmware/src/button.h index 0455fc0..273c928 100644 --- a/firmware/src/buttons.h +++ b/firmware/src/button.h @@ -1,10 +1,10 @@ /* - * Pico Popn Controller Buttons + * Controller Buttons * WHowe */ -#ifndef BUTTONS_H -#define BUTTONS_H +#ifndef BUTTON_H +#define BUTTON_H #include #include @@ -12,11 +12,7 @@ void button_init(); uint8_t button_num(); -uint8_t button_gpio(uint8_t id); - +void button_update(); uint16_t button_read(); -void button_update(); -void button_set_light(uint8_t const *lights, uint8_t num); - #endif diff --git a/firmware/src/buttons.c b/firmware/src/buttons.c deleted file mode 100644 index eedd291..0000000 --- a/firmware/src/buttons.c +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Pico Popn Controller Buttons - * WHowe - * - * A button consists of a switch and an LED - */ - -#include "buttons.h" - -#include -#include - -#include "hardware/gpio.h" -#include "hardware/timer.h" -#include "hardware/pwm.h" - -#include "rgb.h" -#include "config.h" -#include "board_defs.h" - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) - -static const struct button { - int8_t sw_gpio; - int8_t led_gpio; /* led related to the button */ -} BUTTON_DEFS[] = BUTTON_DEF; - -#define BUTTON_NUM (ARRAY_SIZE(BUTTON_DEFS)) - -#define AUX_1 9 -#define AUX_2 10 - -static bool sw_val[BUTTON_NUM]; /* true if pressed */ -static uint64_t sw_freeze_time[BUTTON_NUM]; - -typedef struct { - uint8_t led_level; - uint8_t rgb_level; - uint8_t up_speed; - uint8_t down_speed; -} button_cfg_t; - -static button_cfg_t *cfg; - -static void cfg_loaded() -{ - rgb_set_brightness(cfg->rgb_level); -} - -static void init_cfg() -{ - button_cfg_t def = {8, 8, 0, 0}; - cfg = (button_cfg_t *)config_alloc(sizeof(def), &def, cfg_loaded); -} - -static uint16_t alpha[256]; -static void init_alpha() -{ - for (int i = 0; i < 256; i++) { - alpha[i] = (i + 1) * (i + 1) - 1; - } -} - -static void init_switch() -{ - for (int i = 0; i < BUTTON_NUM; i++) - { - sw_val[i] = false; - sw_freeze_time[i] = 0; - int8_t gpio = BUTTON_DEFS[i].sw_gpio; - gpio_init(gpio); - gpio_set_function(gpio, GPIO_FUNC_SIO); - gpio_set_dir(BUTTON_DEFS[i].sw_gpio, GPIO_IN); - gpio_pull_up(BUTTON_DEFS[i].sw_gpio); - } -} - -static void init_led() -{ - for (int i = 0; i < BUTTON_NUM; i++) - { - int8_t gpio = BUTTON_DEFS[i].led_gpio; - if (gpio >= 0) { - gpio_set_function(gpio, GPIO_FUNC_PWM); - uint slice = pwm_gpio_to_slice_num(gpio); - pwm_config cfg = pwm_get_default_config(); - pwm_config_set_clkdiv(&cfg, 4.f); - pwm_init(slice, &cfg, true); - } - } -} - -void button_init() -{ - init_cfg(); - init_alpha(); - init_switch(); - init_led(); -} - -uint8_t button_num() -{ - return BUTTON_NUM; -} - -uint8_t button_gpio(uint8_t id) -{ - return BUTTON_DEFS[id].sw_gpio; -} - -typedef enum { - NORMAL, - SET_LED_LEVEL, - SET_RGB_LEVEL, - SET_FADING_LEVEL, -} mode_t; - -mode_t run_mode = NORMAL; - -/* If a switch flips, it freezes for a while */ -#define DEBOUNCE_FREEZE_TIME_US 1000 -uint16_t button_read() -{ - uint64_t now = time_us_64(); - uint16_t buttons = 0; - - for (int i = BUTTON_NUM - 1; i >= 0; i--) { - bool sw_pressed = !gpio_get(BUTTON_DEFS[i].sw_gpio); - - if (now >= sw_freeze_time[i]) { - if (sw_pressed != sw_val[i]) { - sw_val[i] = sw_pressed; - sw_freeze_time[i] = now + DEBOUNCE_FREEZE_TIME_US; - if (i < 9) { - rgb_stimulate(); - } - } - } - - buttons <<= 1; - if (sw_val[i]) { - buttons |= 1; - } - } - - /* Hide 9 main button signals while in setting mode */ - return run_mode == NORMAL ? buttons : (buttons & 0xFE00); -} - -#define HID_EXPIRE_DURATION 1000000ULL -static uint32_t hid_expire_time = 0; -static bool hid_lights[BUTTON_NUM]; - -static bool led_on[BUTTON_NUM]; -static uint8_t led_pwm[BUTTON_NUM]; - -static const uint8_t level_val[9] = {0, 33, 77, 117, 150, 180, 205, 230, 255}; -static const uint8_t up_cycles[4] = {1, 20, 40, 60}; -static const uint8_t down_cycles[5] = {1, 32, 64, 96, 128}; - -static void drive_led_pwm() -{ - for (int i = 0; i < BUTTON_NUM; i++) { - if (BUTTON_DEFS[i].led_gpio >= 0) { - pwm_set_gpio_level(BUTTON_DEFS[i].led_gpio, alpha[led_pwm[i]]); - } - } -} - -static void run_led_fade() -{ - static uint32_t up_cnt = 0; - static uint32_t down_cnt = 0; - uint32_t up_cycle = up_cycles[cfg->up_speed] * (9 - cfg->led_level); - uint32_t down_cycle = down_cycles[cfg->down_speed] * (9 - cfg->led_level); - up_cnt = (up_cnt + 1) % up_cycle; - down_cnt = (down_cnt + 1) % down_cycle; - - for (int i = 0; i < BUTTON_NUM; i++) { - uint8_t target = led_on[i] ? level_val[cfg->led_level] : 0; - if ((target > led_pwm[i]) && (up_cnt == 0)) { - led_pwm[i]++; - } else if ((target < led_pwm[i]) && (down_cnt == 0)) { - led_pwm[i]--; - } - - /* quickly limit led level */ - if (led_pwm[i] > level_val[cfg->led_level]) { - led_pwm[i] = level_val[cfg->led_level]; - } - } -} - -static void led_demo(bool reset) -{ - static uint32_t loop = 0; - if (reset) { - loop = 0; - return; - } - - loop = (loop + 1) % 0x24000; - - bool is_on = (loop < 0x12000); - for (int i = 0; i < 9; i++) { - led_on[i] = is_on; - } -} - -static void led_indicate(uint8_t id) -{ - static uint32_t loop = 0; - loop = (loop + 1) % 0x2000; - led_pwm[id] = (loop & 0x1000) > 0 ? 200 : 0; -} - -static void update_mode() -{ - mode_t new_mode; - if (sw_val[AUX_1] && !sw_val[AUX_2]) { - new_mode = SET_LED_LEVEL; - } else if (!sw_val[AUX_1] && sw_val[AUX_2]) { - new_mode = SET_RGB_LEVEL; - } else if (sw_val[AUX_1] && sw_val[AUX_2]) { - new_mode = SET_FADING_LEVEL; - } else { - new_mode = NORMAL; - } - if (new_mode != run_mode) { - run_mode = new_mode; - led_demo(true); - } -} - -static void run_normal() -{ - bool hid_active = (time_us_64() < hid_expire_time); - for (int i = 0; i < BUTTON_NUM; i++) { - led_on[i] = hid_active ? hid_lights[i] : sw_val[i]; - } -} - -static void read_value(uint8_t *value) -{ - for (int i = 0; i < 9; i++) { - if (sw_val[i]) { - *value = i; - config_request_save(); - led_demo(true); - break; - } - } -} - -static void run_led_setup() -{ - read_value(&cfg->led_level); - led_demo(false); - led_indicate(cfg->led_level); -} - -static void run_rgb_setup() -{ - read_value(&cfg->rgb_level); - rgb_set_brightness(cfg->rgb_level); - led_indicate(cfg->rgb_level); -} - -static void get_fade_value() -{ - for (int i = 0; i < 9; i++) { - if (sw_val[i]) { - if (i % 2 == 0) { - cfg->down_speed = i / 2; - } else { - cfg->up_speed = (i - 1) / 2; - } - config_request_save(); - led_demo(true); - return; - } - } -} - -static void run_fading_setup() -{ - get_fade_value(); - led_demo(false); - led_indicate(cfg->up_speed * 2 + 1); - led_indicate(cfg->down_speed * 2); -} - -static void proc_mode() -{ - switch(run_mode) { - case NORMAL: - run_normal(); - break; - case SET_LED_LEVEL: - run_led_setup(); - break; - case SET_RGB_LEVEL: - run_rgb_setup(); - break; - case SET_FADING_LEVEL: - run_fading_setup(); - break; - } -} - -void button_update() -{ - run_led_fade(); - update_mode(); - proc_mode(); - drive_led_pwm(); -} - -void button_set_light(uint8_t const *lights, uint8_t num) -{ - for (int i = 0; (i < num) && (i < BUTTON_NUM); i++) { - hid_lights[i] = (lights[i] > 0); - } - hid_expire_time = time_us_64() + HID_EXPIRE_DURATION; -} diff --git a/firmware/src/cli.c b/firmware/src/cli.c new file mode 100644 index 0000000..6cabc55 --- /dev/null +++ b/firmware/src/cli.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include + +#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; +} + +const char *built_time = __DATE__ " " __TIME__; +static void handle_help(int argc, char *argv[]) +{ + printf("%s", cli_logo); + printf("\tSN: %016llx\n", board_id_64()); + printf("\tBuilt: %s\n\n", built_time); + 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() +{ + static bool was_connected = false; + static uint64_t connect_time = 0; + static bool welcomed = false; + bool connected = stdio_usb_connected(); + bool just_connected = connected && !was_connected; + was_connected = connected; + if (!connected) { + return; + } + if (just_connected) { + connect_time = time_us_64(); + welcomed = false; + return; + } + if (!welcomed && (time_us_64() - connect_time > 200000)) { + welcomed = true; + cmd_len = 0; + handle_help(0, NULL); + printf("\n%s", cli_prompt); + } + int c = getchar_timeout_us(0); + if (c < 0) { + 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."); +} diff --git a/firmware/src/cli.h b/firmware/src/cli.h new file mode 100644 index 0000000..96a3043 --- /dev/null +++ b/firmware/src/cli.h @@ -0,0 +1,21 @@ +/* + * Popn Controller Command Line Framework + * WHowe + */ + +#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); + +extern const char *built_time; +#endif diff --git a/firmware/src/commands.c b/firmware/src/commands.c new file mode 100644 index 0000000..e6b26a1 --- /dev/null +++ b/firmware/src/commands.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +#include "pico/stdio.h" +#include "pico/stdlib.h" + +#include "config.h" +#include "save.h" +#include "cli.h" + +#include "usb_descriptors.h" + +#define SENSE_LIMIT_MAX 9 +#define SENSE_LIMIT_MIN -9 + +static void disp_light() +{ + printf("[Light]\n"); + printf(" Level: %d.\n", popn_cfg->light.level); +} + +void handle_display(int argc, char *argv[]) +{ + const char *usage = "Usage: display [light]\n"; + if (argc > 1) { + printf(usage); + return; + } + + if (argc == 0) { + disp_light(); + return; + } + + const char *choices[] = {"light" }; + switch (cli_match_prefix(choices, count_of(choices), argv[0])) { + case 0: + disp_light(); + break; + default: + printf(usage); + break; + } +} + +static int fps[2]; +void 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_level(int argc, char *argv[]) +{ + const char *usage = "Usage: level <0..255>\n"; + if (argc != 1) { + printf(usage); + return; + } + + int level = cli_extract_non_neg_int(argv[0], 0); + if ((level < 0) || (level > 255)) { + printf(usage); + return; + } + + popn_cfg->light.level = level; + config_changed(); + disp_light(); +} + +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("display", handle_display, "Display all config."); + cli_register("level", handle_level, "Set LED brightness level."); + cli_register("save", handle_save, "Save config to flash."); + cli_register("factory", handle_factory_reset, "Reset everything to default."); +} diff --git a/firmware/src/commands.h b/firmware/src/commands.h new file mode 100644 index 0000000..19751ac --- /dev/null +++ b/firmware/src/commands.h @@ -0,0 +1,11 @@ +/* + * Popn Controller Command Line Commands + * WHowe + */ + +#ifndef COMMANDS_H +#define COMMANDS_H + +void commands_init(); + +#endif diff --git a/firmware/src/config.c b/firmware/src/config.c index 6ae0921..8de04e7 100644 --- a/firmware/src/config.c +++ b/firmware/src/config.c @@ -1,143 +1,44 @@ /* - * Pico Popn Controller Config + * Controller Config and Runtime Data * WHowe * - * Config is stored in last sector of flash + * Config is a global data structure that stores all the configuration + * Runtime is something to share between files. */ #include "config.h" +#include "save.h" -#include -#include -#include +popn_cfg_t *popn_cfg; +static popn_cfg_t default_cfg = { + .light = { + .level = 128, + }, +}; -#include "bsp/board.h" -#include "pico/bootrom.h" -#include "pico/stdio.h" - -#include "hardware/flash.h" -#include "hardware/sync.h" -#include "rgb.h" - -static struct { - size_t size; - size_t offset; - void (*after_load)(); -} modules[8] = {0}; -static int module_num = 0; - -#define CONFIG_PAGE_MAGIC 0x13424321 -#define CONFIG_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) - -typedef struct __attribute__ ((__packed__)) { - uint32_t magic; - uint8_t data[FLASH_PAGE_SIZE - 4]; -} page_t; - -static page_t old_cfg = {0}; -static page_t new_cfg = {0}; -static page_t default_cfg = {0}; -static int cfg_page = -1; - -static bool requesting_save = false; -static uint64_t requesting_time = 0; - -static void config_save() -{ - old_cfg = new_cfg; - - cfg_page = (cfg_page + 1) % (FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE); - printf("Program Flash %d %8lx\n", cfg_page, old_cfg.magic); - rgb_pause(true); - sleep_ms(1); - uint32_t ints = save_and_disable_interrupts(); - if (cfg_page == 0) { - flash_range_erase(CONFIG_SECTOR_OFFSET, FLASH_SECTOR_SIZE); - } - flash_range_program(CONFIG_SECTOR_OFFSET + cfg_page * FLASH_PAGE_SIZE, - (uint8_t *)&old_cfg, FLASH_PAGE_SIZE); - restore_interrupts(ints); - rgb_pause(false); -} - -static void load_default() -{ - printf("Load Default\n"); - new_cfg = default_cfg; - new_cfg.magic = CONFIG_PAGE_MAGIC; -} - -static const page_t *get_page(int id) -{ - int addr = XIP_BASE + CONFIG_SECTOR_OFFSET; - return (page_t *)(addr + FLASH_PAGE_SIZE * id); -} - -static void config_load() -{ - for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) { - if (get_page(i)->magic != CONFIG_PAGE_MAGIC) { - break; - } - cfg_page = i; - } - - if (cfg_page < 0) { - load_default(); - config_request_save(); - return; - } - - old_cfg = *get_page(cfg_page); - new_cfg = old_cfg; - printf("Page Loaded %d %8lx\n", cfg_page, new_cfg.magic); -} +popn_runtime_t popn_runtime; static void config_loaded() { - for (int i = 0; i < module_num; i++) { - modules[i].after_load(); + if (popn_cfg->spin.units_per_turn < 20) { + popn_cfg->spin.units_per_turn = 80; + config_changed(); } } +void config_changed() +{ + save_request(false); +} + +void config_factory_reset() +{ + *popn_cfg = default_cfg; + save_request(true); +} + void config_init() { - config_load(); - config_loop(); - config_loaded(); -} - -void config_loop() -{ - if ((requesting_save) && (time_us_64() - requesting_time > 1000000)) { - requesting_save = false; - /* only when data is actually changed */ - for (int i = 0; i < sizeof(old_cfg); i++) { - if (((uint8_t *)&old_cfg)[i] != ((uint8_t *)&new_cfg)[i]) { - config_save(); - return; - } - } - } -} - -void *config_alloc(size_t size, void *def, void (*after_load)()) -{ - modules[module_num].size = size; - size_t offset = module_num > 0 ? modules[module_num - 1].offset + size : 0; - modules[module_num].offset = offset; - modules[module_num].after_load = after_load; - module_num++; - memcpy(default_cfg.data + offset, def, size); // backup the default - return new_cfg.data + offset; -} - -void config_request_save() -{ - requesting_time = time_us_64(); - if (!requesting_save) { - requesting_save = true; - new_cfg.magic = CONFIG_PAGE_MAGIC; - } + popn_cfg = (popn_cfg_t *)save_alloc(sizeof(*popn_cfg), &default_cfg, config_loaded); } diff --git a/firmware/src/config.h b/firmware/src/config.h index b6a06d9..97ac386 100644 --- a/firmware/src/config.h +++ b/firmware/src/config.h @@ -1,17 +1,41 @@ /* - * Pico Popn Controller Config + * Controller Config * WHowe */ #ifndef CONFIG_H #define CONFIG_H -#include +#include +#include + +typedef struct { + uint8_t rgb_hsv; // 0: RGB, 1: HSV + uint8_t val[3]; // RGB or HSV +} rgb_hsv_t; + +typedef struct __attribute__((packed)) { + struct { + uint8_t units_per_turn; + bool reversed; + } spin; + struct { + uint8_t level; + } light; + uint8_t theme; +} popn_cfg_t; + +typedef struct { + uint16_t fps[2]; + uint8_t chain_id; + uint64_t sync_expire; +} popn_runtime_t; + +extern popn_cfg_t *popn_cfg; +extern popn_runtime_t popn_runtime; void config_init(); -void config_loop(); - -void *config_alloc(size_t size, void *def, void (*after_load)()); -void config_request_save(); +void config_changed(); // Notify the config has changed +void config_factory_reset(); // Reset the config to factory default #endif diff --git a/firmware/src/light.c b/firmware/src/light.c new file mode 100644 index 0000000..f7b936b --- /dev/null +++ b/firmware/src/light.c @@ -0,0 +1,183 @@ +/* + * WS2812B Lights Control (Base + Left and Right Gimbals) + * WHowe + * + */ + +#include "light.h" + +#include +#include +#include +#include + +#include "bsp/board.h" +#include "hardware/pio.h" +#include "hardware/timer.h" + +#include "ws2812.pio.h" + +#include "board_defs.h" +#include "config.h" + +#define HID_TIMEOUT 10*1000*1000 + +static uint32_t buf_main[27]; +static uint32_t buf_cab[9]; + +static const uint8_t light_button_map[9] = RGB_BUTTON_MAP; + +static inline uint32_t _rgb32(uint32_t c1, uint32_t c2, uint32_t c3, bool gamma_fix) +{ + if (gamma_fix) { + c1 = ((c1 + 1) * (c1 + 1) - 1) >> 8; + c2 = ((c2 + 1) * (c2 + 1) - 1) >> 8; + c3 = ((c3 + 1) * (c3 + 1) - 1) >> 8; + } + + return (c1 << 16) | (c2 << 8) | (c3 << 0); +} + +uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix) +{ +#if RGB_ORDER == GRB + return _rgb32(g, r, b, gamma_fix); +#else + return _rgb32(r, g, b, gamma_fix); +#endif +} + +uint32_t rgb32_from_hsv(uint8_t h, uint8_t s, uint8_t v) +{ + uint32_t region, remainder, p, q, t; + + if (s == 0) { + return v << 16 | v << 8 | v; + } + + region = h / 43; + remainder = (h % 43) * 6; + + p = (v * (255 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 8))) >> 8; + t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; + + switch (region) { + case 0: + return v << 16 | t << 8 | p; + case 1: + return q << 16 | v << 8 | p; + case 2: + return p << 16 | v << 8 | t; + case 3: + return p << 16 | q << 8 | v; + case 4: + return t << 16 | p << 8 | v; + default: + return v << 16 | p << 8 | q; + } +} + +static void drive_led() +{ + for (int i = 0; i < count_of(buf_main); i++) { + pio_sm_put_blocking(pio0, 0, buf_main[i] << 8u); + } + + for (int i = 0; i < count_of(buf_cab); i++) { + pio_sm_put_blocking(pio0, 1, buf_cab[i] << 8u); + pio_sm_put_blocking(pio0, 1, buf_cab[i] << 8u); + } +} + +static inline uint32_t apply_level(uint32_t color) +{ + unsigned r = (color >> 16) & 0xff; + unsigned g = (color >> 8) & 0xff; + unsigned b = color & 0xff; + + r = r * popn_cfg->light.level / 255; + g = g * popn_cfg->light.level / 255; + b = b * popn_cfg->light.level / 255; + + return r << 16 | g << 8 | b; +} + +void light_init() +{ + uint offset = pio_add_program(pio0, &ws2812_program); + ws2812_program_init(pio0, 0, offset, RGB_BUTTON_PIN, 800000, false); + ws2812_program_init(pio0, 1, offset, RGB_CAB_PIN, 800000, false); +} + +void light_update() +{ + static uint64_t last = 0; + uint64_t now = time_us_64(); + if (now - last < 5000) { // 200Hz + return; + } + + last = now; + + drive_led(); +} + +void light_set(uint8_t index, uint32_t color) +{ + if (index >= count_of(buf_main)) { + return; + } + buf_main[index] = apply_level(color); +} + +static bool bypass_check(bool hid) +{ + static uint64_t hid_timeout = 0; + uint64_t now = time_us_64(); + + if (hid) { + hid_timeout = now + HID_TIMEOUT; + return false; + } + + return now < hid_timeout; +} + +void light_set_button(uint8_t index, uint32_t color, bool hid) +{ + if (bypass_check(hid)) { + return; + } + if (index >= count_of(buf_main) / 3) { + return; + } + + int offset = light_button_map[index] * 3; + for (int i = 0; i < 3; i++) { + light_set(offset + i, color); + } +} + +void light_set_cab(uint8_t index, uint32_t color, bool hid) +{ + if (bypass_check(hid)) { + return; + } + if (index >= count_of(buf_cab)) { + return; + } + + buf_cab[index] = apply_level(color); +} + +void light_set_cab_all(uint32_t color, bool hid) +{ + if (bypass_check(hid)) { + return; + } + + for (int i = 0; i < 6; i++) { + buf_cab[i] = apply_level(color); + } +} diff --git a/firmware/src/light.h b/firmware/src/light.h new file mode 100644 index 0000000..fb44a68 --- /dev/null +++ b/firmware/src/light.h @@ -0,0 +1,40 @@ +/* + * WS2812B Lights Control (Base + Left and Right Gimbals) + * WHowe + */ + +#ifndef LIGHT_H +#define LIGHT_H + +#include +#include +#include + +#include "config.h" + +void light_init(); +void light_update(); + +uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix); +uint32_t rgb32_from_hsv(uint8_t h, uint8_t s, uint8_t v); + +void light_set_button(uint8_t index, uint32_t color, bool hid); +void light_set_cab(uint8_t index, uint32_t color, bool hid); +void light_set_cab_all(uint32_t color, bool hid); + +#if RGB_ORDER == GRB +#define RGB(r, g, b) ((g << 16) | (r << 8) | b) +#elif RGB_ORDER == RGB +#define RGB(r, g, b) ((r << 16) | (g << 8) | b) +#endif + +#define RED RGB(255, 0, 0) +#define GREEN RGB(0, 255, 0) +#define BLUE RGB(0, 0, 255) +#define YELLOW RGB(255, 255, 0) +#define CYAN RGB(0, 255, 255) +#define MAGENTA RGB(255, 0, 255) +#define WHITE RGB(255, 255, 255) +#define BLACK RGB(0, 0, 0) + +#endif diff --git a/firmware/src/main.c b/firmware/src/main.c index 0552376..a3d7ee9 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -1,72 +1,202 @@ /* - * Pico Popn Controller Main + * Controller Main * WHowe */ -#include +#include #include #include -#include "bsp/board.h" -#include "pico/bootrom.h" #include "pico/stdio.h" +#include "pico/stdlib.h" +#include "bsp/board.h" +#include "pico/multicore.h" +#include "pico/bootrom.h" + +#include "hardware/clocks.h" +#include "hardware/gpio.h" +#include "hardware/sync.h" +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/sio.h" #include "tusb.h" #include "usb_descriptors.h" -#include "buttons.h" -#include "rgb.h" +#include "board_defs.h" + +#include "save.h" #include "config.h" +#include "cli.h" +#include "commands.h" -#define BUTTON_LIGHT_MAX_NUM 32 /* Must be larger than number of buttons */ -bool button_lights[BUTTON_LIGHT_MAX_NUM] = {0}; +#include "light.h" +#include "button.h" -struct report +static void light_render() { - uint16_t buttons; -} report; + uint32_t phase = time_us_32() >> 16; -void main_loop() -{ - if (tud_hid_ready()) { - tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &report, sizeof(report)); + for (int i = 0; i < 9; i ++) { + uint32_t color = rgb32_from_hsv(phase + i * 27, 255, 128); + light_set_button(i, color, false); + } + + uint16_t button = button_read(); + for (int i = 0; i < 9; i++) { + if (button & (1 << i)) { + light_set_button(i, WHITE, false); + } + } + + for (int i = 0; i < 9; i++) { + uint32_t color = rgb32_from_hsv(phase + i * 25, 255, 160); + light_set_cab(i, color, false); } } -void boot_check() +static void run_lights() { - uint64_t key1 = (1 << 9); - uint64_t key2 = (1 << 10); - uint64_t buttons = button_read(); - if ((buttons & key1) && (buttons & key2)) { - reset_usb_boot(0, 2); + static uint64_t next_frame = 0; + uint64_t now = time_us_64(); + if (now < next_frame) { + return; } + next_frame = now + 1000000 / 120; // 120Hz + + light_render(); +} + +static mutex_t core1_io_lock; +static void core1_loop() +{ + while (1) { + if (mutex_try_enter(&core1_io_lock, NULL)) { + run_lights(); + light_update(); + mutex_exit(&core1_io_lock); + } + cli_fps_count(1); + sleep_us(700); + } +} + +struct __attribute__((packed)) { + uint16_t buttons; +} hid_report = {0}; + +static void hid_update() +{ + static uint64_t last_report = 0; + uint64_t now = time_us_64(); + if (now - last_report < 1000) { + return; + } + last_report = now; + + hid_report.buttons = button_read(); + + if (tud_hid_ready()) { + tud_hid_n_report(0, REPORT_ID_JOYSTICK, &hid_report, sizeof(hid_report)); + } +} + +#define LOOP_HZ 4000 + +static void core0_loop() +{ + uint64_t next_frame = 0; + + while(1) { + tud_task(); + + cli_run(); + + save_loop(); + cli_fps_count(0); + + button_update(); + + hid_update(); + + uint64_t now = time_us_64(); + if (now < next_frame) { + sleep_us(next_frame - now - 1); + } + next_frame += 1000000 / LOOP_HZ; + } +} + +/* if certain key pressed when booting, enter update mode */ +static void update_check() +{ + const uint8_t pins[] = BUTTON_DEF; + int pressed = 0; + for (int i = 0; i < count_of(pins); i++) { + uint8_t gpio = pins[i]; + gpio_init(gpio); + gpio_set_function(gpio, GPIO_FUNC_SIO); + gpio_set_dir(gpio, GPIO_IN); + gpio_pull_up(gpio); + sleep_ms(1); + if (!gpio_get(gpio)) { + pressed++; + } + } + + if (pressed >= 3) { + sleep_ms(100); + reset_usb_boot(0, 2); + return; + } +} + +static void theme_check() +{ + uint16_t button = button_read(); + if (button & 0x01) { + popn_cfg->theme = 0; + } else if (button & 0x04) { + popn_cfg->theme = 1; + } else if (popn_cfg->theme > 1) { + popn_cfg->theme = 0; + } else { + return; + } + + config_changed(); } void init() { + sleep_ms(50); + set_sys_clock_khz(150000, true); + board_init(); + + update_check(); + tusb_init(); - button_init(); - rgb_init(); - boot_check(); stdio_init_all(); + config_init(); + mutex_init(&core1_io_lock); + save_init(0xca44caac, &core1_io_lock); + + light_init(); + button_init(); + + cli_init("popn_pico>", "\n << Popn Pico 2 Controller >>\n" + " https://github.com/whowechina\n\n"); + + theme_check(); + commands_init(); } int main(void) { init(); - - while (1) - { - tud_task(); - report.buttons = button_read(); - main_loop(); - button_update(); - config_loop(); - } - + multicore_launch_core1(core1_loop); + core0_loop(); return 0; } @@ -77,6 +207,7 @@ uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) { + printf("Get from USB %d-%d\n", report_id, report_type); return 0; } @@ -86,17 +217,6 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { - if ((report_id == REPORT_ID_LIGHTS) && - (report_type == HID_REPORT_TYPE_OUTPUT)) { - if (bufsize < 13 + 3) { /* including logo rgb */ - return; - } - for (int i = 0; i < 13; i++) { - button_lights[i] = (buffer[i] > 0); - } - button_set_light(buffer, 13); - - uint8_t const *rgb = buffer + 13; - rgb_update_logo(rgb[0], rgb[1], rgb[2]); + if (report_id == REPORT_ID_LIGHTS) { } } diff --git a/firmware/src/rgb.c b/firmware/src/rgb.c deleted file mode 100644 index a772e75..0000000 --- a/firmware/src/rgb.c +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Pico Popn Controller Buttons - * WHowe - * - * A button consists of a switch and an LED - */ - -#include "buttons.h" - -#include -#include -#include - -#include "pico/multicore.h" -#include "hardware/pio.h" -#include "hardware/timer.h" - -#include "ws2812.pio.h" - -#include "board_defs.h" - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) - -static const uint8_t logo_leds[] = RGB_LOGO_LEDS; -static const uint8_t effect_leds[] = RGB_RAINBOW_LEDS; - -static const uint8_t level_val[] = {0, 32, 64, 96, 128, 160, 192, 224, 255}; -static uint8_t rgb_level = 0; - -static uint8_t logo_r = 0; -static uint8_t logo_g = 0; -static uint8_t logo_b = 0; - -static inline uint32_t urgb_u32(uint32_t r, uint32_t g, uint32_t b) -{ - r = r * level_val[rgb_level] / 255; - g = g * level_val[rgb_level] / 255; - b = b * level_val[rgb_level] / 255; - - /* better gamma */ - r = (r + 1) * (r + 1) - 1; - g = (g + 1) * (g + 1) - 1; - b = (b + 1) * (b + 1) - 1; - return (g >> 8 << 16) | (r >> 8 << 8) | (b >> 8 << 0); -} - -/* 6 segment regular hsv color wheel, better color cycle - * https://www.arnevogel.com/rgb-rainbow/ - * https://www.instructables.com/How-to-Make-Proper-Rainbow-and-Random-Colors-With-/ - */ -#define COLOR_WHEEL_SIZE (256 * 6) -static uint32_t color_wheel[COLOR_WHEEL_SIZE]; -static void generate_color_wheel() -{ - for (int i = 0; i < COLOR_WHEEL_SIZE; i++) { - uint32_t sector = i / 256 % 6; - uint8_t incr = i % 256; - uint8_t decr = 255 - incr; - if (sector == 0) { - color_wheel[i] = urgb_u32(incr, 0, 255); - } else if (sector == 1) { - color_wheel[i] = urgb_u32(255, 0, decr); - } else if (sector == 2) { - color_wheel[i] = urgb_u32(255, incr, 0); - } else if (sector == 3) { - color_wheel[i] = urgb_u32(decr, 255, 0); - } else if (sector == 4) { - color_wheel[i] = urgb_u32(0, 255, incr); - } else { - color_wheel[i] = urgb_u32(0, decr, 255); - } - } -} - -void rgb_set_brightness(uint8_t level) -{ - if (rgb_level == level) { - return; - } - rgb_level = level; - generate_color_wheel(); -} - -static bool pio_paused = false; -void rgb_pause(bool pause) -{ - pio_paused = pause; -} - -static uint32_t led_buf[RGB_LED_NUM] = {0}; -void drive_led() -{ - if (pio_paused) { - return; - } - - for (int i = 0; i < ARRAY_SIZE(led_buf); i++) { - pio_sm_put_blocking(pio0, 0, led_buf[i] << 8u); - } -} - -#define HID_EXPIRE_DURATION 1000000ULL -static uint32_t hid_expire_time = 0; - -#define RAINBOW_SPEED_MAX 100 -#define RAINBOW_SPEED_MIN 4 -#define RAINBOW_PITCH 151 -#define RAINBOW_SPEED_DOWN_INTERVAL 200000ULL -uint32_t speed = RAINBOW_SPEED_MIN; - -void rainbow_update() -{ - static uint32_t rotator = 0; - rotator = (rotator + speed) % COLOR_WHEEL_SIZE; - - for (int i = 0; i < sizeof(effect_leds); i++) { - uint32_t index = (rotator + RAINBOW_PITCH * i) % COLOR_WHEEL_SIZE; - led_buf[effect_leds[i]] = color_wheel[index]; - } -} - -void rainbow_speed_up() -{ - if (speed < RAINBOW_SPEED_MAX) { - speed++; - } -} - -void rainbow_speed_down() -{ - static uint64_t next_change_time = 0; - uint64_t now = time_us_64(); - if (now >= next_change_time) { - next_change_time = now + RAINBOW_SPEED_DOWN_INTERVAL; - if (speed > RAINBOW_SPEED_MIN) { - speed = speed * 95 / 100; - } - } -} - -void rgb_update_logo(uint8_t r, uint8_t g, uint8_t b) -{ - logo_r = r; - logo_g = g; - logo_b = b; - hid_expire_time = time_us_64() + HID_EXPIRE_DURATION; -} - -static void hid_update() -{ - if (time_us_64() > hid_expire_time) { - return; - } - - uint32_t color = urgb_u32(logo_r, logo_g, logo_b); - for (int i = 0; i < sizeof(logo_leds); i++) { - led_buf[logo_leds[i]] = color; - } -} - -void rgb_stimulate() -{ - rainbow_speed_up(); -} - -void rgb_entry() -{ - while (1) { - rainbow_update(); - hid_update(); - rainbow_speed_down(); - drive_led(); - sleep_ms(10); - } -} - -void rgb_init() -{ - uint offset = pio_add_program(pio0, &ws2812_program); - ws2812_program_init(pio0, 0, offset, RGB_LED_PIN, 800000, false); - multicore_launch_core1(rgb_entry); - rgb_set_brightness(8); -} diff --git a/firmware/src/rgb.h b/firmware/src/rgb.h deleted file mode 100644 index 1e97b73..0000000 --- a/firmware/src/rgb.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Pico Popn Controller Buttons - * WHowe - */ - -#ifndef RGB_H -#define RGB_H - -#include - -void rgb_init(); -void rgb_update_logo(uint8_t r, uint8_t g, uint8_t b); -void rgb_stimulate(); -void rgb_set_brightness(uint8_t level); -void rgb_pause(bool pause); - -#endif diff --git a/firmware/src/save.c b/firmware/src/save.c new file mode 100644 index 0000000..8152826 --- /dev/null +++ b/firmware/src/save.c @@ -0,0 +1,178 @@ +/* + * Controller Config Save and Load + * WHowe + * + * Config is stored in last sector of flash + */ + +#include "save.h" + +#include +#include +#include +#include +#include + + +#include "pico/bootrom.h" +#include "pico/stdio.h" + +#include "hardware/flash.h" +#include "pico/multicore.h" +#include "pico/unique_id.h" + +static struct { + size_t size; + size_t offset; + void (*after_load)(); +} modules[8] = {0}; +static int module_num = 0; + +static uint32_t my_magic = 0xcafec00e; + +#define SAVE_TIMEOUT_US 5000000 + +#define SAVE_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) + +typedef struct __attribute ((packed)) { + uint32_t magic; + uint8_t data[FLASH_PAGE_SIZE - 4]; +} page_t; + +static page_t old_data = {0}; +static page_t new_data = {0}; +static page_t default_data = {0}; +static int data_page = -1; + +static bool requesting_save = false; +static uint64_t requesting_time = 0; + +static mutex_t *io_lock; + +static void save_program() +{ + old_data = new_data; + + data_page = (data_page + 1) % (FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE); + printf("\nProgram Flash %d %8lx\n", data_page, old_data.magic); + if (mutex_enter_timeout_us(io_lock, 100000)) { + sleep_ms(10); /* wait for all io operations to finish */ + uint32_t ints = save_and_disable_interrupts(); + if (data_page == 0) { + flash_range_erase(SAVE_SECTOR_OFFSET, FLASH_SECTOR_SIZE); + } + flash_range_program(SAVE_SECTOR_OFFSET + data_page * FLASH_PAGE_SIZE, + (uint8_t *)&old_data, FLASH_PAGE_SIZE); + restore_interrupts(ints); + mutex_exit(io_lock); + } else { + printf("Program Flash Failed.\n"); + } +} + +static void load_default() +{ + printf("Load Default\n"); + new_data = default_data; + new_data.magic = my_magic; +} + +static const page_t *get_page(int id) +{ + int addr = XIP_BASE + SAVE_SECTOR_OFFSET; + return (page_t *)(addr + FLASH_PAGE_SIZE * id); +} + +static void save_load() +{ + for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) { + if (get_page(i)->magic != my_magic) { + break; + } + data_page = i; + } + + if (data_page < 0) { + load_default(); + save_request(false); + return; + } + + old_data = *get_page(data_page); + new_data = old_data; + printf("Page Loaded %d %8lx\n", data_page, new_data.magic); +} + +static void save_loaded() +{ + for (int i = 0; i < module_num; i++) { + modules[i].after_load(); + } +} + +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; + save_load(); + save_loop(); + save_loaded(); +} + +void save_loop() +{ + if (requesting_save && (time_us_64() - requesting_time > SAVE_TIMEOUT_US)) { + requesting_save = false; + /* only when data is actually changed */ + if (memcmp(&old_data, &new_data, sizeof(old_data)) == 0) { + return; + } + save_program(); + } +} + +void *save_alloc(size_t size, void *def, void (*after_load)()) +{ + modules[module_num].size = size; + size_t offset = module_num > 0 ? modules[module_num - 1].offset + size : 0; + modules[module_num].offset = offset; + modules[module_num].after_load = after_load; + module_num++; + memcpy(default_data.data + offset, def, size); // backup the default + return new_data.data + offset; +} + +void save_request(bool immediately) +{ + if (!requesting_save) { + printf("Save requested.\n"); + requesting_save = true; + new_data.magic = my_magic; + requesting_time = time_us_64(); + } + if (immediately) { + requesting_time = 0; + save_loop(); + } +} diff --git a/firmware/src/save.h b/firmware/src/save.h new file mode 100644 index 0000000..ee7c401 --- /dev/null +++ b/firmware/src/save.h @@ -0,0 +1,27 @@ +/* + * Controller Flash Save and Load + * WHowe + */ + +#ifndef SAVE_H +#define SAVE_H + +#include +#include +#include + +#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 */ +typedef void (*io_locker_func)(bool pause); +void save_init(uint32_t magic, mutex_t *lock); + +void save_loop(); + +void *save_alloc(size_t size, void *def, void (*after_load)()); +void save_request(bool immediately); + +#endif diff --git a/firmware/src/usb_descriptors.c b/firmware/src/usb_descriptors.c index 0172fa9..1b4f568 100644 --- a/firmware/src/usb_descriptors.c +++ b/firmware/src/usb_descriptors.c @@ -27,18 +27,6 @@ #include "tusb.h" -/* A combination of interfaces must have a unique product id, since PC will save - * device driver after the first plug. Same VID/PID with different interface e.g - * MSC (first), then CDC (later) will possibly cause system error on PC. - * - * Auto ProductID layout's Bitmap: - * [MSB] HID | MSC | CDC [LSB] - */ -#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) -#define USB_PID \ - (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ - _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4)) - //--------------------------------------------------------------------+ // Device Descriptors //--------------------------------------------------------------------+ @@ -51,15 +39,16 @@ tusb_desc_device_t const desc_device_joy = { .bDeviceProtocol = 0x00, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .idVendor = 0xCafe, - .idProduct = USB_PID, + .idVendor = 0xcafe, + .idProduct = 0x0013, .bcdDevice = 0x0100, - .iManufacturer = 0x01, - .iProduct = 0x02, - .iSerialNumber = 0x03, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, - .bNumConfigurations = 0x01}; + .bNumConfigurations = 1 +}; // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor @@ -72,30 +61,36 @@ uint8_t const* tud_descriptor_device_cb(void) { //--------------------------------------------------------------------+ uint8_t const desc_hid_report_joy[] = { - GAMECON_REPORT_DESC_JOYSTICK(HID_REPORT_ID(REPORT_ID_JOYSTICK)), - GAMECON_REPORT_DESC_LIGHTS(HID_REPORT_ID(REPORT_ID_LIGHTS)) + GAMECON_REPORT_DESC_JOYSTICK, + GAMECON_REPORT_DESC_LIGHTS }; // Invoked when received GET HID REPORT DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) { - (void)itf; - return desc_hid_report_joy; + switch (itf) { + case 0: + return desc_hid_report_joy; + default: + return NULL; + } } //--------------------------------------------------------------------+ // Configuration Descriptor //--------------------------------------------------------------------+ -enum { ITF_NUM_HID, ITF_NUM_CDC, ITF_NUM_CDC_DATA, ITF_NUM_TOTAL }; +enum { ITF_NUM_JOY, ITF_NUM_CLI, ITF_NUM_CLI_DATA, ITF_NUM_TOTAL }; -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + \ + TUD_HID_DESC_LEN * 1 + \ + TUD_CDC_DESC_LEN * 1) -#define EPNUM_HID 0x84 -#define EPNUM_CDC_NOTIF 0x81 -#define EPNUM_CDC_OUT 0x02 -#define EPNUM_CDC_IN 0x82 +#define EPNUM_JOY 0x84 +#define EPNUM_CLI_NOTIF 0x81 +#define EPNUM_CLI_OUT 0x02 +#define EPNUM_CLI_IN 0x82 uint8_t const desc_configuration_joy[] = { // Config number, interface count, string index, total length, attribute, @@ -105,12 +100,12 @@ uint8_t const desc_configuration_joy[] = { // Interface number, string index, protocol, report descriptor len, EP In // address, size & polling interval - TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, - sizeof(desc_hid_report_joy), EPNUM_HID, + TUD_HID_DESCRIPTOR(ITF_NUM_JOY, 2, HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_joy), EPNUM_JOY, CFG_TUD_HID_EP_BUFSIZE, 1), - TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, - 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) + TUD_CDC_DESCRIPTOR(ITF_NUM_CLI, 5, EPNUM_CLI_NOTIF, + 8, EPNUM_CLI_OUT, EPNUM_CLI_IN, 64) }; @@ -130,8 +125,9 @@ uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { char const* string_desc_arr[] = { (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) "WHowe", // 1: Manufacturer - "Pico Popn Controller", // 2: Product + "Popn Pico", // 2: Product "123456", // 3: Serials, should use chip ID + "Popn Pico CLI" "Button 1", "Button 2", "Button 3", @@ -141,13 +137,9 @@ char const* string_desc_arr[] = { "Button 7", "Button 8", "Button 9", - "Aux 1", - "Aux 2", - "Ext 1", - "Ext 2", - "Logo Red", - "Logo Green", - "Logo Blue" + "Cab Red", + "Cab Green", + "Cab Blue" }; static uint16_t _desc_str[64]; diff --git a/firmware/src/usb_descriptors.h b/firmware/src/usb_descriptors.h index 8ea7634..79fa764 100644 --- a/firmware/src/usb_descriptors.h +++ b/firmware/src/usb_descriptors.h @@ -18,32 +18,33 @@ enum { #define HID_STRING_MAXIMUM_N(x, n) HID_REPORT_ITEM(x, 9, RI_TYPE_LOCAL, n) // Joystick Report Descriptor Template - Based off Drewol/rp2040-gamecon -// Button Map | X | Y -#define GAMECON_REPORT_DESC_JOYSTICK(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ - HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), HID_USAGE_MIN(1), \ - HID_USAGE_MAX(11 + 2), \ - HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), HID_REPORT_COUNT(11 + 2), \ - HID_REPORT_SIZE(1), HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(16 - 11 - 2), /*Padding*/\ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ +#define GAMECON_REPORT_DESC_JOYSTICK \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_ID(REPORT_ID_JOYSTICK) \ + HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \ + HID_USAGE_MIN(1), HID_USAGE_MAX(11), \ + HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ + HID_REPORT_COUNT(11), HID_REPORT_SIZE(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(16 - 11), /* Padding */ \ + HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ HID_COLLECTION_END // Light Map -#define GAMECON_REPORT_DESC_LIGHTS(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ \ - HID_REPORT_SIZE(8), HID_REPORT_COUNT(11 + 2 + 3), /* LED NUM + RGB */ \ - HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), HID_STRING_MINIMUM(4), \ - HID_STRING_MAXIMUM(19), HID_USAGE_MIN(1), HID_USAGE_MAX(16), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), \ - HID_REPORT_SIZE(8), /*Padding*/ \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ +#define GAMECON_REPORT_DESC_LIGHTS \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_ID(REPORT_ID_LIGHTS) \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(11 + 3), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_STRING_MINIMUM(4), HID_STRING_MAXIMUM(16), \ + HID_USAGE_MIN(1), HID_USAGE_MAX(17), \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), /* Padding */ \ + HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ HID_COLLECTION_END #endif /* USB_DESCRIPTORS_H_ */ \ No newline at end of file