1
0
mirror of https://github.com/whowechina/popn_pico.git synced 2025-01-31 20:15:22 +01:00

Firmware kinda works

This commit is contained in:
whowechina 2024-12-08 17:12:20 +08:00
parent 19d6833ed8
commit 5b06bf9849
24 changed files with 1178 additions and 854 deletions

View File

@ -1,11 +1,12 @@
cmake_minimum_required(VERSION 3.12) cmake_minimum_required(VERSION 3.12)
# Pull in SDK (must set be before project) # Pull in SDK (must set be before project)
include(pico_sdk_import.cmake) include(pico_sdk_import.cmake)
project(popn60 C CXX ASM) project(popn_pico C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD 11)
pico_sdk_init()
pico_sdk_init()
add_subdirectory(src)
add_subdirectory(src)

View File

@ -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.

Binary file not shown.

Binary file not shown.

View File

@ -1,19 +1,30 @@
function(make_firmware board board_def) 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}) target_compile_definitions(${board} PUBLIC ${board_def})
pico_enable_stdio_usb(${board} 1) pico_enable_stdio_usb(${board} 1)
pico_enable_stdio_uart(${board} 0) pico_enable_stdio_uart(${board} 0)
pico_generate_pio_header(${board} ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) pico_generate_pio_header(${board} ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3) target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3)
target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${board} PRIVATE 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) tinyusb_device tinyusb_board)
# Make a UF2 binary
pico_add_extra_outputs(${board}) 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() endfunction()
make_firmware(popn_pico BOARD_POPN_PICO) make_firmware(popn_pico2 BOARD_POPN_PICO2)
make_firmware(popn_ctrl BOARD_GENERAL)

View File

@ -1,60 +1,15 @@
/* /*
* Pico Popn Controller Board Definitions * Popn Pico 2 Board Definitions
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
*/ */
#if defined BOARD_POPN_PICO #if defined BOARD_POPN_PICO2
/* This is for my Pico Popn Controller */
/* A button consists of a switch and an LED /* 9 Main buttons + 2 Aux buttons */
9 main buttons + 2 aux buttons. */ #define BUTTON_DEF { 0, 1, 2, 3, 4, 7, 8, 9, 10, 5, 6 }
#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} \
}
#define RGB_LED_PIN 28 #define RGB_BUTTON_MAP { 0, 8, 1, 7, 2, 6, 3, 5, 4 }
#define RGB_LED_NUM 10 #define RGB_BUTTON_PIN 28
#define RGB_LOGO_LEDS {0, 1, 2} #define RGB_CAB_PIN 27
#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}
#endif #endif

84
firmware/src/button.c Normal file
View File

@ -0,0 +1,84 @@
/*
* Controller Buttons
* WHowe <github.com/whowechina>
*
*/
#include "button.h"
#include <stdint.h>
#include <stdbool.h>
#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;
}

View File

@ -1,10 +1,10 @@
/* /*
* Pico Popn Controller Buttons * Controller Buttons
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
*/ */
#ifndef BUTTONS_H #ifndef BUTTON_H
#define BUTTONS_H #define BUTTON_H
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@ -12,11 +12,7 @@
void button_init(); void button_init();
uint8_t button_num(); uint8_t button_num();
uint8_t button_gpio(uint8_t id); void button_update();
uint16_t button_read(); uint16_t button_read();
void button_update();
void button_set_light(uint8_t const *lights, uint8_t num);
#endif #endif

View File

@ -1,325 +0,0 @@
/*
* Pico Popn Controller Buttons
* WHowe <github.com/whowechina>
*
* A button consists of a switch and an LED
*/
#include "buttons.h"
#include <stdint.h>
#include <stdbool.h>
#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;
}

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

@ -0,0 +1,215 @@
#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;
}
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.");
}

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

@ -0,0 +1,21 @@
/*
* Popn 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);
extern const char *built_time;
#endif

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

@ -0,0 +1,102 @@
#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"
#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.");
}

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

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

View File

@ -1,143 +1,44 @@
/* /*
* Pico Popn Controller Config * Controller Config and Runtime Data
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
* *
* 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 "config.h"
#include "save.h"
#include <stdint.h> popn_cfg_t *popn_cfg;
#include <stdbool.h>
#include <memory.h>
static popn_cfg_t default_cfg = {
.light = {
.level = 128,
},
};
#include "bsp/board.h" popn_runtime_t popn_runtime;
#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);
}
static void config_loaded() static void config_loaded()
{ {
for (int i = 0; i < module_num; i++) { if (popn_cfg->spin.units_per_turn < 20) {
modules[i].after_load(); 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() void config_init()
{ {
config_load(); popn_cfg = (popn_cfg_t *)save_alloc(sizeof(*popn_cfg), &default_cfg, config_loaded);
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;
}
} }

View File

@ -1,17 +1,41 @@
/* /*
* Pico Popn Controller Config * Controller Config
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
*/ */
#ifndef CONFIG_H #ifndef CONFIG_H
#define CONFIG_H #define CONFIG_H
#include <stdlib.h> #include <stdint.h>
#include <stdbool.h>
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_init();
void config_loop(); void config_changed(); // Notify the config has changed
void config_factory_reset(); // Reset the config to factory default
void *config_alloc(size_t size, void *def, void (*after_load)());
void config_request_save();
#endif #endif

