/*
 * $Id: fw.c 464 2019-08-01 02:15:54Z true $
 * begin 20190504 true
 *
 * nametag main firmware code.
 *
 * some notes about this project:
 *
 *   it's a nametag.
 *
 *   some busses are overclocked to reduce CPU awake time
 *     and to allow for more time to get other work done.
 *     when no work is being done, try to save power.
 *     power budget was considered but not the biggest factor.
 *
 *   oled is the only device on SMB1.
 *   oled bus is overclocked to ~666kHz (400kHz nominal).
 *   oled can work at 1MHz or more but product consistency varies.
 *
 *   adxl345 is only device on SPI0.
 *   adxl345 bus is overclocked to 6MHz (5MHz rated max).
 *
 *   I wonder what UART0 and SMB0 do... nothing yet. wait for DC28
 *   (yes firmware will be updated for then)
 *   (no I don't know if there will be more nametags, depends on demand?)
 *
 *   thank you for your support, sorry for all the bugs,
 *   I don't do this shit for a living
 *
 *   As of v0.2.7a, have 8607 bytes for implementing neat shit...
 */


#include "fw.h"

// display shit
#include "disp/draw_ssd1306.h"
#include "driver/efm8_smb1.h"	// manual send i2c stop condition at startup

// sensor shit
#include "sensor/adxl.h"
#include "sensor/lightlevel.h"
#include "sensor/system.h"

// rgbled shit
#include "led/rgbled.h"

// menus and UI
#include "ui/menu.h"
#include "ui/btn.h"

// other shit
#include "driver/efm8ub2_hwid.h"
#include "math/rand16.h"

// user's preferences
#include "user_config.h"


// obvious
uint8_t code ver[] = "0.2.7a";


// fw status bits
bit fw_interrupt;
bit fw_loop_busy;

// timing
uint8_t data  tick_ctr = 0;
uint8_t idata missed = 0;

uint8_t idata cpu_use = 0;
uint8_t idata cpu_max = 0;

uint32_t data uptime = 0;



  // non-timing critical code after oled update
void fw_oled_update_done()
{
	int16_t a;
	int8_t rot;


	// reset oled callback, clear screen, and set default pixel mode and font size
	oled.callback = 0;
	ssd1306_cls(&oled);
	ssd1306fb_set_color(SSD1306_STATE_SET_PIXEL);
	oled.state &= ~SSD1306_STATE_STR_HALFWIDTH;


	// orientation / flipping flags
	rot = adxl345_get_rotation(&adxl);

	if ((rot > (96+4)) || (rot < (32-4))) {
		sysflags &= ~SYS_OLED_ROTATE_X;
	} else if ((rot > (32+4)) && (rot < (96-4))) {
		sysflags |=  SYS_OLED_ROTATE_X;
	}

	if ((rot > (64+4)) && (rot < 124)) {
		sysflags &= ~SYS_OLED_ROTATE_Y;
	} else if ((rot > 4) && (rot < (64-4))) {
		sysflags |=  SYS_OLED_ROTATE_Y;
	}

	if ((rot < 21) || (rot > (64 + 24))) {
		sysflags &= ~SYS_OLED_REVERSE_CHARS;
	} else if (rot > 24 && rot < (64 + 21)){
		sysflags |=  SYS_OLED_REVERSE_CHARS;
	}


	// do menu operations
	menu_tick();


	// calculate CPU usage
	a = ((missed << 3) / 5);

	if (uconf.framemod == UCONF_FRAMERATE_HALF) {
		// the above calculation is tied to framerate.
		// so this will compensate for half framerate mode...
		a >>= 1;
	}

	if (a > cpu_max) {
		cpu_max = a;
	}
	cpu_use = a;


	// reset missed interrupt counter
	missed = 0;
}


  // main firmware application initialization
void fw_init()
{
	uint8_t i;

	// clear RGBLED colors
	for (i = 0; i <= 3; i++) {
		hsv[i].h = 0x00;
		hsv[i].s = hsv[i].v = 0x00;
	}

	// accelerometer
	adxl345_init();
	adxl345_tick();		// get some values for randomness init

	// rand16
	rand16_init(0x1337 - ((adxl.x | adxl.y) ^ adxl.z));

	// others
	hwid_init();
	btn_init();

	// user config
	uconf_load();

	// oled
	smb1_stop();
	smb1_stop();
	ssd1306fb_set_target(&oled);

	ssd1306_init(1);	// we'll try to init later too, since sometimes they fail

	// light pulse
	lsens_pulse(1);

	// start up the menu
	// lol. yes, this "starts" the "menu" in nametag mode
	menu_stop(0);

	// enable the timer5 (main firmware tick) interrupt
	EIE2 |= EIE2_ET5__ENABLED;
}


