1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-13 03:20:00 +03:00
betaflight/src/platform/PICO/light_ws2811strip_pico.c
blckmn 81a1deddfc Renamed files in common location (to avoid issues with naming similiarity)
- reverted array structure as pio pulls data by bit, not whole words. may consider changing this to full size frame with logic for skipping byte depending on format inside PIO
2025-07-10 11:08:35 +10:00

228 lines
7.6 KiB
C

/*
* This file is part of Betaflight.
*
* Betaflight is free software. You can redistribute this software
* and/or modify this software under the terms of the GNU General
* Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* Betaflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#ifdef USE_LED_STRIP
#include "common/color.h"
#include "common/maths.h"
#include "drivers/dma.h"
#include "platform/dma.h"
#include "drivers/io.h"
#include "drivers/time.h"
#include "drivers/light_ws2811strip.h"
#include "hardware/clocks.h"
#include "hardware/pio.h"
#define WS2812_WRAP_TARGET 0
#define WS2812_WRAP 3
#define WS2812_PIO_VERSION 0
#define WS2812_T1 3
#define WS2812_T2 3
#define WS2812_T3 4
#define WS2812_BYTES_PER_LED 4 // Number of bytes per WS2812 LED (RGB)
#define WS2811_LED_STRIP_BUFFER_SIZE (WS2811_LED_STRIP_LENGTH * WS2812_BYTES_PER_LED)
static IO_t ledStripIO = IO_NONE;
static ioTag_t ledStripIoTag = IO_TAG_NONE;
static ledStripFormatRGB_e ledStripFormat = LED_GRB; // Default format is RGB
static uint8_t frameSize = 3; // Default format is RGB, so 3 is the size of the frame (RGB)
// DMA channel
static uint8_t dma_chan;
// Buffer to hold the LED color data
static uint8_t led_data[WS2811_LED_STRIP_BUFFER_SIZE];
static uint8_t ledStripPendingTransferCount = 0;
static timeUs_t ledStripCompletedTime = 0;
static const uint16_t ws2812_program_instructions[] = {
// .wrap_target
0x6321, // 0: out x, 1 side 0 [3]
0x1223, // 1: jmp !x, 3 side 1 [2]
0x1200, // 2: jmp 0 side 1 [2]
0xa242, // 3: nop side 0 [2]
// .wrap
};
static const struct pio_program ws2812_program = {
.instructions = ws2812_program_instructions,
.length = ARRAYLEN(ws2812_program_instructions),
.origin = -1,
.pio_version = WS2812_PIO_VERSION,
.used_gpio_ranges = 0x0
};
static inline pio_sm_config ws2812_program_get_default_config(uint offset)
{
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + WS2812_WRAP_TARGET, offset + WS2812_WRAP);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw)
{
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); // when should we pull from the fifo
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = WS2812_T1 + WS2812_T2 + WS2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static FAST_IRQ_HANDLER void ws2811LedStripDmaHandler(dmaChannelDescriptor_t* descriptor)
{
UNUSED(descriptor);
ws2811LedDataTransferInProgress = false;
ledStripCompletedTime = micros();
}
void ws2811LedStripInit(ioTag_t ioTag, ledStripFormatRGB_e ledFormat)
{
ledStripFormat = ledFormat; // Store the format globally
frameSize = (ledStripFormat == LED_GRBW) ? 4 : 3; // 4 bytes for GRBW, 3 bytes for RGB/GRB
memset(led_data, 0, sizeof(led_data));
ledStripIoTag = ioTag;
}
bool ws2811LedStripHardwareInit(void)
{
if (!ledStripIoTag) {
return false;
}
IO_t io = IOGetByTag(ledStripIoTag);
if (!IOIsFreeOrPreinit(io)) {
return false;
}
// This will find a free pio and state machine for our program and load it for us
PIO pio;
uint sm;
uint offset;
int pinIndex = DEFIO_TAG_PIN(ledStripIoTag);
bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&ws2812_program, &pio, &sm, &offset, pinIndex, 1, true);
if (!success) {
return false; // No free PIO or state machine available
}
ws2812_program_init(pio, sm, offset, pinIndex, WS2811_CARRIER_HZ, ledStripFormat == LED_GRBW);
// --- DMA Configuration ---
const dmaIdentifier_e dma_id = dmaGetFreeIdentifier();
if (dma_id == DMA_NONE) {
return false; // No free DMA channel available
}
dma_chan = DMA_IDENTIFIER_TO_CHANNEL(dma_id);
dma_channel_config c = dma_channel_get_default_config(dma_chan);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_read_increment(&c, true);
channel_config_set_write_increment(&c, false);
channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true));
dma_channel_configure(
dma_chan,
&c,
&pio->txf[sm], // Write address (PIO TX FIFO)
NULL, // Read address (set later)
WS2811_LED_STRIP_BUFFER_SIZE, // Number of transfers
false // Don't start immediately
);
// --- Interrupt Configuration ---
dmaSetHandler(dma_id, ws2811LedStripDmaHandler, 0, 0);
IOInit(io, OWNER_LED_STRIP, 0);
ledStripIO = io;
return true;
}
void ws2811LedStripStartTransfer(void)
{
if (!ledStripIO) {
ws2811LedDataTransferInProgress = false;
return; // Not initialized
}
// guard to ensure we don't start a transfer before a reset period has elapsed.
if (ABS(cmpTimeUs(ledStripCompletedTime, micros())) < 50) {
ws2811LedDataTransferInProgress = false;
return; // Not initialized
}
// Set the read address to the led_data buffer
dma_channel_set_read_addr(dma_chan, led_data, false);
// Start the DMA transfer
dma_channel_set_trans_count(dma_chan, ledStripPendingTransferCount, true);
ledStripPendingTransferCount = 0;
}
void ws2811LedStripUpdateTransferBuffer(rgbColor24bpp_t *color, unsigned ledIndex)
{
if (ledIndex >= WS2811_LED_STRIP_LENGTH) {
return; // Index out of bounds
}
// FIFO buffer for PIO is 32 bits wide, but we use 24 bits for RGB or 32 bits for GRBW
// the PIO pulls data from the buffer bit by bit.
const uint8_t bufferIndex = ledIndex * frameSize; // RGB or GRBW format
switch(ledStripFormat) {
case LED_RGB: // WS2811 drivers use RGB format
led_data[bufferIndex] = color->rgb.r;
led_data[bufferIndex + 1] = color->rgb.g;
led_data[bufferIndex + 2] = color->rgb.b;
break;
case LED_GRBW: // SK6812 drivers use GRBW format
// Reconstruct white channel from RGB, making the intensity a bit nonlinear, but that's fine for this use case
{
uint8_t white = MIN(MIN(color->rgb.r, color->rgb.g), color->rgb.b);
led_data[bufferIndex] = color->rgb.g; // Green
led_data[bufferIndex + 1] = color->rgb.r; // Red
led_data[bufferIndex + 2] = color->rgb.b; // Blue
led_data[bufferIndex + 3] = white; // White
}
break;
case LED_GRB: // WS2812 drivers use GRB format
default:
led_data[bufferIndex] = color->rgb.g;
led_data[bufferIndex + 1] = color->rgb.r;
led_data[bufferIndex + 2] = color->rgb.b;
break;
}
ledStripPendingTransferCount = bufferIndex + frameSize; // Update the count of bytes to transfer
}
#endif