From 5eb5da42e2c16da2fbffe82d3cce48f6262ba3ba Mon Sep 17 00:00:00 2001 From: whowechina Date: Sun, 24 Sep 2023 16:17:09 +0800 Subject: [PATCH] Firmware copied from chu pico --- firmware/CMakeLists.txt | 1 - firmware/README.md | 10 - firmware/lib/pico-mpr121/CMakeLists.txt | 17 - firmware/lib/pico-mpr121/include/mpr121.h | 363 ----------- firmware/lib/pico-mpr121/mpr121.c | 170 ----- firmware/mai_pico.uf2 | Bin 66560 -> 0 bytes firmware/src/CMakeLists.txt | 10 +- firmware/src/board_defs.h | 31 +- firmware/src/buttons.c | 73 --- firmware/src/buttons.h | 17 - firmware/src/cmd.c | 416 +++++++++++++ firmware/src/cmd.h | 13 + firmware/src/config.c | 81 ++- firmware/src/config.h | 56 +- firmware/src/mai_pico.code-workspace | 38 -- firmware/src/main.c | 306 +++++---- firmware/src/mpr121.c | 200 ++++++ firmware/src/mpr121.h | 18 + firmware/src/rgb.c | 292 ++------- firmware/src/rgb.h | 37 +- firmware/src/save.c | 58 +- firmware/src/save.h | 5 +- firmware/src/setup.c | 725 ---------------------- firmware/src/setup.h | 19 - firmware/src/touch.c | 69 ++ firmware/src/touch.h | 17 + firmware/src/tusb_config.h | 2 +- firmware/src/usb_descriptors.c | 184 +++--- firmware/src/usb_descriptors.h | 164 +++-- 29 files changed, 1285 insertions(+), 2107 deletions(-) delete mode 100644 firmware/README.md delete mode 100644 firmware/lib/pico-mpr121/CMakeLists.txt delete mode 100644 firmware/lib/pico-mpr121/include/mpr121.h delete mode 100644 firmware/lib/pico-mpr121/mpr121.c delete mode 100644 firmware/mai_pico.uf2 delete mode 100644 firmware/src/buttons.c delete mode 100644 firmware/src/buttons.h create mode 100644 firmware/src/cmd.c create mode 100644 firmware/src/cmd.h delete mode 100644 firmware/src/mai_pico.code-workspace create mode 100644 firmware/src/mpr121.c create mode 100644 firmware/src/mpr121.h delete mode 100644 firmware/src/setup.c delete mode 100644 firmware/src/setup.h create mode 100644 firmware/src/touch.c create mode 100644 firmware/src/touch.h diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index ec4d575..b328053 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -9,4 +9,3 @@ set(CMAKE_C_STANDARD 11) pico_sdk_init() add_subdirectory(src) -add_subdirectory(lib/pico-mpr121 mpr121) diff --git a/firmware/README.md b/firmware/README.md deleted file mode 100644 index 8d29661..0000000 --- a/firmware/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# IIDX Pico Firmware - -Features: -* 1000Hz polling rate. -* HID lights. -* RGB turntable. -* Configuration save. -* Customizable through board_defs.h -* Dymanic settings. - diff --git a/firmware/lib/pico-mpr121/CMakeLists.txt b/firmware/lib/pico-mpr121/CMakeLists.txt deleted file mode 100644 index bf7542b..0000000 --- a/firmware/lib/pico-mpr121/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -add_library(pico-mpr121 INTERFACE) - -target_include_directories(pico-mpr121 - INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/include -) - -target_link_libraries(pico-mpr121 - INTERFACE - hardware_i2c -) - -target_sources(pico-mpr121 - INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/mpr121.c - ${CMAKE_CURRENT_LIST_DIR}/include/mpr121.h -) diff --git a/firmware/lib/pico-mpr121/include/mpr121.h b/firmware/lib/pico-mpr121/include/mpr121.h deleted file mode 100644 index f5abf5d..0000000 --- a/firmware/lib/pico-mpr121/include/mpr121.h +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (c) 2021-2022 Antonio González - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _MPR121_H_ -#define _MPR121_H_ - -#include "pico.h" -#include "hardware/i2c.h" - -/** \file mpr121.h - * \brief Library for using an MPR121-based touch sensor with the - * Raspberry Pi Pico - * -*/ - -typedef struct mpr121_sensor { - i2c_inst_t *i2c_port; - uint8_t i2c_addr; - // uint8_t i2c_sda; - // uint8_t ic2_scl; -} mpr121_sensor_t; - -/*! \brief MPR121 register map - */ -enum mpr121_register { - MPR121_TOUCH_STATUS_REG = 0x00u, - MPR121_OUT_OF_RANGE_STATUS_0_REG = 0x02u, - MPR121_OUT_OF_RANGE_STATUS_1_REG = 0x03u, - MPR121_ELECTRODE_FILTERED_DATA_REG = 0x04u, - MPR121_BASELINE_VALUE_REG = 0x1Eu, - // Registers 0x2B ~ 0x7F are control and configuration registers - MPR121_MAX_HALF_DELTA_RISING_REG = 0x2Bu, - MPR121_NOISE_HALF_DELTA_RISING_REG = 0x2Cu, - MPR121_NOISE_COUNT_LIMIT_RISING_REG = 0x2Du, - MPR121_FILTER_DELAY_COUNT_RISING_REG = 0x2Eu, - MPR121_MAX_HALF_DELTA_FALLING_REG = 0x2Fu, - MPR121_NOISE_HALF_DELTA_FALLING_REG = 0x30u, - MPR121_NOISE_COUNT_LIMIT_FALLING_REG = 0x31u, - MPR121_FILTER_DELAY_COUNT_FALLING_REG = 0x32u, - MPR121_NOISE_HALF_DELTA_TOUCHED_REG = 0x33u, - MPR121_NOISE_COUNT_LIMIT_TOUCHED_REG = 0x34u, - MPR121_FILTER_DELAY_COUNT_TOUCHED_REG = 0x35u, - // (ELEPROX 0x36 .. 0x40) - MPR121_TOUCH_THRESHOLD_REG = 0x41u, - MPR121_RELEASE_THRESHOLD_REG = 0x42u, - // (ELEPROX 0x59 .. 0x5A) - MPR121_DEBOUNCE_REG = 0x5Bu, - MPR121_AFE_CONFIG_REG = 0x5Cu, - MPR121_FILTER_CONFIG_REG = 0x5Du, - MPR121_ELECTRODE_CONFIG_REG = 0x5Eu, - MPR121_ELECTRODE_CURRENT_REG = 0x5Fu, - MPR121_ELECTRODE_CHARGE_TIME_REG = 0x6Cu, - MPR121_GPIO_CTRL_0_REG = 0x73u, - MPR121_GPIO_CTRL_1_REG = 0x74u, - MPR121_GPIO_DATA_REG = 0x75u, - MPR121_GPIO_DIRECTION_REG = 0x76u, - MPR121_GPIO_ENABLE_REG = 0x77u, - MPR121_GPIO_DATA_SET_REG = 0x78u, - MPR121_GPIO_DATA_CLEAR_REG = 0x79u, - MPR121_GPIO_DATA_TOGGLE_REG = 0x7Au, - MPR121_AUTOCONFIG_CONTROL_0_REG = 0x7Bu, - MPR121_AUTOCONFIG_CONTROL_1_REG = 0x7Cu, - MPR121_AUTOCONFIG_USL_REG = 0x7Du, - MPR121_AUTOCONFIG_LSL_REG = 0x7Eu, - MPR121_AUTOCONFIG_TARGET_REG = 0x7Fu, - MPR121_SOFT_RESET_REG = 0x80u -}; - -/*! \brief Initialise the MPR121 and configure registers - * - * The default parameters used here to configure the sensor are as in - * the MPR121 Quick Start Guide (AN3944). - * - * \param i2c_port The I2C instance, either i2c0 or i2c1 - * \param i2c_addr The I2C address of the MPR121 device - * \param sensor Pointer to the structure that stores the MPR121 info - */ -void mpr121_init(i2c_inst_t *i2c_port, uint8_t i2c_addr, - mpr121_sensor_t *sensor); - -/*! \brief Write a value to the specified register - * - * \param reg The register address - * \param val The value to write - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_write(enum mpr121_register reg, uint8_t val, - mpr121_sensor_t *sensor) { - uint8_t buf[] = {reg, val}; - i2c_write_blocking(sensor->i2c_port, sensor->i2c_addr, buf, 2, - false); -} - -/*! \brief Read a byte from the specified register - * - * \param reg The register address - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_read(enum mpr121_register reg, uint8_t *dst, - mpr121_sensor_t *sensor) { - i2c_write_blocking(sensor->i2c_port, sensor->i2c_addr, ®, 1, - true); - i2c_read_blocking(sensor->i2c_port, sensor->i2c_addr, dst, 1, - false); -} - -/*! \brief Read a 2-byte value from the specified register - * - * \param reg The register address - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_read16(enum mpr121_register reg, uint16_t *dst, - mpr121_sensor_t *sensor) { - uint8_t vals[2]; - i2c_write_blocking(sensor->i2c_port, sensor->i2c_addr, ®, 1, - true); - i2c_read_blocking(sensor->i2c_port, sensor->i2c_addr, vals, 2, - false); - *dst = vals[1] << 8 | vals[0]; -} - -/*! \brief Set touch and release thresholds - * - * From the MPR121 datasheet (section 5.6): - * > In a typical application, touch threshold is in the range 4--16, - * > and it is several counts larger than the release threshold. This - * > is to provide hysteresis and to prevent noise and jitter. - * - * \param touch Touch threshold in the range 0--255 - * \param release Release threshold in the range 0--255 - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_set_thresholds(uint8_t touch, uint8_t release, - mpr121_sensor_t *sensor) { - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - if (config != 0){ - // Stop mode - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - } - - for (uint8_t i=0; i<12; i++) { - mpr121_write(MPR121_TOUCH_THRESHOLD_REG + i * 2, touch, sensor); - mpr121_write(MPR121_RELEASE_THRESHOLD_REG + i * 2, release, - sensor); - } - - if (config != 0){ - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); - } -} - -/*! \brief Enable only the number of electrodes specified - * - * \param nelec Number of electrodes to enable - * \param sensor Pointer to the structure that stores the MPR121 info - * - * E.g. if `nelec` is 3, electrodes 0 to 2 will be enabled; if `nelec` - * is 6, electrodes 0 to 5 will be enabled. From the datasheet: - * "Enabling specific channels will save the scan time and sensing - * field power spent on the unused channels." - */ -static void mpr121_enable_electrodes(uint8_t nelec, - mpr121_sensor_t *sensor){ - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - - // Clear bits 3-0, which controls the operation of the 12 - // electrodes. - config &= ~0x0f; - - // Set number of electrodes enabled - config |= nelec; - - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); -} - -/*! \brief Read the touch/release status of all 13 input channels - * - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - * - * In the value read, bits 11-0 represent electrodes 11 to 0, - * respectively, and bit 12 is the proximity detection channel. Each - * bit represent the status of these channels: 1 if the channel is - * touched, 0 if it is released. - */ -static void mpr121_touched(uint16_t *dst, mpr121_sensor_t *sensor) { - mpr121_read16(MPR121_TOUCH_STATUS_REG, dst, sensor); - *dst &= 0x0fff; -} - -/*! \brief Determine whether an electrode has been touched - * - * \param electrode Electrode number - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_is_touched(uint8_t electrode, bool *dst, - mpr121_sensor_t *sensor){ - uint16_t touched; - mpr121_touched(&touched, sensor); - *dst = (bool) ((touched >> electrode) & 1); -} - -/*! \brief Read an electrode's filtered data value - * - * \param electrode Electrode number - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - * - * The data range of the filtered data is 0 to 1024. - * \sa mpr121_baseline_value - */ -static void mpr121_filtered_data(uint8_t electrode, uint16_t *dst, - mpr121_sensor_t *sensor){ - mpr121_read16(MPR121_ELECTRODE_FILTERED_DATA_REG + (electrode * 2), - dst, sensor); - // Filtered data is 10-bit - *dst &= 0x3ff; -} - -/*! \brief Read an electrode's baseline value - * - * \param electrode Electrode number - * \param dst Pointer to buffer to receive data - * \param sensor Pointer to the structure that stores the MPR121 info - * - * From the MPR112 datasheet: - * > Along with the 10-bit electrode filtered data output, each channel - * > also has a 10-bit baseline value. These values are the output of - * > the internal baseline filter operation tracking the slow-voltage - * > variation of the background capacitance change. Touch/release - * > detection is made based on the comparison between the 10-bit - * > electrode filtered data and the 10-bit baseline value. - * - * > Although internally the baseline value is 10-bit, users can only - * > access the 8 MSB of the 10-bit baseline value through the baseline - * > value registers. - * - * \sa mpr121_filtered_data - */ -static void mpr121_baseline_value(uint8_t electrode, uint16_t *dst, - mpr121_sensor_t *sensor){ - uint8_t baseline; - mpr121_read(MPR121_BASELINE_VALUE_REG + electrode, &baseline, - sensor); - // From the datasheet: Although internally the baseline value is - // 10-bit, users can only access the 8 MSB of the 10-bit baseline - // value through the baseline value registers. The read out from the - // baseline register must be left shift two bits before comparing it - // with the 10-bit electrode data. - *dst = baseline << 2; -} - -/*! \brief Set the Max Half Delta - * - * The Max Half Delta determines the largest magnitude of variation to - * pass through the third level filter. See application note MPR121 - * Baseline System (AN3891) for details. - * - * \param rising Value in the range 1~63 - * \param falling Value in the range 1~63 - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_set_max_half_delta(uint8_t rising, uint8_t falling, - mpr121_sensor_t *sensor) { - // Read current configuration then enter stop mode - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - // Write MHD values - mpr121_write(MPR121_MAX_HALF_DELTA_RISING_REG, rising, sensor); - mpr121_write(MPR121_MAX_HALF_DELTA_FALLING_REG, falling, sensor); - // Re-enable electrodes - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); -} - -/*! \brief Set the Noise Half Delta - * - * The Noise Half Delta determines the incremental change when - * non-noise drift is detected. See application note MPR121 Baseline - * System (AN3891) for details. - * - * \param rising Value in the range 1~63 - * \param falling Value in the range 1~63 - * \param touched Value in the range 1~63 - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_set_noise_half_delta(uint8_t rising, uint8_t falling, - uint8_t touched, mpr121_sensor_t *sensor) { - // Read current configuration then enter stop mode - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - // Write NHD values - mpr121_write(MPR121_NOISE_HALF_DELTA_RISING_REG, rising, sensor); - mpr121_write(MPR121_NOISE_HALF_DELTA_FALLING_REG, falling, sensor); - mpr121_write(MPR121_NOISE_HALF_DELTA_TOUCHED_REG, touched, sensor); - // Re-enable electrodes - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); -} - -/*! \brief Set the Noise Count Limit - * - * The Noise Count Limit determines the number of samples consecutively - * greater than the Max Half Delta necessary before it can be - * determined that it is non-noise. See application note MPR121 Baseline - * System (AN3891) for details. - * - * \param rising Value in the range 0~255 - * \param falling Value in the range 0~255 - * \param touched Value in the range 0~255 - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_set_noise_count_limit(uint8_t rising, - uint8_t falling, uint8_t touched, mpr121_sensor_t *sensor) { - // Read current configuration then enter stop mode - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - // Write new NCL values - mpr121_write(MPR121_NOISE_COUNT_LIMIT_RISING_REG, rising, sensor); - mpr121_write(MPR121_NOISE_COUNT_LIMIT_FALLING_REG, falling, sensor); - mpr121_write(MPR121_NOISE_COUNT_LIMIT_TOUCHED_REG, touched, sensor); - // Re-enable electrodes - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); -} - -/*! \brief Set the Filter Delay Limit - * - * The Filter Delay Limit determines the rate of operation of the - * filter. A larger number makes it operate slower. See application - * note MPR121 Baseline System (AN3891) for details. - * - * \param rising Value in the range 0~255 - * \param falling Value in the range 0~255 - * \param touched Value in the range 0~255 - * \param sensor Pointer to the structure that stores the MPR121 info - */ -static void mpr121_set_filter_delay_limit(uint8_t rising, - uint8_t falling, uint8_t touched, mpr121_sensor_t *sensor) { - // Read current configuration then enter stop mode - uint8_t config; - mpr121_read(MPR121_ELECTRODE_CONFIG_REG, &config, sensor); - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - // Write new FDL values - mpr121_write(MPR121_FILTER_DELAY_COUNT_RISING_REG, rising, sensor); - mpr121_write(MPR121_FILTER_DELAY_COUNT_FALLING_REG, falling, - sensor); - mpr121_write(MPR121_FILTER_DELAY_COUNT_TOUCHED_REG, touched, - sensor); - // Re-enable electrodes - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, config, sensor); -} - -#endif diff --git a/firmware/lib/pico-mpr121/mpr121.c b/firmware/lib/pico-mpr121/mpr121.c deleted file mode 100644 index 7586675..0000000 --- a/firmware/lib/pico-mpr121/mpr121.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2021-2022 Antonio González - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "mpr121.h" - -void mpr121_init(i2c_inst_t *i2c_port, uint8_t i2c_addr, - mpr121_sensor_t *sensor) { - sensor->i2c_port = i2c_port; - sensor->i2c_addr = i2c_addr; - - // Enter stop mode by setting ELEPROX_EN and ELE_EN bits to zero. - // This is needed because register write operations can only take - // place in stop mode. - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x00, sensor); - - // Writing 0x80 (SOFT_RESET) with 0x63 asserts soft reset. - mpr121_write(MPR121_SOFT_RESET_REG, 0x63, sensor); - - // == Capacitance sensing settings (AN2889), Filtering and ========= - // timing settings (AN3890) - - // These settings are configured in two registers: the Filter and - // Global CDC CDT Configuration registers (0x5C, 0x5D). - // - // Charge-discharge current (CDC) and charge-discharge time (CDT) - // can be configured globally or on a per-electrode basis. Here, - // The global CDC and CDT values are set to their defaults, and then - // these values are overriden by independently configuring each - // electrode (auto-configuration). - - // Filter/global CDC configuration register (0x5C) - // - // First filter iterations (FFI), bits 7-6. Number of samples taken - // as input to the first level of filtering. Default is 0b00 (sets - // samples taken to 6) - // - // Charge-discharge current (CDC), bits 5-0. Sets the value of - // charge-discharge current applied to the electrode. Max is 63 µA - // in 1 µA steps. Default is 0b010000 (16 µA) - // - // AFE configuration register default, 0b00010000 = 0x10 - mpr121_write(MPR121_AFE_CONFIG_REG, 0x10, sensor); - - // Filter/global CDC configuration register (0x5D) - // - // Charge discharge time (CDT), bits 7-5. Selects the global value - // of charge time applied to electrode. The maximum is 32 μs, - // programmable as 2^(n-2) μs. Default is 0b001 (time is set to - // 0.5 µs) - // - // Second filter iterations (SFI), bits 4-3. Selects the number of - // samples taken for the second level filter. Default is 0b00 - // (number of samples is set to 4) - // - // Electrode sample interval (ESI), bits 2-0. Controls the sampling - // rate of the device. The maximum is 128 ms, programmable to 2^n - // ms. Decrease this value for better response time, increase to - // save power. Default is 0b100 (period set to 16 ms). - // - // Filter configuration register default, 0b00100100 = 0x24 - // I do not need power saving features but I want fast responses, - // so I set this to 0x20. - mpr121_write(MPR121_FILTER_CONFIG_REG, 0x20, sensor); - - // Auto-configuration - // - // Sets automatically charge current (CDC) and time (CDT) values for - // each electrode. - // - // Autoconfig USL register: the upper limit for the - // auto-configuration. This value (and those that follow below) - // were calculated based on Vdd = 3.3 V and following the equations - // in NXP Application Note AN3889. - // USL = 201 = 0xC9 - mpr121_write(MPR121_AUTOCONFIG_USL_REG, 0xC9, sensor); - - // Autoconfig target level register: the target level for the - // auto-configuration baseline search. - // TL = 181 = 0xB5 - mpr121_write(MPR121_AUTOCONFIG_TARGET_REG, 0xB5, sensor); - - // Autoconfig LSL register: the lower limit for the - // auto-configuration. - // LSL = 131 = 0x83 - mpr121_write(MPR121_AUTOCONFIG_LSL_REG, 0x83, sensor); - - // Autoconfiguration control register. Default value is 0b00001011 = - // 0x0B, where: - // - // First filter iterations (FFI), bits 7-6. Must be the same value - // of FFI as in register MPR121_AFE_CONFIG_REG (0x5C) above; - // default is 0b00. - // - // Retry, bits 5-4. Default is disabled, 0b00. - // - // Baseline value adjust (BVA), bits 3-2. This value must be the - // same as the CL (calibration lock) value in the Electrode - // Configuration Register, below, i.e. 0b10. - // - // Automatic Reconfiguration Enable (ARE), bit 1. Default is 0b1, - // enabled. - // - // Automatic Reconfiguration Enable (ACE), bit 0. Default is 0b1, - // enabled. - mpr121_write(MPR121_AUTOCONFIG_CONTROL_0_REG, 0x0B, sensor); - - // == Baseline system (AN3891) ===================================== - - // Maximum Half Delta (MHD): Determines the largest magnitude of - // variation to pass through the baseline filter. The range of the - // effective value is 1~63. - mpr121_write(MPR121_MAX_HALF_DELTA_RISING_REG, 0x01, sensor); - mpr121_write(MPR121_MAX_HALF_DELTA_FALLING_REG, 0x01, sensor); - - // Noise Half Delta (NHD): Determines the incremental change when - // non-noise drift is detected. The range of the effective value is - // 1~63. - mpr121_write(MPR121_NOISE_HALF_DELTA_RISING_REG, 0x01, sensor); - mpr121_write(MPR121_NOISE_HALF_DELTA_FALLING_REG, 0x01, sensor); - mpr121_write(MPR121_NOISE_HALF_DELTA_TOUCHED_REG, 0x01, sensor); - - // Noise Count Limit (NCL): Determines the number of samples - // consecutively greater than the Max Half Delta value. This is - // necessary to determine that it is not noise. The range of the - // effective value is 0~255. - mpr121_write(MPR121_NOISE_COUNT_LIMIT_RISING_REG, 0x00, sensor); - mpr121_write(MPR121_NOISE_COUNT_LIMIT_FALLING_REG, 0xFF, sensor); - mpr121_write(MPR121_NOISE_COUNT_LIMIT_TOUCHED_REG, 0x00, sensor); - - // Filter Delay Count Limit (FDL): Determines the operation rate of - // the filter. A larger count limit means the filter delay is - // operating more slowly. The range of the effective value is 0~255. - mpr121_write(MPR121_FILTER_DELAY_COUNT_RISING_REG, 0x00, sensor); - mpr121_write(MPR121_FILTER_DELAY_COUNT_FALLING_REG, 0x02, sensor); - mpr121_write(MPR121_FILTER_DELAY_COUNT_TOUCHED_REG, 0x00, sensor); - - // == Debounce and thresholds (AN3892) ============================= - - // Debounce. Value range for each is 0~7. - // Bits 2-0, debounce touch (DT). - // Bits 6-4, debounce release (DR). - mpr121_write(MPR121_DEBOUNCE_REG, 0x00, sensor); - - // Touch and release threshold values for all electrodes. - for (uint8_t i=0; i<12; i++) { - mpr121_write(MPR121_TOUCH_THRESHOLD_REG + i * 2, 0x0F, sensor); - mpr121_write(MPR121_RELEASE_THRESHOLD_REG + i * 2, 0x0A, sensor); - } - - // Electrode Configuration Register (ECR, 0x5E). This must be the - // last register to write to because setting ELEPROX_EN and/or - // ELE_EN to non-zero puts the sensor in Run Mode. - // - // Calibration lock (CL), bits 7-6. The default on reset is 0b00 - // (CL enabled). Here I set this instead to 0b10 because this - // enables baseline tracking with initial baseline value loaded - // with the 5 high bits of the first electrode data value, which - // makes the sensor stabilise sooner. Note that ths value must - // match BVA bits in the Auto-configure Control Register above. - // - // Proximity enable (ELEPROX_EN), bits 5-4. Default, 0b00 - // (proximity detection disabled). - // - // Electrode enabled (ELE_EN), bits 3-0. Default, 0b1100 (enable - // all 12 electrodes). - mpr121_write(MPR121_ELECTRODE_CONFIG_REG, 0x8C, sensor); -} \ No newline at end of file diff --git a/firmware/mai_pico.uf2 b/firmware/mai_pico.uf2 deleted file mode 100644 index e48902ba1fa2a8c27db93e5171ad06bf81d63d81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66560 zcmd?Sdsr0L{XhPmy|Fi7xhQ)41tHVJ99|g`d6(joEfHz1mUZ zl$iH$HI7z?zI=80bUT=99TM}Ja>^XC)Go~^HaI9unMFgWpj%=&U`-ShX2W_2<#rn$ zq{tbqUvI?UnfROh$uZ}QZpy5*_5?^-CM*HWYaGU6QxO#@>vO#8`A8Y@-6Y5O;>5u# z)M-cq0Se=b5{i`$>3g2*%00?LX=Cjs8BJOH1PcFm2c%7sM*VvI`f%6RVf};p2PI7a z5*9y84-f?)y%)?DfN$`(g@)(xyLHMhpaZWDZx1&t{?YJ{F?f=-`Bn9uBk{*NAovp! z7{;HL(OsDS4(@-#{jL9H|L+mk&fxi3-2eRl^A3Nw`S-8ua*u8f{Nd}*_iTCjAzbN*Qh(<$AITGH{42kN=lNc`xzPi= z>mg+9SA=5v<)PDIS?KMasf*H!(?Ah2_esr0nhO1{S86ulv9T8{R3zoS$Qem{r&Sk%sgPtcev63yo%7IzWezY%O_9VW0o+R3NY;#iqKP- zwv=z%D+~R&heCbyw?6!DX05W&>Zq zsO%QF;z5ng04)!_dgIT*8ssB3S_%Zy4?YNo&l`aDG>5}Ly9!-}hKgH@L%VNK_`2b)P&mvmTi(2W z@6X37_T;-p;V&2Qm*ce|{0Z-hb@S+8-CV}HIqD&GGjxqY-r0IXYJW%X2gV^@VX;nk zIW}=;j7pQ@m5-^jUjayMp0xI96>pIRABB z=XvOLwG-0&1yY*H89~Bgba<}o_ONdvL}o%?^+<2sjd$@2`NjMvd`Dv~uRUpSN1rsg z<#2%a@lj5g8Xx$>c9_0Uco_1W#|+Z>CPS%vCj5RYq(h*0D_DH4QkT&s?TCRASf`iH z&$NT3jz8%vFR0@=UPWP5jM;4z{t6L)1%B}m{^bQ4hn=@O)c&U(8vj{Gp3`Iy>umdW zKMxt1LN=`7f9u@ttSOv_HdB&N#;@kfpsXO#AkBN;70{nsOLT9gtBj9vZQuIZ!bBkp z=AS;m*YZdBlYDnmStPeK@0|XSE6xyqHzFgHVs4G=@vT zgfu4=`l>s|4;gJjvg>aVx(PLY!rz5-u#g9)qw(o_rFXr3C!AjmjGtE>fY5}#3a0{M z0cq>Whh9%`LWW3NjI)j}+g^($JB1_8Qu_hkUSOhX3OL^AEOVmiGKRNN_(zHOW9w)b ze*<_-zsrZIl^?5WAXM513|MNgjuq8WUSNEDWrR0Nd86Y=6R-+f!4ef*0-m`dRrK8vkR2M?i>VG(Ka zJsj_M`jG3}oi+Rwr@es6Gr7kw{G;W+Qp8_50)M5~B$0AVInaB91epdw;fwNDo8HKW zjB)L2cv%%?mR1$>lB#^3s#?a=09E0xQv<&VLa4Gg8}cAtZ^yi!U8F2h`*Q0w)Wru; zZWCFL`wm@WVo4=(qfAhnp{vg*v5uNb~ zN{f!hC+jJ%4mAc$ks>{NP-{Rn$CnZvv^OFr#xMbl;(wKhziI^jLvoViCpcf;4w*}Y z^(ZQ58>DymQInpC=+S?moIdQj*EP{q?X=ks@=#0puINovWr4v_>WK0A9I?KOjtr;Y zAm+M1qB(fM0tPtlydO13I%K`gk@8QDGqx=x+?Aggf z+JEEo#xVS&`Cl#KuO5NF8T819^TKZhrKoQi0ktk2TU%2o1sOzxhBFE(5k09d?frhDAd$Jx`y$o%)P}5JWpSQtem;#|t z_i$ZheD*Og5-;T)XV1n~cVbZ7YEE%7^K#0ok)&}%mBw@O?6o2aal zR_dIr-qp0uSe~*!zoVf$cuV2074g@Oz+bhP;Hk1p5YH^oAfAo^D&@+@^qX0DZV7*a z`kY`va3T1FNd4{<{X05nZq&^|O}giy*Ucr&FuoLHfG60LMH69TA9@ z=4Zj5*cohwbsSpyhDG;o+68y7dBv(fmt^}H=U-jHk9UdfK#OA!mI)aO^ytYtNZOQ0 zO7dWb!z2l&48U2-;nHodc}5|QK@`w_UXl(HOQ)x|Vu++I%OlA%Sou@@ru z=l5KT;5!rPc#cEP5?l$!p~fZrU(w&#+!zH}CFPr<;l)kmn_j|idD^OMl?xPolX4oY zU2UT6H>HR|{{f8QZ4~}7BK|S>%{y@?{eNOBnVOVZ+{EtZRc^W@2Bvd+_^%rF@#W|} zRl4R*NGaM2_f@Kw{SGus&6|LpYBdPuo0O4Sej-xKhE^)jU>)E3_QDA$aVOFfD(jj>N8adZL!HTEyYB$Cs{qZe;KPXP{A`?LZo)H98Y20 zb~mBe3_TvsM2xx{KjaJf{k(_NC(#3oGM$hqQkBn|*?L|XWtQKqFi@#+?0z=WI#Gx+ z@8uP9RUPM*B)x=>g+%sO?0lGEjT5MBul4H|V%3Y35rdQw(FJL-?b?X+?{3QEbdc-% zG8oChWJIo$J1WsP@lQdCUW}RHW}dtom}PdEjpUQOM67a^p=T?6kF^+3rp*vsQ`6l;y9oQ&%sog zAVn!oc2^W@K=p@#Qw)6Gh7-D$t2@Q)Mm$9B>%{wKiiq^kX^>v+Fu zMn+mIWhv*YxU<{dK+UN4w+`l z=hz>OPT}(Gt2|y$nEBfl?-q_nzn_C*P*-pVQTt4@Q$D}5Hex4;v@jWIVIRsF>37=v z5L!A=hW&Z^Rr`bF&7R&P%Po_e#+4P!HXJf_Ftg>4Mw=ul+>6*^%^Ub#a3QaOlp?>W zEPpk3OW_|c;vYW(e~32NGdxF=>-bXz_wh+~S*|Rv3=!2WDvN%Z&)^nf+xdQvX5t}J zC4YQN5l`weg;U%A%k!@-#~m66$0u-g{P7^+O#$`nM?{7ok5V`q2Rg!Gs>oAF_!V9V zhqc(BNHvASCr+UH820CPG-_d2J@PL4Z zMl81Pa@{!iM{qem6R&j!z1A}*yMKD)-Jrs}W))1ewA)~*-UGRM5C6Qg#?i5@b&G02#znh5*%+!DsC|<=Pi}6(G*wfNi1HjRC5j8~CS}oHc*VKAVaMuUS(9<3 z;F?ucc#Mx6Eka7j1Ccr8JLGl++9zaR;qW@nITFfOlzYov zg&y4CD%vb#cpJt4<3#+&;WrQQzYOcZ)hLG;K&qIUDnNRw;EDL`r=rh3MyLdn;USRb zRU6LgrSlHC_UQMmh4jRL%#{T(9b3U~sKBf7k*jrsS4sb_!bO9tNeTVIQWweFjW}gB z?s5S0K?Apy%8_lgI(BXS<5u)VIH^oUn>CrrX>UkyNwe#4#Aw(&m9@hfJus|ZI)`&* zHHqyE1^V?ojW9JzSOik%1A6K0rG~rQFhdrgVW@qu4bB*(bNBFE!5a5o9><)zmbtb% z=YbhK*b{-RowP*3%KP*OU26%O@U&bc>KstKE=ayV}LpY)2%`$j@OV0gmRFg^6qvW-Hz`N_~q7XTgiw_U{<)kt7jep z(lfkQZ*w2u!D4e&@ue;bvQaD%|0Ks_PfkXZPzGivhkXZU^`&k|ACLAX>8lKP#f8mI z%8&1p@iz7f|7G}{aKy3ey&!77F}#i9|0EIrr0?MWdf<=2rQkt6Q|XJ`yZ9o$$hF#) zAE~{s2iEYO$k~E$J}-&X`QHaT{9l5hMzQsE7q-9n?J{@-`S{lc>FtPqRsP*p158~m z5PM6S_m2Kv_h}wxBnPPHU|NB2Z97bp3HL*p;T^ns&mjIUZtdI(Q)eRnS+K%YzV*8_ z@S_L~c=T2`+I1Fdga&AshxopLmcM710?LLdASqCaluIb!zyRgPQD07cr;O$RO#Sol zdjVbJ8pH69=6}72KMs%|mjCY(9>_QqczXm5>>Q+lHIe#r4XogI4cG9B;Tl#1*6_4Mx@ z1Vq}W>31Rrn+4MLl;-_o?U(v}=zBJGK)P};?Q`Vueb#RSR(Gij7|OEC#i6zdcQp=_ zZx^31e!#JnqM-vvDmHQM9M|VT%3{DA$0F(9;Imb>U2u?REw>c@1`&V52>dGxbdAZ* zW~gABQxjQ_WjU3=B|D@z`od9=@6a{Lnkg1oidEz2i#lG$I0|rlji6%&lMIn3$+1)p?;Lto zR-=~QRS%)pdcs{tdx;ft(0=N6tKD!+vV>Q$@EF{zegh z;|Tn5#!k;q?4{0dx}+7+hf;^f;x2j2Zr&JAc@`NRFjX!%Hd3Lc8&noG=SHBzyxlIL zjo9KQw35!nISkGsR%)i$IET#abH-Ww1&_sJuEuLBDh^8{Wp1`J<(utmoZ+d^TI!Hm zeo$0t$C2I+33#0byrW}Mf^$NIhNRpCN4(R9CC0G^oPkts6LWYNkJ(R2u}thWHs5V8 zhGmY*^0kpSht@UtoKpKGeCNAHymRHyJ6o1xuYpLT861u>Wjq!?mBGbR_RnE zBL1ck_?2f7S770@^7t5=% zLJOg&+wA37UhatU#toM@<9{iyU1E16*()p3zDJ>x>@OhJmJ#KB(LYF^mI?n$dF1V4 z>_^M5bn4NU4L|ruGKS$Ft^bon{F6uEZ~6`X@Bll%9@s40VTN-JcqAnE7|(!8zn7YWY1RD-UxqARn!s6bG`P9lU^}8;X0_#e%*E7xez{f6jw`MxP&>vI zlRXK3>U@Rk3pAT86ZTSZoY^wj@w`LrAouoPyy8$|-(Ppr0*l$HW*e-nfg{d%d3^Lv z>$$*}=F84A5;g0y{yFeP5ZD`UqHVz9H~<#L@HUG7Cy4k@z;E7(du0A+Goaj+*!&>n z&Q)N4jYrO&U=%hJmEf4c$cxW8DNkX)~{ijLHR*V;mg)OoMEIJm}E1fZZ@N2Z>Kyt zeF4^Dv0F-*MZOKC8;lz+u7BC8Y+;-hd()Y6U+jGe!$%|faHe1p_67u!_ z(h=^8Mf>3AeFI&KZX}f?{l_Q%5}$}(2rOdnEXZ?k<9BLvpN=H z`*9+u{c`a0(f$~y;;VUols=&GYkgj8VoL&Oe81)WMtcG2DOR8?u>i+YxpRT0>ye_m z0w-`>(H&68Yw#`R2)=@35cn0m(NxA$^YwSn#1YEJ3nBC0foQ)!%6x(JG1VEh{-ah? z+dRP0z+-qD#s3pU{3qf!5ApvJzKXwWu=6vqKB?T}g)0|!o#S|IZM%ue`5m^kYpqiJ zZA-Bgv=}-508i!A6tMiwvH#so0w2Kk6d8lV<0zbF9A#O<$KZ(T_`|jQK|Z=p=Z`z| z`>09hVfw~EofXc#-9cIAVb9IPOlW(fL+iUUCCfr}(3w(!%1yuXD9#jGj=uV6;gnU= zGBLBF^=6K>ksPPcKiI37b>TmGVH?IspeM{R9K9z9Vq7^4Fb9*Nhs9Qhy0C0hcQJW6gy4mMb|Zn{kLRhUPq(9 z-p{A{)dy<$Nq$Y0wwlG!UIvt{ROs`5EJgS`ay*Ov*c~`OgbIBWN&93lZF?l`bR_M3 z2MTwF&-28j08TH&MmOYa%El*vQ)b2wjUQd)JicZwfUPg37 ze({j}mtf6`^_qZVaPGI>pI>H8W}_n-y@*%UlCj@ZNYnoWud2F<&N@U#E_i5A?8ZFh z1*)nvpUtj5l)&0}8P>n1R^nCpr3cA;fh@rJ9lkv{7wd%{^!H7BNJ#Trphhu~X-=x) z^7%6UzO4GfX#a-fi%E6*_ShQ!{uG?AH)$n{T47p(pgwVspUBWqS)evkv+glap&#~S z&76rIKl~lq(lZ%nDyXJ^)+%QdUOO+xSw1qaI!B7Lz2<@Xkmj(&8-EhKbMY;*fO;m} zl|B&T$M?vXjxuM8Z_I*^A2teqM#P^Pf&ZV3C#+N3=A)0chEGDv$_%9jnnv4cR(;_s z)^A%&&=#0dQ)#4fa9$wFX7wd1H_@O&p0gs-QxIt>;;pWccyVqZqD`XM_XTpbgRvzF>+*1=N5Y$ z6rnAmdWH(+_HDLp#+;5m3656Jg1JbYIek>8cfB6T_3otT+8n5y`BUq!^V4b{u*znsP&@4E%;O-ixl>hFgmj*6OUL}T z6#kP${3ngTKN@RMDS(C4$P}!PF7&s9aChfq)Bq6J)EVP1P4VVW_MJ0M_EvVHY@HPC zy|>eYvoco{5Y0Rj(4_&Xclc=dh>`>(mZ1J>!Cm@gC4Q*qa`VJWH% zF8TAzvyBTpyQk-qen&DBoU|LCt*_9Sxi38&5?Cbx!CfWOJ`DoYHD}u z9@M`?TJP`W_>1roju-Ol$sBxTwaQE39FLpw{Oum4PvxgVU-qaEXwakZZ0}CA2v*z3WCy!=HJ7|gpt*x$>iEkAOEKqyNKR!9krLzZDn_Efne5ao4qGrA1?9=4 z$#uN)ne_#C@`)W|7JU4$QTR_0@t-mRe~Kxya%fql5no8i7N&BuX5MC)i&1sdW(Zls zs%g-Ap+n$NWM}?8AZ1d02`q(@0WQUytU;@vIwgx>3ES2_u z1qnx^T0u+iHk>^~G(fa>VKCJ%K2^q`#bLh`OhdYQujeKOnR}LFoDWfh{j7()De8)V zNaH0)(Y_v}iooYlaN(B1KTX6xZ6yA)=*(1F1f3PJ$I=ImYKzICrX@E^6E-DofFP+CWA>28Q%s7 zMNCI2wUTs&jM;4z|4$Y1CzIZWdzvafbYeYwT z&~LjN_XD`MV~w|VdIvG=?IgA78-eaukrWZ(jlrJ|bE_1&Rbf|0a?+_0IU=-oGJf>M zWPB9(Yv+C}oA?zL59;^B^~sRt_o9{Y7u#R#dwJyn(jN_h@t1+2-6*e6?&~j4MxSsA zukJ{P&BeFXmP4Y~|z>+hEMp>OqJUJ6-xs*L$lIyA9T5YfF-?o8AphKfum+i zVG@?KBICu@=LSn6S6ps#EfmB!9q?OdHc+!!Xx8Vrv*s8fYhDzn(yzCv58%k9$ou>z z;9*?ub}Wy)@k7a~3}}7k5_#hnFO7%e!58=wd?uV*C~YPBt*ii}72sHef?_gCd-&#Hd`)1~c8-74>@5VOWmuIe;jmU z&7Qx6q1yt^%bW54@+%eizx9%iQRDmyP4&h!3djE+=~pvaFB*2|FI=J+jZeG+fe$aO z!`x>tX?z|g4r96**nPaw3PSc3@*Amw-HX3T2@;&rTc&^ux+@yLNkN^5(1ZOdzwB(h zEM8lUa)9U)5Y}DM_;fjOP|InmmgCdbUIC^S`+pQ(>_LDE5| zYXuF=hh&z9+p<*X&5xQxWglN@P{n41TG#Qa=RzG%qZSEVQu))NmcKu_25mp;tQpUh zhs*fphTj&d{dN3(1!!xw9=P1V--EMEkE3*-2RT69X2#yC zpX|J;118~hYWD50y|bCGLcO;i=LFQCe)$OZw9TL+R8@D>X9PyMXls6nr~8i-e3 zTWzq)1dX3tNVRdi#w%frpk|}1HF($VOAgf24EkCL&7@vBUhp>h?eF5tOxRxZH}L7` zx?js<47UyjX2PAxt%V%F`fj4x7hFn*XECzNQA9TvR$=cjX}xLu>e}Oct#w`7ex!~s zj7onY#&oLZNh$Nty-(dndNKv{U3TG}xxn*FI$G?sTZ!@@VlfDv*C2IEC&w%B>uSa_ z{G;{%bP@mQBk*t5Q`szd4CCFXNg-i@LsZCOymj|cU$Qsj8aY}wfZd<)0&ElE1o zgHkxl-F0nMTk)z{O?jxSi;!Ek>#Qb>|9R;Z-uCMAk7xB1^pEPg2$3 z73GSuCfrroEeGWM*(&dg*Jwt9+N3aDhT7dk88``3!!1ms0yJab{@>(OZV!=DPh8WX z9?}l#CZdn>I%jv1Ql9FR;1x}krgmN0mpI0tjD*4*v)d^9Z-$8f4E*9D`yVb$?fkx8 z{;*@DUIwB4lBks@T+;YewI&6euf421V`^wkGU}~tb%Uy^n!gwukCH@%$muMa+IG;e zRj8ebY>7v`U(QscR?lZ*UYcmx&$l<=n6A9L_=IPU@QZkD-K%`G7pbiy3ugdPv$o#( zD;zbLe2Jb00mG#zFvA>l9^U9Z+n}2IMO(Cwc!b*<{t;C9?~OUS~DY_&AB#a!N(69g@2Zaf7S^6 zQIdCxoRXMh#B7Djiy0_bML8qm%ZYP}&*nB(i{5EL8SEzOJBxzt^d%Hah!~blx~G=|hmh z3O2^8f$eOp_aGnRi}lB2q_3FjSZ`l!1s{)cXddU|Zx;w%-ctC_6!D)q0)LS&!P4Fk z^O8DmKaK{9Mhkjkc11zLGvj`-h>tlGd-w$$_oJ;#^u~5H^N-*dvaIZu2Et>dcm94M zj=YI*>W?oGZ<__%XT-}(VnenxsM0@ci$*K1qM)UrGZBS37xaF?`y zF~5?3iBEX$LQsj){;5&)La2qre)aJb&S@Heer#%6o(_`M+Q=eGmiHpmr89IKf9hr@{6eX_uQ}sAaCKk$U6()NC?hfN(e9 zn1O+=Pj4jP7*Lfr+{N^fwpkUC)9E9T`v2xl+qy++8G6%O3jbUY|J)Jy|KE@zHv34N zp1u(u!HG!P4|`9cF5l6a=r7@8ei8fZOV|rL?oh&WwVf|w`aoxbe=(-Kgwi)xU0Hv26SB0cr~4hJgs;7HNeQdQj9$;Ie?=!ixR+Q zmAr)gLcMZ~MKl%1vFRFBHYp@Jch^HEdiX6+r6^i%mA>?RAw|pTbxk-zq#Nj-AZhm- zFh5l!GgEebVHS>SHq63UMhuAtsNZu-;g8=<#(&Nlfxj%rw1zZ znfBv_L`DYGq#9m7WiMcWZ{F1iOw4W%aa1mkVq&3@&$ zmILbp>&g7FaFF~h+pOD+bNm`TW|iXYq-89z=HBC^B<8qThT{nnAH4IyyEc#C9PYXj zPF#QI`nxua-w?tG6d=S!%1da`tzY&Z$^+tiq?@s%S&@>+Smc{!UHe{H2YH*VdULh1 zf~Q*2(4M)a@SiQ>KYIlJp+)C=#&Fe>Pa>q!%3x zD_nsCvk5pSBurZIZ+0YT)37#!g6CA+lDSx$q$aaY$+X)y&)sa2FBq!%TiT=6d4sJa+X2Hh~8-@QI5r6cj4C6mrtD2VDzFhqf=P~|J z%BxAOjc3YCpRk{@ZyMI|>$#mK7cZNo$f2N(J%xc=$y$uv*r7cIlakf4fJrhwCWj+bldQpC)c9EZg|t(Lzix?<1zM7Sh|{J< zC_{Vu5BXP+va(JeX*h{AvK6VMHh+jl$T^We)NNpTDZi)1odRZz_8htMhx`uBD{R=% zfzhDuPL2{1mprz9ZbS+Z9OrL-rr}V-eaO+Q>DByk+7vB0pDmlAy@Q)3BWbl(Sqmwf z#wg-QYk>pSu5(DCV|W|I|MNur=Z(O>7CEBU`t`YQMNWvw`yw`BePIFUw6>d;SOty(`!cK? zwiQB6`cqPjGbfg>{B+?R*f)o^I=-3^Zd}S**`@3n){Z+y5o{c{1Xpj0mI|k>vVc~a z*b1~NS|wjQoR%zAx8f+_XdEXz+0LrY+ga&_r_s*XX$?1F6l=sf+@wLhAmtLce+y^0 zVtp7b%W%{f1|L6d6#jW4{$$Y0kp2gp&-s_ICm+Zfet3Q+Otm2jO%(3r_wT;<`_)KZ zie}|DT=_-TT8jTE#KToc;ZBZNxEW`MU>xzcl8RW?*ejfgN@!Fd<T+B8zVi3W)i{}8 zU&Z?s>PlPXzRLG2)$45Q_N{w=o%%l8ef#cv|33A4+xmU$-(RoZVB5edb!pPTz76kh zII`~TU`mc3E-yW@t_jz4`U~o$6$K(@CFrao^zmza_ozk(Pwi2wC0U=Q_Xf^1>{QCm zN%pXNK&IoM1LMfqmM)bZh-xBO$DWd&)}9VEQmI#?Dp7`n8nw}XiCv#ETfWz{PA)wc zX+5B|F=oNX4;zL5?IQlSkHDWu)%D5Ws-uLfCZKn~w&cB0d*eOxQ>7E4KaG75>&tI? z0P8E#vN}iQJb?KhjO72q1eP7JJ=q`SkN4a@O*+H0-n9DsF`k~zU!-qKgwyN#R%jm*rRRi=xv4(a}-Z3Y;jU8rb~ed}>~FbdRtO6z8aj zEeM%ZfK{?U&C{Gen0ytjzIb_DL3@;;Ak@?rMcWOCds`IHXo*h!H~^XL0o3f-??$o8 zKS!l;#Y&idNoWhspFZ^>eFqzPClILAuh~S-2yd{-+&K~oZOm??@JAg>`2P<4;vxQj zIruV9&rn{ZXUpIe8x4(YH#;{!jr(PA{tWF!dI1|rC$-ki$~Yyw%4)f{lKv6Y;o44+ zdN7EhV+Y|ak+VA0>*VXACBl8lU#Ml+s;A5)qf%SzSjWC>bUUztBm zKW4$l4;zI)+GGTOoM1DI|2_FY7v?KY?`iO24QaKNT)cKAw-;-!jMSD{C0nOA$c$Q* zz9Rnt{i;ZA{plXbL(y?uNU|wf$9*ADM@s__i2R!uzu0ej6kfB&v^)S&go`I{$zRXU z;zT|+$BVu-}fko zNGqhRVagFEO;f<+Z@KB~Q&bPG|1kDj!zsQPmLL^Ajya`BgGJIA63>#ZYlg(5r1|B{?9~D;vV6?NO*+%bpkc(0o}LKM@`yOA4nEPXL3gx zUTY}jrf9cwvydB)HT(d_&m&Ewrpgu=l4TRL$Mlcory3r}2VYgg%MImR5^`gP_Fk?U z?>a`P#}Y4lQ*V(m+U5G~`BWXbe|bzBrREB#=Z#M^G$55p6HC$Ku$6lXTbh4OYB9i+ z$Ma{){DvgicINuBea8 z{g^iS9MK}FGO}J-G~CIpGjZHi<1-CEX?O(R?r}~!_;#-kzuhf`Ke2?7|MN%SpDoML zE;nq?Uv90-hfK+N`ogNqq(Xx2S5J(%zz1+I?1OOT2uq{)_?jEsg7R zel!=5Uvmude+O?MuMR|E!4_zwvvDP{ZY5v~0)K19sn6?(moGH4J28>Ug^OT5o0M2W zV{3tU87m5M=V{Mt)1xlRpo%@fF1OOz=UFq_^Q~MP{-?8}&TILDk@UEHfu%7`31^b% zZ0UKDSC@|#KWZag(=Plzb@T_ZD^gI-7?+Q*@|L63vLd-1t(O74f&?ClBd=%HComZ8jonmvJ0-M!%nr)9z}pb2{|xmLo5FP}f#)$ubjaU)||7 z`3X1{yIH?1zr29lmw^37@kPn*aMwLO6i}IJLHe=B5{qkW6?Mm8Yr@_WgRKc&le%Q3 zg+_6Hd$-13dEe3duH470uUvn0{gw61hRO{`H(c4kR9q>)MrI7bM@FEB?EYWkJO4l1 z{dnI{KOZ~T&qraqQ;+XSepBer7&^z51sauLa*kc}|AYSLMEtoC_{Rb1{Uh<13i%Il z6(8=gD4ZFO5u!706CjmvdCJH3lp5Y-$CwP68MtDm>F(&fQtOKR_J;Jk)#(WsljlY2 zS0DYvD*bq-G0&N%uPOYsfx11zE#}I5WGF5-t`Qq}-YUIl9?Vt5OU!vrDz^sbV|*OU zd+cvPsWT2cWNHepVf!z23z-Y_F0Ta@uImhqo~a7Kp3FGb=_hXydm3Zy6Pz$@yueJT z!Tudd$Fz+wEmp|DNM$`j9{#6eDy@W-x)Nh(+o~$czz~5m+2WERdLR%uaJ5xf%+PxWnlI zXUdcLe{cBi8m6+F*%x9{CpWJ&OI#q(mCa#E2D^xpxR7aAzb zOdO|oZh)ZuWdLT#kr$=)qBICKu~^!ozJDV^azyCE;1E7T*pT-k@u4N5{$7mn400@w z#`30wl@VG~2$e_!a$F&jl;4eIyPC+^^*~RMl%bBSO(73xJB(o3sLLi1>T0ar*xM`# zNH-=pC|m;qruGYC82-`iKVHP240;&S|2ju+q`rn|;Ft9zdJ6JC;-5ZU}#X`m}JEcJVE-biA=G6EINFe)%(N`U1j4WpjA@t;9G%5Bso(=5XH z5>aZ$4N7eyFDP6G?3;kpp9V&dy+q;2pzHx6xxWb5dFeCB&JThS+0%P&khfnAN}oA9 zg1?^|!QX_2NSwl5^;psw3fC>)k87DJPs{4{g&55$bfp`;$76UK#s7sO{%8&jq$tlhmK|{K_PO-&)~g>@Q63h@eX9OxZ`SPV6E7e~WdT7(sqB;(962-?gT< z4C8c?@zr47&BqoI3*xX<(t0j%aABa0=;f9kT<6oGLaG0J5EZsrjug1YL9YC%=TJls z5ZvF>0ZqRU1q6S&UDt@Kl&8rA8Af$5I3d(BKy20_dKIt@Vp1eK`??0g{aw%l%=0*# z6c;;alH27)CU;*K3{YknwniQvpqQ_NA3Ov)@{FZu4~${>N6Y_W5&z;5`0E-a*jAIF zBy8r7VVz_(!lb>Z15_4_xp5pYUfL)9n*>Gvc+SO*q2K#t|RgW=(wfgI777X zL#aJlCV5fVa&m*5HWl}G{j870x!#5n7Rw---`AgyV~D6JU@_C?U5UjTT$P^7kG|m0 zI4mpg&2sE!dHN~&oY-Q^%2->azw#ODK3{i$QQ5NKA4|hs zZ-m8G#4UxtO~l_e0)K$Gr57+*+kV7mH|UlErCaL3qfVf31?Dn^Bw%;Tar~F7aG#sP zsLj17o5?*QZXdY!xYgV2+jf|bMNzCt@+VaSw*-#FCPMb&9~qWY4f+EvigvO~T|k$~ z%c2EW!mjg=dieK&nwBG^7ojhoswmivYY#g3oeq}QVSI-iVUq0(9N;VX{DNhU1(pRk z(?^0aJ5=&V~S+X!|X8ZpiGKy4awYA zXR;%%Bk{Y>sSux|!nI}|pl&Jr?IQkU(bFOM|EOWBBier5fup7|8g8gbd9&^-nu9mG zj`o}j4$+1T>EZy&2<)kk&i=?RlZ(URE&3mMOyrhOA^}L{Z%ACi7n`{MBA6kbQqajYuAp5ORg@d8HiWDe=_J{2>)=`gMBKdas!e7&;cxvnCQ*8BaaiDU5&dQCoKZv5i%gh znBmG!D(A(WM<1P3{(Q1&rX(am8$c#_%m)~yZLcJ-ltz0m3)67jb%yz&3nR!Hx-SPw zYIJuOYWyMDVOeakp$8!~2Uq(^7igP#$pQYTb$aU+BhCWNkhF>PSPJKGG)SMm%t<^$ zIc_TVYC~!A5$k6c$Cs=aL^HZiX9=4-lEYofJ{o8|Psj>)N%}s<^{!J5?B5)8QL-05jJGT@HPtnMI!!-M&REPct7|-gr@=ii8j}Kw2humAnT>C zc9Ze_%?++X@weX14z8b0eoO6ocX-ZFj%I#8+EU^geV)ddi)DaF(cuiKFy#FsSY@IG z_M6^JY%}zA{iZL&KEaWJ=ZRT;T_5xjl(ybTv!^YY;7CLN4`j%MIe--T=47-FtvE(F z59x#!7+eX~Y^&l=TA9|*Ba}kME1eCBbix&oQAT-gYStG>B`2((U!-xZ@Nm}$H;4uC z>J5s19ckjF0V<^G!SXWxb!)Z%7mN5W9)UlOtO?qXC+!L7j{-m+K|N0}C0MV< z{Ttj}1YoGEmJkQVn_ae>9Zxl>-KC}_n4t?UG z*AuXhI)<~YYLTyK`NI4j-Cj4K!sUW_?ZiFbV@;EJ+^ zZNVKD^R`#*YIYT_Bv`}0r}7lA7?UU~>s|LD?p@crZfOoVl2xjn=-~L{w)P)Gxs{Hq zb|3zya)y2ZO@1?Oms2AT>gYx8Dd(4eD!2Uv$45>N^`V9 z`V(j`P+rG23C8u19Pn`%MIf;F5_!(ULGY_j^yv>7vSwd*OS1h@?Xd|S^a(TCmoVO7 z-#*?gxve;O53W_8v$!3; z)rZha(AG!3?z&TZqK3@$5#H={SjGdhErpFlp9)rPQpoRN=ubVU8$ z8`RrK?T>eo+K1^7kOWT$NeS^-f(h6CyIBUt-3mg+#lTk>o2efA`G}4odW8Tnq+7^2 z*@!(tj^{BSu}_X}lY5P@-R-kR3ENSFgYbh(a_mV0p}RxwC-1)91))!Ri#xZw)h8s` z2`-`)$QS*ycR^>z9*MfSAvPV_Uh90X*b8S@ld{L^HVXgcBL2(qgNN`x%xjNh+}A#q ze_>m=>%~3^%6}sLr*>Z1gR~(W?o?t5%X>NPOLh_v3`No0&91p<$Do^w~ZO%e?!Qrh#qZ*~4El4s*^o^_op zpWr5_RrNM>n)mi~UFepid=psRDM1<1-%%IIzf8=J-bo19JI@9Cx?b;&M_HqNiABxy zaPKpnqRhCxvuapo{5&EvrVq=Ev5GzUu2J}}5b<9z0{; z0RAEQ?2pR5K-O-Cz@ckHynnLyx|1?%Pp~}7RvJP_dvlq-wW!rE?7#MLr~7-dp#PeH z68~m-f4kr!?T4=Teu?F}+sj>dp%(CwKt5cdA`nou5Z9?GvYaB-T!Zm<|KV75DS(D${=8G$fQ?nX&Ifbj4ar4{_IQt{d zrNU^my9+9DHK2FgJKeVw{wqcNSB}8nBuU`j(JL_KOH`H2tMCJ^x&^I`9pI1hmAR$W znha$C{1@@eNLtPAx=77|Z-rZiHGSZk!ar}Z-{^0Uc;})80MqZlk(szkRCUe5Y3Ho` zInl;$`|&j!zk-`-`>S5!gIZb5YiPmm=9OuZHi>ViwV(x~$zbfK8eBI9W6)Qd+@;CZ zg&$umzsBOMp~ZcFK>34fhxPrzwYRgk>w~ZakDAI5GcAP z@k1^49M%xq^vOQ)8CHI$_0AT_A;8s4WHk^ve1oJz>&$CEvEEYn-znmM=Lr0vW%{)! zzh-d_YDxWU$}7c%Q40`5mY>kkk2?4J^+WVC(aA&?llCF_{ciNoH1z4)+_<`{5Os~p zZgsUZNc}!*C;*3~P{GfH)qDryQ5envt^m9-cFJeMSb{!Wp=V+6%d0Yf4X=(!^a&A( zZtSeb$c#~oDerHB3VVaoXMH`O#E71+VViWwJ|OS#SsCbVt|O;~j>hpY5w*d5!G%2s zDR!9T=O@^Gys@JUDVN6Bk}LRHH-!Fs0Me4N53&fU>igd}mVS?t|Eom&SB=2mgl+Zl z!cq4{6_sufFw)no1Q+AJs&BL!E>@!FUm3XK#u*;5Z&;-PD}U5&w6+RJ0q85P#g)TO zVqdFf#68#ADLND!@*99fhpWL+81YY;UFwRl)jATLs~zRU1F-X<*Ol0#Cwc%3aHJf^ zi-e@z6&sI!#{)njdEnSI;(M^T8lQG+ArdE+b~P#NG)!;%ty5zlWB&ouP$yw-E` z`5M?kW6vSmu3{l42UnZ7$%^8f#3wC@^dAzR##qAp``tlVpEpua$!?`onZUvsO9W^v%YhW@s0><#`VKv}M! zZolHX;+AHULVhZ}O-J21vKn3HUI5&8IJSk(dMjf3@1)`>Yog}Z(f9`fsw{JJ)e87!SamJ#p& zv`twu564~7h{3`e9IFfh>^LZr`fN?9}qUwxlpEUuc5=_HZVACwrBvOMeA_Sg9ydlMdm7lZ>8*_W&4wKrnMfHYthvGjqz+QQ z-wwPRBsDC@8kR(A7}8>doBRsMi-cY-4>Tb5^+AgMFQf!=XHRz)t_KzF`gb=)9}BWm zJ`A3~7JU@b4MF=vKcSirf`qQl1PNVCz`kpdW@LDGJ<1o19E1Ie;jRWe?+HAQeU8n5 z<3sSjUN7B7(R%h@NNXXqQ>5U*Sn|5YX8hlb{gS#Yq63ykXl~4cj~_M)|56eE(h>NR z`Uy4ehE)C4$UYg~<&&1^W`Apd#w@6AfKc-dIbx0e)f)@aq^(17p7PKRY(o!lxT^%U z?{$IXD8wA9HnKkNd)aO776Z%R_^qyW16PA6V=#BP%Qo=IAST^7nhWn8#IYN3TpYpi zWmspp8rdg7!s66fzc!>{$^E$A;OD|^_{`s7DGB(*tAZ*woO{ebv_6gy2|aM*cW!x# z64xoP0D0Vn+!ZtM!QgXU!{4g5;}_@NQuw323=}}w2>h?=<%lwkb*vmv z5OXN>ZvTQbsx=G8>k)hEWVk2zpLi%W!b2bS6M0OoA4B`hP_nZg%rq=V4t}RE%v5d} zk2d%f4>ehd{l+v*euCM0-xI85&P;eFDo>Y1eD?5*$Q7U%*5aP|+u5m@x~M-3db?ii zpIk8+nrCOhwesGs!~K&h@2iy56P-_<2XBYN7z3_E9_a%f=z1D^ep7QvnM()AS1iI2 zU*xzEEf|r;?b{T9>s4d4nWwi&P(#2ZG9DSm@HUG7*NXVB#cv*x|DmQCOWr^~YFFTC zw`x+JOY7DeD&46Wnt(E0(i+=w!Ww;!vSGs@QkeczWAkUa6$gmcPzj&HJmylYv zAzH1y@73y?bzb@ zLVq>#gh22sv?;?~vu?Z+RNJSxDKC`+UWrA*WWgmvxa%pD1TP3Nv%WC=x}kZHry&mL zuaPFY8y!p{#JlB?zyeKY#0O?9uG}_dTQ+(d^EjVX9jJ6$GN$~W_O3oIsw(fFJ1@W} z=qOq+pxxo}jv4}*rqwV5S70m&EKRH!0UHz%@NMYLLERS6ebzL46x`HkH`6Semfp;j zt=(4JZTF63L2NaM&E#Viy*$gS%=10>-oa7Yzn(vy&j+8&=f^qs+}}NO&-tGB^E~=Prk$x4o^OFGBLNJr)aWWuqo!!(RQ36t+Luc zYxM7~jK^DNFybl>BJ5le1&s_e)<1dz(K5snO^&xG61($nqh!!lXK0XNUl-dQe{lXK zVodGEXTk=ct9kh%JKv8v;Z#y@EOs_HwA>w-;7S)W>%PnogeTru>wgbP_&?-_e*?}$(7dw14&;x{0{($&ip;m31;{zI zK%Xqe6YSWysQ>CG67A*!hzu+N7g!M}u-kXS|=5{Kuwzj3oQ&|Je2%o{rNlqIJeNY4E6R z7jpfB!CzwBM04!>fupuMaSZj%K!dNn#rwadvgEiXq3zwlnM`A|Fuo4Uunl3X?;D#B z^<03z4|+LzFdw6cs0JBssiDQu?fDzV^u5hHqUou_3=@l)ds?`f)M1=xvF+q(!zCB>9F`w7&+9lyN|i;lw${*Y1$Y*PaOYM z;>!;Y_~xi|ZX1lZ%S=IO2OMePzPah9z6&0jhS7SBz`sz!zYq!b;eQv`ig~+)hqif# zJRdI><+UkBSHqSKhjlm8u~Y~~Q_7?<&YTm=c7MfJixF&db)t!lR{L|Wz4qwd+Pi2L_vP6rG0b;o_C?|zZrf1Mdu;g z0`tsDX=EVv?dS09b7!Z`Xs$%KO70a%{AsiW^!;GG|2}w@;r%mCS9Mc0QG}V!+YZ@O zk%o9En~Y8~zu+JV(C!=`(4NI6FRwLX#5O;fhO*?D{+>Jf7|5wH2}k6wPQ+LD^)u{V zgdKEtRUbt8QHn8!MH(RIU|{WtFIixcX0*eKR2(yBX4MUa{|X8J6@K{J9hy|KlVUrm z@2;>D4oFfsH;JRLFy!W0ZmWDCt=-hGRTRrk3kj9H!W0?=WSb#*Bi$w>xm_ej;z^e>gnWhcPeI!Z}wtD!KH{LE2&>a zzLMJp`n&XZa2ACf>X{*~`DmMmdTxPFX{%`Veb;DoN7Vl!3I8G_*4O@b1ggk5^7(jz zR#Ry!2xQLDleE}BC)qgbwSSOwGn(8sDM&c?)qnz#yui^P2GAu-7qcUcF! zklz)}Aq3~F@Frcec@pmy1b%^cRUP#He$47CPSW^PVl2B6#}6J7+wCz`E5wpipk5a8 zzPQ_@z}Qong!AMqlN5xrCyInf_zm1{qX81h|MDp2nvD;nhnV;CmW-#xuy5qBZj5EJ zjnu}VVW#xtOOFbCD>8DSb*#nFhVP`hF36DATOW3V;a@D_U+jnf)5gHFn`0N+)YDW# zuuT(=`qfr(GL8JoHVwKKRpFO4MfXYJ-?aUJypzuTs7SzJwaw1f@;77lMHE|%acwTF zsVfCWPs$;0pdN_@8r*`_eR`{+CGjm-yiyA?KZV z=C?G2Tt)4aZG|7%%KJ2A!=jCby0S5=Aw8>6j0`y~cs| z^R;?wo+>Xm-xQBg-j$xe3|Z=!7;|K!<6!MR<2YEAH4w_nmKMp#FNEAb&IQL5)ipW{ zWUKM}j&D5>BGuK0?Q)#H?QkK;~KQe)zeXD)1e&haX~_j1HBxb%B%Hx~XY zCHzFNwP8G*>H->>rRUFqhz=7 z>yEE*7AK3ZGF|CO9cuHWMjrQ^11^VAk9(o_p~X7RuPnCp$JDWPmb&L_k2$tFe(k6= z#|1Qi;7W{*U zNG(4ZGd_~IN@($G>9_aC)t#>{s{38-DMzE@tj6R#U@SUYMOt+Oh>1i!%|B;MZU0fH zLw+ActDP9R+p!NeTS-#9(1Fs^21e)3QzEImRe5ZARb%Bo7uHv~lK46y0Wa3)=0KHp_A^V|wZ zc(RLMTZm3Aq#bdlSz~HH>RYi z{mzxq3ad9wU3qThq{5>4wW;3u=Zf|eeGEMSqY-ysnnvKiO2U7YAO253ifSj7<8%=N z#_a~uLJa=x{?_Brx^kGS@z_mj)LZsK4%4!FtZ{(K9>Kdk`lHeAg6(T_?{56pI zcx+(T#|qY9ba~$5Kkz`pJLyv=v{>xIDlhv9%1w9kCp;ZVwm|kE$d@KxW{pC31pccf z{8#(o?~m#25~i^h{vLx}pAC&~_PKTZMulaqmTJm^jvsp*(QhV(%!xvrMd3QMmLyDY zX7iS%mNg`yN1%C6al1P5Tf)yYjW>Pm(U(qGOmY2Am}dRdlTb|(+J%F0kpCfP$frQ3 z3w=U3)}Ss737kDDB4C$l*$S#tU~6EUpu~D|EaWBLfD6sGy?xJ z3I8%b{O!1Un_GD1_X=pqVBRfUKu#UPzOVm1bKU*LkZYe`e}8Puwa+V=Ywt^x*WUjT zdqe)eR|z96{`Vo*-G^Uye-6JFlV%%36TT#C?It?E%`Gij%VuY1EM-k=%hy+}Eh{S# z*Lr8qN=r?dNr-X7`t@teS@=fpH3I)N68>wDWZ(K9O}(5KDSrM`Kffp=CG$|zfj<7( ze*W92KNGo`iC8l6b|&7^V8X6Z6cZ&w4yMo>hAzBm^b;7uL@g(Q>TOB`VS>go3Wb7l zBN!f)IvVh+qZy)5MJp8Q=m16$sIE~45v@jrgQOJZK(#_)pw1TRs2Ppw5c3O0tqI4L z&SVn9>XI#%S}XelkTtSCc89Fv%s$mXWslWk(*8$&31G)~sC zZe2;$da|;-l#-~2quf$X{nIHX%MyupoJ@IseqWfsn*mhms_@2@5zfU9D4-@|HXvYkq?FWw3{oj5^EaLIS zF@QRj=sUx6Mc=xP*yCqPy+W{}kPlk3LGzD=90ntScca)~;Qj^6;jV zS(V9iurozw&X_fWx@#<@4_9DJK;KW_@CeJ!VpEb+#BPoTjmm2T{`5Q_Lr5i3>#P3( zBro@bcm5mSplnco=a$;dg6wbY-e5cpe+U}w`|^$Ky7k4S-;giIK`(%4eo=Z#4)fjrtP7rO z$djK0(Q}v|LALlcDqo5#1ad}t&@9lOK{ZtF09OFyPd)*?2MPw!`|HV$u;m~FXklgs z(V&dsRmJ`)qJ3EbT@j#XKq$o8TDZY*!$1e|d_G*dKRFrt;#Js}R9LUqHKgLY?k7pJ zJEE+jo*`CSNWW{eUL){dC*i*i3HIS%13gPPyc0Ju;L{}saK@9c^6o3vZGv4=KBe=L zKBjo+TF1UVWcn^SqiEi4t$JgEm1@zZxfxZxInmu7WsR%!*WtuqyY9gutPpp{;J8M zj;LfZ7kp{>rm%nWO`dimA5|$ME^C(ZX*(qBXvLnUYXtu5CH&EJ598nOVPcs@8=-+6 z>mks!JF?DrIc25t8srS8Di#Dm-jlA(jB(nzfb77W0yT7V=c-b78~Y)RrL3Uq^nb^B zo#090Z)%|@(#)hvwk8n9s?9npw6cnE+^HSjfjSgk6xrFkzujzrYPWlzEhzkEKBgw>k(=xl1&KA%^McTwV<9jBF= zyl*WyyujOY%1fq22rc>>3jYlf{u})8k0L>F3N~AP2id-AiXjY|ImtAQ5E>DJQ=qW3 zU}NTwUCBUWMrQ^`$n;=msEH(f?ezA%>TN+TKI6T*)<+V*7si$|x2INw5@t4M`Y#We z-YSqpr|=`nRyL0{0USK#P55yxGDq9IyRfw|B8TXytIN@4NiO{#o^$nonCO@O-z@s2 z|2K(#>HlQWFa4h+`nyr4NO$^`iROtpI`PaXfi6p|AmnO6f16BLNQLIIpc@MRjS~JF z{qT=R9wdY2fDE84P%a2Zg>%t$=UPD+0*ZQH?a=qy-Mj>Q!t?}&t2x&E%i{;ukXxgL z7&D392MrR9JZENN8+etAN!v&+I9S+;xYO7RDeh(bb0tMV1>|{CV^{%`pAm+gCDQ!{ zWW0l&Oxl~K3l4nMt!`)FHT#c7=z%y}uRPXhV|Ec}nnu3>y;J7~1G&`|*ePg8s-%4d zqd!u(x{`Fi2`xs@BRxRZ`bGVv$rEy^-r2p}8SE0BTWaT~kw%3!cH8ktSTd~^{$&31 zjb;CJlZ5{!Kl~e3{n<=nb%pb|uI133hBh|c+(H&x<{>wJ9J zKfCkYVSi@l;IO~C^A)qiUDtU`_U-LFD*ImSJR^v;{+B;vAeJ^+ZR`%_2y&(I( z=p-|OoQJ5UnIx6du0NQwGnML`WnS574u&*nGTC27x-$n`xMC;;D-Ky-m`qabjNTqi zy5k4--1CCsQoyQ@u{WuhV!T;kq9E)%imws%*z&kRFd%9DMGtuLBz>R<#fqYasUp5lq)lL;8 zbeUHq6e3N~u`zfMvC-U5Mq7Nb-yXHzJdx3rcs%}G-whzPVXja5wNb0hx)Pk)jg2%F zn5Pzcd#pn=PWmP7;9bQR;0x)O-Bd-o|A1H@HcwRQN`CKwe%GNiveWYr$_kZYF7|wf z@x%G$kX#lPcjikaNGSCzk)8{8bQXE$jOqn|5%^b2_*eVk?_N&zdZ|2$R57QVUN?zi z$@#@>GVJyB_quJU?KC%g-H%5dS`Jyy1mgU`O*ob`Ek@<)zNPUr%Od9?6wT3ljllm=3I9itWMBQ4^7P%l z7odw#>@y8&1MM@nf#xX)c?@&{Gys~X#69Q)XaMvP#`gq$tQKcN2*wESAgmwMH=O62 z-9vtPZWHsoQ$}Y?UxoW_xvo~Ye~l5F4{zuJ zy)xgjzC8ClnJ>@%svnW%q!~aXaeB#A?YLoLR zrSDF6f}Bscx=;D>#fyAz$$V4$ST`&4vF?BP@r8?gr)9n|eG}X_%X|@TFG8gjheeBX zesSbHigj-<-Wk68zBd^BRHNTGcavLBG+Ctv(mlKXb`-ZqLGMtPmN~e$%Vm$2%Pgz| z^)@TEZ>?8hHQ^>vT6}7xJ?0HZW5jt|qvJAZ+5$~Qi3(w7%ngPA;}ZUl`{93#fo*1e zj^TU-PS+%Aps`sY_3tL=MiQp0;vr4jWqpg;&!>>W41Fo-{5hp3;JF&T}T^&`S}Nqa+i{ z2)ip#`7ty~YpA|btW4MxbEGo>z4APOA+QfLQvsU*Zk$LC7O-An-oXdvG_IORMIlG+ zHKP50Lc;$Eq<9PHe(&}RMH!8-n)*RhgLsNYd(t)N^%C>}wEZXC){Q2MQq;!;SsY-? z2$5F3fh0~4?9ms<1W4Wl3p#6*ajbJp&KQHP(CfMSx78YsnfK%mW2RcEUY}@hsCf+&QKmLE297`11p71N1+951qzN0< zsiqYu6QPAH26V6^0J*g$VE&!ZR8Nw=!MPJ!DpcNsoI0mNonlZ!*QZ;?AM0ALg1)S1 z{snAVkm;cU&Y64Zd(a^?imws)KPln=B+~4|pVmj3b8EcnsW`pEkr-~%PaYcL@ODGT zz7IX6j9yP~3;(!`owRmqg1(Jy$Ds|>`MAs6M(2?AE@jd(oWZqVRUid3HR9aode~gJ zXs@^~aKVuu*<`$_<9SGfzu-EgJL6yyerz+70?Ez?v`Kv<#M?@tAv8s__CdM7{_I}p zhR+LJa89My*_gxiCS3*5V_wkxkKR>$k}b4S&E;2S!v^7s5%vWRS(uktmpW;J(;(`X zj}habG2B6I7LMX;1pb>P{5SjI@1I{9=YVI@jI^G@K*9iRW68?51 z+gJbnB0{VU-VN(?==Gd8VUazB-w}m)zo9)JbZaYAJd#=WoG^dPZm`0nmJ5%?E>cHbh@A8 zcjaOQh1zOg49WxDoewe*k`MVm6X>_p<|wYoa2eT8We^Ud4|y5%&j`Y5q384GF7Yc2 lDuejUF1yz;I#H%BrV;Jsc|@BI|4b!*huS&(|4ZEe`yVoxfoK2# diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index 1c2a198..f34b749 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -4,8 +4,8 @@ set(LWIP_ROOT ${PICO_SDK_PATH}/lib/lwip) function(make_firmware board board_def) pico_sdk_init() add_executable(${board} - main.c buttons.c rgb.c save.c config.c setup.c - usb_descriptors.c) + main.c touch.c rgb.c save.c config.c cmd.c + mpr121.c usb_descriptors.c) target_compile_definitions(${board} PUBLIC ${board_def}) pico_enable_stdio_usb(${board} 1) pico_enable_stdio_uart(${board} 0) @@ -21,13 +21,13 @@ function(make_firmware board board_def) target_link_libraries(${board} PRIVATE pico_multicore pico_stdlib hardware_pio hardware_pwm hardware_flash hardware_adc hardware_i2c hardware_watchdog - tinyusb_device tinyusb_board pico-mpr121) + tinyusb_device tinyusb_board) pico_add_extra_outputs(${board}) add_custom_command(TARGET ${board} POST_BUILD - COMMAND cp ${board}.uf2 /mnt/d/Code/iidx_pico/firmware/build) + COMMAND cp ${board}.uf2 ${CMAKE_CURRENT_LIST_DIR}/..) endfunction() -make_firmware(mai_pico BOARD_IIDX_PICO) +make_firmware(mai_pico BOARD_MAI_PICO) diff --git a/firmware/src/board_defs.h b/firmware/src/board_defs.h index 899dc34..e991554 100644 --- a/firmware/src/board_defs.h +++ b/firmware/src/board_defs.h @@ -1,32 +1,19 @@ /* - * IIDX Controller Board Definitions + * Mai Controller Board Definitions * WHowe */ -#if defined BOARD_IIDX_PICO -/* List of button pins */ -#define BUTTON_DEF { 8, 7, 6, 5, 4, 3, 2, 12, 11, 10, 9, 1, 0 } +#if defined BOARD_MAI_PICO -#define BUTTON_RGB_PIN 13 -#define BUTTON_RGB_ORDER GRB // or RGB +#define I2C_PORT i2c0 +#define I2C_SDA 16 +#define I2C_SCL 17 +#define I2C_FREQ 733*1000 -#define BUTTON_RGB_NUM 11 -#define BUTTON_RGB_MAP { 6, 0, 5, 1, 4, 2, 3, 7, 8, 9, 10} - - -#define TT_RGB_PIN 28 -#define TT_RGB_ORDER GRB // or RGB - -#define TT_AS5600_ANALOG 26 -#define TT_AS5600_SCL 27 -#define TT_AS5600_SDA 26 -#define TT_AS5600_I2C i2c1 - -// Alternative I2C pins -//#define TT_AS5600_SCL 21 -//#define TT_AS5600_SDA 20 -//#define TT_AS5600_I2C i2c0 +#define RGB_PIN 2 +#define RGB_ORDER GRB // or RGB +#define NKRO_KEYMAP "1aqz2swx3dec4frv5gtb6hyn7jum8ki90olp,." #else #endif diff --git a/firmware/src/buttons.c b/firmware/src/buttons.c deleted file mode 100644 index 10d0033..0000000 --- a/firmware/src/buttons.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Controller Buttons - * WHowe - * - * A button consists of a switch and an LED - */ - -#include "buttons.h" - -#include -#include - -#include "bsp/board.h" -#include "hardware/gpio.h" - -#include "board_defs.h" - -static const uint8_t BUTTON_GPIOS[] = BUTTON_DEF; -#define BUTTON_NUM (sizeof(BUTTON_GPIOS)) - -static bool sw_val[BUTTON_NUM]; /* true if pressed */ -static uint64_t sw_freeze_time[BUTTON_NUM]; - -#define LIMIT_MAX(a, max, def) { if (a > max) a = def; } - -void button_init() -{ - for (int i = 0; i < BUTTON_NUM; i++) { - sw_val[i] = false; - sw_freeze_time[i] = 0; - int8_t gpio = BUTTON_GPIOS[i]; - gpio_init(gpio); - gpio_set_function(gpio, GPIO_FUNC_SIO); - gpio_set_dir(gpio, GPIO_IN); - gpio_pull_up(gpio); - } -} - -uint8_t button_num() -{ - return BUTTON_NUM; -} - -uint8_t button_gpio(uint8_t id) -{ - return BUTTON_GPIOS[id]; -} - -/* If a switch flips, it freezes for a while */ -#define DEBOUNCE_FREEZE_TIME_US 5000 -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_GPIOS[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; - } - } - - return buttons; -} diff --git a/firmware/src/buttons.h b/firmware/src/buttons.h deleted file mode 100644 index e5c5019..0000000 --- a/firmware/src/buttons.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Controller Buttons - * WHowe - */ - -#ifndef BUTTONS_H -#define BUTTONS_H - -#include - -void button_init(); -uint8_t button_num(); -uint8_t button_gpio(uint8_t id); - -uint16_t button_read(); - -#endif diff --git a/firmware/src/cmd.c b/firmware/src/cmd.c new file mode 100644 index 0000000..b912ded --- /dev/null +++ b/firmware/src/cmd.c @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include + +#include "pico/stdio.h" +#include "pico/stdlib.h" + +#include "config.h" +#include "touch.h" +#include "save.h" + +#define SENSE_LIMIT_MAX 9 +#define SENSE_LIMIT_MIN -9 + +#define MAX_COMMANDS 20 +#define MAX_PARAMETERS 5 +#define MAX_PARAMETER_LENGTH 20 + +const char *mai_prompt = "mai_pico>"; + +typedef void (*cmd_handler_t)(int argc, char *argv[]); + +static const char *commands[MAX_COMMANDS]; +static cmd_handler_t handlers[MAX_COMMANDS]; +static int num_commands = 0; + +static void register_command(const char *cmd, cmd_handler_t handler) +{ + if (num_commands < MAX_COMMANDS) { + commands[num_commands] = cmd; + handlers[num_commands] = handler; + num_commands++; + } +} + +// return -1 if not matched, return -2 if ambiguous +static int 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 (strncmp(str[i], prefix, strlen(prefix)) == 0) { + if (found) { + return -2; + } + found = true; + match = i; + } + } + + return match; +} + +static void handle_help(int argc, char *argv[]) +{ + printf("Available commands:\n"); + for (int i = 0; i < num_commands; i++) { + printf("%s\n", commands[i]); + } +} + +static void disp_colors() +{ + printf("[Colors]\n"); + printf(" Key on: %06x, off: %06x\n", + mai_cfg->colors.key_on, mai_cfg->colors.key_off); +} + +static void disp_style() +{ + printf("[Style]\n"); + printf(" Key: %d, Level: %d\n", mai_cfg->style.key, mai_cfg->style.level); +} + +static void disp_sense() +{ + printf("[Sense]\n"); + printf(" Filter: %d, %d\n", mai_cfg->sense.filter >> 4, mai_cfg->sense.filter & 0xf); + printf(" Sensitivity (global: %+d):\n", mai_cfg->sense.global); + printf(" | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|\n"); + printf(" ---------------------------------------------------\n"); + printf(" A |"); + for (int i = 0; i < 16; i++) { + printf("%+2d|", mai_cfg->sense.keys[i * 2]); + } + printf("\n B |"); + for (int i = 0; i < 16; i++) { + printf("%+2d|", mai_cfg->sense.keys[i * 2 + 1]); + } + printf("\n"); + printf(" Debounce (touch, release): %d, %d\n", + mai_cfg->sense.debounce_touch, mai_cfg->sense.debounce_release); +} + +static void disp_hid() +{ + printf("[HID]\n"); + printf(" Joy: %s, NKRO: %s.\n", + mai_cfg->hid.joy ? "on" : "off", + mai_cfg->hid.nkro ? "on" : "off" ); +} + +void handle_display(int argc, char *argv[]) +{ + const char *usage = "Usage: display [colors|style|tof|sense|hid]\n"; + if (argc > 1) { + printf(usage); + return; + } + + if (argc == 0) { + disp_colors(); + disp_style(); + disp_sense(); + disp_hid(); + return; + } + + const char *choices[] = {"colors", "style", "sense", "hid"}; + switch (match_prefix(choices, 5, argv[0])) { + case 0: + disp_colors(); + break; + case 1: + disp_style(); + break; + case 2: + disp_sense(); + break; + case 3: + disp_hid(); + 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_fps(int argc, char *argv[]) +{ + printf("FPS: core 0: %d, core 1: %d\n", fps[0], fps[1]); +} + +static void handle_hid(int argc, char *argv[]) +{ + const char *usage = "Usage: hid \n"; + if (argc != 1) { + printf(usage); + return; + } + + const char *choices[] = {"joy", "nkro", "both"}; + int match = match_prefix(choices, 3, argv[0]); + if (match < 0) { + printf(usage); + return; + } + + mai_cfg->hid.joy = ((match == 0) || (match == 2)) ? 1 : 0; + mai_cfg->hid.nkro = ((match == 1) || (match == 2)) ? 1 : 0; + config_changed(); + disp_hid(); +} + +static int 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(param[i])) { + return -1; + } + result = result * 10 + param[i] - '0'; + } + return result; +} + +static void handle_filter(int argc, char *argv[]) +{ + const char *usage = "Usage: filter \n" + " first, second: 0..3\n"; + if ((argc < 2) || (argc > 2)) { + printf(usage); + return; + } + + int ffi = extract_non_neg_int(argv[0], 0); + int sfi = extract_non_neg_int(argv[1], 0); + + if ((ffi < 0) || (ffi > 3) || (sfi < 0) || (sfi > 3)) { + printf(usage); + return; + } + + mai_cfg->sense.filter = (ffi << 4) | sfi; + + touch_update_config(); + config_changed(); + disp_sense(); +} + +static uint8_t *extract_key(const char *param) +{ + int len = strlen(param); + + int offset; + if (toupper(param[len - 1]) == 'A') { + offset = 0; + } else if (toupper(param[len - 1]) == 'B') { + offset = 1; + } else { + return NULL; + } + + int id = extract_non_neg_int(param, len - 1) - 1; + if ((id < 0) || (id > 15)) { + return NULL; + } + + return &mai_cfg->sense.keys[id * 2 + offset]; +} + +static void sense_do_op(int8_t *target, char op) +{ + if (op == '+') { + if (*target < SENSE_LIMIT_MAX) { + (*target)++; + } + } else if (op == '-') { + if (*target > SENSE_LIMIT_MIN) { + (*target)--; + } + } else if (op == '0') { + *target = 0; + } +} + +static void handle_sense(int argc, char *argv[]) +{ + const char *usage = "Usage: sense [key|*] <+|-|0>\n" + "Example:\n" + " >sense +\n" + " >sense -\n" + " >sense 1A +\n" + " >sense 13B -\n"; + " >sense * 0\n"; + if ((argc < 1) || (argc > 2)) { + printf(usage); + return; + } + + const char *op = argv[argc - 1]; + if ((strlen(op) != 1) || !strchr("+-0", op[0])) { + printf(usage); + return; + } + + if (argc == 1) { + sense_do_op(&mai_cfg->sense.global, op[0]); + } else { + if (strcmp(argv[0], "*") == 0) { + for (int i = 0; i < 32; i++) { + sense_do_op(&mai_cfg->sense.keys[i], op[0]); + } + } else { + uint8_t *key = extract_key(argv[0]); + if (!key) { + printf(usage); + return; + } + sense_do_op(key, op[0]); + } + } + + touch_update_config(); + config_changed(); + disp_sense(); +} + +static void handle_debounce(int argc, char *argv[]) +{ + const char *usage = "Usage: debounce [release]\n" + " touch, release: 0..7\n"; + if ((argc < 1) || (argc > 2)) { + printf(usage); + return; + } + + int touch = mai_cfg->sense.debounce_touch; + int release = mai_cfg->sense.debounce_release; + if (argc >= 1) { + touch = extract_non_neg_int(argv[0], 0); + } + if (argc == 2) { + release = extract_non_neg_int(argv[1], 0); + } + + if ((touch < 0) || (release < 0) || + (touch > 7) || (release > 7)) { + printf(usage); + return; + } + + mai_cfg->sense.debounce_touch = touch; + mai_cfg->sense.debounce_release = release; + + touch_update_config(); + config_changed(); + disp_sense(); +} + +static void handle_save() +{ + save_request(true); +} + +static void handle_factory_reset() +{ + config_factory_reset(); + printf("Factory reset done.\n"); +} + +void cmd_init() +{ + register_command("?", handle_help); + register_command("display", handle_display); + register_command("fps", handle_fps); + register_command("hid", handle_hid); + register_command("filter", handle_filter); + register_command("sense", handle_sense); + register_command("debounce", handle_debounce); + register_command("save", handle_save); + register_command("factory", config_factory_reset); +} + +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 = match_prefix(commands, num_commands, cmd); + if (match == -2) { + printf("Ambiguous command.\n"); + return; + } + if (match == -1) { + printf("Unknown command.\n"); + handle_help(0, NULL); + return; + } + + handlers[match](argc, argv); +} + +void cmd_run() +{ + int c = getchar_timeout_us(0); + if (c == EOF) { + 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(mai_prompt); +} diff --git a/firmware/src/cmd.h b/firmware/src/cmd.h new file mode 100644 index 0000000..d50c77d --- /dev/null +++ b/firmware/src/cmd.h @@ -0,0 +1,13 @@ +/* + * Mai Controller Command Line + * WHowe + */ + +#ifndef CMD_H +#define CMD_H + +void cmd_init(); +void cmd_run(); +void fps_count(int core); + +#endif diff --git a/firmware/src/config.c b/firmware/src/config.c index 1e6e849..1929063 100644 --- a/firmware/src/config.c +++ b/firmware/src/config.c @@ -1,68 +1,63 @@ /* - * Controller Config Data + * Controller Config and Runtime Data * WHowe * * Config is a global data structure that stores all the configuration + * Runtime is something to share between files. */ #include "config.h" #include "save.h" -iidx_cfg_t *iidx_cfg; +mai_cfg_t *mai_cfg; -static iidx_cfg_t default_cfg = { - .key_off = { {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, - .key_on = { {40,40,40}, {40,40,40}, {40,40,40}, {40,40,40}, {40,40,40}, {40,40,40}, - {40,40,40}, {40,40,40}, {40,40,40}, {40,40,40}, {40,40,40}, +static mai_cfg_t default_cfg = { + .colors = { + .key_on = 0x00FF00, + .key_off = 0x000000, }, - .tt_led = { - .start = 0, - .num = 24, - .effect = 0, - .param = 0, - .mode = 0, + .style = { + .key = 0, + .level = 7, }, - .tt_sensor = { - .mode = 2, - .deadzone = 1, - .ppr = 1, + .sense = { + .filter = 0x11, + .debounce_touch = 1, + .debounce_release = 2, + }, + .hid = { + .joy = 1, + .nkro = 0, }, - .effects = { - .e1 = 255, - .e2 = 128, - .e3 = 128, - .e4 = 128, - }, - .level = 128, - .konami = false, }; +mai_runtime_t *mai_runtime; + static void config_loaded() { - if (iidx_cfg->tt_led.num == 0) { - iidx_cfg->tt_led.num = 24; + if (mai_cfg->style.level > 10) { + mai_cfg->style.level = default_cfg.style.level; config_changed(); } - if ((iidx_cfg->tt_led.start > 8) || - (iidx_cfg->tt_led.start + iidx_cfg->tt_led.num > 128)) { - iidx_cfg->tt_led.start = 0; - iidx_cfg->tt_led.num = 24; + if ((mai_cfg->sense.filter & 0x0f) > 3 || + ((mai_cfg->sense.filter >> 4) & 0x0f) > 3) { + mai_cfg->sense.filter = default_cfg.sense.filter; config_changed(); } - if (iidx_cfg->tt_sensor.deadzone > 2) { - iidx_cfg->tt_sensor.deadzone = 0; + if ((mai_cfg->sense.global > 9) || (mai_cfg->sense.global < -9)) { + mai_cfg->sense.global = default_cfg.sense.global; config_changed(); } - if (iidx_cfg->tt_led.mode > 2) { - iidx_cfg->tt_led.mode = 0; - config_changed(); + for (int i = 0; i < 32; i++) { + if ((mai_cfg->sense.keys[i] > 9) || (mai_cfg->sense.keys[i] < -9)) { + mai_cfg->sense.keys[i] = default_cfg.sense.keys[i]; + config_changed(); + } } - if (iidx_cfg->tt_sensor.mode > 3) { - iidx_cfg->tt_sensor.mode = 2; - config_changed(); - } - if (iidx_cfg->tt_sensor.ppr > 3) { - iidx_cfg->tt_sensor.ppr = 1; + if ((mai_cfg->sense.debounce_touch > 7) | + (mai_cfg->sense.debounce_release > 7)) { + mai_cfg->sense.debounce_touch = default_cfg.sense.debounce_touch; + mai_cfg->sense.debounce_release = default_cfg.sense.debounce_release; config_changed(); } } @@ -74,11 +69,11 @@ void config_changed() void config_factory_reset() { - *iidx_cfg = default_cfg; + *mai_cfg = default_cfg; save_request(true); } void config_init() { - iidx_cfg = (iidx_cfg_t *)save_alloc(sizeof(iidx_cfg), &default_cfg, config_loaded); + mai_cfg = (mai_cfg_t *)save_alloc(sizeof(*mai_cfg), &default_cfg, config_loaded); } diff --git a/firmware/src/config.h b/firmware/src/config.h index 9d7353c..94e1cd4 100644 --- a/firmware/src/config.h +++ b/firmware/src/config.h @@ -9,38 +9,34 @@ #include #include -typedef struct __attribute ((packed)) { - uint8_t h; // hue; - uint8_t s; // saturation; - uint8_t v; // value; -} hsv_t; +typedef struct __attribute__((packed)) { + struct { + uint32_t key_on; + uint32_t key_off; + } colors; + struct { + uint8_t key; + uint8_t level; + } style; + struct { + int8_t filter; + int8_t global; + uint8_t debounce_touch; + uint8_t debounce_release; + int8_t keys[34]; + } sense; + struct { + uint8_t joy : 4; + uint8_t nkro : 4; + } hid; +} mai_cfg_t; -typedef struct __attribute ((packed)) { - hsv_t key_off[11]; - hsv_t key_on[11]; - struct { - uint8_t start; - uint8_t num; - uint8_t effect; - uint8_t param; - uint8_t mode; /* 0: on, 1: reversed, 2: off */ - } tt_led; - struct { - uint8_t mode; /* 0: analog, 1: analog reversed, 2: i2c, 3: i2c reversed */ - uint8_t deadzone; /* only for analog */ - uint8_t ppr; /* 0: 256, 1: 128, 2: 96, 3: 64, other: 256 */ - } tt_sensor; - struct { - uint8_t e1; - uint8_t e2; - uint8_t e3; - uint8_t e4; - } effects; - uint8_t level; /* led brightness limit */ - bool konami; /* konami spoof */ -} iidx_cfg_t; +typedef struct { + uint16_t fps[2]; +} mai_runtime_t; -extern iidx_cfg_t *iidx_cfg; +extern mai_cfg_t *mai_cfg; +extern mai_runtime_t *mai_runtime; void config_init(); void config_changed(); // Notify the config has changed diff --git a/firmware/src/mai_pico.code-workspace b/firmware/src/mai_pico.code-workspace deleted file mode 100644 index 0de5248..0000000 --- a/firmware/src/mai_pico.code-workspace +++ /dev/null @@ -1,38 +0,0 @@ -{ - "folders": [ - { - "path": "../.." - }, - { - "path": "../../../pico-examples" - } - ], - "settings": { - "files.associations": { - "turntable.h": "c", - "stdint.h": "c", - "_default_types.h": "c", - "limits.h": "c", - "cdefs.h": "c", - "gpio.h": "c", - "pico.h": "c", - "flash.h": "c", - "bootrom.h": "c", - "multicore.h": "c", - "stdbool.h": "c", - "sync.h": "c", - "stdio.h": "c", - "config.h": "c", - "stdlib.h": "c", - "setup.h": "c", - "config_autogen.h": "c", - "board_defs.h": "c", - "rgb.h": "c", - "type_traits": "c", - "cmath": "c", - "*.tcc": "c", - "cinttypes": "c", - "cstdlib": "c" - } - } -} \ No newline at end of file diff --git a/firmware/src/main.c b/firmware/src/main.c index 318a95a..12e02b5 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -3,202 +3,183 @@ * WHowe */ +#include #include #include +#include "pico/stdio.h" +#include "pico/stdlib.h" #include "bsp/board.h" #include "pico/multicore.h" #include "pico/bootrom.h" -#include "pico/stdio.h" -#include "hardware/watchdog.h" -#include "hardware/gpio.h" -#include "hardware/adc.h" -#include "hardware/i2c.h" -#include "mpr121.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" -/* Measure the time of a function call */ -#define RUN_TIME(func) \ - { uint64_t _t = time_us_64(); func; \ - printf(#func ":%lld\n", time_us_64() - _t); } +#include "board_defs.h" -struct { - uint16_t buttons; - uint8_t joy[6]; -} hid_report; +#include "save.h" +#include "config.h" +#include "cmd.h" + +#include "touch.h" +#include "rgb.h" + +struct __attribute__((packed)) { + uint16_t buttons; // 16 buttons; see JoystickButtons_t for bit mapping + uint8_t HAT; // HAT switch; one nibble w/ unused nibble + uint32_t axis; // slider touch data + uint8_t VendorSpec; +} hid_joy; + +struct __attribute__((packed)) { + uint8_t modifier; + uint8_t keymap[15]; +} hid_nkro, sent_hid_nkro; void report_usb_hid() { if (tud_hid_ready()) { - hid_report.joy[2] = 0; - hid_report.joy[3] = 64; - hid_report.joy[4] = 128; - hid_report.joy[5] = 192; - tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_report, sizeof(hid_report)); + hid_joy.HAT = 0; + hid_joy.VendorSpec = 0; + if (mai_cfg->hid.joy) { + tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_joy, sizeof(hid_joy)); + } + if (mai_cfg->hid.nkro && + (memcmp(&hid_nkro, &sent_hid_nkro, sizeof(hid_nkro)) != 0)) { + sent_hid_nkro = hid_nkro; + tud_hid_n_report(0x02, 0, &sent_hid_nkro, sizeof(sent_hid_nkro)); + } } } -static bool request_core1_pause = false; - -static void pause_core1(bool pause) +static void gen_joy_report() { - request_core1_pause = pause; - if (pause) { - sleep_ms(5); /* wait for any IO ops to finish */ + hid_joy.axis = 0; + for (int i = 0; i < 16; i++) { + if (touch_touched(i * 2)) { + hid_joy.axis |= 1 << (30 - i * 2); + } + if (touch_touched(i * 2 + 1)) { + hid_joy.axis |= 1 << (31 - i * 2); + } + + } + hid_joy.axis ^= 0x80808080; // some magic number from CrazyRedMachine + hid_joy.buttons = 0x0; +} + +const uint8_t keycode_table[128][2] = { HID_ASCII_TO_KEYCODE }; +const char keymap[38 + 1] = NKRO_KEYMAP; // 32 keys, 6 air keys, 1 terminator +static void gen_nkro_report() +{ + for (int i = 0; i < 32; i++) { + uint8_t code = keycode_table[keymap[i]][1]; + uint8_t byte = code / 8; + uint8_t bit = code % 8; + if (touch_touched(i)) { + hid_nkro.keymap[byte] |= (1 << bit); + } else { + hid_nkro.keymap[byte] &= ~(1 << bit); + } + } + for (int i = 0; i < 6; i++) { + uint8_t code = keycode_table[keymap[32 + i]][1]; + uint8_t byte = code / 8; + uint8_t bit = code % 8; + if (hid_joy.buttons & (1 << i)) { + hid_nkro.keymap[byte] |= (1 << bit); + } else { + hid_nkro.keymap[byte] &= ~(1 << bit); + } } } +static uint64_t last_hid_time = 0; + +static void run_lights() +{ + uint64_t now = time_us_64(); + if (now - last_hid_time < 1000000) { + return; + } + + const uint32_t colors[] = {0x000000, 0x0000ff, 0xff0000, 0xffff00, + 0x00ff00, 0x00ffff, 0xffffff}; + + for (int i = 0; i < 15; i++) { + int x = 15 - i; + uint8_t r = (x & 0x01) ? 10 : 0; + uint8_t g = (x & 0x02) ? 10 : 0; + uint8_t b = (x & 0x04) ? 10 : 0; + rgb_gap_color(i, rgb32(r, g, b, false)); + } + + for (int i = 0; i < 16; i++) { + bool r = touch_touched(i * 2); + bool g = touch_touched(i * 2 + 1); + rgb_set_color(30 - i * 2, rgb32(r ? 80 : 0, g ? 80 : 0, 0, false)); + } +} + +static mutex_t core1_io_lock; static void core1_loop() { -} - - -// I2C definitions: port and pin numbers -#define MPR121_PORT i2c0 -#define MPR121_SDA 16 -#define MPR121_SCL 17 - -// MPR121 I2C definitions: address and frequency. -#define MPR121_ADDR 0x5A -#define MPR121_I2C_FREQ 400000 - -// Touch and release thresholds. -#define MPR121_TOUCH_THRESHOLD 16 -#define MPR121_RELEASE_THRESHOLD 10 - -#define GP2Y_PORT i2c0 -#define GP2Y_I2C_FREQ 200000 -#define GP2Y_SDA 16 -#define GP2Y_SCL 17 - -static void core0_loop_gp2y() -{ - i2c_init(GP2Y_PORT, GP2Y_I2C_FREQ); - gpio_set_function(GP2Y_SDA, GPIO_FUNC_I2C); - gpio_set_function(GP2Y_SCL, GPIO_FUNC_I2C); - gpio_pull_up(GP2Y_SDA); - gpio_pull_up(GP2Y_SCL); - - while(1) { - tud_task(); - - hid_report.buttons = 0xcccc; - report_usb_hid(); - - uint8_t reg = 0x00; - i2c_write_blocking(GP2Y_PORT, 0x40, ®, 1, true); - - uint16_t v = 0x00; - i2c_read_blocking(GP2Y_PORT, 0x5e, (uint8_t *)&v, 2, false); - - printf("%4x\n", v); - - sleep_ms(1); - } -} - - -static void core0_loop_adc() -{ - adc_init(); - adc_gpio_init(28); - adc_select_input(2); - - while(1) { - tud_task(); - - hid_report.buttons = 0xcccc; - report_usb_hid(); - - uint16_t result = adc_read(); - printf("%6o\n", result); - sleep_ms(1); - } -} - -static void core0_loop_mpr121() -{ - // Initialise I2C. - i2c_init(MPR121_PORT, MPR121_I2C_FREQ); - gpio_set_function(MPR121_SDA, GPIO_FUNC_I2C); - gpio_set_function(MPR121_SCL, GPIO_FUNC_I2C); - gpio_pull_up(MPR121_SDA); - gpio_pull_up(MPR121_SCL); - - struct mpr121_sensor mpr121[3]; - for (int m = 0; m < 3; m++) - { - mpr121_init(MPR121_PORT, MPR121_ADDR + m, mpr121 + m); - mpr121_set_thresholds(MPR121_TOUCH_THRESHOLD, - MPR121_RELEASE_THRESHOLD, mpr121 + m); - // Enable only one touch sensor (electrode 0). - mpr121_enable_electrodes(12, mpr121 + m); - } - - int16_t baseline[34] = {0}; - - uint32_t counter[34] = {0}; - for (int c = 0; c < 1000; c++) { - tud_task(); - hid_report.buttons = 0xcccc; - report_usb_hid(); - for (int i = 0; i < 34; i++) { - int16_t touch_data; - mpr121_baseline_value(i % 12, &touch_data, mpr121 + i / 12); - counter[i] += touch_data; + while (1) { + if (mutex_try_enter(&core1_io_lock, NULL)) { + run_lights(); + rgb_update(); + mutex_exit(&core1_io_lock); } - } - - for (int i = 0; i < 34; i++) { - baseline[i] = counter[i] / 1000; - } - - while(1) { - tud_task(); - - hid_report.buttons = 0xcccc; - report_usb_hid(); - - for (int i = 0; i < 34; i++) { - int16_t touch_data; - mpr121_baseline_value(i % 12, &touch_data, mpr121 + i / 12); - int16_t display_data = (baseline[i] - touch_data) / 4; - if (display_data) { - printf("%2d", display_data); - } else { - printf(" "); - } - printf("%c", i % 12 == 11 ? ':' : ' '); - } - printf("\n"); + fps_count(1); + sleep_ms(1); } } static void core0_loop() { - core0_loop_mpr121(); + while(1) { + cmd_run(); + save_loop(); + fps_count(0); + + touch_update(); + + gen_joy_report(); + gen_nkro_report(); + report_usb_hid(); + tud_task(); + } } void init() { + sleep_ms(100); + set_sys_clock_khz(150000, true); board_init(); tusb_init(); - stdio_init_all(); + config_init(); + mutex_init(&core1_io_lock); + save_init(0xca34cafe, &core1_io_lock); + + touch_init(); + rgb_init(); + + cmd_init(); } int main(void) { - sleep_ms(1000); - init(); - //multicore_launch_core1(core1_loop); - + multicore_launch_core1(core1_loop); core0_loop(); - return 0; } @@ -209,6 +190,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; } @@ -218,10 +200,22 @@ 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 >= 0) { - return; + if (report_type == HID_REPORT_TYPE_OUTPUT) { + if (report_id == REPORT_ID_LED_touch_16) { + rgb_set_brg(0, buffer, bufsize / 3); + } else if (report_id == REPORT_ID_LED_touch_15) { + rgb_set_brg(16, buffer, bufsize / 3); + } else if (report_id == REPORT_ID_LED_TOWER_6) { + rgb_set_brg(31, buffer, bufsize / 3); } + last_hid_time = time_us_64(); + return; + } + + if (report_type == HID_REPORT_TYPE_FEATURE) { + if (report_id == REPORT_ID_LED_COMPRESSED) { + } + last_hid_time = time_us_64(); + return; } } diff --git a/firmware/src/mpr121.c b/firmware/src/mpr121.c new file mode 100644 index 0000000..c4790af --- /dev/null +++ b/firmware/src/mpr121.c @@ -0,0 +1,200 @@ +/* + * MP121 Captive Touch Sensor + * WHowe + * + */ + +#include +#include "hardware/i2c.h" + +#include "mpr121.h" +#include "board_defs.h" + +#define IO_TIMEOUT_US 1000 + +#define TOUCH_THRESHOLD_BASE 17 +#define RELEASE_THRESHOLD_BASE 12 + +#define MPR121_TOUCH_STATUS_REG 0x00 +#define MPR121_OUT_OF_RANGE_STATUS_0_REG 0x02 +#define MPR121_OUT_OF_RANGE_STATUS_1_REG 0x03 +#define MPR121_ELECTRODE_FILTERED_DATA_REG 0x04 +#define MPR121_BASELINE_VALUE_REG 0x1E + +#define MPR121_MAX_HALF_DELTA_RISING_REG 0x2B +#define MPR121_NOISE_HALF_DELTA_RISING_REG 0x2C +#define MPR121_NOISE_COUNT_LIMIT_RISING_REG 0x2D +#define MPR121_FILTER_DELAY_COUNT_RISING_REG 0x2E +#define MPR121_MAX_HALF_DELTA_FALLING_REG 0x2F +#define MPR121_NOISE_HALF_DELTA_FALLING_REG 0x30 +#define MPR121_NOISE_COUNT_LIMIT_FALLING_REG 0x31 +#define MPR121_FILTER_DELAY_COUNT_FALLING_REG 0x32 +#define MPR121_NOISE_HALF_DELTA_TOUCHED_REG 0x33 +#define MPR121_NOISE_COUNT_LIMIT_TOUCHED_REG 0x34 +#define MPR121_FILTER_DELAY_COUNT_TOUCHED_REG 0x35 + +#define MPR121_TOUCH_THRESHOLD_REG 0x41 +#define MPR121_RELEASE_THRESHOLD_REG 0x42 + +#define MPR121_DEBOUNCE_REG 0x5B +#define MPR121_AFE_CONFIG_REG 0x5C +#define MPR121_FILTER_CONFIG_REG 0x5D +#define MPR121_ELECTRODE_CONFIG_REG 0x5E +#define MPR121_ELECTRODE_CURRENT_REG 0x5F +#define MPR121_ELECTRODE_CHARGE_TIME_REG 0x6C +#define MPR121_GPIO_CTRL_0_REG 0x73 +#define MPR121_GPIO_CTRL_1_REG 0x74 +#define MPR121_GPIO_DATA_REG 0x75 +#define MPR121_GPIO_DIRECTION_REG 0x76 +#define MPR121_GPIO_ENABLE_REG 0x77 +#define MPR121_GPIO_DATA_SET_REG 0x78 +#define MPR121_GPIO_DATA_CLEAR_REG 0x79 +#define MPR121_GPIO_DATA_TOGGLE_REG 0x7A +#define MPR121_AUTOCONFIG_CONTROL_0_REG 0x7B +#define MPR121_AUTOCONFIG_CONTROL_1_REG 0x7C +#define MPR121_AUTOCONFIG_USL_REG 0x7D +#define MPR121_AUTOCONFIG_LSL_REG 0x7E +#define MPR121_AUTOCONFIG_TARGET_REG 0x7F +#define MPR121_SOFT_RESET_REG 0x80 + +static void write_reg(uint8_t addr, uint8_t reg, uint8_t val) +{ + uint8_t buf[] = {reg, val}; + i2c_write_blocking_until(I2C_PORT, addr, buf, 2, false, + time_us_64() + IO_TIMEOUT_US); +} + +static uint8_t read_reg(uint8_t addr, uint8_t reg) +{ + uint8_t value; + i2c_write_blocking_until(I2C_PORT, addr, ®, 1, true, + time_us_64() + IO_TIMEOUT_US); + i2c_read_blocking_until(I2C_PORT, addr, &value, 1, false, + time_us_64() + IO_TIMEOUT_US); + return value; +} + +void mpr121_init(uint8_t i2c_addr) +{ + write_reg(i2c_addr, 0x80, 0x63); // Soft reset MPR121 if not reset correctly + + //touch pad baseline filter + //rising: baseline quick rising + write_reg(i2c_addr, 0x2B, 0x01); // Max half delta Rising + write_reg(i2c_addr, 0x2C, 0x01); // Noise half delta Rising + write_reg(i2c_addr, 0x2D, 0x00); // Noise count limit Rising + write_reg(i2c_addr, 0x2E, 0x00); // Delay limit Rising + + //falling: baseline slow falling + write_reg(i2c_addr, 0x2F, 0x01); // Max half delta Falling + write_reg(i2c_addr, 0x30, 0x01); // Noise half delta Falling + write_reg(i2c_addr, 0x31, 0xFF); // Noise count limit Falling + write_reg(i2c_addr, 0x32, 0x0F); // Delay limit Falling + + //touched: baseline keep + write_reg(i2c_addr, 0x33, 0x00); // Noise half delta Touched + write_reg(i2c_addr, 0x34, 0x00); // Noise count Touched + write_reg(i2c_addr, 0x35, 0x00); // Delay limit Touched + + //Touch pad threshold + for (int i = 0; i < 12; i++) { + write_reg(i2c_addr, 0x41 + i * 2, TOUCH_THRESHOLD_BASE); + write_reg(i2c_addr, 0x42 + i * 2, RELEASE_THRESHOLD_BASE); + } + + //touch and release debounce + write_reg(i2c_addr, 0x5B, 0x00); + + //AFE and filter configuration + write_reg(i2c_addr, 0x5C, 0b00010000); // AFES=6 samples, same as AFES in 0x7B, Global CDC=16uA + write_reg(i2c_addr, 0x5D, 0b00101000); // CT=0.5us, TDS=4samples, TDI=16ms + write_reg(i2c_addr, 0x5E, 0x80); // Set baseline calibration enabled, baseline loading 5MSB + + //Auto Configuration + write_reg(i2c_addr, 0x7B, 0b00001011); // AFES=6 samples, same as AFES in 0x5C + // retry=2b00, no retry, + // BVA=2b10, load 5MSB after AC, + // ARE/ACE=2b11, auto configuration enabled + //write_reg(i2c_addr, 0x7C,0x80); // Skip charge time search, use setting in 0x5D, + // OOR, AR, AC IE disabled + // Not used. Possible Proximity CDC shall over 63uA + // if only use 0.5uS CDT, the TGL for proximity cannot meet + // Possible if manually set Register0x72=0x03 + // (Auto configure result) alone. + write_reg(i2c_addr, 0x7D, 0xc8); // AC up limit /C8/BD/C0/9C + write_reg(i2c_addr, 0x7E, 0x82); // AC low limit /82/7A/7C/65 + write_reg(i2c_addr, 0x7F, 0xb4); // AC target /B4/AA/AC/8C target for /3.0V/2.8V/1.8V + write_reg(i2c_addr, 0x5E, 0x8C); // Run 12 touch, CL=2b10, load 5MSB to baseline +} + +#define ABS(x) ((x) < 0 ? -(x) : (x)) + +static void mpr121_read_many(uint8_t addr, uint8_t reg, uint8_t *buf, size_t n) +{ + i2c_write_blocking_until(I2C_PORT, addr, ®, 1, true, + time_us_64() + IO_TIMEOUT_US); + i2c_read_blocking_until(I2C_PORT, addr, buf, n, false, + time_us_64() + IO_TIMEOUT_US * n / 2); +} + +static void mpr121_read_many16(uint8_t addr, uint8_t reg, uint16_t *buf, size_t n) +{ + uint8_t vals[n * 2]; + mpr121_read_many(addr, reg, vals, n * 2); + for (int i = 0; i < n; i++) { + buf[i] = (vals[i * 2 + 1] << 8) | vals[i * 2]; + } +} + +uint16_t mpr121_touched(uint8_t addr) +{ + uint16_t touched; + mpr121_read_many16(addr, MPR121_TOUCH_STATUS_REG, &touched, 2); + return touched; +} + +static uint8_t mpr121_stop(uint8_t addr) +{ + uint8_t ecr = read_reg(addr, MPR121_ELECTRODE_CONFIG_REG); + write_reg(addr, MPR121_ELECTRODE_CONFIG_REG, ecr & 0xC0); + return ecr; +} + +static uint8_t mpr121_resume(uint8_t addr, uint8_t ecr) +{ + write_reg(addr, MPR121_ELECTRODE_CONFIG_REG, ecr); +} + +void mpr121_filter(uint8_t addr, uint8_t ffi, uint8_t sfi) +{ + uint8_t ecr = mpr121_stop(addr); + + uint8_t afe = read_reg(addr, MPR121_AFE_CONFIG_REG); + write_reg(addr, MPR121_AFE_CONFIG_REG, (afe & 0x3f) | ffi << 6); + uint8_t acc = read_reg(addr, MPR121_AUTOCONFIG_CONTROL_0_REG); + write_reg(addr, MPR121_AUTOCONFIG_CONTROL_0_REG, (acc & 0x3f) | ffi << 6); + uint8_t fcr = read_reg(addr, MPR121_FILTER_CONFIG_REG); + write_reg(addr, MPR121_FILTER_CONFIG_REG, (fcr & 0xe7) | (sfi & 3) << 3); + + mpr121_resume(addr, ecr); +} + +void mpr121_sense(uint8_t addr, int8_t sense, int8_t *sense_keys) +{ + uint8_t ecr = mpr121_stop(addr); + for (int i = 0; i < 12; i++) { + int8_t delta = sense + sense_keys[i]; + write_reg(addr, MPR121_TOUCH_THRESHOLD_REG + i * 2, + TOUCH_THRESHOLD_BASE - delta); + write_reg(addr, MPR121_RELEASE_THRESHOLD_REG + i * 2, + RELEASE_THRESHOLD_BASE - delta / 2); + } + mpr121_resume(addr, ecr); +} + +void mpr121_debounce(uint8_t addr, uint8_t touch, uint8_t release) +{ + uint8_t ecr = mpr121_stop(addr); + write_reg(addr, 0x5B, (release & 0x07) << 4 | (touch & 0x07)); + mpr121_resume(addr, ecr); +} diff --git a/firmware/src/mpr121.h b/firmware/src/mpr121.h new file mode 100644 index 0000000..bf84a4c --- /dev/null +++ b/firmware/src/mpr121.h @@ -0,0 +1,18 @@ +/* + * MP121 Captive Touch Sensor + * WHowe + * + */ + +#ifndef MP121_H +#define MP121_H + +void mpr121_init(uint8_t addr); + +uint16_t mpr121_touched(uint8_t addr); + +void mpr121_filter(uint8_t addr, uint8_t ffi, uint8_t sfi); +void mpr121_sense(uint8_t addr, int8_t sense, int8_t *sense_keys); +void mpr121_debounce(uint8_t addr, uint8_t touch, uint8_t release); + +#endif diff --git a/firmware/src/rgb.c b/firmware/src/rgb.c index 82a9349..f1c3201 100644 --- a/firmware/src/rgb.c +++ b/firmware/src/rgb.c @@ -6,7 +6,6 @@ #include "rgb.h" -#include "buttons.h" #include #include #include @@ -23,12 +22,7 @@ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -static const uint8_t button_rgb_map[BUTTON_RGB_NUM] = BUTTON_RGB_MAP; - -static void trap() {} -static tt_effect_t effects[10] = { {trap, trap, trap, 0} }; -static size_t effect_num = 0; -static unsigned current_effect = 0; +static uint32_t rgb_buf[47]; // 16(Keys) + 15(Gaps) + 16(maximum ToF indicators) #define _MAP_LED(x) _MAKE_MAPPER(x) #define _MAKE_MAPPER(x) MAP_LED_##x @@ -40,10 +34,6 @@ static unsigned current_effect = 0; static inline uint32_t _rgb32(uint32_t c1, uint32_t c2, uint32_t c3, bool gamma_fix) { - c1 = c1 * iidx_cfg->level / 255; - c2 = c2 * iidx_cfg->level / 255; - c3 = c3 * iidx_cfg->level / 255; - if (gamma_fix) { c1 = ((c1 + 1) * (c1 + 1) - 1) >> 8; c2 = ((c2 + 1) * (c2 + 1) - 1) >> 8; @@ -53,7 +43,7 @@ static inline uint32_t _rgb32(uint32_t c1, uint32_t c2, uint32_t c3, bool gamma_ return (c1 << 16) | (c2 << 8) | (c3 << 0); } -uint32_t button_rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix) +uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix) { #if BUTTON_RGB_ORDER == GRB return _rgb32(g, r, b, gamma_fix); @@ -62,250 +52,84 @@ uint32_t button_rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix) #endif } -uint32_t tt_rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix) +static void drive_led() { -#if TT_RGB_ORDER == GRB - return _rgb32(g, r, b, gamma_fix); -#else - return _rgb32(r, g, b, gamma_fix); -#endif -} - -uint8_t rgb_button_num() -{ - return BUTTON_RGB_NUM; -} - -uint8_t button_lights[BUTTON_RGB_NUM]; -uint32_t tt_led_buf[128] = {0}; -uint32_t tt_led_angle = 0; - -static uint32_t button_led_buf[BUTTON_RGB_NUM] = {0}; - -void set_effect(uint32_t index) -{ - if (index < effect_num) { - current_effect = index; - effects[current_effect].init(effects[current_effect].context); - } else { - current_effect = effect_num; - } -} - -void drive_led() -{ - for (int i = 0; i < ARRAY_SIZE(button_led_buf); i++) { - pio_sm_put_blocking(pio0, 0, button_led_buf[i] << 8u); - } - - if (iidx_cfg->tt_led.mode == 2) { + static uint64_t last = 0; + uint64_t now = time_us_64(); + if (now - last < 4000) { // no faster than 250Hz return; } + last = now; - for (int i = 0; i < iidx_cfg->tt_led.start; i++) { - pio_sm_put_blocking(pio1, 0, 0); + for (int i = 30; i >= 0; i--) { + pio_sm_put_blocking(pio0, 0, rgb_buf[i] << 8u); } - for (int i = 0; i < TT_LED_NUM; i++) { - bool reversed = iidx_cfg->tt_led.mode & 0x01; - uint8_t id = reversed ? TT_LED_NUM - i - 1 : i; - pio_sm_put_blocking(pio1, 0, tt_led_buf[id] << 8u); - } - for (int i = 0; i < 8; i++) { // a few more to wipe out the last led - pio_sm_put_blocking(pio1, 0, 0); + for (int i = 31; i < ARRAY_SIZE(rgb_buf); i++) { + pio_sm_put_blocking(pio0, 0, rgb_buf[i] << 8u); } } -static uint32_t rgb32_from_hsv(hsv_t hsv) +void rgb_set_colors(const uint32_t *colors, unsigned index, size_t num) { - uint32_t region, remainder, p, q, t; - - if (hsv.s == 0) { - return hsv.v << 16 | hsv.v << 8 | hsv.v; - } - - region = hsv.h / 43; - remainder = (hsv.h % 43) * 6; - - p = (hsv.v * (255 - hsv.s)) >> 8; - q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8; - t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8; - - switch (region) { - case 0: - return hsv.v << 16 | t << 8 | p; - case 1: - return q << 16 | hsv.v << 8 | p; - case 2: - return p << 16 | hsv.v << 8 | t; - case 3: - return p << 16 | q << 8 | hsv.v; - case 4: - return t << 16 | p << 8 | hsv.v; - default: - return hsv.v << 16 | p << 8 | q; - } -} - -uint32_t button_hsv(hsv_t hsv) -{ - uint32_t rgb = rgb32_from_hsv(hsv); - uint32_t r = (rgb >> 16) & 0xff; - uint32_t g = (rgb >> 8) & 0xff; - uint32_t b = (rgb >> 0) & 0xff; -#if BUTTON_RGB_ORDER == GRB - return _rgb32(g, r, b, false); -#else - return _rgb32(r, g, b, false); -#endif -} - -uint32_t tt_hsv(hsv_t hsv) -{ - uint32_t rgb = rgb32_from_hsv(hsv); - uint32_t r = (rgb >> 16) & 0xff; - uint32_t g = (rgb >> 8) & 0xff; - uint32_t b = (rgb >> 0) & 0xff; -#if TT_RGB_ORDER == GRB - return _rgb32(g, r, b, false); -#else - return _rgb32(r, g, b, false); -#endif -} - -#define HID_EXPIRE_DURATION 1000000ULL -static uint64_t hid_expire_time = 0; - -static void button_lights_update() -{ - for (int i = 0; i < BUTTON_RGB_NUM; i++) { - int led = button_rgb_map[i]; - if (button_lights[i] > 0) { - button_led_buf[led] = button_hsv(iidx_cfg->key_on[i]); - } else { - button_led_buf[led] = button_hsv(iidx_cfg->key_off[i]); - } - } -} - -void rgb_set_angle(uint32_t angle) -{ - tt_led_angle = angle; - effects[current_effect].set_angle(angle); -} - -void rgb_set_button_light(uint16_t buttons) -{ - if (time_us_64() < hid_expire_time) { + if (index >= ARRAY_SIZE(rgb_buf)) { return; } - for (int i = 0; i < BUTTON_RGB_NUM; i++) { - uint16_t flag = 1 << i; - button_lights[i] = (buttons & flag) > 0 ? 0xff : 0; + if (index + num > ARRAY_SIZE(rgb_buf)) { + num = ARRAY_SIZE(rgb_buf) - index; + } + memcpy(&rgb_buf[index], colors, num * sizeof(*colors)); +} + +void rgb_set_color(unsigned index, uint32_t color) +{ + if (index >= ARRAY_SIZE(rgb_buf)) { + return; + } + rgb_buf[index] = color; +} + +void rgb_key_color(unsigned index, uint32_t color) +{ + if (index > 16) { + return; + } + rgb_buf[index * 2] = color; +} + +void rgb_gap_color(unsigned index, uint32_t color) +{ + if (index > 15) { + return; + } + rgb_buf[index * 2 + 1] = color; +} + +void rgb_set_brg(unsigned index, const uint8_t *brg_array, size_t num) +{ + if (index >= ARRAY_SIZE(rgb_buf)) { + return; + } + if (index + num > ARRAY_SIZE(rgb_buf)) { + num = ARRAY_SIZE(rgb_buf) - index; + } + for (int i = 0; i < num; i++) { + uint8_t b = brg_array[i * 3 + 0]; + uint8_t r = brg_array[i * 3 + 1]; + uint8_t g = brg_array[i * 3 + 2]; + rgb_buf[index + i] = rgb32(r, g, b, false); } } -void rgb_set_hid_light(uint8_t const *lights, uint8_t num) -{ - memcpy(button_lights, lights, num); - hid_expire_time = time_us_64() + HID_EXPIRE_DURATION; -} - -static void effect_update() -{ - effects[current_effect].update(effects[current_effect].context); -} - -#define FORCE_EXPIRE_DURATION 100000ULL -static uint64_t force_expire_time = 0; -uint32_t *force_buttons = NULL; -uint32_t *force_tt = NULL; - -void force_update() -{ - for (int i = 0; i < BUTTON_RGB_NUM; i++) { - int led = button_rgb_map[i]; - button_led_buf[led] = force_buttons[i]; - } - - memcpy(tt_led_buf, force_tt, TT_LED_NUM * sizeof(uint32_t)); -} - -void rgb_force_display(uint32_t *buttons, uint32_t *tt) -{ - force_buttons = buttons; - force_tt = tt; - force_expire_time = time_us_64() + FORCE_EXPIRE_DURATION; -} - -static void wipe_out_tt_led() -{ - sleep_ms(5); - for (int i = 0; i < 128; i++) { - pio_sm_put_blocking(pio1, 0, 0); - } - sleep_ms(5); -} - -static uint pio1_offset; -static bool pio1_running = false; - -static void pio1_run() -{ - gpio_set_drive_strength(TT_RGB_PIN, GPIO_DRIVE_STRENGTH_8MA); - ws2812_program_init(pio1, 0, pio1_offset, TT_RGB_PIN, 800000, false); -} - -static void pio1_stop() -{ - wipe_out_tt_led(); - pio_sm_set_enabled(pio1, 0, false); - - gpio_set_function(TT_RGB_PIN, GPIO_FUNC_SIO); - gpio_set_dir(TT_RGB_PIN, GPIO_IN); - gpio_disable_pulls(TT_RGB_PIN); -} void rgb_init() { uint pio0_offset = pio_add_program(pio0, &ws2812_program); - pio1_offset = pio_add_program(pio1, &ws2812_program); - gpio_set_drive_strength(BUTTON_RGB_PIN, GPIO_DRIVE_STRENGTH_2MA); - ws2812_program_init(pio0, 0, pio0_offset, BUTTON_RGB_PIN, 800000, false); - - /* We don't start the tt LED program yet */ -} - -static void follow_mode_change() -{ - bool pio1_should_run = (iidx_cfg->tt_led.mode != 2); - if (pio1_should_run == pio1_running) { - return; - } - pio1_running = pio1_should_run; - if (pio1_should_run) { - pio1_run(); - } else { - pio1_stop(); - } + gpio_set_drive_strength(RGB_PIN, GPIO_DRIVE_STRENGTH_2MA); + ws2812_program_init(pio0, 0, pio0_offset, RGB_PIN, 800000, false); } void rgb_update() { - follow_mode_change(); - set_effect(iidx_cfg->tt_led.effect); - if (time_us_64() > force_expire_time) { - effect_update(); - button_lights_update(); - } else { - force_update(); - } drive_led(); } - -void rgb_reg_tt_effect(tt_effect_t effect) -{ - effects[effect_num] = effect; - effect_num++; - effects[effect_num] = (tt_effect_t) { trap, trap, trap, 0 }; -} diff --git a/firmware/src/rgb.h b/firmware/src/rgb.h index a08ed74..903cca0 100644 --- a/firmware/src/rgb.h +++ b/firmware/src/rgb.h @@ -6,44 +6,23 @@ #ifndef RGB_H #define RGB_H +#include #include #include #include "config.h" void rgb_init(); -void rgb_set_hardware(uint16_t tt_start, uint16_t tt_num, bool tt_reversed); - -uint8_t rgb_button_num(); void rgb_update(); -void rgb_set_angle(uint32_t angle); -void rgb_set_level(uint8_t level); +uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix); -void rgb_set_button_light(uint16_t buttons); -void rgb_set_hid_light(uint8_t const *lights, uint8_t num); +void rgb_set_colors(const uint32_t *colors, unsigned index, size_t num); +void rgb_set_color(unsigned index, uint32_t color); +void rgb_key_color(unsigned index, uint32_t color); +void rgb_gap_color(unsigned index, uint32_t color); -void rgb_force_display(uint32_t *keyboard, uint32_t *tt); - -typedef struct { - void (*init)(uint32_t context); - void (*set_angle)(uint32_t angle); - void (*update)(uint32_t context); - uint32_t context; -} tt_effect_t; - -void rgb_reg_tt_effect(tt_effect_t effect); - -extern uint32_t tt_led_buf[]; -#define TT_LED_NUM (iidx_cfg->tt_led.num) - -/* These global variables meant to be accessed by effect codes */ -extern uint32_t tt_led_angle; - - -uint32_t button_rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix); -uint32_t tt_rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix); -uint32_t button_hsv(hsv_t hsv); -uint32_t tt_hsv(hsv_t hsv); +/* num of the rgb leds, num*3 bytes in the array */ +void rgb_set_brg(unsigned index, const uint8_t *brg_array, size_t num); #endif diff --git a/firmware/src/save.c b/firmware/src/save.c index 01365e0..d3b227e 100644 --- a/firmware/src/save.c +++ b/firmware/src/save.c @@ -1,8 +1,8 @@ /* - * Controller Save Save and Load + * Controller Config Save and Load * WHowe * - * Save is stored in last sector of flash + * Config is stored in last sector of flash */ #include "save.h" @@ -18,7 +18,7 @@ #include "pico/stdio.h" #include "hardware/flash.h" -#include "hardware/sync.h" +#include "pico/multicore.h" static struct { size_t size; @@ -27,7 +27,10 @@ static struct { } modules[8] = {0}; static int module_num = 0; -#define SAVE_PAGE_MAGIC 0x13424321 +static uint32_t my_magic = 0xcafecafe; + +#define SAVE_TIMEOUT_US 5000000 + #define SAVE_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) typedef struct __attribute ((packed)) { @@ -43,30 +46,34 @@ static int data_page = -1; static bool requesting_save = false; static uint64_t requesting_time = 0; -static io_locker_func io_lock; +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("Program Flash %d %8lx\n", data_page, old_data.magic); - io_lock(true); - uint32_t ints = save_and_disable_interrupts(); - if (data_page == 0) { - flash_range_erase(SAVE_SECTOR_OFFSET, FLASH_SECTOR_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"); } - flash_range_program(SAVE_SECTOR_OFFSET + data_page * FLASH_PAGE_SIZE, - (uint8_t *)&old_data, FLASH_PAGE_SIZE); - restore_interrupts(ints); - io_lock(false); } static void load_default() { printf("Load Default\n"); new_data = default_data; - new_data.magic = SAVE_PAGE_MAGIC; + new_data.magic = my_magic; } static const page_t *get_page(int id) @@ -78,7 +85,7 @@ static const page_t *get_page(int id) static void save_load() { for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) { - if (get_page(i)->magic != SAVE_PAGE_MAGIC) { + if (get_page(i)->magic != my_magic) { break; } data_page = i; @@ -102,8 +109,9 @@ static void save_loaded() } } -void save_init(io_locker_func locker) +void save_init(uint32_t magic, mutex_t *locker) { + my_magic = magic; io_lock = locker; save_load(); save_loop(); @@ -112,17 +120,13 @@ void save_init(io_locker_func locker) void save_loop() { - if (requesting_save && (time_us_64() - requesting_time > 1000000)) { + if (requesting_save && (time_us_64() - requesting_time > SAVE_TIMEOUT_US)) { requesting_save = false; - printf("Time to save.\n"); /* only when data is actually changed */ - for (int i = 0; i < sizeof(old_data); i++) { - if (((uint8_t *)&old_data)[i] != ((uint8_t *)&new_data)[i]) { - save_program(); - return; - } + if (memcmp(&old_data, &new_data, sizeof(old_data)) == 0) { + return; } - printf("No change.\n"); + save_program(); } } @@ -140,9 +144,9 @@ void *save_alloc(size_t size, void *def, void (*after_load)()) void save_request(bool immediately) { if (!requesting_save) { - printf("Save marked.\n"); + printf("Save requested.\n"); requesting_save = true; - new_data.magic = SAVE_PAGE_MAGIC; + new_data.magic = my_magic; requesting_time = time_us_64(); } if (immediately) { diff --git a/firmware/src/save.h b/firmware/src/save.h index e1b9ae6..2cf5f1b 100644 --- a/firmware/src/save.h +++ b/firmware/src/save.h @@ -7,11 +7,14 @@ #define SAVE_H #include +#include #include +#include "pico/multicore.h" + /* 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(io_locker_func locker); +void save_init(uint32_t magic, mutex_t *lock); void save_loop(); diff --git a/firmware/src/setup.c b/firmware/src/setup.c deleted file mode 100644 index 6c39492..0000000 --- a/firmware/src/setup.c +++ /dev/null @@ -1,725 +0,0 @@ -/* - * Controller Setup Menu - * WHowe - * - * Setup is a mode, so one can change settings live - */ - -#include "setup.h" - -#include -#include -#include - -#include "bsp/board.h" -#include "pico/bootrom.h" - -#include "rgb.h" -#include "config.h" - -static iidx_cfg_t cfg_save; - -static uint64_t setup_tick_ms = 0; -#define CONCAT(a, b) a ## b -#define TVAR(line) CONCAT(a, line) -#define RUN_EVERY_N_MS(a, ms) { static uint64_t TVAR(__LINE__) = 0; \ - if (setup_tick_ms - TVAR(__LINE__) >= ms) { a; TVAR(__LINE__) = setup_tick_ms; } } -static uint32_t blink_fast = 0xffffffff; -static uint32_t blink_slow = 0xffffffff; - -uint32_t setup_led_tt[128]; -uint32_t setup_led_button[BUTTON_RGB_NUM]; - -typedef enum { - MODE_NONE, - MODE_TURNTABLE, - MODE_ANALOG, - MODE_LEVEL, - MODE_TT_THEME, - MODE_KEY_THEME, - MODE_KEY_OFF, - MODE_KEY_ON, -} setup_mode_t; -static setup_mode_t current_mode = MODE_NONE; - -static struct { - uint16_t last_keys; - uint16_t keys; - uint16_t just_pressed; - uint16_t just_released; - - int16_t last_angle; - int16_t angle; - int16_t rotate; -} input = { 0 }; - -#define KEY_1 0x0001 -#define KEY_2 0x0002 -#define KEY_3 0x0004 -#define KEY_4 0x0008 -#define KEY_5 0x0010 -#define KEY_6 0x0020 -#define KEY_7 0x0040 -#define E1 0x0080 -#define E2 0x0100 -#define E3 0x0200 -#define E4 0x0400 -#define AUX_NO 0x0800 -#define AUX_YES 0x1000 - -#define LED_KEY_1 0 -#define LED_KEY_2 1 -#define LED_KEY_3 2 -#define LED_KEY_4 3 -#define LED_KEY_5 4 -#define LED_KEY_6 5 -#define LED_KEY_7 6 -#define LED_E1 7 -#define LED_E2 8 -#define LED_E3 9 -#define LED_E4 10 - -#define PRESSED_ALL(k) ((input.keys & (k)) == (k)) -#define PRESSED_ANY(k) (input.keys & (k)) -#define JUST_PRESSED(k) (input.just_pressed & (k)) -#define JUST_RELEASED(k) (input.just_released & (k)) - -#define RED button_rgb32(99, 0, 0, false) -#define GREEN button_rgb32(0, 99, 0, false) -#define CYAN button_rgb32(0, 40, 99, false) -#define YELLOW button_rgb32(99, 99, 0, false) -#define SILVER button_rgb32(60, 60, 60, false) - -#define TT_RED tt_rgb32(99, 0, 0, false) -#define TT_GREEN tt_rgb32(0, 99, 0, false) -#define TT_CYAN tt_rgb32(0, 40, 99, false) -#define TT_YELLOW tt_rgb32(99, 99, 0, false) -#define TT_SILVER tt_rgb32(60, 60, 60, false) - -typedef void (*mode_func)(); - -static void join_mode(setup_mode_t new_mode); -static void quit_mode(bool apply); - -static void nop() -{ -} - -static void check_exit() -{ - if (JUST_PRESSED(AUX_YES)) { - quit_mode(true); - } else if (JUST_PRESSED(AUX_NO)) { - quit_mode(false); - } -} - -static int16_t input_delta(int16_t start_angle) -{ - int16_t delta = input.angle - start_angle; - if (delta > 128) { - delta -= 256; - } - if (delta < -128) { - delta += 256; - } - return delta; -} - -static setup_mode_t key_to_mode[11] = { - MODE_KEY_THEME, MODE_TT_THEME, MODE_KEY_ON, MODE_KEY_OFF, - MODE_NONE, MODE_NONE, MODE_NONE, - MODE_ANALOG, MODE_ANALOG, MODE_ANALOG, MODE_ANALOG, -}; - -static struct { - bool escaped; - uint64_t escape_time; - uint16_t start_angle; -} none_ctx = { 0 }; - -static void none_rotate() -{ - if (!none_ctx.escaped) { - return; - } - - int16_t delta = input_delta(none_ctx.start_angle); - if (abs(delta) > 10) { - join_mode(MODE_LEVEL); - none_ctx.escaped = false; - } -} - -static void none_loop() -{ - if (PRESSED_ALL(AUX_YES | AUX_NO)) { - if (!none_ctx.escaped) { - none_ctx.escaped = true; - none_ctx.escape_time = time_us_64(); - none_ctx.start_angle = input.angle; - } - } else { - none_ctx.escaped = false; - } - - if (!none_ctx.escaped) { - return; - } - - for (int i = 0; i < 11; i++) { - if (PRESSED_ANY(KEY_1 << i)) { - none_ctx.escaped = false; - join_mode(key_to_mode[i]); - return; - } - } - - if (time_us_64() - none_ctx.escape_time > 5000000) { - none_ctx.escaped = false; - join_mode(MODE_TURNTABLE); - return; - } -} - -static struct { - uint8_t adjust_led; /* 0: nothing, 1: adjust start, 2: adjust stop */ - int16_t start_angle; -} tt_ctx; - -static void tt_enter() -{ - tt_ctx.start_angle = input.angle; -} - -static void tt_key_change() -{ - if (JUST_PRESSED(E1)) { - tt_ctx.adjust_led = (tt_ctx.adjust_led == 1) ? 0 : 1; - tt_ctx.start_angle = input.angle; - } else if (JUST_PRESSED(E2)) { - tt_ctx.adjust_led = (tt_ctx.adjust_led == 2) ? 0 : 2; - tt_ctx.start_angle = input.angle; - } else if (JUST_PRESSED(E3)) { - iidx_cfg->tt_led.mode = (iidx_cfg->tt_led.mode + 1) % 3; - } else if (JUST_PRESSED(E4)) { - iidx_cfg->tt_sensor.mode = (iidx_cfg->tt_sensor.mode + 1) % 4; - } else if (JUST_PRESSED(KEY_2)) { - iidx_cfg->tt_sensor.deadzone = 0; - } else if (JUST_PRESSED(KEY_4)) { - iidx_cfg->tt_sensor.deadzone = 1; - } else if (JUST_PRESSED(KEY_6)) { - iidx_cfg->tt_sensor.deadzone = 2; - } else if (JUST_PRESSED(KEY_1)) { - iidx_cfg->tt_sensor.ppr = 0; - } else if (JUST_PRESSED(KEY_3)) { - iidx_cfg->tt_sensor.ppr = 1; - } else if (JUST_PRESSED(KEY_5)) { - iidx_cfg->tt_sensor.ppr = 2; - } else if (JUST_PRESSED(KEY_7)) { - iidx_cfg->tt_sensor.ppr = 3; - } - - check_exit(); -} - -static void tt_rotate() -{ - int16_t delta = input_delta(tt_ctx.start_angle); - if (abs(delta) > 8) { - tt_ctx.start_angle = input.angle; - - #define LED_START iidx_cfg->tt_led.start - #define LED_NUM iidx_cfg->tt_led.num - - if (tt_ctx.adjust_led == 1) { - if ((delta > 0) & (LED_START < 8)) { - LED_START++; - if (LED_NUM > 1) { - LED_NUM--; - } - } else if ((delta < 0) & (LED_START > 0)) { - LED_START--; - LED_NUM++; - } - } else if (tt_ctx.adjust_led == 2) { - if ((delta > 0) & (LED_NUM + LED_START < 128)) { - LED_NUM++; - } else if ((delta < 0) & (LED_NUM > 1)) { // at least 1 led - LED_NUM--; - } - } - } -} - -static void tt_loop() -{ - for (int i = 1; i < iidx_cfg->tt_led.num - 1; i++) { - setup_led_tt[i] = tt_rgb32(10, 10, 10, false); - } - - bool led_reversed = (iidx_cfg->tt_led.mode == 1); - int head = led_reversed ? TT_LED_NUM - 1 : 0; - int tail = led_reversed ? 0 : TT_LED_NUM - 1; - - setup_led_tt[head] = tt_rgb32(0xa0, 0, 0, false); - setup_led_tt[tail] = tt_rgb32(0, 0xa0, 0, false); - setup_led_button[LED_E2] = button_rgb32(0, 10, 0, false); - setup_led_button[LED_E1] = button_rgb32(10, 0, 0, false); - - if (tt_ctx.adjust_led == 1) { - setup_led_tt[head] &= blink_fast; - setup_led_button[LED_E1] = RED & blink_fast; - } else if (tt_ctx.adjust_led == 2) { - setup_led_tt[tail] &= blink_fast; - setup_led_button[LED_E2] = GREEN & blink_fast; - } - - switch (iidx_cfg->tt_led.mode) { - case 0: - setup_led_button[LED_E3] = GREEN; - break; - case 1: - setup_led_button[LED_E3] = RED; - break; - default: - setup_led_button[LED_E3] = 0; - break; - } - - switch (iidx_cfg->tt_sensor.mode) { - case 0: - setup_led_button[LED_E4] = GREEN; - break; - case 1: - setup_led_button[LED_E4] = RED; - break; - case 2: - setup_led_button[LED_E4] = CYAN; - break; - default: - setup_led_button[LED_E4] = YELLOW; - break; - } - setup_led_button[LED_KEY_2] = iidx_cfg->tt_sensor.deadzone == 0 ? SILVER : 0; - setup_led_button[LED_KEY_4] = iidx_cfg->tt_sensor.deadzone == 1 ? SILVER : 0; - setup_led_button[LED_KEY_6] = iidx_cfg->tt_sensor.deadzone == 2 ? SILVER : 0; - setup_led_button[LED_KEY_1] = iidx_cfg->tt_sensor.ppr == 0 ? SILVER : 0; - setup_led_button[LED_KEY_3] = iidx_cfg->tt_sensor.ppr == 1 ? SILVER : 0; - setup_led_button[LED_KEY_5] = iidx_cfg->tt_sensor.ppr == 2 ? SILVER : 0; - setup_led_button[LED_KEY_7] = iidx_cfg->tt_sensor.ppr == 3 ? SILVER : 0; -} - -static void level_rotate() -{ - int16_t new_value = iidx_cfg->level; - new_value += input.rotate; - if (new_value < 0) { - new_value = 0; - } else if (new_value > 255) { - new_value = 255; - } - iidx_cfg->level = new_value; - printf("Level: %d\n", iidx_cfg->level); -} - -static void level_key_change() -{ - if (JUST_PRESSED(KEY_1)) { - iidx_cfg->level = 0; - } else if (JUST_PRESSED(KEY_2)) { - iidx_cfg->level = 20; - } else if (JUST_PRESSED(KEY_3)) { - iidx_cfg->level = 50; - } else if (JUST_PRESSED(KEY_4)) { - iidx_cfg->level = 85; - } else if (JUST_PRESSED(KEY_5)) { - iidx_cfg->level = 130; - } else if (JUST_PRESSED(KEY_6)) { - iidx_cfg->level = 190; - } else if (JUST_PRESSED(KEY_7)) { - iidx_cfg->level = 255; - } - - check_exit(); -} - -static void level_loop() -{ - for (int i = 0; i < 7; i++) { - hsv_t key_color = {i * 255 / 7, 255, 255 }; - setup_led_button[i] = button_hsv(key_color); - } - - uint16_t pos = iidx_cfg->level * iidx_cfg->tt_led.num / 256; - for (unsigned i = 0; i < iidx_cfg->tt_led.num; i++) { - setup_led_tt[i] = (i == pos) ? tt_rgb32(90, 90, 90, false) : 0; - } -} - -static struct { - uint8_t channel; /* 0:E1(Start), 1:E2(Effect), 2:E3(VEFX), 3:E4 */ - volatile uint8_t *value; - int16_t start_angle; -} analog_ctx; - -static void analog_key_change() -{ - if (JUST_PRESSED(E1)) { - analog_ctx.channel = 0; - analog_ctx.value = &iidx_cfg->effects.e1; - } else if (JUST_PRESSED(E2)) { - analog_ctx.channel = 1; - analog_ctx.value = &iidx_cfg->effects.e2; - } else if (JUST_PRESSED(E3)) { - analog_ctx.channel = 2; - analog_ctx.value = &iidx_cfg->effects.e3; - } else if (JUST_PRESSED(E4)) { - analog_ctx.channel = 3; - analog_ctx.value = &iidx_cfg->effects.e4; - } else if (JUST_PRESSED(KEY_1)) { - *analog_ctx.value = 0; - } else if (JUST_PRESSED(KEY_2)) { - *analog_ctx.value = 43; - } else if (JUST_PRESSED(KEY_3)) { - *analog_ctx.value = 85; - } else if (JUST_PRESSED(KEY_4)) { - *analog_ctx.value = 128; - } else if (JUST_PRESSED(KEY_5)) { - *analog_ctx.value = 170; - } else if (JUST_PRESSED(KEY_6)) { - *analog_ctx.value = 213; - } else if (JUST_PRESSED(KEY_7)) { - *analog_ctx.value = 255; - } - - check_exit(); -} - -static void analog_enter() -{ - analog_key_change(); -} - -static void analog_rotate() -{ - int16_t new_value = *analog_ctx.value; - new_value += input.rotate; - if (new_value < 0) { - new_value = 0; - } else if (new_value > 255) { - new_value = 255; - } - *analog_ctx.value = new_value; -} - -static uint32_t scale_color(uint32_t color, uint8_t value, uint8_t factor) -{ - uint8_t r = (color >> 16) & 0xff; - uint8_t g = (color >> 8) & 0xff; - uint8_t b = color & 0xff; - - r = (r * value) / factor; - g = (g * value) / factor; - b = (b * value) / factor; - - return (r << 16) | (g << 8) | b; -} - -static void analog_loop() -{ - uint32_t colors[4] = { RED, GREEN, CYAN, YELLOW}; - uint32_t tt_colors[4] = { TT_RED, TT_GREEN, TT_CYAN, TT_YELLOW }; - - for (int i = 0; i < 4; i++) { - uint32_t color = colors[i]; - if (analog_ctx.channel == i) { - color &= blink_fast; - } - setup_led_button[LED_E1 + i] = color; - } - - int tt_split = (int)*analog_ctx.value * iidx_cfg->tt_led.num / 255; - - for (int i = 1; i < iidx_cfg->tt_led.num - 1; i++) { - setup_led_tt[i] = i < tt_split ? tt_colors[analog_ctx.channel] : 0; - } - - int button_split = *analog_ctx.value / 37; - int scale = *analog_ctx.value % 37; - for (int i = 0; i < 7; i++) { - uint32_t color = colors[analog_ctx.channel]; - if (i == button_split) { - color = scale_color(color, scale, 37); - } else if (i > button_split) { - color = 0; - } - setup_led_button[LED_KEY_1 + i] = color; - } -} - -static struct { - uint8_t phase; /* 0:H, 1:S, 2:V */ - hsv_t hsv; - uint8_t *value; - int16_t start_angle; - uint16_t keys; - hsv_t *leds; -} key_ctx; - -static void key_apply() -{ - for (int i = 0; i < 11; i++) { - if (key_ctx.keys & (1 << i)) { - key_ctx.leds[i] = key_ctx.hsv; - } - } -} - -static void key_change() -{ - if (JUST_PRESSED(AUX_NO)) { - quit_mode(false); - return; - } - - if (JUST_PRESSED(AUX_YES)) { - key_ctx.phase++; - if (key_ctx.phase == 3) { - key_apply(); - quit_mode(true); - return; - } - - if (key_ctx.phase == 1) { - key_ctx.value = &key_ctx.hsv.s; - } else { - key_ctx.value = &key_ctx.hsv.v; - } - return; - } - - key_ctx.keys ^= input.just_pressed; -} - -static void key_rotate() -{ - int16_t new_value = *key_ctx.value; - new_value += input.rotate; - if (key_ctx.phase > 0) { - if (new_value < 0) { - new_value = 0; - } else if (new_value > 255) { - new_value = 255; - } - } - *key_ctx.value = (uint8_t)new_value; -} - -static void key_loop() -{ - for (int i = 0; i < 11; i ++) { - if (key_ctx.keys == 0) { - setup_led_button[i] = button_hsv(key_ctx.hsv) & blink_slow; - } else if (key_ctx.keys & (1 << i)) { - setup_led_button[i] = button_hsv(key_ctx.hsv); - } else { - setup_led_button[i] = 0; - } - } - - uint16_t pos = *key_ctx.value * iidx_cfg->tt_led.num / 256; - for (unsigned i = 0; i < iidx_cfg->tt_led.num; i++) { - setup_led_tt[i] = (i == pos) ? tt_rgb32(90, 90, 90, false) : 0; - } -} - -static void key_enter() -{ - key_ctx = (typeof(key_ctx)) { - .phase = 0, - .hsv = { .h = 200, .s = 255, .v = 128 }, - .value = &key_ctx.hsv.h, - .start_angle = input.angle, - .keys = 0, - .leds = iidx_cfg->key_on, - }; - - if (current_mode == MODE_KEY_OFF) { - key_ctx.hsv = (hsv_t) { .h = 60, .s = 255, .v = 5 }; - key_ctx.leds = iidx_cfg->key_off; - } -} - -#define K0_WHITE {.v = 5} -#define K0_SKY {.h = 215, .s = 255, .v = 20} -#define K0_RED {.h = 87, .s = 255, .v = 20} -#define K0_GREEN {.h = 0, .s = 255, .v = 20} - -#define K1_WHITE {.v = 200} -#define K1_SKY {.h = 215, .s = 255, .v = 230} -#define K1_RED {.h = 87, .s = 255, .v = 230} -#define K1_GREEN {.h = 0, .s = 255, .v = 230} - -#define K0_RAINBOW(x) {.h = 23 * x, .s = 255, .v = 20} -#define K1_RAINBOW(x) {.h = 23 * x, .s = 255, .v = 230} - -static struct { - hsv_t key_off[11]; - hsv_t key_on[11]; -} themes[7] = { - {{ { 0 }, }, - { K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, - K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE }, - }, - {{ { 0 }, }, - { K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, - K1_RED, K1_GREEN, K1_GREEN, K1_SKY }, - }, - {{ { 0 }, }, - { K1_RED, K1_SKY, K1_RED, K1_SKY, K1_RED, K1_SKY, K1_RED, - K1_RED, K1_GREEN, K1_GREEN, K1_GREEN }, - }, - {{ K0_WHITE, K0_WHITE, K0_WHITE, K0_WHITE, K0_WHITE, K0_WHITE, K0_WHITE, - K0_WHITE, K0_WHITE, K0_WHITE, K0_WHITE }, - { K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, - K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE }, - }, - {{ K0_RED, K0_SKY, K0_RED, K0_SKY, K0_RED, K0_SKY, K0_RED, - K0_RED, K0_GREEN, K0_GREEN, K0_GREEN }, - { K1_RED, K1_SKY, K1_RED, K1_SKY, K1_RED, K1_SKY, K1_RED, - K1_RED, K1_GREEN, K1_GREEN, K1_GREEN }, - }, - {{ K0_RAINBOW(0), K0_RAINBOW(1), K0_RAINBOW(2), K0_RAINBOW(3), - K0_RAINBOW(4), K0_RAINBOW(5), K0_RAINBOW(6), - K0_RAINBOW(7), K0_RAINBOW(8), K0_RAINBOW(9), K0_RAINBOW(10) - }, - { K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE, - K1_WHITE, K1_WHITE, K1_WHITE, K1_WHITE }, - }, - {{ K0_RAINBOW(0), K0_RAINBOW(1), K0_RAINBOW(2), K0_RAINBOW(3), - K0_RAINBOW(4), K0_RAINBOW(5), K0_RAINBOW(6), - K0_RAINBOW(7), K0_RAINBOW(8), K0_RAINBOW(9), K0_RAINBOW(10) - }, - { K1_RAINBOW(0), K1_RAINBOW(1), K1_RAINBOW(2), K1_RAINBOW(3), - K1_RAINBOW(4), K1_RAINBOW(5), K1_RAINBOW(6), - K1_RAINBOW(7), K1_RAINBOW(8), K1_RAINBOW(9), K1_RAINBOW(10) - } - }, -}; - -static void key_theme_key_change() -{ - for (int i = 0; i < 7; i++) { - if (JUST_PRESSED(KEY_1 << i)) { - memcpy(iidx_cfg->key_off, themes[i].key_off, sizeof(iidx_cfg->key_off)); - memcpy(iidx_cfg->key_on, themes[i].key_on, sizeof(iidx_cfg->key_on)); - break; - } - } - check_exit(); -} - -static void key_theme_loop() -{ - for (int i = 0; i < 11; i++) { - if (blink_slow) { - setup_led_button[i] = button_hsv(iidx_cfg->key_on[i]); - } else { - setup_led_button[i] = button_hsv(iidx_cfg->key_off[i]); - } - } -} - -static void tt_theme_key_change() -{ - for (int i = 0; i < 7; i++) { - if (JUST_PRESSED(KEY_1 << i)) { - iidx_cfg->tt_led.effect = i; - break; - } - } - check_exit(); -} - -static void tt_theme_loop() -{ - for (int i = 0; i < 7; i++) { - setup_led_button[i] = iidx_cfg->tt_led.effect == i ? SILVER : 0; - } -} - -static struct { - mode_func key_change; - mode_func rotate; - mode_func loop; - mode_func enter; -} mode_defs[] = { - [MODE_NONE] = { nop, none_rotate, none_loop, nop}, - [MODE_TURNTABLE] = { tt_key_change, tt_rotate, tt_loop, tt_enter}, - [MODE_ANALOG] = { analog_key_change, analog_rotate, analog_loop, analog_enter}, - [MODE_LEVEL] = { level_key_change, level_rotate, level_loop, nop}, - [MODE_TT_THEME] = { tt_theme_key_change, nop, tt_theme_loop, nop}, - [MODE_KEY_THEME] = { key_theme_key_change, nop, key_theme_loop, nop}, - [MODE_KEY_OFF] = { key_change, key_rotate, key_loop, key_enter}, - [MODE_KEY_ON] = { key_change, key_rotate, key_loop, key_enter}, -}; - -static void join_mode(setup_mode_t new_mode) -{ - cfg_save = *iidx_cfg; - memset(&setup_led_tt, 0, sizeof(setup_led_tt)); - memset(&setup_led_button, 0, sizeof(setup_led_button)); - current_mode = new_mode; - mode_defs[current_mode].enter(); - printf("Entering setup %d\n", new_mode); -} - -static void quit_mode(bool apply) -{ - if (apply) { - config_changed(); - } else { - *iidx_cfg = cfg_save; - } - current_mode = MODE_NONE; - printf("Quit setup %s\n", apply ? "saved." : "discarded."); -} - -bool setup_run(uint16_t keys, uint16_t angle) -{ - setup_tick_ms = time_us_64() / 1000; - input.keys = keys; - input.angle = angle; - input.just_pressed = keys & ~input.last_keys; - input.just_released = ~keys & input.last_keys; - input.rotate = input_delta(input.last_angle); - if (input.rotate != 0) { - printf("@ %3d %2x\n", input.rotate, input.angle); - mode_defs[current_mode].rotate(); - } - if (input.just_pressed) { - printf("+ %04x\n", input.just_pressed); - } - if (input.just_released) { - printf("- %04x\n", input.just_released); - } - - if (input.just_pressed || input.just_released) { - mode_defs[current_mode].key_change(); - } - - RUN_EVERY_N_MS(blink_fast = ~blink_fast, 100); - RUN_EVERY_N_MS(blink_slow = ~blink_slow, 500); - - mode_defs[current_mode].loop(); - - input.last_keys = keys; - input.last_angle = angle; - - return current_mode != MODE_NONE; -} - -void setup_init() -{ -} \ No newline at end of file diff --git a/firmware/src/setup.h b/firmware/src/setup.h deleted file mode 100644 index cf00854..0000000 --- a/firmware/src/setup.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Controller Setup - * WHowe - */ - -#ifndef SETUP_H -#define SETUP_H - -#include -#include -#include "board_defs.h" - -void setup_init(); -bool setup_run(uint16_t key_flag, uint16_t tt_angle); - -extern uint32_t setup_led_tt[]; -extern uint32_t setup_led_button[BUTTON_RGB_NUM]; - -#endif diff --git a/firmware/src/touch.c b/firmware/src/touch.c new file mode 100644 index 0000000..3b2b6a2 --- /dev/null +++ b/firmware/src/touch.c @@ -0,0 +1,69 @@ +/* + * Mai Pico Silder Keys + * WHowe + * + * MPR121 CapSense based Keys + */ + +#include "touch.h" + +#include +#include +#include + +#include "bsp/board.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +#include "board_defs.h" + +#include "config.h" +#include "mpr121.h" + +#define MPR121_ADDR 0x5A + +static uint16_t baseline[36]; +static int16_t error[36]; +static uint16_t readout[36]; +static bool touched[36]; +static uint16_t touch[3]; + +void touch_init() +{ + i2c_init(I2C_PORT, I2C_FREQ); + gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA); + gpio_pull_up(I2C_SCL); + + for (int m = 0; m < 3; m++) { + mpr121_init(MPR121_ADDR + m); + } + touch_update_config(); +} + +void touch_update() +{ + touch[0] = mpr121_touched(MPR121_ADDR); + touch[1] = mpr121_touched(MPR121_ADDR + 1); + touch[2] = mpr121_touched(MPR121_ADDR + 2); +} + +bool touch_touched(unsigned key) +{ + if (key >= 32) { + return 0; + } + return touch[key / 12] & (1 << (key % 12)); +} + +void touch_update_config() +{ + for (int m = 0; m < 3; m++) { + mpr121_debounce(MPR121_ADDR + m, mai_cfg->sense.debounce_touch, + mai_cfg->sense.debounce_release); + mpr121_sense(MPR121_ADDR + m, mai_cfg->sense.global, mai_cfg->sense.keys + m * 12); + mpr121_filter(MPR121_ADDR + m, mai_cfg->sense.filter & 0x0f, + (mai_cfg->sense.filter >> 4) & 0x0f); + } +} diff --git a/firmware/src/touch.h b/firmware/src/touch.h new file mode 100644 index 0000000..39ffd63 --- /dev/null +++ b/firmware/src/touch.h @@ -0,0 +1,17 @@ +/* + * Mai Pico Touch Keys + * WHowe + */ + +#ifndef TOUCH_H +#define TOUCH_H + +#include +#include + +void touch_init(); +void touch_update(); +bool touch_touched(unsigned key); +void touch_update_config(); + +#endif diff --git a/firmware/src/tusb_config.h b/firmware/src/tusb_config.h index e7a9ac6..9f81ac2 100644 --- a/firmware/src/tusb_config.h +++ b/firmware/src/tusb_config.h @@ -96,7 +96,7 @@ extern "C" { #endif //------------- CLASS -------------// -#define CFG_TUD_HID 1 +#define CFG_TUD_HID 3 #define CFG_TUD_CDC 1 #define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 diff --git a/firmware/src/usb_descriptors.c b/firmware/src/usb_descriptors.c index 203f61e..12ee53a 100644 --- a/firmware/src/usb_descriptors.c +++ b/firmware/src/usb_descriptors.c @@ -36,8 +36,8 @@ */ #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)) + (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4)) //--------------------------------------------------------------------+ // Device Descriptors @@ -51,8 +51,11 @@ tusb_desc_device_t desc_device_joy = { .bDeviceProtocol = 0x00, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .idVendor = 0xCaff, - .idProduct = USB_PID, +// To match CrazyRedMachine dll +// vid 0x0f0d, pid 0x0092, interface 1 + + .idVendor = 0x0f0d, + .idProduct = 0x0092, .bcdDevice = 0x0100, .iManufacturer = 0x01, @@ -64,7 +67,7 @@ tusb_desc_device_t desc_device_joy = { // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const* tud_descriptor_device_cb(void) { - return (uint8_t const*)&desc_device_joy; + return (uint8_t const*)&desc_device_joy; } //--------------------------------------------------------------------+ @@ -72,27 +75,49 @@ 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)) + MAIPICO_REPORT_DESC_JOYSTICK, +}; + +uint8_t const desc_hid_report_led[] = { + MAIPICO_LED_HEADER, + MAIPICO_REPORT_DESC_LED_touch_16, + MAIPICO_REPORT_DESC_LED_touch_15, + MAIPICO_REPORT_DESC_LED_TOWER_6, + MAIPICO_REPORT_DESC_LED_COMPRESSED, + MAIPICO_LED_FOOTER +}; + +uint8_t const desc_hid_report_nkro[] = { + MAIPICO_REPORT_DESC_NKRO, }; // 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; +uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) +{ + switch (itf) { + case 0: + return desc_hid_report_joy; + case 1: + return desc_hid_report_led; + case 2: + return desc_hid_report_nkro; + 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_LED, ITF_NUM_NKRO, ITF_NUM_CDC, ITF_NUM_CDC_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 * 3 + TUD_CDC_DESC_LEN) -#define EPNUM_HID 0x84 +#define EPNUM_JOY 0x84 +#define EPNUM_LED 0x85 +#define EPNUM_KEY 0x86 #define EPNUM_CDC_NOTIF 0x81 #define EPNUM_CDC_OUT 0x02 #define EPNUM_CDC_IN 0x82 @@ -101,25 +126,31 @@ uint8_t const desc_configuration_joy[] = { // Config number, interface count, string index, total length, attribute, // power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, - TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 200), // 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, 4, 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_HID_DESCRIPTOR(ITF_NUM_LED, 5, HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_led), EPNUM_LED, + CFG_TUD_HID_EP_BUFSIZE, 4), + + TUD_HID_DESCRIPTOR(ITF_NUM_NKRO, 6, HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_nkro), EPNUM_KEY, + CFG_TUD_HID_EP_BUFSIZE, 1), + + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 7, EPNUM_CDC_NOTIF, + 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) +}; // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { - (void)index; // for multiple configurations - return desc_configuration_joy; + return desc_configuration_joy; } //--------------------------------------------------------------------+ @@ -130,66 +161,61 @@ uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { const char *string_desc_arr[] = { (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) "WHowe" , // 1: Manufacturer - "Pico IIDX Controller", // 2: Product - "654321", // 3: Serials, should use chip ID - "Button 1", - "Button 2", - "Button 3", - "Button 4", - "Button 5", - "Button 6", - "Button 7", - "E1", - "E2", - "E3", - "E4" + "Mai Pico Controller", // 2: Product + "123456", // 3: Serials, should use chip ID + "Mai Pico Joystick", + "Mai Pico LED", + "Mai Pico NKRO", + "Mai Pico Serial Port", }; -static uint16_t _desc_str[64]; - // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long // enough for transfer to complete -uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { - (void)langid; - - uint8_t chr_count; - - if (index == 0) { - memcpy(&_desc_str[1], string_desc_arr[0], 2); - chr_count = 1; - } else { - // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. - // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors - - if (index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) { - const char* str = string_desc_arr[index]; - - // Cap at max char - chr_count = strlen(str); - if (chr_count > 63) chr_count = 63; - - // Convert ASCII string into UTF-16 - for (uint8_t i = 0; i < chr_count; i++) { - _desc_str[1 + i] = str[i]; - } - } else { - _desc_str[1] = 'X'; - chr_count = 1; - } - } - - - // first byte is length (including header), second byte is string type - _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); - - return _desc_str; -} - -void konami_mode() +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { - desc_device_joy.idVendor = 0x1ccf; - desc_device_joy.idProduct = 0x8048; - string_desc_arr[1] = "Konami Amusement"; - string_desc_arr[2] = "beatmania IIDX controller premium model"; + static uint16_t _desc_str[64]; + + if (index == 0) { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 + 2); + return _desc_str; + } + + const size_t base_num = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); + const char *colors[] = {"Blue", "Red", "Green"}; + char str[64]; + + if (index < base_num) { + strcpy(str, string_desc_arr[index]); + } else if (index < base_num + 48 + 45) { + const char *names[] = {"Key ", "Splitter "}; + int led = index - base_num; + int id = led / 6 + 1; + int type = led / 3 % 2; + int brg = led % 3; + sprintf(str, "%s%02d %s", names[type], id, colors[brg]); + } else if (index < base_num + 48 + 45 + 18) { + int led = index - base_num - 48 - 45; + int id = led / 3 + 1; + int brg = led % 3; + sprintf(str, "Tower %02d %s", id, colors[brg]); + } else { + sprintf(str, "Unknown %d", index); + } + + uint8_t chr_count = strlen(str); + if (chr_count > 63) { + chr_count = 63; + } + + // Convert ASCII string into UTF-16 + for (uint8_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); + + return _desc_str; } diff --git a/firmware/src/usb_descriptors.h b/firmware/src/usb_descriptors.h index c6021d8..cfc8029 100644 --- a/firmware/src/usb_descriptors.h +++ b/firmware/src/usb_descriptors.h @@ -5,10 +5,11 @@ #include "device/usbd.h" enum { - REPORT_ID_JOYSTICK = 1, - REPORT_ID_LIGHTS, - REPORT_ID_KEYBOARD, - REPORT_ID_MOUSE, + REPORT_ID_JOYSTICK = 1, + REPORT_ID_LED_touch_16 = 4, + REPORT_ID_LED_touch_15 = 5, + REPORT_ID_LED_TOWER_6 = 6, + REPORT_ID_LED_COMPRESSED = 11, }; // because they are missing from tusb_hid.h @@ -21,51 +22,120 @@ enum { // 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(13), \ - HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), HID_REPORT_COUNT(13), \ - HID_REPORT_SIZE(1), HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(16 - 13), /*Padding*/ \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_LOGICAL_MIN(0x00), \ - HID_LOGICAL_MAX_N(0x00ff, 2), /* Below is Joystick/analog */ \ - HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \ - HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX), \ - HID_USAGE(HID_USAGE_DESKTOP_RY), HID_USAGE(HID_USAGE_DESKTOP_RZ), \ - HID_REPORT_COUNT(6), HID_REPORT_SIZE(8), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), HID_COLLECTION_END +#define MAIPICO_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(16), \ + HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ + HID_REPORT_COUNT(16), HID_REPORT_SIZE(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_HAT_SWITCH), \ + HID_LOGICAL_MIN(1), HID_LOGICAL_MAX(8), \ + HID_PHYSICAL_MIN(0), HID_PHYSICAL_MAX_N(315, 2), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \ + HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), /* Analog */ \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(4), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + \ + HID_USAGE_PAGE_N(HID_USAGE_PAGE_VENDOR, 2), \ + HID_USAGE(0), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ + HID_INPUT(HID_DATA | 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_COUNT(11), /* LED NUM */ \ - HID_REPORT_SIZE(8), HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), HID_STRING_MINIMUM(4), \ - HID_STRING_MAXIMUM(16), 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), \ - HID_COLLECTION_END +#define MAIPICO_LED_HEADER \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ + HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE) -// NKRO Descriptor -#define GAMECON_REPORT_DESC_NKRO(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(HID_USAGE_PAGE_KEYBOARD), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_REPORT_SIZE(1), HID_REPORT_COUNT(8), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), HID_USAGE_MIN(224), \ - HID_USAGE_MAX(231), HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ - HID_INPUT(HID_VARIABLE), HID_REPORT_SIZE(1), HID_REPORT_COUNT(31 * 8), \ - HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), HID_USAGE_MIN(0), \ - HID_USAGE_MAX(31 * 8 - 1), HID_INPUT(HID_VARIABLE), HID_COLLECTION_END +#define MAIPICO_LED_FOOTER \ + HID_COLLECTION_END +// Slider First 16 LEDs (48 rgb zones, BRG order) +#define MAIPICO_REPORT_DESC_LED_touch_16 \ + HID_REPORT_ID(REPORT_ID_LED_touch_16) \ + HID_REPORT_COUNT(48), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(1), HID_USAGE_MAX(48), \ + HID_STRING_MINIMUM(8), HID_STRING_MAXIMUM(55), \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) -/* Enable Konami spoof mode */ -void konami_mode(); +// Slider Remaining 15 LEDs (45 rgb zones, BRG order) +#define MAIPICO_REPORT_DESC_LED_touch_15 \ + HID_REPORT_ID(REPORT_ID_LED_touch_15) \ + HID_REPORT_COUNT(45), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(49), HID_USAGE_MAX(93), \ + HID_STRING_MINIMUM(8), HID_STRING_MAXIMUM(52), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) -#endif /* USB_DESCRIPTORS_H_ */ \ No newline at end of file +// Tower LEDs (18 rgb zones, BRG order) +#define MAIPICO_REPORT_DESC_LED_TOWER_6 \ + HID_REPORT_ID(REPORT_ID_LED_TOWER_6) \ + HID_REPORT_COUNT(18), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(94), HID_USAGE_MAX(111), \ + HID_STRING_MINIMUM(8), HID_STRING_MAXIMUM(25), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// LEDs Compressed +#define MAIPICO_REPORT_DESC_LED_COMPRESSED \ + HID_REPORT_ID(REPORT_ID_LED_COMPRESSED) \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE(0x00), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(63), \ + HID_FEATURE(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +#define MAIPICO_REPORT_DESC_NKRO \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_KEYBOARD), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + /* Modifier */ \ + HID_REPORT_SIZE(1), \ + HID_REPORT_COUNT(8), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \ + HID_USAGE_MIN(224), \ + HID_USAGE_MAX(231), \ + HID_LOGICAL_MIN(0), \ + HID_LOGICAL_MAX(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + /* LED output that we don't care */ \ + HID_REPORT_COUNT(5), \ + HID_REPORT_SIZE(1), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_LED), \ + HID_USAGE_MIN(1), \ + HID_USAGE_MAX(5), \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_REPORT_COUNT(1), \ + HID_REPORT_SIZE(3), \ + HID_OUTPUT(HID_CONSTANT), \ + /* Full Keyboard Bitmap */ \ + HID_REPORT_SIZE(1), \ + HID_REPORT_COUNT(120), \ + HID_LOGICAL_MIN(0), \ + HID_LOGICAL_MAX(1), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \ + HID_USAGE_MIN(0), \ + HID_USAGE_MAX(119), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_COLLECTION_END + +// HID_REPORT_ID(REPORT_ID_NKRO) + +#endif /* USB_DESCRIPTORS_H_ */