Best commit message yet

This commit is contained in:
Bottersnike 2024-08-15 14:54:01 +01:00
parent c3ca4679d0
commit 8e21ab8b88
Signed by: Bottersnike
SSH Key Fingerprint: SHA256:3g0ghwd4dNX1k1RX8qazbiT+3RIYn/daeBevHZVCiU0
33 changed files with 1058 additions and 350 deletions

View File

@ -71,5 +71,6 @@
"bold": false,
"italic": false
}
]
],
"python.analysis.typeCheckingMode": "basic"
}

View File

@ -2,7 +2,8 @@ SRC_DIR := src
OBJ_DIR := obj
BINARY_NAME := host_aprom
OPTIM := -O0
# OPTIM := -O0
OPTIM := -O1
# OPTIM := -Os -flto
include GCC.mk
LIBRARY_MODULES := clk uart timer i2c

View File

@ -27,7 +27,9 @@ ENTRY(Reset_Handler)
SECTIONS {
.text : {
KEEP(*(.vectors))
. = . + 16;
KEEP(*(.compile_timestamp))
. = ALIGN(16);
/* KEEP(*(.init)) */
/* KEEP(*(.text._entry)) */
*(.text*)

View File

@ -2,16 +2,22 @@
See [Development](./Development.md) for development information.
Yes, the code is a mess. I just wanted to get something onto git y'know :).
Like what you see? [Buy me a coffee](https://ko-fi.com/bottersnike)
## Features
- Superior slider processing; no more dropped holds or missed notes!
- Native arcade IO support
- Air sensor anti-jitter (fixes dodgy sensors)
- Intuitive menus
- Arcade-accurate colours
And of course, it's all open source :).
## Setup
### Installing Firmware
- Disconnect the PC cable from the TASOLLER
- Hold down the FN2 button, while reconnecting the PC cable
- `HOSTMCU` should show as a device
- Use `Update V1.1.exe` from official firmware updates to load the firmware
Currently, Dao CFW is required on the LED board. This is controlled by `LED_FIRMWARE_CFW` in `src/led.h`.
- Download both `host_aprom.bin` and `led_aprom.bin`
- Run `TASOLLER-FirmwareUpdater.exe`
- Follow the instructions
### Configuring segatools
This firmware emulates arcade IO. As such, segatools' emulation should be disabled, by adding the following lines:

View File

@ -1,11 +0,0 @@
| | RGB | Approx HSV |
| ----------- | ------- | ----------- |
| Light gates | #910c45 | 334,92 ,57 |
| Cells: | #FEFE00 | 60 ,100,100 |
| Dividers | #FE00FE | 300,100,100 |
Something: FF 00 8B (FF) [Magenta]
Something: 30 0A 60 (FF) [Purple?]
Something: 2D 0A 5A (FF)
Something: 2B 0A 55 (FF)

View File

@ -71,6 +71,30 @@ Additionally, the following tests may be run for diagnostics:
xHSETT can be used to directly control the USB device, such as sending SUSPEND signals.
### Endpoint mapping
The NUC123 has 8 endpoints available. They are currently allocated as:
| Endpoint | Use | Direction | Buffer size |
| -------- | --------------- | --------- | ----------- |
| 0 | Control | IN | 64 |
| 1 | Control | OUT | 64 |
| 2 | Slider CDC Data | IN | 64 |
| 3 | Slider CDC Data | OUT | 64 |
| 4 | CDC Command | IN | 16 |
| 5 | IO4 HID | IN | 64 |
| 6 | Misc HID | IN | 64 |
| 7 | Misc HID | OUT | 64 |
The USB PHY has a 512 byte memory buffer. 16 bytes of this are reserved for setup packets. We currently use 464 bytes for endpoints, leaving 32 bytes unassigned.
amdaemon rejects HID packets with unknown report IDs, so endpoint 5 and 6 cannot be merged.
Endpoint 7 is currently unused, and could be bundled into endpoint 1 (as IO4 is currently doing).
To add CDC serial for the air towers for we would need 4 additional endpoints, which we don't have.
We can't use the NUC121 because it's muxed with the 123 :).
## Airs
See https://www.shinkoh-elecs.jp/wp-content/uploads/2024/05/C_KB1281_1581_24A.pdf

230
docs/Protocol_LED.md Normal file
View File

@ -0,0 +1,230 @@
# LED microcontroller communication protocol
The Host and LED controller are connected with a pair of wires that are on I2C-capable pins for each. They do not exclusively use I2C.
During the LED bootloader, PA10 and PA11 are read as digital inputs. If both are low, the LED bootloader enters programming mode. When the Host firmware starts, it reads FN2. If the button is depressed, the two lines are pulled low. Otherwise the Host begins operation as an I2C slave, pulling both lines high.
The Host operates as the slave, as the LED microcontroller writing to the LEDs is a timing-critical that cannot be interrupted.
## General comments
The TASOLLER has 48 LEDs on the ground slider (on two 24-LED PCBs) and 24 LEDs in each tower.
The 48 ground LEDs, while individually controllable by the LED firmware, are not exposed to the Host by this protocol. Instead, 31 "logical" LEDs are exposed. The 48th LED is obscured by the housing of the controller, and as such is always left unlit. Each cell has two LEDs and each divider has one, giving 16*2+15 = 31.
All RGB and HSV colours are transmitted as in the following structures:
```c
typedef struct grb {
uint8_t g;
uint8_t r;
uint8_t b;
} grb_t;
typedef struct hsv {
uint16_t h;
uint8_t s;
uint8_t v;
} hsv_t;
```
## Stock protocol
In the stock protocol, the Host exposes 256 single-byte registers. The LED microcontroller reads register 0 to get the command byte, then subsequently reads the appropriate number of registers for the given command.
Stock firmware implements two commands. `A5` is a basic LED write based on a number of parameters. `5A` writes raw RGB data to all LEDs. Factory-stock and mainland "custom" firmware implement slight variants of both.
### Basic write (factory) [`A5`]
```c
{
uint8_t u8Cmd = 0xA5;
uint32_t u32Ground;
uint8_t u8TowerFill;
uint8_t u8HueLeft;
uint8_t u8HueRight;
uint8_t u8HueGround;
uint8_t u8HueGroundActive;
struct {
uint8_t bTowersOff: 1;
uint8_t bGroundOff: 1;
uint8_t bSeparators: 2;
uint8_t Rsv: 1;
uint8_t bKeyMode: 3;
};
uint8_t u8LedSpecial;
}
```
| Parameter | Value |
| ------------------- | ---------------------------------------------------------- |
| `u32Ground` | 32 bits indicating boolean state of every pad |
| `u8TowerFill` | Bits 0 through 5 indicate the state of the air sensors |
| `u8HueLeft` | Hue of the left tower |
| `u8HueRight` | Hue of the right tower |
| `u8HueGround` | Hue of the slider cells |
| `u8HueGroundActive` | Hue of the slider cells when active, and dividers |
| `bTowersOff` | Disable tower LEDs |
| `bGroundOff` | Disable ground LEDs |
| `bSeparators` | Number of separating dividers |
| `bKeyMode` | How to group cells when lighting them based on `u32Ground` |
| `u8LedSpecial` | Performs specific functions as described below |
`bSeparators` enumeration:
- `0`: Divider every 4 cells (creates 4 sections)
- `1`: Divider every 2 cells (creates 8 sections)
- `2`: Divider every 1 cell (creates 16 sections)
- `3`: No dividers
`u8LedSpecial` enumeration:
- `0x00`: Normal operation (all other values ignore ground data)
- `0x01`: [Stock] Uses colours. Bar 1 full (from right)
- `0x02`: [Stock] Uses colours. Bar 2 full (from right)
- `0x03`: [Stock] Uses colours. Bar 3 full (from right)
- `0x04`: [Stock] Uses colours. Bar 4 full (from right)
- `0x1X`: [Stock] All white with black gaps. X bars black (from right)
- `0x80`: [Stock] Flashes three times
All hues are divided by 5, such that 360° = `72`
### Basic write (mainland) [`A5`]
```c
{
uint8_t u8Cmd = 0xA5;
uint32_t u32Ground;
uint8_t u8TowerFill;
struct {
uint8_t bInvertRainbow: 1;
uint8_t bRainbowSeparator: 7;
};
uint8_t Rsv07[3];
struct {
bTowersOff: 1;
bGroundOff: 1;
Rsv: 6;
};
uint8_t u8LedSpecial;
}
```
| Parameter | Value |
| ------------------- | ---------------------------------------------- |
| `bDisplayRainbow ` | Displays a rainbow across the ground LEDs |
| `bRainbowSeparator` | The number of separator LEDs to light |
| `u8LedSpecial` | Performs specific functions as described below |
Other parameters function as in factory firmware.
`bRainbowSeparator` will light separators from 1 through (x+1). Maximum value 14.
`u8LedSpecial` enumeration:
- `0x00`: Normal operation (all other values ignore ground data)
- `0x1X`: Rainbow with black gaps. X bars black (from right)
- `0x20`: Flashes three times
### RGB write (factory) [`5A`]
```c
struct {
uint8_t u8Cmd = 0x5A;
grb_t aGround[31];
grb_t aTowerLeft[24];
grb_t aTowerRight[24];
}
```
Writes raw RGB data to all LEDs. Colours are transmitted in GRB format.
### RGB write (mainland) [`5A`]
```c
struct {
uint8_t u8Cmd = 0x5A;
struct {
bTowersOff: 1;
bGroundOff: 1;
u8LedSpecial: 6;
} u8Config;
grb_t aGround[31];
grb_t aTowerLeft[24];
grb_t aTowerRight[24];
}
```
Writes raw RGB data to all LEDs. Colours are transmitted in GRB format.
See "Basic write (mainland)" for documentation of `u8LedSpecial`.
## Custom firmware protocol
In our custom protocol packets larger than 256 bytes need transmitted. To retain compatibility with the stock protocol, the Host still only exposes registers with an 8-bit address. Register `FF` however is reserved. Reading from register `FF` will begin a multiple read starting at address `0` but continuing until the complete buffer has been transferred, which may be more than 256 bytes.
The RGB write packet is implemented as in stock factory firmware. The Basic write packet is not implemented.
The LED microcontroller polls the Host for a packet once approximately every millisecond. This should not be relied upon as a guarantee.
The following additional packets are implemented:
### RGB write (custom) [`5B`]
```c
struct {
uint8_t u8Cmd = 0x5B;
uint8_t u8GroundBrightness;
uint8_t u8TowerBrightness;
grb_t aGround[31];
grb_t aTowerLeft[24];
grb_t aTowerRight[24];
}
```
### HSV write [`5C`]
```c
struct {
uint8_t u8Cmd = 0x5C;
uint8_t u8GroundBrightness;
uint8_t u8TowerBrightness;
hsv_t aGround[31];
hsv_t aTowerLeft[24];
hsv_t aTowerRight[24];
};
```
### Mixed RGB and HSV write [`5D`]
This packet transmits RGB data for the ground slider, but HSV for the towers. The rationale here is that the game sends data in RGB for the slider, which we use, but for the towers we wish to continue using our custom lighting.
```c
struct {
uint8_t u8Cmd = 0x5C;
uint8_t u8GroundBrightness;
uint8_t u8TowerBrightness;
grb_t aGround[31];
hsv_t aTowerLeft[24];
hsv_t aTowerRight[24];
};
```
### Flash read [`5E`]
Read 32 bytes from flash at the provided offset. The LED microcontroller will respond to this packet by writing four bytes, starting at address 0 (in a single transfer).
```c
struct {
uint8_t u8Cmd = 0x5E;
uint32_t u32Offset;
};
```
### Enter LDROM [`5F`]
Instruct the LED microcontroller to enter LDROM. After transmission of this packet the Host microcontroller should transition to driving both I2C lines low if it wishes the LED bootloader to enter programming mode.
Alternatively, the Host may drive both I2C low at any time outside of an I2C transmission, and the LED microcontroller will enter LDROM. This check is performed before the LED microcontroller begins its I2C read, and as such occurs at the same frequency.
```c
struct {
uint8_t u8Cmd = 0x5F;
};
```

22
docs/Protocol_PSoC.md Normal file
View File

@ -0,0 +1,22 @@
# PSoC communication protocol
Commands to and from the PSoC follow the structure
```c
{
uint8_t bCommand;
uint8_t bLength;
uint8_t bData0;
uint8_t bData1;
uint8_t bChecksum;
}
```
`bChecksum` is the sum of the preceding four bytes.
For requests, `bLength` is always `2`, and this is validated aggressively on the PSoC.
**There is no synchronisation mechanism** and the existing PSoC implementation is brittle. If sync is lost between the PSoC and Host this is unrecoverable without a reset.
If the appropriate debug flags have been set on the PSoC, it will transmit `00` before calling `CapSense_Start(); CapSense_InitializeAllBaselines()`, and `FF` afterwards. These bytes do not follow the standard packet structure.
The PSoC transmits `C1` once when it has completed its first initialisation. This does not follow the standard packet structure. There is no mechanism to hard reset the PSoC, so if the Host resets for any reason other than a hardware power cycle, this byte will not be observed.

2
docs/Protocols.md Normal file
View File

@ -0,0 +1,2 @@
# TASOLLER Protocols
The TASOLLER is comprised of four ICs which all communicate between each other.

View File

@ -37,4 +37,4 @@ clean:
$(shell mkdir $(OBJ_DIR))
.PHONY: all clean
.PHONY: all clean $(SRC_DIR)/timestamp.c

View File

@ -1,41 +1,42 @@
#include <stddef.h>
#include "sys/syslimits.h"
#include "tasoller.h"
#define __
static const uint8_t IO4_ReportDescriptor[] = {
// Analog input (28 bytes)
HID_USAGE_PAGE(GENERIC_DESKTOP),
HID_USAGE(JOYSTICK),
HID_COLLECTION(APPLICATION),
HID_REPORT_ID(HID_REPORT_ID_IO4),
HID_USAGE(POINTER),
HID_COLLECTION(PHYSICAL),
__ HID_REPORT_ID(HID_REPORT_ID_IO4),
__ HID_USAGE(POINTER),
__ HID_COLLECTION(PHYSICAL),
// 8 ADC channels
HID_USAGE(X),
HID_USAGE(Y),
HID_USAGE(X),
HID_USAGE(Y),
HID_USAGE(X),
HID_USAGE(Y),
HID_USAGE(X),
HID_USAGE(Y),
__ __ HID_USAGE(X),
__ __ HID_USAGE(Y),
__ __ HID_USAGE(X),
__ __ HID_USAGE(Y),
__ __ HID_USAGE(X),
__ __ HID_USAGE(Y),
__ __ HID_USAGE(X),
__ __ HID_USAGE(Y),
// 4 Rotary channels
HID_USAGE(RX),
HID_USAGE(RY),
HID_USAGE(RX),
HID_USAGE(RY),
__ __ HID_USAGE(RX),
__ __ HID_USAGE(RY),
__ __ HID_USAGE(RX),
__ __ HID_USAGE(RY),
// 2 Coin chutes
HID_USAGE(SLIDER),
HID_USAGE(SLIDER),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(4, 65534),
HID_PHYSICAL_MINIMUM(1, 0),
HID_PHYSICAL_MAXIMUM(4, 65534),
HID_REPORT_COUNT(14),
HID_REPORT_SIZE(16),
HID_INPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
HID_END_COLLECTION(PHYSICAL),
__ __ HID_USAGE(SLIDER),
__ __ HID_USAGE(SLIDER),
__ __ HID_LOGICAL_MINIMUM(1, 0),
__ __ HID_LOGICAL_MAXIMUM(4, 65534),
__ __ HID_PHYSICAL_MINIMUM(1, 0),
__ __ HID_PHYSICAL_MAXIMUM(4, 65534),
__ __ HID_REPORT_COUNT(14),
__ __ HID_REPORT_SIZE(16),
__ __ HID_INPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
__ HID_END_COLLECTION(PHYSICAL),
// Digital input (6 bytes = 48 bits)
// [ 0~15]: Player 1 buttons
// [16~31]: Player 2 buttons
@ -50,91 +51,142 @@ static const uint8_t IO4_ReportDescriptor[] = {
// -> 01h: ?
// -> 02h: ?
// -> 04h: ? (is set on timeout)
HID_USAGE_PAGE(SIMULATION),
HID_USAGE_PAGE(BUTTONS),
HID_USAGE_MINIMUM(1, 1),
HID_USAGE_MAXIMUM(1, 48),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(1, 1),
HID_PHYSICAL_MAXIMUM(1, 1),
HID_REPORT_SIZE(1),
HID_REPORT_COUNT(48),
HID_INPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
__ HID_USAGE_PAGE(SIMULATION),
__ HID_USAGE_PAGE(BUTTONS),
__ HID_USAGE_MINIMUM(1, 1),
__ HID_USAGE_MAXIMUM(1, 48),
__ HID_LOGICAL_MINIMUM(1, 0),
__ HID_LOGICAL_MAXIMUM(1, 1),
__ HID_PHYSICAL_MAXIMUM(1, 1),
__ HID_REPORT_SIZE(1),
__ HID_REPORT_COUNT(48),
__ HID_INPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
// Reserved for future use. Pad with null. (29 bytes)
HID_USAGE(UNDEFINED),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(29),
HID_INPUT(CONSTANT, ARRAY, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
HID_USAGE_PAGE2(2, 0xFFA0), // Vendor defined FF0A
HID_USAGE(UNDEFINED),
__ HID_USAGE(UNDEFINED),
__ HID_REPORT_SIZE(8),
__ HID_REPORT_COUNT(29),
__ HID_INPUT(CONSTANT, ARRAY, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
__ HID_USAGE_PAGE2(2, 0xFFA0), // Vendor defined FF0A
__ HID_USAGE(UNDEFINED),
// General-purpose commands to the board. First byte is the command, then 62 data byte
HID_REPORT_ID(HID_REPORT_ID_IO4_CMD),
HID_COLLECTION(APPLICATION),
HID_USAGE(UNDEFINED),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(2, 255),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(63),
HID_OUTPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION,
__ HID_REPORT_ID(HID_REPORT_ID_IO4_CMD),
__ HID_COLLECTION(APPLICATION),
__ __ HID_USAGE(UNDEFINED),
__ __ HID_LOGICAL_MINIMUM(1, 0),
__ __ HID_LOGICAL_MAXIMUM(2, 255),
__ __ HID_REPORT_SIZE(8),
__ __ HID_REPORT_COUNT(63),
__ __ HID_OUTPUT(DATA, VARIABLE, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION,
NON_VOLATILE),
HID_END_COLLECTION(APPLICATION),
__ HID_END_COLLECTION(APPLICATION),
HID_END_COLLECTION(APPLICATION),
};
#define _FINGER_DESCRIPTOR \
HID_USAGE(FINGER), /**/ \
HID_COLLECTION(LOGICAL), /**/ \
HID_USAGE(TIP_SWITCH), /**/ \
HID_LOGICAL_MINIMUM(1, 0), /**/ \
HID_LOGICAL_MAXIMUM(1, 1), /**/ \
HID_REPORT_SIZE(1), /**/ \
HID_REPORT_COUNT(1), /**/ \
HID_INPUT(DATA, VARIABLE, ABSOLUTE), /**/ \
HID_REPORT_COUNT(7), /**/ \
HID_INPUT(CONSTANT, ARRAY, ABSOLUTE), /**/ \
HID_REPORT_SIZE(8), /**/ \
HID_USAGE(CONTACT_IDENTIFIER), /**/ \
HID_REPORT_COUNT(1), /**/ \
HID_INPUT(DATA, VARIABLE, ABSOLUTE), /**/ \
HID_USAGE_PAGE(GENERIC_DESKTOP), /**/ \
HID_LOGICAL_MAXIMUM(2, 127), /**/ \
HID_REPORT_SIZE(8), /**/ \
HID_REPORT_COUNT(2), /**/ \
HID_PHYSICAL_MINIMUM(1, 0), /**/ \
HID_PHYSICAL_MAXIMUM(2, 127), /**/ \
HID_USAGE(X), /**/ \
HID_USAGE(Y), /**/ \
HID_INPUT(DATA, VARIABLE, ABSOLUTE), /**/ \
HID_USAGE_PAGE(DIGITIZER), /**/ \
HID_USAGE(WIDTH), /**/ \
HID_USAGE(HEIGHT), /**/ \
HID_INPUT(DATA, VARIABLE, ABSOLUTE), /**/ \
HID_END_COLLECTION(LOGICAL)
static const uint8_t Keyboard_ReportDescriptor[] = {
// Keyboard input report
HID_USAGE_PAGE(GENERIC_DESKTOP),
HID_USAGE(KEYBOARD),
HID_COLLECTION(APPLICATION),
HID_REPORT_ID(HID_REPORT_ID_KEYBOARD),
HID_USAGE_PAGE(KEYBOARD),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(2, 231),
HID_USAGE_MINIMUM(1, 0),
HID_USAGE_MAXIMUM(1, 231),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(NUM_FN + NUM_AIR + NUM_GROUND),
HID_INPUT(DATA, ARRAY, ABSOLUTE),
__ HID_REPORT_ID(HID_REPORT_ID_KEYBOARD),
__ HID_USAGE_PAGE(KEYBOARD),
__ HID_LOGICAL_MINIMUM(1, 0),
__ HID_LOGICAL_MAXIMUM(2, 231),
__ HID_USAGE_MINIMUM(1, 0),
__ HID_USAGE_MAXIMUM(1, 231),
__ HID_REPORT_SIZE(8),
__ HID_REPORT_COUNT(NUM_FN + NUM_AIR + NUM_GROUND),
__ HID_INPUT(DATA, ARRAY, ABSOLUTE),
HID_END_COLLECTION(APPLICATION),
// Consumer control report
HID_USAGE_PAGE(CONSUMER),
HID_USAGE(CONSUMER_CONTROL),
HID_COLLECTION(APPLICATION),
HID_REPORT_ID(HID_REPORT_ID_CONSUMER_CONTROL),
HID_USAGE_PAGE(CONSUMER),
HID_USAGE_MINIMUM(1, 0),
HID_USAGE_MAXIMUM(2, 0x0FFF),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(2, 0x0FFF),
HID_REPORT_SIZE(16),
HID_REPORT_COUNT(2),
HID_INPUT(DATA, ARRAY, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
__ HID_REPORT_ID(HID_REPORT_ID_CONSUMER_CONTROL),
__ HID_USAGE_PAGE(CONSUMER),
__ HID_USAGE_MINIMUM(1, 0),
__ HID_USAGE_MAXIMUM(2, 0x0FFF),
__ HID_LOGICAL_MINIMUM(1, 0),
__ HID_LOGICAL_MAXIMUM(2, 0x0FFF),
__ HID_REPORT_SIZE(16),
__ HID_REPORT_COUNT(2),
__ HID_INPUT(DATA, ARRAY, ABSOLUTE, NO_WRAP, LINEAR, PREFERRED_STATE, NO_NULL_POSITION),
HID_END_COLLECTION(APPLICATION),
// Report for sending the enter key
HID_USAGE_PAGE(GENERIC_DESKTOP),
HID_USAGE(KEYBOARD),
HID_COLLECTION(APPLICATION),
HID_REPORT_ID(HID_REPORT_ID_ENTER),
__ HID_REPORT_ID(HID_REPORT_ID_ENTER),
__ HID_USAGE_PAGE(KEYBOARD),
__ HID_LOGICAL_MINIMUM(1, 0),
__ HID_LOGICAL_MAXIMUM(2, 231),
__ HID_USAGE_MINIMUM(1, 0),
__ HID_USAGE_MAXIMUM(1, 231),
__ HID_REPORT_SIZE(8),
__ HID_REPORT_COUNT(1),
__ HID_INPUT(DATA, ARRAY, ABSOLUTE),
HID_END_COLLECTION(APPLICATION),
HID_USAGE_PAGE(KEYBOARD),
HID_LOGICAL_MINIMUM(1, 0),
HID_LOGICAL_MAXIMUM(2, 231),
HID_USAGE_MINIMUM(1, 0),
HID_USAGE_MAXIMUM(1, 231),
HID_REPORT_SIZE(8),
#ifdef ENABLE_TOUCH_INPUT
// Touch input report
HID_USAGE_PAGE(DIGITIZER),
HID_USAGE(TOUCH_SCREEN),
HID_COLLECTION(APPLICATION),
HID_REPORT_ID(HID_REPORT_ID_TOUCH),
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
_FINGER_DESCRIPTOR,
HID_USAGE_PAGE(DIGITIZER),
HID_USAGE(CONTACT_COUNT),
HID_LOGICAL_MAXIMUM(1, 127),
HID_REPORT_COUNT(1),
HID_INPUT(DATA, ARRAY, ABSOLUTE),
HID_REPORT_SIZE(8),
HID_INPUT(DATA, VARIABLE, ABSOLUTE),
// Aren't we meant to have contact count maximum? (id 55h)
HID_END_COLLECTION(APPLICATION),
#endif
};
usb_device_descr_t gIO4DeviceDescriptor = {
@ -179,8 +231,8 @@ typedef struct __packed {
* We're meant to have an OUT endpoint for IO4, but IO4 uses the Win32 WriteFile API, which will
* happily fall back to SET_REPORT calls on the control endpoints if there's no OUT endpoint.
*
* Chunithm never uses the output capability of IO4, so we have no high-frequency data that
* might choke our control endpoints--we just need to support the initial configuration packets.
* IO4 receives OUT packets at a 47ms interval on average, 25%=46.8ms, 75%=61.3ms
* As such, we're not going to saturate our control endpoint by bootlegging off it for HID!
*/
// const usb_desc_endpoint_t HID_EndpointOut;
@ -316,7 +368,7 @@ static const config_desc_t gConfigDescriptor = {
DESC_ENDPOINT,
USBD_HID_IO4_EP_IN,
EP_INT,
USBD_HID_BUF_LEN,
USBD_HID_BUF_LEN_IO4,
HID_IO4_INT_IN_INTERVAL,
},
@ -346,7 +398,7 @@ static const config_desc_t gConfigDescriptor = {
DESC_ENDPOINT,
USBD_HID_MISC_EP_IN,
EP_INT,
USBD_HID_BUF_LEN,
USBD_HID_BUF_LEN_IN,
HID_DEFAULT_INT_IN_INTERVAL,
},
{
@ -354,7 +406,7 @@ static const config_desc_t gConfigDescriptor = {
DESC_ENDPOINT,
USBD_HID_MISC_EP_OUT,
EP_INT,
USBD_HID_BUF_LEN,
USBD_HID_BUF_LEN_OUT,
HID_DEFAULT_INT_IN_INTERVAL,
},
};

View File

@ -56,14 +56,21 @@ void FMC_EEPROM_Load(void) {
gConfig.u8Sens = 8;
gConfig.u16HueWingLeft = 330;
gConfig.u16HueWingRight = 180;
// TODO: These are the DJ DAO defaults, but the game looks like the hue should be 60 and 300
gConfig.u16HueGround = 45;
gConfig.u16HueTowerLeft = 330;
gConfig.u16HueTowerRight = 180;
#ifdef LED_CORRECTION_ON_LED_MCU
gConfig.u16HueGround = 60;
gConfig.u16HueGroundActive = 300;
#else
// The game uses 60° and 300°. Our red channel is approximately half as strong as the game,
// so by shifting 30° we get the appearance of correct colours.
gConfig.u16HueGround = 30;
gConfig.u16HueGroundActive = 330;
#endif
gConfig.u8LedGroundBrightness = 255;
gConfig.u8LedWingBrightness = 255;
gConfig.u8LedTowerBrightness = 255;
for (uint8_t i = 0; i < 32; i++) {
gConfig.u16PSoCScaleMin[i] = 0;
@ -72,6 +79,11 @@ void FMC_EEPROM_Load(void) {
bConfigDirty = 1;
}
// If it's an invalid value, it's probably from uninitialized flash; don't boot to the
// bootloader!
if (!(gConfig.u8NextBootLEDBootloader == 0 || gConfig.u8NextBootLEDBootloader == 1)) {
gConfig.u8NextBootLEDBootloader = 0;
}
FMC_Close();
}

View File

@ -44,17 +44,20 @@ typedef struct __attribute__((aligned(4), packed)) {
// Only needs 4 bits but we aren't short atm
uint8_t u8Sens; // [1~16], Higher = more sensitive
uint16_t u16HueWingLeft;
uint16_t u16HueWingRight;
uint16_t u16HueTowerLeft;
uint16_t u16HueTowerRight;
uint16_t u16HueGround;
uint16_t u16HueGroundActive;
uint8_t u8LedGroundBrightness;
uint8_t u8LedWingBrightness;
uint8_t u8LedTowerBrightness;
// Calibration data
uint16_t u16PSoCScaleMin[32];
uint16_t u16PSoCScaleMax[32];
// State data
uint8_t u8NextBootLEDBootloader;
} flash_t;
extern flash_t gConfig;
extern uint8_t bConfigDirty;

136
src/hid.c
View File

@ -129,10 +129,140 @@ static uint8_t _HID_Enter_Tick(void) {
return 1;
}
#ifdef ENABLE_TOUCH_INPUT
enum {
u8MaxFingers = (sizeof((hid_touch_report_t *)(0))->sFinger) /
(sizeof((hid_touch_report_t *)(0))->sFinger[0])
};
#define OVERLAP(start1, end1, start2, end2) (Minimum((end2), (end1)) >= Maximum((start1), (start2)))
typedef struct {
uint8_t bActive;
uint8_t u8Start;
uint8_t u8End;
uint8_t u8FakeCentreX;
uint8_t u8FakeCentreY;
} tracked_finger_t;
tracked_finger_t trackedFingers[u8MaxFingers] = { 0 };
#define SCREEN_HEIGHT 127
#define SCREEN_WIDTH 128
// Definition for Sonolus on my Pixel 6
// #define LEFT_EDGE 28
// #define RIGHT_EDGE 20
// Definition for PJSK on my Pixel 6
// #define LEFT_EDGE 22
// #define RIGHT_EDGE 20
#define LEFT_EDGE 0
#define RIGHT_EDGE 0
#define VERTICAL_MOVEMENT 10
#define SCREEN_Y (SCREEN_HEIGHT / 5)
#define TOUCH_WIDTH ((SCREEN_WIDTH - LEFT_EDGE - RIGHT_EDGE) / 16)
static void _TrackTouches(void) {
static tracked_finger_t newFingers[u8MaxFingers];
memset(newFingers, 0, sizeof newFingers);
uint8_t nFingers = 0;
int8_t iStart = -1;
static struct {
uint16_t u16Bias;
uint8_t u8Pos;
} sBiasCalc[16] = { 0 };
uint16_t u16BiasTop = 0;
uint16_t u16BiasBottom = 0;
// Identify our currently active fingers
for (uint8_t x = 0; x <= 16; x++) {
if ((x < 16) && gu16PSoCDigital & (1 << x)) {
if (iStart == -1) {
u16BiasTop = 0;
u16BiasBottom = 0;
iStart = x;
}
// Position = [LEFT_EDGE, {scale based on X} , RIGHT_EDGE] = SCREEN_WIDTH
// Virtual width = SCREEN_WIDTH - LEFT_EDGE - RIGHT_EDGE
// L + ((W-L-R) * (16 - x)) / 16)
// TODO: Might need -1 here not +1
sBiasCalc[x - iStart].u8Pos = LEFT_EDGE + ((SCREEN_WIDTH - LEFT_EDGE - RIGHT_EDGE) * (32 - (x * 2 + 1))) / 32;
// sBiasCalc[x - iStart].u8Pos -=
// (LEFT_EDGE + ((16 - x) * TOUCH_WIDTH) - (TOUCH_WIDTH / 2));
sBiasCalc[x - iStart].u16Bias = gu8GroundData[x * 2] + gu8GroundData[x * 2 + 1];
u16BiasTop += gu8GroundData[x * 2];
u16BiasBottom += gu8GroundData[x * 2 + 1];
} else {
if (iStart != -1) {
newFingers[nFingers].bActive = 1;
newFingers[nFingers].u8Start = iStart;
newFingers[nFingers].u8End = x - 1;
uint32_t u32TotalWeight = 0;
uint64_t u64Biased = 0;
for (uint8_t i = 0; i < x - iStart; i++) {
u32TotalWeight += (uint32_t)sBiasCalc[i].u16Bias;
u64Biased += (uint64_t)sBiasCalc[i].u8Pos * (uint64_t)sBiasCalc[i].u16Bias;
}
newFingers[nFingers].u8FakeCentreX = u64Biased / u32TotalWeight;
newFingers[nFingers].u8FakeCentreY = (u16BiasTop * VERTICAL_MOVEMENT) / (u16BiasTop + u16BiasBottom);
nFingers++;
iStart = -1;
}
}
if (nFingers >= u8MaxFingers) break;
}
memcpy(trackedFingers, newFingers, sizeof newFingers);
}
static uint8_t _HID_Touch_Tick(void) {
hid_touch_report_t *buf = (hid_touch_report_t *)HID_MISC_BUF;
memset(buf, 0, sizeof *buf);
buf->bReportId = HID_REPORT_ID_TOUCH;
_TrackTouches();
// When in portrait, X and Y make sense
// In landscape, X and Y are inverse (ie screen rotation is ignored)
uint8_t nFingers = 0;
for (uint8_t i = 0; i < u8MaxFingers; i++) {
buf->sFinger[nFingers].bTipSwitch = trackedFingers[i].bActive;
buf->sFinger[nFingers].bIdentifier = i;
if (trackedFingers[i].bActive) {
uint8_t width = (trackedFingers[i].u8End + 1) - trackedFingers[i].u8Start;
buf->sFinger[nFingers].bX = SCREEN_Y + trackedFingers[i].u8FakeCentreY;
buf->sFinger[nFingers].bY = trackedFingers[i].u8FakeCentreX;
buf->sFinger[nFingers].bW = TOUCH_WIDTH / 2;
buf->sFinger[nFingers].bH = width * TOUCH_WIDTH;
}
nFingers++;
}
buf->bContactCount = nFingers;
HID_MISC_SEND(buf);
return 0;
}
#endif
typedef enum {
TIMESLOT_KEYBOARD = 0,
TIMESLOT_CONSUMER,
TIMESLOT_ENTER,
#ifdef ENABLE_TOUCH_INPUT
TIMESLOT_TOUCH,
#endif
_TIMESLOT_COUNT,
} eTimeslot_t;
static void _HID_Misc_Tick(void) {
@ -166,6 +296,12 @@ static void _HID_Misc_Tick(void) {
if (_HID_Enter_Tick()) goto timeslot_used;
break;
#ifdef ENABLE_TOUCH_INPUT
case TIMESLOT_TOUCH:
if (_HID_Touch_Tick()) goto timeslot_used;
break;
#endif
default:
break;
}

View File

@ -16,6 +16,7 @@ enum {
HID_REPORT_ID_KEYBOARD,
HID_REPORT_ID_CONSUMER_CONTROL,
HID_REPORT_ID_ENTER,
HID_REPORT_ID_TOUCH,
HID_REPORT_ID_IO4_CMD = 16,
};
@ -37,6 +38,40 @@ typedef struct __packed {
uint8_t bReportId;
uint8_t u8Keyboard[1];
} hid_enter_report_t;
typedef struct __packed {
uint8_t bReportId;
/*
uint8_t bTipSwitch : 1;
uint8_t _1_3 : 3;
uint8_t bInRange : 1;
// uint8_t bConfidence : 1;
uint16_t _5_11 : 11;
uint16_t wX;
uint16_t wY;
uint16_t wWidth;
uint16_t wHeight;
uint16_t _80_16 : 16;
*/
// uint8_t bTipSwitch : 1;
// uint8_t bInRange : 1;
// uint8_t _2_6 : 6;
// uint8_t _8;
// uint16_t wX;
// uint16_t wY;
struct {
uint8_t bTipSwitch : 1;
uint8_t _1_7 : 1;
uint8_t bIdentifier;
uint8_t bX;
uint8_t bY;
uint8_t bW;
uint8_t bH;
} sFinger[8];
uint8_t bContactCount;
} hid_touch_report_t;
typedef struct __packed {
uint8_t bReportId;

View File

@ -58,16 +58,6 @@ void IO4_HID_Tick(void) {
}
void IO4_Control(uint8_t u8Cmd, uint32_t u32Size, volatile uint8_t *pu8Buffer) {
/**
* Setup sequence:
*
* - IO4_CMD_CLEAR_BOARD_STATUS
* - IO4_CMD_SET_COMM_TIMEOUT (00)
* - IO4_CMD_SET_SAMPLING_COUNT (06)
* - IO4_CMD_SET_GENERAL_OUTPUT (00 00 00)
* - IO4_CMD_SET_UNIQUE_OUTPUT (38 00 00 00 ..)
*/
switch (u8Cmd) {
case IO4_CMD_SET_COMM_TIMEOUT:
if (u32Size >= 1) {

View File

@ -168,17 +168,17 @@ static const uint8_t _LED_GroundBrightness(void) {
}
return gConfig.u8LedGroundBrightness;
}
static const uint8_t _LED_WingBrightness(void) {
if (gbLedDataIsControlledInt) return gConfig.u8LedWingBrightness;
static const uint8_t _LED_TowerBrightness(void) {
if (gbLedDataIsControlledInt) return gConfig.u8LedTowerBrightness;
if (g_u8UsbState == USB_STATE_SUSPEND && gu32NowMs > 5000) return 0;
if (gbLedDataIsControlledExt)
return ((uint16_t)gConfig.u8LedGroundBrightness * (uint16_t)gu8IO4PWMScale) / 255;
return gConfig.u8LedWingBrightness;
return ((uint16_t)gConfig.u8LedTowerBrightness * (uint16_t)gu8IO4PWMScale) / 255;
return gConfig.u8LedTowerBrightness;
}
static inline void _LED_SetPower(void) {
PIN_LED_GROUND_PWR = _LED_GroundBrightness() ? 1 : 0;
PIN_LED_WING_PWR = _LED_WingBrightness() ? 1 : 0;
PIN_LED_TOWER_PWR = _LED_TowerBrightness() ? 1 : 0;
}
void LED_Write(void) {
@ -195,25 +195,25 @@ void LED_Write(void) {
if (gbLedDataIsControlledInt) {
pTxHSV->u8Cmd = LED_CMD_CUSTOM_HSV;
pTxHSV->u8GroundBrightness = _LED_GroundBrightness();
pTxHSV->u8WingBrightness = _LED_WingBrightness();
pTxHSV->u8TowerBrightness = _LED_TowerBrightness();
_LED_SetPower();
LED_Ground_Internal_HSV(pTxHSV->aGround);
LED_Wings_Reactive_HSV(&pTxHSV->Wings);
LED_Towers_Reactive_HSV(&pTxHSV->Towers);
} else if (gbLedDataIsControlledExt) {
// RGB control from the game
pTxRGB->u8Cmd = LED_CMD_CUSTOM_RGB;
pTxRGB->u8GroundBrightness = _LED_GroundBrightness();
pTxRGB->u8WingBrightness = _LED_WingBrightness();
pTxRGB->u8TowerBrightness = _LED_TowerBrightness();
_LED_SetPower();
LED_Ground_Controlled_RGB(pTxRGB->aGround);
LED_Wings_Controlled_RGB(&pTxRGB->Wings);
LED_Towers_Controlled_RGB(&pTxRGB->Towers);
} else {
// User-set coloring scheme
pTxHSV->u8Cmd = LED_CMD_CUSTOM_HSV;
pTxHSV->u8GroundBrightness = _LED_GroundBrightness();
pTxHSV->u8WingBrightness = _LED_WingBrightness();
pTxHSV->u8TowerBrightness = _LED_TowerBrightness();
_LED_SetPower();
if (gConfig.bEnableRainbow) {
@ -221,7 +221,7 @@ void LED_Write(void) {
} else {
LED_Ground_Static_HSV(pTxHSV->aGround);
}
LED_Wings_Reactive_HSV(&pTxHSV->Wings);
LED_Towers_Reactive_HSV(&pTxHSV->Towers);
}
}

View File

@ -19,22 +19,22 @@ extern volatile uint16_t u16I2CRxIndex;
extern uint8_t gbLedIsCustom;
// === Assigners (led_impl.c) ===
void LED_Wings_Reactive_HSV(_led_wings_hsv* pWings);
void LED_Towers_Reactive_HSV(_led_towers_hsv* pTowers);
void LED_Ground_Rainbow_HSV(hsv_t* aGround);
void LED_Ground_Static_HSV(hsv_t* aGround);
void LED_Ground_Internal_HSV(hsv_t* aGround);
/** All "controlled" data is in RGB format, so these aren't a thing
* void LED_Wings_Controlled_HSV(_led_wings_hsv* pWings);
* void LED_Towers_Controlled_HSV(_led_towers_hsv* pTowers);
* void LED_Ground_Controlled_HSV(hsv_t* aGround);
*/
// Unimplemented nonsense stuff
void LED_Wings_Reactive_RGB(_led_wings_rgb* pWings);
void LED_Ground_Rainbow_RGB(rgb_t* pWings);
void LED_Towers_Reactive_RGB(_led_towers_rgb* pTowers);
void LED_Ground_Rainbow_RGB(rgb_t* pTowers);
void LED_Ground_Internal_RGB(rgb_t* aGround);
void LED_Ground_Static_RGB(rgb_t* aGround);
// Actual RGBs that're used
void LED_Wings_Controlled_RGB(_led_wings_rgb* pWings);
void LED_Towers_Controlled_RGB(_led_towers_rgb* pTowers);
void LED_Ground_Controlled_RGB(rgb_t* aGround);
// === Exported functions ===

View File

@ -1,3 +1,5 @@
#include <stdlib.h>
#include "tasoller.h"
static inline uint8_t LED_ScaleU8(uint8_t u8V, uint8_t u8Scale) {
@ -10,7 +12,33 @@ static inline uint8_t LED_ScaleU8(uint8_t u8V, uint8_t u8Scale) {
#endif
}
static const uint8_t su8aWingSensors[LED_NUM_WING] = {
#define INDEX_IS_CELL(x) ((x) % 2 == 0)
#define INDEX_IS_ACTIVE_CELL(x) (gu16PSoCDigital & (1 << ((x) >> 1)))
#define INDEX_IS_SEPARATING_DIVIDER(x) (((x) >> 1) % 4 == 3)
#define INDEX_IS_LIT_DIVIDER(x) \
(gu16PSoCDigital & (1 << ((x) >> 1)) && gu16PSoCDigital & (1 << (((x) >> 1) + 1)))
typedef enum {
// A cell that's currently active
IndexType_CellActive,
// A cell that isn't currently active
IndexType_Cell,
// A divider that's being lit up because it's part of the visual grouping
IndexType_SeparatingDivider,
// A divider that's being lit up because both cells either side are active
IndexType_LitDivider,
// A divider that's not being lit up
IndexType_Divider,
} IndexType_t;
#define INDEX_TYPE(x) \
(IndexType_t)((INDEX_IS_CELL(x) \
? (INDEX_IS_ACTIVE_CELL(x) ? IndexType_CellActive : IndexType_Cell) \
: INDEX_IS_SEPARATING_DIVIDER(x) ? IndexType_SeparatingDivider \
: INDEX_IS_LIT_DIVIDER(x) ? IndexType_LitDivider \
: IndexType_Divider))
static const uint8_t su8aTowerSensors[LED_NUM_TOWER] = {
DIGITAL_AIR1_Msk, DIGITAL_AIR1_Msk, DIGITAL_AIR1_Msk, DIGITAL_AIR1_Msk, //
DIGITAL_AIR2_Msk, DIGITAL_AIR2_Msk, DIGITAL_AIR2_Msk, DIGITAL_AIR2_Msk, //
DIGITAL_AIR3_Msk, DIGITAL_AIR3_Msk, DIGITAL_AIR3_Msk, DIGITAL_AIR3_Msk, //
@ -21,92 +49,194 @@ static const uint8_t su8aWingSensors[LED_NUM_WING] = {
#define SATURATION_ACTIVE 255
#define SATURATION_INACTIVE 240
#define VALUE_ACTIVE (gConfig.u8LedWingBrightness)
#define VALUE_INACTIVE (gConfig.u8LedWingBrightness / 2)
#define VALUE_ACTIVE (gConfig.u8LedTowerBrightness)
#define VALUE_INACTIVE (gConfig.u8LedTowerBrightness / 2)
void LED_Wings_Reactive_HSV(_led_wings_hsv* pWings) {
for (uint8_t i = 0; i < LED_NUM_WING; i++) {
// Left wing
if (gu8DigitalButtons & su8aWingSensors[LED_NUM_WING - i - 1]) {
pWings->aWingL[i].h = gConfig.u16HueWingLeft;
pWings->aWingL[i].s = SATURATION_ACTIVE;
pWings->aWingL[i].v = VALUE_ACTIVE;
void LED_Towers_Reactive_HSV(_led_towers_hsv* pTowers) {
for (uint8_t i = 0; i < LED_NUM_TOWER; i++) {
// Left tower
if (gu8DigitalButtons & su8aTowerSensors[LED_NUM_TOWER - i - 1]) {
pTowers->aTowerL[i].h = gConfig.u16HueTowerLeft;
pTowers->aTowerL[i].s = SATURATION_ACTIVE;
pTowers->aTowerL[i].v = VALUE_ACTIVE;
} else {
pWings->aWingL[i].h = gConfig.u16HueWingLeft;
pWings->aWingL[i].s = SATURATION_INACTIVE;
pWings->aWingL[i].v = VALUE_INACTIVE;
pTowers->aTowerL[i].h = gConfig.u16HueTowerLeft;
pTowers->aTowerL[i].s = SATURATION_INACTIVE;
pTowers->aTowerL[i].v = VALUE_INACTIVE;
}
// Right wing
if (gu8DigitalButtons & su8aWingSensors[i]) {
pWings->aWingR[i].h = gConfig.u16HueWingRight;
pWings->aWingR[i].s = SATURATION_ACTIVE;
pWings->aWingR[i].v = VALUE_ACTIVE;
// Right tower
if (gu8DigitalButtons & su8aTowerSensors[i]) {
pTowers->aTowerR[i].h = gConfig.u16HueTowerRight;
pTowers->aTowerR[i].s = SATURATION_ACTIVE;
pTowers->aTowerR[i].v = VALUE_ACTIVE;
} else {
pWings->aWingR[i].h = gConfig.u16HueWingRight;
pWings->aWingR[i].s = SATURATION_INACTIVE;
pWings->aWingR[i].v = VALUE_INACTIVE;
pTowers->aTowerR[i].h = gConfig.u16HueTowerRight;
pTowers->aTowerR[i].s = SATURATION_INACTIVE;
pTowers->aTowerR[i].v = VALUE_INACTIVE;
}
}
}
void LED_Ground_Rainbow_HSV(hsv_t* aGround) {
// 5 ticks * 360 hue = 1800 calls for one cycle (1.8s)
static uint16_t u16Hue = 0;
static struct {
uint8_t value;
uint8_t direction;
} twinkle[LED_NUM_GROUND_LOGICAL] = { 0 };
// A true blue or true green will have a different brightness to other colours (currently) so
// avoid that awkward discoloration.
static const uint16_t u16HueOffset = 2;
static uint8_t u8Ticker = 0;
if (++u8Ticker == 5) {
u8Ticker = 0;
u16Hue++;
if (u16Hue == LED_HUE_MAX) u16Hue = 0;
// 5 ticks * 360 hue = 1800 calls for one cycle (1.8s)
// u16HueOffset++ or smth, I forgot
// Twinkle animation
// for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
// if (twinkle[i].direction) {
// if (rand() < RAND_MAX / 256) {
// twinkle[i].direction = 0;
// }
// } else {
// if (rand() < RAND_MAX / 512) {
// twinkle[i].direction = 1;
// }
// }
//
// if (twinkle[i].direction) {
// if (twinkle[i].value < 255) twinkle[i].value++;
// } else {
// if (twinkle[i].value > 0) twinkle[i].value--;
// }
// }
// Fade-out animation
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
switch (INDEX_TYPE(i)) {
case IndexType_CellActive:
// u8New = (gu8GroundData[(i >> 1) * 2] / 2 + gu8GroundData[(i >> 1) * 2 + 1] /
// 2); if (u8New > twinkle[i].value) twinkle[i].value = u8New; break;
case IndexType_LitDivider:
// u8New =
// (gu8GroundData[(i >> 1) * 2] / 4 + gu8GroundData[(i >> 1) * 2 + 1] / 4 +
// gu8GroundData[(i >> 1) * 2 + 2] / 4 + gu8GroundData[(i >> 1) * 2 + 3] /
// 4);
// u8New = 255;
// if (u8New > twinkle[i].value) twinkle[i].value = u8New;
twinkle[i].value = 255;
break;
default:
if (twinkle[i].value > 200)
twinkle[i].value -= 8;
else if (twinkle[i].value > 150)
twinkle[i].value -= 6;
else if (twinkle[i].value > 100)
twinkle[i].value -= 4;
else if (twinkle[i].value > 50)
twinkle[i].value -= 1;
if (twinkle[i].value < 50) twinkle[i].value = 50;
break;
}
}
}
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
uint16_t h = 0;
uint8_t v = 190;
uint8_t nCell = i >> 1;
if (i % 2 == 0) {
if (gu16PSoCDigital & (1 << nCell)) {
v = 255;
h = LED_HUE_MAX / 2;
}
} else if (nCell % 4 == 3) {
h = LED_HUE_MAX / 2;
const uint16_t u16H =
(u16HueOffset + (i * (LED_HUE_MAX / (LED_NUM_GROUND_LOGICAL - 1)))) % LED_HUE_MAX;
if (1) {
switch (INDEX_TYPE(i)) {
case IndexType_CellActive:
case IndexType_LitDivider:
case IndexType_SeparatingDivider:
aGround[i].h = u16H;
aGround[i].s = 255;
aGround[i].v = 255;
break;
case IndexType_Cell:
case IndexType_Divider:
aGround[i].h = u16H;
aGround[i].s = 255;
// aGround[i].v = 20 + (twinkle[i].value / 4);
aGround[i].v = twinkle[i].value;
break;
}
aGround[i].h = (u16Hue + h + (i * (LED_HUE_MAX / LED_NUM_GROUND_LOGICAL))) % LED_HUE_MAX;
aGround[i].s = v;
aGround[i].v = v - 63;
continue;
}
const uint16_t u16Value = 50;
const uint16_t u16ValueActive = 255;
uint8_t u8Value;
switch (INDEX_TYPE(i)) {
case IndexType_CellActive:
// aGround[i].h = u16H;
// aGround[i].s = 255;
// aGround[i].v = u16ValueActive;
// break;
case IndexType_Cell:
aGround[i].h = u16H;
aGround[i].s = 255;
u8Value = gu8GroundData[(i >> 1) * 2] / 2 + gu8GroundData[(i >> 1) * 2 + 1] / 2;
aGround[i].v = u8Value;
break;
case IndexType_SeparatingDivider:
aGround[i].h = u16H;
aGround[i].s = 255;
aGround[i].v = u16ValueActive;
break;
case IndexType_LitDivider:
// aGround[i].h = u16H;
// aGround[i].s = 255;
// aGround[i].v = u16ValueActive;
// break;
case IndexType_Divider:
aGround[i].h = u16H;
aGround[i].s = 255;
u8Value = gu8GroundData[(i >> 1) * 2] / 4 + gu8GroundData[(i >> 1) * 2 + 1] / 4 +
gu8GroundData[(i >> 1) * 2 + 2] / 4 + gu8GroundData[(i >> 1) * 2 + 3] / 4;
aGround[i].v = u8Value;
break;
}
}
}
void LED_Ground_Static_HSV(hsv_t* aGround) {
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
const uint8_t nCell = i >> 1;
if (i % 2 == 0) {
// This is a cell. Light it according to the touch input
if (gu16PSoCDigital & (1 << nCell)) {
switch (INDEX_TYPE(i)) {
case IndexType_CellActive:
aGround[i].h = gConfig.u16HueGroundActive;
aGround[i].s = 255;
aGround[i].v = 255;
} else {
break;
case IndexType_Cell:
aGround[i].h = gConfig.u16HueGround;
aGround[i].s = 255;
aGround[i].v = 255;
}
} else if (nCell % 4 == 3) {
// This is a separating divider. Light it with the active colour
break;
case IndexType_SeparatingDivider:
aGround[i].h = gConfig.u16HueGroundActive;
aGround[i].s = 255;
aGround[i].v = 255;
} else {
// This is a non-separating divider. Light it based on the two cells either side
if (gu16PSoCDigital & (1 << nCell) && gu16PSoCDigital & (1 << (nCell + 1))) {
break;
case IndexType_LitDivider:
aGround[i].h = gConfig.u16HueGroundActive;
aGround[i].s = 255;
aGround[i].v = 255;
} else {
break;
case IndexType_Divider:
aGround[i].h = gConfig.u16HueGround;
aGround[i].s = 255;
aGround[i].v = 255;
}
break;
}
}
}
@ -169,41 +299,41 @@ void HsvToHost(rgb_t* pRGB, uint16_t u16H, uint8_t u8S, uint8_t u8V) {
return;
}
void LED_Wings_Reactive_RGB(_led_wings_rgb* pWings) {
void LED_Towers_Reactive_RGB(_led_towers_rgb* pTowers) {
rgb_t u8aRgbActive;
rgb_t u8aRgbInactive;
uint8_t i;
// Left wing
HsvToHost(&u8aRgbActive, gConfig.u16HueWingLeft, SATURATION_ACTIVE, VALUE_ACTIVE);
HsvToHost(&u8aRgbInactive, gConfig.u16HueWingLeft, SATURATION_INACTIVE, VALUE_INACTIVE);
for (i = 0; i < LED_NUM_WING; i++) {
// Left tower
HsvToHost(&u8aRgbActive, gConfig.u16HueTowerLeft, SATURATION_ACTIVE, VALUE_ACTIVE);
HsvToHost(&u8aRgbInactive, gConfig.u16HueTowerLeft, SATURATION_INACTIVE, VALUE_INACTIVE);
for (i = 0; i < LED_NUM_TOWER; i++) {
// GRB
if (gu8DigitalButtons & su8aWingSensors[LED_NUM_WING - i - 1]) {
pWings->aWingL[i].host.r = u8aRgbActive.host.r;
pWings->aWingL[i].host.g = u8aRgbActive.host.g;
pWings->aWingL[i].host.b = u8aRgbActive.host.b;
if (gu8DigitalButtons & su8aTowerSensors[LED_NUM_TOWER - i - 1]) {
pTowers->aTowerL[i].host.r = u8aRgbActive.host.r;
pTowers->aTowerL[i].host.g = u8aRgbActive.host.g;
pTowers->aTowerL[i].host.b = u8aRgbActive.host.b;
} else {
pWings->aWingL[i].host.r = u8aRgbInactive.host.r;
pWings->aWingL[i].host.g = u8aRgbInactive.host.g;
pWings->aWingL[i].host.b = u8aRgbInactive.host.b;
pTowers->aTowerL[i].host.r = u8aRgbInactive.host.r;
pTowers->aTowerL[i].host.g = u8aRgbInactive.host.g;
pTowers->aTowerL[i].host.b = u8aRgbInactive.host.b;
}
}
// Right wing
HsvToHost(&u8aRgbActive, gConfig.u16HueWingRight, SATURATION_ACTIVE, VALUE_ACTIVE);
HsvToHost(&u8aRgbInactive, gConfig.u16HueWingRight, SATURATION_INACTIVE, VALUE_INACTIVE);
for (i = 0; i < LED_NUM_WING; i++) {
// Right tower
HsvToHost(&u8aRgbActive, gConfig.u16HueTowerRight, SATURATION_ACTIVE, VALUE_ACTIVE);
HsvToHost(&u8aRgbInactive, gConfig.u16HueTowerRight, SATURATION_INACTIVE, VALUE_INACTIVE);
for (i = 0; i < LED_NUM_TOWER; i++) {
// GRB
if (gu8DigitalButtons & su8aWingSensors[i]) {
pWings->aWingR[i].host.r = u8aRgbActive.host.r;
pWings->aWingR[i].host.g = u8aRgbActive.host.g;
pWings->aWingR[i].host.b = u8aRgbActive.host.b;
if (gu8DigitalButtons & su8aTowerSensors[i]) {
pTowers->aTowerR[i].host.r = u8aRgbActive.host.r;
pTowers->aTowerR[i].host.g = u8aRgbActive.host.g;
pTowers->aTowerR[i].host.b = u8aRgbActive.host.b;
} else {
pWings->aWingR[i].host.r = u8aRgbInactive.host.r;
pWings->aWingR[i].host.g = u8aRgbInactive.host.g;
pWings->aWingR[i].host.b = u8aRgbInactive.host.b;
pTowers->aTowerR[i].host.r = u8aRgbInactive.host.r;
pTowers->aTowerR[i].host.g = u8aRgbInactive.host.g;
pTowers->aTowerR[i].host.b = u8aRgbInactive.host.b;
}
}
}
@ -250,24 +380,28 @@ void LED_Ground_Static_RGB(rgb_t* aGround) {
}
}
void LED_Wings_Controlled_RGB(_led_wings_rgb* pWings) {
// TODO: Get data from game when gbLedDataIsControlledExt (HID, probably)
for (uint8_t i = 0; i < LED_NUM_WING; i++) {
// The PWM output is wired as BGR on PWM3~5
pWings->aWingL[i].host.b = gu8IO4PWMOutput[2];
pWings->aWingR[i].host.b = gu8IO4PWMOutput[2];
pWings->aWingL[i].host.r = gu8IO4PWMOutput[3];
pWings->aWingR[i].host.r = gu8IO4PWMOutput[3];
pWings->aWingL[i].host.g = gu8IO4PWMOutput[4];
pWings->aWingR[i].host.g = gu8IO4PWMOutput[4];
void LED_Towers_Controlled_RGB(_led_towers_rgb* pTowers) {
for (uint8_t i = 0; i < LED_NUM_TOWER; i++) {
// The PWM output is wired as BGR on IO4 PWM3~5
pTowers->aTowerL[i].host.b = gu8IO4PWMOutput[2];
pTowers->aTowerR[i].host.b = gu8IO4PWMOutput[2];
pTowers->aTowerL[i].host.r = gu8IO4PWMOutput[3];
pTowers->aTowerR[i].host.r = gu8IO4PWMOutput[3];
pTowers->aTowerL[i].host.g = gu8IO4PWMOutput[4];
pTowers->aTowerR[i].host.g = gu8IO4PWMOutput[4];
}
}
void LED_Ground_Controlled_RGB(rgb_t* aGround) {
// Swap from BRG(game) to GRB(host)
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
#ifdef LED_CORRECTION_ON_LED_MCU
aGround[i].host.r = LED_ScaleU8(gaControlledExtLedData[i].game.r, 255);
aGround[i].host.g = LED_ScaleU8(gaControlledExtLedData[i].game.g, 255);
aGround[i].host.b = LED_ScaleU8(gaControlledExtLedData[i].game.b, 255);
#else
aGround[i].host.r = LED_ScaleU8(gaControlledExtLedData[i].game.r, 255);
aGround[i].host.g = LED_ScaleU8(gaControlledExtLedData[i].game.g / 2, 255);
aGround[i].host.b = LED_ScaleU8(gaControlledExtLedData[i].game.b / 2, 255);
#endif
}
}

View File

@ -11,6 +11,9 @@
#include "_compiler.h"
// Are we going to be doing global colour correction on the LED MCU (yes!)
#define LED_CORRECTION_ON_LED_MCU
// === LED Firmware Type Configuration ===
// (Only applicable on the host)
#define LED_FW_STOCK 0
@ -32,9 +35,9 @@
#define LED_NUM_GROUND_LOGICAL 31
// Ground: [0] = Obscured, [1] = Right, [47] = Left
#define LED_NUM_GROUND_PHYSICAL 48
// Right wing: [0] = Bottom, [23] = Top
// Left wing: [23] = Bottom, [0] = Top
#define LED_NUM_WING 24
// Right tower: [0] = Bottom, [23] = Top
// Left tower: [23] = Bottom, [0] = Top
#define LED_NUM_TOWER 24
// === Colour Byte Order Definitions ===
// What we know and love
@ -103,19 +106,19 @@ typedef struct __packed {
// === HOST->LED MCU Packets ===
typedef struct __packed {
rgb_t aWingL[LED_NUM_WING];
rgb_t aWingR[LED_NUM_WING];
} _led_wings_rgb;
rgb_t aTowerL[LED_NUM_TOWER];
rgb_t aTowerR[LED_NUM_TOWER];
} _led_towers_rgb;
typedef struct __packed {
hsv_t aWingL[LED_NUM_WING];
hsv_t aWingR[LED_NUM_WING];
} _led_wings_hsv;
hsv_t aTowerL[LED_NUM_TOWER];
hsv_t aTowerR[LED_NUM_TOWER];
} _led_towers_hsv;
typedef struct __packed {
uint8_t u8Cmd;
uint32_t u32Ground;
uint8_t u8WingFill;
uint8_t u8TowerFill;
#if LED_FIRMWARE_TYPE == LED_FW_MAINLAND
uint8_t u8Rainbow;
uint8_t u8Rsv07;
@ -137,31 +140,31 @@ typedef struct __packed {
uint8_t u8Config;
#endif
rgb_t aGround[LED_NUM_GROUND_LOGICAL];
_led_wings_rgb Wings;
_led_towers_rgb Towers;
} led_rx_full, *Pled_rx_full;
typedef struct __packed {
uint8_t u8Cmd;
uint8_t u8GroundBrightness;
uint8_t u8WingBrightness;
uint8_t u8TowerBrightness;
rgb_t aGround[LED_NUM_GROUND_LOGICAL];
_led_wings_rgb Wings;
_led_towers_rgb Towers;
} led_rx_custom_rgb, *Pled_rx_custom_rgb;
typedef struct __packed {
uint8_t u8Cmd;
uint8_t u8GroundBrightness;
uint8_t u8WingBrightness;
uint8_t u8TowerBrightness;
rgb_t aGround[LED_NUM_GROUND_LOGICAL];
_led_wings_hsv Wings;
_led_towers_hsv Towers;
} led_rx_custom_mixed, *Pled_rx_custom_mixed;
typedef struct __packed {
uint8_t u8Cmd;
uint8_t u8GroundBrightness;
uint8_t u8WingBrightness;
uint8_t u8TowerBrightness;
hsv_t aGround[LED_NUM_GROUND_LOGICAL];
_led_wings_hsv Wings;
_led_towers_hsv Towers;
} led_rx_custom_hsv, *Pled_rx_custom_hsv;
typedef struct __packed {
@ -209,5 +212,4 @@ typedef struct __packed {
#define LED_DIVIDER_14_15 29
// === HSV Definitions ===
#define LED_HUE_SCALE 5 // For transmission to LED board in basic mode
#define LED_HUE_MAX 360

View File

@ -70,6 +70,10 @@ void Digital_TickInputs() {
int _entry(void) {
SYS_UnlockReg();
// Load our configuration, so we know if the LED processor should be rebooted
FMC_EEPROM_Load();
SYS_Init();
#ifdef ENABLE_BOOTLOADER_CHECK
SYS_Bootloader_Check();
@ -77,8 +81,6 @@ int _entry(void) {
SYS_ModuleInit();
// TODO: Re-lock registers, ideally. Need to check which registers we use where
FMC_EEPROM_Load();
gu8VComReady = 1;
gu8HIDIO4Ready = 1;
gu8HIDMiscReady = 1;
@ -108,12 +110,11 @@ int _entry(void) {
if (bPSoCDirtyVolatile) {
bPSoCDirtyVolatile = 0;
PSoC_PostProcessing();
if (!bPSoCHasTalked) {
}
if (bPSoCAliveVolatile && !bPSoCHasTalked) {
PSoC_SetFingerCapacitanceFromConfig(1);
bPSoCHasTalked = 1;
}
}
Slider_TickSerial();
USBD_HID_PrepareReport();

View File

@ -10,7 +10,7 @@
#define PIN_RX2 PD9
#define PIN_RX3 PD10
#define PIN_RX4 PD11
#define PIN_LED_WING_PWR PB13
#define PIN_LED_TOWER_PWR PB13
#define PIN_LED_GROUND_PWR PC3
#define _PIN_SDA PA, BIT10
@ -25,7 +25,7 @@
#define _PIN_RX2 PD, BIT9
#define _PIN_RX3 PD, BIT10
#define _PIN_RX4 PD, BIT11
#define _PIN_LED_WING_PWR PB, BIT13
#define _PIN_LED_TOWER_PWR PB, BIT13
#define _PIN_LED_GROUND_PWR PC, BIT3
#define PIN_AIR1 PIN_RX2

View File

@ -6,6 +6,9 @@
// #pragma GCC push_options
// #pragma GCC optimize ("O0")
// TODO: Anything higher than O1 is breaking this code. Probably something hasn't been marked as
// volatile that should be!
uint16_t gu16PSoCDiff[32] = { 0 };
// Touch threshold as calculated by SmartSense. Has hysteresis built in.
// 0 is an insane default, so instead we default to 100 which is far less likely to break things :)
@ -27,9 +30,10 @@ uint32_t gu32LastCapSenseStart = 0;
uint32_t gu32LastCapSenseEnd = 0;
volatile uint8_t bPSoCDirtyVolatile = 0;
volatile uint8_t bPSoCAliveVolatile = 0;
volatile uint8_t bForceSliderSend = 0;
static void* pu8PsocRxDestination = NULL;
static volatile void* pu8PsocRxDestination = NULL;
static uint8_t pu8PsocRxDestinationLen = 0;
static volatile uint8_t* volatile pu8PsocGotData = NULL;
static volatile PSoC_CMD_RX eBlockingCommand = _PSoC_CMD_RX_NONE;
@ -41,9 +45,8 @@ static void PSoC_HandleRx(PSoC_CMD_RX eCmd, uint8_t u8Len, uint8_t* u8Data) {
}
switch (eCmd) {
case PSoC_CMD_RX_REQUEST_FINGER_CAP:
// We're in the IRQ here, so don't block on this!
PSoC_SetFingerCapacitanceFromConfig(0);
case PSoC_CMD_RX_INITIALISATION_COMPLETE:
bPSoCAliveVolatile = 1;
return;
// Debug traces
@ -64,6 +67,7 @@ static void PSoC_HandleRx(PSoC_CMD_RX eCmd, uint8_t u8Len, uint8_t* u8Data) {
gu16PSoCDiff[16 + i] |= u8Data[(i * 2) + 1];
}
bPSoCDirtyVolatile = 1;
bPSoCAliveVolatile = 1;
return;
case PSoC_CMD_RX_MASTER_DIFF:
@ -76,6 +80,7 @@ static void PSoC_HandleRx(PSoC_CMD_RX eCmd, uint8_t u8Len, uint8_t* u8Data) {
gu16PSoCDiff[i] |= u8Data[(i * 2) + 1];
}
bPSoCDirtyVolatile = 1;
bPSoCAliveVolatile = 1;
return;
// Arbitrary data reception
@ -112,7 +117,7 @@ static void PSoC_HandleRx(PSoC_CMD_RX eCmd, uint8_t u8Len, uint8_t* u8Data) {
if (pu8PsocRxDestination) {
if (u8Len > pu8PsocRxDestinationLen) u8Len = pu8PsocRxDestinationLen;
memcpy(pu8PsocRxDestination, u8Data, u8Len);
memcpy((void*)pu8PsocRxDestination, u8Data, u8Len);
// Make sure we don't go clobbering stuff later!
pu8PsocRxDestination = NULL;
}
@ -164,9 +169,12 @@ static uint8_t PSoC_Valid_Len(PSoC_CMD_RX eCmd, uint8_t u8Len) {
// We should never be seeing a length for these!
return 0;
// TODO: This doesn't seem to be a packet!
case PSoC_CMD_RX_INITIALISATION_COMPLETE:
return 0;
// Commands that have a payload we need to receive
case PSoC_CMD_RX_SET_FINGER_CAP:
case PSoC_CMD_RX_REQUEST_FINGER_CAP:
case PSoC_CMD_RX_ENABLE_DEBUG:
return u8Len == 2;
case PSoC_CMD_RX_MASTER_DIFF:
@ -221,10 +229,12 @@ void UART1_IRQHandler(void) {
case PSoC_CMD_RX_CS_END:
// PSoC_HandleRx(u8Data, 0, NULL);
return;
case PSoC_CMD_RX_INITIALISATION_COMPLETE:
PSoC_HandleRx(u8Data, 0, NULL);
return;
// Commands that have a payload we need to receive
case PSoC_CMD_RX_SET_FINGER_CAP:
case PSoC_CMD_RX_REQUEST_FINGER_CAP:
case PSoC_CMD_RX_MASTER_DIFF:
case PSoC_CMD_RX_SLAVE_DIFF:
case PSoC_CMD_RX_MASTER_TOUCH_TH:
@ -325,8 +335,7 @@ static inline void PSoC_Cmd(PSoC_CMD_TX eCmd, uint8_t u8D0, uint8_t u8D1, PSoC_C
}
uint16_t PSoC_GetFingerCapacitance(void) {
uint16_t u16FingerCap;
static volatile uint16_t u16FingerCap = 0;
pu8PsocRxDestination = &u16FingerCap;
pu8PsocRxDestinationLen = sizeof u16FingerCap;
PSoC_Cmd(PSoC_CMD_TX_GET_FINGER_CAP, 0, 0, PSoC_CMD_RX_GET_FINGER_CAP);

View File

@ -28,7 +28,7 @@ typedef enum : uint8_t {
// PSoC -> Host
PSoC_CMD_RX_MASTER_DIFF = 0xAD,
PSoC_CMD_RX_SLAVE_DIFF = 0xAF,
PSoC_CMD_RX_REQUEST_FINGER_CAP = 0xC1,
PSoC_CMD_RX_INITIALISATION_COMPLETE = 0xC1,
// PSoC -> Host (Debug)
PSoC_CMD_RX_MASTER_TOUCH_TH = 0xAA,
@ -59,6 +59,8 @@ extern uint16_t gu16PSoCDiff[32];
// Has the difference data changed in the interrupt handler?
extern volatile uint8_t bPSoCDirtyVolatile;
// Have we received indication that the PSoC is ready for packets?
extern volatile uint8_t bPSoCAliveVolatile;
// Has the post-processed difference data changed?
extern volatile uint8_t bForceSliderSend;

View File

@ -229,12 +229,14 @@ static void Slider_Process(slider_cmd_Rx u8SliderCmd, uint8_t* pu8Packet, uint8_
break;
case SLIDER_DEBUG_CMD_Rx_HOST_ENTER_LDROM:
// Remember if we're going to want to kick the LED board into LDROM next reboot
gConfig.u8NextBootLEDBootloader = pu8Packet[1];
bConfigDirty = 1;
FMC_EEPROM_Store();
SYS_EnterLDROM();
break;
case SLIDER_DEBUG_CMD_Rx_LED_ENTER_LDROM:
gu8LEDTx[0] = LED_CMD_FMC_ENTER_LDROM;
// The LED firmware checks every 10ms or so
CLK_SysTickLongDelay(15 ms);
SYS_WaitBootloaderLED();
break;
case SLIDER_DEBUG_CMD_Rx_LED_CHECK:
@ -242,7 +244,7 @@ static void Slider_Process(slider_cmd_Rx u8SliderCmd, uint8_t* pu8Packet, uint8_
sizeof gbLedIsCustom);
break;
case SLIDER_DEBUG_CMD_Rx_LED_GET_DIGITAL:
case SLIDER_DEBUG_CMD_Rx_GET_DIGITAL:
Slider_Respond(SLIDER_CMD_Tx_DEBUG, &gu8DigitalButtons,
sizeof gu8DigitalButtons);
break;
@ -342,8 +344,8 @@ void Slider_TickSerial(void) {
void Slider_Tick1ms() {
if (gbLedDataIsControlledExt) {
// If we haven't had an LED packet in 5 seconds, call it quits
if (++su32SinceLastControlled == 5 * 1000) gbLedDataIsControlledExt = 0;
// If we haven't had an LED packet in 1 second, call it quits
if (++su32SinceLastControlled == 1000) gbLedDataIsControlledExt = 0;
}
static uint16_t u16Counter = 0;

View File

@ -121,7 +121,7 @@ typedef enum : uint8_t {
SLIDER_DEBUG_CMD_Rx_LED_CHECK = 0x22,
// IO Access
SLIDER_DEBUG_CMD_Rx_LED_GET_DIGITAL = 0x30,
SLIDER_DEBUG_CMD_Rx_GET_DIGITAL = 0x30,
} slider_debug_cmd_Rx;
typedef enum : uint8_t {
SLIDER_CMD_Tx_REPORT = 0x01,

View File

@ -75,12 +75,28 @@ void SYS_Init(void) {
GPIO_SetMode(_PIN_EC3, GPIO_PMD_INPUT);
GPIO_SetMode(_PIN_USB_MUX_SEL, GPIO_PMD_OUTPUT);
GPIO_SetMode(_PIN_USB_MUX_EN, GPIO_PMD_OUTPUT);
GPIO_SetMode(_PIN_LED_WING_PWR, GPIO_PMD_OUTPUT);
GPIO_SetMode(_PIN_LED_TOWER_PWR, GPIO_PMD_OUTPUT);
GPIO_SetMode(_PIN_LED_GROUND_PWR, GPIO_PMD_OUTPUT);
PIN_LED_WING_PWR = 1;
PIN_LED_TOWER_PWR = 1;
PIN_LED_GROUND_PWR = 1;
if (gConfig.u8NextBootLEDBootloader) {
// If we've been instructed to, immediately reboot the LED module into LDROM
gConfig.u8NextBootLEDBootloader = 0;
bConfigDirty = 1;
FMC_EEPROM_Store();
// Switch PA10 and PA11 to GPIO so we can pull them low
SYS->GPA_MFP &= ~(SYS_GPA_MFP_PA10_Msk | SYS_GPA_MFP_PA11_Msk);
SYS->GPA_MFP |= (SYS_GPA_MFP_PA10_GPIO | SYS_GPA_MFP_PA11_GPIO);
SYS->ALT_MFP &= ~(SYS_ALT_MFP_PA10_Msk | SYS_ALT_MFP_PA11_Msk);
SYS->ALT_MFP |= (SYS_ALT_MFP_PA10_GPIO | SYS_ALT_MFP_PA11_GPIO);
SYS_WaitBootloaderLED();
}
// If FN2 is depressed, trigger the LED bootloader to enter bootloading mode by pulling both
// PA10 and PA11 low rather than configuring them for I2C (pulling high).
//
@ -95,6 +111,10 @@ void SYS_Init(void) {
GPIO_SetMode(_PIN_SCL, GPIO_PMD_OUTPUT);
PIN_SDA = 0;
PIN_SCL = 0;
// TODO: Nicer way of doing this for the automated firmware process?
// while (1)
// ;
} else {
// Set GPA multi-function pins for I2C1 SDA and SCL
SYS->GPA_MFP &= ~(SYS_GPA_MFP_PA10_Msk | SYS_GPA_MFP_PA11_Msk);
@ -208,6 +228,13 @@ void TMR0_IRQHandler(void) {
void __attribute__((noreturn)) SYS_EnterLDROM(void) {
SYS_UnlockReg();
// Turn off our USB PHY
USBD->ATTR = 0x650;
NVIC_DisableIRQ(USBD_IRQn);
SYS_ResetModule(USBD_RST);
// Give Windows a moment to notice the disconnection
CLK_SysTickLongDelay(1000 ms);
// If we use a CPU reset, I2C is left setup and so the LED board will be timing out rather than
// early-NACKS.
// If we use a CHIP reset we aren't guaranteed to land in LDROM because it's based on the CONFIG
@ -255,7 +282,7 @@ void SYS_WaitBootloaderLED(void) {
NVIC_DisableIRQ(USBD_IRQn);
SYS_ResetModule(USBD_RST);
// Give Windows a moment to notice the disconnection
CLK_SysTickLongDelay(1000 ms);
CLK_SysTickLongDelay(500 ms);
// Switch the USB connection over the LED microcontroller
PIN_USB_MUX_SEL = USB_MUX_LEDS;
PIN_USB_MUX_EN = USB_MUX_ENABLE;

View File

@ -14,6 +14,9 @@ extern uint8_t gbUIOpen;
#define BYTESWAP_U16(x) ((((x) & 0xFF) << 8) | ((x) >> 8))
// === Touch input ===
// #define ENABLE_TOUCH_INPUT
// === DAO-DRM ===
// * For now the bootloader check is being left enabled.
// * It'll help catch if I break that in my bootloader :P

2
src/timestamp.c Normal file
View File

@ -0,0 +1,2 @@
__attribute__((section(".compile_timestamp"))) const char compile_time[] __attribute__((used)) =
"HOST" __DATE__ " " __TIME__;

118
src/ui.c
View File

@ -1,5 +1,6 @@
#include "tasoller.h"
#define FN1_TAP_TIME 250 // ms of FN1 to enter config
#define FN1_HOLD_TIME 250 // ms of FN1 to enter config
#define FN2_TAP_TIME 250 // ms of FN2 to enter service/test
#define FN2_HOLD_TIME 500 // ms of FN2 to enter service/test
@ -16,12 +17,18 @@
uint8_t gbUIOpen = 0;
static uint8_t u8TestIsActive = 0;
static void UI_TickSensitivity(void) {
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
gaControlledIntLedData[i].h = 0;
gaControlledIntLedData[i].s = (gConfig.u8Sens - 1) * 16;
gaControlledIntLedData[i].v = 0;
static inline void UI_WriteRange(const uint8_t u8Start, const uint8_t u8End, const uint16_t u16H,
const uint8_t u8S, const uint8_t u8V) {
if (u8End < u8Start) return;
for (uint8_t i = u8Start; i <= u8End; i++) {
gaControlledIntLedData[i].h = u16H;
gaControlledIntLedData[i].s = u8S;
gaControlledIntLedData[i].v = u8V;
}
}
static void UI_TickSensitivity(void) {
UI_WriteRange(0, LED_NUM_GROUND_LOGICAL - 1, 0, (gConfig.u8Sens - 1) * 16, 0);
for (uint8_t i = 0; i < gConfig.u8Sens; i++) gaControlledIntLedData[i * 2].v = 255;
@ -57,14 +64,10 @@ static void UI_TickSettings(void) {
if (++u8Pulser == 255) u8PulserDir = 1;
}
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
gaControlledIntLedData[i].h = 0;
gaControlledIntLedData[i].s = 255;
gaControlledIntLedData[i].v = 0;
}
UI_WriteRange(0, LED_NUM_GROUND_LOGICAL - 1, 0, 255, 0);
{ // LED colour control
gaControlledIntLedData[LED_CELL_0].h = gConfig.u16HueWingLeft;
gaControlledIntLedData[LED_CELL_0].h = gConfig.u16HueTowerLeft;
gaControlledIntLedData[LED_CELL_0].v = 255;
gaControlledIntLedData[LED_CELL_1].h = gConfig.u16HueGround;
gaControlledIntLedData[LED_CELL_1].v = 255;
@ -72,17 +75,17 @@ static void UI_TickSettings(void) {
gaControlledIntLedData[LED_DIVIDER_1_2].v = 255;
gaControlledIntLedData[LED_CELL_2].h = gConfig.u16HueGround;
gaControlledIntLedData[LED_CELL_2].v = 255;
gaControlledIntLedData[LED_CELL_3].h = gConfig.u16HueWingRight;
gaControlledIntLedData[LED_CELL_3].h = gConfig.u16HueTowerRight;
gaControlledIntLedData[LED_CELL_3].v = 255;
if (gu32PSoCDigitalTrig & PAD_1_Msk) MOD_INCR(gConfig.u16HueWingLeft, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_2_Msk) MOD_DECR(gConfig.u16HueWingLeft, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_1_Msk) MOD_INCR(gConfig.u16HueTowerLeft, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_2_Msk) MOD_DECR(gConfig.u16HueTowerLeft, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_3_Msk) MOD_INCR(gConfig.u16HueGround, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_4_Msk) MOD_DECR(gConfig.u16HueGround, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_5_Msk) MOD_INCR(gConfig.u16HueGroundActive, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_6_Msk) MOD_DECR(gConfig.u16HueGroundActive, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_7_Msk) MOD_INCR(gConfig.u16HueWingRight, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_8_Msk) MOD_DECR(gConfig.u16HueWingRight, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_7_Msk) MOD_INCR(gConfig.u16HueTowerRight, LED_HUE_MAX);
if (gu32PSoCDigitalTrig & PAD_8_Msk) MOD_DECR(gConfig.u16HueTowerRight, LED_HUE_MAX);
}
// [Cell 4 no function]
{ // Lighting toggles
@ -105,17 +108,17 @@ static void UI_TickSettings(void) {
} else {
gaControlledIntLedData[LED_CELL_6].v = 255;
}
if (gConfig.u8LedWingBrightness) {
if (gConfig.u8LedTowerBrightness) {
gaControlledIntLedData[LED_CELL_7].s = 0;
gaControlledIntLedData[LED_CELL_7].v = gConfig.u8LedWingBrightness;
gaControlledIntLedData[LED_CELL_7].v = gConfig.u8LedTowerBrightness;
} else {
gaControlledIntLedData[LED_CELL_7].v = 255;
}
if (gu32PSoCDigitalTrig & PAD_13_Msk) INCR(gConfig.u8LedGroundBrightness, 255);
if (gu32PSoCDigitalTrig & PAD_14_Msk) DECR(gConfig.u8LedGroundBrightness, 0);
if (gu32PSoCDigitalTrig & PAD_15_Msk) INCR(gConfig.u8LedWingBrightness, 255);
if (gu32PSoCDigitalTrig & PAD_16_Msk) DECR(gConfig.u8LedWingBrightness, 0);
if (gu32PSoCDigitalTrig & PAD_15_Msk) INCR(gConfig.u8LedTowerBrightness, 255);
if (gu32PSoCDigitalTrig & PAD_16_Msk) DECR(gConfig.u8LedTowerBrightness, 0);
}
// [Cell 8 no function]
@ -162,11 +165,7 @@ static uint32_t su32EnteredTestMenuAt = 0;
static void UI_TickServiceTest(void) {
uint8_t u8V = 0;
// Zero out the LED data
for (uint8_t i = 0; i < LED_NUM_GROUND_LOGICAL; i++) {
gaControlledIntLedData[i].h = 0;
gaControlledIntLedData[i].s = 0;
gaControlledIntLedData[i].v = 0;
}
UI_WriteRange(0, LED_NUM_GROUND_LOGICAL - 1, 0, 0, 0);
uint8_t u8ForceTest = 0;
if (u8TestIsActive) {
@ -183,11 +182,8 @@ static void UI_TickServiceTest(void) {
} else {
u8V = 50;
}
gaControlledIntLedData[LED_CELL_0].v = u8V;
gaControlledIntLedData[LED_DIVIDER_0_1].v = u8V;
gaControlledIntLedData[LED_CELL_1].v = u8V;
gaControlledIntLedData[LED_DIVIDER_1_2].v = u8V;
gaControlledIntLedData[LED_CELL_2].v = u8V;
UI_WriteRange(LED_CELL_0, LED_CELL_2, 0, 0, u8V);
}
gaControlledIntLedData[LED_DIVIDER_2_3].v = 255;
{ // Up
@ -196,11 +192,7 @@ static void UI_TickServiceTest(void) {
} else {
u8V = 50;
}
gaControlledIntLedData[LED_CELL_3].v = u8V;
gaControlledIntLedData[LED_DIVIDER_3_4].v = u8V;
gaControlledIntLedData[LED_CELL_4].v = u8V;
gaControlledIntLedData[LED_DIVIDER_4_5].v = u8V;
gaControlledIntLedData[LED_CELL_5].v = u8V;
UI_WriteRange(LED_CELL_3, LED_CELL_5, 0, 0, u8V);
}
gaControlledIntLedData[LED_DIVIDER_5_6].v = 255;
@ -211,11 +203,7 @@ static void UI_TickServiceTest(void) {
} else {
u8V = 50;
}
gaControlledIntLedData[LED_CELL_13].v = u8V;
gaControlledIntLedData[LED_DIVIDER_13_14].v = u8V;
gaControlledIntLedData[LED_CELL_14].v = u8V;
gaControlledIntLedData[LED_DIVIDER_14_15].v = u8V;
gaControlledIntLedData[LED_CELL_15].v = u8V;
UI_WriteRange(LED_CELL_13, LED_CELL_15, 0, 255, u8V);
}
}
@ -250,15 +238,8 @@ static void UI_TickServiceTest(void) {
gaControlledIntLedData[LED_DIVIDER_9_10].v = 255;
}
void UI_Tick(void) {
// Handle hold trigger on FN1
static uint8_t u8Fn1Held = 0;
if (gu8DigitalButtons & DIGITAL_FN1_Msk) {
if (u8Fn1Held < FN1_HOLD_TIME) u8Fn1Held++;
} else {
// We released the button after holding it for long enough to be in the configuration UI, so
// assume something changed
if (u8Fn1Held >= FN1_HOLD_TIME) {
static inline void _UI_SettingsOnExit(void) {
// If FN2 was released while still in sensitivity adjustment, make sure the changes save
if (su8SensTimeout) {
PSoC_SetFingerCapacitanceFromConfig(1);
@ -266,18 +247,45 @@ void UI_Tick(void) {
}
bConfigDirty = 1;
}
u16RequestedConsumerControl = 0;
u32EnterPressStarted = 0;
}
void UI_Tick(void) {
static uint8_t u8LastDB = 0;
const uint8_t u8PosDb = gu8DigitalButtons & (~u8LastDB);
static uint8_t u8ConfigIsActive = 0;
// Handle double tap trigger on FN1
static uint32_t u32LastFn1 = 0;
if (u8PosDb & DIGITAL_FN1_Msk) {
if (u32LastFn1 && MS_SINCE(u32LastFn1) < FN1_TAP_TIME) {
u8ConfigIsActive = !u8ConfigIsActive;
if (!u8ConfigIsActive) {
_UI_SettingsOnExit();
}
}
u32LastFn1 = gu32NowMs;
}
// Handle hold trigger on FN1
if (gu8DigitalButtons & DIGITAL_FN1_Msk) {
if (u8Fn1Held < FN1_HOLD_TIME) u8Fn1Held++;
} else {
// We released the button after holding it for long enough to be in the configuration UI, so
// assume something changed
if (u8Fn1Held >= FN1_HOLD_TIME) {
_UI_SettingsOnExit();
}
u8Fn1Held = 0;
}
// Handle double tap trigger on FN2
static uint32_t u32LastFn2 = 0;
static uint8_t u8LastDB = 0;
const uint8_t u8PostDb = gu8DigitalButtons & (~u8LastDB);
if (u8PostDb & DIGITAL_FN2_Msk) {
if (u8PosDb & DIGITAL_FN2_Msk) {
if (u32LastFn2 && MS_SINCE(u32LastFn2) < FN2_TAP_TIME) {
u8TestIsActive = !u8TestIsActive;
@ -291,7 +299,6 @@ void UI_Tick(void) {
}
u32LastFn2 = gu32NowMs;
}
u8LastDB = gu8DigitalButtons;
// Handle hold trigger on FN2
static uint16_t u16Fn2Held = 0;
@ -301,8 +308,11 @@ void UI_Tick(void) {
u16Fn2Held = 0;
}
// Persistent state
u8LastDB = gu8DigitalButtons;
// Render the appropriate UI based on what's being done
if (u8Fn1Held >= FN1_HOLD_TIME) {
if (u8Fn1Held >= FN1_HOLD_TIME || u8ConfigIsActive) {
gbLedDataIsControlledInt = 1;
gbUIOpen = 1;
UI_TickSettings();

View File

@ -55,23 +55,28 @@ enum : uint8_t {
#define USBD_CDC_CMD_MAX_SIZE (16)
#define USBD_CDC_IN_MAX_SIZE (64) // Device -> Host
#define USBD_CDC_OUT_MAX_SIZE (64) // Host -> Device
#define USBD_HID_BUF_LEN (64)
#define USBD_HID_BUF_LEN_IO4 (64)
#define USBD_HID_BUF_LEN_IN (64) // 104
#define USBD_HID_BUF_LEN_OUT (64)
_Static_assert(USBD_HID_BUF_LEN >= sizeof(hid_kbd_report_t) &&
USBD_HID_BUF_LEN >= sizeof(hid_consumer_report_t) &&
USBD_HID_BUF_LEN >= sizeof(io4_hid_in_t) &&
USBD_HID_BUF_LEN >= sizeof(io4_hid_out_t),
_Static_assert(USBD_HID_BUF_LEN_IN >= sizeof(hid_kbd_report_t) &&
USBD_HID_BUF_LEN_IN >= sizeof(hid_consumer_report_t) &&
USBD_HID_BUF_LEN_IN >= sizeof(hid_enter_report_t) &&
USBD_HID_BUF_LEN_IO4 >= sizeof(io4_hid_in_t) &&
USBD_HID_BUF_LEN_IO4 >= sizeof(io4_hid_out_t),
"HID USB buffer insufficient size for possible reports");
// Endpoint packet max size (cannot total more than 512!)
#define EP0_MAX_PKT_SIZE 64
#define EP1_MAX_PKT_SIZE 64
#define EP2_MAX_PKT_SIZE USBD_CDC_IN_MAX_SIZE
#define EP3_MAX_PKT_SIZE USBD_CDC_OUT_MAX_SIZE
#define EP4_MAX_PKT_SIZE USBD_CDC_CMD_MAX_SIZE
#define EP5_MAX_PKT_SIZE USBD_HID_BUF_LEN
#define EP6_MAX_PKT_SIZE USBD_HID_BUF_LEN
#define EP7_MAX_PKT_SIZE USBD_HID_BUF_LEN
// Endpoint packet max size (cannot total more than 504!)
// Control must be 64 bytes because we're receiving IO4 OUT over EP1
#define EP0_MAX_PKT_SIZE 64 // Control | device->host
#define EP1_MAX_PKT_SIZE 64 // Control | host ->device
#define EP2_MAX_PKT_SIZE USBD_CDC_IN_MAX_SIZE // CDC | device->host
#define EP3_MAX_PKT_SIZE USBD_CDC_OUT_MAX_SIZE // CDC | host ->device
#define EP4_MAX_PKT_SIZE USBD_CDC_CMD_MAX_SIZE // CDC command | device->host (hmm?)
#define EP5_MAX_PKT_SIZE USBD_HID_BUF_LEN_IO4 // IO4 HID | device->host
#define EP6_MAX_PKT_SIZE USBD_HID_BUF_LEN_IN // Misc HID | device->host
#define EP7_MAX_PKT_SIZE USBD_HID_BUF_LEN_OUT // Misc HID | host ->device
// TODO: Do we need host->device HID? We could save quite a few bytes and an endpoint!
#define SETUP_BUF_BASE 0
#define SETUP_BUF_LEN 8

View File

@ -1341,6 +1341,12 @@
#define HID_USAGE_BARREL_SWITCH 1, 0x44
#define HID_USAGE_ERASER 1, 0x45
#define HID_USAGE_TABLET_PICK 1, 0x46
#define HID_USAGE_CONFIDENCE 1, 0x47
#define HID_USAGE_WIDTH 1, 0x48
#define HID_USAGE_HEIGHT 1, 0x49
#define HID_USAGE_CONTACT_IDENTIFIER 1, 0x51
#define HID_USAGE_CONTACT_COUNT 1, 0x54
#define HID_USAGE_SCAN_TIME 1, 0x56
/* Alphanumeric Display Usages */
#define HID_USAGE_ALPHANUMERIC_DISPLAY 1, 0x01

View File

@ -55,12 +55,12 @@ void Tas_USBD_Init(void) {
USBD_CONFIG_EP(EP_HID_IO4_IN, USBD_CFG_EPMODE_IN | 4);
USBD_SET_EP_BUF_ADDR(EP_HID_IO4_IN, EP5_BUF_BASE);
// Misc HID IN/OUT on EP6/EP6
// Misc HID IN/OUT on EP6/EP7
USBD_CONFIG_EP(EP_HID_MISC_IN, USBD_CFG_EPMODE_IN | 5);
USBD_SET_EP_BUF_ADDR(EP_HID_MISC_IN, EP6_BUF_BASE);
USBD_CONFIG_EP(EP_HID_MISC_OUT, USBD_CFG_EPMODE_OUT | 6);
USBD_SET_EP_BUF_ADDR(EP_HID_MISC_OUT, EP7_BUF_BASE);
USBD_SET_PAYLOAD_LEN(EP_HID_MISC_OUT, USBD_HID_BUF_LEN);
USBD_SET_PAYLOAD_LEN(EP_HID_MISC_OUT, USBD_HID_BUF_LEN_OUT);
}
void Tas_USBD_Start(void) {
// 100ms delay required as part of spec