diff --git a/.github/workflows/push-master-pico.yml b/.github/workflows/push-master-pico.yml new file mode 100644 index 0000000..022322c --- /dev/null +++ b/.github/workflows/push-master-pico.yml @@ -0,0 +1,114 @@ +name: HyperSPI Pico CI Build + +on: [push] + +jobs: + +############################# +#### HyperSPI for Pico ###### +############################# + + HyperSpiPico: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./rp2040 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install GNU Arm Embedded Toolchain + uses: carlosperate/arm-none-eabi-gcc-action@v1 + with: + release: '12.2.Rel1' + + - name: Build packages + shell: bash + run: | + mkdir build + cd build + cmake .. + cmake --build . --config Release + + - uses: actions/upload-artifact@v3 + name: Upload artifacts (commit) + if: (startsWith(github.event.ref, 'refs/tags') != true) + with: + path: | + rp2040/HyperSerialPico/firmware/*.uf2 + + - uses: actions/upload-artifact@v3 + name: Upload artifacts (release) + if: startsWith(github.ref, 'refs/tags/') + with: + name: firmware-release + path: | + rp2040/HyperSerialPico/firmware/*.uf2 + + - name: Build packages for Adafruit Feather RP2040 Scorpio + shell: bash + run: | + cd build + rm *.* + rm ../HyperSerialPico/firmware/* + echo "Neopixel is using GPIO16(OUTPUT_DATA_PIN) on output 0." > ../HyperSerialPico/firmware/Firmwares_for_Adafruit_Feather_RP2040_Scorpio.txt + cmake -DOVERRIDE_DATA_PIN=16 -DCMAKE_BUILD_TYPE=Release .. + cmake --build . + zip -j ../HyperSerialPico/firmware/Adafruit_Feather_RP2040_Scorpio.zip ../HyperSerialPico/firmware/* + + - uses: actions/upload-artifact@v3 + name: Upload artifacts (Adafruit_Feather) + if: (startsWith(github.event.ref, 'refs/tags') != true) + with: + path: | + rp2040/HyperSerialPico/firmware/*.zip + + - uses: actions/upload-artifact@v3 + name: Upload artifacts (release for Adafruit_Feather) + if: startsWith(github.ref, 'refs/tags/') + with: + name: firmware-release + path: | + rp2040/HyperSerialPico/firmware/*.zip + +################################ +###### Publish Releases ######## +################################ + + publish: + name: Publish Releases + if: startsWith(github.event.ref, 'refs/tags') + needs: [HyperSpiPico] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + # generate environment variables + - name: Generate environment variables from version and tag + run: | + echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + echo "preRelease=false" >> $GITHUB_ENV + + # If version contains alpha or beta, mark draft release as pre-release + - name: Mark release as pre-release + if: contains(env.VERSION, 'alpha') || contains(env.VERSION, 'beta') + run: echo "preRelease=true" >> $GITHUB_ENV + + - uses: actions/download-artifact@v3 + with: + name: firmware-release + + # create draft release and upload artifacts + - name: Create draft release + uses: softprops/action-gh-release@v1 + with: + name: HyperSPI ${{ env.VERSION }} + tag_name: ${{ env.TAG }} + files: | + *.uf2 + *.zip + draft: true + prerelease: ${{ env.preRelease }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index 96e6f6e..a620eee 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -1,4 +1,4 @@ -name: HyperSPI CI Build +name: HyperSPI ESP32/8266 CI Build on: [push] diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a1e8b92 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rp2040/HyperSerialPico"] + path = rp2040/HyperSerialPico + url = https://github.com/awawa-dev/HyperSerialPico diff --git a/README.md b/README.md index c5fdb51..b4f2685 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # HyperSPI SPI bridge for AWA protocol to control a LED strip from HyperHDR. Diagnostic and performance data available at the serial port output [read more](#performancedebug-output). -Rpi acts as a master, ESP8266/ESP32/ESP32-S2 is in slave mode. +Raspberry Pi acts as a master, ESP8266/ESP32/ESP32-S2/rp2040(Raspberry Pi Pico) is in slave mode. -| LED strip / Device | ESP8266
(limited performance) | ESP32 / ESP32-S2 mini | -|--------------------------------|:-------:|:-----------:| -| SK6812 cold white | yes | yes | -| SK6812 neutral white | yes | yes | -| WS281x | yes | yes | +| LED strip / Device | rp2040 / Pico | ESP8266
(limited performance) | ESP32 / ESP32-S2 mini +|--------------------------------|:-------:|:-----------:|:-------:| +| SK6812 cold white | yes | yes | yes | +| SK6812 neutral white | yes | yes | yes | +| WS281x | yes | yes | yes | # Why this project was created? @@ -23,9 +23,19 @@ Rpi acts as a master, ESP8266/ESP32/ESP32-S2 is in slave mode. If you are using an ESP board compatible with the Wemos board (ESP8266 Wemos D1/pro, ESP32 MH-ET Live, ESP32-S2 lolin mini), the SPI connection uses the same pinout location on the ESP board! The pin positions of the LED output may vary. Cables (including ground) should not exceed 15-20cm or it may be necessary to lower the SPI speed. +The photos below use the same home-made adapter throughout, so you can see a repeating pattern and the cable colors should help you locate the correct pins. However, always consult the GPIO diagram for your boards to confirm that you have connected the cables correctly, because if you make a mistake and connect to the 5V GPIO line, it may damage both devices. + +As you can also notice, the pinout of the SPI0 interface is identical for the entire Raspberry Pi SBC family: 3, 4, 5, Zero 2W, etc. + - + + + + + + + @@ -52,13 +62,16 @@ If you are using an ESP board compatible with the Wemos board (ESP8266 Wemos D1/ ## Default pinout (can be changed for esp32 and esp32-s2) -| PINOUT | ESP8266 | ESP32 | ESP32-S2 lolin mini| -|-------------|-----------|-----------|-----------| -| Clock (SCK) | GPIO 14 | GPIO 18 | GPIO 7 | -| Data (MOSI) | GPIO 13 | GPIO 23 | GPIO 11 | -| SPI Chip Select(e.g. CE0) | not used | GPIO 5 | GPIO 12 | -| GROUND | mandatory | mandatory | mandatory | -| LED output | GPIO 2 | GPIO 2 | GPIO 2 | +| PINOUT | ESP8266 | ESP32 | ESP32-S2 lolin mini| Pico (rp2040) +|-------------|-----------|-----------|-----------|-----------| +| Clock (SCK) | GPIO 14 | GPIO 18 | GPIO 7 | GPIO 2 | +| Data (MOSI) | GPIO 13 | GPIO 23 | GPIO 11 | GPIO 4 | +| SPI Chip Select(e.g. CE0) | not used | GPIO 5 | GPIO 12 | GPIO 5 | +| GROUND | mandatory | mandatory | mandatory | mandatory | +| LED output | GPIO 2 | GPIO 2 | GPIO 2 | GPIO 14 | + +> [!CAUTION] +> The ground connection between both GPIOs is as important as the other SPI data connections. The ground cable should be of a similar length as them and run directly next to them. # Flashing the firmware diff --git a/rp2040/.gitignore b/rp2040/.gitignore new file mode 100644 index 0000000..1c26e7f --- /dev/null +++ b/rp2040/.gitignore @@ -0,0 +1,2 @@ +build +vscode diff --git a/rp2040/CMakeLists.txt b/rp2040/CMakeLists.txt new file mode 100644 index 0000000..54e535f --- /dev/null +++ b/rp2040/CMakeLists.txt @@ -0,0 +1,42 @@ +set(CMAKE_SYSTEM_NAME Generic) +# User configuration section starts here + +# Some boards, such as the first Adafruit revisions, may have trouble booting properly +# due to bad componets used in the design. +# Turn this setting to ON if your rp2040 is not detected after firmware upload and reset +set(BOOT_WORKAROUND ON) + +# Default output data pin for the non-SPI LED strips (only for sk6812/ws2812b) +set(OUTPUT_DATA_PIN 14) + +# Use multi-segment, starting index of second led strip or OFF to disable +set(SECOND_SEGMENT_INDEX OFF) + +# If multi-segment is used and it's reversed, set this option to ON to enable reversing +set(SECOND_SEGMENT_REVERSED OFF) + +# HyperSPI communication interface with Raspberry Pi +set(OUTPUT_SPI_DATA_PIN 4) +set(OUTPUT_SPI_CLOCK_PIN 2) +set(OUTPUT_SPI_CHIP_SELECT 5) +set(OUTPUT_SPI_INTERFACE spi0) + +# User configuration section ends here +# Usually you don't need to change anything below this section +cmake_minimum_required(VERSION 3.13) + +set(PICO_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/HyperSerialPico/sdk/pico) +set(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/HyperSerialPico/sdk/freertos) +include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) +include(${FREERTOS_KERNEL_PATH}/portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake) + +project(HyperSPI_Pico C CXX ASM) +pico_sdk_init() + +set(PICO_PROGRAM_MAIN_ENTRY "../hyperspi.cpp") +set(DISABLE_SPI_LEDS ON) +add_definitions(-DSPI_INTERFACE=${OUTPUT_SPI_INTERFACE} + -DSPI_DATA_PIN=${OUTPUT_SPI_DATA_PIN} + -DSPI_CLOCK_PIN=${OUTPUT_SPI_CLOCK_PIN} + -DSPI_CHIP_SELECT=${OUTPUT_SPI_CHIP_SELECT}) +add_subdirectory(HyperSerialPico) diff --git a/rp2040/HyperSerialPico b/rp2040/HyperSerialPico new file mode 160000 index 0000000..5bf4754 --- /dev/null +++ b/rp2040/HyperSerialPico @@ -0,0 +1 @@ +Subproject commit 5bf4754b73110772325dabcc1590c45cdbeb918f diff --git a/rp2040/hyperspi.cpp b/rp2040/hyperspi.cpp new file mode 100644 index 0000000..513416a --- /dev/null +++ b/rp2040/hyperspi.cpp @@ -0,0 +1,198 @@ +/* hyperspi.cpp +* +* MIT License +* +* Copyright (c) 2023 awawa-dev +* +* https://github.com/awawa-dev/HyperSPI +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#define TUD_OPT_HIGH_SPEED + + +#include "FreeRTOS.h" +#include "task.h" +#include +#include +#include "pico/stdio/driver.h" +#include "pico/stdlib.h" +#include "pico/stdio.h" +#include "pico/stdio_usb.h" +#include "pico/multicore.h" +#include "pico/sem.h" +#include "leds.h" + +#define SPI_FRAME_SIZE 1536 +#define SPI_SPEED 20833333 + +uint8_t spiBuffer[SPI_FRAME_SIZE] {}; + +/////////////////////////////////////////////////////////////////////////// +// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE CmakeList.txt // +/////////////////////////////////////////////////////////////////////////// + +#define _STR(x) #x +#define _XSTR(x) _STR(x) +#define VAR_NAME_VALUE(var) #var " = " _XSTR(var) +#define _XSTR2(x,y) _STR(x) _STR(y) +#define VAR_NAME_VALUE2(var) #var " = " _XSTR2(var) + +#if defined(BOOT_WORKAROUND) && defined(PICO_XOSC_STARTUP_DELAY_MULTIPLIER) + #pragma message("Enabling boot workaround") + #pragma message(VAR_NAME_VALUE(PICO_XOSC_STARTUP_DELAY_MULTIPLIER)) +#endif + + +#ifdef NEOPIXEL_RGBW + #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGBW)) +#endif +#ifdef NEOPIXEL_RGB + #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGB)) +#endif +#ifdef COLD_WHITE + #pragma message(VAR_NAME_VALUE(COLD_WHITE)) +#endif + +#ifdef NEOPIXEL_RGBW + #define LED_DRIVER sk6812 +#elif NEOPIXEL_RGB + #define LED_DRIVER ws2812 +#endif + +#pragma message(VAR_NAME_VALUE(DATA_PIN)) +#pragma message(VAR_NAME_VALUE(SPI_INTERFACE)) +#pragma message(VAR_NAME_VALUE(SPI_DATA_PIN)) +#pragma message(VAR_NAME_VALUE(SPI_CLOCK_PIN)) +#pragma message(VAR_NAME_VALUE(SPI_CHIP_SELECT)) + +#if defined(SECOND_SEGMENT_START_INDEX) + #pragma message("Using parallel mode for segments") + + #ifdef NEOPIXEL_RGBW + #undef LED_DRIVER + #define LED_DRIVER sk6812p + #define LED_DRIVER2 sk6812p + #elif NEOPIXEL_RGB + #undef LED_DRIVER + #define LED_DRIVER ws2812p + #define LED_DRIVER2 ws2812p + #else + #error "Parallel mode is unsupportd for selected LEDs configuration" + #endif + + #pragma message(VAR_NAME_VALUE(LED_DRIVER)) + #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_START_INDEX)) + #pragma message(VAR_NAME_VALUE(LED_DRIVER2)) + #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_REVERSED)) +#else + #pragma message(VAR_NAME_VALUE(LED_DRIVER)) + + typedef LedDriver LED_DRIVER2; +#endif + +///////////////////////////////////////////////////////////////////////// +#define delay(x) sleep_ms(x) +#define yield() busy_wait_us(100) +#define millis xTaskGetTickCount + +#include "main.h" + +static void core1() +{ + for( ;; ) + { + if (sem_acquire_timeout_us(&base.receiverSemaphore, portMAX_DELAY)) + { + processData(); + } + } +} + +static uint initSpi(uint baudrate, spi_inst_t* _spi, uint32_t spiMosipin, uint32_t spiClockpin, uint32_t spiSelectPin) +{ + uint selectedSpeed = spi_init(_spi, baudrate); + printf("Using baudrate: %i\n", selectedSpeed); + spi_set_format(_spi, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + hw_set_bits(&spi_get_hw(_spi)->dmacr, SPI_SSPDMACR_TXDMAE_BITS | SPI_SSPDMACR_RXDMAE_BITS); + spi_set_slave(_spi, true); + hw_set_bits(&spi_get_hw(_spi)->cr1, SPI_SSPCR1_SSE_BITS); + gpio_set_function(spiClockpin, GPIO_FUNC_SPI); + gpio_set_function(spiMosipin, GPIO_FUNC_SPI); + gpio_set_function(spiSelectPin, GPIO_FUNC_SPI); + gpio_set_function(3, GPIO_FUNC_SPI); + bi_decl(bi_4pins_with_func(spiMosipin, 3, spiClockpin, spiSelectPin, GPIO_FUNC_SPI)); + + uint dmaChannelNumber = dma_claim_unused_channel(true); + dma_channel_config channelConfig = dma_channel_get_default_config(dmaChannelNumber); + channel_config_set_transfer_data_size(&channelConfig, DMA_SIZE_8); + channel_config_set_dreq(&channelConfig, spi_get_dreq(_spi, false)); + channel_config_set_read_increment(&channelConfig, false); + channel_config_set_write_increment(&channelConfig, true); + dma_channel_configure(dmaChannelNumber, &channelConfig, + spiBuffer, + &spi_get_hw(_spi)->dr, + sizeof(spiBuffer), + false); + return dmaChannelNumber; +} + +static void core0( void *pvParameters ) +{ + uint dmaChannelNumber = initSpi(SPI_SPEED, SPI_INTERFACE, SPI_DATA_PIN, SPI_CLOCK_PIN, SPI_CHIP_SELECT); + + while(true) + { + dma_channel_set_write_addr(dmaChannelNumber, spiBuffer, true); + dma_channel_wait_for_finish_blocking(dmaChannelNumber); + + int remains = SPI_FRAME_SIZE; + int wanted, received; + do + { + wanted = std::min(MAX_BUFFER - base.queueEnd, MAX_BUFFER - 1); + received = std::min(remains, wanted); + memcpy((void*)&(base.buffer[base.queueEnd]), &(spiBuffer[SPI_FRAME_SIZE - remains]), received); + base.queueEnd = (base.queueEnd + received) % (MAX_BUFFER); + remains -= received; + }while(remains); + + sem_release(&base.receiverSemaphore); + } +} + +int main(void) +{ + stdio_init_all(); + + sem_init(&base.receiverSemaphore, 0, 1); + + multicore_launch_core1(core1); + + xTaskCreate(core0, + "HyperSPI:core0", + configMINIMAL_STACK_SIZE * 2, + NULL, + (configMAX_PRIORITIES - 1), + &base.processSerialHandle); + + vTaskStartScheduler(); + panic_unsupported(); +}

See how easy it is to connect ESP to Raspberry Pi using SPI

See how easy it is to connect Raspberry Pi Pico (rp2040) to Raspberry Pi 5 using SPI

or if you prefer ESP32/ESP32-S2/Esp8266