JS: Flipboard

This commit is contained in:
Derek Jamison
2024-04-19 18:00:58 -05:00
parent 6b9195fb42
commit 3f74c682b1
35 changed files with 1592 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
#include "../js_modules.h"
#include <infrared.h>
#include <infrared_transmit.h>
static void js_infrared_send_protocol(struct mjs* mjs) {
size_t num_args = mjs_nargs(mjs);
if(num_args != 3 || !mjs_is_string(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1)) ||
!mjs_is_number(mjs_arg(mjs, 2))) {
mjs_prepend_errorf(
mjs, MJS_BAD_ARGS_ERROR, "Invalid args (protocolName, address, command)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
mjs_val_t protocol_arg = mjs_arg(mjs, 0);
const char* protocol_name = mjs_get_string(mjs, &protocol_arg, NULL);
uint32_t address = mjs_get_int(mjs, mjs_arg(mjs, 1));
uint32_t command = mjs_get_int(mjs, mjs_arg(mjs, 2));
InfraredMessage message;
message.protocol = infrared_get_protocol_by_name(protocol_name);
if(message.protocol == InfraredProtocolUnknown) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid protocol (%s)", protocol_name);
mjs_return(mjs, MJS_UNDEFINED);
return;
}
int transmit_count = 1;
message.address = address;
message.command = command;
message.repeat = transmit_count != 1;
infrared_send(&message, transmit_count);
mjs_return(mjs, mjs_mk_boolean(mjs, true));
}
static void* js_infrared_create(struct mjs* mjs, mjs_val_t* object) {
mjs_val_t infrared_obj = mjs_mk_object(mjs);
mjs_set(mjs, infrared_obj, "sendProtocol", ~0, MJS_MK_FN(js_infrared_send_protocol));
*object = infrared_obj;
return (void*)1;
}
static void js_infrared_destroy(void* inst) {
UNUSED(inst);
}
static const JsModuleDescriptor js_infrared_desc = {
"infrared",
js_infrared_create,
js_infrared_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_infrared_desc,
};
const FlipperAppPluginDescriptor* js_infrared_ep(void) {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,213 @@
#include <core/common_defines.h>
#include "../../js_modules.h"
#include <furi_hal.h>
#include "rgbleds.h"
typedef struct {
RgbLeds* leds;
} JsRgbledsInst;
typedef struct {
const GpioPin* pin;
const char* name;
} GpioPinCtx;
static const GpioPinCtx js_gpio_pins[] = {
{.pin = &gpio_ext_pa7, .name = "PA7"}, // 2
{.pin = &gpio_ext_pa6, .name = "PA6"}, // 3
{.pin = &gpio_ext_pa4, .name = "PA4"}, // 4
{.pin = &gpio_ext_pb3, .name = "PB3"}, // 5
{.pin = &gpio_ext_pb2, .name = "PB2"}, // 6
{.pin = &gpio_ext_pc3, .name = "PC3"}, // 7
{.pin = &gpio_swclk, .name = "PA14"}, // 10
{.pin = &gpio_swdio, .name = "PA13"}, // 12
{.pin = &gpio_usart_tx, .name = "PB6"}, // 13
{.pin = &gpio_usart_rx, .name = "PB7"}, // 14
{.pin = &gpio_ext_pc1, .name = "PC1"}, // 15
{.pin = &gpio_ext_pc0, .name = "PC0"}, // 16
{.pin = &gpio_ibutton, .name = "PB14"}, // 17
};
static const GpioPin* get_gpio_pin(const char* name) {
for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) {
if(strcmp(js_gpio_pins[i].name, name) == 0) {
return js_gpio_pins[i].pin;
}
}
return NULL;
}
static void js_rgbleds_setup(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsRgbledsInst* rgbleds = mjs_get_ptr(mjs, obj_inst);
furi_assert(rgbleds);
if(mjs_nargs(mjs) != 1 || !mjs_is_object(mjs_arg(mjs, 0))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
mjs_val_t pin_obj = mjs_get(mjs, mjs_arg(mjs, 0), "pin", ~0);
mjs_val_t count_obj = mjs_get(mjs, mjs_arg(mjs, 0), "count", ~0);
mjs_val_t spec_obj = mjs_get(mjs, mjs_arg(mjs, 0), "spec", ~0);
if(!mjs_is_string(pin_obj)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "pin must be a string");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!mjs_is_number(count_obj)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "count must be a number");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!mjs_is_string(spec_obj)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "spec must be a string");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
const GpioPin* pin = get_gpio_pin(mjs_get_string(mjs, &pin_obj, NULL));
if(!pin) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "invalid pin");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(rgbleds->leds) {
rgbleds_free(rgbleds->leds);
}
uint16_t count = mjs_get_int(mjs, count_obj);
rgbleds->leds = rgbleds_alloc(count, pin);
}
static void js_rgbleds_set(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsRgbledsInst* rgbleds = mjs_get_ptr(mjs, obj_inst);
furi_assert(rgbleds);
if(!rgbleds->leds) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "LEDs not setup");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
uint32_t color;
if(mjs_nargs(mjs) == 2) {
if(mjs_is_number(mjs_arg(mjs, 0)) && mjs_is_number(mjs_arg(mjs, 1))) {
color = mjs_get_int(mjs, mjs_arg(mjs, 1));
} else if(mjs_is_number(mjs_arg(mjs, 0)) && mjs_is_object(mjs_arg(mjs, 1))) {
mjs_val_t red_obj = mjs_get(mjs, mjs_arg(mjs, 1), "red", ~0);
mjs_val_t green_obj = mjs_get(mjs, mjs_arg(mjs, 1), "green", ~0);
mjs_val_t blue_obj = mjs_get(mjs, mjs_arg(mjs, 1), "blue", ~0);
if(!mjs_is_number(red_obj) || !mjs_is_number(green_obj) || !mjs_is_number(blue_obj)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
color = (mjs_get_int(mjs, red_obj) << 16) | (mjs_get_int(mjs, green_obj) << 8) |
mjs_get_int(mjs, blue_obj);
} else {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
} else if(mjs_nargs(mjs) == 4) {
if(!mjs_is_number(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1)) ||
!mjs_is_number(mjs_arg(mjs, 2)) || !mjs_is_number(mjs_arg(mjs, 3))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
color = (mjs_get_int(mjs, mjs_arg(mjs, 1)) << 16) |
(mjs_get_int(mjs, mjs_arg(mjs, 2)) << 8) | mjs_get_int(mjs, mjs_arg(mjs, 3));
} else {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
uint16_t index = mjs_get_int(mjs, mjs_arg(mjs, 0));
uint16_t original_color = rgbleds_get(rgbleds->leds, index);
rgbleds_set(rgbleds->leds, index, color);
mjs_return(mjs, mjs_mk_number(mjs, original_color));
}
static void js_rgbleds_get(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsRgbledsInst* rgbleds = mjs_get_ptr(mjs, obj_inst);
furi_assert(rgbleds);
if(!rgbleds->leds) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "LEDs not setup");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(mjs_nargs(mjs) != 1 || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
uint16_t index = mjs_get_int(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, rgbleds_get(rgbleds->leds, index)));
}
static void js_rgbleds_update(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsRgbledsInst* rgbleds = mjs_get_ptr(mjs, obj_inst);
furi_assert(rgbleds);
if(!rgbleds->leds) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "LEDs not setup");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
rgbleds_update(rgbleds->leds);
}
static void* js_rgbleds_create(struct mjs* mjs, mjs_val_t* object) {
JsRgbledsInst* rgbleds = malloc(sizeof(JsRgbledsInst));
rgbleds->leds = NULL;
mjs_val_t rgbleds_obj = mjs_mk_object(mjs);
mjs_set(mjs, rgbleds_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, rgbleds));
mjs_set(mjs, rgbleds_obj, "setup", ~0, MJS_MK_FN(js_rgbleds_setup));
mjs_set(mjs, rgbleds_obj, "set", ~0, MJS_MK_FN(js_rgbleds_set));
mjs_set(mjs, rgbleds_obj, "get", ~0, MJS_MK_FN(js_rgbleds_get));
mjs_set(mjs, rgbleds_obj, "update", ~0, MJS_MK_FN(js_rgbleds_update));
*object = rgbleds_obj;
return rgbleds;
}
static void js_rgbleds_destroy(void* inst) {
JsRgbledsInst* rgbleds = inst;
if(rgbleds->leds) {
rgbleds_free(rgbleds->leds);
}
free(rgbleds);
}
static const JsModuleDescriptor js_rgbleds_desc = {
"rgbleds",
js_rgbleds_create,
js_rgbleds_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_rgbleds_desc,
};
const FlipperAppPluginDescriptor* js_rgbleds_ep(void) {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,299 @@
#include "led_driver_i.h"
#define TAG "led_driver"
struct LedDriver {
LL_DMA_InitTypeDef dma_gpio_update;
LL_DMA_InitTypeDef dma_led_transition_timer;
const GpioPin* gpio;
uint32_t gpio_buf[2]; // On/Off for GPIO
uint16_t timer_buffer[LED_DRIVER_BUFFER_SIZE + 2];
uint32_t write_pos;
uint32_t read_pos;
uint32_t count_leds;
uint32_t* led_data;
};
/**
* @brief Initializes the DMA for GPIO pin toggle via BSRR.
* @param led_driver The led driver to initialize.
* @param gpio The GPIO pin to toggle.
*/
static void led_driver_init_dma_gpio_update(LedDriver* led_driver, const GpioPin* gpio) {
led_driver->gpio = gpio;
// Memory to Peripheral
led_driver->dma_gpio_update.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
// Peripheral (GPIO - We populate GPIO port's BSRR register)
led_driver->dma_gpio_update.PeriphOrM2MSrcAddress = (uint32_t)&gpio->port->BSRR;
led_driver->dma_gpio_update.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
led_driver->dma_gpio_update.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
// Memory (State to set GPIO)
led_driver->dma_gpio_update.MemoryOrM2MDstAddress = (uint32_t)led_driver->gpio_buf;
led_driver->dma_gpio_update.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
led_driver->dma_gpio_update.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
// Data
led_driver->dma_gpio_update.Mode = LL_DMA_MODE_CIRCULAR;
led_driver->dma_gpio_update.NbData = 2; // We cycle between two (HIGH/LOW)values
// When to perform data exchange
led_driver->dma_gpio_update.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
led_driver->dma_gpio_update.Priority = LL_DMA_PRIORITY_VERYHIGH;
}
/**
* @brief Initializes the DMA for the LED timings via ARR.
* @param led_driver The led driver to initialize.
*/
static void led_driver_init_dma_led_transition_timer(LedDriver* led_driver) {
// Timer that triggers based on user data.
led_driver->dma_led_transition_timer.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
// Peripheral (Timer - We populate TIM2's ARR register)
led_driver->dma_led_transition_timer.PeriphOrM2MSrcAddress = (uint32_t)&TIM2->ARR;
led_driver->dma_led_transition_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
led_driver->dma_led_transition_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
// Memory (Timings)
led_driver->dma_led_transition_timer.MemoryOrM2MDstAddress =
(uint32_t)led_driver->timer_buffer;
led_driver->dma_led_transition_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
led_driver->dma_led_transition_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
// Data
led_driver->dma_led_transition_timer.Mode = LL_DMA_MODE_NORMAL;
led_driver->dma_led_transition_timer.NbData = LED_DRIVER_BUFFER_SIZE;
// When to perform data exchange
led_driver->dma_led_transition_timer.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
led_driver->dma_led_transition_timer.Priority = LL_DMA_PRIORITY_HIGH;
}
/**
* @brief Allocate and initialize LedDriver structure.
* @details This function allocate and initialize LedDriver structure.
* @return Pointer to allocated LedDriver structure.
*/
LedDriver* led_driver_alloc(int count_leds, const GpioPin* gpio) {
furi_assert(gpio);
furi_assert(count_leds && count_leds <= MAX_LED_COUNT);
LedDriver* led_driver = malloc(sizeof(LedDriver));
led_driver_init_dma_gpio_update(led_driver, gpio);
led_driver_init_dma_led_transition_timer(led_driver);
led_driver->led_data = malloc(MAX_LED_COUNT * sizeof(uint32_t));
led_driver->count_leds = count_leds;
return led_driver;
}
/**
* @brief Frees a led driver.
* @details Frees a led driver.
* @param led_driver The led driver to free.
*/
void led_driver_free(LedDriver* led_driver) {
furi_assert(led_driver);
furi_hal_gpio_init_simple(led_driver->gpio, GpioModeAnalog);
free(led_driver->led_data);
free(led_driver);
}
/**
* @brief Sets the LED at the given index to the given color.
* @note You must still call led_driver_transmit to actually update the LEDs.
* @param led_driver The led driver to use.
* @param index The index of the LED to set.
* @param rrggbb The color to set the LED to (0xrrggbb format).
* @return The previous color of the LED (0xrrggbb format).
*/
uint32_t led_driver_set_led(LedDriver* led_driver, uint32_t index, uint32_t rrggbb) {
furi_assert(led_driver);
if(index >= led_driver->count_leds) {
return 0xFFFFFFFF;
}
uint32_t previous = led_driver->led_data[index];
led_driver->led_data[index] = rrggbb;
return previous;
}
/**
* @brief Gets the LED at the given index.
* @param led_driver The led driver to use.
* @param index The index of the LED to get.
* @return The color of the LED (0xrrggbb format).
*/
uint32_t led_driver_get_led(LedDriver* led_driver, uint32_t index) {
furi_assert(led_driver);
if(index >= led_driver->count_leds) {
return 0xFFFFFFFF;
}
return led_driver->led_data[index];
}
/**
* @brief Initializes the DMA for GPIO pin toggle and led transititions.
* @param led_driver The led driver to initialize.
*/
static void led_driver_start_dma(LedDriver* led_driver) {
furi_assert(led_driver);
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &led_driver->dma_gpio_update);
LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &led_driver->dma_led_transition_timer);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
}
/**
* @brief Stops the DMA for GPIO pin toggle and led transititions.
* @param led_driver The led driver to initialize.
*/
static void led_driver_stop_dma() {
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
LL_DMA_ClearFlag_TC1(DMA1);
LL_DMA_ClearFlag_TC2(DMA1);
}
/**
* @brief Starts the timer for led transitions.
* @param led_driver The led driver to initialize.
*/
static void led_driver_start_timer() {
furi_hal_bus_enable(FuriHalBusTIM2);
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
LL_TIM_SetPrescaler(TIM2, 0);
// Updated by led_driver->dma_led_transition_timer.PeriphOrM2MSrcAddress
LL_TIM_SetAutoReload(TIM2, LED_DRIVER_TIMER_SETINEL);
LL_TIM_SetCounter(TIM2, 0);
LL_TIM_EnableCounter(TIM2);
LL_TIM_EnableUpdateEvent(TIM2);
LL_TIM_EnableDMAReq_UPDATE(TIM2);
LL_TIM_GenerateEvent_UPDATE(TIM2);
}
/**
* @brief Stops the timer for led transitions.
* @param led_driver The led driver to initialize.
*/
static void led_driver_stop_timer() {
LL_TIM_DisableCounter(TIM2);
LL_TIM_DisableUpdateEvent(TIM2);
LL_TIM_DisableDMAReq_UPDATE(TIM2);
furi_hal_bus_disable(FuriHalBusTIM2);
}
/**
* @brief Waits for the DMA to complete.
* @param led_driver The led driver to use.
*/
static void led_driver_spin_lock(LedDriver* led_driver) {
const uint32_t prev_timer = DWT->CYCCNT;
const uint32_t wait_time = LED_DRIVER_SETINEL_WAIT_MS * SystemCoreClock / 1000;
do {
/* Make sure it's started (allow 100 ticks), but then check for sentinel value. */
if(TIM2->ARR == LED_DRIVER_TIMER_SETINEL && DWT->CYCCNT - prev_timer > 100) {
break;
}
// We should have seen it above, but just in case we also have a timeout.
if((DWT->CYCCNT - prev_timer > wait_time)) {
FURI_LOG_E(
TAG,
"0x%04x not found (ARR 0x%08lx, read %lu)",
LED_DRIVER_TIMER_SETINEL,
TIM2->ARR,
led_driver->read_pos);
led_driver->read_pos = led_driver->write_pos - 1;
break;
}
} while(true);
}
static void led_driver_add_period_length(LedDriver* led_driver, uint32_t length) {
led_driver->timer_buffer[led_driver->write_pos++] = length;
led_driver->timer_buffer[led_driver->write_pos] = LED_DRIVER_TIMER_SETINEL;
}
static void led_driver_add_period(LedDriver* led_driver, uint16_t duration_ns) {
furi_assert(led_driver);
uint32_t reload_value = duration_ns / LED_DRIVER_TIMER_NANOSECOND;
if(reload_value > 255) {
FURI_LOG_E(TAG, "reload_value: %ld", reload_value);
}
furi_check(reload_value > 0);
furi_check(reload_value < 256 * 256);
led_driver_add_period_length(led_driver, reload_value - 1);
}
static void led_driver_add_color(LedDriver* led_driver, uint32_t rrggbb) {
UNUSED(rrggbb);
uint32_t ggrrbb = (rrggbb & 0xFF) | ((rrggbb & 0xFF00) << 8) | ((rrggbb & 0xFF0000) >> 8);
for(int i = 23; i >= 0; i--) {
if(ggrrbb & (1 << i)) {
led_driver_add_period(led_driver, LED_DRIVER_T0L);
led_driver_add_period(led_driver, LED_DRIVER_T1L);
} else {
led_driver_add_period(led_driver, LED_DRIVER_T0H);
led_driver_add_period(led_driver, LED_DRIVER_T1H);
}
}
}
/**
* @brief Send the LED data to the LEDs.
* @param led_driver The led driver to use.
*/
void led_driver_transmit(LedDriver* led_driver) {
furi_assert(led_driver);
furi_assert(!led_driver->read_pos);
furi_assert(!led_driver->write_pos);
furi_hal_gpio_init(led_driver->gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(led_driver->gpio, false);
const uint32_t bit_set = led_driver->gpio->pin << GPIO_BSRR_BS0_Pos;
const uint32_t bit_reset = led_driver->gpio->pin << GPIO_BSRR_BR0_Pos;
// Always start with HIGH
led_driver->gpio_buf[0] = bit_set;
led_driver->gpio_buf[1] = bit_reset;
for(size_t i = 0; i < LED_DRIVER_BUFFER_SIZE; i++) {
led_driver->timer_buffer[i] = LED_DRIVER_TIMER_SETINEL;
}
for(size_t i = 0; i < led_driver->count_leds; i++) {
led_driver_add_color(led_driver, led_driver->led_data[i]);
}
led_driver_add_period(led_driver, LED_DRIVER_TDONE);
led_driver->dma_led_transition_timer.NbData = led_driver->write_pos + 1;
FURI_CRITICAL_ENTER();
led_driver_start_dma(led_driver);
led_driver_start_timer();
led_driver_spin_lock(led_driver);
led_driver_stop_timer();
led_driver_stop_dma();
FURI_CRITICAL_EXIT();
memset(led_driver->timer_buffer, LED_DRIVER_TIMER_SETINEL, LED_DRIVER_BUFFER_SIZE);
led_driver->read_pos = 0;
led_driver->write_pos = 0;
}

View File

@@ -0,0 +1,42 @@
#include <furi.h>
#include <furi_hal.h>
typedef struct LedDriver LedDriver;
/**
* @brief Allocate and initialize LedDriver structure.
* @details This function allocate and initialize LedDriver structure.
* @return Pointer to allocated LedDriver structure.
*/
LedDriver* led_driver_alloc(int count_leds, const GpioPin* gpio);
/**
* @brief Frees a led driver.
* @details Frees a led driver.
* @param led_driver The led driver to free.
*/
void led_driver_free(LedDriver* led_driver);
/**
* @brief Sets the LED at the given index to the given color.
* @note You must still call led_driver_transmit to actually update the LEDs.
* @param led_driver The led driver to use.
* @param index The index of the LED to set.
* @param rrggbb The color to set the LED to.
* @return The previous color of the LED.
*/
uint32_t led_driver_set_led(LedDriver* led_driver, uint32_t index, uint32_t rrggbb);
/**
* @brief Gets the LED at the given index.
* @param led_driver The led driver to use.
* @param index The index of the LED to get.
* @return The color of the LED (0xrrggbb format).
*/
uint32_t led_driver_get_led(LedDriver* led_driver, uint32_t index);
/**
* @brief Send the LED data to the LEDs.
* @param led_driver The led driver to use.
*/
void led_driver_transmit(LedDriver* led_driver);

View File

@@ -0,0 +1,26 @@
#pragma once
#include <stm32wbxx_ll_dma.h>
#include "led_driver.h"
#define MAX_LED_COUNT 16
// We store the HIGH/LOW durations (2 values) for each color bit (24 bits per LED)
#define LED_DRIVER_BUFFER_SIZE (MAX_LED_COUNT * 2 * 24)
// We use a setinel value to figure out when the timer is complete.
#define LED_DRIVER_TIMER_SETINEL 0xFFFFU
/** 64 transitions per us @ 64MHz. Our timing is in NANO_SECONDS */
#define LED_DRIVER_TIMER_NANOSECOND (1000U / (SystemCoreClock / 1000000U))
// Timings for WS2812B
#define LED_DRIVER_T0H 400U
#define LED_DRIVER_T1H 800U
#define LED_DRIVER_T0L 850U
#define LED_DRIVER_T1L 450U
#define LED_DRIVER_TRESETL 55 * 1000U
#define LED_DRIVER_TDONE 2000U
// Max wait for the DMA to complete. NOTE: 4000 leds*(850ns+450ns)*24 = 124.8ms + 50ms blanking = 174.8ms
#define LED_DRIVER_SETINEL_WAIT_MS 200

View File

@@ -0,0 +1,131 @@
#include <furi_hal.h>
#include "rgbleds.h"
#include "led_driver.h"
#define LED_COUNT 16
struct RgbLeds {
uint16_t num_leds;
uint32_t* color;
uint16_t brightness;
LedDriver* led_driver;
};
/**
* @brief Allocates a RgbLeds struct.
* @details This method allocates a RgbLeds struct. This is used to
* control the addressable LEDs.
* @param num_leds The number of LEDs to allocate.
* @param leds_pin The GPIO pin to use for the LEDs. (&gpio_ext_pc3)
* @return The allocated RgbLeds struct.
*/
RgbLeds* rgbleds_alloc(uint16_t num_leds, const GpioPin* const leds_pin) {
RgbLeds* leds = malloc(sizeof(RgbLeds));
leds->num_leds = num_leds;
leds->color = malloc(sizeof(uint32_t) * leds->num_leds);
leds->brightness = 255;
leds->led_driver = led_driver_alloc(leds->num_leds, leds_pin);
rgbleds_reset(leds);
return leds;
}
/**
* @brief Frees a RgbLeds struct.
* @param leds The RgbLeds struct to free.
*/
void rgbleds_free(RgbLeds* leds) {
if(leds->led_driver) {
led_driver_free(leds->led_driver);
}
free(leds->color);
free(leds);
}
/**
* @brief Resets the LEDs to their default color pattern (off).
* @details This method resets the LEDs data to their default color pattern (off).
* You must still call rgbleds_update to update the LEDs.
* @param leds The RgbLeds struct to reset.
*/
void rgbleds_reset(RgbLeds* leds) {
for(int i = 0; i < leds->num_leds; i++) {
leds->color[i] = 0x000000;
}
}
/**
* @brief Sets the color of the LEDs.
* @details This method sets the color of the LEDs.
* @param leds The RgbLeds struct to set the color of.
* @param led The LED index to set the color of.
* @param color The color to set the LED to (Hex value: RRGGBB).
* @return True if the LED was set, false if the LED was out of range.
*/
bool rgbleds_set(RgbLeds* leds, uint16_t led, uint32_t color) {
if(led > leds->num_leds) {
return false;
}
leds->color[led] = color;
return true;
}
/**
* @brief Gets the color of the LEDs.
* @details This method gets the color of the LEDs.
* @param leds The RgbLeds struct to get the color of.
* @param led The LED index to get the color of.
* @return The color of the LED (Hex value: RRGGBB).
*/
uint32_t rgbleds_get(RgbLeds* leds, uint16_t led) {
if(led > leds->num_leds) {
return 0;
}
return leds->color[led];
}
/**
* @brief Sets the brightness of the LEDs.
* @details This method sets the brightness of the LEDs.
* @param leds The RgbLeds struct to set the brightness of.
* @param brightness The brightness to set the LEDs to (0-255).
*/
void rgbleds_set_brightness(RgbLeds* leds, uint8_t brightness) {
leds->brightness = brightness;
}
/**
* @brief Adjusts the brightness of a color.
* @details This method adjusts the brightness of a color.
* @param color The color to adjust.
* @param brightness The brightness to adjust the color to (0-255).
* @return The adjusted color.
*/
static uint32_t adjust_color_brightness(uint32_t color, uint8_t brightness) {
uint32_t red = (color & 0xFF0000) >> 16;
uint32_t green = (color & 0x00FF00) >> 8;
uint32_t blue = (color & 0x0000FF);
red = (red * brightness) / 255;
green = (green * brightness) / 255;
blue = (blue * brightness) / 255;
return (red << 16) | (green << 8) | blue;
}
/**
* @brief Updates the LEDs.
* @details This method changes the LEDs to the colors set by rgbleds_set.
* @param leds The RgbLeds struct to update.
*/
void rgbleds_update(RgbLeds* leds) {
for(int i = 0; i < leds->num_leds; i++) {
uint32_t color = adjust_color_brightness(leds->color[i], leds->brightness);
led_driver_set_led(leds->led_driver, i, color);
}
led_driver_transmit(leds->led_driver);
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <furi_hal.h>
typedef struct RgbLeds RgbLeds;
/**
* @brief Allocates a RgbLeds struct.
* @details This method allocates a RgbLeds struct. This is used to
* control the addressable LEDs.
* @param num_leds The number of LEDs to allocate.
* @param leds_pin The GPIO pin to use for the LEDs. (&gpio_ext_pc3)
* @return The allocated RgbLeds struct.
*/
RgbLeds* rgbleds_alloc(uint16_t num_leds, const GpioPin* const leds_pin);
/**
* @brief Frees a RgbLeds struct.
* @param leds The RgbLeds struct to free.
*/
void rgbleds_free(RgbLeds* leds);
/**
* @brief Resets the LEDs to their default color pattern (off).
* @details This method resets the LEDs data to their default color pattern (off).
* You must still call rgbleds_update to update the LEDs.
* @param leds The RgbLeds struct to reset.
*/
void rgbleds_reset(RgbLeds* leds);
/**
* @brief Sets the color of the LEDs.
* @details This method sets the color of the LEDs.
* @param leds The RgbLeds struct to set the color of.
* @param led The LED index to set the color of.
* @param color The color to set the LED to (Hex value: RRGGBB).
* @return True if the LED was set, false if the LED was out of range.
*/
bool rgbleds_set(RgbLeds* leds, uint16_t led, uint32_t color);
/**
* @brief Gets the color of the LEDs.
* @details This method gets the color of the LEDs.
* @param leds The RgbLeds struct to get the color of.
* @param led The LED index to get the color of.
* @return The color of the LED (Hex value: RRGGBB).
*/
uint32_t rgbleds_get(RgbLeds* leds, uint16_t led);
/**
* @brief Sets the brightness of the LEDs.
* @details This method sets the brightness of the LEDs.
* @param leds The RgbLeds struct to set the brightness of.
* @param brightness The brightness to set the LEDs to (0-255).
*/
void rgbleds_set_brightness(RgbLeds* leds, uint8_t brightness);
/**
* @brief Updates the LEDs.
* @details This method changes the LEDs to the colors set by rgbleds_set.
* @param leds The RgbLeds struct to update.
*/
void rgbleds_update(RgbLeds* leds);

View File

@@ -0,0 +1,210 @@
#include "../js_modules.h"
#include <furi_hal.h>
typedef struct {
bool acquired;
} JsSpeakerInst;
static void js_speaker_acquire(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpeakerInst* speaker = mjs_get_ptr(mjs, obj_inst);
furi_assert(speaker);
if(mjs_nargs(mjs) != 1) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid args (timeoutMs)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!mjs_is_number(mjs_arg(mjs, 0))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid numeric arg (timeoutMs)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
uint32_t timeout = mjs_get_int(mjs, mjs_arg(mjs, 0));
if(!speaker->acquired) {
speaker->acquired = furi_hal_speaker_acquire(timeout);
}
mjs_return(mjs, mjs_mk_boolean(mjs, speaker->acquired));
}
static void js_speaker_release(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpeakerInst* speaker = mjs_get_ptr(mjs, obj_inst);
furi_assert(speaker);
if(mjs_nargs(mjs) != 0) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "No arguments expected");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(speaker->acquired) {
furi_hal_speaker_stop();
furi_hal_speaker_release();
speaker->acquired = false;
}
mjs_return(mjs, mjs_mk_boolean(mjs, true));
}
static void js_speaker_start(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpeakerInst* speaker = mjs_get_ptr(mjs, obj_inst);
furi_assert(speaker);
size_t num_args = mjs_nargs(mjs);
float frequency;
float volume;
if(num_args == 1) {
if(!mjs_is_number(mjs_arg(mjs, 0))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid numeric arg (freq [, volume])");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
volume = 1.0;
} else if(num_args == 2) {
if(!mjs_is_number(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1))) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid numeric arg (freq [, volume])");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
volume = mjs_get_double(mjs, mjs_arg(mjs, 1));
if(volume < 0 || volume > 1) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid volume (0 <= volume <= 1)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
} else {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid args (freq [, volume])");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!speaker->acquired) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Speaker must be acquired first");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
frequency = mjs_get_double(mjs, mjs_arg(mjs, 0));
furi_hal_speaker_start(frequency, volume);
mjs_return(mjs, mjs_mk_boolean(mjs, true));
}
static void js_speaker_stop(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpeakerInst* speaker = mjs_get_ptr(mjs, obj_inst);
furi_assert(speaker);
if(mjs_nargs(mjs) != 0) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "No arguments expected");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!speaker->acquired) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Speaker must be acquired first");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
furi_hal_speaker_stop();
mjs_return(mjs, mjs_mk_boolean(mjs, true));
}
static void js_speaker_play(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSpeakerInst* speaker = mjs_get_ptr(mjs, obj_inst);
furi_assert(speaker);
size_t num_args = mjs_nargs(mjs);
float frequency;
float volume;
uint32_t duration;
uint32_t timeout = 1000;
bool acquired_in_play = false;
if(num_args != 3) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid args (freq, volume, duration)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
if(!mjs_is_number(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1)) ||
!mjs_is_number(mjs_arg(mjs, 2))) {
mjs_prepend_errorf(
mjs, MJS_BAD_ARGS_ERROR, "Invalid numeric arg (freq, volume, duration)");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
frequency = mjs_get_double(mjs, mjs_arg(mjs, 0));
volume = mjs_get_double(mjs, mjs_arg(mjs, 1));
duration = mjs_get_int(mjs, mjs_arg(mjs, 2));
if(!speaker->acquired) {
acquired_in_play = true;
speaker->acquired = furi_hal_speaker_acquire(timeout);
}
if(speaker->acquired) {
furi_hal_speaker_start(frequency, volume);
furi_delay_ms(duration);
furi_hal_speaker_stop();
if(acquired_in_play) {
furi_hal_speaker_release();
speaker->acquired = false;
}
mjs_return(mjs, mjs_mk_boolean(mjs, true));
} else {
mjs_return(mjs, mjs_mk_boolean(mjs, false));
}
}
static void* js_speaker_create(struct mjs* mjs, mjs_val_t* object) {
JsSpeakerInst* speaker = malloc(sizeof(JsSpeakerInst));
speaker->acquired = false;
mjs_val_t speaker_obj = mjs_mk_object(mjs);
mjs_set(mjs, speaker_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, speaker));
mjs_set(mjs, speaker_obj, "acquire", ~0, MJS_MK_FN(js_speaker_acquire));
mjs_set(mjs, speaker_obj, "release", ~0, MJS_MK_FN(js_speaker_release));
mjs_set(mjs, speaker_obj, "start", ~0, MJS_MK_FN(js_speaker_start));
mjs_set(mjs, speaker_obj, "stop", ~0, MJS_MK_FN(js_speaker_stop));
mjs_set(mjs, speaker_obj, "play", ~0, MJS_MK_FN(js_speaker_play));
*object = speaker_obj;
return speaker;
}
static void js_speaker_destroy(void* inst) {
JsSpeakerInst* speaker = (JsSpeakerInst*)inst;
if(speaker->acquired) {
furi_hal_speaker_stop();
furi_hal_speaker_release();
speaker->acquired = false;
}
free(speaker);
}
static const JsModuleDescriptor js_speaker_desc = {
"speaker",
js_speaker_create,
js_speaker_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_speaker_desc,
};
const FlipperAppPluginDescriptor* js_speaker_ep(void) {
return &plugin_descriptor;
}