Commit e87cdd45 authored by fleur's avatar fleur Committed by Rahix
Browse files

feat: Add proper LED module

The new LED modules allow a much broader range of uses of the RGB LEDs.
It also includes smart powersaving and options to set all LEDs at once.
parent b3979a7d
......@@ -87,7 +87,7 @@ html_context = {
# }}}
# -- Options for Auto-Doc ---------------------------------------------------- {{{
autodoc_mock_imports = ["sys_display", "ucollections", "urandom", "utime"]
autodoc_mock_imports = ["sys_display", "sys_leds", "ucollections", "urandom", "utime"]
autodoc_member_order = "bysource"
# }}}
......
``leds`` - LEDs
===============
The ``leds`` module provides functions to interact with card10's RGB LEDs.
This is the 11 LEDs above the display and 4 LEDs on the underside of the
top-board, in the four corners.
.. py:function:: leds.set(led, color)
Set one of the card10's RGB LEDs to a certain color.
**Example**:
.. code-block:: python
import leds, color
# Set all of the top LEDs to red
for i in range(11):
leds.set(i, color.RED)
:param led: Which led to set. 0-10 are the leds on the top
and 11-14 are the 4 "ambient" leds.
:param color: What color to set the led to. Should be a
:py:class:`color.Color` but any list/tuple with 3 elements
will work just as well.
.. py:data:: leds.BOTTOM_RIGHT
Index of the LED in the bottom right of card10.
.. py:data:: leds.BOTTOM_LEFT
Index of the LED in the bottom left of card10.
.. py:data:: leds.TOP_RIGHT
Index of the LED in the top right of card10.
.. py:data:: leds.TOP_LEFT
Index of the LED in the top left of card10.
.. automodule:: leds
:members:
......@@ -5,10 +5,12 @@
#include <errno.h>
#ifndef __SPHINX_DOC
/* stddef.h is not recognized by hawkmoth for some odd reason */
/* Some headers are not recognized by hawkmoth for some odd reason */
#include <stddef.h>
#include <stdbool.h>
#else
typedef unsigned int size_t;
typedef _Bool bool;
#endif /* __SPHINX_DOC */
/*
......@@ -63,6 +65,18 @@ typedef unsigned int size_t;
#define API_RTC_SCHEDULE_ALARM 0x51
#define API_LEDS_SET 0x60
#define API_LEDS_SET_HSV 0x61
#define API_LEDS_PREP 0x62
#define API_LEDS_PREP_HSV 0x63
#define API_LEDS_UPDATE 0x64
#define API_LEDS_SET_POWERSAVE 0x65
#define API_LEDS_SET_ROCKET 0x66
#define API_LEDS_SET_FLASHLIGHT 0x67
#define API_LEDS_DIM_TOP 0x68
#define API_LEDS_DIM_BOTTOM 0x69
#define API_LEDS_SET_ALL 0x6a
#define API_LEDS_SET_ALL_HSV 0x6b
#define API_LEDS_SET_GAMMA_TABLE 0x6c
#define API_VIBRA_SET 0x70
#define API_VIBRA_VIBRATE 0x71
......@@ -211,22 +225,169 @@ API_ISR(EPIC_INT_CTRL_C, epic_isr_ctrl_c);
*/
/**
* Set one of card10's RGB LEDs to a certain color.
* Set one of card10's RGB LEDs to a certain color in RGB format.
*
* .. warning::
* This function is rather slow when setting multiple LEDs, use
* :c:func:`leds_set_all` or :c:func:`leds_prep` + :c:func:`leds_update`
* instead.
*
* This API function is not yet stable and is this not part of the API
* freeze. Any binary using :c:func:`epic_leds_set` might stop working at
* any time. Once this warning is removed, the function can be considered
* stable like the rest of the API.
*
* :param led: Which led to set. 0-10 are the leds on the top and 11-14 are the 4 "ambient" leds.
* :param r: Red component of the color.
* :param g: Green component of the color.
* :param b: Blue component of the color.
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t r: Red component of the color.
* :param uint8_t g: Green component of the color.
* :param uint8_t b: Blue component of the color.
*/
API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b));
/**
* Set one of card10's RGB LEDs to a certain color in HSV format.
*
* This function is rather slow when setting multiple LEDs, use
* :c:func:`leds_set_all_hsv` or :c:func:`leds_prep_hsv` + :c:func:`leds_update`
* instead.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient" LEDs.
* :param float h: Hue component of the color. (0 <= h < 360)
* :param float s: Saturation component of the color. (0 <= s <= 1)
* :param float v: Value/Brightness component of the color. (0 <= v <= 0)
*/
API(API_LEDS_SET_HSV, void epic_leds_set_hsv(int led, float h, float s, float v));
/**
* Set multiple of card10's RGB LEDs to a certain color in RGB format.
*
* The first ``len`` leds are set, the remaining ones are not modified.
*
* :param uint8_t[len][r,g,b] pattern: Array with RGB Values with 0 <= len <=
* 15. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient" LEDs.
* :param uint8_t len: Length of 1st dimension of ``pattern``, see above.
*/
API(API_LEDS_SET_ALL, void epic_leds_set_all(uint8_t *pattern, uint8_t len));
/**
* Set multiple of card10's RGB LEDs to a certain color in HSV format.
*
* The first ``len`` led are set, the remaining ones are not modified.
*
* :param uint8_t[len][h,s,v] pattern: Array of format with HSV Values with 0
* <= len <= 15. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient"
* LEDs. (0 <= h < 360, 0 <= s <= 1, 0 <= v <= 1)
* :param uint8_t len: Length of 1st dimension of ``pattern``, see above.
*/
API(API_LEDS_SET_ALL_HSV, void epic_leds_set_all_hsv(float *pattern, uint8_t len));
/**
* Prepare one of card10's RGB LEDs to be set to a certain color in RGB format.
*
* Use :c:func:`leds_update` to apply changes.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t r: Red component of the color.
* :param uint8_t g: Green component of the color.
* :param uint8_t b: Blue component of the color.
*/
API(API_LEDS_PREP, void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b));
/**
* Prepare one of card10's RGB LEDs to be set to a certain color in HSV format.
*
* Use :c:func:`leds_update` to apply changes.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t h: Hue component of the color. (float, 0 <= h < 360)
* :param uint8_t s: Saturation component of the color. (float, 0 <= s <= 1)
* :param uint8_t v: Value/Brightness component of the color. (float, 0 <= v <= 0)
*/
API(API_LEDS_PREP_HSV, void epic_leds_prep_hsv(int led, float h, float s, float v));
/**
* Set global brightness for top RGB LEDs.
*
* Aside from PWM, the RGB LEDs' overall brightness can be controlled with a
* current limiter independently to achieve a higher resolution at low
* brightness which can be set with this function.
*
* :param uint8_t value: Global brightness of top LEDs. (1 <= value <= 8, default = 1)
*/
API(API_LEDS_DIM_BOTTOM, void epic_leds_dim_bottom(uint8_t value));
/**
* Set global brightness for bottom RGB LEDs.
*
* Aside from PWM, the RGB LEDs' overall brightness can be controlled with a
* current limiter independently to achieve a higher resolution at low
* brightness which can be set with this function.
*
* :param uint8_t value: Global brightness of bottom LEDs. (1 <= value <= 8, default = 8)
*/
API(API_LEDS_DIM_TOP, void epic_leds_dim_top(uint8_t value));
/**
* Enables or disables powersave mode.
*
* Even when set to zero, the RGB LEDs still individually consume ~1mA.
* Powersave intelligently switches the supply power in groups. This introduces
* delays in the magnitude of ~10µs, so it can be disabled for high speed
* applications such as POV.
*
* :param bool eco: Activates powersave if true, disables it when false. (default = True)
*/
API(API_LEDS_SET_POWERSAVE, void epic_leds_set_powersave(bool eco));
/**
* Updates the RGB LEDs with changes that have been set with :c:func:`leds_prep`
* or :c:func:`leds_prep_hsv`.
*
* The LEDs can be only updated in bulk, so using this approach instead of
* :c:func:`leds_set` or :c:func:`leds_set_hsv` significantly reduces the load
* on the corresponding hardware bus.
*/
API(API_LEDS_UPDATE, void epic_leds_update(void));
/**
* Set the brightness of one of the rocket LEDs.
*
* :param int led: Which LED to set.
*
* +-------+--------+----------+
* | ID | Color | Location |
* +=======+========+==========+
* | ``0`` | Blue | Left |
* +-------+--------+----------+
* | ``1`` | Yellow | Top |
* +-------+--------+----------+
* | ``2`` | Green | Right |
* +-------+--------+----------+
* :param uint8_t value: Brightness of LED (only two brightness levels are
* supported right now).
*/
API(API_LEDS_SET_ROCKET, void epic_leds_set_rocket(int led, uint8_t value));
/**
* Turn on the bright side LED which can serve as a flashlight if worn on the left wrist or as a rad tattoo illuminator if worn on the right wrist.
*
*:param bool power: Side LED on if true.
*/
API(API_LEDS_SET_FLASHLIGHT, void epic_set_flashlight(bool power));
/**
* Set gamma lookup table for individual rgb channels.
*
* Since the RGB LEDs' subcolor LEDs have different peak brightness and the
* linear scaling introduced by PWM is not desireable for color accurate work,
* custom lookup tables for each individual color channel can be loaded into the
* Epicardium's memory with this function.
*
* :param uint8_t rgb_channel: Color whose gamma table is to be updated, 0->Red, 1->Green, 2->Blue.
* :param uint8_t[256] gamma_table: Gamma lookup table. (default = 4th order power function rounded up)
*/
API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table(
uint8_t rgb_channel,
uint8_t *gamma_table
));
/**
* Sensor Data Streams
* ===================
......
#include "leds.h"
#include "pmic.h"
//#include "FreeRTOS.h"
//#include "task.h"
//TODO: create smth like vTaskDelay(pdMS_TO_TICKS(//put ms here)) for us, remove blocking delay from /lib/leds.c to avoid process blocking
void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)
{
leds_set(led, r, g, b);
leds_prep(led, r, g, b);
leds_update_power();
leds_update();
}
void epic_leds_set_hsv(int led, float h, float s, float v)
{
leds_prep_hsv(led, h, s, v);
leds_update_power();
leds_update();
}
void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b)
{
leds_prep(led, r, g, b);
}
void epic_leds_prep_hsv(int led, float h, float s, float v)
{
leds_prep_hsv(led, h, s, v);
}
void epic_leds_set_all(uint8_t *pattern_ptr, uint8_t len)
{
uint8_t(*pattern)[3] = (uint8_t(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) {
leds_prep(i, pattern[i][0], pattern[i][1], pattern[i][2]);
}
leds_update_power();
leds_update();
}
void epic_leds_set_all_hsv(float *pattern_ptr, uint8_t len)
{
float(*pattern)[3] = (float(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) {
leds_prep_hsv(i, pattern[i][0], pattern[i][1], pattern[i][2]);
}
leds_update_power();
leds_update();
}
\ No newline at end of file
}
void epic_leds_dim_top(uint8_t value)
{
leds_set_dim_top(value);
leds_update();
}
void epic_leds_dim_bottom(uint8_t value)
{
leds_set_dim_bottom(value);
leds_update();
}
void epic_leds_set_rocket(int led, uint8_t value)
{
pmic_set_led(led, value);
}
void epic_set_flashlight(bool power)
{
leds_flashlight(power);
}
void epic_leds_update(void)
{
leds_update_power();
leds_update();
}
void epic_leds_set_powersave(bool eco)
{
leds_powersave(eco);
}
void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t gamma_table[256])
{
leds_set_gamma_table(rgb_channel, gamma_table);
}
......@@ -24,31 +24,12 @@ int main(void)
Paint_DrawImage(Heart, 0, 0, 160, 80);
LCD_Update();
for (int i = 0; i < 11; i++) {
leds_set_dim(i, 1);
}
int h = 0;
// Release core1
core1_start((void *)0x10080000);
int h = 0;
while (1) {
#define NUM 15
for (int i = 0; i < NUM; i++) {
if (i < 12) {
leds_set_hsv(
i,
(h + 360 / NUM * i) % 360,
1.,
1. / 8
);
} else {
leds_set_hsv(
i, (h + 360 / NUM * i) % 360, 1., 1.
);
}
}
leds_update();
TMR_Delay(MXC_TMR0, MSEC(10), 0);
......
......@@ -33,7 +33,7 @@ int main(void)
LCD_Update();
for (int i = 0; i < 11; i++) {
leds_set_dim(i, 1);
// leds_set_dim(i, 1);
}
int __attribute__((unused)) h = 0;
......
#include "gpio.h"
#include "portexpander.h"
#include "max32665.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#define NUM_LEDS 15
#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
......@@ -12,7 +16,13 @@ static const gpio_cfg_t rgb_dat_pin = {
static const gpio_cfg_t rgb_clk_pin = {
PORT_1, PIN_15, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static uint8_t leds[NUM_LEDS][4];
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 *****/
// *****************************************************************************
......@@ -174,19 +184,45 @@ static void leds_stop(void)
shift(0xFF);
}
void leds_set_dim(uint8_t led, uint8_t dim)
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)
{
leds[led][3] = dim;
bottom_dim = (value > MAX_DIM) ? MAX_DIM : value;
}
void leds_set(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
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_set_hsv(uint8_t led, float h, float s, float v)
#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);
......@@ -195,15 +231,121 @@ void leds_set_hsv(uint8_t led, float h, float s, float v)
leds[led][2] = out.b * 255;
}
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)
{
for (int i = 0; i < 3; i++) {
portexpander_prep(i, 0);
}
portexpander_update();
}
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;
}
for (int i = 0; i < 3; i++) {
if (i < new_groups) {
portexpander_prep(power_pin_conversion(i), 0);
} else {
portexpander_prep(power_pin_conversion(i), 1);
}
}
portexpander_update();
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--) {
leds_shift(leds[i][0], leds[i][1], leds[i][2], leds[i][3]);
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();
}
void leds_flashlight(bool power)
{
portexpander_set(7, (power) ? 0 : 1);
}
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);
......@@ -214,17 +356,19 @@ void leds_init(void)
memset(leds, 0, sizeof(leds));
powersave = TRUE;
top_dim = DEFAULT_DIM_TOP;
bottom_dim = DEFAULT_DIM_BOTTOM;
for (int i = 0; i < NUM_LEDS; i++) {
leds[i][3] = 8;
for (int j = 0; j < 3; j++) {
leds[i][j] = 0;
}
}
if (portexpander_detected()) {
// Turn on LEDs
// TODO: only turn on LEDs if value != 0,0,0 && dim > 0
portexpander_set(0, 0);
portexpander_set(1, 0);
portexpander_set(2, 0);
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 3; j++) {
int k = (i * (1 + i) + 255) >> 8;