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:
parent
19d6833ed8
commit
5b06bf9849
@ -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)
|
||||
|
@ -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.
@ -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)
|
||||
|
||||
|
@ -1,60 +1,15 @@
|
||||
/*
|
||||
* Pico Popn Controller Board Definitions
|
||||
* Popn Pico 2 Board Definitions
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#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
|
||||
|
84
firmware/src/button.c
Normal file
84
firmware/src/button.c
Normal 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;
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Pico Popn Controller Buttons
|
||||
* Controller Buttons
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef BUTTONS_H
|
||||
#define BUTTONS_H
|
||||
#ifndef BUTTON_H
|
||||
#define BUTTON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
@ -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
|
@ -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
215
firmware/src/cli.c
Normal 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
21
firmware/src/cli.h
Normal 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
102
firmware/src/commands.c
Normal 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
11
firmware/src/commands.h
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Popn Controller Command Line Commands
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef COMMANDS_H
|
||||
#define COMMANDS_H
|
||||
|
||||
void commands_init();
|
||||
|
||||
#endif
|
@ -1,143 +1,44 @@
|
||||
/*
|
||||
* Pico Popn Controller Config
|
||||
* Controller Config and Runtime Data
|
||||
* 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 "save.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <memory.h>
|
||||
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);
|
||||
}
|
||||
|
@ -1,17 +1,41 @@
|
||||
/*
|
||||
* Pico Popn Controller Config
|
||||
* Controller Config
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef 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_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
|
||||
|
183
firmware/src/light.c
Normal file
183
firmware/src/light.c
Normal 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
40
firmware/src/light.h
Normal 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
|
@ -1,72 +1,202 @@
|
||||
/*
|
||||
* Pico Popn Controller Main
|
||||
* Controller Main
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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) {
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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
178
firmware/src/save.c
Normal 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
27
firmware/src/save.h
Normal 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
|
@ -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];
|
||||
|
@ -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_ */
|
Loading…
x
Reference in New Issue
Block a user