From 0c6665200b947d9634c3b6c532d05eab02143fd4 Mon Sep 17 00:00:00 2001 From: "@CIOPortfolio" <1richard.barton@gmail.com> Date: Wed, 23 Oct 2024 22:01:06 +0100 Subject: [PATCH] C++ style example based upon existing c example --- pio/pio_ws2812_cpp/CMakeLists.txt | 55 ++++++++++ pio/pio_ws2812_cpp/CRGB.hpp | 79 ++++++++++++++ pio/pio_ws2812_cpp/README.md | 37 +++++++ pio/pio_ws2812_cpp/pled.hpp | 150 +++++++++++++++++++++++++++ pio/pio_ws2812_cpp/pled_demo.cpp | 165 ++++++++++++++++++++++++++++++ pio/pio_ws2812_cpp/ws2812.pio | 42 ++++++++ 6 files changed, 528 insertions(+) create mode 100644 pio/pio_ws2812_cpp/CMakeLists.txt create mode 100644 pio/pio_ws2812_cpp/CRGB.hpp create mode 100644 pio/pio_ws2812_cpp/README.md create mode 100644 pio/pio_ws2812_cpp/pled.hpp create mode 100644 pio/pio_ws2812_cpp/pled_demo.cpp create mode 100644 pio/pio_ws2812_cpp/ws2812.pio diff --git a/pio/pio_ws2812_cpp/CMakeLists.txt b/pio/pio_ws2812_cpp/CMakeLists.txt new file mode 100644 index 000000000..412277c6e --- /dev/null +++ b/pio/pio_ws2812_cpp/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NEVER EDIT THE NEXT LINES for Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.0.0) +set(toolchainVersion 13_2_Rel1) +set(picotoolVersion 2.0.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(pio_ws2812 C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(pio_ws2812) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/generated) + +# generate the header file into the source tree as it is included in the RP2040 datasheet +pico_generate_pio_header(pio_ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated) + +target_sources(pio_ws2812 PRIVATE pled_demo.cpp) + + +target_compile_definitions(pio_ws2812 PRIVATE + PIN_DBG1=3) + +target_link_libraries(pio_ws2812 PRIVATE pico_stdlib hardware_pio hardware_dma) +# enable usb output, disable uart output +pico_enable_stdio_usb(pio_ws2812 1) +pico_enable_stdio_uart(pio_ws2812 0) +pico_add_extra_outputs(pio_ws2812) diff --git a/pio/pio_ws2812_cpp/CRGB.hpp b/pio/pio_ws2812_cpp/CRGB.hpp new file mode 100644 index 000000000..b0999109a --- /dev/null +++ b/pio/pio_ws2812_cpp/CRGB.hpp @@ -0,0 +1,79 @@ +#ifndef CRGB_H +#define CRGB_H + +#include + +#include + +struct CRGB { + uint8_t g, r, b, n = 0; + + CRGB() : r(0), g(0), b(0) {}; + CRGB(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b) {}; + + CRGB(unsigned long rgb) { + b = rgb & 0xffu; + g = (rgb >> 8u) & 0xffu; + r = (rgb >> 16u) & 0xffu; + } + + uint8_t &operator[](int i) { + switch (i) { + case 0: + return r; + break; + case 1: + return g; + break; + case 2: + return b; + default: + return n; + break; + }; + } + + CRGB operator*(double f) { + CRGB res(static_cast(ceil(r * f)), static_cast(ceil(g * f)), static_cast(ceil(b * f))); + return res; + } + + CRGB operator*(uint8_t f) { + CRGB res(((uint16_t)r * (f + 1)) >> 8, ((uint16_t)g * (f + 1)) >> 8, ((uint16_t)b * (f + 1)) >> 8); + return res; + } + + CRGB operator/(double f) { + return *this * (1.0 / f); + } + + CRGB operator+(CRGB rgb2) { + CRGB res(r + rgb2.r, g + rgb2.g, b + rgb2.b); + return res; + } + + static CRGB blend(CRGB rgb1, CRGB rgb2, double f) { + return rgb1 * f + rgb2 * (1.0 - f); + } + + CRGB operator=(uint32_t rgb) { + b = rgb & 0xffu; + g = (rgb >> 8u) & 0xffu; + r = (rgb >> 16u) & 0xffu; + return *this; + } + + uint32_t toRGB() { + return ((uint32_t)(r) << 16) | + ((uint32_t)(g) << 8) | + (uint32_t)(b); + } + + uint32_t toGRB() { + return ((uint32_t)(g) << 16) | + ((uint32_t)(r) << 8) | + (uint32_t)(b); + } +}; + +#endif \ No newline at end of file diff --git a/pio/pio_ws2812_cpp/README.md b/pio/pio_ws2812_cpp/README.md new file mode 100644 index 000000000..1bc2fe618 --- /dev/null +++ b/pio/pio_ws2812_cpp/README.md @@ -0,0 +1,37 @@ +# Raspberry Pi Pico WS2812 Parallel Pio C++ Example + +This is an example using C++ based on the ws2812_parallel example + +This example is split into separate files for easier reuse in projects. + +## CRGB.hpp + +Structure for handling RGB data inspired by FastLED: +- Provides constructors for colours defined as three 8-bit and one 24-bit values. +- Can be accessed using r,g,b members, by byte index or as a 24-bit value. +- Provide overloads for common maths operations +- toGRB() member function returns 24-bit colour in GRB format used for WS2812 LEDS + +## PLED.hpp + +PLED namespace contains a collection of the low level parallel pio code + +To override defaults add #define before including this file: + +- NUM_STRIPS - up to 32 parallel strips + +- LEDS_PER_STRIP + +- PIN_BASE - first pin in the contiguous set + +- INITIAL_BRIGHT - Initialise value for PLED::brightness which sets the global brightness + +In the main programme: + +- PLED::init() - sets up the DMA channels and pio program +- PLED::show() - transmits the color data to the LED strips + +The same set of colour data can be access through arrays of CRGB structs: + +- PLED::strip[NUM_STRIPS][LEDS_PER_STRIP] +- PLED::led[NUM_STRIPS*LEDS_PER_STRIP] \ No newline at end of file diff --git a/pio/pio_ws2812_cpp/pled.hpp b/pio/pio_ws2812_cpp/pled.hpp new file mode 100644 index 000000000..962a16798 --- /dev/null +++ b/pio/pio_ws2812_cpp/pled.hpp @@ -0,0 +1,150 @@ +// Adapted from https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812_parallel.c +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef PLED_H +#define PLED_H + +#include +#ifdef DEBUG +#include +#endif +#include +#include + +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/pio.h" +#include "pico/sem.h" +#include "pico/stdlib.h" +#include "ws2812.pio.h" + +#include "CRGB.hpp" + +#define LEDBITS 24 +// bit plane content dma channel +#define DMA_CHANNEL 0 +// chain channel for configuring main dma channel to output from disjoint 8 word fragments of memory +#define DMA_CB_CHANNEL 1 + +#define DMA_CHANNEL_MASK (1u << DMA_CHANNEL) +#define DMA_CB_CHANNEL_MASK (1u << DMA_CB_CHANNEL) +#define DMA_CHANNELS_MASK (DMA_CHANNEL_MASK | DMA_CB_CHANNEL_MASK) +#define PIO_FREQ 800000 + +#ifndef NUM_STRIPS +#define NUM_STRIPS 1 +#endif + +#ifndef LEDS_PER_STRIP +#define LEDS_PER_STRIP 1 +#endif + +#ifndef PIN_BASE +#define PIN_BASE 2 +#endif + +#ifndef INITIAL_BRIGHT +#define INITIAL_BRIGHT 64 +#endif + +namespace PLED { +uint8_t brightness = INITIAL_BRIGHT; +uint32_t planes[LEDS_PER_STRIP][LEDBITS]; +CRGB strip[NUM_STRIPS][LEDS_PER_STRIP]; +CRGB *led = &strip[0][0]; +// start of each value fragment (+1 for NULL terminator) +uintptr_t fragment_start[LEDS_PER_STRIP + 1]; +// posted when it is safe to output a new set of values +struct semaphore reset_delay_complete_sem; +// alarm handle for handling delay +alarm_id_t reset_delay_alarm_id; +PIO pio = pio0; +int sm = 0; +uint offset; + +void transpose_bits() { + memset(&planes, 0, sizeof(planes)); + for (uint l = 0; l < LEDS_PER_STRIP; l++) { + uint32_t s; + uint32_t sbit; + for (s = 0, sbit = 1; s < NUM_STRIPS; s++, sbit <<= 1) { + for (uint32_t b = 0, grb = (strip[s][l] * brightness).toGRB(); b < LEDBITS && grb; b++, grb >>= 1) { + if (grb & 1) { + planes[l][LEDBITS - 1 - b] |= sbit; + } + } + } + } +} + +int64_t reset_delay_complete(__unused alarm_id_t id, __unused void *user_data) { + reset_delay_alarm_id = 0; + sem_release(&reset_delay_complete_sem); + // no repeat + return 0; +} + +void __isr dma_complete_handler() { + if (dma_hw->ints0 & DMA_CHANNEL_MASK) { + // clear IRQ + dma_hw->ints0 = DMA_CHANNEL_MASK; + // when the dma is complete we start the reset delay timer + if (reset_delay_alarm_id) + cancel_alarm(reset_delay_alarm_id); + reset_delay_alarm_id = add_alarm_in_us(400, reset_delay_complete, NULL, true); + } +} + +void dma_init(PIO pio, uint sm) { + dma_claim_mask(DMA_CHANNELS_MASK); + + // main DMA channel outputs 24 word fragments, and then chains back to the chain channel + dma_channel_config channel_config = dma_channel_get_default_config(DMA_CHANNEL); + channel_config_set_dreq(&channel_config, pio_get_dreq(pio, sm, true)); + channel_config_set_chain_to(&channel_config, DMA_CB_CHANNEL); + channel_config_set_irq_quiet(&channel_config, true); + dma_channel_configure(DMA_CHANNEL, + &channel_config, + &pio->txf[sm], + NULL, // set by chain + 24, // 24 words for 24 bit planes + false); + + // chain channel sends single word pointer to start of fragment each time + dma_channel_config chain_config = dma_channel_get_default_config(DMA_CB_CHANNEL); + dma_channel_configure(DMA_CB_CHANNEL, + &chain_config, + &dma_channel_hw_addr( + DMA_CHANNEL) + ->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger) + NULL, // set later + 1, + false); + + irq_set_exclusive_handler(DMA_IRQ_0, dma_complete_handler); + dma_channel_set_irq0_enabled(DMA_CHANNEL, true); + irq_set_enabled(DMA_IRQ_0, true); +} + +void output_strips_dma() { + for (uint l = 0; l < LEDS_PER_STRIP; l++) { + fragment_start[l] = (uintptr_t)planes[l]; // MSB first + } + fragment_start[LEDS_PER_STRIP] = 0; + dma_channel_hw_addr(DMA_CB_CHANNEL)->al3_read_addr_trig = (uintptr_t)fragment_start; +} + +void init() { + offset = pio_add_program(pio, &ws2812_program); + ws2812_program_init(pio, sm, offset, PIN_BASE, NUM_STRIPS, PIO_FREQ); + sem_init(&reset_delay_complete_sem, 1, 1); // initially posted so we don't block first time + dma_init(pio, sm); +} + +void show() { + sem_acquire_blocking(&reset_delay_complete_sem); + transpose_bits(); + output_strips_dma(); +} +} // namespace PLED +#endif diff --git a/pio/pio_ws2812_cpp/pled_demo.cpp b/pio/pio_ws2812_cpp/pled_demo.cpp new file mode 100644 index 000000000..13fb7adda --- /dev/null +++ b/pio/pio_ws2812_cpp/pled_demo.cpp @@ -0,0 +1,165 @@ +// Adapted from https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812_parallel.c +// SPDX-License-Identifier: BSD-3-Clause + +// #define DEBUG +// #define TEST_CRGB +// #define TEST_PLED + +#ifdef DEBUG +#include +#include +#endif +#include +#include + +#define LEDS_PER_STRIP 64 +#define NUM_STRIPS 4 +#define PIN_BASE 2 +#define TOTAL_PIXELS (NUM_STRIPS * LEDS_PER_STRIP) +#define INITIAL_BRIGHT 16 + +#include "pled.hpp" + +void pattern_snakes(int tick) { + for (uint i = 0; i < TOTAL_PIXELS; i++) { + uint x = (i + (tick >> 1)) % 64; + if (x < 10) + PLED::led[i] = CRGB(0xff, 0, 0); + else if (x >= 15 && x < 25) + PLED::led[i] = CRGB(0, 0xff, 0); + else if (x >= 30 && x < 40) + PLED::led[i] = CRGB(0, 0, 0xff); + else + PLED::led[i] = CRGB(0); + } +} + +void pattern_random(int tick) { + if (tick % 8) + return; + for (uint i = 0; i < TOTAL_PIXELS; ++i) + PLED::led[i] = CRGB(rand()); +} + +void pattern_sparkle(int tick) { + if (tick % 8) + return; + for (uint i = 0; i < TOTAL_PIXELS; ++i) + PLED::led[i] = CRGB(rand() % 16 ? 0 : 0xffffffff); +} + +void pattern_greys(int tick) { + uint max = 100; // let's not draw too much current! + tick %= max; + for (uint i = 0; i < TOTAL_PIXELS; ++i) { + PLED::led[i] = CRGB(tick * 0x10101); + if (++tick >= max) + tick = 0; + } +} + +void pattern_point(int tick) { + for (uint i = 0; i < TOTAL_PIXELS; ++i) { + PLED::led[i] = CRGB(0); + } + PLED::led[(uint)tick % TOTAL_PIXELS] = CRGB(0xFFFFFF); +} + +typedef void (*pattern)(int tick); +const struct +{ + pattern pat; + const char *name; +} pattern_table[] = { + {pattern_snakes, "Snakes!"}, + {pattern_random, "Random data"}, + {pattern_sparkle, "Sparkles"}, + {pattern_greys, "Greys"}, + {pattern_point, "Point"}}; + +void prompt(const char *p) { +#ifdef DEBUG + printf("%s (Anykey):", p); + char resp = getchar(); + printf("\nOK\n"); +#endif +} + +int main() { +#ifdef DEBUG + stdio_init_all(); + + while (!tud_cdc_connected()) { + sleep_ms(100); + } + sleep_ms(500); + prompt("WS2812 parallel demo\n"); +#endif + PLED::init(); +#ifdef DEBUG + printf("Planes@%X data@%X diff %d\n", &PLED::planes[0][0], &PLED::strip[0][0]); +#ifdef TEST_CRGB + printf("CRGB tests\n"); + CRGB t, t1(0x010203), t2(0x10, 0x20, 0x30); + printf("Default CRGB 00000000=%08X\n", t.toRGB()); + printf("Word CRGB 00010203=%08X\n", t1.toRGB()); + printf("Byte CRGB 00102030=%08X\n", t2.toRGB()); + t.r = 0xFF; + printf("Set r 00FF0000=%08X\n", t.toRGB()); + t.g = 0x80; + printf("Set g 00FF8000=%08X\n", t.toRGB()); + t.b = 0x40; + printf("set b 00FF8040=%08X\n", t.toRGB()); + t2[1] = 0xff; + printf("Set [1] 0010FF30=%08X\n", t2.toRGB()); + printf("Read g 00000002=%08X\n", t1.g); + printf("Read [0] 00000001=%08X\n", t1[0]); + printf("Half CRGB 00804020=%08X\n", (t / 2.0).toRGB()); + printf("Scale CRGB 00804020=%08X\n", (t * (uint8_t)127).toRGB()); + printf("Add 00C06030=%08X\n", (t / 4.0 + t / 2.0).toRGB()); + t = 0x804020; + printf("Set word 00804020=%08X\n", t.toRGB()); + printf("Get grb 00408020=%08X\n", t.toGRB()); + printf("Done\n"); +#endif +#ifdef TEST_PLED + printf("PLED tests\n"); + PLED::strip[0][0] = CRGB(0x102030); + PLED::strip[1][0] = CRGB(0x010203); + printf("Read led 00102030=%08X\n", PLED::led[0].toRGB()); + printf("Read led 00010203=%08X\n", PLED::led[LEDS_PER_STRIP].toRGB()); + PLED::led[2 * LEDS_PER_STRIP] = CRGB(0x804020); + printf("Write led 00804020=%08X\n", PLED::strip[2][0].toRGB()); + PLED::transpose_bits(); + printf("Check 00804020=%08X\n", PLED::strip[2][0].toRGB()); + printf("Read planes\n"); + for (int b = 0; b < 8; b++) { + printf("%d:%08X\n", b, PLED::planes[0][b]); + } + printf("Done\n"); +#endif +#endif + while (1) { + int tick = 0; + int pat = rand() % count_of(pattern_table); + int dir = (rand() >> 30) & 1 ? 1 : -1; + if (rand() & 1) + dir = 0; +#ifdef DEBUG + printf(pattern_table[pat].name); + prompt(dir == 1 ? "(forward)" : dir ? "(backward)" + : "(still)"); +#endif + for (int i = 0; i < 1000; ++i) { + pattern_table[pat].pat(tick); +#ifdef DEBUG + printf("First pixel %06X\r", PLED::led[0].toRGB()); +#endif + tick += dir; + PLED::show(); + } +#ifdef DEBUG + printf("First pixel %06X\n", PLED::led[0].toRGB()); +#endif + } +} diff --git a/pio/pio_ws2812_cpp/ws2812.pio b/pio/pio_ws2812_cpp/ws2812.pio new file mode 100644 index 000000000..3b99fc477 --- /dev/null +++ b/pio/pio_ws2812_cpp/ws2812.pio @@ -0,0 +1,42 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; +.pio_version 0 // only requires PIO version 0 + +.program ws2812 + +.define public T1 3 +.define public T2 3 +.define public T3 4 + +.wrap_target + out x, 32 + mov pins, !null [T1-1] + mov pins, x [T2-1] + mov pins, null [T3-2] +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) { + for(uint i=pin_base; i