183
firmware/src/light.c Normal file
View File

@ -0,0 +1,183 @@
/*
* WS2812B Lights Control (Base + Left and Right Gimbals)
* WHowe <github.com/whowechina>
*
*/
#include "light.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#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);
}
}

40
firmware/src/light.h Normal file
View File

@ -0,0 +1,40 @@
/*
* WS2812B Lights Control (Base + Left and Right Gimbals)
* WHowe <github.com/whowechina>
*/
#ifndef LIGHT_H
#define LIGHT_H
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#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

View File

@ -1,72 +1,202 @@
/* /*
* Pico Popn Controller Main * Controller Main
* WHowe <github.com/whowechina> * WHowe <github.com/whowechina>
*/ */
#include <stdio.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "bsp/board.h"
#include "pico/bootrom.h"
#include "pico/stdio.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 "tusb.h"
#include "usb_descriptors.h" #include "usb_descriptors.h"
#include "buttons.h" #include "board_defs.h"
#include "rgb.h"
#include "save.h"
#include "config.h" #include "config.h"
#include "cli.h"
#include "commands.h"
#define BUTTON_LIGHT_MAX_NUM 32 /* Must be larger than number of buttons */ #include "light.h"
bool button_lights[BUTTON_LIGHT_MAX_NUM] = {0}; #include "button.h"
struct report static void light_render()
{ {
uint16_t buttons; uint32_t phase = time_us_32() >> 16;
} report;
void main_loop() for (int i = 0; i < 9; i ++) {
{ uint32_t color = rgb32_from_hsv(phase + i * 27, 255, 128);
if (tud_hid_ready()) { light_set_button(i, color, false);
tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &report, sizeof(report)); }
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); static uint64_t next_frame = 0;
uint64_t key2 = (1 << 10); uint64_t now = time_us_64();
uint64_t buttons = button_read(); if (now < next_frame) {
if ((buttons & key1) && (buttons & key2)) { return;
reset_usb_boot(0, 2);
} }
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() void init()
{ {
sleep_ms(50);
set_sys_clock_khz(150000, true);
board_init(); board_init();
update_check();
tusb_init(); tusb_init();
button_init();
rgb_init();
boot_check();
stdio_init_all(); stdio_init_all();
config_init(); 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) int main(void)
{ {
init(); init();
multicore_launch_core1(core1_loop);
while (1) core0_loop();
{
tud_task();
report.buttons = button_read();
main_loop();
button_update();
config_loop();
}
return 0; 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, hid_report_type_t report_type, uint8_t *buffer,
uint16_t reqlen) uint16_t reqlen)
{ {
printf("Get from USB %d-%d\n", report_id, report_type);
return 0; 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, hid_report_type_t report_type, uint8_t const *buffer,
uint16_t bufsize) uint16_t bufsize)
{ {
if ((report_id == REPORT_ID_LIGHTS) && 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]);
} }
} }

View File

@ -1,183 +0,0 @@
/*
* Pico Popn Controller Buttons
* WHowe <github.com/whowechina>
*
* A button consists of a switch and an LED
*/
#include "buttons.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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);
}

View File

@ -1,17 +0,0 @@
/*
* Pico Popn Controller Buttons
* WHowe <github.com/whowechina>
*/
#ifndef RGB_H
#define RGB_H
#include <stdint.h>
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

178
firmware/src/save.c Normal file
View File

@ -0,0 +1,178 @@
/*
* Controller Config Save and Load
* WHowe <github.com/whowechina>
*
* Config is stored in last sector of flash
*/
#include "save.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <memory.h>
#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();
}
}

27
firmware/src/save.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Controller Flash Save and Load
* WHowe <github.com/whowechina>
*/
#ifndef SAVE_H
#define SAVE_H
#include <stdlib.h>
#include <stdint.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 */
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

View File

