4#if FL_PLATFORM_HAS_LARGE_MEMORY
19#include "platforms/arm/rp/is_rp.h"
21#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
22#include "platforms/arm/rp/rpcommon/rp_spi_bus_traits.h"
27#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
30 if (!config.
contains(key) || !config[key].is_int())
return false;
31 const fl::i64 parsed = config[key].
as_int().value();
36 *value =
static_cast<int>(parsed);
40template <fl::u8 Which>
42 int mosi_pin,
int miso_pin,
int sck_pin,
int clock_hz) {
43 const fl::u8 loopback_pattern[] = {
44 0x00, 0xFF, 0x55, 0xAA, 0x12, 0x34, 0x56, 0x78,
45 0x87, 0x65, 0x43, 0x21, 0x0F, 0xF0, 0xCC, 0x33,
46 0x11, 0xEE, 0x22, 0xDD, 0x44, 0xBB, 0x88, 0x77,
47 0x66, 0x99, 0x3C, 0xC3, 0x5A, 0xA5, 0x7E, 0x81,
50 const int expected_mosi = Which == 0 ? 3 : 11;
51 const int expected_miso = Which == 0 ? 0 : 8;
52 const int expected_sck = Which == 0 ? 2 : 10;
53 if (mosi_pin != expected_mosi || miso_pin != expected_miso ||
54 sck_pin != expected_sck) {
56 response.set(
"error",
"UnsupportedPins");
57 response.set(
"message",
"Pins do not select the requested RP fixed SPI instance.");
60 if (clock_hz < 100000 || clock_hz > 62500000) {
62 response.set(
"error",
"InvalidClock");
63 response.set(
"message",
"clock_hz must be in [100000, 62500000].");
70 runtime_state->rx_channel.
reset();
71 runtime_state->invalidateLedRxValidation();
75 tx.
assign(loopback_pattern, loopback_pattern +
sizeof(loopback_pattern));
76 fl::u8 captured[
sizeof(loopback_pattern)] = {};
82 if (!driver.captureNextRxBytes(captured,
sizeof(captured))) {
85 response.set(
"message",
"RP SPI driver was busy before the loopback transfer.");
88 driver.enqueue(channel);
92 const unsigned long start_ms =
millis();
93 while (
millis() - start_ms < 2000) {
94 state = driver.poll();
105 for (
size_t index = 0; index <
sizeof(loopback_pattern); ++index) {
106 expected.
push_back(
static_cast<int64_t
>(loopback_pattern[index]));
107 received.
push_back(
static_cast<int64_t
>(captured[index]));
108 exact_match = exact_match && captured[index] == loopback_pattern[index];
112 data.
set(
"driver", Which == 0 ?
"SPI0" :
"SPI1");
113 data.
set(
"mosiPin",
static_cast<int64_t
>(mosi_pin));
114 data.
set(
"misoPin",
static_cast<int64_t
>(miso_pin));
115 data.
set(
"sckPin",
static_cast<int64_t
>(sck_pin));
116 data.
set(
"requestedClockHz",
static_cast<int64_t
>(clock_hz));
117 data.
set(
"actualClockHz",
static_cast<int64_t
>(driver.lastActualClockHz()));
119 data.
set(
"expected", expected);
120 data.
set(
"received", received);
121 response.set(
"success", exact_match);
122 response.set(
"message", exact_match ?
"RP SPI loopback passed." :
"RP SPI loopback failed.");
127template <
bool Sk9822>
128struct PublicApiLoopbackState {
132 PublicApiLoopbackState() : initialized(false) {}
135template <
bool Sk9822>
136PublicApiLoopbackState<Sk9822>& publicApiLoopbackState() {
140template <
bool Sk9822>
144 constexpr fl::u8 kMosiPin = 11;
145 constexpr fl::u8 kSckPin = 10;
146 constexpr size_t kLedCount = 257;
147 constexpr size_t kFrameBytes = 4 + (kLedCount * 4) + 36;
148 PublicApiLoopbackState<Sk9822>&
state = publicApiLoopbackState<Sk9822>();
152 if (pattern < 0 || pattern > 5) {
154 response.set(
"error",
"InvalidPattern");
159 runtime_state->rx_channel.
reset();
160 runtime_state->invalidateLedRxValidation();
163 if (!
state.initialized) {
164 if constexpr (Sk9822) {
171 state.initialized =
true;
174 for (
size_t index = 0; index < kLedCount; ++index)
leds[index] =
CRGB::Black;
176 case 1:
for (
size_t index = 0; index < kLedCount; ++index)
leds[index] =
CRGB::Red;
break;
177 case 2:
for (
size_t index = 0; index < kLedCount; ++index)
leds[index] =
CRGB::Green;
break;
178 case 3:
for (
size_t index = 0; index < kLedCount; ++index)
leds[index] =
CRGB::Blue;
break;
179 case 4:
for (
size_t index = 0; index < kLedCount; ++index)
leds[index] =
CRGB::White;
break;
183 fl::u8 captured[kFrameBytes] = {};
185 if (!driver.captureNextRxBytes(captured,
sizeof(captured))) {
187 response.set(
"error",
"DriverBusy");
193 bool exact = driver.lastActualClockHz() > 0;
194 for (
size_t index = 0; index < 4; ++index) exact = exact && captured[index] == 0x00;
195 for (
size_t led = 0; led < kLedCount; ++led) {
196 const size_t offset = 4 + (led * 4);
197 exact = exact && captured[
offset] == 0xFF;
198 exact = exact && captured[
offset + 1] ==
leds[led].r;
199 exact = exact && captured[
offset + 2] ==
leds[led].g;
200 exact = exact && captured[
offset + 3] ==
leds[led].b;
202 for (
size_t index = 4 + (kLedCount * 4); index < kFrameBytes; ++index) {
203 exact = exact && captured[index] == 0xFF;
207 data.
set(
"chipset", Sk9822 ?
"SK9822" :
"APA102");
208 data.
set(
"pattern",
static_cast<int64_t
>(pattern));
209 data.
set(
"actualClockHz",
static_cast<int64_t
>(driver.lastActualClockHz()));
210 data.
set(
"wireIdle",
true);
211 data.
set(
"frameBytes",
static_cast<int64_t
>(kFrameBytes));
213 response.set(
"message", exact ?
"RP SPI public API loopback passed."
214 :
"RP SPI public API loopback failed.");
224 remote.bind(
"rpSpiLoopback", [
this](
const fl::json&
args) -> fl::json {
225#if !defined(FL_IS_RP2040) && !defined(FL_IS_RP2350)
230 response.set(
"error",
"PlatformNotSupported");
231 response.set(
"message",
"rpSpiLoopback requires an RP2040 or RP2350 target.");
234 if (!
args.is_object()) {
237 response.set(
"error",
"InvalidArgs");
238 response.set(
"message",
"Expected an object with spi_index, mosi_pin, miso_pin, sck_pin, and clock_hz.");
241 const fl::json config =
args;
247 if (!
parseInt(config,
"spi_index", &spi_index) ||
248 !
parseInt(config,
"mosi_pin", &mosi_pin) ||
249 !
parseInt(config,
"miso_pin", &miso_pin) ||
250 !
parseInt(config,
"sck_pin", &sck_pin) ||
251 !
parseInt(config,
"clock_hz", &clock_hz)) {
254 response.set(
"error",
"InvalidArgs");
255 response.set(
"message",
"spi_index, mosi_pin, miso_pin, sck_pin, and clock_hz must be integers.");
258 if (spi_index == 0) {
259 return runLoopback<0>(
mState, mosi_pin, miso_pin, sck_pin,
262 if (spi_index == 1) {
263 return runLoopback<1>(
mState, mosi_pin, miso_pin, sck_pin,
268 response.set(
"error",
"InvalidSpiIndex");
269 response.set(
"message",
"spi_index must be 0 or 1.");
273 remote.bind(
"rpSpiPublicApiLoopback", [
this](
const fl::json&
args) -> fl::json {
274#if !defined(FL_IS_RP2040) && !defined(FL_IS_RP2350)
279 response.set(
"error",
"PlatformNotSupported");
282 if (!
args.is_object() || !
args.contains(
"sk9822") ||
283 !
args[
"sk9822"].is_bool() || !
args.contains(
"pattern") ||
284 !
args[
"pattern"].is_int()) {
287 response.set(
"error",
"InvalidArgs");
290 const fl::i64 pattern_value =
args[
"pattern"].as_int().value();
291 if (pattern_value < 0 || pattern_value > 5) {
294 response.set(
"error",
"InvalidPattern");
297 const bool sk9822 =
args[
"sk9822"].as_bool().value();
298 const int pattern =
static_cast<int>(pattern_value);
299 return sk9822 ? runPublicApiLoopback<true>(
mState, pattern)
300 : runPublicApiLoopback<false>(
mState, pattern);
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::unique_ptr< fl::Remote > remote
SPI encoder configuration for clocked LED chipsets.
void bindRpSpiMethods(fl::Remote &remote)
fl::shared_ptr< AutoResearchState > mState
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >(), ChannelPixelFormat pixelFormat=ChannelPixelFormat::RGB) FL_NO_EXCEPT
Create channel transmission data (modern variant-based API)
JSON-RPC server with scheduling support.
fl::optional< i64 > as_int() const FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
void push_back(const json &value) FL_NO_EXCEPT
bool contains(size_t idx) const FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
static json array() FL_NO_EXCEPT
void reset() FL_NO_EXCEPT
void assign(InputIt first, InputIt last) FL_NO_EXCEPT
Channel transmission data - lightweight DTO for driver transmission.
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
@ SK9822
SK9822 LED chipset.
@ APA102
APA102 LED chipset.
FastLED's Elegant JSON Library: fl::json
bool parseInt(const fl::string &str, int &out)
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
Primary template — intentionally undefined.
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
@ White
<div style='background:#FFFFFF;width:4em;height:4em;'></div>
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
@ READY
Hardware idle; ready to accept new transmissions.
@ ERROR
Driver encountered an error.
@ BUSY
Active: channels transmitting or queued.
Driver state with optional error message.
SPI chipset configuration (data + clock pins)
static SpiEncoder apa102(u32 clock_hz=6000000) FL_NO_EXCEPT
Create APA102 encoder configuration.
static constexpr T min() FL_NO_EXCEPT
static constexpr T max() FL_NO_EXCEPT