413 lines
14 KiB
C
413 lines
14 KiB
C
#include "tasoller.h"
|
|
|
|
hsv_t gaControlledIntLedData[LED_NUM_GROUND] = { 0 };
|
|
uint8_t gbLedDataIsControlledInt = 0;
|
|
uint8_t gu8aControlledExtLedData[32 * 3];
|
|
uint8_t gbLedDataIsControlledExt = 0;
|
|
|
|
volatile uint8_t gu8LEDTx[LED_Tx_BUFFER];
|
|
static void (*s_I2C1HandlerFn)(uint32_t u32Status) = NULL;
|
|
|
|
volatile static uint8_t su8LedTxDataLock = 0;
|
|
|
|
// Helper definitions
|
|
#define SLAVE_RX_ADDR_ACK 0x60
|
|
#define SLAVE_RX_ACK 0x80
|
|
#define I2C_SLAVE_RX_NACK 0x88
|
|
#define I2C_SLAVE_TX_REPEAT_START_STOP 0xA0
|
|
#define SLAVE_TX_ACK 0xA8
|
|
#define I2C_SLAVE_TX_NACK 0xC0
|
|
|
|
void I2C1_IRQHandler(void) {
|
|
if (I2C_GET_TIMEOUT_FLAG(I2C1)) {
|
|
I2C_ClearTimeoutFlag(I2C1);
|
|
} else {
|
|
if (s_I2C1HandlerFn != NULL) (s_I2C1HandlerFn)(I2C1->I2CSTATUS);
|
|
}
|
|
}
|
|
|
|
void I2C1_SlaveTx(uint32_t u32Status) {
|
|
static uint8_t su8I2CReadAddr = 0;
|
|
|
|
switch (u32Status) {
|
|
case SLAVE_RX_ACK:
|
|
su8I2CReadAddr = I2C1->I2CDAT;
|
|
su8LedTxDataLock = 1;
|
|
I2C_SET_CONTROL_REG(I2C1, I2C_I2CON_SI_AA);
|
|
break;
|
|
case SLAVE_TX_ACK:
|
|
I2C_SET_DATA(I2C1, gu8LEDTx[su8I2CReadAddr]);
|
|
I2C_SET_CONTROL_REG(I2C1, I2C_I2CON_SI_AA);
|
|
break;
|
|
|
|
case SLAVE_RX_ADDR_ACK:
|
|
case I2C_SLAVE_TX_NACK:
|
|
case I2C_SLAVE_RX_NACK:
|
|
case I2C_SLAVE_TX_REPEAT_START_STOP:
|
|
I2C_SET_CONTROL_REG(I2C1, I2C_I2CON_SI_AA);
|
|
break;
|
|
|
|
default:
|
|
// Hmm?
|
|
I2C_SET_CONTROL_REG(I2C1, I2C_I2CON_SI_AA);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LED_I2C1_Init(void) {
|
|
I2C_Open(I2C1, 100000);
|
|
I2C_SetSlaveAddr(I2C1, 0, 0x18, 0);
|
|
I2C_SetSlaveAddr(I2C1, 1, 0x30, 0);
|
|
I2C_SetSlaveAddr(I2C1, 2, 0x55, 0);
|
|
I2C_SetSlaveAddr(I2C1, 3, 0x18, 0);
|
|
I2C_SetSlaveAddrMask(I2C1, 0, 1);
|
|
I2C_SetSlaveAddrMask(I2C1, 1, 4);
|
|
I2C_SetSlaveAddrMask(I2C1, 2, 1);
|
|
I2C_SetSlaveAddrMask(I2C1, 3, 4);
|
|
I2C_EnableInt(I2C1);
|
|
NVIC_EnableIRQ(I2C1_IRQn);
|
|
|
|
// I2C1 enter no address SLV mode
|
|
I2C_SET_CONTROL_REG(I2C1, I2C_I2CON_SI_AA);
|
|
|
|
s_I2C1HandlerFn = I2C1_SlaveTx;
|
|
}
|
|
|
|
// static inline void LEDTxLock(void) {
|
|
// gu8LEDTx[0] = 0;
|
|
// }
|
|
// static inline void LEDTxCommit(uint8_t u8Command, uint8_t u8NExpected) {
|
|
// gu8LEDTx[0] = u8Command;
|
|
// }
|
|
|
|
/**
|
|
* @brief Convert from RGB to HSV
|
|
*
|
|
* @param pu8aRGB Destination 3-tuple to receive RGB values
|
|
* @param u16H Hue, ranging 0~LED_HUE_MAX
|
|
* @param u8S Saturation, ranging 0~255
|
|
* @param u8V Value, ranging 0~255
|
|
*/
|
|
void HsvToRgb(uint8_t* pu8aRGB, uint16_t u16H, uint8_t u8S, uint8_t u8V) {
|
|
if (u8S == 0) {
|
|
pu8aRGB[0] = u8V;
|
|
pu8aRGB[1] = u8V;
|
|
pu8aRGB[2] = u8V;
|
|
return;
|
|
}
|
|
|
|
uint8_t region = u16H / (LED_HUE_MAX / 6);
|
|
uint8_t remainder = (u16H - (region * (LED_HUE_MAX / 6))) * (255 / (LED_HUE_MAX / 6));
|
|
|
|
uint8_t p = (u8V * (255 - u8S)) >> 8;
|
|
uint8_t q = (u8V * (255 - ((u8S * remainder) >> 8))) >> 8;
|
|
uint8_t t = (u8V * (255 - ((u8S * (255 - remainder)) >> 8))) >> 8;
|
|
|
|
switch (region) {
|
|
case 0:
|
|
pu8aRGB[0] = u8V;
|
|
pu8aRGB[1] = t;
|
|
pu8aRGB[2] = p;
|
|
break;
|
|
case 1:
|
|
pu8aRGB[0] = q;
|
|
pu8aRGB[1] = u8V;
|
|
pu8aRGB[2] = p;
|
|
break;
|
|
case 2:
|
|
pu8aRGB[0] = p;
|
|
pu8aRGB[1] = u8V;
|
|
pu8aRGB[2] = t;
|
|
break;
|
|
case 3:
|
|
pu8aRGB[0] = p;
|
|
pu8aRGB[1] = q;
|
|
pu8aRGB[2] = u8V;
|
|
break;
|
|
case 4:
|
|
pu8aRGB[0] = t;
|
|
pu8aRGB[1] = p;
|
|
pu8aRGB[2] = u8V;
|
|
break;
|
|
default:
|
|
pu8aRGB[0] = u8V;
|
|
pu8aRGB[1] = p;
|
|
pu8aRGB[2] = q;
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// 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)
|
|
// 0x1X: [CFW] Rainbow with black gaps. X bars black (from right)
|
|
// 0x20: [CFW] Flashes three times
|
|
// 0x80: [Stock] Flashes three times
|
|
static uint8_t su8LedSpecial = 0x05;
|
|
|
|
// 0: Separator bar every 4 (4 sections)
|
|
// 1: Separator bar every 2 (8 sections)
|
|
// 2: Separator bar every 1 (16 sections)
|
|
// 3: No separator bars
|
|
// 4~7: LEDs off
|
|
// For some reason this value can be |8, even though firmware suggests otherwise
|
|
static uint8_t su8LedSeparators = 1;
|
|
|
|
// 0: Invalid, but functions as 1
|
|
// 1: 32-key mode
|
|
// 2: 16-key mode (same as 32key mode)
|
|
// 3: 8-key mode (bars light in pairs)
|
|
// 4: 4-key mode (bars light in quads)
|
|
static uint8_t su8LedNKey = 1;
|
|
|
|
// 0x40: Turns off ground LEDs
|
|
// 0x80: Turns off wing LEDs
|
|
static uint8_t su8LedOff = 0;
|
|
|
|
// 0x8X: Separator 1~(X+1) lit (ie X ranges from 0~E; F is the same as E)
|
|
static uint8_t su8LedCfwRainbow = 0x8F;
|
|
|
|
void LED_WriteBasicGrounds(void) {
|
|
gu8LEDTx[0] = LED_CMD_BASIC;
|
|
|
|
// 32 bits of grounds
|
|
// (01,02)=key1, (04,08)=key2 (10,20)=key3, (40,80)=key4
|
|
gu8LEDTx[1] = 0;
|
|
gu8LEDTx[2] = 0;
|
|
gu8LEDTx[3] = 0;
|
|
gu8LEDTx[4] = 0;
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
for (uint8_t j = 0; j < 8; j++) {
|
|
if (gu8GroundData[i * 8 + j] > PSoC_INTERNAL_DIGITAL_TH) gu8LEDTx[i + 1] |= (1 << j);
|
|
}
|
|
}
|
|
|
|
#ifdef LED_FIRMWARE_CFW
|
|
gu8LEDTx[5] = 0; // Wing fill
|
|
for (uint8_t i = 0; i < 6; i++)
|
|
if (gu8DigitalButtons & (1 << (i + 2))) gu8LEDTx[5] |= 1 << i;
|
|
gu8LEDTx[6] = su8LedCfwRainbow;
|
|
gu8LEDTx[7] = 0; // Unused
|
|
#else
|
|
// Wings
|
|
gu8LEDTx[5] = 0; // Wing fill
|
|
for (uint8_t i = 0; i < 6; i++)
|
|
if (gu8DigitalButtons & (1 << (i + 2))) gu8LEDTx[5] |= 1 << i;
|
|
gu8LEDTx[6] = gConfig.u16HueWingLeft / LED_HUE_SCALE; // Hue left (default 330)
|
|
gu8LEDTx[7] = gConfig.u16HueWingRight / LED_HUE_SCALE; // Hue right (default 180)
|
|
#endif
|
|
|
|
// Unused in CFW
|
|
gu8LEDTx[8] = gConfig.u16HueGround / LED_HUE_SCALE; // Hue ground inactive (default 45)
|
|
gu8LEDTx[9] = gConfig.u16HueGroundActive / LED_HUE_SCALE; // Hue ground active (default 330)
|
|
|
|
// In CFW only su8LedOff is respected
|
|
gu8LEDTx[10] = (su8LedSeparators << 4) | su8LedOff | su8LedNKey;
|
|
gu8LEDTx[11] = su8LedSpecial;
|
|
}
|
|
|
|
static const uint8_t su8aWingSensors[LED_NUM_LEFT] = {
|
|
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, //
|
|
DIGITAL_AIR4_Msk, DIGITAL_AIR4_Msk, DIGITAL_AIR4_Msk, DIGITAL_AIR4_Msk, //
|
|
DIGITAL_AIR5_Msk, DIGITAL_AIR5_Msk, DIGITAL_AIR5_Msk, DIGITAL_AIR5_Msk, //
|
|
DIGITAL_AIR6_Msk, DIGITAL_AIR6_Msk, DIGITAL_AIR6_Msk, DIGITAL_AIR6_Msk, //
|
|
};
|
|
|
|
#define SATURATION_ACTIVE 255
|
|
#define SATURATION_INACTIVE 240
|
|
#define VALUE_ACTIVE (gConfig.u8LedWingBrightness)
|
|
#define VALUE_INACTIVE (gConfig.u8LedWingBrightness / 2)
|
|
|
|
static void LED_OffGround(void) {
|
|
memset((uint8_t*)&gu8LEDTx[LED_DATA_OFFSET], 0, LED_NUM_GROUND * 3);
|
|
}
|
|
static void LED_OffWings(void) {
|
|
memset((uint8_t*)&gu8LEDTx[LED_DATA_OFFSET + LED_NUM_GROUND * 3], 0,
|
|
(LED_NUM_LEFT + LED_NUM_RIGHT) * 3);
|
|
}
|
|
static void LED_AirWings(void) {
|
|
uint8_t u8aRgbActive[3];
|
|
uint8_t u8aRgbInactive[3];
|
|
|
|
uint8_t j, i = LED_NUM_GROUND;
|
|
|
|
// Left wing
|
|
HsvToRgb(u8aRgbActive, gConfig.u16HueWingLeft, SATURATION_ACTIVE, VALUE_ACTIVE);
|
|
HsvToRgb(u8aRgbInactive, gConfig.u16HueWingLeft, SATURATION_INACTIVE, VALUE_INACTIVE);
|
|
for (j = 0; i < LED_NUM_GROUND + LED_NUM_LEFT; i++, j++) {
|
|
// GRB
|
|
if (gu8DigitalButtons & su8aWingSensors[LED_NUM_LEFT - j - 1]) {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRgbActive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRgbActive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRgbActive[2];
|
|
} else {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRgbInactive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRgbInactive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRgbInactive[2];
|
|
}
|
|
}
|
|
|
|
// Right wing
|
|
HsvToRgb(u8aRgbActive, gConfig.u16HueWingRight, SATURATION_ACTIVE, VALUE_ACTIVE);
|
|
HsvToRgb(u8aRgbInactive, gConfig.u16HueWingRight, SATURATION_INACTIVE, VALUE_INACTIVE);
|
|
for (j = 0; i < LED_NUM_GROUND + LED_NUM_LEFT + LED_NUM_RIGHT; i++, j++) {
|
|
// GRB
|
|
if (gu8DigitalButtons & su8aWingSensors[j]) {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRgbActive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRgbActive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRgbActive[2];
|
|
} else {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRgbInactive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRgbInactive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRgbInactive[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint8_t LED_ScaleU8(uint8_t u8V, uint8_t u8Scale) {
|
|
return ((uint16_t)u8V * (uint16_t)u8Scale) / 255;
|
|
}
|
|
|
|
static void LED_GroundRainbow(void) {
|
|
uint8_t u8aRGB[3];
|
|
|
|
// 5 ticks * 360 hue = 1800 calls for one cycle (1.8s)
|
|
static uint16_t u16Hue = 0;
|
|
static uint8_t u8Ticker = 0;
|
|
if (++u8Ticker == 5) {
|
|
u8Ticker = 0;
|
|
u16Hue++;
|
|
if (u16Hue == LED_HUE_MAX) u16Hue = 0;
|
|
}
|
|
|
|
/**
|
|
* There are 48 LEDs for ground, but we only send 31 values
|
|
* They're mapped to the LEDs as follows:
|
|
* 00a11b22c33d44f55g66h77i...
|
|
*
|
|
* That is, we can't split-colour a key :P
|
|
*/
|
|
for (uint8_t i = 0; i < LED_NUM_GROUND; i++) {
|
|
uint8_t v = 190;
|
|
uint8_t h = 0;
|
|
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;
|
|
}
|
|
|
|
// GRB
|
|
HsvToRgb(u8aRGB, (u16Hue + h + (i * (LED_HUE_MAX / LED_NUM_GROUND))) % LED_HUE_MAX, v,
|
|
LED_ScaleU8(v - 63, gConfig.u8LedGroundBrightness));
|
|
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRGB[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRGB[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRGB[2];
|
|
}
|
|
}
|
|
static void LED_GroundStatic(void) {
|
|
uint8_t u8aGround[3];
|
|
uint8_t u8aGroundActive[3];
|
|
|
|
HsvToRgb(u8aGround, gConfig.u16HueGround, 255, gConfig.u8LedGroundBrightness);
|
|
HsvToRgb(u8aGroundActive, gConfig.u16HueGroundActive, 255, gConfig.u8LedGroundBrightness);
|
|
for (uint8_t i = 0; i < LED_NUM_GROUND; 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)) {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aGroundActive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aGroundActive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aGroundActive[2];
|
|
} else {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aGround[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aGround[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aGround[2];
|
|
}
|
|
} else if (nCell % 4 == 3) {
|
|
// This is a separating divider. Light it with the active colour
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aGroundActive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aGroundActive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aGroundActive[2];
|
|
} else {
|
|
// This is a non-separating divider. Light it based on the two cells either side
|
|
if (gu16PSoCDigital & (1 << nCell) && gu16PSoCDigital & (1 << (nCell + 1))) {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aGroundActive[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aGroundActive[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aGroundActive[2];
|
|
} else {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aGround[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aGround[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aGround[2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LED_WriteRGB(void) {
|
|
gu8LEDTx[0] = LED_CMD_RGB_FULL;
|
|
|
|
#ifdef LED_FIRMWARE_CFW
|
|
// "CFW" added this byte
|
|
gu8LEDTx[1] = su8LedSpecial | su8LedOff;
|
|
#endif
|
|
|
|
// Even when grounds are disabled, internal control overrides that
|
|
if (gbLedDataIsControlledInt) {
|
|
PIN_LED_GROUND_PWR = 1;
|
|
|
|
uint8_t u8aRGB[3];
|
|
// Convert from HSV to GRB
|
|
for (uint8_t i = 0; i < LED_NUM_GROUND; i++) {
|
|
HsvToRgb(u8aRGB, gaControlledIntLedData[LED_NUM_GROUND - i - 1].u16H,
|
|
gaControlledIntLedData[LED_NUM_GROUND - i - 1].u8S,
|
|
gaControlledIntLedData[LED_NUM_GROUND - i - 1].u8V);
|
|
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] = u8aRGB[1];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] = u8aRGB[0];
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] = u8aRGB[2];
|
|
}
|
|
} else if (gConfig.u8LedGroundBrightness) {
|
|
PIN_LED_GROUND_PWR = 1;
|
|
|
|
if (gbLedDataIsControlledExt) {
|
|
// Swap from BRG to GRB
|
|
for (uint8_t i = 0; i < LED_NUM_GROUND; i++) {
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 0] =
|
|
LED_ScaleU8(gu8aControlledExtLedData[i * 3 + 2], gConfig.u8LedGroundBrightness);
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 1] =
|
|
LED_ScaleU8(gu8aControlledExtLedData[i * 3 + 1], gConfig.u8LedGroundBrightness);
|
|
gu8LEDTx[LED_DATA_OFFSET + i * 3 + 2] =
|
|
LED_ScaleU8(gu8aControlledExtLedData[i * 3 + 0], gConfig.u8LedGroundBrightness);
|
|
}
|
|
} else if (gConfig.bEnableRainbow) {
|
|
LED_GroundRainbow();
|
|
} else {
|
|
LED_GroundStatic();
|
|
}
|
|
} else {
|
|
PIN_LED_GROUND_PWR = 0;
|
|
LED_OffGround();
|
|
}
|
|
|
|
if (gConfig.u8LedWingBrightness) {
|
|
PIN_LED_WING_PWR = 1;
|
|
// TODO: Get data from game when gbLedDataIsControlledExt (HID, probably)
|
|
LED_AirWings();
|
|
} else {
|
|
PIN_LED_WING_PWR = 0;
|
|
LED_OffWings();
|
|
}
|
|
}
|