@ -27,18 +27,6 @@
#include "tusb.h" #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 // Device Descriptors
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
@ -51,15 +39,16 @@ tusb_desc_device_t const desc_device_joy = {
.bDeviceProtocol = 0x00, .bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xCafe, .idVendor = 0xcafe,
.idProduct = USB_PID, .idProduct = 0x0013,
.bcdDevice = 0x0100, .bcdDevice = 0x0100,
.iManufacturer = 0x01, .iManufacturer = 1,
.iProduct = 0x02, .iProduct = 2,
.iSerialNumber = 0x03, .iSerialNumber = 3,
.bNumConfigurations = 0x01}; .bNumConfigurations = 1
};
// Invoked when received GET DEVICE DESCRIPTOR // Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to 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[] = { uint8_t const desc_hid_report_joy[] = {
GAMECON_REPORT_DESC_JOYSTICK(HID_REPORT_ID(REPORT_ID_JOYSTICK)), GAMECON_REPORT_DESC_JOYSTICK,
GAMECON_REPORT_DESC_LIGHTS(HID_REPORT_ID(REPORT_ID_LIGHTS)) GAMECON_REPORT_DESC_LIGHTS
}; };
// Invoked when received GET HID REPORT DESCRIPTOR // Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor // Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete // Descriptor contents must exist long enough for transfer to complete
uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) { uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) {
(void)itf; switch (itf) {
return desc_hid_report_joy; case 0:
return desc_hid_report_joy;
default:
return NULL;
}
} }
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
// Configuration Descriptor // 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_JOY 0x84
#define EPNUM_CDC_NOTIF 0x81 #define EPNUM_CLI_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02 #define EPNUM_CLI_OUT 0x02
#define EPNUM_CDC_IN 0x82 #define EPNUM_CLI_IN 0x82
uint8_t const desc_configuration_joy[] = { uint8_t const desc_configuration_joy[] = {
// Config number, interface count, string index, total length, attribute, // 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 // Interface number, string index, protocol, report descriptor len, EP In
// address, size & polling interval // address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, TUD_HID_DESCRIPTOR(ITF_NUM_JOY, 2, HID_ITF_PROTOCOL_NONE,
sizeof(desc_hid_report_joy), EPNUM_HID, sizeof(desc_hid_report_joy), EPNUM_JOY,
CFG_TUD_HID_EP_BUFSIZE, 1), CFG_TUD_HID_EP_BUFSIZE, 1),
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, TUD_CDC_DESCRIPTOR(ITF_NUM_CLI, 5, EPNUM_CLI_NOTIF,
8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) 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[] = { char const* string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"WHowe", // 1: Manufacturer "WHowe", // 1: Manufacturer
"Pico Popn Controller", // 2: Product "Popn Pico", // 2: Product
"123456", // 3: Serials, should use chip ID "123456", // 3: Serials, should use chip ID
"Popn Pico CLI"
"Button 1", "Button 1",
"Button 2", "Button 2",
"Button 3", "Button 3",
@ -141,13 +137,9 @@ char const* string_desc_arr[] = {
"Button 7", "Button 7",
"Button 8", "Button 8",
"Button 9", "Button 9",
"Aux 1", "Cab Red",
"Aux 2", "Cab Green",
"Ext 1", "Cab Blue"
"Ext 2",
"Logo Red",
"Logo Green",
"Logo Blue"
}; };
static uint16_t _desc_str[64]; static uint16_t _desc_str[64];

View File

@ -18,32 +18,33 @@ enum {
#define HID_STRING_MAXIMUM_N(x, n) HID_REPORT_ITEM(x, 9, RI_TYPE_LOCAL, n) #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 // Joystick Report Descriptor Template - Based off Drewol/rp2040-gamecon
// Button Map | X | Y #define GAMECON_REPORT_DESC_JOYSTICK \
#define GAMECON_REPORT_DESC_JOYSTICK(...) \ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \
HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \ HID_COLLECTION(HID_COLLECTION_APPLICATION), \
HID_COLLECTION(HID_COLLECTION_APPLICATION), \ HID_REPORT_ID(REPORT_ID_JOYSTICK) \
__VA_ARGS__ HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), HID_USAGE_MIN(1), \ HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \
HID_USAGE_MAX(11 + 2), \ HID_USAGE_MIN(1), HID_USAGE_MAX(11), \
HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), HID_REPORT_COUNT(11 + 2), \ HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \
HID_REPORT_SIZE(1), HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ HID_REPORT_COUNT(11), HID_REPORT_SIZE(1), \
HID_REPORT_COUNT(1), HID_REPORT_SIZE(16 - 11 - 2), /*Padding*/\ HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
HID_INPUT(HID_CONSTANT | 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 HID_COLLECTION_END
// Light Map // Light Map
#define GAMECON_REPORT_DESC_LIGHTS(...) \ #define GAMECON_REPORT_DESC_LIGHTS \
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \
HID_COLLECTION(HID_COLLECTION_APPLICATION), \ HID_COLLECTION(HID_COLLECTION_APPLICATION), \
__VA_ARGS__ \ HID_REPORT_ID(REPORT_ID_LIGHTS) \
HID_REPORT_SIZE(8), HID_REPORT_COUNT(11 + 2 + 3), /* LED NUM + RGB */ \ HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \
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_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \
HID_STRING_MAXIMUM(19), HID_USAGE_MIN(1), HID_USAGE_MAX(16), \ HID_STRING_MINIMUM(4), HID_STRING_MAXIMUM(16), \
HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ HID_USAGE_MIN(1), HID_USAGE_MAX(17), \
HID_REPORT_COUNT(1), \ HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
HID_REPORT_SIZE(8), /*Padding*/ \ HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), /* Padding */ \
HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \
HID_COLLECTION_END HID_COLLECTION_END
#endif /* USB_DESCRIPTORS_H_ */ #endif /* USB_DESCRIPTORS_H_ */