Skip to content
Snippets Groups Projects
leds.c 7.13 KiB
Newer Older
#include "gpio.h"
#include "portexpander.h"
fleur's avatar
fleur committed
#include "max32665.h"
#include <stdint.h>
#include <string.h>
fleur's avatar
fleur committed
#include <stdbool.h>
#include <stdio.h>
#define NUM_LEDS 15
fleur's avatar
fleur committed
#define DEFAULT_DIM_TOP 1
#define DEFAULT_DIM_BOTTOM 8
#define MAX_DIM 8

static const gpio_cfg_t rgb_dat_pin = {
	PORT_1, PIN_14, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static const gpio_cfg_t rgb_clk_pin = {
	PORT_1, PIN_15, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
fleur's avatar
fleur committed
static uint8_t leds[NUM_LEDS][3];
static uint8_t gamma_table[3][256];
static uint8_t active_groups;
static uint8_t bottom_dim; //index 11-14
static uint8_t top_dim;    //index 0-10
static bool powersave;
static long powerup_wait_cycles = 500;

/***** Functions *****/
// *****************************************************************************

typedef struct {
	float r; // a fraction between 0 and 1
	float g; // a fraction between 0 and 1
	float b; // a fraction between 0 and 1
} rgb;

typedef struct {
	float h; // angle in degrees
	float s; // a fraction between 0 and 1
	float v; // a fraction between 0 and 1
} hsv;

#if 0
static hsv rgb2hsv(rgb in)
{
    hsv         out;
    float      min, max, delta;

    min = in.r < in.g ? in.r : in.g;
    min = min  < in.b ? min  : in.b;

    max = in.r > in.g ? in.r : in.g;
    max = max  > in.b ? max  : in.b;

    out.v = max;                                // v
    delta = max - min;
    if (delta < 0.00001)
    {
        out.s = 0;
        out.h = 0; // undefined, maybe nan?
        return out;
    }
    if( max > 0.0 ) { // NOTE: if Max is == 0, this divide would cause a crash
        out.s = (delta / max);                  // s
    } else {
        // if max is 0, then r = g = b = 0
        // s = 0, h is undefined
        out.s = 0.0;
        out.h = 0.0;                            // its now undefined
        return out;
    }
    if( in.r >= max )                           // > is bogus, just keeps compilor happy
        out.h = ( in.g - in.b ) / delta;        // between yellow & magenta
    else
    if( in.g >= max )
        out.h = 2.0 + ( in.b - in.r ) / delta;  // between cyan & yellow
    else
        out.h = 4.0 + ( in.r - in.g ) / delta;  // between magenta & cyan

    out.h *= 60.0;                              // degrees

    if( out.h < 0.0 )
        out.h += 360.0;

    return out;
}
#endif

static rgb hsv2rgb(hsv in)
{
	float hh, p, q, t, ff;
	long i;
	rgb out;

	if (in.s <= 0.0) { // < is bogus, just shuts up warnings
		out.r = in.v;
		out.g = in.v;
		out.b = in.v;
		return out;
	}
	hh = in.h;
	if (hh >= 360.0)
		hh = 0.0;
	hh /= 60.0;
	i  = (long)hh;
	ff = hh - i;
	p  = in.v * (1.0 - in.s);
	q  = in.v * (1.0 - (in.s * ff));
	t  = in.v * (1.0 - (in.s * (1.0 - ff)));

	switch (i) {
	case 0:
		out.r = in.v;
		out.g = t;
		out.b = p;
		break;
	case 1:
		out.r = q;
		out.g = in.v;
		out.b = p;
		break;
	case 2:
		out.r = p;
		out.g = in.v;
		out.b = t;
		break;

	case 3:
		out.r = p;
		out.g = q;
		out.b = in.v;
		break;
	case 4:
		out.r = t;
		out.g = p;
		out.b = in.v;
		break;
	case 5:
	default:
		out.r = in.v;
		out.g = p;
		out.b = q;
		break;
	}
	return out;
}

static void shift(uint8_t data)
{
	for (int i = 0; i < 8; i++) {
		if (data & 0x80) {
			GPIO_OutSet(&rgb_dat_pin);
		} else {
			GPIO_OutClr(&rgb_dat_pin);
		}
		GPIO_OutClr(&rgb_clk_pin);
		//TMR_Delay(MXC_TMR0, MSEC(1), 0);
		GPIO_OutSet(&rgb_clk_pin);
		//TMR_Delay(MXC_TMR0, MSEC(1), 0);
		data <<= 1;
	}
}

static void leds_start(void)
{
	shift(0x00);
	shift(0x00);
	shift(0x00);
	shift(0x00);
}

static void leds_shift(uint8_t r, uint8_t g, uint8_t b, uint8_t dim)
{
	shift(0xE0 | (dim & 0x1F));
	shift(b);
	shift(g);
	shift(r);
}

static void leds_stop(void)
{
	shift(0xFF);
	shift(0xFF);
	shift(0xFF);
	shift(0xFF);
fleur's avatar
fleur committed
static uint8_t led_to_dim_value(uint8_t led)
{
	return (led < 11) ? top_dim : bottom_dim;
}

void leds_set_dim_top(uint8_t value)
{
	top_dim = (value > MAX_DIM) ? MAX_DIM : value;
}

void leds_set_dim_bottom(uint8_t value)
fleur's avatar
fleur committed
	bottom_dim = (value > MAX_DIM) ? MAX_DIM : value;
fleur's avatar
fleur committed
void leds_prep(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
	leds[led][0] = r;
	leds[led][1] = g;
	leds[led][2] = b;
void leds_get_rgb(uint8_t led, uint8_t *rgb)
{
	rgb[0] = leds[led][0];
	rgb[1] = leds[led][1];
	rgb[2] = leds[led][2];
}

fleur's avatar
fleur committed
#if 0
//don't use, is buggy
void leds_set_autodim(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
{
    if(led==NUM_LEDS){
        leds_set(led,r,g,b);
        return;
    }
    leds[led][3] = max(r,max(g,b));
    float gain = (float)255/leds[led][3]; //might cause rounding->overflow errors might debug later idk~
    leds[led][0] = (uint8_t)(r*gain);
    leds[led][1] = (uint8_t)(g*gain);
    leds[led][2] = (uint8_t)(b*gain);
}
#endif

void leds_prep_hsv(uint8_t led, float h, float s, float v)
	hsv in       = { h, s, v };
	rgb out      = hsv2rgb(in);
	leds[led][0] = out.r * 255;
	leds[led][1] = out.g * 255;
	leds[led][2] = out.b * 255;
fleur's avatar
fleur committed
static bool is_led_on(uint8_t led) // scheduled to be on after next update
{
	if (!led_to_dim_value(led)) {
		return false;
	}
	for (int i = 0; i < 3; i++) {
		if (leds[led][i] != 0) {
			return true;
		}
	}
	return false;
}

static uint8_t led_to_group(uint8_t led)
{
	if (led == 14) {
		return 1;
	} else if (led >= 11) {
		return 2;
	}
	return 3;
}

static uint8_t
check_privilege(void) //returns number of hierarchical groups with power
{
	for (int i = 0; i < NUM_LEDS; i++) {
		if (is_led_on(i)) {
			return led_to_group(i);
		}
	}
	return 0;
}

static uint8_t power_pin_conversion(uint8_t group)
{
	if (group == 2) {
		return 1;
	}
	if (group == 1) {
		return 2;
	}
	return 0;
}

static void power_all(void)
{
	portexpander_out_clr(PIN_0 | PIN_1 | PIN_2);
fleur's avatar
fleur committed
}

void leds_update_power(void)
{
	if (!powersave) {
		return;
	}
	uint8_t new_groups =
		check_privilege(); //there must be a prettier way to do this but meh
	if (new_groups == active_groups) {
		return;
	}

	uint8_t out_val = 0;
	for (int i = new_groups; i < 3; ++i) {
		out_val |= (1 << power_pin_conversion(i));
fleur's avatar
fleur committed
	}

	portexpander_out_put(PIN_0 | PIN_1 | PIN_2, out_val);

fleur's avatar
fleur committed
	if (active_groups < new_groups) {
		for (int i = 0; i < powerup_wait_cycles; i++) {
			__NOP();
		}
	}
	active_groups = new_groups;
}

void leds_powersave(bool eco)
{
	powersave = eco;
	if (!powersave) {
		power_all();
	} else {
		leds_update_power();
	}
}

void leds_update(void)
{
	leds_start();
	for (int i = NUM_LEDS - 1; i >= 0; i--) {
fleur's avatar
fleur committed
		leds_shift(
			gamma_table[0][leds[i][0]],
			gamma_table[1][leds[i][1]],
			gamma_table[2][leds[i][2]],
			led_to_dim_value(i)
		);
	}
	leds_stop();
fleur's avatar
fleur committed
void leds_flashlight(bool power)
{
	portexpander_out_put(PIN_7, (power) ? 0 : 0xFF);
fleur's avatar
fleur committed
}

void leds_set_gamma_table(uint8_t rgb_channel, uint8_t table[256])
{
	for (int i = 0; i < 256; i++) {
		gamma_table[rgb_channel][i] = table[i];
	}
}

void leds_init(void)
{
	GPIO_Config(&rgb_clk_pin);
	GPIO_Config(&rgb_dat_pin);
	GPIO_OutSet(&rgb_clk_pin);
	GPIO_OutClr(&rgb_dat_pin);
	memset(leds, 0, sizeof(leds));
fleur's avatar
fleur committed
	powersave  = TRUE;
	top_dim    = DEFAULT_DIM_TOP;
	bottom_dim = DEFAULT_DIM_BOTTOM;
	for (int i = 0; i < NUM_LEDS; i++) {
fleur's avatar
fleur committed
		for (int j = 0; j < 3; j++) {
			leds[i][j] = 0;
		}
fleur's avatar
fleur committed
	for (int i = 0; i < 256; i++) {
		for (int j = 0; j < 3; j++) {
			int k             = (i * (1 + i) + 255) >> 8;
			gamma_table[j][i] = (k * (k + 1) + 255) >> 8;
		}
	}
	leds_update();