/*
 * timing-sensitive, direct-in-ISR tasks.
 */
void fw_tick_critical()
{
	uint8_t xdata led[14];


	/*** timing counter ***/
	tick_ctr++;
	if (!tick_ctr) {
		uptime++;
	}

	if (fw_loop_busy) {
		missed++;
	}



	/*** rgbled driver ***/
	// we run this right away as any variation will create flicker
	if (uconf.flags & UCONF_FLAGS_LEDS_ENABLE) {
		// set pwms off
		LED_K_R = LED_K_G = LED_K_B = 0;

		// now set PWMs
		rgbled_update(tick_ctr & 0x03);

		// reset PWM counter
		if (tick_ctr & 0x03) {
			PCA0L = 0x00;
		}

		// enable lines
		LED_K_R = ((tick_ctr & 0x03) == 0x01) ? 1 : 0;
		LED_K_G = ((tick_ctr & 0x03) == 0x02) ? 1 : 0;
		LED_K_B = ((tick_ctr & 0x03) == 0x03) ? 1 : ((tick_ctr & 0x03) == 0x00) ? 1 : 0;
	} else {
		// LEDs disabled... but we still process them...
		LED_K_R = LED_K_G = LED_K_B = 0;
	}



	/*** accelerometer ***/ 		// ~128Hz, after smoothing 64Hz usable data
	// update
	if ((tick_ctr % 8) == 0) {
		adxl345_tick();
	}



	/*** buttons ****/
	btn_tick();



	/*** rgbled programs ***/
	// here's some hardcoded values as I don't have a proper editor
	// or multi-program support yet.
	led[0] = 0x00;
	led[1] = uconf.favcolor_hue;
	led[2] = uconf.favcolor_sat;
	led[3] = uconf.favcolor_val;
	led[4] = uconf.altcolor_hue;
	led[5] = uconf.altcolor_sat;
	led[6] = uconf.altcolor_val;
	led[7] = 0x80;
	led[8] = (uconf.ledprog_active & 0xf0) - 2;
	led[12] = 0x28;		// random rate
	led[13] = 0x0f;		// individual bit enabler

	// special per-program overrides
	switch (uconf.ledprog_active & 0x0f) {
		case 3: {	// rotate cw
			led[0] = 0x01;		// need CW mode
			break;
		}
		case 5: {	// color flicker
			led[12] = 0x60;		// more often show colors
			break;
		}
		case 6: {	// color flicker
			led[12] = 0xff;		// always show colors
			break;
		}
		case 11: {	// pulsefade
			led[13] = led[8];	// speed is here
			break;
		}
		case 12: {	// rainbow
			led[1] = 0;
			led[4] = 255;
			led[9] = led[10] = led[11] = led[12] = 0;
			break;
		}
		case 13: {	// rainbow offset
			led[1] = 0;
			led[4] = 255;
			led[9] = 0;
			led[10] = 64;
			led[11] = 128;
			led[12] = 192;
			break;
		}
		case 14: {	// colorfade
			led[0] = 0x02;	// auto-reverse
			led[9] = led[10] = led[11] = led[12] = 0;
			break;
		}
	}

	// aha nice try hacker
	if ((uconf.ledprog_active & 0x0f) > (sizeof(ledprog) / sizeof(ledprog[0]))) {
		uconf.ledprog_active &= 0xf0;
	}

	if (ledprog[(uconf.ledprog_active & 0x0f)].progfn) {
		ledprog[(uconf.ledprog_active & 0x0f)].progfn(&led);
	}
}

/*
 * less timing sensitive, yet still timed tasks.
 */
void fw_tick_mainloop()
{
	fw_loop_busy = 1;



	/*** some OLEDs have dramatically bad startup delay  ***/
	/***  try to re-init every second for first 10 secs ***/
	if ((uptime < 40) && (tick_ctr < uconf.framemod)) {
		if ((uint8_t)uptime && ((uint8_t)uptime % 4 == 0) && (tick_ctr == 0)) {
			ssd1306_init(0);
		}
		fw_loop_busy = 0;
		return;
	}

	/*** button callback processing ***/
	btn_callback();



	/*** oled ***/
	if (oled.callback && !(oled.state & SSD1306_STATE_BUSY)) {
		if (ssd1306_cb_get()) {
			oled.callback();
		}
	}

	// update
	if ((tick_ctr % uconf.framemod) == 0) {		// 64 == 16fps, gives us some good cycles to update UI
		// process other tasks
		if (oled.state & SSD1306_STATE_INITIALIZED) {
			oled.callback = fw_oled_update_done;
		}
		ssd1306_update();
	}



	/*** status LED ***/
	lsens_tick();



	/*** wrapping up ***/
	rand16();



	fw_loop_busy = 0;
}
