host-aprom/src/led.c
2024-06-29 21:17:34 +01:00

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();
}
}