diff --git a/Firmware/PocketVoltex/LED_Driver_SK9822.c b/Firmware/PocketVoltex/LED_Driver_SK9822.c new file mode 100644 index 0000000..b04a6da --- /dev/null +++ b/Firmware/PocketVoltex/LED_Driver_SK9822.c @@ -0,0 +1,52 @@ +#define R 0 +#define G 1 +#define B 2 + +#define SK9822_BRIGHTNESS 1 + +static const uint8_t SK9822_map[] = {0, 2, 4, 6, 7, 5, 3, 1}; + +void led_init(void) { + // SCLK/MOSI PB1/2 + DDRB |= _BV(1)|_BV(2); + + // SPI enabled, master mode, CLK/4 speed + SPCR = _BV(SPE)|_BV(MSTR); + + // normal speed mode + SPSR = 0; +} + +// busy wait +void SPI_write(uint8_t val) { + cli(); + SPDR = val; + while(!(SPSR & _BV(SPIF))) + ; + SPSR = _BV(SPIF); // clear flag + sei(); +} + +void led_commit(void) { + SPI_write(0x00); // Start Frame + SPI_write(0x00); + SPI_write(0x00); + SPI_write(0x00); + + for (uint8_t i = 0; i < LED_COUNT; i++) + { + SPI_write(0xe0|SK9822_BRIGHTNESS); // Maximum global brightness + uint8_t offset = SK9822_map[i] * 3; + SPI_write(leds[offset+B]); + SPI_write(leds[offset+G]); + SPI_write(leds[offset+R]); + } + + // Reset frame - Only needed for SK9822, has no effect on APA102 + SPI_write(0x00); + SPI_write(0x00); + SPI_write(0x00); + SPI_write(0x00); + // End frame - 1 for every 16 LEDs + SPI_write(0x00); +} \ No newline at end of file diff --git a/Firmware/PocketVoltex/LED_Driver_Software.c b/Firmware/PocketVoltex/LED_Driver_Software.c new file mode 100644 index 0000000..73af5fe --- /dev/null +++ b/Firmware/PocketVoltex/LED_Driver_Software.c @@ -0,0 +1,128 @@ +#define GND_COUNT 4 +// RGB * 2 +#define LED_PINS 6 + +// LED gnd 0-3 are on PC7-4 +#define GND_PORT PORTC +#define GND_DDR DDRC +#define GND_MASK 0xF0 +#define GND_OFFSET 4 // in bits + +// LED power BGR BGR PB2-7 +#define LED_PORT PORTB +#define LED_DDR DDRB +#define LED_MASK (0b111111 << 2) + +// How many are actually PWM'd, because the chip isn't that quick +// MUST be a multiple of BRIGHTNESS_LEVELS +#define BRIGHTNESS_DOWNSCALE 128 +#define BRIGHTNESS_INCREMENT (BRIGHTNESS_LEVELS / BRIGHTNESS_DOWNSCALE) + +#define UPDATE_HZ 100 +// prescaler is the div8 +#define TIMER_COMPARE ((F_CPU / 8 / UPDATE_HZ / GND_COUNT / BRIGHTNESS_DOWNSCALE)-1) +#if TIMER_COMPARE > 255 + #error timer compare too large for timer register +#endif + +#define R 2 +#define G 1 +#define B 0 + +static volatile uint8_t leds_frontbuffer[LED_PHYSICAL_COUNT]; + +void led_init(void) { + // all GNDs low level for high impedence or gnd + GND_PORT &= ~GND_MASK; + // all GNDs input + GND_DDR &= ~GND_MASK; + + // all LEDs off + LED_PORT &= ~LED_MASK; + // all LEDs output + LED_DDR |= LED_MASK; + + memset(leds, 0, LED_PHYSICAL_COUNT); + memset((uint8_t*)leds_frontbuffer, 0, LED_PHYSICAL_COUNT); + + // 64 light levels * 60Hz update * 4 different GND pins = 15360Hz + // 520 clock cycles for our interrupt handler + // CTC mode + TCCR0A = _BV(WGM01); + // clk/8 prescaler + TCCR0B = _BV(CS01); + OCR0A = TIMER_COMPARE; + // Enable interrupt on OCR0A + TIMSK0 = _BV(OCIE0A); + // Clear interrupt + TIFR0 = _BV(OCF0A); +} + +void led_commit(void) { + memcpy((uint8_t*)leds_frontbuffer, leds, LED_PHYSICAL_COUNT); +} + +/* Straight voodoo magic, consult the Inline Assembler Cookbook + Equivalent to: + if(*led++ > brightness) + out |= _BV(outPin) +*/ +#define LED_PIN_SET(led, outPin) \ + __asm__ volatile( \ + "ld __tmp_reg__, %a["#led"]+ \n\t\ + cp %[bright], __tmp_reg__ \n\t\ + brcc skip%= \n\t\ + ori %[out], (1 << "#outPin") \n\t\ + skip%=:" \ + : [out] "+a" (out), [led] "+z" (led) /* outputs */ \ + : [bright] "r" (brightness) /* inputs */ ) + +// This function once took about 279 clock cycles. +// Optimised GND accesses got it to 157 +// Optimised variables to static, got it to 100 +// Made LED setter assembly, got it to 90 +ISR(TIMER0_COMPA_vect) { + /* Why are these static here instead of at the top of file? + The compiler won't optimise 2 consecutive operations to use a register, + and instead will perform a costly lds-sts every time. Making them + static here will cache them in a local register. + */ + // Because we roll over on each loop and want to start at 0 this starts at max + static uint8_t currentGnd = GND_COUNT - 1; + // This saves us doing a costly dynamic _BV() + static uint8_t currentGndMask = 0; + static uint8_t brightness = BRIGHTNESS_LEVELS - BRIGHTNESS_INCREMENT; + static volatile uint8_t* offset = &leds_frontbuffer[0]; + + uint8_t out = 0; + + currentGnd++; + currentGndMask >>= 1; + if(currentGnd >= GND_COUNT) { + currentGnd = 0; + // Because we work backwards start at the high end and shift down + currentGndMask = _BV(7); + offset = &leds_frontbuffer[0]; + brightness += BRIGHTNESS_INCREMENT; + // brightness rolls over cleanly due to being a multiple + #if BRIGHTNESS_LEVELS != 256 + if(brightness > BRIGHTNESS_MAX) + brightness = 0; + #endif + } + + // Faster than loops + // NOTE: ASM MACRO INCREMENTS OFFSET + LED_PIN_SET(offset, 2); + LED_PIN_SET(offset, 3); + LED_PIN_SET(offset, 4); + LED_PIN_SET(offset, 5); + LED_PIN_SET(offset, 6); + LED_PIN_SET(offset, 7); + + // Turn off before switch + LED_PORT &= ~LED_MASK; + // Enable new ground + GND_DDR = (GND_DDR & ~GND_MASK) | currentGndMask; + LED_PORT |= out; +} \ No newline at end of file diff --git a/Firmware/PocketVoltex/TestingApp.c b/Firmware/PocketVoltex/TestingApp.c new file mode 100644 index 0000000..ebcf07c --- /dev/null +++ b/Firmware/PocketVoltex/TestingApp.c @@ -0,0 +1,283 @@ +#include "PocketVoltex.h" +#include "Config.h" +#include "Encoder.h" +#include "LED.h" +#include "LEDPatterns.h" +#include "Macro.h" +#include + +#undef SK9822_BRIGHTNESS +#define SK9822_BRIGHTNESS 1 + +#define LOAD_SWITCH(source, sourceBit, result, resultBit) result |= !((source) & _BV(sourceBit)) << resultBit + +// B 0,4,5 +#define SWITCH_MASKB 0b00110001 +// C 2 +#define SWITCH_MASKC 0b00000100 +// D 4,5,6,7 +#define SWITCH_MASKD 0b11110000 + +// How long to wait before moving to internal lighting +#define HID_LED_TIMEOUT 2000 + +// If I add more buttons with the macro key I don't need to care +#if SWITCH_COUNT <= 8 + #define SWITCH_BITMASK_UINT uint8_t +#elif SWITCH_COUNT <= 16 + #define SWITCH_BITMASK_UINT uint16_t +#else + #error TOO MANY SWITCHES +#endif +typedef struct +{ + int8_t X; // VOL-L + int8_t Y; // VOL-R + SWITCH_BITMASK_UINT Buttons; // bitmask +} Joystick_Report_t; + +typedef struct +{ + uint8_t Modifier; // Keyboard modifier byte indicating pressed modifier keys (\c HID_KEYBOARD_MODIFER_* masks) + uint8_t Reserved; // Reserved for OEM use, always set to 0. + uint8_t KeyCode[SWITCH_COUNT]; // Length determined by the number of keys that can be reported +} Keyboard_Report_t; + +typedef struct +{ + uint8_t mainLights[LED_PHYSICAL_COUNT]; + uint8_t btFx[6]; +} LED_Report_t; + +static uint8_t updateLEDs = 1; + +/** LUFA HID Class driver interface configuration and state information. This structure is + * passed to all HID Class driver functions, so that multiple instances of the same class + * within a device can be differentiated from one another. + */ +USB_ClassInfo_HID_Device_t Inputs_HID_Interface = +{ + .Config = + { + .InterfaceNumber = INTERFACE_ID_Inputs, + .ReportINEndpoint = + { + .Address = INPUTS_EPADDR, + .Size = INPUTS_EPSIZE, + .Banks = 1, + }, + .PrevReportINBuffer = NULL, + .PrevReportINBufferSize = MAX(MAX(sizeof(Keyboard_Report_t), sizeof(Joystick_Report_t)), sizeof(USB_MouseReport_Data_t)), + }, +}; + +USB_ClassInfo_HID_Device_t LED_HID_Interface = +{ + .Config = + { + .InterfaceNumber = INTERFACE_ID_LED, + .ReportINEndpoint = + { + .Address = LED_EPADDR, + .Size = LED_EPSIZE, + .Banks = 1, + }, + .PrevReportINBuffer = NULL, + .PrevReportINBufferSize = sizeof(LED_Report_t), + }, +}; + + +// Set to max already so we have our init flash + +void RebootToBootloader(void) { + /* Disconnect from the host - USB interface will be reset later along with the AVR */ + USB_Detach(); + + // Back to the bootloader + wdt_enable(WDTO_250MS); + while(1); +} + +int main(void) +{ + GlobalInterruptDisable(); + + InitConfig(); + + SetupHardware(); + + GlobalInterruptEnable(); + + while(1) { + int8_t x = encoder_get(0); + int8_t y = encoder_get(1); + led_knobs_update(x, y); + encoder_set(0, 0); + encoder_set(1, 0); + if(led_on_frame()) { + updateLEDs = 1; + } + + if(updateLEDs) { + updateLEDs = 0; + led_set_all(32,32,32); + //led_pattern_animate(); + // knob lights go above all + led_overlay_knobs(); + led_commit(); + } + _delay_ms(1); + } + + for (;;) + { + HID_Device_USBTask(&Inputs_HID_Interface); + HID_Device_USBTask(&LED_HID_Interface); + USB_USBTask(); + + Endpoint_SelectEndpoint(CONFIG_OUT_EPADDR); + if (Endpoint_IsOUTReceived()) { + uint8_t ReceivedData[COMMAND_BYTES]; + Endpoint_Read_Stream_LE(ReceivedData, COMMAND_BYTES, NULL); + Endpoint_ClearOUT(); + + command_response_t respond = HandleConfig(ReceivedData); + switch(respond) { + // we are returning the requested data + case RESPOND: + Endpoint_SelectEndpoint(CONFIG_IN_EPADDR); + Endpoint_Write_Stream_LE(ReceivedData, COMMAND_BYTES, NULL); + Endpoint_ClearIN(); + break; + case REBOOT: + RebootToBootloader(); + break; + // no data to return + default: + break; + } + } + + if(updateLEDs) { + updateLEDs = 0; + led_set_all(32,32,32); + //led_pattern_animate(); + // knob lights go above all + led_overlay_knobs(); + led_commit(); + } + } +} + +/** Configures the board hardware and chip peripherals */ +void SetupHardware() +{ + /* Disable watchdog if enabled by bootloader/fuses */ + MCUSR &= ~_BV(WDRF); + wdt_disable(); + + // Inputs + DDRB &= ~SWITCH_MASKB; + DDRC &= ~SWITCH_MASKC; + DDRD &= ~SWITCH_MASKD; + // Pullups + PORTB |= SWITCH_MASKB; + PORTC |= SWITCH_MASKC; + PORTD |= SWITCH_MASKD; + + /* Hardware Initialization */ + encoder_init(); + led_init(); + led_pattern_init(); + + //USB_Init(); +} + +/** HID class driver callback function for the creation of HID reports to the host. + * + * \param[in] HIDInterfaceInfo Pointer to the HID class interface configuration structure being referenced + * \param[in,out] ReportID Report ID requested by the host if non-zero, otherwise callback should set to the generated report ID + * \param[in] ReportType Type of the report to create, either HID_REPORT_ITEM_In or HID_REPORT_ITEM_Feature + * \param[out] ReportData Pointer to a buffer where the created report should be stored + * \param[out] ReportSize Number of bytes written in the report (or zero if no report is to be sent) + * + * \return Boolean \c true to force the sending of the report, \c false to let the library determine if it needs to be sent + */ +bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, + uint8_t* const ReportID, + const uint8_t ReportType, + void* ReportData, + uint16_t* const ReportSize) +{ + *ReportSize = 0; + return false; +} + +/** HID class driver callback function for the processing of HID reports from the host. + * + * \param[in] HIDInterfaceInfo Pointer to the HID class interface configuration structure being referenced + * \param[in] ReportID Report ID of the received report from the host + * \param[in] ReportType The type of report that the host has sent, either HID_REPORT_ITEM_Out or HID_REPORT_ITEM_Feature + * \param[in] ReportData Pointer to a buffer where the received report has been stored + * \param[in] ReportSize Size in bytes of the received HID report + */ +void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, + const uint8_t ReportID, + const uint8_t ReportType, + const void* ReportData, + const uint16_t ReportSize) +{ + +} + + +/** Event handler for the library USB Connection event. */ +void EVENT_USB_Device_Connect(void) +{ + +} + +/** Event handler for the library USB Disconnection event. */ +void EVENT_USB_Device_Disconnect(void) +{ + led_set_all(0,0,0); + led_commit(); +} + +/** Event handler for the library USB Configuration Changed event. */ +void EVENT_USB_Device_ConfigurationChanged(void) +{ + Endpoint_ConfigureEndpoint(CONFIG_OUT_EPADDR, EP_TYPE_BULK, CONFIG_EPSIZE, 1); + Endpoint_ConfigureEndpoint(CONFIG_IN_EPADDR, EP_TYPE_BULK, CONFIG_EPSIZE, 1); + HID_Device_ConfigureEndpoints(&Inputs_HID_Interface); + HID_Device_ConfigureEndpoints(&LED_HID_Interface); + + USB_Device_EnableSOFEvents(); +} + +/** Event handler for the library USB Control Request reception event. */ +void EVENT_USB_Device_ControlRequest(void) +{ + USB_Process_BOS(); + HID_Device_ProcessControlRequest(&Inputs_HID_Interface); + HID_Device_ProcessControlRequest(&LED_HID_Interface); +} + +/** Event handler for the USB device Start Of Frame event. */ +void EVENT_USB_Device_StartOfFrame(void) +{ + HID_Device_MillisecondElapsed(&Inputs_HID_Interface); + HID_Device_MillisecondElapsed(&LED_HID_Interface); + + int8_t x = encoder_get(0); + int8_t y = encoder_get(1); + led_knobs_update(x, y); + encoder_set(0, 0); + encoder_set(1, 0); + // we use a sentinel since this is actually inside an interrupt! + // less LED flicker if ran outside + if(led_on_frame()) { + updateLEDs = 1; + } +}