#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; }