From 7c6744da4f508d3abbf880d19b74f89752db0fce Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Mon, 11 Mar 2024 09:36:13 -0700 Subject: [PATCH] Rp2040 (#779) Support x4 on each PIO --- .../NeoPixel_RP2040_PioX4.ino | 56 +++ src/internal/NeoMethods.h | 4 + src/internal/methods/NeoArmMethod.h | 2 +- .../methods/Rp2040/NeoRp2040DmaState.h | 175 +++++++ .../methods/Rp2040/NeoRp2040PioInstance.h | 66 +++ .../Rp2040/NeoRp2040PioMonoProgram.cpp | 77 ++++ .../methods/Rp2040/NeoRp2040PioMonoProgram.h | 211 +++++++++ .../methods/Rp2040/NeoRp2040PioSpeed.h | 113 +++++ .../methods/Rp2040/NeoRp2040x4Method.h | 431 ++++++++++++++++++ 9 files changed, 1134 insertions(+), 1 deletion(-) create mode 100644 examples/RP2040/NeoPixel_RP2040_PioX4/NeoPixel_RP2040_PioX4.ino create mode 100644 src/internal/methods/Rp2040/NeoRp2040DmaState.h create mode 100644 src/internal/methods/Rp2040/NeoRp2040PioInstance.h create mode 100644 src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.cpp create mode 100644 src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.h create mode 100644 src/internal/methods/Rp2040/NeoRp2040PioSpeed.h create mode 100644 src/internal/methods/Rp2040/NeoRp2040x4Method.h diff --git a/examples/RP2040/NeoPixel_RP2040_PioX4/NeoPixel_RP2040_PioX4.ino b/examples/RP2040/NeoPixel_RP2040_PioX4/NeoPixel_RP2040_PioX4.ino new file mode 100644 index 0000000..0d131c8 --- /dev/null +++ b/examples/RP2040/NeoPixel_RP2040_PioX4/NeoPixel_RP2040_PioX4.ino @@ -0,0 +1,56 @@ +// +// NeoPixel_RP2040_PioX4 - +// This sketch demonstrates the use of the PIO method allowing upto 4 instances +// Per one of the two PIO channels +// +// This example only works on the RP2040 +// +// The key part of the method name is Rp2040Pio1X4, +// Rp2040 (platform specific method), +// PIO Channel 1 (most commonly available), +// X4 (4 instances allowed) +// +// In this example, it demonstrates different ColorFeatures, Method specification, and count per strip +// +#include + +// Demonstrating the use of the four channels +NeoPixelBus strip1(120, 15); // note: older WS2811 and longer strip +NeoPixelBus strip2(100, 2); // note: modern WS2812 with letter like WS2812b +NeoPixelBus strip3(100, 4); // note: inverted +NeoPixelBus strip4(50, 16); // note: RGBW and Sk6812 and smaller strip + +void setup() { + Serial.begin(115200); + while (!Serial); // wait for serial attach + + Serial.println(); + Serial.println("Initializing..."); + Serial.flush(); + + // must call begin on all the strips + strip1.Begin(); + strip2.Begin(); + strip3.Begin(); + strip4.Begin(); + + Serial.println(); + Serial.println("Running..."); +} + +void loop() { + delay(1000); + + // draw on the strips + strip1.SetPixelColor(0, RgbColor(255, 0, 0)); // red + strip2.SetPixelColor(0, RgbColor(0, 127, 0)); // green + strip3.SetPixelColor(0, RgbColor(0, 0, 53)); // blue + strip4.SetPixelColor(0, RgbwColor(0, 0, 128, 255)); // white channel with a little blue + + // show them, + // only on the last show, no matter the order, will the data be sent + strip1.Show(); + strip2.Show(); + strip3.Show(); + strip4.Show(); +} \ No newline at end of file diff --git a/src/internal/NeoMethods.h b/src/internal/NeoMethods.h index 876b9fe..49c953d 100644 --- a/src/internal/NeoMethods.h +++ b/src/internal/NeoMethods.h @@ -62,6 +62,10 @@ License along with NeoPixel. If not, see #include "methods/NeoNrf52xMethod.h" +#elif defined(ARDUINO_ARCH_RP2040) // must be before __arm__ + +#include "methods/Rp2040/NeoRp2040x4Method.h" + #elif defined(__arm__) // must be before ARDUINO_ARCH_AVR due to Teensy incorrectly having it set #include "methods/NeoArmMethod.h" diff --git a/src/internal/methods/NeoArmMethod.h b/src/internal/methods/NeoArmMethod.h index c68324c..0298be3 100644 --- a/src/internal/methods/NeoArmMethod.h +++ b/src/internal/methods/NeoArmMethod.h @@ -30,7 +30,7 @@ License along with NeoPixel. If not, see #pragma once -#if defined(__arm__) && !defined(ARDUINO_ARCH_NRF52840) +#if defined(__arm__) && !defined(ARDUINO_ARCH_NRF52840) && !defined(ARDUINO_ARCH_RP2040) template class NeoArmMethodBase { diff --git a/src/internal/methods/Rp2040/NeoRp2040DmaState.h b/src/internal/methods/Rp2040/NeoRp2040DmaState.h new file mode 100644 index 0000000..2190bfe --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040DmaState.h @@ -0,0 +1,175 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_RP2040 + +// DMA Finished State Tracking +// -------------------------------------------------------- + +enum Rp2040PioDmaState +{ + Rp2040PioDmaState_Sending, + Rp2040PioDmaState_DmaCompleted, + Rp2040PioDmaState_FifoEmptied +}; + +template +class NeoRp2040DmaState +{ +public: + NeoRp2040DmaState() : + _endTime(0), + _state(Rp2040PioDmaState_FifoEmptied) + { + } + + void SetSending() + { + _state = Rp2040PioDmaState_Sending; + } + + void DmaFinished() + { + _endTime = micros(); + _state = Rp2040PioDmaState_DmaCompleted; + } + + bool IsReadyToSend(uint32_t resetTimeUs) const + { + bool isReady = false; + + switch (_state) + { + case Rp2040PioDmaState_Sending: + break; + + case Rp2040PioDmaState_DmaCompleted: + { + uint32_t delta = micros() - _endTime; + + if (delta >= resetTimeUs) + { + // const method requires that we const cast to change state + *const_cast(&_state) = Rp2040PioDmaState_FifoEmptied; + isReady = true; + } + } + break; + + default: + isReady = true; + break; + } + + return isReady; + } + + void Register(uint dmChannel) + { + if (s_dmaIrqObjectTable[dmChannel] == nullptr) + { + s_dmaIrqObjectTable[dmChannel] = this; + + int32_t refCount = s_refCountHandler++; + + if (refCount == 0) + { + // Set up end-of-DMA interrupt handler + irq_add_shared_handler(V_IRQ_INDEX ? DMA_IRQ_1 : DMA_IRQ_0, + dmaFinishIrq, + PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + irq_set_enabled(V_IRQ_INDEX ? DMA_IRQ_1 : DMA_IRQ_0, true); + } + } + +#if defined(NEORP2040_DEBUG) + + Serial.print(" V_IRQ_INDEX = "); + Serial.print(V_IRQ_INDEX); + Serial.print(", s_dmaIrqObjectTable = "); + Serial.print((uint32_t)(s_dmaIrqObjectTable)); // address of s_dmaIrqObjectTable + Serial.println(); + +#endif + + } + + void Unregister(uint dmChannel) + { + if (s_dmaIrqObjectTable[dmChannel] == this) + { + int32_t refCount = s_refCountHandler--; + + if (refCount == 0) + { + irq_set_enabled(V_IRQ_INDEX ? DMA_IRQ_1 : DMA_IRQ_0, false); + irq_remove_handler(V_IRQ_INDEX ? DMA_IRQ_1 : DMA_IRQ_0, dmaFinishIrq); + } + + s_dmaIrqObjectTable[dmChannel] = nullptr; + } + } + +private: + volatile uint32_t _endTime; // Latch/Reset timing reference + volatile Rp2040PioDmaState _state; + + static NeoRp2040DmaState* s_dmaIrqObjectTable[NUM_DMA_CHANNELS]; + static volatile int32_t s_refCountHandler; + + static void dmaFinishIrq() + { + // dmaChannels are unique across both PIOs, while stateMachines are per + // PIO, so this current model below works even if both PIOs are used + for (uint dmaChannel = 0; dmaChannel < NUM_DMA_CHANNELS; dmaChannel++) + { + NeoRp2040DmaState* that = s_dmaIrqObjectTable[dmaChannel]; + if (that != nullptr) + { + if (dma_irqn_get_channel_status(V_IRQ_INDEX, dmaChannel)) + { + dma_irqn_acknowledge_channel(V_IRQ_INDEX, dmaChannel); + that->DmaFinished(); + } + } + } + } +}; + +template +NeoRp2040DmaState* NeoRp2040DmaState::s_dmaIrqObjectTable[NUM_DMA_CHANNELS] = + { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + +template +volatile int32_t NeoRp2040DmaState::s_refCountHandler = 0; + + +#endif \ No newline at end of file diff --git a/src/internal/methods/Rp2040/NeoRp2040PioInstance.h b/src/internal/methods/Rp2040/NeoRp2040PioInstance.h new file mode 100644 index 0000000..91f558b --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040PioInstance.h @@ -0,0 +1,66 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_RP2040 + +// PIO Instances +// -------------------------------------------------------- +class NeoRp2040PioInstance0 +{ +public: + NeoRp2040PioInstance0() : + Instance(pio0) + {}; + + const PIO Instance; +}; + +class NeoRp2040PioInstance1 +{ +public: + NeoRp2040PioInstance1() : + Instance(pio1) + {}; + + const PIO Instance; +}; + +// dynamic channel support +class NeoRp2040PioInstanceN +{ +public: + NeoRp2040PioInstanceN(NeoBusChannel channel) : + Instance(channel == NeoBusChannel_0 ? pio0 : pio1) + { + } + NeoRp2040PioInstanceN() = delete; // no default constructor + + const PIO Instance; +}; + +#endif diff --git a/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.cpp b/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.cpp new file mode 100644 index 0000000..7f07213 --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.cpp @@ -0,0 +1,77 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include +#include "../../NeoUtil.h" +#include "../../NeoBusChannel.h" +#include "../../NeoSettings.h" +#include "NeoRp2040x4Method.h" + +#ifdef ARDUINO_ARCH_RP2040 + +// See NeoRp2040PioCadenceMono3Step, NeoRp2040PioCadenceMono4Step in header for details +// on changing the PIO programs +// +// these are only required here in the .cpp due to current compiler version doesn't +// support doing this in the header file +// +// + +const uint16_t NeoRp2040PioCadenceMono3Step::program_instructions[] = + { + // .wrap_target + 0x6021, // 0: out x, 1 side 0 + 0x1023, // 1: jmp !x, 3 side 1 + 0x1000, // 2: jmp 0 side 1 + 0xa042, // 3: nop side 0 + // .wrap + }; + +const struct pio_program NeoRp2040PioCadenceMono3Step::program = + { + .instructions = NeoRp2040PioCadenceMono3Step::program_instructions, + .length = 4, + .origin = -1, + }; + + +const uint16_t NeoRp2040PioCadenceMono4Step::program_instructions[] = + { + // .wrap_target + 0x6021, // 0: out x, 1 side 0 + 0x1023, // 1: jmp !x, 3 side 1 + 0x1100, // 2: jmp 0 side 1 [1] + 0xa142, // 3: nop side 0 [1] + // .wrap + }; + +const struct pio_program NeoRp2040PioCadenceMono4Step::program = + { + .instructions = NeoRp2040PioCadenceMono4Step::program_instructions, + .length = 4, + .origin = -1, + }; +#endif \ No newline at end of file diff --git a/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.h b/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.h new file mode 100644 index 0000000..ae08eaa --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040PioMonoProgram.h @@ -0,0 +1,211 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_RP2040 + +// PIO Programs (cadence) +// -------------------------------------------------------- +// use https://wokwi.com/tools/pioasm +// copy relevant parts into NeoRp2040PioCadenceMono3Step and NeoRp2040PioCadenceMono4Step +// +// 3 step program +// the pulse width is divided by 3, using 33% for each stage +// where 0 = 33% and 1 = 66% +/* +.program rgbic_mono +.side_set 1 + +; 0 bit +; TH0 TH1 TL1 +; +++ ___ ___ + +; 1 bit +; TH0 TH1 TL1 +; +++ +++ ___ +.define public TH0 1 ; T1 +.define public TH1 1 ; T2 +.define public TL1 1 ; T3 + +.wrap_target +bitloop: + out x, 1 side 0 [TL1 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [TH0 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [TH1 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [TH1 - 1] ; Or drive low, for a short pulse +.wrap +*/ +// +class NeoRp2040PioCadenceMono3Step +{ +protected: + static constexpr uint8_t wrap_target = 0; + static constexpr uint8_t wrap = 3; + + static constexpr uint8_t TH0 = 1; + static constexpr uint8_t TH1 = 1; + static constexpr uint8_t TL1 = 1; + + // changed from constexpr with initializtion due to + // that is only supported in c17+ + static const uint16_t program_instructions[]; + +public: + // changed from constexpr with initializtion due to + // that is only supported in c17+ + static const struct pio_program program; + + static inline pio_sm_config get_default_config(uint offset) + { + pio_sm_config c = pio_get_default_sm_config(); + + sm_config_set_wrap(&c, offset + wrap_target, offset + wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; + } + + static constexpr uint8_t bit_cycles = TH0 + TH1 + TL1; +}; + +// 4 step program +// the pulse width is divided by 4, using 25% for each stage +// where 0 = 25% and 1 = 75% +// +/* +.program rgbic_mono +.side_set 1 + +; 0 bit +; TH0 TH1 TL1 +; +++ ___ ___ + +; 1 bit +; TH0 TH1 TL1 +; +++ +++ ___ +.define public TH0 1 ; T1 +.define public TH1 2 ; T2 +.define public TL1 1 ; T3 + +.wrap_target +bitloop: + out x, 1 side 0 [TL1 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [TH0 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [TH1 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [TH1 - 1] ; Or drive low, for a short pulse +.wrap +*/ +// +class NeoRp2040PioCadenceMono4Step +{ +protected: + static constexpr uint8_t wrap_target = 0; + static constexpr uint8_t wrap = 3; + + static constexpr uint8_t TH0 = 1; + static constexpr uint8_t TH1 = 2; + static constexpr uint8_t TL1 = 1; + + // changed from constexpr with initializtion due to + // that is only supported in c17+ + static const uint16_t program_instructions[]; + +public: + // changed from constexpr with initializtion due to + // that is only supported in c17+ + static const struct pio_program program; + + static inline pio_sm_config get_default_config(uint offset) + { + pio_sm_config c = pio_get_default_sm_config(); + + sm_config_set_wrap(&c, offset + wrap_target, offset + wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; + } + + static constexpr uint8_t bit_cycles = TH0 + TH1 + TL1; +}; + +// Program Wrapper +// -------------------------------------------------------- +constexpr uint c_ProgramNotLoaded = static_cast(-1); + +template +class NeoRp2040PioMonoProgram +{ +public: + static inline uint add(PIO pio_instance) + { + if (s_loadedOffset == c_ProgramNotLoaded) + { + assert(pio_can_add_program(pio_instance, &T_CADENCE::program)); + s_loadedOffset = pio_add_program(pio_instance, &T_CADENCE::program); + } + return s_loadedOffset; + } + + static inline void init(PIO pio_instance, + uint sm, + uint offset, + uint pin, + float bitrate, + uint shiftBits) + { + float div = clock_get_hz(clk_sys) / (bitrate * T_CADENCE::bit_cycles); + pio_sm_config c = T_CADENCE::get_default_config(offset); + + sm_config_set_sideset_pins(&c, pin); + + sm_config_set_out_shift(&c, false, true, shiftBits); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_clkdiv(&c, div); + + // Set this pin's GPIO function (connect PIO to the pad) + pio_gpio_init(pio_instance, pin); + + // Set the pin direction to output at the PIO + pio_sm_set_consecutive_pindirs(pio_instance, sm, pin, 1, true); + + // Load our configuration, and jump to the start of the program + pio_sm_init(pio_instance, sm, offset, &c); + + // Set the state machine running + pio_sm_set_enabled(pio_instance, sm, true); + } + +private: + static uint s_loadedOffset; // singlet instance of loaded program +}; + +template +uint NeoRp2040PioMonoProgram::s_loadedOffset = c_ProgramNotLoaded; + +#endif diff --git a/src/internal/methods/Rp2040/NeoRp2040PioSpeed.h b/src/internal/methods/Rp2040/NeoRp2040PioSpeed.h new file mode 100644 index 0000000..a7b97e8 --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040PioSpeed.h @@ -0,0 +1,113 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_RP2040 + +// Speeds +// -------------------------------------------------------- +class NeoRp2040PioSpeedWs2811 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.0f; // 300+950 + static constexpr uint32_t ResetTimeUs = 300; +}; + +class NeoRp2040PioSpeedWs2812x : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.0f; // 400+850 + static constexpr uint32_t ResetTimeUs = 300; +}; + +class NeoRp2040PioSpeedSk6812 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.0f; // 400+850 + static constexpr uint32_t ResetTimeUs = 80; +}; + +// normal is inverted signal +class NeoRp2040PioSpeedTm1814 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.00f; // 360+890 + static constexpr uint32_t ResetTimeUs = 200; +}; + +// normal is inverted signal +class NeoRp2040PioSpeedTm1829 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 833333.33f; // 300+900 + static constexpr uint32_t ResetTimeUs = 200; +}; + +// normal is inverted signal +class NeoRp2040PioSpeedTm1914 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.0f; // 360+890 + static constexpr uint32_t ResetTimeUs = 200; +}; + +class NeoRp2040PioSpeed800Kbps : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 800000.0f; // 400+850 + static constexpr uint32_t ResetTimeUs = 50; +}; + +class NeoRp2040PioSpeed400Kbps : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 400000.0f; // 800+1700 + static constexpr uint32_t ResetTimeUs = 50; +}; + +class NeoRp2040PioSpeedApa106 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 588235.29f; // 350+1350 + static constexpr uint32_t ResetTimeUs = 50; +}; + +class NeoRp2040PioSpeedTx1812 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 1111111.11f; // 300+600 + static constexpr uint32_t ResetTimeUs = 80; +}; + +class NeoRp2040PioSpeedGs1903 : public NeoRp2040PioMonoProgram +{ +public: + static constexpr float BitRateHz = 833333.33f; // 300+900 + static constexpr uint32_t ResetTimeUs = 40; +}; + +#endif \ No newline at end of file diff --git a/src/internal/methods/Rp2040/NeoRp2040x4Method.h b/src/internal/methods/Rp2040/NeoRp2040x4Method.h new file mode 100644 index 0000000..42d5f28 --- /dev/null +++ b/src/internal/methods/Rp2040/NeoRp2040x4Method.h @@ -0,0 +1,431 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for RP2040. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_RP2040 + +//#define NEORP2040_DEBUG + +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "pico/mutex.h" + +#include "NeoRp2040DmaState.h" +#include "NeoRp2040PioMonoProgram.h" +#include "NeoRp2040PioInstance.h" +#include "NeoRp2040PioSpeed.h" + + +// Method +// -------------------------------------------------------- +template +class NeoRp2040x4MethodBase +{ +public: + typedef NeoNoSettings SettingsObject; + + NeoRp2040x4MethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _sizeData(pixelCount * elementSize + settingsSize), + _pin(pin), + _mergedFifoCount((_pio.Instance->dbg_cfginfo & PIO_DBG_CFGINFO_FIFO_DEPTH_BITS) * 2) // merged TX / RX FIFO buffer in words + { + construct(); + } + + NeoRp2040x4MethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) : + _sizeData(pixelCount* elementSize + settingsSize), + _pin(pin), + _pio(channel), + _mergedFifoCount((_pio.Instance->dbg_cfginfo& PIO_DBG_CFGINFO_FIFO_DEPTH_BITS) * 2) // merged TX / RX FIFO buffer in words + { + construct(); + } + + ~NeoRp2040x4MethodBase() + { + // wait for last send + while (!IsReadyToUpdate()) + { + yield(); + } + + // clear any remaining just to be extra sure + pio_sm_clear_fifos(_pio.Instance, _sm); + + // disable the state machine + pio_sm_set_enabled(_pio.Instance, _sm, false); + + // Disable and remove interrupts + // dma_channel_cleanup(_dmaChannel); // NOT PRESENT?! + dma_irqn_set_channel_enabled(V_IRQ_INDEX, _dmaChannel, false); + + // unregister static dma callback object + _dmaState.Unregister(_dmaChannel); + + // unclaim dma channel and then state machine + dma_channel_unclaim(_dmaChannel); + pio_sm_unclaim(_pio.Instance, _sm); + + pinMode(_pin, INPUT); + + free(_dataEditing); + free(_dataSending); + } + + bool IsReadyToUpdate() const + { + return _dmaState.IsReadyToSend(T_SPEED::ResetTimeUs + _fifoCacheEmptyDelta); + } + + void Initialize() + { + // Select the largest FIFO fetch size that aligns with our data size + // BUT, since RP2040 is little endian, if the source element size is + // 8 bits, then the larger shift bits accounts for endianess + // and will mangle the order thinking its source was a 16/32 bits + // must you use channel_config_set_bswap() to address this, see below + uint fifoWordBits = 8; // size of a FIFO word in bits + + if (_sizeData % 4 == 0) + { + // data is 4 byte aligned in size, + // use a 32 bit fifo word for effeciency + fifoWordBits = 32; + } + else if (_sizeData % 2 == 0) + { + // data is 2 byte aligned in size, + // use a 16 bit fifo word for effeciency + fifoWordBits = 16; + } + + // calc the two related values from fifoWordBits + dma_channel_transfer_size dmaTransferSize = static_cast(fifoWordBits / 16); + uint dmaTransferCount = _sizeData / (fifoWordBits / 8);; + + // IRQ triggers on DMA buffer finished, + // not the FIFO buffer finished sending, + // so we calc this delta so it can be added to the reset time + // + + // 1000000.0f / T_SPEED::BitRateHz = us to send one bit + float bitLengthUs = 1000000.0f / T_SPEED::BitRateHz; + + // _mergedFifoCount is merged TX/RX FIFO buffer in words + // Add another word for any IRQ trigger latency (error) as + // too short is catastrophic and too long is fine + _fifoCacheEmptyDelta = bitLengthUs * fifoWordBits * (_mergedFifoCount + 1); + +#if defined(NEORP2040_DEBUG) + +Serial.print(", _pio.Instance = "); +Serial.print((_pio.Instance == pio1)); +Serial.print(", _sizeData = "); +Serial.print(_sizeData); +Serial.print(", dmaTransferSize = "); +Serial.print(dmaTransferSize); +Serial.print(", dmaTransferCount = "); +Serial.print(dmaTransferCount); +Serial.print(", fifoWordBits = "); +Serial.print(fifoWordBits); +Serial.print(", _mergedFifoCount = "); +Serial.print(_mergedFifoCount); +Serial.print(", _fifoCacheEmptyDelta = "); +Serial.print(_fifoCacheEmptyDelta); + +#endif + + // Our assembled program needs to be loaded into this PIO's instruction + // memory. This SDK function will find a location (offset) in the + // instruction memory where there is enough space for our program. We need + // + uint offset = T_SPEED::add(_pio.Instance); + +#if defined(NEORP2040_DEBUG) + +Serial.println(); +Serial.print("offset = "); +Serial.print(offset); + +#endif + + // Find a free state machine on our chosen PIO. + _sm = pio_claim_unused_sm(_pio.Instance, true); // panic if none available + +#if defined(NEORP2040_DEBUG) + +Serial.print(", _sm = "); +Serial.print(_sm); + +#endif + + // Configure it to run our program, and start it, using the + // helper function we included in our .pio file. + T_SPEED::init(_pio.Instance, + _sm, + offset, + _pin, + T_SPEED::BitRateHz, + fifoWordBits); + + // invert output if needed + if (V_INVERT) + { + gpio_set_outover(_pin, GPIO_OVERRIDE_INVERT); + } + + // find a free dma channel + _dmaChannel = dma_claim_unused_channel(true); // panic if none available + +#if defined(NEORP2040_DEBUG) + +Serial.print(", *_dmaChannel = "); +Serial.print(_dmaChannel); +Serial.println(); + +#endif + + // register for IRQ shared static endTime updates + _dmaState.Register(_dmaChannel); + + // Set up DMA transfer + dma_channel_config dmaConfig = dma_channel_get_default_config(_dmaChannel); + channel_config_set_transfer_data_size(&dmaConfig, dmaTransferSize); + channel_config_set_read_increment(&dmaConfig, true); + channel_config_set_write_increment(&dmaConfig, false); + // source is byte data stream, even with 16/32 transfer size + channel_config_set_bswap(&dmaConfig, true); + + // Set DMA trigger + channel_config_set_dreq(&dmaConfig, pio_get_dreq(_pio.Instance, _sm, true)); + + dma_channel_configure(_dmaChannel, + &dmaConfig, + &(_pio.Instance->txf[_sm]), // dest + _dataSending, // src + dmaTransferCount, + false); + + dma_irqn_set_channel_enabled(V_IRQ_INDEX, _dmaChannel, true); + } + + void Update(bool maintainBufferConsistency) + { + // wait for last send + while (!IsReadyToUpdate()) + { + yield(); + } + + _dmaState.SetSending(); + + // start next send + // + dma_channel_set_read_addr(_dmaChannel, _dataEditing, false); + dma_channel_start(_dmaChannel); // Start new transfer + + if (maintainBufferConsistency) + { + // copy editing to sending, + // this maintains the contract that "colors present before will + // be the same after", otherwise GetPixelColor will be inconsistent + memcpy(_dataSending, _dataEditing, _sizeData); + } + + // swap so the user can modify without affecting the async operation + std::swap(_dataSending, _dataEditing); + } + + bool AlwaysUpdate() + { + // this method requires update to be called only if changes to buffer + return false; + } + + uint8_t* getData() const + { + return _dataEditing; + }; + + size_t getDataSize() const + { + return _sizeData; + } + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + } + +private: + const size_t _sizeData; // Size of '_data*' buffers + const uint8_t _pin; // output pin number + const T_PIO_INSTANCE _pio; // holds instance for multi channel support + const uint8_t _mergedFifoCount; + + NeoRp2040DmaState _dmaState; // Latch timing reference + uint32_t _fifoCacheEmptyDelta; // delta between dma IRQ finished and PIO Fifo empty + + // Holds data stream which include LED color values and other settings as needed + uint8_t* _dataEditing; // exposed for get and set + uint8_t* _dataSending; // used for async send using DMA + + // holds pio state + int _sm; + int _dmaChannel; + + void construct() + { + _dataEditing = static_cast(malloc(_sizeData)); + // data cleared later in Begin() with a ClearTo(0) + + _dataSending = static_cast(malloc(_sizeData)); + // no need to initialize it, it gets overwritten on every send + } +}; + +// normal +typedef NeoRp2040x4MethodBase Rp2040x4NWs2811Method; +typedef NeoRp2040x4MethodBase Rp2040x4NWs2812xMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NWs2816Method; +typedef NeoRp2040x4MethodBase Rp2040x4NSk6812Method; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1814Method; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1829Method; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1914Method; +typedef NeoRp2040x4MethodBase Rp2040x4NApa106Method; +typedef NeoRp2040x4MethodBase Rp2040x4NTx1812Method; +typedef NeoRp2040x4MethodBase Rp2040x4NGs1903Method; +typedef NeoRp2040x4MethodBase Rp2040x4N800KbpsMethod; +typedef NeoRp2040x4MethodBase Rp2040x4N400KbpsMethod; + +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2811Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2812xMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2816Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Sk6812Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1814Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1829Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1914Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Apa106Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tx1812Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Gs1903Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0800KbpsMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0400KbpsMethod; + +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2811Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2812xMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2816Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Sk6812Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1814Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1829Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1914Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Apa106Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tx1812Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Gs1903Method; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1800KbpsMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1400KbpsMethod; + +// inverted +typedef NeoRp2040x4MethodBase Rp2040x4NWs2811InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NWs2812xInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NWs2816InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NSk6812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1814InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1829InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NTm1914InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NApa106InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NTx1812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4NGs1903InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4N800KbpsInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4N400KbpsInvertedMethod; + +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2811InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2812xInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Ws2816InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Sk6812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1814InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1829InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tm1914InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Apa106InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Tx1812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0Gs1903InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0800KbpsInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio0400KbpsInvertedMethod; + +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2811InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2812xInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Ws2816InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Sk6812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1814InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1829InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tm1914InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Apa106InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Tx1812InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1Gs1903InvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1800KbpsInvertedMethod; +typedef NeoRp2040x4MethodBase Rp2040x4Pio1400KbpsInvertedMethod; + +// PIO 1 method is the default method, and still x4 instances +typedef Rp2040x4Pio1Ws2812xMethod NeoWs2813Method; +typedef Rp2040x4Pio1Ws2812xMethod NeoWs2812xMethod; +typedef Rp2040x4Pio1800KbpsMethod NeoWs2812Method; +typedef Rp2040x4Pio1Ws2812xMethod NeoWs2811Method; +typedef Rp2040x4Pio1Ws2812xMethod NeoWs2816Method; +typedef Rp2040x4Pio1Sk6812Method NeoSk6812Method; +typedef Rp2040x4Pio1Tm1814Method NeoTm1814Method; +typedef Rp2040x4Pio1Tm1829Method NeoTm1829Method; +typedef Rp2040x4Pio1Tm1914Method NeoTm1914Method; +typedef Rp2040x4Pio1Sk6812Method NeoLc8812Method; +typedef Rp2040x4Pio1Apa106Method NeoApa106Method; +typedef Rp2040x4Pio1Tx1812Method NeoTx1812Method; +typedef Rp2040x4Pio1Gs1903Method NeoGs1903Method; + +typedef Rp2040x4Pio1Ws2812xMethod Neo800KbpsMethod; +typedef Rp2040x4Pio1400KbpsMethod Neo400KbpsMethod; + +typedef Rp2040x4Pio1Ws2812xInvertedMethod NeoWs2813InvertedMethod; +typedef Rp2040x4Pio1Ws2812xInvertedMethod NeoWs2812xInvertedMethod; +typedef Rp2040x4Pio1Ws2812xInvertedMethod NeoWs2811InvertedMethod; +typedef Rp2040x4Pio1800KbpsInvertedMethod NeoWs2812InvertedMethod; +typedef Rp2040x4Pio1Ws2812xInvertedMethod NeoWs2816InvertedMethod; +typedef Rp2040x4Pio1Sk6812InvertedMethod NeoSk6812InvertedMethod; +typedef Rp2040x4Pio1Tm1814InvertedMethod NeoTm1814InvertedMethod; +typedef Rp2040x4Pio1Tm1829InvertedMethod NeoTm1829InvertedMethod; +typedef Rp2040x4Pio1Tm1914InvertedMethod NeoTm1914InvertedMethod; +typedef Rp2040x4Pio1Sk6812InvertedMethod NeoLc8812InvertedMethod; +typedef Rp2040x4Pio1Apa106InvertedMethod NeoApa106InvertedMethod; +typedef Rp2040x4Pio1Tx1812InvertedMethod NeoTx1812InvertedMethod; +typedef Rp2040x4Pio1Gs1903InvertedMethod NeoGs1903InvertedMethod; + +typedef Rp2040x4Pio1Ws2812xInvertedMethod Neo800KbpsInvertedMethod; +typedef Rp2040x4Pio1400KbpsInvertedMethod Neo400KbpsInvertedMethod; + + +#endif