Skip to content

C++ style example based upon existing c example #566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions pio/pio_ws2812_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
79 changes: 79 additions & 0 deletions pio/pio_ws2812_cpp/CRGB.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#ifndef CRGB_H
#define CRGB_H

#include <stdlib.h>

#include <cmath>

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<uint8_t>(ceil(r * f)), static_cast<uint8_t>(ceil(g * f)), static_cast<uint8_t>(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
37 changes: 37 additions & 0 deletions pio/pio_ws2812_cpp/README.md
Original file line number Diff line number Diff line change
@@ -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]
150 changes: 150 additions & 0 deletions pio/pio_ws2812_cpp/pled.hpp
Original file line number Diff line number Diff line change
@@ -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 <stdlib.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include <cmath>
#include <cstring>

#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
Loading